| /* | |
| * Copyright 2016 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include "layers_extensions.h" | |
| #include <alloca.h> | |
| #include <dirent.h> | |
| #include <dlfcn.h> | |
| #include <string.h> | |
| #include <sys/prctl.h> | |
| #include <mutex> | |
| #include <string> | |
| #include <vector> | |
| #include <android/dlext.h> | |
| #include <android-base/strings.h> | |
| #include <cutils/properties.h> | |
| #include <log/log.h> | |
| #include <ziparchive/zip_archive.h> | |
| #include <vulkan/vulkan_loader_data.h> | |
| // TODO(jessehall): The whole way we deal with extensions is pretty hokey, and | |
| // not a good long-term solution. Having a hard-coded enum of extensions is | |
| // bad, of course. Representing sets of extensions (requested, supported, etc.) | |
| // as a bitset isn't necessarily bad, if the mapping from extension to bit were | |
| // dynamic. Need to rethink this completely when there's a little more time. | |
| // TODO(jessehall): This file currently builds up global data structures as it | |
| // loads, and never cleans them up. This means we're doing heap allocations | |
| // without going through an app-provided allocator, but worse, we'll leak those | |
| // allocations if the loader is unloaded. | |
| // | |
| // We should allocate "enough" BSS space, and suballocate from there. Will | |
| // probably want to intern strings, etc., and will need some custom/manual data | |
| // structures. | |
| namespace vulkan { | |
| namespace api { | |
| struct Layer { | |
| VkLayerProperties properties; | |
| size_t library_idx; | |
| // true if the layer intercepts vkCreateDevice and device commands | |
| bool is_global; | |
| std::vector<VkExtensionProperties> instance_extensions; | |
| std::vector<VkExtensionProperties> device_extensions; | |
| }; | |
| namespace { | |
| const char kSystemLayerLibraryDir[] = "/data/local/debug/vulkan"; | |
| class LayerLibrary { | |
| public: | |
| explicit LayerLibrary(const std::string& path) | |
| : path_(path), dlhandle_(nullptr), refcount_(0) {} | |
| LayerLibrary(LayerLibrary&& other) | |
| : path_(std::move(other.path_)), | |
| dlhandle_(other.dlhandle_), | |
| refcount_(other.refcount_) { | |
| other.dlhandle_ = nullptr; | |
| other.refcount_ = 0; | |
| } | |
| LayerLibrary(const LayerLibrary&) = delete; | |
| LayerLibrary& operator=(const LayerLibrary&) = delete; | |
| // these are thread-safe | |
| bool Open(); | |
| void Close(); | |
| bool EnumerateLayers(size_t library_idx, | |
| std::vector<Layer>& instance_layers) const; | |
| void* GetGPA(const Layer& layer, | |
| const char* gpa_name, | |
| size_t gpa_name_len) const; | |
| private: | |
| const std::string path_; | |
| std::mutex mutex_; | |
| void* dlhandle_; | |
| size_t refcount_; | |
| }; | |
| bool LayerLibrary::Open() { | |
| std::lock_guard<std::mutex> lock(mutex_); | |
| if (refcount_++ == 0) { | |
| ALOGV("opening layer library '%s'", path_.c_str()); | |
| // Libraries in the system layer library dir can't be loaded into | |
| // the application namespace. That causes compatibility problems, since | |
| // any symbol dependencies will be resolved by system libraries. They | |
| // can't safely use libc++_shared, for example. Which is one reason | |
| // (among several) we only allow them in non-user builds. | |
| auto app_namespace = LoaderData::GetInstance().app_namespace; | |
| if (app_namespace && | |
| !android::base::StartsWith(path_, kSystemLayerLibraryDir)) { | |
| android_dlextinfo dlextinfo = {}; | |
| dlextinfo.flags = ANDROID_DLEXT_USE_NAMESPACE; | |
| dlextinfo.library_namespace = app_namespace; | |
| dlhandle_ = android_dlopen_ext(path_.c_str(), RTLD_NOW | RTLD_LOCAL, | |
| &dlextinfo); | |
| } else { | |
| dlhandle_ = dlopen(path_.c_str(), RTLD_NOW | RTLD_LOCAL); | |
| } | |
| if (!dlhandle_) { | |
| ALOGE("failed to load layer library '%s': %s", path_.c_str(), | |
| dlerror()); | |
| refcount_ = 0; | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| void LayerLibrary::Close() { | |
| std::lock_guard<std::mutex> lock(mutex_); | |
| if (--refcount_ == 0) { | |
| ALOGV("closing layer library '%s'", path_.c_str()); | |
| dlclose(dlhandle_); | |
| dlhandle_ = nullptr; | |
| } | |
| } | |
| bool LayerLibrary::EnumerateLayers(size_t library_idx, | |
| std::vector<Layer>& instance_layers) const { | |
| PFN_vkEnumerateInstanceLayerProperties enumerate_instance_layers = | |
| reinterpret_cast<PFN_vkEnumerateInstanceLayerProperties>( | |
| dlsym(dlhandle_, "vkEnumerateInstanceLayerProperties")); | |
| PFN_vkEnumerateInstanceExtensionProperties enumerate_instance_extensions = | |
| reinterpret_cast<PFN_vkEnumerateInstanceExtensionProperties>( | |
| dlsym(dlhandle_, "vkEnumerateInstanceExtensionProperties")); | |
| if (!enumerate_instance_layers || !enumerate_instance_extensions) { | |
| ALOGE("layer library '%s' missing some instance enumeration functions", | |
| path_.c_str()); | |
| return false; | |
| } | |
| // device functions are optional | |
| PFN_vkEnumerateDeviceLayerProperties enumerate_device_layers = | |
| reinterpret_cast<PFN_vkEnumerateDeviceLayerProperties>( | |
| dlsym(dlhandle_, "vkEnumerateDeviceLayerProperties")); | |
| PFN_vkEnumerateDeviceExtensionProperties enumerate_device_extensions = | |
| reinterpret_cast<PFN_vkEnumerateDeviceExtensionProperties>( | |
| dlsym(dlhandle_, "vkEnumerateDeviceExtensionProperties")); | |
| // get layer counts | |
| uint32_t num_instance_layers = 0; | |
| uint32_t num_device_layers = 0; | |
| VkResult result = enumerate_instance_layers(&num_instance_layers, nullptr); | |
| if (result != VK_SUCCESS || !num_instance_layers) { | |
| if (result != VK_SUCCESS) { | |
| ALOGE( | |
| "vkEnumerateInstanceLayerProperties failed for library '%s': " | |
| "%d", | |
| path_.c_str(), result); | |
| } | |
| return false; | |
| } | |
| if (enumerate_device_layers) { | |
| result = enumerate_device_layers(VK_NULL_HANDLE, &num_device_layers, | |
| nullptr); | |
| if (result != VK_SUCCESS) { | |
| ALOGE( | |
| "vkEnumerateDeviceLayerProperties failed for library '%s': %d", | |
| path_.c_str(), result); | |
| return false; | |
| } | |
| } | |
| // get layer properties | |
| VkLayerProperties* properties = static_cast<VkLayerProperties*>(alloca( | |
| (num_instance_layers + num_device_layers) * sizeof(VkLayerProperties))); | |
| result = enumerate_instance_layers(&num_instance_layers, properties); | |
| if (result != VK_SUCCESS) { | |
| ALOGE("vkEnumerateInstanceLayerProperties failed for library '%s': %d", | |
| path_.c_str(), result); | |
| return false; | |
| } | |
| if (num_device_layers > 0) { | |
| result = enumerate_device_layers(VK_NULL_HANDLE, &num_device_layers, | |
| properties + num_instance_layers); | |
| if (result != VK_SUCCESS) { | |
| ALOGE( | |
| "vkEnumerateDeviceLayerProperties failed for library '%s': %d", | |
| path_.c_str(), result); | |
| return false; | |
| } | |
| } | |
| // append layers to instance_layers | |
| size_t prev_num_instance_layers = instance_layers.size(); | |
| instance_layers.reserve(prev_num_instance_layers + num_instance_layers); | |
| for (size_t i = 0; i < num_instance_layers; i++) { | |
| const VkLayerProperties& props = properties[i]; | |
| Layer layer; | |
| layer.properties = props; | |
| layer.library_idx = library_idx; | |
| layer.is_global = false; | |
| uint32_t count = 0; | |
| result = | |
| enumerate_instance_extensions(props.layerName, &count, nullptr); | |
| if (result != VK_SUCCESS) { | |
| ALOGE( | |
| "vkEnumerateInstanceExtensionProperties(\"%s\") failed for " | |
| "library '%s': %d", | |
| props.layerName, path_.c_str(), result); | |
| instance_layers.resize(prev_num_instance_layers); | |
| return false; | |
| } | |
| layer.instance_extensions.resize(count); | |
| result = enumerate_instance_extensions( | |
| props.layerName, &count, layer.instance_extensions.data()); | |
| if (result != VK_SUCCESS) { | |
| ALOGE( | |
| "vkEnumerateInstanceExtensionProperties(\"%s\") failed for " | |
| "library '%s': %d", | |
| props.layerName, path_.c_str(), result); | |
| instance_layers.resize(prev_num_instance_layers); | |
| return false; | |
| } | |
| for (size_t j = 0; j < num_device_layers; j++) { | |
| const auto& dev_props = properties[num_instance_layers + j]; | |
| if (memcmp(&props, &dev_props, sizeof(props)) == 0) { | |
| layer.is_global = true; | |
| break; | |
| } | |
| } | |
| if (layer.is_global && enumerate_device_extensions) { | |
| result = enumerate_device_extensions( | |
| VK_NULL_HANDLE, props.layerName, &count, nullptr); | |
| if (result != VK_SUCCESS) { | |
| ALOGE( | |
| "vkEnumerateDeviceExtensionProperties(\"%s\") failed for " | |
| "library '%s': %d", | |
| props.layerName, path_.c_str(), result); | |
| instance_layers.resize(prev_num_instance_layers); | |
| return false; | |
| } | |
| layer.device_extensions.resize(count); | |
| result = enumerate_device_extensions( | |
| VK_NULL_HANDLE, props.layerName, &count, | |
| layer.device_extensions.data()); | |
| if (result != VK_SUCCESS) { | |
| ALOGE( | |
| "vkEnumerateDeviceExtensionProperties(\"%s\") failed for " | |
| "library '%s': %d", | |
| props.layerName, path_.c_str(), result); | |
| instance_layers.resize(prev_num_instance_layers); | |
| return false; | |
| } | |
| } | |
| instance_layers.push_back(layer); | |
| ALOGD("added %s layer '%s' from library '%s'", | |
| (layer.is_global) ? "global" : "instance", props.layerName, | |
| path_.c_str()); | |
| } | |
| return true; | |
| } | |
| void* LayerLibrary::GetGPA(const Layer& layer, | |
| const char* gpa_name, | |
| size_t gpa_name_len) const { | |
| void* gpa; | |
| size_t layer_name_len = | |
| std::max(size_t{2}, strlen(layer.properties.layerName)); | |
| char* name = static_cast<char*>(alloca(layer_name_len + gpa_name_len + 1)); | |
| strcpy(name, layer.properties.layerName); | |
| strcpy(name + layer_name_len, gpa_name); | |
| if (!(gpa = dlsym(dlhandle_, name))) { | |
| strcpy(name, "vk"); | |
| strcpy(name + 2, gpa_name); | |
| gpa = dlsym(dlhandle_, name); | |
| } | |
| return gpa; | |
| } | |
| // ---------------------------------------------------------------------------- | |
| std::vector<LayerLibrary> g_layer_libraries; | |
| std::vector<Layer> g_instance_layers; | |
| void AddLayerLibrary(const std::string& path) { | |
| LayerLibrary library(path); | |
| if (!library.Open()) | |
| return; | |
| if (!library.EnumerateLayers(g_layer_libraries.size(), g_instance_layers)) { | |
| library.Close(); | |
| return; | |
| } | |
| library.Close(); | |
| g_layer_libraries.emplace_back(std::move(library)); | |
| } | |
| template <typename Functor> | |
| void ForEachFileInDir(const std::string& dirname, Functor functor) { | |
| auto dir_deleter = [](DIR* handle) { closedir(handle); }; | |
| std::unique_ptr<DIR, decltype(dir_deleter)> dir(opendir(dirname.c_str()), | |
| dir_deleter); | |
| if (!dir) { | |
| // It's normal for some search directories to not exist, especially | |
| // /data/local/debug/vulkan. | |
| int err = errno; | |
| ALOGW_IF(err != ENOENT, "failed to open layer directory '%s': %s", | |
| dirname.c_str(), strerror(err)); | |
| return; | |
| } | |
| ALOGD("searching for layers in '%s'", dirname.c_str()); | |
| dirent* entry; | |
| while ((entry = readdir(dir.get())) != nullptr) | |
| functor(entry->d_name); | |
| } | |
| template <typename Functor> | |
| void ForEachFileInZip(const std::string& zipname, | |
| const std::string& dir_in_zip, | |
| Functor functor) { | |
| int32_t err; | |
| ZipArchiveHandle zip = nullptr; | |
| if ((err = OpenArchive(zipname.c_str(), &zip)) != 0) { | |
| ALOGE("failed to open apk '%s': %d", zipname.c_str(), err); | |
| return; | |
| } | |
| std::string prefix(dir_in_zip + "/"); | |
| const ZipString prefix_str(prefix.c_str()); | |
| void* iter_cookie = nullptr; | |
| if ((err = StartIteration(zip, &iter_cookie, &prefix_str, nullptr)) != 0) { | |
| ALOGE("failed to iterate entries in apk '%s': %d", zipname.c_str(), | |
| err); | |
| CloseArchive(zip); | |
| return; | |
| } | |
| ALOGD("searching for layers in '%s!/%s'", zipname.c_str(), | |
| dir_in_zip.c_str()); | |
| ZipEntry entry; | |
| ZipString name; | |
| while (Next(iter_cookie, &entry, &name) == 0) { | |
| std::string filename( | |
| reinterpret_cast<const char*>(name.name) + prefix.length(), | |
| name.name_length - prefix.length()); | |
| // only enumerate direct entries of the directory, not subdirectories | |
| if (filename.find('/') != filename.npos) | |
| continue; | |
| // Check whether it *may* be possible to load the library directly from | |
| // the APK. Loading still may fail for other reasons, but this at least | |
| // lets us avoid failed-to-load log messages in the typical case of | |
| // compressed and/or unaligned libraries. | |
| if (entry.method != kCompressStored || entry.offset % PAGE_SIZE != 0) | |
| continue; | |
| functor(filename); | |
| } | |
| EndIteration(iter_cookie); | |
| CloseArchive(zip); | |
| } | |
| template <typename Functor> | |
| void ForEachFileInPath(const std::string& path, Functor functor) { | |
| size_t zip_pos = path.find("!/"); | |
| if (zip_pos == std::string::npos) { | |
| ForEachFileInDir(path, functor); | |
| } else { | |
| ForEachFileInZip(path.substr(0, zip_pos), path.substr(zip_pos + 2), | |
| functor); | |
| } | |
| } | |
| void DiscoverLayersInPathList(const std::string& pathstr) { | |
| std::vector<std::string> paths = android::base::Split(pathstr, ":"); | |
| for (const auto& path : paths) { | |
| ForEachFileInPath(path, [&](const std::string& filename) { | |
| if (android::base::StartsWith(filename, "libVkLayer") && | |
| android::base::EndsWith(filename, ".so")) { | |
| AddLayerLibrary(path + "/" + filename); | |
| } | |
| }); | |
| } | |
| } | |
| const VkExtensionProperties* FindExtension( | |
| const std::vector<VkExtensionProperties>& extensions, | |
| const char* name) { | |
| auto it = std::find_if(extensions.cbegin(), extensions.cend(), | |
| [=](const VkExtensionProperties& ext) { | |
| return (strcmp(ext.extensionName, name) == 0); | |
| }); | |
| return (it != extensions.cend()) ? &*it : nullptr; | |
| } | |
| void* GetLayerGetProcAddr(const Layer& layer, | |
| const char* gpa_name, | |
| size_t gpa_name_len) { | |
| const LayerLibrary& library = g_layer_libraries[layer.library_idx]; | |
| return library.GetGPA(layer, gpa_name, gpa_name_len); | |
| } | |
| } // anonymous namespace | |
| void DiscoverLayers() { | |
| if (property_get_bool("ro.debuggable", false) && | |
| prctl(PR_GET_DUMPABLE, 0, 0, 0, 0)) { | |
| DiscoverLayersInPathList(kSystemLayerLibraryDir); | |
| } | |
| if (!LoaderData::GetInstance().layer_path.empty()) | |
| DiscoverLayersInPathList(LoaderData::GetInstance().layer_path); | |
| } | |
| uint32_t GetLayerCount() { | |
| return static_cast<uint32_t>(g_instance_layers.size()); | |
| } | |
| const Layer& GetLayer(uint32_t index) { | |
| return g_instance_layers[index]; | |
| } | |
| const Layer* FindLayer(const char* name) { | |
| auto layer = | |
| std::find_if(g_instance_layers.cbegin(), g_instance_layers.cend(), | |
| [=](const Layer& entry) { | |
| return strcmp(entry.properties.layerName, name) == 0; | |
| }); | |
| return (layer != g_instance_layers.cend()) ? &*layer : nullptr; | |
| } | |
| const VkLayerProperties& GetLayerProperties(const Layer& layer) { | |
| return layer.properties; | |
| } | |
| bool IsLayerGlobal(const Layer& layer) { | |
| return layer.is_global; | |
| } | |
| const VkExtensionProperties* GetLayerInstanceExtensions(const Layer& layer, | |
| uint32_t& count) { | |
| count = static_cast<uint32_t>(layer.instance_extensions.size()); | |
| return layer.instance_extensions.data(); | |
| } | |
| const VkExtensionProperties* GetLayerDeviceExtensions(const Layer& layer, | |
| uint32_t& count) { | |
| count = static_cast<uint32_t>(layer.device_extensions.size()); | |
| return layer.device_extensions.data(); | |
| } | |
| const VkExtensionProperties* FindLayerInstanceExtension(const Layer& layer, | |
| const char* name) { | |
| return FindExtension(layer.instance_extensions, name); | |
| } | |
| const VkExtensionProperties* FindLayerDeviceExtension(const Layer& layer, | |
| const char* name) { | |
| return FindExtension(layer.device_extensions, name); | |
| } | |
| LayerRef GetLayerRef(const Layer& layer) { | |
| LayerLibrary& library = g_layer_libraries[layer.library_idx]; | |
| return LayerRef((library.Open()) ? &layer : nullptr); | |
| } | |
| LayerRef::LayerRef(const Layer* layer) : layer_(layer) {} | |
| LayerRef::~LayerRef() { | |
| if (layer_) { | |
| LayerLibrary& library = g_layer_libraries[layer_->library_idx]; | |
| library.Close(); | |
| } | |
| } | |
| LayerRef::LayerRef(LayerRef&& other) : layer_(other.layer_) { | |
| other.layer_ = nullptr; | |
| } | |
| PFN_vkGetInstanceProcAddr LayerRef::GetGetInstanceProcAddr() const { | |
| return layer_ ? reinterpret_cast<PFN_vkGetInstanceProcAddr>( | |
| GetLayerGetProcAddr(*layer_, "GetInstanceProcAddr", 19)) | |
| : nullptr; | |
| } | |
| PFN_vkGetDeviceProcAddr LayerRef::GetGetDeviceProcAddr() const { | |
| return layer_ ? reinterpret_cast<PFN_vkGetDeviceProcAddr>( | |
| GetLayerGetProcAddr(*layer_, "GetDeviceProcAddr", 17)) | |
| : nullptr; | |
| } | |
| } // namespace api | |
| } // namespace vulkan |