| /* |
| * 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 |