首页
社区
课程
招聘
[原创]自定义Linker实现分析之路(一)
发表于: 2024-4-2 11:22 28691

[原创]自定义Linker实现分析之路(一)

2024-4-2 11:22
28691

环境准备

  • 手机:Pixel 6a
  • 系统:Android 12
  • Linker源码 android12-d1-release
  • 一个好的IDE对阅读源码来说事半功倍,这里推荐CLion

前言

Linker是Android系统中的一个重要组件,它负责将各个模块编译生成的so文件链接成一个整体,以及对so文件进行重定位等操作。Linker的源码位于bionic/linker目录下,通过对Linker源码的学习、分析,我们也可以实现一个自定义的Linker加载器

Linker的加载过程

Linker的加载过程主要包括以下几个步骤:

  1. 读取程序头表,获取动态段信息
  2. 加载动态段中的so文件
  3. 进行重定位操作
  4. 初始化so文件中的全局变量
  5. 调用so文件中的初始化函数
  6. 结束

Linker源码核心函数详解

本文只讨论在dlopen函数下打开一个So的链接过程,Jni情况下的链接过程不在本文讨论范围内。

接下来使用dlopen函数来打开一个so文件,
我们来看一下dlopen函数的实现

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

我们可以看到最终调用的是do_dlopen函数,do_dlopen即为链接器的起始入口,我们来看一下do_dlopen函数的实现,代码的位置在bionic/linker/linker.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
void* do_dlopen(const char* name, int flags,
                const android_dlextinfo* extinfo,
                const void* caller_addr) {
 ...
  soinfo* const caller = find_containing_library(caller_addr);
  android_namespace_t* ns = get_caller_namespace(caller);
 ...
 ...
  soinfo* si = find_library(ns, translated_name, flags, extinfo, caller);
  loading_trace.End();
 
  if (si != nullptr) {
    void* handle = si->to_handle();
    LD_LOG(kLogDlopen,
           "... dlopen calling constructors: realpath=\"%s\", soname=\"%s\", handle=%p",
           si->get_realpath(), si->get_soname(), handle);
    si->call_constructors();
    failure_guard.Disable();
    LD_LOG(kLogDlopen,
           "... dlopen successful: realpath=\"%s\", soname=\"%s\", handle=%p",
           si->get_realpath(), si->get_soname(), handle);
    return handle;
  }
 
  return nullptr;
}

由于篇幅的原因,这里只展示了部分核心代码,do_dlopen函数的主要功能是通过find_library函数找到指定的so文件,然后调用so文件中的构造函数(即init_array),最后返回so文件的句柄。
这里有个 android_namespace_t* ns = get_caller_namespace(caller);
这个函数是获取调用者的namespace,namespace是Android 7.0引入的概念,用于解决so文件的命名冲突问题,这里不做详细讨论。

我们来看一下find_library函数的实现

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
static soinfo* find_library(android_namespace_t* ns,
                            const char* name, int rtld_flags,
                            const android_dlextinfo* extinfo,
                            soinfo* needed_by) {
  soinfo* si = nullptr;
  if (name == nullptr) {
    si = solist_get_somain();
  } else if (!find_libraries(ns,
                             needed_by,
                             &name,
                             1,
                             &si,
                             nullptr,
                             0,
                             rtld_flags,
                             extinfo,
                             false /* add_as_children */)) {
    if (si != nullptr) {
      soinfo_unload(si);
    }
    return nullptr;
  }
  si->increment_ref_count();
  return si;
}
}

