/* * 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 <mutex> #include <sys/prctl.h> #include <string> #include <string.h> #include <vector> #include <android-base/strings.h> #include <android/dlext.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: 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