1 /*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #define LOG_TAG "nativeloader"
18
19 #include "nativeloader/native_loader.h"
20
21 #include <dlfcn.h>
22 #include <sys/types.h>
23
24 #include <algorithm>
25 #include <memory>
26 #include <mutex>
27 #include <optional>
28 #include <regex>
29 #include <string>
30 #include <vector>
31
32 #include "android-base/file.h"
33 #include "android-base/macros.h"
34 #include <android-base/properties.h>
35 #include "android-base/strings.h"
36 #include "android-base/thread_annotations.h"
37 #include "base/macros.h"
38 #include "nativebridge/native_bridge.h"
39 #include "nativehelper/scoped_utf_chars.h"
40 #include "public_libraries.h"
41
42 #ifdef ART_TARGET_ANDROID
43 #include "android-modules-utils/sdk_level.h"
44 #include "android/api-level.h"
45 #include "library_namespaces.h"
46 #include "log/log.h"
47 #include "nativeloader/dlext_namespaces.h"
48 #endif
49
50 namespace android {
51
52 namespace {
53
54 #if defined(ART_TARGET_ANDROID)
55
56 using ::android::base::Result;
57 using ::android::nativeloader::LibraryNamespaces;
58
59 // NATIVELOADER_DEFAULT_NAMESPACE_LIBS is an environment variable that can be
60 // used to list extra libraries (separated by ":") that libnativeloader will
61 // load from the default namespace. The libraries must be listed without paths,
62 // and then LD_LIBRARY_PATH is typically set to the directories to load them
63 // from. The libraries will be available in all classloader namespaces, and also
64 // in the fallback namespace used when no classloader is given.
65 //
66 // kNativeloaderExtraLibs is the name of that fallback namespace.
67 //
68 // NATIVELOADER_DEFAULT_NAMESPACE_LIBS is intended to be used for testing only,
69 // and in particular in the ART run tests that are executed through dalvikvm in
70 // the APEX. In that case the default namespace links to the ART namespace
71 // (com_android_art) for all libraries, which means this can be used to load
72 // test libraries that depend on ART internal libraries.
73 //
74 // There's also code in art/dalvikvm.cc to add links from com_android_art back
75 // to the default namespace for NATIVELOADER_DEFAULT_NAMESPACE_LIBS, enabling
76 // access in the opposite direction as well. Useful e.g. to load ART plugins in
77 // NATIVELOADER_DEFAULT_NAMESPACE_LIBS.
78 constexpr const char* kNativeloaderExtraLibs = "nativeloader-extra-libs";
79
80 std::mutex g_namespaces_mutex;
81 LibraryNamespaces* g_namespaces GUARDED_BY(g_namespaces_mutex) = new LibraryNamespaces;
82 NativeLoaderNamespace* g_nativeloader_extra_libs_namespace GUARDED_BY(g_namespaces_mutex) = nullptr;
83
FindApexNamespace(const char * caller_location)84 std::optional<NativeLoaderNamespace> FindApexNamespace(const char* caller_location) {
85 std::optional<std::string> name = nativeloader::FindApexNamespaceName(caller_location);
86 if (name.has_value()) {
87 // Native Bridge is never used for APEXes.
88 Result<NativeLoaderNamespace> ns =
89 NativeLoaderNamespace::GetExportedNamespace(name.value(), /*is_bridged=*/false);
90 LOG_ALWAYS_FATAL_IF(!ns.ok(),
91 "Error finding ns %s for APEX location %s: %s",
92 name.value().c_str(),
93 caller_location,
94 ns.error().message().c_str());
95 return ns.value();
96 }
97 return std::nullopt;
98 }
99
GetNamespaceForApiDomain(nativeloader::ApiDomain api_domain,bool is_bridged)100 Result<NativeLoaderNamespace> GetNamespaceForApiDomain(nativeloader::ApiDomain api_domain,
101 bool is_bridged) {
102 switch (api_domain) {
103 case nativeloader::API_DOMAIN_VENDOR:
104 return NativeLoaderNamespace::GetExportedNamespace(nativeloader::kVendorNamespaceName,
105 is_bridged);
106 case nativeloader::API_DOMAIN_PRODUCT:
107 return NativeLoaderNamespace::GetExportedNamespace(nativeloader::kProductNamespaceName,
108 is_bridged);
109 case nativeloader::API_DOMAIN_SYSTEM:
110 return NativeLoaderNamespace::GetSystemNamespace(is_bridged);
111 default:
112 LOG_FATAL("Invalid API domain %d", api_domain);
113 UNREACHABLE();
114 }
115 }
116
CreateNativeloaderDefaultNamespaceLibsLink(NativeLoaderNamespace & ns)117 Result<void> CreateNativeloaderDefaultNamespaceLibsLink(NativeLoaderNamespace& ns)
118 REQUIRES(g_namespaces_mutex) {
119 const char* links = getenv("NATIVELOADER_DEFAULT_NAMESPACE_LIBS");
120 if (links == nullptr || *links == 0) {
121 return {};
122 }
123 // Pass nullptr to Link() to create a link to the default namespace without
124 // requiring it to be visible.
125 return ns.Link(nullptr, links);
126 }
127
GetNativeloaderExtraLibsNamespace()128 Result<NativeLoaderNamespace*> GetNativeloaderExtraLibsNamespace() REQUIRES(g_namespaces_mutex) {
129 if (g_nativeloader_extra_libs_namespace != nullptr) {
130 return g_nativeloader_extra_libs_namespace;
131 }
132
133 Result<NativeLoaderNamespace> ns =
134 NativeLoaderNamespace::Create(kNativeloaderExtraLibs,
135 /*search_paths=*/"",
136 /*permitted_paths=*/"",
137 /*parent=*/nullptr,
138 /*is_shared=*/false,
139 /*is_exempt_list_enabled=*/false,
140 /*also_used_as_anonymous=*/false);
141 if (!ns.ok()) {
142 return ns.error();
143 }
144 g_nativeloader_extra_libs_namespace = new NativeLoaderNamespace(std::move(ns.value()));
145 Result<void> linked =
146 CreateNativeloaderDefaultNamespaceLibsLink(*g_nativeloader_extra_libs_namespace);
147 if (!linked.ok()) {
148 return linked.error();
149 }
150 return g_nativeloader_extra_libs_namespace;
151 }
152
153 // If the given path matches a library in NATIVELOADER_DEFAULT_NAMESPACE_LIBS
154 // then load it in the nativeloader-extra-libs namespace, otherwise return
155 // nullptr without error.
TryLoadNativeloaderExtraLib(const char * path)156 Result<void*> TryLoadNativeloaderExtraLib(const char* path) {
157 const char* links = getenv("NATIVELOADER_DEFAULT_NAMESPACE_LIBS");
158 if (links == nullptr || *links == 0) {
159 return nullptr;
160 }
161 std::vector<std::string> lib_list = base::Split(links, ":");
162 if (std::find(lib_list.begin(), lib_list.end(), path) == lib_list.end()) {
163 return nullptr;
164 }
165
166 std::lock_guard<std::mutex> guard(g_namespaces_mutex);
167 Result<NativeLoaderNamespace*> ns = GetNativeloaderExtraLibsNamespace();
168 if (!ns.ok()) {
169 return ns.error();
170 }
171
172 Result<void*> res = ns.value()->Load(path);
173 ALOGD("Load %s using ns %s from NATIVELOADER_DEFAULT_NAMESPACE_LIBS match: %s",
174 path,
175 ns.value()->name().c_str(),
176 res.ok() ? "ok" : res.error().message().c_str());
177 return res;
178 }
179
CreateClassLoaderNamespaceLocked(JNIEnv * env,int32_t target_sdk_version,jobject class_loader,nativeloader::ApiDomain api_domain,bool is_shared,const std::string & dex_path,jstring library_path_j,jstring permitted_path_j,jstring uses_library_list_j)180 Result<NativeLoaderNamespace*> CreateClassLoaderNamespaceLocked(JNIEnv* env,
181 int32_t target_sdk_version,
182 jobject class_loader,
183 nativeloader::ApiDomain api_domain,
184 bool is_shared,
185 const std::string& dex_path,
186 jstring library_path_j,
187 jstring permitted_path_j,
188 jstring uses_library_list_j)
189 REQUIRES(g_namespaces_mutex) {
190 Result<NativeLoaderNamespace*> ns = g_namespaces->Create(env,
191 target_sdk_version,
192 class_loader,
193 api_domain,
194 is_shared,
195 dex_path,
196 library_path_j,
197 permitted_path_j,
198 uses_library_list_j);
199 if (!ns.ok()) {
200 return ns;
201 }
202 Result<void> linked = CreateNativeloaderDefaultNamespaceLibsLink(*ns.value());
203 if (!linked.ok()) {
204 return linked.error();
205 }
206 return ns;
207 }
208
209 #endif // ART_TARGET_ANDROID
210
211 } // namespace
212
InitializeNativeLoader()213 void InitializeNativeLoader() {
214 #if defined(ART_TARGET_ANDROID)
215 std::lock_guard<std::mutex> guard(g_namespaces_mutex);
216 g_namespaces->Initialize();
217 #endif
218 }
219
ResetNativeLoader()220 void ResetNativeLoader() {
221 #if defined(ART_TARGET_ANDROID)
222 std::lock_guard<std::mutex> guard(g_namespaces_mutex);
223 g_namespaces->Reset();
224 delete g_nativeloader_extra_libs_namespace;
225 g_nativeloader_extra_libs_namespace = nullptr;
226 #endif
227 }
228
229 // dex_path_j may be a ':'-separated list of paths, e.g. when creating a shared
230 // library loader - cf. mCodePaths in android.content.pm.SharedLibraryInfo.
CreateClassLoaderNamespace(JNIEnv * env,int32_t target_sdk_version,jobject class_loader,bool is_shared,jstring dex_path_j,jstring library_path_j,jstring permitted_path_j,jstring uses_library_list_j)231 jstring CreateClassLoaderNamespace(JNIEnv* env,
232 int32_t target_sdk_version,
233 jobject class_loader,
234 bool is_shared,
235 jstring dex_path_j,
236 jstring library_path_j,
237 jstring permitted_path_j,
238 jstring uses_library_list_j) {
239 #if defined(ART_TARGET_ANDROID)
240 std::string dex_path;
241 if (dex_path_j != nullptr) {
242 ScopedUtfChars dex_path_chars(env, dex_path_j);
243 dex_path = dex_path_chars.c_str();
244 }
245
246 Result<nativeloader::ApiDomain> api_domain = nativeloader::GetApiDomainFromPathList(dex_path);
247 if (!api_domain.ok()) {
248 return env->NewStringUTF(api_domain.error().message().c_str());
249 }
250
251 std::lock_guard<std::mutex> guard(g_namespaces_mutex);
252 Result<NativeLoaderNamespace*> ns = CreateClassLoaderNamespaceLocked(env,
253 target_sdk_version,
254 class_loader,
255 api_domain.value(),
256 is_shared,
257 dex_path,
258 library_path_j,
259 permitted_path_j,
260 uses_library_list_j);
261 if (!ns.ok()) {
262 return env->NewStringUTF(ns.error().message().c_str());
263 }
264
265 #else
266 UNUSED(env,
267 target_sdk_version,
268 class_loader,
269 is_shared,
270 dex_path_j,
271 library_path_j,
272 permitted_path_j,
273 uses_library_list_j);
274 #endif
275
276 return nullptr;
277 }
278
279 #if defined(ART_TARGET_ANDROID)
ShouldBypassLoadingForB349878424()280 static bool ShouldBypassLoadingForB349878424() {
281 struct stat st;
282 if (stat("/system/lib64/libsobridge.so", &st) != 0 &&
283 stat("/system/lib64/libwalkstack.so", &st) != 0) {
284 return false;
285 }
286 std::string property = android::base::GetProperty("ro.product.build.fingerprint", "");
287 return android_get_device_api_level() == 33 &&
288 (property.starts_with("Xiaomi") ||
289 property.starts_with("Redmi") ||
290 property.starts_with("POCO"));
291 }
292 #endif
293
OpenNativeLibrary(JNIEnv * env,int32_t target_sdk_version,const char * path,jobject class_loader,const char * caller_location,jstring library_path_j,bool * needs_native_bridge,char ** error_msg)294 void* OpenNativeLibrary(JNIEnv* env,
295 int32_t target_sdk_version,
296 const char* path,
297 jobject class_loader,
298 const char* caller_location,
299 jstring library_path_j,
300 bool* needs_native_bridge,
301 char** error_msg) {
302 #if defined(ART_TARGET_ANDROID)
303 if (class_loader == nullptr) {
304 // class_loader is null only for the boot class loader (see
305 // IsBootClassLoader call in JavaVMExt::LoadNativeLibrary), i.e. the caller
306 // is in the boot classpath.
307 *needs_native_bridge = false;
308 if (caller_location != nullptr) {
309 std::optional<NativeLoaderNamespace> ns = FindApexNamespace(caller_location);
310 if (ns.has_value()) {
311 const android_dlextinfo dlextinfo = {
312 .flags = ANDROID_DLEXT_USE_NAMESPACE,
313 .library_namespace = ns.value().ToRawAndroidNamespace(),
314 };
315 void* handle = android_dlopen_ext(path, RTLD_NOW, &dlextinfo);
316 char* dlerror_msg = handle == nullptr ? strdup(dlerror()) : nullptr;
317 ALOGD("Load %s using APEX ns %s for caller %s: %s",
318 path,
319 ns.value().name().c_str(),
320 caller_location,
321 dlerror_msg == nullptr ? "ok" : dlerror_msg);
322 if (dlerror_msg != nullptr) {
323 *error_msg = dlerror_msg;
324 }
325 return handle;
326 }
327 }
328
329 // Check if the library is in NATIVELOADER_DEFAULT_NAMESPACE_LIBS and should
330 // be loaded from the kNativeloaderExtraLibs namespace.
331 {
332 Result<void*> handle = TryLoadNativeloaderExtraLib(path);
333 if (!handle.ok()) {
334 *error_msg = strdup(handle.error().message().c_str());
335 return nullptr;
336 }
337 if (handle.value() != nullptr) {
338 return handle.value();
339 }
340 }
341
342 // Handle issue b/349878424.
343 static bool bypass_loading_for_b349878424 = ShouldBypassLoadingForB349878424();
344
345 if (bypass_loading_for_b349878424 &&
346 (strcmp("libsobridge.so", path) == 0 || strcmp("libwalkstack.so", path) == 0)) {
347 // Load a different library to pretend the loading was successful. This
348 // allows the device to boot.
349 ALOGD("Loading libbase.so instead of %s due to b/349878424", path);
350 path = "libbase.so";
351 }
352
353 // Fall back to the system namespace. This happens for preloaded JNI
354 // libraries in the zygote.
355 void* handle = OpenSystemLibrary(path, RTLD_NOW);
356 char* dlerror_msg = handle == nullptr ? strdup(dlerror()) : nullptr;
357 ALOGD("Load %s using system ns (caller=%s): %s",
358 path,
359 caller_location == nullptr ? "<unknown>" : caller_location,
360 dlerror_msg == nullptr ? "ok" : dlerror_msg);
361 if (dlerror_msg != nullptr) {
362 *error_msg = dlerror_msg;
363 }
364 return handle;
365 }
366
367 // If the caller is in any of the system image partitions and the library is
368 // in the same partition then load it without regards to public library
369 // restrictions. This is only done if the library is specified by an absolute
370 // path, so we don't affect the lookup process for libraries specified by name
371 // only.
372 if (caller_location != nullptr &&
373 // Apps in the partition may have their own native libraries which should
374 // be loaded with the app's classloader namespace, so only do this for
375 // libraries in the partition-wide lib(64) directories.
376 nativeloader::IsPartitionNativeLibPath(path) &&
377 // Don't do this if the system image is older than V, to avoid any compat
378 // issues with apps and shared libs in them.
379 android::modules::sdklevel::IsAtLeastV()) {
380 nativeloader::ApiDomain caller_api_domain = nativeloader::GetApiDomainFromPath(caller_location);
381 if (caller_api_domain != nativeloader::API_DOMAIN_DEFAULT) {
382 nativeloader::ApiDomain library_api_domain = nativeloader::GetApiDomainFromPath(path);
383
384 if (library_api_domain == caller_api_domain) {
385 bool is_bridged = false;
386 if (library_path_j != nullptr) {
387 ScopedUtfChars library_path_utf_chars(env, library_path_j);
388 if (library_path_utf_chars[0] != '\0') {
389 is_bridged = NativeBridgeIsPathSupported(library_path_utf_chars.c_str());
390 }
391 }
392
393 Result<NativeLoaderNamespace> ns = GetNamespaceForApiDomain(caller_api_domain, is_bridged);
394 if (!ns.ok()) {
395 ALOGD("Failed to find ns for caller %s in API domain %d to load %s (is_bridged=%b): %s",
396 caller_location,
397 caller_api_domain,
398 path,
399 is_bridged,
400 ns.error().message().c_str());
401 *error_msg = strdup(ns.error().message().c_str());
402 return nullptr;
403 }
404
405 *needs_native_bridge = ns.value().IsBridged();
406 Result<void*> handle = ns.value().Load(path);
407 ALOGD("Load %s using ns %s for caller %s in same partition (is_bridged=%b): %s",
408 path,
409 ns.value().name().c_str(),
410 caller_location,
411 is_bridged,
412 handle.ok() ? "ok" : handle.error().message().c_str());
413 if (!handle.ok()) {
414 *error_msg = strdup(handle.error().message().c_str());
415 return nullptr;
416 }
417 return handle.value();
418 }
419 }
420 }
421
422 NativeLoaderNamespace* ns;
423 const char* ns_descr;
424 {
425 std::lock_guard<std::mutex> guard(g_namespaces_mutex);
426
427 ns = g_namespaces->FindNamespaceByClassLoader(env, class_loader);
428 ns_descr = "class loader";
429
430 if (ns == nullptr) {
431 // This is the case where the classloader was not created by ApplicationLoaders
432 // In this case we create an isolated not-shared namespace for it.
433 const std::string empty_dex_path;
434 Result<NativeLoaderNamespace*> res =
435 CreateClassLoaderNamespaceLocked(env,
436 target_sdk_version,
437 class_loader,
438 nativeloader::API_DOMAIN_DEFAULT,
439 /*is_shared=*/false,
440 empty_dex_path,
441 library_path_j,
442 /*permitted_path_j=*/nullptr,
443 /*uses_library_list_j=*/nullptr);
444 if (!res.ok()) {
445 ALOGD("Failed to create isolated ns for %s (caller=%s)",
446 path,
447 caller_location == nullptr ? "<unknown>" : caller_location);
448 *error_msg = strdup(res.error().message().c_str());
449 return nullptr;
450 }
451 ns = res.value();
452 ns_descr = "isolated";
453 }
454 }
455
456 *needs_native_bridge = ns->IsBridged();
457 Result<void*> handle = ns->Load(path);
458 ALOGD("Load %s using %s ns %s (caller=%s): %s",
459 path,
460 ns_descr,
461 ns->name().c_str(),
462 caller_location == nullptr ? "<unknown>" : caller_location,
463 handle.ok() ? "ok" : handle.error().message().c_str());
464 if (!handle.ok()) {
465 *error_msg = strdup(handle.error().message().c_str());
466 return nullptr;
467 }
468 return handle.value();
469
470 #else // !ART_TARGET_ANDROID
471 UNUSED(env, target_sdk_version, class_loader, caller_location);
472
473 // Do some best effort to emulate library-path support. It will not
474 // work for dependencies.
475 //
476 // Note: null has a special meaning and must be preserved.
477 std::string library_path; // Empty string by default.
478 if (library_path_j != nullptr && path != nullptr && path[0] != '/') {
479 ScopedUtfChars library_path_utf_chars(env, library_path_j);
480 library_path = library_path_utf_chars.c_str();
481 }
482
483 std::vector<std::string> library_paths = base::Split(library_path, ":");
484
485 for (const std::string& lib_path : library_paths) {
486 *needs_native_bridge = false;
487 const char* path_arg;
488 std::string complete_path;
489 if (path == nullptr) {
490 // Preserve null.
491 path_arg = nullptr;
492 } else {
493 complete_path = lib_path;
494 if (!complete_path.empty()) {
495 complete_path.append("/");
496 }
497 complete_path.append(path);
498 path_arg = complete_path.c_str();
499 }
500 void* handle = dlopen(path_arg, RTLD_NOW);
501 if (handle != nullptr) {
502 return handle;
503 }
504 if (NativeBridgeIsSupported(path_arg)) {
505 *needs_native_bridge = true;
506 handle = NativeBridgeLoadLibrary(path_arg, RTLD_NOW);
507 if (handle != nullptr) {
508 return handle;
509 }
510 *error_msg = strdup(NativeBridgeGetError());
511 } else {
512 *error_msg = strdup(dlerror());
513 }
514 }
515 return nullptr;
516 #endif // !ART_TARGET_ANDROID
517 }
518
CloseNativeLibrary(void * handle,const bool needs_native_bridge,char ** error_msg)519 bool CloseNativeLibrary(void* handle, const bool needs_native_bridge, char** error_msg) {
520 bool success;
521 if (needs_native_bridge) {
522 success = (NativeBridgeUnloadLibrary(handle) == 0);
523 if (!success) {
524 *error_msg = strdup(NativeBridgeGetError());
525 }
526 } else {
527 success = (dlclose(handle) == 0);
528 if (!success) {
529 *error_msg = strdup(dlerror());
530 }
531 }
532
533 return success;
534 }
535
NativeLoaderFreeErrorMessage(char * msg)536 void NativeLoaderFreeErrorMessage(char* msg) {
537 // The error messages get allocated through strdup, so we must call free on them.
538 free(msg);
539 }
540
541 #if defined(ART_TARGET_ANDROID)
OpenNativeLibraryInNamespace(NativeLoaderNamespace * ns,const char * path,bool * needs_native_bridge,char ** error_msg)542 void* OpenNativeLibraryInNamespace(NativeLoaderNamespace* ns, const char* path,
543 bool* needs_native_bridge, char** error_msg) {
544 Result<void*> handle = ns->Load(path);
545 if (!handle.ok() && error_msg != nullptr) {
546 *error_msg = strdup(handle.error().message().c_str());
547 }
548 if (needs_native_bridge != nullptr) {
549 *needs_native_bridge = ns->IsBridged();
550 }
551 return handle.ok() ? *handle : nullptr;
552 }
553
IsNamespaceNativeBridged(const struct NativeLoaderNamespace * ns)554 bool IsNamespaceNativeBridged(const struct NativeLoaderNamespace* ns) { return ns->IsBridged(); }
555
556 // native_bridge_namespaces are not supported for callers of this function.
557 // This function will return nullptr in the case when application is running
558 // on native bridge.
FindNamespaceByClassLoader(JNIEnv * env,jobject class_loader)559 android_namespace_t* FindNamespaceByClassLoader(JNIEnv* env, jobject class_loader) {
560 std::lock_guard<std::mutex> guard(g_namespaces_mutex);
561 NativeLoaderNamespace* ns = g_namespaces->FindNamespaceByClassLoader(env, class_loader);
562 if (ns != nullptr && !ns->IsBridged()) {
563 return ns->ToRawAndroidNamespace();
564 }
565 return nullptr;
566 }
567
FindNativeLoaderNamespaceByClassLoader(JNIEnv * env,jobject class_loader)568 NativeLoaderNamespace* FindNativeLoaderNamespaceByClassLoader(JNIEnv* env, jobject class_loader) {
569 std::lock_guard<std::mutex> guard(g_namespaces_mutex);
570 return g_namespaces->FindNamespaceByClassLoader(env, class_loader);
571 }
572
LinkNativeLoaderNamespaceToExportedNamespaceLibrary(struct NativeLoaderNamespace * ns,const char * exported_ns_name,const char * library_name,char ** error_msg)573 void LinkNativeLoaderNamespaceToExportedNamespaceLibrary(struct NativeLoaderNamespace* ns,
574 const char* exported_ns_name,
575 const char* library_name,
576 char** error_msg) {
577 Result<NativeLoaderNamespace> exported_ns =
578 NativeLoaderNamespace::GetExportedNamespace(exported_ns_name, ns->IsBridged());
579 if (!exported_ns.ok()) {
580 *error_msg = strdup(exported_ns.error().message().c_str());
581 return;
582 }
583
584 Result<void> linked = ns->Link(&exported_ns.value(), std::string(library_name));
585 if (!linked.ok()) {
586 *error_msg = strdup(linked.error().message().c_str());
587 }
588 }
589
590 #endif // ART_TARGET_ANDROID
591
592 } // namespace android
593