首页
社区
课程
招聘
[分享]Android SO文件加载过程探究
发表于: 2025-2-28 17:03 4048

[分享]Android SO文件加载过程探究

2025-2-28 17:03
4048

Android SO文件加载过程探究

在安卓中的app进行so加载过程中,分析一下so的动态静态的so加载过程

在 Android 中,.so 文件是 共享库文件,.so 文件可以分为 动态链接库(动态 .so 文件)和 静态链接库(静态 .a 文件),但 Android 中一般更常见的是动态 .so 文件,静态链接库通常在编译时被集成到最终的应用中,而不直接加载。所以经常看到的so文件的链接大多是都是以动态链接的

  1. 动态链接会利用对应的打包的生成的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);  // 加载libsofile.so
    }

    或者通过动态加载路径的so文件的过程来实现

    1
    2
    String soPath = "/data/data/com.example.libsofile/libsofile.so";
    System.load(soPath);
  2. 在 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
//libcore/ojluni/src/main/java/java/lang/Runtime.java
    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;
    }
  }
 
  // Don't let a pending exception from JNI_OnLoad cause a CheckJNI issue with NewStringUTF.
  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
//art/libnativeloader/native_loader.cpp
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) {
    // class_loader is null only for the boot class loader (see
    // IsBootClassLoader call in JavaVMExt::LoadNativeLibrary), i.e. the caller
    // is in the boot classpath.
    *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
//bionic/linker/dlfcn.cpp
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编辑 ,原因:
收藏
免费 38
支持
分享
最新回复 (21)
雪    币: 2731
活跃值: (3162)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
2
蛮不错的 !
2025-3-1 10:52
0
雪    币: 20
活跃值: (1610)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
蛮不错的 !
2025-3-3 14:03
0
雪    币: 534
活跃值: (834)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
666
2025-3-3 16:26
0
雪    币: 1472
活跃值: (2098)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
5
2025-3-3 17:04
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
6666
2025-3-3 18:39
0
雪    币: 11465
活跃值: (3117)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
6666
2025-3-3 22:03
0
雪    币: 633
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8
6666
2025-3-4 10:31
0
雪    币: 375
活跃值: (2411)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
9
666
2025-3-5 13:57
0
雪    币: 228
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
10
感谢分享
2025-3-6 10:00
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
11
666
2025-3-6 11:17
0
雪    币: 9
活跃值: (798)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
6666
2025-3-7 18:17
0
雪    币: 160
活跃值: (208)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
666
6天前
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
14
666
6天前
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
15
6
5天前
0
雪    币: 19
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
16
好文章
2天前
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
17
看看再说
2天前
0
雪    币: 59
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
18
66
2天前
0
雪    币: 411
活跃值: (1661)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
能否动态定义System.loadLibrary("libsofile") 对应默认目录为自定义私有目录
2天前
0
雪    币: 2059
活跃值: (211)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
20
6
1天前
0
雪    币: 329
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
21
666
1天前
0
雪    币: 940
活跃值: (1232)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
66
23小时前
0
游客
登录 | 注册 方可回帖
返回