Skip to content

Commit 77ccbe4

Browse files
Get real path to layer & driver binaries
Queries the real path of loaded layers and drivers so that logging messages contain the exact path used, instead of the path the loader gave to dlopen/ LoadLibrary. This ensures that logs are as accurate as can be, as the dynamic linker may not respect the passed in library (due to environment variables or other mechanisms which change which binary is loaded). This requires normalizing paths before comparison, as the path given to dlopen/LoadLibrary may represent the same location as the queried path, but might not be identical strings. Normalization is done using the realpath() function on platforms that support it, and a fallback implementation is used elsewhere. The implementation only removes extra directory separators ("//"), extra current directory specifiers ("/./"), and removes relative to parent directory specifiers ("/foo/../").
1 parent c8ac60e commit 77ccbe4

File tree

2 files changed

+286
-0
lines changed

2 files changed

+286
-0
lines changed

CMakeLists.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,19 @@ if (NOT (WIN32 OR APPLE))
258258
endif()
259259
endif()
260260

261+
if (NOT (WIN32))
262+
# Check for the existance of the realpath() command
263+
include(CheckFunctionExists)
264+
265+
check_function_exists(realpath HAVE_REALPATH)
266+
267+
if (HAVE_REALPATH)
268+
target_compile_definitions(loader_common_options INTERFACE HAVE_REALPATH)
269+
else()
270+
message(INFO "Platform support for realpath() is missing. Using fallback path normalization implementation.")
271+
endif()
272+
endif()
273+
261274
option(LOADER_CODEGEN "Enable vulkan loader code generation")
262275
if(LOADER_CODEGEN)
263276
find_package(Python3 REQUIRED)

loader/loader.c

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,269 @@ void free_string_list(const struct loader_instance *inst, struct loader_string_l
387387
memset(string_list, 0, sizeof(struct loader_string_list));
388388
}
389389

