///
不需要牛刀,不需要阅读源码,如果只是为解决109的含义。楼主执行的查询命令readelf -S test2.o
[ 8] .symtab SYMTAB 00000000 0002a8 0000c0 10 9 7 4
.symtab起始于2a8,保存了符号信息,根据楼主提供的test2.o,从2a8开始,读到367:
00002a8: 0000 0000 0000 0000
00002b0: 0000 0000 0000 0000 0100 0000 0000 0000 ................
00002c0: 0000 0000 0400 f1ff 0000 0000 0000 0000 ................
00002d0: 0000 0000 0300 0100 0000 0000 0000 0000 ................
00002e0: 0000 0000 0300 0300 0000 0000 0000 0000 ................
00002f0: 0000 0000 0300 0400 0000 0000 0000 0000 ................
0000300: 0000 0000 0300 0500 0000 0000 0000 0000 ................
0000310: 0000 0000 0300 0600 0900 0000 0000 0000 ................
0000320: 0400 0000 1100 0300 0b00 0000 0400 0000 ................
0000330: 0400 0000 1100 0300 0d00 0000 0800 0000 ................
0000340: 0400 0000 1100 0300 0f00 0000 0000 0000 ................
0000350: 0e00 0000 1200 0100 1300 0000 0e00 0000 ................
0000360: 5200 0000 1200 0100
我们按照8字节为一个整体,数7个(前6个是symtab头不信息?),到了318:
0900 0000 0000 0000 0400 0000 1100 0300
其中0900就是符号的具体位置偏移,我们再从368开始看.strtab:
0074 6573 7432 2e63 0061 0062 0063 0066 756e 006d 6169 6e00
自己数数09位置是什么?没错就是61,它是“a”的asic的16进制值。
以此类推,0b是“b”,0d是“c”。
讲到这里,0109、0108、0107的秘密应该大白天下了吧?二楼已经讲了01是object,你在1100中的第二个`1`指的就是这个。而09、08、07,指的就是.symtab按八字节为一整块算时的块号罢了!每个块代表一个符号,并给出了strtab中相应字符串的位置偏移和属性解释!
以上结论纯属观察,本人完全不懂编译原理,哈哈^_^
//
不是搞编译的,只是记得以前看过一些介绍的文章,尝试解答一下。
.text+2a:
2a: ff 35 00 00 00 00 pushl 0x0
很明显,这是在push一个参数,只是在编译的时候,由于不知道最后的地址,操作数暂时为0。
经过了链接阶段,所有变量的地址都确定了,就可以替换这些临时的操作数了。
如楼主所言,这些信息是保留在elf文件的relcoation section里的。
代码中一共有8处引用全局变量
#fun(a, b, c)
[2a]: ff 35 00 00 00 00 pushl 0x0
[30]: ff 35 00 00 00 00 pushl 0x0
[36]: ff 35 00 00 00 00 pushl 0x0
[3c]: e8 fc ff ff ff call 3d
#func(c, b, a)
[44]: ff 35 00 00 00 00 pushl 0x0
[4a]: ff 35 00 00 00 00 pushl 0x0
[50]: ff 35 00 00 00 00 pushl 0x0
[56]: e8 fc ff ff ff call 57
.rel.text:
0000380: [2c00] 0000 <0109> 0000 [3200] 0000 <0108> 0000 ,.......2.......
0000390: [3800] 0000 <0107> 0000 [3d00] 0000 <020a> 0000 8.......=.......
00003a0: [4600] 0000 <0108> 0000 [4c00] 0000 <0107> 0000 F.......L.......
00003b0: [5200] 0000 <0109> 0000 [5700] 0000 <020a> 0000 R.......W.......
00003c0: 0a
显然,.rel.text需要描述这8个对全局变量的引用,并观察到[]里的地址的相似性,
因此可以推测,每个引用占8个字节。
并且,<>里面的部分可以分为两类:6个传参的属于一类01**,函数调用属于一类02**。
猜测**是变量/函数名在符号表中的id。
观察其中一组01类型的:
.text:
[2a]: ff 35 [00 00 00 00] pushl 0x0
.rel.text:
0000380: [2c00] 0000 <0109> 0000
2c正好是2a(push指令的地址)+2(push操作码ff35的长度),这就告诉ld,
在连接的时候,需要把2c的内容(就是push指令的参数)替换掉(当前为0)。
替换成什么内容,由<0109>决定。01是一种类型,09应该是变量在symbol表中的id。
由于楼主没有贴出test.o经过linker链接后的结果,手头也没有32bit的机器,所以就不太好猜了……
最简单的可能性是,linker直接替换为变量的绝对地址。
至于02类型的:
3c: e8 fc ff ff ff call 3d
0000398: [3d00] 0000 <020a> 0000
分析同上,3d=3c(call指定的地址)+1(call操作码的长度),就是call的参数的地址。
我手头有64bit的机器,传参和函数调用都被编译为02类型的,通过readelf可以看到,02属于R_X86_64_PC32类型。
实际上,这个call是个相对jmp,参数是相对于当前ip/pc的偏移,在.o文件里,目的地址是-4(0xfffffffc),就是这条指令本身。
可以想象,在ld阶段,-4会被替换为func函数跟call指令之间的地址差值。
(实际上,由于func是编译单元内部的函数调用,这个差值在编译阶段也可以确定的。)
由于<02>是相对偏移,因此[3c]和[56]虽然都是call $func,它们的偏移值是不一样的,所以两条指令并不相同。
作为对比,30和4a都是push $b,如果<01>类型是绝对地址的话,那么两条指令就完全一样。
以上,啰啰嗦嗦的,不知道有没有说清楚。
1,调用fun前,将a,b,c作为参数压栈,我们只看到连续push三个0,因为编译阶段,a,b,c的地址都不确定,暂时用0代替。
2,到了链接阶段,linker会搜集所有.o文件中的符号,并放到一张全局符号表里。像a,b,c,它们就有名字“a","b","c",并算出了对应的地址,就这样存到全局符号表里。
3,之前说到的,那三条push 0的指令,它们对应的机器码,是存储在test2.o的.text段的,这时候当然要把那三个零换成a,b,c的地址。
4,linker是根据test.o的.rel.text段来完成这个重定位的。.rel.text段是一个表,每个entry(表项)8个字节,数据结构是这样:
struct{
unsigned r_offset;
usnigned r_info;
}
linker根据r_offset知道,要修正的位置相对于.text段的偏移。就这样定位到那三个0的位置。
根据r_info知道要修正的是哪个符号。再说细点:linker根据r_info的高24位在全局符号表里索引到这个符号,这样,符号的地址就
顺藤摸瓜的出来了。把它写入r_offset处。
这样重定位就完成了。
----------------以上是就32位elf来说的,某些叙述有简化。你可以去看《程序员的自我修养》