|  | // 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 || *error != 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 |