Android SO文件加载过程探究
在安卓中的app进行so加载过程中,分析一下so的动态静态的so加载过程
在 Android 中,.so
文件是 共享库文件,.so
文件可以分为 动态链接库(动态 .so
文件)和 静态链接库(静态 .a
文件),但 Android 中一般更常见的是动态 .so
文件,静态链接库通常在编译时被集成到最终的应用中,而不直接加载。所以经常看到的so文件的链接大多是都是以动态链接的
-
动态链接会利用对应的打包的生成的APK,按照对应的架构(lib/armeabi-v7a/,lib/arm64-v8a/,lib/x86/,lib/x86_64/)去选择对应的so文件,然后去实现在 Java 层,通过 JNI 来进行。
Java 代码使用 静态System.loadLibrary("libsofile")
来加载共享库文件。
1
2
3
|
static {
System.loadLibrary("libsofile);
}
|
或者通过动态加载路径的so文件的过程来实现
1
2
|
String soPath = "/data/data/com.example.libsofile/libsofile.so" ;
System.load(soPath);
|
-
在 Android 中,静态链接库(.a
文件)是被链接到最终的可执行文件中的,而不是在运行时加载。Android NDK 编译时,静态库会被打包到 APK 中的应用代码部分。
我们要去探究SO文件最真实的加载过程就要从System.load(sopath)这里开始,去剖析安卓源码
安卓源码
安卓源码剖析:
System.load(sopath)开始进行解析,查看整个so文件加载过程
System.load(sopath)
1
2
3
4
|
@CallerSensitive
public static void load(String filename) {
Runtime.getRuntime().load0(Reflection.getCallerClass(), filename);
}
|
先解释一下这里的情况Reflection.getCallerClass()
通过反射机制获取调用此方法的类的引用。它返回的是调用 load0
方法的 调用者类。这里加载到了直接去加载了load0函数。
load0(Class> fromClass, String filename)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
synchronized void load0(Class> fromClass, String filename) {
File file = new File(filename);
if (!(file.isAbsolute())) {
throw new UnsatisfiedLinkError(
"Expecting an absolute path of the library: " + filename);
}
if (filename == null ) {
throw new NullPointerException( "filename == null" );
}
if (Flags.readOnlyDynamicCodeLoad()) {
if (!file.toPath().getFileSystem().isReadOnly() && file.canWrite()) {
if (VMRuntime.getSdkVersion() >= VersionCodes.VANILLA_ICE_CREAM) {
System.logW( "Attempt to load writable file: " + filename
+ ". This will throw on a future Android version" );
}
}
}
String error = nativeLoad(filename, fromClass.getClassLoader(), fromClass);
if (error != null ) {
throw new UnsatisfiedLinkError(error);
}
}
|
在这里去检测了对应加载过程中的sofile。然后就开始往nativeLoad函数走了
nativeLoad(filename, fromClass.getClassLoader(), fromClass);

这里直接是naitve函数了,我们要去看对应的c文件,所以要重新去搜索了,这里的搜索方法就是类名_函数名的形式,转换过程就是Runtime_nativeLoad函数
Runtime_nativeLoad
1
2
3
4
5
6
|
JNIEXPORT jstring JNICALL
Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,
jobject javaLoader, jclass caller)
{
return JVM_NativeLoad(env, javaFilename, javaLoader, caller);
}
|
这里是最正常的返回,直接走 JVM_NativeLoad(env, javaFilename, javaLoader, caller)
JVM_NativeLoad(env, javaFilename, javaLoader, caller)
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;
}
}
env->ExceptionClear();
return env->NewStringUTF(error_msg.c_str());
}
|
同样得直接向下去分析就好了 vm->LoadNativeLibrary函数

vm->LoadNativeLibrary(env, filename.c_str(),javaLoader,caller,&error_msg);
这里的大多数的函数都是对于so加载中的中途函数,也就是一层一层得调用到关键函数的,所以这里直接往下走就是了
在 JavaVMExt::LoadNativeLibrary这个函数中有需要去注意和理解的地方,同时这里也是在进行调用dlopen来进行真正so文件加载的地方。