可以看到只是一个中转函数,最终调用的是find_libraries函数,find_libraries就是比较重要的部分了 我们将详细的对其进行分析

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
bool find_libraries(android_namespace_t *ns,
                    soinfo *start_with,
                    const char *const library_names[],
                    size_t library_names_count,
                    soinfo *soinfos[],
                    std::vector *ld_preloads,
                    size_t ld_preloads_count,
                    int rtld_flags,
                    const android_dlextinfo *extinfo,
                    bool add_as_children,
                    std::vector *namespaces = nullptr) {
...
  // Step 0: prepare.
    // 第一步开始准备 加入一个tasks list里
    std::unordered_map<const soinfo *, ElfReader> readers_map;
    LoadTaskList load_tasks;
    for (size_t i = 0; i < library_names_count; ++i) {
        const char *name = library_names[i];
        LOGI("load task create %s ", name);
        load_tasks.push_back(LoadTask::create(name, start_with, ns, &readers_map));
    }
 }
 ...
    //这一步是动态链接过程中的一个重要环节,其目的是扩展要加载的库(load_tasks)列表,
    // 以包括所有通过DT_NEEDED条目指定的依赖库。DT_NEEDED条目是在ELF(Executable and Linkable Format)文件的动态段中指定的,
    // 表示当前库需要加载的其他库。这一步骤并不立即加载这些依赖库,而是准备加载任务
  for (size_t i = 0; i < load_tasks.size(); ++i) {
        LoadTask *task = load_tasks[i];
        soinfo *needed_by = task->get_needed_by();
        bool is_dt_needed = needed_by != nullptr && (needed_by != start_with || add_as_children);
        task->set_extinfo(is_dt_needed ? nullptr : extinfo);
        task->set_dt_needed(is_dt_needed);
 
        LOGI("find_libraries(ns=%s): task=%s, is_dt_needed=%d", "null",
             task->get_name(), is_dt_needed);
        //
        // Note: start from the namespace that is stored in the LoadTask. This namespace
        // is different from the current namespace when the LoadTask is for a transitive
        // dependency and the lib that created the LoadTask is not found in the
        // current namespace but in one of the linked namespace.
        if (!find_library_internal(const_cast(task->get_start_from()),
                                   task,
                                   &zip_archive_cache,
                                   &load_tasks,
                                   rtld_flags)) {
            return false;
        }
  }

我们在for循环下面之后先不进行展开分析,我们先看一下find_library_internal函数的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static bool find_library_internal(android_namespace_t *ns,
                                  LoadTask *task,
                                  ZipArchiveCache *zip_archive_cache,
                                  LoadTaskList *load_tasks,
                                  int rtld_flags) {
    soinfo *candidate;
    if (find_loaded_library_by_soname(ns, task->get_name(), true /* search_linked_namespaces */,
                                      &candidate)) {
        LOGI(
                "find_library_internal(ns=%s, task=%s): Already loaded (by soname): %s",
                ns->get_name(), task->get_name(), candidate->get_realpath());
        task->set_soinfo(candidate);
        return true;
    }
    //start  load_library
    if (load_library(ns, task, zip_archive_cache, load_tasks, rtld_flags,
                     true /* search_linked_namespaces */)) {
        return true;
    }
    return false;
}

可以看到代码非常清晰,先查找是否已经加载了so文件,如果没有则调用load_library函数进行加载,我们来看一下load_library函数的实现


[注意]看雪招聘,专注安全领域的专业人才平台!

最后于 2024-4-3 14:07 被IIImmmyyy编辑 ,原因:
收藏
免费 7
支持
分享
最新回复 (8)
雪    币: 102
活跃值: (2470)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2
厉害了,顶lmy大佬。
2024-4-2 18:05
0
雪    币: 268
活跃值: (1071)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
厉害
2024-4-3 11:52
0
雪    币: 4033
活跃值: (31446)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
感谢分享
2024-4-5 21:36
1
雪    币: 8489
活跃值: (2975)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
感谢分享,学习一下
2024-4-14 05:59
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
感谢分享,我有问题了可以问你吗?
2024-4-18 16:08
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
mark
2024-5-20 11:20
0
雪    币: 498
活跃值: (4461)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
这是要自己定义linker修复pairipcore么
2024-5-23 14:17
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
9
自定义linker加载so时,try-catch会失效,无法捕获到异常了。大佬知道是什么原因吗
2024-5-30 14:56
0
游客
登录 | 注册 方可回帖
返回