读书频道 > 系统 > linux > 深度探索Linux操作系统系统构建和原理解析
2. 符号重定位
2013-09-28 13:29:55     我来说两句 
收藏    我要投稿   
全书一共8章:第1章介绍了如何准备工作环境。在第2章中构建了编译工具链,这是后面构建操作系统各个组件的基础。在这一章中,不仅详细讲解了工具链的构建过程,而且还通过对编译链接过程的探讨,深入讨论了工具链  立即去当当网订购

链接时,在第一阶段完成后,目标文件已经合并完成,并且已经为符号分配了运行时地址,链接器将进行符号重定位。

模块hello.o中有两处需要重定位,一处是偏移0xb处的变量foo2,另外一处是偏移0x1b处的函数foo2_func。汇编器已经将这两处需要重定位的符号记录在了重定位表中。
root@baisheng:~/demo# readelf -r hello.o
Relocation section '.rel.text' at offset 0x3c8 contains 2 entries:
 Offset     Info    Type             Sym.Value  Sym. Name
0000000b  00000901 R_386_32          00000000   foo2
0000001b  00000a02 R_386_PC32        00000000   foo2_func
...

符号foo2的重定位类型是R_386_32,ELF标准规定的计算修订值的公式是:

S + A

其中,S表示符号的运行时地址,A就是汇编器填充在引用外部符号处的Addend。

符号foo2_func的重定位类型是R_386_PC32,ELF标准规定的计算修订值的公式是:

S + A - P

其中S、A的意义与前面完全相同,P为修订处的运行时地址或者偏移。对于目标文件,P为修订处在段内的偏移。对于可执行文件和动态库,P为修订处的运行时地址。

首先我们先来确定S。运行时地址在链接时才分配,因此,变量foo2和函数foo2_func的运行时地址在链接后的可执行文件hello的符号表中:
root@baisheng:~/demo# readelf -s hello | grep foo2
    38: 00000000     0 FILE    LOCAL  DEFAULT  ABS foo2.c
    53: 0804a020     4 OBJECT  GLOBAL DEFAULT   24 foo2
    68: 08048414    16 FUNC    GLOBAL DEFAULT   13 foo2_func

可见,符号foo2的运行时地址为0x0804a020,符号foo2_func的运行时地址是0x08048414。
接下来,我们再来看看汇编器为这两个符号填充的Addend是多少。我们使用工具objdump反汇编hello.o,其中黑体标识的分别是汇编器在引用foo2和foo2_func的地址处填充的Addend:
root@baisheng:~/demo# objdump -d hello.o
hello.o:     file format elf32-i386
Disassembly of section .text:
00000000 <main>:
   0: 55                    push   %ebp
   1: 89 e5                 mov    %esp,%ebp
   3: 83 e4 f0              and    $0xfffffff0,%esp
   6: 83 ec 10              sub    $0x10,%esp
   9: c7 05 00 00 00 00 05  movl   $0x5,0x0
  10: 00 00 00
  13: c7 04 24 32 00 00 00  movl   $0x32,(%esp)
  1a: e8 fc ff ff ff        call   1b <main+0x1b>
  1f: b8 00 00 00 00        mov    $0x0,%eax
  24: c9                    leave 
  25: c3                    ret   

根据输出可见,汇编器在引用符号foo2处填充的Addend是0,在引用符号foo2_func处填充的Addend是–4。

于是,可执行文件hello中引用符号foo2的位置的修订值为:

S + A = 0x0804a020 + 0 = 0x0804a020

我们反汇编可执行文件hello,来验证一下引用符号foo2处的值是否修订为我们计算的这个值:
root@baisheng:~/demo# objdump -d hello
hello:     file format elf32-i386
...
080483dc <main>:
 80483dc: 55                    push   %ebp
 80483dd: 89 e5                 mov    %esp,%ebp
 80483df: 83 e4 f0              and    $0xfffffff0,%esp
 80483e2: 83 ec 10              sub    $0x10,%esp
 80483e5: c7 05 20 a0 04 08 05  movl   $0x5,0x804a020
 80483ec: 00 00 00
 80483ef: c7 04 24 32 00 00 00  movl   $0x32,(%esp)
 80483f6: e8 19 00 00 00        call   8048414
                                         <foo2_func>
 80483fb: b8 00 00 00 00        mov    $0x0,%eax
 8048400: c9                    leave 
 8048401: c3                    ret   
 8048402: 66 90                 xchg   %ax,%ax
...

注意偏移0x1b处,确实已经被链接器修订为0x0804a020了。

对于符号foo2_func的修订值,还需要变量P,即引用符号foo2_func处的运行时地址。根据可执行文件hello的反汇编代码可见,引用符号foo2_func的指令的地址是:

0x80483f6 + 1 = 0x80483f7

所以,可执行文件hello中引用符号foo2_func的位置的修订值为:

S + A – P = 0x08048414 + (-4) - 0x80483f7 = 0x19

观察hello的反汇编代码,从地址0x80483f7开始处的4字节,确实也已经被链接器修订为0x19。

这里提醒一下读者,如果foo2_func占据的运行时地址小于main函数,那么这里foo2_func与PC的相对地址将是负数。在机器指令中,使用的是数的补码形式,所以一定要注意,以免造成困惑。

事实上,对于符号foo2使用的重定位类型R_386_32,是绝对地址重定位,链接器只要解析符号foo2的运行时地址替换修订处即可。而对于符号foo2_func,其使用的重定位类型是R_386_PC32,这是一个PC相对地址重定位。而当执行当前指令时,PC中已经加载了下一条指令的地址,并不是当前指令的地址,这就是在引用符号foo2_func处填充“–4”的原因。

我们看到,在链接时,链接器在需要重定位的符号所在的偏移处直接进行了编辑修订,所以人们通常也将链接器形象地称为“link editor”。

点击复制链接 与好友分享!回本站首页
分享到: 更多
您对本文章有什么意见或着疑问吗?请到论坛讨论您的关注和建议是我们前行的参考和动力  
上一篇:1. 合并目标文件
下一篇:3. 链接静态库
相关文章
图文推荐
3.3.6 GNOME的软件管
3.3.5 GNOME的文件管
3.3.4 GNOME的窗口管
3.3.3 收藏夹和快捷
排行
热门
文章
下载
读书

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训
版权所有: 红黑联盟--致力于做最好的IT技术学习网站