1
2
3
4
5
6
7
8
9
10
11
12
|
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
if (class_linker->IsBootClassLoader(loader)) {
loader = nullptr;
class_loader = nullptr;
}
if (caller_class != nullptr) {
ObjPtr caller = soa.Decode(caller_class);
ObjPtr dex_cache = caller->GetDexCache();
if (dex_cache != nullptr) {
caller_location = dex_cache->GetLocation()->ToModifiedUtf8();
}
}
|
首先是这里的Linker的位置,这里去解码了 ClassLoader 和 Caller Class 信息,同时去判断了加载器是否为 BootClassLoader
。其实在so加载过程也有借助linker判断so文件结构,链接的位置则是so文件的头部,判断的是so文件结构是否正确。

这里也去判断了这里加载的so文件是否以及被加载过了,最后开始的对于共享库so的加载(dlopen)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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 << "]" ;
if (handle == nullptr) {
*error_msg = nativeloader_error_msg;
android::NativeLoaderFreeErrorMessage(nativeloader_error_msg);
VLOG(jni) << "dlopen(\"" << path << "\", RTLD_NOW) failed: " << *error_msg;
return false ;
|
OpenNativeLibrary

在这里开始找到了我们最为熟悉的 android_dlopen_ext(path, RTLD_NOW, &dlextinfo);函数,也就是经常进行HOOK的位置了
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
|
void * 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) {
#if defined(ART_TARGET_ANDROID)
if (class_loader == nullptr) {
*needs_native_bridge = false ;
if (caller_location != nullptr) {
std::optional ns = FindApexNamespace(caller_location);
if (ns.has_value()) {
const android_dlextinfo dlextinfo = {
.flags = ANDROID_DLEXT_USE_NAMESPACE,
.library_namespace = ns.value().ToRawAndroidNamespace(),
};
void * handle = android_dlopen_ext(path, RTLD_NOW, &dlextinfo);
char * dlerror_msg = handle == nullptr ? strdup(dlerror()) : nullptr;
ALOGD( "Load %s using APEX ns %s for caller %s: %s" ,
path,
ns.value().name().c_str(),
caller_location,
dlerror_msg == nullptr ? "ok" : dlerror_msg);
if (dlerror_msg != nullptr) {
*error_msg = dlerror_msg;
}
return handle;
}
}
|
在android12中会直接由 android_dlopen_ext直接返回到 __loader_android_dlopen_ext函数,而在其他版本可以会到 mock->mock_dlopen_ext(这里会走到mock_dlopen_ext
会模拟 dlopen
的行为,同时通过flag和宏定义走到不同的函数位置)

这里我们固定在android12的位置去实现。
__loader_android_dlopen_ext
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);
}
|
直接的返回进入下一个函数。
dlopen_ext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
static void * dlopen_ext( const char * filename,
int flags,
const android_dlextinfo* extinfo,
const void * caller_addr) {
ScopedPthreadMutexLocker locker(&g_dl_mutex);
g_linker_logger.ResetState();
void * result = do_dlopen(filename, flags, extinfo, caller_addr);
if (result == nullptr) {
__bionic_format_dlerror( "dlopen failed" , linker_get_error_buffer());
return nullptr;
}
return result;
}
|
同样进入do_dlopen(filename, flags, extinfo, caller_addr)
do_dlopen(filename, flags, extinfo, caller_addr)
在这个函数中附加了很多对于do_dlopen函数参数的检测和判断

这种大面积的对于extinfo,对于so文件相关的属性进行的检测。

通过还对于这里的path进行了对应路径的转换和翻译。

1
2
3
|
ProtectedDataGuard guard;
soinfo* si = find_library(ns, translated_name, flags, extinfo, caller);
loading_trace.End();
|
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2025-3-2 19:05
被ovo_帮我不c编辑
,原因: