ELF以前一直有了解,但是觉得不够详细,抽时间记录一下ELF重定位相关知识,防止忘记。
.dynsym(动态符号字符串表)
这里面主要存放了函数名称,保存了导入和导出等函数名字信息,符号表里将包含进程中所有的动态链接所需要的符号。dynsym会被全部加载到内存。
.dynstr(字符串表)
So文件里面包含全部的字符串,dynsym只是该字段的一部分
.hash
这个是字符串表的一个hash序列,主要用于快速索引对应字符串,如果修改So文件里面的字符串,该字段也需要进行修改。这也就是为什么So文件直接修改字符串会出现崩溃等问题。
.rel.dyn和.rel.plt
这两个表为重点表,可以看到他的类型是REL(重定位)类型。
在讲这两个表之前需要重点讲讲重定位相关的知识点
为什么需要重定位表?
Linux有沙盒机制,每个进程都是一个独立的"空间",之间相互不打扰。
但是比如Libc.so,如果每个进程都独立引用一个的话,可能会导致当Libc.so更新的话,每个进程里面的Libc.so都进行更新,这种涉及是不合理的。
这种So文件全部的内存里面只有一个,可能A进程会引用。
B进程也可能会引用,所以当我们引用的时候就需要进行重定位,去找到我们需要的函数地址。也就是我们常说的重定位。
重定位和这两个表有什么关系?
REL表项主要是为了告诉链接器,哪些地方需要重定位。
当我们调用libc.so里面的一个A函数的时候,.rel.plt 就是记录了A函数的地址,连接器链接的时候会去读取.rel.plt 字段,读取完毕以后会将拿到的函数地址,放到.Got表里面
(现阶段只有mips平台会直接使用Got表,Got表可以存放函数/变量地址),有时候我们引用的可能不止是函数,含有可能用的是libc.so里面的一个变量,比如一个int常量之类的。这种时候就用到了.rel.dyn 他是储存除了.rel.plt表以外全部引用的内容,连接的时候会进行重定位。ELF中每一个这样不确定地址的地方,都会生成一个rel表项。
每个rel表项,都对应一个需要修正地址的地方。
总结:
.rel.dyn节的每个表项对应了除了外部过程调用的符号以外的所有重定位对象,而.rel.plt节的每个表项对应了所有外部过程调用符号的重定位信息。
每个rel表项,都对应指出一个got表项地址。got表是在连接的时候进行的填充。
每个got表项用来存放找到的地址。
.plt
当我们在代码Test方法里面调用clock()函数(libc.so里面的函数)
image.png可以看到对应的汇编代码
我们切双击倒clock函数 ,直接跳转到.plt段
这个表其实是一段代码段,可以理解为一个“跳床”,跳到GOT表
下面是.plt表内容,这里面的- 00061AF4 是相对寻址,可以理解为,
计算一个偏移并跳转过去
当我们调用clock函数的时候,其实会执行bl指令 bl会 跳转到.plt,修改pc寄存器以后,跳转到got表 ,最终调用libc.so里面的 clock()
总结:
Got存储的是地址,需要跳转的最终地址,.plt是一段代码指令,是“跳床“”,为了跳转到Got而存在的。
.text(代码段)
存放代码指令的地方 ,当前So的全部代码段都在这里
.rodata
rodata的意义同样明显,ro代表read only,即只读数据(const)
关于rodata类型的数据,要注意以下几点:
1.常量不一定就放在rodata里,有的立即数直接编码在指令里,存放在代码段(.text)中。
2.对于字符串常量,编译器会自动去掉重复的字符串,保证一个字符串在一个可执行文件(EXE/SO)中只存在一份拷贝。
3.rodata是在多个进程间是共享的,这可以提高空间利用率。
4.在有的嵌入式系统中,rodata放在ROM(如norflash)里,运行时直接读取ROM内存,无需要加载到RAM内存中。
5.在嵌入式linux系统中,通过一种叫作XIP(就地执行)的技术,也可以直接读取,而无需要加载到RAM内存中。
由此可见,把在运行过程中不会改变的数据设为rodata类型的,是有很多好处的:在多个进程间共享,可以大大提高空间利用率,甚至不占用RAM空间。同时由于rodata在只读的内存页面(page)中,是受保护的,任何试图对它的修改都会被及时发现,这可以帮助提高程序的稳定性。
.dynamic
.dynamic段是动态链接中最重要的段,它记录了和动态链接有关的段的类型,地址或者数值。
.got
如同上面所说存放函数的绝对地址
.got的格式就是存放地址的数组。
参考:
https://bbs.pediy.com/thread-221821.htm
https://blog.csdn.net/beyond702/article/details/52105778
网友评论