ELF文件符号动态解析的过程

链接器在把所需要的共享库加载到内存后,并没有把共享库中的函数的地址写到GOT表中,而是延迟到函数的第一次调用时,才会对函数的地址进行定位。(红线为第一次函数调用的顺序,蓝线为后续函数调用的顺序)
下面以puts函数为例分析第一次函数调用的过程:
在这里插入图片描述

链接器在把所需要的共享库加载到内存后,并没有把共享库中的函数的地址写到GOT表中,而是延迟到函数的第一次调用时,才会对函数的地址进行定位。(红线为第一次函数调用的顺序,蓝线为后续函数调用的顺序)
下面以puts函数为例分析第一次函数调用的过程:
(1)进入puts@plt,执行指令
(2)跳到GOT表中相应位置,此时GOT表里保存的是pushl n的地址,实际上就是顺序执行下一步
(3)执行pushl n,n为puts函数地址在GOT表中的位置,向堆栈中压入这个偏移量的主要作用就是为了找到puts函数的符号名以及puts函数地址在GOT表项中所占的位置,以便在函数定位完成后将函数的实际地址写到这个位置。
(4)跳到PTL0的位置,这步将GOT[1],即共享库链表的地址压栈
(5)顺序执行jmp GOT[2],GOT[2]保存的是_dl_runtime_resolve函数的入口
(6)执行_dl_runtime_resolve函数,该函数会找到puts函数的实际加载地址,并把它写到GOT表中,返回时就会进入puts函数内执行。
  函数后续执行就会直接跳转的GOT表puts函数对应位置,该位置保存着puts函数的地址,直接进入puts函数。

GOT表

Global Offset Table,在位置无关代码中,一般不能包含绝对虚拟地址(如共享库)。当在程序中引用某个共享库中的符号时,编译链接阶段并不知道这个符号的具体位置,只有等到动态链接器将所需要的共享库加载时进内存后,也就是在运行阶段,符号的地址才会最终确定。因此,需要有一个数据结构来保存符号的绝对地址,这就是GOT表的作用,GOT表中每项保存程序中引用其它符号的绝对地址。这样,程序就可以通过引用GOT表来获得某个符号的地址。
  在x86结构中,GOT表的前三项保留,用于保存特殊的数据结构地址,其它的各项保存符号的绝对地址。对于符号的动态解析过程,我们只需要了解的就是第二项和第三项,即GOT[1]和GOT[2]:GOT[1]保存的是一个地址,指向已经加载的共享库的链表地址(加载的共享库会形成一个链表);GOT[2]保存的是一个函数的地址,定义如下:GOT[2] = &_dl_runtime_resolve,这个函数的主要作用就是找到某个符号的地址,并把它写到与此符号相关的GOT项中

PLT表

Procedure Linkage Table,过程链接表(PLT)的作用就是将位置无关的函数调用转移到绝对地址。在编译链接时,链接器并不能控制执行从一个可执行文件或者共享文件中转移到另一个中(这时候函数的地址还不能确定),因此,链接器将控制转移到PLT中的某一项。而PLT通过引用GOT表中的函数的绝对地址,来把控制转移到实际的函数。
  在实际的可执行程序或者共享目标文件中,GOT表在名称为.got.plt的section中,PLT表在名称为.plt的section中。

原文链接:https://de4dcr0w.github.io/Linux内核如何装载和启动一个可执行程序.html

以上是动态加载过程。
android中还有个/proc/虚拟文件夹,不在硬盘上。包含了各种进程和系统信息。self/maps XXX/maps
分别是自身进程映射和其他进程映射。
在maps中有个 Linker是共享库的加载/链接器,也可以称为解释器(interpreter)。(/system/bin/linker)
http://www.voidcn.com/article/p-kzjxymae-qr.html

获取其他进程函数地址一般是根据本地函数地址和模块的基址,加上远程模块的基址进行计算就可以得到远程函数的地址。

猜你喜欢

转载自blog.csdn.net/bme314/article/details/83055632