动态链接器
动态链接器属于glibc的一部分, 本文章用glibc2.17分析
link map
一个可执行文件或一个共享库对应一个link map, link map有三种类型, lt_executable, lt_library和lt_loaded。 其中lt_executable代表可执行文件,lt_loaded代表dlopen的共享库, lt_library代表lt_executable或lt_loaded或lt_library的DT_NEEDED项的link map。(或是预加载的共享库?)
link map 是got.plt的第二项的值, 数据结构为 struct link_map。结构体中的成员含义如下:
成员 | 含义 |
---|---|
l_type | link map类型 |
l_ld | 指向dynamic section |
l_ldnum | dynamic section entity 数量 |
l_info | dynamic section 解析后的数据 |
l_direct_opencoun | dlopen次数 |
l_rpath_dirs | DT_RPATH数据 |
real | link map的地址 |
l_global | 对应RTLD_GLOBAL和RTLD_LOCAL,该动态库的符号是否可以用于后续的符号解析(PS:manpage 上写的是后续加载的动态库的符号解析, 我理解的是后续的符号解析,因为当先打开main program的link_map再加载该动态库, 用main program的link map 也可以解析到该动态库函数的地址) |
l_reserved | 获取新模块的link map时用于判断模块否加载过 |
l_loader | 装载该模块的模块,main program 和 dlopen的共享库都是NULL |
l_name | link_map 对应的模块的名字 main program对应的是"“,所以可以用dlopen(”")获取 main program的link map |
l_scope | 该命名空间的loader的searchlist。如果是dlopen的并且不是主模块依赖或者依赖的依赖的object, 还包括该object的searchlist, [0]为命名空间的loader的searchlist,[1]为该object的searchlist |
l_local_scope[0] | 该object的searchlist |
l_scope_mem | l_scope指向的内存空间 |
l_symbolic_searchlist.r_list | |
l_libname | libname是一个链表, 包括realname,soname. realname包含路径, soname可以通过readelf -d查询.相关的还有linkname, 为lib*.so格式, 不包含版本信息, 用于静态链接.ldconfig可以生成一个名为soname的软链接, ldconfig -p的输出为soname => realname .realname可以是一个链接, linkname可以指向ld脚本 |
l_initfini | main program存放所有的依赖,其它存放直接依赖. main program会重排, 把依赖的放在被依赖的前面. 调用object的构造析构函数会使用它 |
l_init_called | 构造函数是否已经被调用过 |
l_searchlist | 包括可执行文件模块和依赖的所有共享库的link map, 指向l_initfini的后半段. 可执行文件在最前面, 广度优先搜索排序 |
l_ns | namespace, 和loader共享一个namespace |
l_phdr | 程序头虚拟地址偏移 |
l_map_start | 模块映射的起始地址 |
l_map_end | 模块映射的终止地址, 通过PT_LOAD的segment算出来 |
l_contiguous | 模块映射各个segment之间是否有空洞 |
l_versions | .gnu.version_r section 数据 |
l_used | 是否有符号被引用 |
l_serial | 进程第几个加载该共享库 |
l_ino | 共享库inode |
l_dev | 用于区分共享库所在的文件系统 |
特殊的link map
1 RTLD_DEFAULT 搜索 base namespace
2 RTLD_NEXT 按namespace的搜索顺序搜索, 略过调用的link map及前面的link map
3 vdso的link map 属于base namespace, 但是loader不是main program, 没有loader
关键函数
_dl_map_object_deps -> openaux -> _dl_map_object -> _dl_map_object_from_fd -> _dl_new_object
_dl_map_object 在已经加载的模块中寻找object, 如果找不到的话,获取共享库的realname并打开, 接着调用_dl_map_object_from_fd加载object, 创建link map
_dl_map_object_deps 调用_dl_map_object, 打开依赖的模块
_dl_map_object_from_fd 调用_dl_new_object创建link map并用mmap 的MAP_PRIVATE 映射object的LOAD segment
_dl_new_object 创建一个 link map, 初始化内容
openaux 调用_dl_map_object, 通过原模块的link map类型确认打开的新模块的link map 类型
_dl_start -> _dl_start_final ->_dl_sysdep_start -> dl_main -> _dl_map_object_deps
-> _dl_relocate_object
_dl_start_user -> _dl_init
_dl_relocate_object 装载时解析符号, 用l_scope
_dl_runtime_resolve 运行时解析符号, 用l_scope. .got.plt第三项的值
dl_sym 手动解析符号, DT_DEFAULT时用本object的l_scope, DT_NEXT时用loader的l_local_scope, 用dlopen的handle时用handle(也是一个link_map)的l_local_scope
do_sym 根据handle类型确认
_dl_lookup_symbol_x
全局变量
//?GL(dl_ns)[0]._ns_main_searchlist指向全局符号的模块链表的scope,其它指向dlmopen的名字空间
GL(dl_rtld_map) 动态链接器的link map
GL(dl_nns) 使用的名字空间的数量
GL(dl_load_adds) 装载的共享库的数量
GLRO(dl_lazy) 是否默认为lazy加载
常量
LM_ID_BASE main program 和它的依赖所在的nsid
__LM_ID_CALLER dlopen打开的link map的默认nsid,会重新赋值loader的nsid
宏
RTLD_NOW
RTLD_LAZY
在加载的时候还是引用的时候解析未定义的函数动态符号
RTLD_LOCAL
RTLD_GLOBAL
解析动态符号的时候会搜索一系列的object.把这一系列的object称为scope.scope又分为global scope和local scope.全局scope包括lt_executable和它所有的依赖项或lt_loaded和它的所有依赖项.用local scope的时候包括object的所有依赖项.
dlopen的handle能查找全局符号和本object的符号
RTLD_GLOBAL会把该object加入lt_executable的全局scope.
RTLD_LOCAL 之后加载的object不能查找该object的符号.
RTLD_DEFAULT 查找调用模块的l_scope
RTLD_NEXT 查找调用模块的起始loader的l_local_scope(lt_executable
的l_local_scope和l_scope相同, 但是lt_loaded
的l_local_scope比l_scope少了lt_executabled的searchlist), 跳过调用模块和调用模块前所有模块
直接用handle 查找handle的l_local_scope
重定位类型
pic用got表来引用动态符号
R_X86_64_COPY 非fPIC的object引用shared object的变量
R_X86_64_GLOB_DAT 导入的变量值, 放在.got中
R_X86_64_JUMP_SLO 导入的函数地址, 放在.got.plt中, 用plt调用
R_X86_64_64 object起始地址的64位偏移量?
R_X86_64_IRELATIV ifunc使用, ifunc使用got表
R_X86_64_RELATIVE 模块基址重定位
.plt 间接跳转的地址存在.got.plt中的间接跳转代码
.plt.got 间接跳转地址存在.got中的间接跳转代码
.got.plt 存放外部函数符号地址的表, 地址可以在运行时解析
.got 存放外部符号地址的表, 地址可以是变量地址, 也可以是函数地址, 地址在加载时解析(如果一个函数的地址被直接通过got读取, 那么该函数的地址放在.got而不是.got.plt中, 因为如果读取的时候该符号还没被解析的话, 就会读取一个错误的地址, 所以要在加载时解析, 同时, 如果调用该函数的话会通过.plt.got中的项调用)
如果符号在编译单元中或者静态库中定义,那么静态链接, 编译后就有地址。如果符号在动态库中定义, 动态链接,放在动态符号表中, 符号在库加载时或者执行时用plt和got解析地址。
一般情况下, 没有定义的符号链接时发生错误。 但是没有定义的弱符号不会链接错误,(包括没在编译单元中定义和没在静态库和动态库中定义),但是执行时调用会发生段错误。