准备:可重定位文件(Relocatable file),可执行文件(Executable file),共享文件(Shared object file)。
Relocatable file: an object file that holds code and data suitable for linking with other object files to create an executable or a shared object file. In other word, you can say that relocatable file is a foundation for creating executables and libraries.
This is kind of file you get if you compile a source code like this:
$ gcc -c test.c
That will produce test.o, which is a relocatable file.
Kernel module (either suffixed with .o or .ko) is also a form of relocatable file.
Executable file: object file that holds a program suitable for execution. Yes, that means, your XMMS mp3 player, your vcd software player, even your text editor are all ELF executable files.
This is also a familiar file if you compile a program:
$ gcc -o test test.c
After you make sure the executable bit of "test" is enabled, you can execute it. The question is, what about shell script? Shell script is NOT ELF executable, but the interpreter IS.
Shared object file: This file holds code and data suitable for linking in two contexts:
The link editor may process it with other relocatable and shared shared object file to create another object file.
The dynamic linker combines it with an executable file and other shared objects to create a process image.
In simple words, these are the files that you usually see with suffix .so (normally located inside /usr/lib on most Linux installation).
重定位:就是把符号的value进行重新定位,value可是地址也可是其他类型值.要重点理解
编译器、连接器和装载器各个职能,熟悉编译、连接和加载过程、符号(symbol)、段(section)等概念
符号:高级语言经编译处理后,在binary层面上,统一以符号表示各个变量、函数实体。是连接和重定位桥
梁。不同编译器有不同的地符号影射,比如c语言中,以前有的编译器固定变量前加下划线 int a ==>_a
段:binary层面对指令和数据的组织,方便管理、理解。深入参考汇编中有关段概念。
符号和段都是属于ELF表示范畴,是对binary层面的描述。深入可参考ELF或者coff相关文档
重定位分为两大类:普通和动态。普通就是连接时候重定位,动态是指加载、运行时
重定位,一般有程序启动时动态加载符号来重定位,和函数符号被调用时候动态加载。
重定位过程要用到一些表,来辅助定位,有.rel.text、 .rel.dyn和.rel.plt、.plt.下面根据他们来展开说明。
.rel.text
重定位的地方在.text段内,以offset指定具体要定位位置。在连接时候由连接器完成。注意比较.text段前后变化。指的是比较.o文件和最终的执行文件(或者动态库文件)。就是重定位前后比较,以上是说明了具体比较对象而已。
.rel.dyn
重定位的地方在.got段内。主要是针对外部数据变量符号。例如全局数据。重定位在程序运行时定位,一般是在.init段内。定位过程:获得符号对应value后,根据rel.dyn表中对应的offset,修改.got表对应位置的value。另外,.rel.dyn 含义是指和dyn有关,一般是指在程序运行时候,动态加载。区别于rel.plt,rel.plt是指和plt相关,具体是指在某个函数被调用时候加载。
.rel.plt
重定位的地方在.got.plt段内(注意也是.got内,具体区分而已)。 主要是针对外部函数符号。一般是函数首次被调用时候重定位。可看汇编,理解其首次访问是如何重定位的,实际很简单,就是初次重定位函数地址,然后把最终函数地址放到.got.plt内,以后读取该.got.plt就直接得到最终函数地址(参考过程说明)。 所有外部函数调用都是经过一个对应桩函数,这些桩函数都在.plt段内。
过程说明:调用对应桩函数--->桩函数取出.got表(具体是.got.plt)表内地址--->然后跳转到这个地址.如果是第一次,这个跳转地址默认是桩函数本身跳转处地址的下一个指令地址(目的是通过桩函数统一集中取地址和加载地址),后续接着把对应函数的真实地址加载进来放到.got.plt表对应处,同时跳转执行该地址指令.以后桩函数从.got.plt取得地址都是真实函数地址了。
.plt段,存放重定位桩函数的。
重要区别
.rel.text属于普通重定位辅助段 ,他由编译器编译产生,存在于obj文件内。连接器连接时,他 用于最终可执行文件或者动态库的重定位。通过它修改原obj文件的.text段后,和并 到 最终可执行文件或者动态文件的.text段。
注:readelf -r a.o 查看 .rel.text。其类型一般为R_386_32和R_386_PC32
.rel.dyn和.rel.plt是动态定位辅助段。由连接器产生,存在于可执行文件或者动态库文件内。借助这两个辅助段可以动态修改对应.got和.got.plt段,从而实现运行时重定位。
.rel.dyn 对应地点在.got表内;.rel.plt 在.got.plt,注意不是在.text,这点和普通不同,也是重要点。
.rel.text由编译器产生,然后在连接时候,由链接器负责根据.rel.text对.text段进行修改,从而达到重定位目的;
.rel.dyn和.rel.plt由连接器产生,然后在运行时候,动态加载符号地址。
对于数据,根据.rel.dyn找到.got中的offset位置;
对于函数则通过.plt桩函数和.rel.plt段来获取函数真实地址,然后存在于.got.plt。
要理解动态连接中访问外部符号是通过.got和.got.plt
注1:规律:.rel.plt和.got.plt偏移 有对应,另外和.dynsym好似有次序对应关系(不很确定)。通过这可方便解析函数符号地址,详细可参考连接器解析函数(连接器一般是动态加载,像全局数据符号那样,程序启动时加载)。
注2:readelf -r a.out 查看 .rel.dyn 和.rel.plt.其中 .rel.dyn 类型一般为R_386_GLOB_DAT和R_386_COPY;.rel.plt为R_386_JUMP_SLOT
例子在此不举了,分析这个重定位过程,最好还是自己动手写个例子来,比单纯看文章好理解。
分析没多久,还有好多问题需要慢慢分析透彻,大家多多指正。