Android dynamic linker

/system/bin/linker是Android的dynamic linker。尽管它是个很小的程序,但它的实现里依然有不少值得学习的地方。

由于dynamic linker要在libc.so被载入之前工作,因此没有malloc可用。它使用了两个算法,为不同目的分配(并管理)内容。

一类内存是为了载入动态链接库的代码段和数据段,这块内存使用buddy memory allocation算法管理。虽然是个简洁的算法,但如果是第一次接触,想通过阅读代码的方式理解它的工作原理,还是有些难度的。幸好维基百科上的条目解释得非常清楚,在此基础上,一些实现上的小技巧也就好理解了。

另一类是使用array + freelist的方式管理soinfo数据。因为数据类型相同,可以使用静态分配的数组;由于soinfo数据可能动态添加和删除,使用array + freelist方式,可以在O(1)内找到空余的项目。

还有一个很神奇的地方,即如果要使用dlopen/dlclose之类的函数,需要链接libdl.so这个动态库,然而在运行时调用的,确是dynamic linker里面的实现。

简单地说,提供libdl.so是为了让linker知道,dlopen/dlclose这几个函数是存在的,在静态链接时不要报undefined reference之类的错误。在实际调用的时候,dynamic linker把这几个symbol的值解析为自己的实现,以达到代码复用,避免重复的目的。在具体实现里,libdl.so对应的soinfo,并不是像其它动态链接库一样,是通过分析对应的文件得来的,而是dynamic linker硬造出来的,使得dlopen/dlclose等symbol解析为dynamic linker内的实现。

collect2

本以为gcc会调用cc1asld来完成一系列编译、汇编和链接的工作,实际上,gcc是通过collect2间接调用ld的。

collect2主要实现了两个额外的功能,都是为C++程序准备的。其一是生成代码调用全局静态对象的构造函数和析构函数;其二是支持Cfront方式生成模板代码。

C++程序的全局静态对象,需要在main函数运行前构造好,在main函数执行完成后析构掉。构造函数和析构函数是由程序员编写的,那么该由谁负责调用呢?通常来讲,调用这些函数的工作由初始化代码完成,比如,GNU ld就可以生成这些代码。如果这些代码不能由linker自动生成,就需要collect2的帮助。

collect2调用ld生成一个执行程序,然后调用nm程序,根据命名规则,查找所有的调用全局静态对象构造函数和析构函数的函数,并将这些函数保存为一张表,再生成一段代码遍历每个表项,调用对应的函数。所有这些信息被保存为一个C程序,然后编译、汇编为目标文件,在链接时作为第一个目标文件传给linker,做第二次链接。此功能实现在gcc/collect2.c文件内。通过在config.gcc文件里查找use_collect2=yes来确定哪些系统使用了该功能,PC上默认是关闭的。

Cfront支持代码实现在gcc/tlink.c文件里,这里的调用关系就更复杂了。gcc在编译C++程序时不生成任何模板代码,而是使用-frepo生成一个辅助文件,collect2调用ld做链接后,分析错误信息,提取未定义的符号,然后通过分析所有的repo文件,判断要生成哪些模板代码,并调用gcc重新编译特定文件,然后再链接,如果还有未定义的符号,则重复上述过程。如此反复,最多17次。如果还不能正确链接,则报错。

查看collect2的手册

info gccint 'collect2'

查看Cfront模型

info gcc 'C++ Extensions' 'Template Instantiation'