ELF是Executable and Linkable Format的缩写,它是一种对可执行文件、目标文件和库文件使用的文件格式。
从源代码到形成最终的目标文件,这个过程是由编译器帮助完成的;
编译器还"偷偷"增加了很多额外的代码,来完成参数入栈、保存函数返回地址等等很多工作,
这样使得最终的程序得以正常的运行。
对于库文件和最终的可执行文件,其ELF格式可能会略有不同,不过也都差不多。
ELF文件基本的布局是:
库文件 可执行文件
ELF文件头 ELF文件头
程序头表 程序头表
section1 parag1
section2 parag2
... ...
sectionn paragn
节头表 节头表
使用readelf -S命令可以用来查看ELF文件中有哪些section信息,比如:
.text,这个是代码段;
.data,是数据段,是已经初始化了的全局变量;
.bss,是未初始化的全局变量;
.symtab,这个节用来确定符号的名称和符号值之间的关联;
.strtab,这个节保存了字符串数组;
...
以上是程序的静态表现形式。
而程序运行时,会形成一个程序的进程地址空间。
这时共享库是动态加载的,共享库在进程虚拟地址空间中的位置是无法事先预知的。
只能确定共享库内部各个符号的相对位置。
可以通过cat /proc/pid/maps命令来查看进程号为pid的进程的动态表现形式。
这样我们就能建立静态和动态之间的关联性:根据动态的地址来计算一个符号在共享库中的相对位置。
为什么很多共享库在进程虚拟地址空间中有好几份呢?
以下以pthread为例,
40db8000-40dcd000位于这一段地址空间的应该是pthread.so的text section;
40dcd000-40dd4000位于这一段地址空间的是?
40dd4000-40dd5000位于这一段地址空间的是rodata section吗?
40dd5000-40dd6000位于这一段时间空间的是data/bss section吗?
40db8000-40dcd000 r-xp 00000000 fe:00 368 /lib/libpthread-2.12.2.so
40dcd000-40dd4000 ---p 00015000 fe:00 368 /lib/libpthread-2.12.2.so
40dd4000-40dd5000 r--p 00014000 fe:00 368 /lib/libpthread-2.12.2.so
40dd5000-40dd6000 rw-p 00015000 fe:00 368 /lib/libpthread-2.12.2.so
这个应该也是可以验证的。
首先编译好动态库,这样动态库内部的各个section的位置、内容也就能够确定下来了。
那进程的虚拟地址空间是如何形成的呢?
linux系统调用execv用来装载一个ELF可执行文件,从而就会形成进程的虚拟地址空间。
所以为什么虚拟地址空间会是这样,这都是内核来帮忙完成的。
有了以上的学习,
我们就能够通过一些能够分析ELF二进制文件的工具如nm/readelf/objdump等来查到符号和符号所在地址直接的关联。
这能够帮助我们debug。
符号(symbol)是一个程序的创建块,比如他可能是一个变量名或函数名。
符号表是所有符号及其对应地址的一个列表。