Add tests for system installed binaries
authorCharles Giessen <charles@lunarg.com>
Thu, 11 May 2023 18:52:50 +0000 (12:52 -0600)
committerCharles Giessen <46324611+charles-lunarg@users.noreply.github.com>
Mon, 29 May 2023 23:45:08 +0000 (17:45 -0600)
When library_path in a manifest doesn't contain any directory separators, it
signals to the loader that the library name should be passed directly to
dlopen/LoadLibrary and for those functions to use the dynamic linkers default
search paths. This commit adds tests that do that, as well as sets up the
required functionality to implement this behavior in the test framework. For
Unix systems, this involves redirecting dlopen so that it loads the correct
binary - rather than relying on dlopen to find it for us. For Windows, we
use AddDllDirectory to modify LoadLibrary's search path as well as
SetDefaultDllDirectory to change LoadLibrary's behavior to do what we want.

tests/framework/shim/shim.h
tests/framework/shim/shim_common.cpp
tests/framework/shim/unix_shim.cpp
tests/framework/test_environment.cpp
tests/framework/test_environment.h
tests/framework/test_util.h
tests/loader_regression_tests.cpp

index b0b9f05a2e77f2295d37e308ad499648acae2417..b58de03b64d1cdb72c4e8b1230ed8b318e2590a8 100644 (file)
@@ -196,7 +196,11 @@ struct PlatformShim {
     bool is_known_path(fs::path const& path);
     void remove_known_path(fs::path const& path);
 
+    void redirect_dlopen_name(fs::path const& filename, fs::path const& actual_path);
+    bool is_dlopen_redirect_name(fs::path const& filename);
+
     std::unordered_map<std::string, fs::path> redirection_map;
+    std::unordered_map<std::string, fs::path> dlopen_redirection_map;
     std::unordered_set<std::string> known_path_set;
 
     void set_elevated_privilege(bool elev) { use_fake_elevation = elev; }
index 7dbfa3fcf4bec6ca8804670c1936b21374dde33d..15a83c879424245a5b03dabbc9449b3c45d901b7 100644 (file)
@@ -229,4 +229,10 @@ void PlatformShim::set_fake_path(ManifestCategory category, fs::path const& path
     redirect_path(fs::path(SYSCONFDIR) / "vulkan" / category_path_name(category), path);
 }
 
+void PlatformShim::redirect_dlopen_name(fs::path const& filename, fs::path const& actual_path) {
+    dlopen_redirection_map[filename.str()] = actual_path;
+}
+
+bool PlatformShim::is_dlopen_redirect_name(fs::path const& filename) { return dlopen_redirection_map.count(filename.str()) == 1; }
+
 #endif
index c3c8115a51332dc7052b6cc8b3e356563c85c6b9..d2fec892135028ec23df6f7c9a0257a6170dfee5 100644 (file)
@@ -54,6 +54,7 @@ FRAMEWORK_EXPORT PlatformShim* get_platform_shim(std::vector<fs::FolderManager>*
 #define CLOSEDIR_FUNC_NAME closedir
 #define ACCESS_FUNC_NAME access
 #define FOPEN_FUNC_NAME fopen
+#define DLOPEN_FUNC_NAME dlopen
 #define GETEUID_FUNC_NAME geteuid
 #define GETEGID_FUNC_NAME getegid
 #if defined(HAVE_SECURE_GETENV)
@@ -68,6 +69,7 @@ FRAMEWORK_EXPORT PlatformShim* get_platform_shim(std::vector<fs::FolderManager>*
 #define CLOSEDIR_FUNC_NAME my_closedir
 #define ACCESS_FUNC_NAME my_access
 #define FOPEN_FUNC_NAME my_fopen
+#define DLOPEN_FUNC_NAME my_dlopen
 #define GETEUID_FUNC_NAME my_geteuid
 #define GETEGID_FUNC_NAME my_getegid
 #if defined(HAVE_SECURE_GETENV)
@@ -83,6 +85,7 @@ using PFN_READDIR = struct dirent* (*)(DIR* dir_stream);
 using PFN_CLOSEDIR = int (*)(DIR* dir_stream);
 using PFN_ACCESS = int (*)(const char* pathname, int mode);
 using PFN_FOPEN = FILE* (*)(const char* filename, const char* mode);
+using PFN_DLOPEN = void* (*)(const char* in_filename, int flags);
 using PFN_GETEUID = uid_t (*)(void);
 using PFN_GETEGID = gid_t (*)(void);
 #if defined(HAVE_SECURE_GETENV) || defined(HAVE___SECURE_GETENV)
@@ -95,6 +98,7 @@ using PFN_SEC_GETENV = char* (*)(const char* name);
 #define real_closedir closedir
 #define real_access access
 #define real_fopen fopen
+#define real_dlopen dlopen
 #define real_geteuid geteuid
 #define real_getegid getegid
 #if defined(HAVE_SECURE_GETENV)
@@ -109,6 +113,7 @@ PFN_READDIR real_readdir = nullptr;
 PFN_CLOSEDIR real_closedir = nullptr;
 PFN_ACCESS real_access = nullptr;
 PFN_FOPEN real_fopen = nullptr;
+PFN_DLOPEN real_dlopen = nullptr;
 PFN_GETEUID real_geteuid = nullptr;
 PFN_GETEGID real_getegid = nullptr;
 #if defined(HAVE_SECURE_GETENV)
@@ -241,6 +246,17 @@ FRAMEWORK_EXPORT FILE* FOPEN_FUNC_NAME(const char* in_filename, const char* mode
     return f_ptr;
 }
 
+FRAMEWORK_EXPORT void* DLOPEN_FUNC_NAME(const char* in_filename, int flags) {
+#if !defined(__APPLE__)
+    if (!real_dlopen) real_dlopen = (PFN_DLOPEN)dlsym(RTLD_NEXT, "dlopen");
+#endif
+
+    if (platform_shim.is_dlopen_redirect_name(in_filename)) {
+        return real_dlopen(platform_shim.dlopen_redirection_map[in_filename].c_str(), flags);
+    }
+    return real_dlopen(in_filename, flags);
+}
+
 FRAMEWORK_EXPORT uid_t GETEUID_FUNC_NAME(void) {
 #if !defined(__APPLE__)
     if (!real_geteuid) real_geteuid = (PFN_GETEUID)dlsym(RTLD_NEXT, "geteuid");
@@ -333,6 +349,7 @@ __attribute__((used)) static Interposer _interpose_opendir MACOS_ATTRIB = {VOIDP
 __attribute__((used)) static Interposer _interpose_closedir MACOS_ATTRIB = {VOIDP_CAST(my_closedir), VOIDP_CAST(closedir)};
 __attribute__((used)) static Interposer _interpose_access MACOS_ATTRIB = {VOIDP_CAST(my_access), VOIDP_CAST(access)};
 __attribute__((used)) static Interposer _interpose_fopen MACOS_ATTRIB = {VOIDP_CAST(my_fopen), VOIDP_CAST(fopen)};
+__attribute__((used)) static Interposer _interpose_dlopen MACOS_ATTRIB = {VOIDP_CAST(my_dlopen), VOIDP_CAST(dlopen)};
 __attribute__((used)) static Interposer _interpose_euid MACOS_ATTRIB = {VOIDP_CAST(my_geteuid), VOIDP_CAST(geteuid)};
 __attribute__((used)) static Interposer _interpose_egid MACOS_ATTRIB = {VOIDP_CAST(my_getegid), VOIDP_CAST(getegid)};
 #if defined(HAVE_SECURE_GETENV)
index c5d76c1d79b614d429fcedc633adb4704d01e0a6..3ffce5cba9b1aeab25f510bcb29366caef41ac52 100644 (file)
@@ -436,9 +436,21 @@ TestICDHandle& FrameworkEnvironment::add_icd(TestICDDetails icd_details) noexcep
 
         auto new_driver_location = folder->copy_file(icd_details.icd_manifest.lib_path, new_driver_name.str());
 
+#if COMMON_UNIX_PLATFORMS
+        if (icd_details.use_dynamic_library_default_search_paths) {
+            platform_shim->redirect_dlopen_name(new_driver_name, new_driver_location);
+        }
+#endif
+#if defined(WIN32)
+        if (icd_details.use_dynamic_library_default_search_paths) {
+            SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_USER_DIRS);
+            AddDllDirectory(conver_str_to_wstr(new_driver_location.parent_path().str()).c_str());
+        }
+#endif
         icds.push_back(TestICDHandle(new_driver_location));
         icds.back().reset_icd();
-        icd_details.icd_manifest.lib_path = new_driver_location.str();
+        icd_details.icd_manifest.lib_path =
+            icd_details.use_dynamic_library_default_search_paths ? new_driver_name.str() : new_driver_location.str();
     }
     if (icd_details.discovery_type != ManifestDiscoveryType::none) {
         std::string full_json_name = icd_details.json_name;
@@ -545,6 +557,18 @@ void FrameworkEnvironment::add_layer_impl(TestLayerDetails layer_details, Manife
 
             auto new_layer_location = folder.copy_file(layer.lib_path, layer_binary_name.str());
 
+#if COMMON_UNIX_PLATFORMS
+            if (layer_details.use_dynamic_library_default_search_paths) {
+                platform_shim->redirect_dlopen_name(layer_binary_name, new_layer_location);
+            }
+#endif
+#if defined(WIN32)
+            if (layer_details.use_dynamic_library_default_search_paths) {
+                SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_USER_DIRS);
+                AddDllDirectory(conver_str_to_wstr(new_layer_location.parent_path().str()).c_str());
+            }
+#endif
+
             // Don't load the layer binary if using any of the wrap objects layers, since it doesn't export the same interface
             // functions
             if (!layer_details.is_fake &&
@@ -552,7 +576,7 @@ void FrameworkEnvironment::add_layer_impl(TestLayerDetails layer_details, Manife
                 layers.push_back(TestLayerHandle(new_layer_location));
                 layers.back().reset_layer();
             }
-            layer.lib_path = new_layer_location;
+            layer.lib_path = layer_details.use_dynamic_library_default_search_paths ? layer_binary_name : new_layer_location;
         }
     }
     if (layer_details.discovery_type != ManifestDiscoveryType::none) {
index 8284fb342558ad2b75e3c4cdb1237be1e73d7ca5..feb42c4bfeaed4ee0661fe73b68a9f55a26ab124 100644 (file)
@@ -472,8 +472,8 @@ enum class ManifestDiscoveryType {
 
 struct TestICDDetails {
     TestICDDetails(ManifestICD icd_manifest) noexcept : icd_manifest(icd_manifest) {}
-    TestICDDetails(fs::path icd_path, uint32_t api_version = VK_API_VERSION_1_0) noexcept {
-        icd_manifest.set_lib_path(icd_path.str()).set_api_version(api_version);
+    TestICDDetails(fs::path icd_binary_path, uint32_t api_version = VK_API_VERSION_1_0) noexcept {
+        icd_manifest.set_lib_path(icd_binary_path.str()).set_api_version(api_version);
     }
     BUILDER_VALUE(TestICDDetails, ManifestICD, icd_manifest, {});
     BUILDER_VALUE(TestICDDetails, std::string, json_name, "test_icd");
@@ -481,6 +481,8 @@ struct TestICDDetails {
     BUILDER_VALUE(TestICDDetails, bool, disable_icd_inc, false);
     BUILDER_VALUE(TestICDDetails, ManifestDiscoveryType, discovery_type, ManifestDiscoveryType::generic);
     BUILDER_VALUE(TestICDDetails, bool, is_fake, false);
+    // Dont add any path information to the library_path - force the use of the default search paths
+    BUILDER_VALUE(TestICDDetails, bool, use_dynamic_library_default_search_paths, false);
 };
 
 struct TestLayerDetails {
@@ -490,7 +492,10 @@ struct TestLayerDetails {
     BUILDER_VALUE(TestLayerDetails, std::string, json_name, "test_layer");
     BUILDER_VALUE(TestLayerDetails, ManifestDiscoveryType, discovery_type, ManifestDiscoveryType::generic);
     BUILDER_VALUE(TestLayerDetails, bool, is_fake, false);
+    // If discovery type is env-var, is_dir controls whether the env-var has the full name to the manifest or just the folder
     BUILDER_VALUE(TestLayerDetails, bool, is_dir, true);
+    // Dont add any path information to the library_path - force the use of the default search paths
+    BUILDER_VALUE(TestLayerDetails, bool, use_dynamic_library_default_search_paths, false);
 };
 
 // Locations manifests can go in the test framework
index 31f64f9bc42aa90bc50b34a1a5aa865df4d2c798..1826c03b808e4aa37ad2923a4e92085ba0ecfb61 100644 (file)
@@ -172,7 +172,6 @@ namespace fs {
 std::string make_native(std::string const&);
 
 struct path {
-   private:
 #if defined(WIN32)
     static const char path_separator = '\\';
 #elif COMMON_UNIX_PLATFORMS
@@ -867,4 +866,13 @@ inline std::string test_platform_executable_path() {
     buffer[ret] = '\0';
     return buffer;
 }
+
+inline std::wstring conver_str_to_wstr(std::string const& input) {
+    std::wstring output{};
+    output.resize(input.size());
+    size_t characters_converted = 0;
+    mbstowcs_s(&characters_converted, &output[0], output.size() + 1, input.c_str(), input.size());
+    return output;
+}
+
 #endif
index 4d5ffc72023af8e6eb56b07af1490855fd6fa2a6..e6aeddcc3b20c9dbca81bbb5409f162ba960b338 100644 (file)
@@ -3882,6 +3882,40 @@ TEST(DuplicateRegistryEntries, Drivers) {
 }
 #endif
 
+TEST(LibraryLoading, SystemLocations) {
+    FrameworkEnvironment env{};
+    EnvVarWrapper ld_library_path("LD_LIBRARY_PATH", env.get_folder(ManifestLocation::driver).location().str());
+    ld_library_path.add_to_list(env.get_folder(ManifestLocation::explicit_layer).location().str());
+
+    env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2).set_use_dynamic_library_default_search_paths(true));
+    env.get_test_icd().physical_devices.push_back({});
+    const char* fake_ext_name = "VK_FAKE_extension";
+    env.get_test_icd().physical_devices.back().add_extension(fake_ext_name);
+
+    const char* layer_name = "TestLayer";
+    env.add_explicit_layer(
+        TestLayerDetails{ManifestLayer{}.add_layer(
+                             ManifestLayer::LayerDescription{}.set_name(layer_name).set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2)),
+                         "test_layer.json"}
+            .set_use_dynamic_library_default_search_paths(true));
+
+    auto props = env.GetLayerProperties(1);
+    ASSERT_TRUE(string_eq(props.at(0).layerName, layer_name));
+
+    InstWrapper inst{env.vulkan_functions};
+    inst.create_info.add_layer(layer_name);
+    FillDebugUtilsCreateDetails(inst.create_info, env.debug_log);
+    inst.CheckCreate();
+
+    auto phys_dev = inst.GetPhysDev();
+
+    auto active_props = inst.GetActiveLayers(phys_dev, 1);
+    ASSERT_TRUE(string_eq(active_props.at(0).layerName, layer_name));
+
+    auto device_extensions = inst.EnumerateDeviceExtensions(phys_dev, 1);
+    ASSERT_TRUE(string_eq(device_extensions.at(0).extensionName, fake_ext_name));
+}
+
 #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__)
 // Check that valid symlinks do not cause the loader to crash when directly in an XDG env-var
 TEST(ManifestDiscovery, ValidSymlinkInXDGEnvVar) {