| // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| #include "lib/ffi_dynamic_library.h" |
| |
| #include "platform/globals.h" |
| #include "platform/utils.h" |
| #if defined(DART_HOST_OS_WINDOWS) |
| #include <Psapi.h> |
| #include <Windows.h> |
| #include <combaseapi.h> |
| #include <stdio.h> |
| #include <tchar.h> |
| #endif |
| |
| #include "vm/bootstrap_natives.h" |
| #include "vm/dart_api_impl.h" |
| #include "vm/exceptions.h" |
| #include "vm/ffi/native_assets.h" |
| #include "vm/native_entry.h" |
| #include "vm/symbols.h" |
| #include "vm/zone_text_buffer.h" |
| |
| #if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_MACOS) || \ |
| defined(DART_HOST_OS_ANDROID) || defined(DART_HOST_OS_FUCHSIA) |
| #include <dlfcn.h> |
| #endif |
| |
| namespace dart { |
| |
| #if defined(USING_SIMULATOR) || (defined(DART_PRECOMPILER) && !defined(TESTING)) |
| |
| DART_NORETURN static void SimulatorUnsupported() { |
| #if defined(USING_SIMULATOR) |
| Exceptions::ThrowUnsupportedError( |
| "Not supported on simulated architectures."); |
| #else |
| Exceptions::ThrowUnsupportedError("Not supported in precompiler."); |
| #endif |
| } |
| |
| DEFINE_NATIVE_ENTRY(Ffi_dl_open, 0, 1) { |
| SimulatorUnsupported(); |
| } |
| DEFINE_NATIVE_ENTRY(Ffi_dl_processLibrary, 0, 0) { |
| SimulatorUnsupported(); |
| } |
| DEFINE_NATIVE_ENTRY(Ffi_dl_executableLibrary, 0, 0) { |
| SimulatorUnsupported(); |
| } |
| DEFINE_NATIVE_ENTRY(Ffi_dl_lookup, 1, 2) { |
| SimulatorUnsupported(); |
| } |
| DEFINE_NATIVE_ENTRY(Ffi_dl_getHandle, 0, 1) { |
| SimulatorUnsupported(); |
| } |
| DEFINE_NATIVE_ENTRY(Ffi_dl_close, 0, 1) { |
| SimulatorUnsupported(); |
| } |
| DEFINE_NATIVE_ENTRY(Ffi_dl_providesSymbol, 0, 2) { |
| SimulatorUnsupported(); |
| } |
| |
| DEFINE_NATIVE_ENTRY(Ffi_GetFfiNativeResolver, 1, 0) { |
| SimulatorUnsupported(); |
| } |
| |
| #else // defined(USING_SIMULATOR) || \ |
| // (defined(DART_PRECOMPILER) && !defined(TESTING)) |
| |
| // If an error occurs populates |error| (if provided) with an error message |
| // (caller must free this message when it is no longer needed). |
| static void* LoadDynamicLibrary(const char* library_file, |
| char** error = nullptr) { |
| char* utils_error = nullptr; |
| void* handle = Utils::LoadDynamicLibrary(library_file, &utils_error); |
| if (utils_error != nullptr) { |
| if (error != nullptr) { |
| *error = OS::SCreate( |
| /*use malloc*/ nullptr, "Failed to load dynamic library '%s': %s", |
| library_file != nullptr ? library_file : "<process>", utils_error); |
| } |
| free(utils_error); |
| } |
| return handle; |
| } |
| |
| #if defined(DART_HOST_OS_WINDOWS) |
| // On windows, nullptr signals trying a lookup in all loaded modules. |
| const nullptr_t kWindowsDynamicLibraryProcessPtr = nullptr; |
| |
| void* co_task_mem_allocated = nullptr; |
| |
| // If an error occurs populates |error| with an error message |
| // (caller must free this message when it is no longer needed). |
| void* LookupSymbolInProcess(const char* symbol, char** error) { |
| // Force loading ole32.dll. |
| if (co_task_mem_allocated == nullptr) { |
| co_task_mem_allocated = CoTaskMemAlloc(sizeof(intptr_t)); |
| CoTaskMemFree(co_task_mem_allocated); |
| } |
| |
| HANDLE current_process = |
| OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, |
| GetCurrentProcessId()); |
| if (current_process == nullptr) { |
| *error = OS::SCreate(nullptr, "Failed to open current process."); |
| return nullptr; |
| } |
| |
| HMODULE modules[1024]; |
| DWORD cb_needed; |
| if (EnumProcessModules(current_process, modules, sizeof(modules), |
| &cb_needed) != 0) { |
| for (intptr_t i = 0; i < (cb_needed / sizeof(HMODULE)); i++) { |
| if (auto result = |
| reinterpret_cast<void*>(GetProcAddress(modules[i], symbol))) { |
| CloseHandle(current_process); |
| return result; |
| } |
| } |
| } |
| CloseHandle(current_process); |
| |
| *error = OS::SCreate( |
| nullptr, // Use `malloc`. |
| "None of the loaded modules contained the requested symbol '%s'.", |
| symbol); |
| return nullptr; |
| } |
| #endif |
| |
| // If an error occurs populates |error| with an error message |
| // (caller must free this message when it is no longer needed). |
| static void* ResolveSymbol(void* handle, const char* symbol, char** error) { |
| #if defined(DART_HOST_OS_WINDOWS) |
| if (handle == kWindowsDynamicLibraryProcessPtr) { |
| return LookupSymbolInProcess(symbol, error); |
| } |
| #endif |
| return Utils::ResolveSymbolInDynamicLibrary(handle, symbol, error); |
| } |
| |
| static bool SymbolExists(void* handle, const char* symbol) { |
| char* error = nullptr; |
| #if !defined(DART_HOST_OS_WINDOWS) |
| Utils::ResolveSymbolInDynamicLibrary(handle, symbol, &error); |
| #else |
| if (handle == nullptr) { |
| LookupSymbolInProcess(symbol, &error); |
| } else { |
| Utils::ResolveSymbolInDynamicLibrary(handle, symbol, &error); |
| } |
| #endif |
| if (error != nullptr) { |
| free(error); |
| return false; |
| } |
| return true; |
| } |
| |
| DEFINE_NATIVE_ENTRY(Ffi_dl_open, 0, 1) { |
| GET_NON_NULL_NATIVE_ARGUMENT(String, lib_path, arguments->NativeArgAt(0)); |
| |
| char* error = nullptr; |
| void* handle = LoadDynamicLibrary(lib_path.ToCString(), &error); |
| if (error != nullptr) { |
| const String& msg = String::Handle(String::New(error)); |
| free(error); |
| Exceptions::ThrowArgumentError(msg); |
| } |
| return DynamicLibrary::New(handle, true); |
| } |
| |
| DEFINE_NATIVE_ENTRY(Ffi_dl_processLibrary, 0, 0) { |
| #if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_MACOS) || \ |
| defined(DART_HOST_OS_ANDROID) || defined(DART_HOST_OS_FUCHSIA) |
| return DynamicLibrary::New(RTLD_DEFAULT, false); |
| #else |
| return DynamicLibrary::New(kWindowsDynamicLibraryProcessPtr, false); |
| #endif |
| } |
| |
| DEFINE_NATIVE_ENTRY(Ffi_dl_executableLibrary, 0, 0) { |
| return DynamicLibrary::New(LoadDynamicLibrary(nullptr), false); |
| } |
| |
| DEFINE_NATIVE_ENTRY(Ffi_dl_close, 0, 1) { |
| GET_NON_NULL_NATIVE_ARGUMENT(DynamicLibrary, dlib, arguments->NativeArgAt(0)); |
| if (dlib.IsClosed()) { |
| // Already closed, nothing to do |
| } else if (!dlib.CanBeClosed()) { |
| const String& msg = String::Handle( |
| String::New("DynamicLibrary.process() and DynamicLibrary.executable() " |
| "can't be closed.")); |
| Exceptions::ThrowStateError(msg); |
| } else { |
| void* handle = dlib.GetHandle(); |
| char* error = nullptr; |
| Utils::UnloadDynamicLibrary(handle, &error); |
| |
| if (error == nullptr) { |
| dlib.SetClosed(true); |
| } else { |
| const String& msg = String::Handle(String::New(error)); |
| free(error); |
| Exceptions::ThrowStateError(msg); |
| } |
| } |
| |
| return Object::null(); |
| } |
| |
| DEFINE_NATIVE_ENTRY(Ffi_dl_lookup, 1, 2) { |
| GET_NON_NULL_NATIVE_ARGUMENT(DynamicLibrary, dlib, arguments->NativeArgAt(0)); |
| GET_NON_NULL_NATIVE_ARGUMENT(String, argSymbolName, |
| arguments->NativeArgAt(1)); |
| |
| if (dlib.IsClosed()) { |
| const String& msg = |
| String::Handle(String::New("Cannot lookup symbols in closed library.")); |
| Exceptions::ThrowStateError(msg); |
| } |
| |
| void* handle = dlib.GetHandle(); |
| |
| char* error = nullptr; |
| const uword pointer = reinterpret_cast<uword>( |
| ResolveSymbol(handle, argSymbolName.ToCString(), &error)); |
| if (error != nullptr) { |
| const String& msg = String::Handle(String::NewFormatted( |
| "Failed to lookup symbol '%s': %s", argSymbolName.ToCString(), error)); |
| free(error); |
| Exceptions::ThrowArgumentError(msg); |
| } |
| return Pointer::New(pointer); |
| } |
| |
| DEFINE_NATIVE_ENTRY(Ffi_dl_getHandle, 0, 1) { |
| GET_NON_NULL_NATIVE_ARGUMENT(DynamicLibrary, dlib, arguments->NativeArgAt(0)); |
| |
| intptr_t handle = reinterpret_cast<intptr_t>(dlib.GetHandle()); |
| return Integer::NewFromUint64(handle); |
| } |
| |
| DEFINE_NATIVE_ENTRY(Ffi_dl_providesSymbol, 0, 2) { |
| GET_NON_NULL_NATIVE_ARGUMENT(DynamicLibrary, dlib, arguments->NativeArgAt(0)); |
| GET_NON_NULL_NATIVE_ARGUMENT(String, argSymbolName, |
| arguments->NativeArgAt(1)); |
| |
| void* handle = dlib.GetHandle(); |
| return Bool::Get(SymbolExists(handle, argSymbolName.ToCString())).ptr(); |
| } |
| |
| // nullptr if no native resolver is installed. |
| static Dart_FfiNativeResolver GetFfiNativeResolver(Thread* const thread, |
| const String& lib_url_str) { |
| const Library& lib = |
| Library::Handle(Library::LookupLibrary(thread, lib_url_str)); |
| if (lib.IsNull()) { |
| // It is not an error to not have a native resolver installed. |
| return nullptr; |
| } |
| return lib.ffi_native_resolver(); |
| } |
| |
| // If an error occurs populates |error| with an error message |
| // (caller must free this message when it is no longer needed). |
| static void* FfiResolveWithFfiNativeResolver(Thread* const thread, |
| Dart_FfiNativeResolver resolver, |
| const String& symbol, |
| intptr_t args_n, |
| char** error) { |
| auto* result = resolver(symbol.ToCString(), args_n); |
| if (result == nullptr) { |
| *error = OS::SCreate(/*use malloc*/ nullptr, |
| "Couldn't resolve function: '%s'", symbol.ToCString()); |
| } |
| return result; |
| } |
| |
| // Array::null if asset is not in mapping or no mapping. |
| static ArrayPtr GetAssetLocation(Thread* const thread, const String& asset) { |
| Zone* const zone = thread->zone(); |
| auto& result = Array::Handle(zone); |
| |
| const auto& native_assets_map = |
| Array::Handle(zone, GetNativeAssetsMap(thread)); |
| if (!native_assets_map.IsNull()) { |
| NativeAssetsMap map(native_assets_map.ptr()); |
| const auto& lookup = Object::Handle(zone, map.GetOrNull(asset)); |
| if (!lookup.IsNull()) { |
| result = Array::Cast(lookup).ptr(); |
| } |
| map.Release(); |
| } |
| return result.ptr(); |
| } |
| |
| // String is zone allocated. |
| static char* AvailableAssetsToCString(Thread* const thread) { |
| Zone* const zone = thread->zone(); |
| |
| const auto& native_assets_map = |
| Array::Handle(zone, GetNativeAssetsMap(thread)); |
| ZoneTextBuffer buffer(zone, 1024); |
| |
| if (native_assets_map.IsNull()) { |
| buffer.Printf("No available native assets."); |
| } else { |
| bool first = true; |
| buffer.Printf("Available native assets: "); |
| NativeAssetsMap map(native_assets_map.ptr()); |
| NativeAssetsMap::Iterator it(&map); |
| auto& asset_id = String::Handle(zone); |
| while (it.MoveNext()) { |
| if (!first) { |
| buffer.Printf(" ,"); |
| first = false; |
| } |
| auto entry = it.Current(); |
| asset_id ^= map.GetKey(entry); |
| buffer.Printf("%s", asset_id.ToCString()); |
| } |
| buffer.Printf("."); |
| map.Release(); |
| } |
| return buffer.buffer(); |
| } |
| |
| // If an error occurs populates |error| with an error message |
| // (caller must free this message when it is no longer needed). |
| // |
| // The |asset_location| is formatted as follows: |
| // ['<path_type>', '<path (optional)>'] |
| // The |asset_location| is conform to: pkg/vm/lib/native_assets/validator.dart |
| static void* FfiResolveAsset(Thread* const thread, |
| const String& asset, |
| const String& symbol, |
| char** error) { |
| void* handle = nullptr; |
| NativeAssetsApi* native_assets_api = |
| thread->isolate_group()->native_assets_api(); |
| if (native_assets_api->dlopen != nullptr) { |
| // Let embedder resolve the asset id to asset path. |
| NoActiveIsolateScope no_active_isolate_scope; |
| handle = native_assets_api->dlopen(asset.ToCString(), error); |
| } |
| if (*error == nullptr && handle == nullptr) { |
| // Fall back on VM reading ffi:native-assets from special library in kernel. |
| // Allow for both embedder and VM resolution so flutter/engine and |
| // flutter/flutter PRs can land without manual roll. |
| Zone* const zone = thread->zone(); |
| const auto& asset_location = |
| Array::Handle(zone, GetAssetLocation(thread, asset)); |
| if (asset_location.IsNull()) { |
| return nullptr; |
| } |
| |
| const auto& asset_type = |
| String::Cast(Object::Handle(zone, asset_location.At(0))); |
| String& path = String::Handle(zone); |
| const char* path_cstr = nullptr; |
| if (asset_type.Equals(Symbols::absolute()) || |
| asset_type.Equals(Symbols::relative()) || |
| asset_type.Equals(Symbols::system())) { |
| path = String::RawCast(asset_location.At(1)); |
| path_cstr = path.ToCString(); |
| } |
| |
| if (asset_type.Equals(Symbols::absolute())) { |
| if (native_assets_api->dlopen_absolute == nullptr) { |
| *error = OS::SCreate(/*use malloc*/ nullptr, |
| "NativeAssetsApi::dlopen_absolute not set."); |
| return nullptr; |
| } |
| NoActiveIsolateScope no_active_isolate_scope; |
| handle = native_assets_api->dlopen_absolute(path_cstr, error); |
| } else if (asset_type.Equals(Symbols::relative())) { |
| if (native_assets_api->dlopen_relative == nullptr) { |
| *error = OS::SCreate(/*use malloc*/ nullptr, |
| "NativeAssetsApi::dlopen_relative not set."); |
| return nullptr; |
| } |
| NoActiveIsolateScope no_active_isolate_scope; |
| handle = native_assets_api->dlopen_relative(path_cstr, error); |
| } else if (asset_type.Equals(Symbols::system())) { |
| if (native_assets_api->dlopen_system == nullptr) { |
| *error = OS::SCreate(/*use malloc*/ nullptr, |
| "NativeAssetsApi::dlopen_system not set."); |
| return nullptr; |
| } |
| NoActiveIsolateScope no_active_isolate_scope; |
| handle = native_assets_api->dlopen_system(path_cstr, error); |
| } else if (asset_type.Equals(Symbols::executable())) { |
| if (native_assets_api->dlopen_executable == nullptr) { |
| *error = OS::SCreate(/*use malloc*/ nullptr, |
| "NativeAssetsApi::dlopen_executable not set."); |
| return nullptr; |
| } |
| NoActiveIsolateScope no_active_isolate_scope; |
| handle = native_assets_api->dlopen_executable(error); |
| } else { |
| RELEASE_ASSERT(asset_type.Equals(Symbols::process())); |
| if (native_assets_api->dlopen_process == nullptr) { |
| *error = OS::SCreate(/*use malloc*/ nullptr, |
| "NativeAssetsApi::dlopen_process not set."); |
| return nullptr; |
| } |
| NoActiveIsolateScope no_active_isolate_scope; |
| handle = native_assets_api->dlopen_process(error); |
| } |
| } |
| |
| if (*error != nullptr) { |
| return nullptr; |
| } |
| if (native_assets_api->dlsym == nullptr) { |
| *error = |
| OS::SCreate(/*use malloc*/ nullptr, "NativeAssetsApi::dlsym not set."); |
| return nullptr; |
| } |
| void* const result = |
| native_assets_api->dlsym(handle, symbol.ToCString(), error); |
| return result; |
| } |
| |
| // Frees |error|. |
| static void ThrowFfiResolveError(const String& symbol, |
| const String& asset, |
| char* error) { |
| const String& error_message = String::Handle(String::NewFormatted( |
| "Couldn't resolve native function '%s' in '%s' : %s.\n", |
| symbol.ToCString(), asset.ToCString(), error)); |
| free(error); |
| Exceptions::ThrowArgumentError(error_message); |
| } |
| |
| intptr_t FfiResolveInternal(const String& asset, |
| const String& symbol, |
| uintptr_t args_n, |
| char** error) { |
| Thread* thread = Thread::Current(); |
| // Resolver resolution. |
| auto resolver = GetFfiNativeResolver(thread, asset); |
| if (resolver != nullptr) { |
| void* ffi_native_result = FfiResolveWithFfiNativeResolver( |
| thread, resolver, symbol, args_n, error); |
| return reinterpret_cast<intptr_t>(ffi_native_result); |
| } |
| |
| // Native assets resolution. |
| void* asset_result = FfiResolveAsset(thread, asset, symbol, error); |
| if (asset_result != nullptr) { |
| return reinterpret_cast<intptr_t>(asset_result); |
| } |
| |
| // Resolution in current process. |
| #if !defined(DART_HOST_OS_WINDOWS) |
| void* const result = Utils::ResolveSymbolInDynamicLibrary( |
| RTLD_DEFAULT, symbol.ToCString(), error); |
| #else |
| void* const result = LookupSymbolInProcess(symbol.ToCString(), error); |
| #endif |
| |
| if (*error != nullptr) { |
| // Process lookup failed, but the user might have tried to use native |
| // asset lookup. So augment the error message to include native assets info. |
| char* process_lookup_error = *error; |
| NativeAssetsApi* native_assets_api = |
| thread->isolate_group()->native_assets_api(); |
| const char* const format = |
| "No asset with id '%s' found. %s " |
| "Attempted to fallback to process lookup. %s"; |
| if (native_assets_api->available_assets != nullptr) { |
| // Embedder is resolving asset ids to asset paths. |
| char* available_assets = native_assets_api->available_assets(); |
| *error = OS::SCreate(/*use malloc*/ nullptr, format, asset.ToCString(), |
| available_assets, process_lookup_error); |
| free(available_assets); |
| } else { |
| // VM is resolving asset ids to asset paths. |
| *error = |
| OS::SCreate(/*use malloc*/ nullptr, format, asset.ToCString(), |
| AvailableAssetsToCString(thread), process_lookup_error); |
| } |
| free(process_lookup_error); |
| } |
| |
| return reinterpret_cast<intptr_t>(result); |
| } |
| |
| // FFI native C function pointer resolver. |
| static intptr_t FfiResolve(Dart_Handle asset_handle, |
| Dart_Handle symbol_handle, |
| uintptr_t args_n) { |
| auto* const thread = Thread::Current(); |
| DARTSCOPE(thread); |
| auto* const zone = thread->zone(); |
| const String& asset = Api::UnwrapStringHandle(zone, asset_handle); |
| const String& symbol = Api::UnwrapStringHandle(zone, symbol_handle); |
| char* error = nullptr; |
| |
| const intptr_t result = FfiResolveInternal(asset, symbol, args_n, &error); |
| if (error != nullptr) { |
| ThrowFfiResolveError(symbol, asset, error); |
| } |
| ASSERT(result != 0x0); |
| return result; |
| } |
| |
| // Bootstrap to get the FFI Native resolver through a `native` call. |
| DEFINE_NATIVE_ENTRY(Ffi_GetFfiNativeResolver, 1, 0) { |
| return Pointer::New(reinterpret_cast<intptr_t>(FfiResolve)); |
| } |
| |
| #endif // defined(USING_SIMULATOR) || \ |
| // (defined(DART_PRECOMPILER) && !defined(TESTING)) |
| |
| } // namespace dart |