blob: 2c6df520f710b16f19ec9c857a69b85f44491d84 [file] [log] [blame]
// 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"
#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/uri.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;
}
#if defined(DART_TARGET_OS_WINDOWS)
// Replaces back slashes with forward slashes in place.
static void ReplaceBackSlashes(char* cstr) {
const intptr_t length = strlen(cstr);
for (int i = 0; i < length; i++) {
cstr[i] = cstr[i] == '\\' ? '/' : cstr[i];
}
}
#endif
const char* file_schema = "file://";
const int file_schema_length = 7;
// Get a file path with only forward slashes from the script path.
static StringPtr GetPlatformScriptPath(Thread* thread) {
IsolateGroupSource* const source = thread->isolate_group()->source();
#if defined(DART_TARGET_OS_WINDOWS)
// Isolate.spawnUri sets a `source` including the file schema.
// And on Windows we get an extra forward slash in that case.
const char* file_schema_slash = "file:///";
const int file_schema_slash_length = 8;
const char* path = source->script_uri;
if (strlen(source->script_uri) > file_schema_slash_length &&
strncmp(source->script_uri, file_schema_slash,
file_schema_slash_length) == 0) {
path = (source->script_uri + file_schema_slash_length);
}
// Replace backward slashes with forward slashes.
const intptr_t len = strlen(path);
char* path_copy = reinterpret_cast<char*>(malloc(len + 1));
snprintf(path_copy, len + 1, "%s", path);
ReplaceBackSlashes(path_copy);
const auto& result = String::Handle(String::New(path_copy));
free(path_copy);
return result.ptr();
#else
// Isolate.spawnUri sets a `source` including the file schema.
if (strlen(source->script_uri) > file_schema_length &&
strncmp(source->script_uri, file_schema, file_schema_length) == 0) {
const char* path = (source->script_uri + file_schema_length);
return String::New(path);
}
return String::New(source->script_uri);
#endif
}
// 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(" ,");
}
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 Array& asset_location,
const String& symbol,
char** error) {
Zone* const zone = thread->zone();
const auto& asset_type =
String::Cast(Object::Handle(zone, asset_location.At(0)));
String& path = String::Handle(zone);
if (asset_type.Equals(Symbols::absolute()) ||
asset_type.Equals(Symbols::relative()) ||
asset_type.Equals(Symbols::system())) {
path = String::RawCast(asset_location.At(1));
}
void* handle = nullptr;
if (asset_type.Equals(Symbols::absolute())) {
handle = LoadDynamicLibrary(path.ToCString(), error);
} else if (asset_type.Equals(Symbols::relative())) {
const auto& platform_script_uri = String::Handle(
zone,
String::NewFormatted(
"%s%s", file_schema,
String::Handle(zone, GetPlatformScriptPath(thread)).ToCString()));
const char* target_uri = nullptr;
char* path_cstr = path.ToMallocCString();
#if defined(DART_TARGET_OS_WINDOWS)
ReplaceBackSlashes(path_cstr);
#endif
const bool resolved =
ResolveUri(path_cstr, platform_script_uri.ToCString(), &target_uri);
free(path_cstr);
if (!resolved) {
*error = OS::SCreate(/*use malloc*/ nullptr,
"Failed to resolve '%s' relative to '%s'.",
path.ToCString(), platform_script_uri.ToCString());
} else {
const char* target_path = target_uri + file_schema_length;
handle = LoadDynamicLibrary(target_path, error);
}
} else if (asset_type.Equals(Symbols::system())) {
handle = LoadDynamicLibrary(path.ToCString(), error);
} else if (asset_type.Equals(Symbols::process())) {
#if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_MACOS) || \
defined(DART_HOST_OS_ANDROID) || defined(DART_HOST_OS_FUCHSIA)
handle = RTLD_DEFAULT;
#else
handle = kWindowsDynamicLibraryProcessPtr;
#endif
} else if (asset_type.Equals(Symbols::executable())) {
handle = LoadDynamicLibrary(nullptr, error);
} else {
UNREACHABLE();
}
if (*error != nullptr) {
char* inner_error = *error;
*error = OS::SCreate(/*use malloc*/ nullptr,
"Failed to load dynamic library '%s': %s",
path.ToCString(), inner_error);
free(inner_error);
} else {
void* const result = ResolveSymbol(handle, symbol.ToCString(), error);
if (*error != nullptr) {
char* inner_error = *error;
*error = OS::SCreate(/*use malloc*/ nullptr,
"Failed to lookup symbol '%s': %s",
symbol.ToCString(), inner_error);
free(inner_error);
} else {
return result;
}
}
ASSERT(*error != nullptr);
return nullptr;
}
// 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();
Zone* zone = thread->zone();
// 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.
const auto& asset_location =
Array::Handle(zone, GetAssetLocation(thread, asset));
if (!asset_location.IsNull()) {
void* asset_result = FfiResolveAsset(thread, asset_location, symbol, error);
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;
*error = OS::SCreate(/*use malloc*/ nullptr,
"No asset with id '%s' found. %s "
"Attempted to fallback to process lookup. %s",
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