自定义linker踩坑记录

自定义linker踩坑记录

1.需求描述

众所周知linker是Android系统中实现动态链接的程序,存放在路径/apex/com.android.runtime/bin/linker64,现在我想把linker链接过的so库dump到本地,那就需要对linker的代码进行修改,添加dump代码。

2.实现方法

首先我想到的是直接在linker的代码中添加dump代码,比如dlopen等函数,等它链接完,我就根据返回的soinfo信息(包含起始和终止地址)来dump一下。

但遇到的问题是,我无法区分要dump的so以及dump的时候,文件系统可能还未初始化好,此时我调用getuid()等函数也是失败了。

这时候我的实现方法变为:在linker中添加接口,然后我在nativeLoad调用的时候我再dump(nativeLoadSystem.loadlibrary()最终调用的native函数)。这样只有Java层主动加载的so库才会被dump。

3.(踩坑)过程记录(基于AOSP10)

(1)首先,我们应该梳理一下代码的调用关系,以及库的依赖关系

nativeLoad声明在libcore/ojluni/src/main/java/java/lang/Runtime.java

1
private static native String nativeLoad(String filename, ClassLoader loader, Class<?> caller);

然后其native层实现在libcore/ojluni/src/main/native/Runtime.c(编译到`libcore/NativeCode.bp中的libopenjdk

1
2
3
4
5
Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,
jobject javaLoader, jclass caller)
{
return JVM_NativeLoad(env, javaFilename, javaLoader, caller);
}

JVM_NativeLoad的实现在art/openjdkjvm/OpenjdkJvm.cc(编译到art/openjdkjvm/Android.bp中的libopenjdkjvm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,
jstring javaFilename,
jobject javaLoader,
jclass caller) {
ScopedUtfChars filename(env, javaFilename);
if (filename.c_str() == nullptr) {
return nullptr;
}

std::string error_msg;
{
art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM();
bool success = vm->LoadNativeLibrary(env,
filename.c_str(),
javaLoader,
caller,
&error_msg);
if (success) {
return nullptr;
}
}

// Don't let a pending exception from JNI_OnLoad cause a CheckJNI issue with NewStringUTF.
env->ExceptionClear();
return env->NewStringUTF(error_msg.c_str());
}

LoadNativeLibrary的实现在art/runtime/jni/java_vm_ext.cc(编译到libart.soart/runtime/Android.bp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
const std::string& path,
jobject class_loader,
jclass caller_class,
std::string* error_msg) {
error_msg->clear();

// See if we've already loaded this library. If we have, and the class loader
// matches, return successfully without doing anything.
// TODO: for better results we should canonicalize the pathname (or even compare
// inodes). This implementation is fine if everybody is using System.loadLibrary.
SharedLibrary* library;
Thread* self = Thread::Current();
{
// TODO: move the locking (and more of this logic) into Libraries.
MutexLock mu(self, *Locks::jni_libraries_lock_);
library = libraries_->Get(path);
}
void* class_loader_allocator = nullptr;
std::string caller_location;
{
ScopedObjectAccess soa(env);
// As the incoming class loader is reachable/alive during the call of this function,
// it's okay to decode it without worrying about unexpectedly marking it alive.
ObjPtr<mirror::ClassLoader> loader = soa.Decode<mirror::ClassLoader>(class_loader);

ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
if (class_linker->IsBootClassLoader(soa, loader.Ptr())) {
loader = nullptr;
class_loader = nullptr;
if (caller_class != nullptr) {
ObjPtr<mirror::Class> caller = soa.Decode<mirror::Class>(caller_class);
ObjPtr<mirror::DexCache> dex_cache = caller->GetDexCache();
if (dex_cache != nullptr) {
caller_location = dex_cache->GetLocation()->ToModifiedUtf8();
}
}
}

class_loader_allocator = class_linker->GetAllocatorForClassLoader(loader.Ptr());
CHECK(class_loader_allocator != nullptr);
}
if (library != nullptr) {
// Use the allocator pointers for class loader equality to avoid unnecessary weak root decode.
if (library->GetClassLoaderAllocator() != class_loader_allocator) {
// The library will be associated with class_loader. The JNI
// spec says we can't load the same library into more than one
// class loader.
//
// This isn't very common. So spend some time to get a readable message.
auto call_to_string = [&](jobject obj) -> std::string {
if (obj == nullptr) {
return "null";
}
// Handle jweaks. Ignore double local-ref.
ScopedLocalRef<jobject> local_ref(env, env->NewLocalRef(obj));
if (local_ref != nullptr) {
ScopedLocalRef<jclass> local_class(env, env->GetObjectClass(local_ref.get()));
jmethodID to_string = env->GetMethodID(local_class.get(),
"toString",
"()Ljava/lang/String;");
DCHECK(to_string != nullptr);
ScopedLocalRef<jobject> local_string(env,
env->CallObjectMethod(local_ref.get(), to_string));
if (local_string != nullptr) {
ScopedUtfChars utf(env, reinterpret_cast<jstring>(local_string.get()));
if (utf.c_str() != nullptr) {
return utf.c_str();
}
}
if (env->ExceptionCheck()) {
// We can't do much better logging, really. So leave it with a Describe.
env->ExceptionDescribe();
env->ExceptionClear();
}
return "(Error calling toString)";
}
return "null";
};
std::string old_class_loader = call_to_string(library->GetClassLoader());
std::string new_class_loader = call_to_string(class_loader);
StringAppendF(error_msg, "Shared library \"%s\" already opened by "
"ClassLoader %p(%s); can't open in ClassLoader %p(%s)",
path.c_str(),
library->GetClassLoader(),
old_class_loader.c_str(),
class_loader,
new_class_loader.c_str());
LOG(WARNING) << *error_msg;
return false;
}
VLOG(jni) << "[Shared library \"" << path << "\" already loaded in "
<< " ClassLoader " << class_loader << "]";
if (!library->CheckOnLoadResult()) {
StringAppendF(error_msg, "JNI_OnLoad failed on a previous attempt "
"to load \"%s\"", path.c_str());
return false;
}
return true;
}

// Open the shared library. Because we're using a full path, the system
// doesn't have to search through LD_LIBRARY_PATH. (It may do so to
// resolve this library's dependencies though.)

// Failures here are expected when java.library.path has several entries
// and we have to hunt for the lib.

// Below we dlopen but there is no paired dlclose, this would be necessary if we supported
// class unloading. Libraries will only be unloaded when the reference count (incremented by
// dlopen) becomes zero from dlclose.

// Retrieve the library path from the classloader, if necessary.
ScopedLocalRef<jstring> library_path(env, GetLibrarySearchPath(env, class_loader));

Locks::mutator_lock_->AssertNotHeld(self);
const char* path_str = path.empty() ? nullptr : path.c_str();
bool needs_native_bridge = false;
char* nativeloader_error_msg = nullptr;
void* handle = android::OpenNativeLibrary(
env,
runtime_->GetTargetSdkVersion(),
path_str,
class_loader,
(caller_location.empty() ? nullptr : caller_location.c_str()),
library_path.get(),
&needs_native_bridge,
&nativeloader_error_msg);

VLOG(jni) << "[Call to dlopen(\"" << path << "\", RTLD_NOW) returned " << handle << "]";
}

OpenNativeLibrary的实现在system/core/libnativeloader/native_loader.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
void* OpenNativeLibrary(JNIEnv* env, int32_t target_sdk_version, const char* path,
jobject class_loader, const char* caller_location, jstring library_path,
bool* needs_native_bridge, char** error_msg) {
// openLinker();

#if defined(__ANDROID__)
UNUSED(target_sdk_version);
if (class_loader == nullptr) {
*needs_native_bridge = false;
if (caller_location != nullptr) {
android_namespace_t* boot_namespace = FindExportedNamespace(caller_location);
if (boot_namespace != nullptr) {
const android_dlextinfo dlextinfo = {
.flags = ANDROID_DLEXT_USE_NAMESPACE,
.library_namespace = boot_namespace,
};
void* handle = android_dlopen_ext(path, RTLD_NOW, &dlextinfo);
if (handle == nullptr) {
*error_msg = strdup(dlerror());
}
return handle;
}
}
void* handle = dlopen(path, RTLD_NOW);
if (handle == nullptr) {
*error_msg = strdup(dlerror());
}
return handle;
}

std::lock_guard<std::mutex> guard(g_namespaces_mutex);
NativeLoaderNamespace* ns;

if ((ns = g_namespaces->FindNamespaceByClassLoader(env, class_loader)) == nullptr) {
// This is the case where the classloader was not created by ApplicationLoaders
// In this case we create an isolated not-shared namespace for it.
std::string create_error_msg;
if ((ns = g_namespaces->Create(env, target_sdk_version, class_loader, false /* is_shared */,
nullptr, library_path, nullptr, &create_error_msg)) == nullptr) {
*error_msg = strdup(create_error_msg.c_str());
return nullptr;
}
}

return OpenNativeLibraryInNamespace(ns, path, needs_native_bridge, error_msg);
#else
UNUSED(env, target_sdk_version, class_loader, caller_location);

// Do some best effort to emulate library-path support. It will not
// work for dependencies.
//
// Note: null has a special meaning and must be preserved.
std::string c_library_path; // Empty string by default.
if (library_path != nullptr && path != nullptr && path[0] != '/') {
ScopedUtfChars library_path_utf_chars(env, library_path);
c_library_path = library_path_utf_chars.c_str();
}

std::vector<std::string> library_paths = base::Split(c_library_path, ":");

for (const std::string& lib_path : library_paths) {
*needs_native_bridge = false;
const char* path_arg;
std::string complete_path;
if (path == nullptr) {
// Preserve null.
path_arg = nullptr;
} else {
complete_path = lib_path;
if (!complete_path.empty()) {
complete_path.append("/");
}
complete_path.append(path);
path_arg = complete_path.c_str();
}
void* handle = dlopen(path_arg, RTLD_NOW);
if (handle != nullptr) {
return handle;
}
if (NativeBridgeIsSupported(path_arg)) {
*needs_native_bridge = true;
handle = NativeBridgeLoadLibrary(path_arg, RTLD_NOW);
if (handle != nullptr) {
return handle;
}
*error_msg = strdup(NativeBridgeGetError());
} else {
*error_msg = strdup(dlerror());
}
}
return nullptr;
#endif
}

android_dlopen_ext声明在android/dlext.h中,并通过system/core/libnativeloader/include/nativeloader/dlext_namespaces.h间接include

1
2
void* android_dlopen_ext(const char* __filename, int __flags, const android_dlextinfo* __info)
__INTRODUCED_IN(21);

android_dlopen_extbionic/libdl/libdl.cpp中实现

1
2
3
4
5
__attribute__((__weak__))
void* android_dlopen_ext(const char* filename, int flag, const android_dlextinfo* extinfo) {
const void* caller_addr = __builtin_return_address(0);
return __loader_android_dlopen_ext(filename, flag, extinfo, caller_addr);
}

同时还在此文件中声明原型

1
2
3
4
5
__attribute__((__weak__, visibility("default")))
void* __loader_android_dlopen_ext(const char* filename,
int flag,
const android_dlextinfo* extinfo,
const void* caller_addr);

还需要在bionic/libdl/libdl.map.txt中声明接口(坑点)

1
2
3
4
5
6
7
8
9
10
11
12
13
LIBC {
global:
android_dlopen_ext; # introduced=21
dl_iterate_phdr; # introduced-arm=21
dl_unwind_find_exidx; # arm
dladdr;
dlclose;
dlerror;
dlopen;
dlsym;
local:
*;
};

以下开始linker部分

bionic/linker/dlfcn.cpp声明__loader_android_dlopen_ext原型

1
2
3
4
void* __loader_android_dlopen_ext(const char* filename,
int flags,
const android_dlextinfo* extinfo,
const void* caller_addr) __LINKER_PUBLIC__;

实现

1
2
3
4
5
6
void* __loader_android_dlopen_ext(const char* filename,
int flags,
const android_dlextinfo* extinfo,
const void* caller_addr) {
return dlopen_ext(filename, flags, extinfo, caller_addr);
}

bionic/linker/ld_android.cpp中写下(不写libdl.so无法链接到linker)

1
__strong_alias(__loader_android_dlopen_ext, __internal_linker_error);

除此之外还需要在bionic/linker/linker.arm.mapbionic/linker/linker.generic.map分别写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
global:
__loader_dlopen;
__loader_dlclose;
__loader_dlsym;
__loader_dlerror;
__loader_dladdr;
__loader_android_update_LD_LIBRARY_PATH;
__loader_android_get_LD_LIBRARY_PATH;
__loader_dl_iterate_phdr;
__loader_android_dlopen_ext;
__loader_android_set_application_target_sdk_version;
__loader_android_get_application_target_sdk_version;
__loader_android_init_anonymous_namespace;
__loader_android_create_namespace;
__loader_dlvsym;
__loader_android_dlwarning;
__loader_cfi_fail;
__loader_android_link_namespaces;
__loader_android_link_namespaces_all_libs;
__loader_android_get_exported_namespace;
__loader_dl_unwind_find_exidx;
__loader_add_thread_local_dtor;
__loader_remove_thread_local_dtor;
__loader_shared_globals;
rtld_db_dlactivity;
local:
*;
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
global:
__loader_dlopen;
__loader_dlclose;
__loader_dlsym;
__loader_dlerror;
__loader_dladdr;
__loader_android_update_LD_LIBRARY_PATH;
__loader_android_get_LD_LIBRARY_PATH;
__loader_dl_iterate_phdr;
__loader_android_dlopen_ext;
__loader_android_set_application_target_sdk_version;
__loader_android_get_application_target_sdk_version;
__loader_android_init_anonymous_namespace;
__loader_android_create_namespace;
__loader_dlvsym;
__loader_android_dlwarning;
__loader_cfi_fail;
__loader_android_link_namespaces;
__loader_android_link_namespaces_all_libs;
__loader_android_get_exported_namespace;
__loader_add_thread_local_dtor;
__loader_remove_thread_local_dtor;
__loader_shared_globals;
rtld_db_dlactivity;
local:
*;
};

(2)从linker中导出solist_get_head()给nativLoader.so用

linker和ld-android部分

首先在bionic/linker/dlfcn.cpp中include头文件

1
#include "linker_main.h"

然后在extern "C"中添加导出接口声明

1
void* __loader_get_head() __LINKER_PUBLIC__;

定义:

1
2
3
void* __loader_get_head(){
return solist_get_head();
}

然后bionic/linker/ld_android.cpp中添加

1
__strong_alias(__loader_get_head, __internal_linker_error);

然后bionic/linker/linker.arm.mapbionic/linker/linker.generic.map中均添加

1
2
3
4
5
6
7
8
{
global:
...
__loader_android_dlopen_ext;
__loader_get_head;
...
};

libdl部分

bionic/libdl/libdl.cpp中添加

1
2
__attribute__((__weak__, visibility("default")))
void* __loader_get_head();//原型声明,实际实现在linker中

1
2
3
4
__attribute__((__weak__))
void* get_head() {//函数定义
return __loader_get_head();
}

并在头文件bionic/libc/include/android/dlext.h中添加get_head函数声明

1
2
void* get_head()
__INTRODUCED_IN(21);

然后在system/core/libnativeloader/native_loader.cpp的OpenNativeLibrary函数中使用get_head函数进行测试

1
2
void* head = get_head();
ALOGW("showfaker find \"%p\" is listhead", head);

效果展示:

image-20240515162838816

(3)库依赖分析

上面我们算是把需求实现了,这其中之所以这么麻烦,是因为每部分都是在单独的库里,然后通过动态链接来引用/调用的,如果都是在同一个库里,那直接include头文件然后调用就可以了,如果链接不对,通常是使用的函数所在库没有正确导出这个符号。

nativeloader

system/core/libnativeloader/Android.bp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
cc_library {
name: "libnativeloader",
defaults: ["libnativeloader-defaults"],
host_supported: true,
srcs: ["native_loader.cpp"],
shared_libs: [
"libnativehelper",
"liblog",
"libnativebridge",
"libbase",
],
target: {
android: {
shared_libs: [
"libdl_android",
],
},
},
required: [
"llndk.libraries.txt",
"vndksp.libraries.txt",
],
stubs: {
symbol_file: "libnativeloader.map.txt",
versions: ["1"],
},
}
libdl

bionic/libdl/Android.bp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
cc_library {
name: "libdl",
recovery_available: true,
static_ndk_lib: true,

defaults: ["linux_bionic_supported"],

// NOTE: --exclude-libs=libgcc.a makes sure that any symbols libdl.so pulls from
// libgcc.a are made static to libdl.so. This in turn ensures that libraries that
// a) pull symbols from libgcc.a and b) depend on libdl.so will not rely on libdl.so
// to provide those symbols, but will instead pull them from libgcc.a. Specifically,
// we use this property to make sure libc.so has its own copy of the code from
// libgcc.a it uses.
//
// DO NOT REMOVE --exclude-libs!

ldflags: [
"-Wl,--exclude-libs=libgcc.a",
"-Wl,--exclude-libs=libgcc_stripped.a",
"-Wl,--exclude-libs=libclang_rt.builtins-arm-android.a",
"-Wl,--exclude-libs=libclang_rt.builtins-aarch64-android.a",
"-Wl,--exclude-libs=libclang_rt.builtins-x86-android.a",
"-Wl,--exclude-libs=libclang_rt.builtins-x86_64-android.a",
],

// for x86, exclude libgcc_eh.a for the same reasons as above
arch: {
arm: {
version_script: ":libdl.arm.map",
pack_relocations: false,
ldflags: ["-Wl,--hash-style=both"],
},
arm64: {
version_script: ":libdl.arm64.map",
},
x86: {
pack_relocations: false,
ldflags: [
"-Wl,--exclude-libs=libgcc_eh.a",
"-Wl,--hash-style=both",
],
version_script: ":libdl.x86.map",
},
x86_64: {
ldflags: ["-Wl,--exclude-libs=libgcc_eh.a"],
version_script: ":libdl.x86_64.map",
},
},
shared: {
whole_static_libs: ["libdl_static"],
},
static: {
srcs: ["libdl_static.cpp"],
},
cflags: [
"-Wall",
"-Wextra",
"-Wunused",
"-Werror",
],
stl: "none",

nocrt: true,
system_shared_libs: [],

// Opt out of native_coverage when opting out of system_shared_libs
native_coverage: false,

// This is placeholder library the actual implementation is (currently)
// provided by the linker.
shared_libs: ["ld-android"],

sanitize: {
never: true,
},

stubs: {
symbol_file: "libdl.map.txt",
versions: ["10000"],
},
}
ld-android
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
cc_library {
// NOTE: --exclude-libs=libgcc.a makes sure that any symbols ld-android.so pulls from
// libgcc.a are made static to ld-android.so. This in turn ensures that libraries that
// a) pull symbols from libgcc.a and b) depend on ld-android.so will not rely on ld-android.so
// to provide those symbols, but will instead pull them from libgcc.a. Specifically,
// we use this property to make sure libc.so has its own copy of the code from
// libgcc.a it uses.
//
// DO NOT REMOVE --exclude-libs!

ldflags: [
"-Wl,--exclude-libs=libgcc.a",
"-Wl,--exclude-libs=libgcc_stripped.a",
"-Wl,--exclude-libs=libclang_rt.builtins-arm-android.a",
"-Wl,--exclude-libs=libclang_rt.builtins-aarch64-android.a",
"-Wl,--exclude-libs=libclang_rt.builtins-x86-android.a",
"-Wl,--exclude-libs=libclang_rt.builtins-x86_64-android.a",
],

// for x86, exclude libgcc_eh.a for the same reasons as above
arch: {
arm: {
version_script: "linker.arm.map",
},
arm64: {
version_script: "linker.generic.map",
},
x86: {
ldflags: ["-Wl,--exclude-libs=libgcc_eh.a"],
version_script: "linker.generic.map",
},
x86_64: {
ldflags: ["-Wl,--exclude-libs=libgcc_eh.a"],
version_script: "linker.generic.map",
},
mips: {
version_script: "linker.generic.map",
},
mips64: {
version_script: "linker.generic.map",
},
},

srcs: ["ld_android.cpp"],
cflags: [
"-Wall",
"-Wextra",
"-Wunused",
"-Werror",
],
stl: "none",

name: "ld-android",
defaults: ["linux_bionic_supported"],
recovery_available: true,

nocrt: true,
system_shared_libs: [],

// Opt out of native_coverage when opting out of system_shared_libs
native_coverage: false,

sanitize: {
never: true,
},
}
linker
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
cc_binary {
defaults: ["linux_bionic_supported", "linker_defaults"],
srcs: [ ":linker_sources" ],

arch: {
arm: {
srcs: [ ":linker_sources_arm" ],
version_script: ":linker_version_script_arm",
},
arm64: {
srcs: [":linker_sources_arm64"],
version_script: ":linker_version_script",
},
x86: {
srcs: [":linker_sources_x86"],
version_script: ":linker_version_script",
},
x86_64: {
srcs: [":linker_sources_x86_64"],
version_script: ":linker_version_script",
},
mips: {
srcs: [":linker_sources_mips"],
version_script: ":linker_version_script",
},
mips64: {
srcs: [":linker_sources_mips64"],
version_script: ":linker_version_script",
},
},

// We need to access Bionic private headers in the linker.
include_dirs: ["bionic/libc"],

static_libs: [
"libc_nomalloc",
"libm",
"libziparchive",
"libutils",
"libbase",
"libz",

"libasync_safe",

"liblog",
"libc++_static",

// Important: The liblinker_malloc should be the last library in the list
// to overwrite any other malloc implementations by other static libraries.
"liblinker_malloc",
],

name: "linker",
symlinks: ["linker_asan"],
recovery_available: true,
multilib: {
lib32: {
cflags: ["-DLIB_PATH=\"lib\""],
},
lib64: {
cflags: ["-DLIB_PATH=\"lib64\""],
suffix: "64",
},
},
system_shared_libs: [],

// Opt out of native_coverage when opting out of system_shared_libs
native_coverage: false,

target: {
android: {
static_libs: ["libdebuggerd_handler_fallback"],
},
},
compile_multilib: "both",
xom: false,
}

自定义linker踩坑记录
http://showfaker.top/2024/05/17/linker-add-interface-md/
作者
ShowFaker
发布于
2024年5月17日
许可协议