390+
// In place modify the passed in path to do the following:
391+
// If HAVE_REALPATH is defined, then this simply calls realpath() so its behavior is defined by realpath()
392+
// Else:
393+
// * Windows-only: Replace forward slashes with backwards slashes (platform correct directory separator)
394+
// * Replace contiguous directory separators with a single directory separator
395+
// * Replace "/./" separator with "/" (where / is the platform correct directory separator)
396+
// * Replace "/<directory_name>/../" with just "/" (where / is the platform correct directory separator)
397+
VkResult normalize_path(const struct loader_instance *inst, char **passed_in_path) {
398+
// passed_in_path doesn't point to anything, can't modify inplace so just return
399+
if (passed_in_path == NULL) {
400+
return VK_SUCCESS;
401+
}
402+
403+
// POSIX systems has the realpath() function to do this for us, fallback to basic normalization on other platforms
404+
#if defined(HAVE_REALPATH)
405+
char *path = loader_instance_heap_calloc(inst, PATH_MAX, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE);
406+
if (NULL == path) {
407+
return VK_ERROR_OUT_OF_HOST_MEMORY;
408+
}
409+
char *ret = realpath(*passed_in_path, path);
410+
if (NULL == ret) {
411+
// error path
412+
int error_code = errno;
413+
loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0,
414+
"normalize_path: Call to realpath() failed with error code %d when given the path %s", error_code,
415+
*passed_in_path);
416+
loader_instance_heap_free(inst, path);
417+
} else {
418+
// Replace string pointed to by passed_in_path with the one given to us by realpath()
419+
loader_instance_heap_free(inst, *passed_in_path);
420+
*passed_in_path = path;
421+
}
422+
return VK_SUCCESS;
423+
424+
// Windows has GetFullPathName which does essentially the same thing. Note that we call GetFullPathNameA because the path has
425+
// already been converted from the wide char format when it was initially gotten
426+
#elif defined(WIN32)
427+
VkResult res = VK_SUCCESS;
428+
DWORD path_len = (DWORD)strlen(*passed_in_path) + 1;
429+
char *path = loader_instance_heap_calloc(inst, (size_t)path_len, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE);
430+
if (NULL == path) {
431+
res = VK_ERROR_OUT_OF_HOST_MEMORY;
432+
goto out;
433+
}
434+
DWORD actual_len = GetFullPathNameA(*passed_in_path, path_len, path, NULL);
435+
if (actual_len == 0) {
436+
size_t last_error = (size_t)GetLastError();
437+
loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0,
438+
"normalize_path: Call to GetFullPathNameA() failed with error code %llu when given the path %s", last_error,
439+
*passed_in_path);
440+
res = VK_ERROR_INITIALIZATION_FAILED;
441+
goto out;
442+
}
443+
444+
// If path_len wasn't big enough, need to realloc and call again
445+
// actual_len doesn't include null terminator
446+
if (actual_len + 1 > path_len) {
447+
path = loader_instance_heap_realloc(inst, path, path_len, actual_len + 1, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE);
448+
if (NULL == path) {
449+
res = VK_ERROR_OUT_OF_HOST_MEMORY;
450+
goto out;
451+
}
452+
// store the updated allocation size (sans null terminator)
453+
path_len = actual_len + 1;
454+
455+
actual_len = GetFullPathNameA(*passed_in_path, path_len, path, NULL);
456+
if (actual_len == 0) {
457+
size_t last_error = (size_t)GetLastError();
458+
loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0,
459+
"normalize_path: Call to GetFullPathNameA() failed with error code %llu when given the path %s", last_error,
460+
*passed_in_path);
461+
res = VK_ERROR_INITIALIZATION_FAILED;
462+
goto out;
463+
// actual_len doesn't include null terminator
464+
} else if (actual_len + 1 != path_len) {
465+
loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0,
466+
"normalize_path: Call to GetFullPathNameA() with too small of a buffer when given the path %s after the "
467+
"initial call to GetFullPathNameA() failed for the same reason. Buffer size is %llu, actual size is %llu",
468+
*passed_in_path, (size_t)path_len, (size_t)actual_len);
469+
res = VK_ERROR_INITIALIZATION_FAILED;
470+
goto out;
471+
}
472+
}
473+
// Replace string pointed to by passed_in_path with the one given to us by realpath()
474+
loader_instance_heap_free(inst, *passed_in_path);
475+
*passed_in_path = path;
476+
out:
477+
if (VK_SUCCESS != res) {
478+
if (NULL != path) {
479+
loader_instance_heap_free(inst, path);
480+
}
481+
}
482+
return res;
483+
484+
#else
485+
(void)inst;
486+
char *path = *passed_in_path;
487+
size_t path_len = strlen(path) + 1;
488+
489+
size_t output_index = 0;
490+
// Iterate through the string up to the last character, excluding the null terminator
491+
for (size_t i = 0; i < path_len - 1; i++) {
492+
if (i + 1 < path_len && path[i] == DIRECTORY_SYMBOL && path[i + 1] == DIRECTORY_SYMBOL) {
493+
continue;
494+
} else if (i + 2 < path_len && path[i] == DIRECTORY_SYMBOL && path[i + 1] == '.' && path[i + 2] == DIRECTORY_SYMBOL) {
495+
i += 1;
496+
} else {
497+
path[output_index++] = path[i];
498+
}
499+
}
500+
// Add null terminator and set the new length
501+
path[output_index++] = '\0';
502+
path_len = output_index;
503+
504+
// Loop while there are still ..'s in the path. Easiest implementation resolves them one by one, which requires quadratic
505+
// iteration through the string
506+
char *directory_stack = loader_stack_alloc(path_len);
507+
if (directory_stack == NULL) {
508+
return VK_ERROR_OUT_OF_HOST_MEMORY;
509+
}
510+
511+
size_t top_of_stack = 0;
512+
513+
// Iterate through the path, push characters as we see them, if we find a "..", pop off the top of the directory stack until the
514+
// current directory is gone.
515+
for (size_t i = 0; i < path_len - 1; i++) {
516+
// if the next part of path is "/../" we need to pop from the directory stack until we hit the previous directory symbol.
517+
if (i + 3 < path_len && path[i] == DIRECTORY_SYMBOL && path[i + 1] == '.' && path[i + 2] == '.' && path_len &&
518+
path[i + 3] == DIRECTORY_SYMBOL) {
519+
// Pop until we hit the next directory symbol in the stack
520+
while (top_of_stack > 0 && directory_stack[top_of_stack - 1] != DIRECTORY_SYMBOL) {
521+
top_of_stack--;
522+
directory_stack[top_of_stack] = '\0';
523+
}
524+
// Amend the directory stack so that the top isn't a directory separator
525+
if (top_of_stack > 0 && directory_stack[top_of_stack - 1] == DIRECTORY_SYMBOL) {
526+
top_of_stack--;
527+
directory_stack[top_of_stack] = '\0';
528+
}
529+
i += 2; // need to skip the second dot & directory separator
530+
} else {
531+
// push characters as we come across them
532+
directory_stack[top_of_stack++] = path[i];
533+
}
534+
}
535+
536+
// Can't forget the null terminator
537+
directory_stack[top_of_stack] = '\0';
538+
539+
// We now have the path without any ..'s, so just copy it out
540+
loader_strncpy(path, path_len, directory_stack, path_len);
541+
path[top_of_stack] = '\0';
542+
path_len = top_of_stack + 1;
543+
544+
return VK_SUCCESS;
545+
#endif
546+
}
547+
548+
// Queries the path to the library that lib_handle & gipa are assoicated with, allocating a string to hold it and returning it in
549+
// out_path
550+
VkResult get_library_path_of_dl_handle(const struct loader_instance *inst, loader_platform_dl_handle lib_handle,
551+
PFN_vkGetInstanceProcAddr gipa, char **out_path) {
552+
#if COMMON_UNIX_PLATFORMS
553+
(void)lib_handle;
554+
Dl_info dl_info = {0};
555+
if (dladdr(gipa, &dl_info) != 0 && NULL != dl_info.dli_fname) {
556+
return loader_copy_to_new_str(inst, dl_info.dli_fname, out_path);
557+
}
558+
return VK_SUCCESS;
559+
560+
#elif defined(WIN32)
561+
(void)gipa;
562+
size_t module_file_name_len = MAX_PATH; // start with reasonably large buffer
563+
wchar_t *buffer_utf16 = (wchar_t *)loader_stack_alloc(module_file_name_len * sizeof(wchar_t));
564+
DWORD ret = GetModuleFileNameW(lib_handle, buffer_utf16, (DWORD)module_file_name_len);
565+
if (ret == 0) {
566+
return VK_SUCCESS;
567+
}
568+
while (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
569+
module_file_name_len *= 2;
570+
buffer_utf16 = (wchar_t *)loader_stack_alloc(module_file_name_len * sizeof(wchar_t));
571+
ret = GetModuleFileNameW(lib_handle, buffer_utf16, (DWORD)module_file_name_len);
572+
if (ret == 0) {
573+
return VK_SUCCESS;
574+
}
575+
}
576+
577+
// Need to convert from utf16 to utf8
578+
int buffer_utf8_size = WideCharToMultiByte(CP_UTF8, 0, buffer_utf16, -1, NULL, 0, NULL, NULL);
579+
if (buffer_utf8_size <= 0) {
580+
return VK_SUCCESS;
581+
}
582+
583+
char *buffer_utf8 = loader_instance_heap_calloc(inst, buffer_utf8_size, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE);
584+
if (NULL == buffer_utf8) {
585+
return VK_ERROR_OUT_OF_HOST_MEMORY;
586+
}
587+
if (WideCharToMultiByte(CP_UTF8, 0, buffer_utf16, -1, buffer_utf8, buffer_utf8_size, NULL, NULL) != buffer_utf8_size) {
588+
return VK_SUCCESS;
589+
}
590+
591+
// Successfully got the 'real' path to the library.
592+
*out_path = buffer_utf8;
593+
return VK_SUCCESS;
594+
595+
#else
596+
// Do nothing, platform doesn't handle getting the path to a library
597+
#endif
598+
}
599+
600+
// Find and replace the path that was loaded using the lib_name path with the real path of the library. This is done to provide
601+
// accurate logging info for users.
602+
// This function prints a warning if there is a mismatch between the lib_name path and the real path.
603+
VkResult fixup_library_binary_path(const struct loader_instance *inst, char **lib_name, loader_platform_dl_handle lib_handle,
604+
PFN_vkGetInstanceProcAddr gipa) {
605+
if (lib_name == NULL) {
606+
// do nothing as we got an invalid lib_path pointer
607+
return VK_SUCCESS;
608+
}
609+
610+
bool system_path = true;
611+
size_t lib_name_len = strlen(*lib_name) + 1;
612+
for (size_t i = 0; i < lib_name_len; i++) {
613+
if ((*lib_name)[i] == DIRECTORY_SYMBOL) {
614+
system_path = false;
615+
break;
616+
}
617+
}
618+
619+
if (!system_path) {
620+
// The OS path we get for a binary is normalized, so we need to normalize the path passed into LoadLibrary/dlopen so that
621+
// mismatches are minimized. EG, do not warn when we give dlopen/LoadLibrary "/foo/./bar" but get "/foo/bar" as the loaded
622+
// binary path from the OS.
623+
VkResult res = normalize_path(inst, lib_name);
624+
if (res == VK_ERROR_OUT_OF_HOST_MEMORY) {
625+
return res;
626+
}
627+
}
628+
char *os_determined_lib_name = NULL;
629+
VkResult res = get_library_path_of_dl_handle(inst, lib_handle, gipa, &os_determined_lib_name);
630+
if (res == VK_ERROR_OUT_OF_HOST_MEMORY) {
631+
return res;
632+
}
633+
634+
if (NULL != os_determined_lib_name) {
635+
if (0 != strcmp(os_determined_lib_name, *lib_name)) {
636+
// Paths do not match, so we need to replace lib_name with the real path
637+
if (!system_path) {
638+
// Only warn when the library_path is relative or absolute, not system. EG lib_name had no directory separators
639+
loader_log(inst, VULKAN_LOADER_WARN_BIT | VULKAN_LOADER_LAYER_BIT, 0,
640+
"Path to given binary %s was found to differ from OS loaded path %s", *lib_name, os_determined_lib_name);
641+
}
642+
loader_instance_heap_free(inst, *lib_name);
643+
*lib_name = os_determined_lib_name;
644+
} else {
645+
// Paths match, so just need to free temporary allocation
646+
loader_instance_heap_free(inst, os_determined_lib_name);
647+
}
648+
}
649+
650+
return res;
651+
}
652+
390653
// Given string of three part form "maj.min.pat" convert to a vulkan version number.
391654
// Also can understand four part form "variant.major.minor.patch" if provided.
392655
uint32_t loader_parse_version_string(char *vers_str) {
@@ -1996,6 +2259,11 @@ VkResult loader_scanned_icd_add(const struct loader_instance *inst, struct loade
19962259
}
19972260
icd_tramp_list->count++;
19982261

2262+
// Uses OS calls to find the 'true' path to the binary, for more accurate logging later on.
2263+
res = fixup_library_binary_path(inst, &(new_scanned_icd->lib_name), new_scanned_icd->handle, fp_get_proc_addr);
2264+
if (res == VK_ERROR_OUT_OF_HOST_MEMORY) {
2265+
goto out;
2266+
}
19992267
out:
20002268
if (res != VK_SUCCESS) {
20012269
if (NULL != handle) {
@@ -4807,6 +5075,11 @@ VkResult loader_create_instance_chain(const VkInstanceCreateInfo *pCreateInfo, c
48075075

48085076
chain_info.u.pLayerInfo = &layer_instance_link_info[num_activated_layers];
48095077

5078+
res = fixup_library_binary_path(inst, &(layer_prop->lib_name), layer_prop->lib_handle, cur_gipa);
5079+
if (res == VK_ERROR_OUT_OF_HOST_MEMORY) {
5080+
return res;
5081+
}
5082+
48105083
activated_layers[num_activated_layers].name = layer_prop->info.layerName;
48115084
activated_layers[num_activated_layers].manifest = layer_prop->manifest_file_name;
48125085
activated_layers[num_activated_layers].library = layer_prop->lib_name;

0 commit comments

Comments
 (0)