| /* |
| * 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. |
| */ |
| |
| #define ATRACE_TAG ATRACE_TAG_GRAPHICS |
| |
| #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 <graphicsenv/GraphicsEnv.h> |
| #include <log/log.h> |
| #include <nativebridge/native_bridge.h> |
| #include <nativeloader/native_loader.h> |
| #include <utils/Trace.h> |
| #include <ziparchive/zip_archive.h> |
| |
| // TODO(b/143296676): 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, |
| const std::string& filename) |
| : path_(path), |
| filename_(filename), |
| dlhandle_(nullptr), |
| native_bridge_(false), |
| refcount_(0) {} |
| |
| LayerLibrary(LayerLibrary&& other) noexcept |
| : path_(std::move(other.path_)), |
| filename_(std::move(other.filename_)), |
| dlhandle_(other.dlhandle_), |
| native_bridge_(other.native_bridge_), |
| 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 std::string_view gpa_name) const; |
| |
| const std::string GetFilename() { return filename_; } |
| |
| private: |
| // TODO(b/79940628): remove that adapter when we could use NativeBridgeGetTrampoline |
| // for native libraries. |
| template<typename Func = void*> |
| Func GetTrampoline(const char* name) const { |
| if (native_bridge_) { |
| return reinterpret_cast<Func>(android::NativeBridgeGetTrampoline( |
| dlhandle_, name, nullptr, 0)); |
| } |
| return reinterpret_cast<Func>(dlsym(dlhandle_, name)); |
| } |
| |
| const std::string path_; |
| |
| // Track the filename alone so we can detect duplicates |
| const std::string filename_; |
| |
| std::mutex mutex_; |
| void* dlhandle_; |
| bool native_bridge_; |
| 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 = android::GraphicsEnv::getInstance().getAppNamespace(); |
| if (app_namespace && |
| !android::base::StartsWith(path_, kSystemLayerLibraryDir)) { |
| char* error_msg = nullptr; |
| dlhandle_ = OpenNativeLibraryInNamespace( |
| app_namespace, path_.c_str(), &native_bridge_, &error_msg); |
| if (!dlhandle_) { |
| ALOGE("failed to load layer library '%s': %s", path_.c_str(), error_msg); |
| android::NativeLoaderFreeErrorMessage(error_msg); |
| refcount_ = 0; |
| return false; |
| } |
| } 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()); |
| char* error_msg = nullptr; |
| if (!android::CloseNativeLibrary(dlhandle_, native_bridge_, &error_msg)) { |
| ALOGE("failed to unload library '%s': %s", path_.c_str(), error_msg); |
| android::NativeLoaderFreeErrorMessage(error_msg); |
| refcount_++; |
| } else { |
| dlhandle_ = nullptr; |
| } |
| } |
| } |
| |
| bool LayerLibrary::EnumerateLayers(size_t library_idx, |
| std::vector<Layer>& instance_layers) const { |
| PFN_vkEnumerateInstanceLayerProperties enumerate_instance_layers = |
| GetTrampoline<PFN_vkEnumerateInstanceLayerProperties>( |
| "vkEnumerateInstanceLayerProperties"); |
| PFN_vkEnumerateInstanceExtensionProperties enumerate_instance_extensions = |
| GetTrampoline<PFN_vkEnumerateInstanceExtensionProperties>( |
| "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 = |
| GetTrampoline<PFN_vkEnumerateDeviceLayerProperties>( |
| "vkEnumerateDeviceLayerProperties"); |
| PFN_vkEnumerateDeviceExtensionProperties enumerate_device_extensions = |
| GetTrampoline<PFN_vkEnumerateDeviceExtensionProperties>( |
| "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 |
| std::vector<VkLayerProperties> properties(num_instance_layers + num_device_layers); |
| result = enumerate_instance_layers(&num_instance_layers, properties.data()); |
| 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 std::string_view gpa_name) const { |
| std::string layer_name { layer.properties.layerName }; |
| if (void* gpa = GetTrampoline((layer_name.append(gpa_name).c_str()))) |
| return gpa; |
| return GetTrampoline((std::string {"vk"}.append(gpa_name)).c_str()); |
| } |
| |
| // ---------------------------------------------------------------------------- |
| |
| std::vector<LayerLibrary> g_layer_libraries; |
| std::vector<Layer> g_instance_layers; |
| |
| void AddLayerLibrary(const std::string& path, const std::string& filename) { |
| LayerLibrary library(path + "/" + filename, filename); |
| 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 + "/"); |
| void* iter_cookie = nullptr; |
| if ((err = StartIteration(zip, &iter_cookie, prefix, "")) != 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; |
| std::string name; |
| while (Next(iter_cookie, &entry, &name) == 0) { |
| std::string filename(name.substr(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) { |
| ATRACE_CALL(); |
| |
| 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")) { |
| |
| // Check to ensure we haven't seen this layer already |
| // Let the first instance of the shared object be enumerated |
| // We're searching for layers in following order: |
| // 1. system path |
| // 2. libraryPermittedPath (if enabled) |
| // 3. libraryPath |
| |
| bool duplicate = false; |
| for (auto& layer : g_layer_libraries) { |
| if (layer.GetFilename() == filename) { |
| ALOGV("Skipping duplicate layer %s in %s", |
| filename.c_str(), path.c_str()); |
| duplicate = true; |
| } |
| } |
| |
| if (!duplicate) |
| 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 std::string_view gpa_name) { |
| const LayerLibrary& library = g_layer_libraries[layer.library_idx]; |
| return library.GetGPA(layer, gpa_name); |
| } |
| |
| } // anonymous namespace |
| |
| void DiscoverLayers() { |
| ATRACE_CALL(); |
| |
| if (android::GraphicsEnv::getInstance().isDebuggable()) { |
| DiscoverLayersInPathList(kSystemLayerLibraryDir); |
| } |
| if (!android::GraphicsEnv::getInstance().getLayerPaths().empty()) |
| DiscoverLayersInPathList(android::GraphicsEnv::getInstance().getLayerPaths()); |
| } |
| |
| 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) noexcept : layer_(other.layer_) { |
| other.layer_ = nullptr; |
| } |
| |
| PFN_vkGetInstanceProcAddr LayerRef::GetGetInstanceProcAddr() const { |
| return layer_ ? reinterpret_cast<PFN_vkGetInstanceProcAddr>( |
| GetLayerGetProcAddr(*layer_, "GetInstanceProcAddr")) |
| : nullptr; |
| } |
| |
| PFN_vkGetDeviceProcAddr LayerRef::GetGetDeviceProcAddr() const { |
| return layer_ ? reinterpret_cast<PFN_vkGetDeviceProcAddr>( |
| GetLayerGetProcAddr(*layer_, "GetDeviceProcAddr")) |
| : nullptr; |
| } |
| |
| } // namespace api |
| } // namespace vulkan |