PowerPC上ELF可执行文件的符号解析(一)

Global Offset Table(GOT全局偏移量表)[From PowerPC上ELF可执行文件的符号解析]  

2008-03-04 14:12:13|  分类: Work-2008 |字号 订阅

Global Offset Table(GOT全局偏移量表):

PowerPC上ELF可执行文件的符号解析(一)

developerWorks
文档选项

级别: 初级

陈剑 (chenjian@cn.ibm.com), 软件工程师, IBM
金戈, 高级软件工程师, IBM

2004 年 2 月 01 日

作者首先简单介绍了符号静态解析和动态解析的不同之处,接着分析了和动态解析相关的概念,然后讲述PowerPC Linux是如何作动态符号解析的,并辅以实例详细说明,最后总结了i386和PowerPC实现动态解析的异同之处;本篇文章侧重分析32位PowerPC Linux上的符号动态解析。

一. 前言

符号解析是Linux系统导入二进制可执行文件的重要过程,它完成的工作包括将一个符号定位到实际的内存地址,并且要保证可以正确引用这些符号。按解析对象的不同它可以分为变量符号解析和函数符号解析;按解析方式的不同可以分为静态解析和动态解析。

对于静态解析的符号,它们的地址在文件生成时就由link editor(在Linux下通常是ld)已经确定下来了;对于动态解析的符号,他们的地址在程序运行时才由dynamic linker(动态链接器,32位Linux平台下通常是/lib/ld.so.1)确定下来。我们可以这么认为,如果一个符号在共享库中定义,那么当其他可执行文件或共享库引用这个符号时,就需要对它作动态解析。

变量符号的动态解析过程比较简单,系统在载入程序过程中将变量symbol地址存入到GOT(Global Offset Table)中,引用变量symbol时首先计算出GOT表的实际地址,然后以它作为基址加上(变量symbol在GOT表中的偏移量)就可以从GOT表中取得该symbol的实际地址。下面以SUSE Linux Enterprise Server 8.1 for IBM pSeries为例,主要讲述和演示32位PowerPC Linux下函数符号的动态解析过程。




回页首


二. 概念

在讲述解析过程之前,先介绍一下在解析过程中要用到的基本概念。

1. ELF(Executable and Linkable Format)文件

ELF是Linux缺省采用的可执行文件(包括共享库,object文件)的格式,具体规范参见参考文献〔1〕、〔2〕。这里需要提一下的是section这个概念:section是ELF文件中一段互相联系信息,它可以是一段数据,也可以是一段代码。比如可执行代码信息就放在.text section中,被用户初始化的变量会放在.data section中,没有被用户初始化的变量会放在.bss section(bss是below stack segment的缩写)中。还有其他的一些 section: .debug、 .hash、 .symtab、 .dynsym、 .plt、 .rel.plt 等等。 .dynsym(动态符号表)、 .plt(过程链接表)和.rel.plt(重定位表)和我们的话题有关。

2. 符号表(symbol table)

符号表记录了程序中符号的定义信息和引用信息,它是一个结构数组,数组中的每个元素对应一个符号所有的信息。我们可以在glibc的源代码glibc-2.2/elf/elf.h中看到这个结构的c语言定义:

typedef struct{
Elf32_Word st_name;  /* 符号名 (.string表的索引) */
Elf32_Addr st_value;   /* 符号值(Symbol value) */
Elf32_Word st_size;  
unsigned char st_info;  
unsigned char st_other;  
Elf32_Section st_shndx; 
} Elf32_Sym;

对于可执行文件和共享库而言,符号值记录了该符号的内存地址。可执行文件知道运行时刻他们的地址,所以他们内部的引用符号在编译时候就已经确定了;共享库symbol的符号值(symbol value)就要等到共享库被载入到内存中才确定下来。

我们可以用"readelf -s 文件名"来查看elf文件的symbol值,一般会有两个symbol表:.symtab(包含静态符号和动态符号)和.dynsym(仅包含动态符号)表。下面是我自己机器上的一个输出:

cj@bluesky:~/program/GOT> readelf -s test32
Symbol table '.dynsym' contains 6 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 10010894   488 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.0
  ……………………
Symbol table '.symtab' contains 228 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 10000114     0 SECTION LOCAL  DEFAULT    1 
……………………

3.过程链接表(Procedure Linkage Table,PLT)

静态解析函数符号很简单,因为引用是在内部进行的,只要用下面的命令就可实现:
bl resolved_symbol //resolved_symbol是被引用函数的入口点

动态解析符号则不然,由于link editor不能在编译时就确定被引用函数的入口点,所以它只能将控制权交给第三方,再由这个第三方来完成确定被引用函数入口点的任务,这个第三方就是过程链接表。

过程链接表的格式如下:

  • PLT表开头的18个字(72字节)为dynamic linker保留
  • 如果可执行文件或共享库需要N个.PLTi入口,那么紧跟着这18个字,link editor就会保留3*N个字(12*N字节),开头的2*N个字就是所有的.PLT入口,对于第i个引用符号,它的.PLT入口是(72+(i-1)*8)(1<=i<=N),剩下的N个字留给dynamic linker使用。
    过程链接表虽然存在于文件当中,但它的初始化是由dynamic linker在装载可执行文件和共享库时完成的。下面是一个可能的被初始化的PLT表的内容:
    .PLT:
    .PLTresolve:
    addis r12,r0,dynamic_linker@ha
    addi r12,r12,dynamic_linker@l 
    mtctr r12 //到此为此,r12和ctr中是dynamic linker的地址
    addis r12,r0,symtab_addr@ha
    addi r12,r12,symtab_addr@l
    bctr   //此时r12中是共享库symbol表的地址
    .PLTcall:
    addis r11,r11,.PLTtable@ha
    lwz r11,.PLTtable@l(r11)
    mtctr r11
    bctr
    8 个nop指令
    .PLT1:
    addi r11,r0,4*0
    b .PLTresolve
    . . .
    .PLTi:
    addi r11,r0,4*(i-1)
    b .PLTresolve
    . . .
    .PLTN:
    addi r11,r0,4*(N-1)
    b .PLTresolve
    .PLTtable:
    <N word table begins here>
    

4. Relocation表

Relocation表总是和PLT表紧紧联系在一起,也就是说,PLT表有多少个入口,Relocation表就有多少个入口,他们是一对一的关系。下面是Relocation结构的c语言定义:

typedef struct {
Elf32_Addr r_offset;  //.PLTi的地址
Elf32_Word r_info;  //包含symbol index信息
} Elf32_Rel;

在上述过程链接表的例子中r11是Relocation表和PLT表的index,dynamic linker需要r11来找到对应于这次解析的Relocation表的元素,然后得到r_info,由于r_info中包含了symbol index信息,我们就可确定是寻找哪个symbol的地址;得到symbol的地址后还需要r11来确定将这个地址正确重定位到那个PLT入口(r_offset保存了.PLTi的地址)。




回页首


三. 函数符号动态解析过程

当调用一个外部的函数时,它传输控制到PLT 中跟该symbol 相关的那个entry (是在编译时候由link editor完成的),然后将偏移量(就是Relocation表的index)置入r11,转入到.PLTresolve处执行;.PLTresolve取得dynamic linker的地址,将共享库的symbol表地址赋给r12后调用dynamic linker的解析函数_dl_runtime_resolve;_dl_runtime_resolve会根据r11取得和该PLT entry对应的Relocation entry,然后得到symbol index,结合r12找到该函数符号的载入地址loaded_addr,并从Relocation entry中得到.PLTi的地址,将.PLTi处的命令修改为b loaded_addr,完成该次符号解析。这样以后若还有调用该函数的语句,就会直接跳往loaded_addr。

下面将以程序为例,演示SUSE SLES 8.1 for IBM pSeries是如何动态解析函数符号printf 的。

例子程序Sample.c

      1 #include <stdio.h>
      2 
      3 int main(int argc, char *argv[])
      4 {
      5     printf("Hello, world!\n");
      6     return 0;
      7 }
      




回页首


四. 过程演示

1.Run gdb:gdb sample

2.反汇编printf符号

(gdb) disassemble printf
Dump of assembler code for function printf:
0x1001089c <printf>:    .long 0x0
……………………..
0x100108b4 <printf+24>: .long 0x0
End of assembler dump.
 

由于sample还没有运行,所以printf函数还没有被载入到内存中,它的.PLTi入口初始为0

3.反汇编main函数

(gdb) disassemble main
Dump of assembler code for function main:
0x10000448 <main>:      stwu    r1,-32(r1)
……………………..
0x10000470 <main+40>:   bl      0x1001089c <printf>
……………………..
 

4. 设置断点并运行sample

(gdb) b *0x10000470
Breakpoint 1 at 0x10000470
(gdb) r
Starting program: /home/cj/program/GOT/sample 
Breakpoint 1, 0x10000470 in main ()
(gdb) disassemble 0x1001089c
Dump of assembler code for function printf:
0x1001089c <printf>:    li      r11,4
0x100108a0 <printf+4>:  b       0x1001086c <_GLOBAL_OFFSET_TABLE_+48>
0x100108a4 <printf+8>:  li      r11,8
0x100108a8 <printf+12>: b       0x1001086c <_GLOBAL_OFFSET_TABLE_+48>
……………………..
End of assembler dump.

我们发现0x1001089c处的代码已经改变,表明在载入sample程序的过程中ld.so.1已经修改了printf函数的.PLTi入口。r11值为4,即是printf 在relocation表中的index。

5.反汇编0x1001086c

0x1001086c相当于.PLTresolve,它会调用_dl_runtime_resolve来解析符号

(gdb) disassemble 0x1001086c
Dump of assembler code for function _GLOBAL_OFFSET_TABLE_:
……………………..
0x1001084c <_GLOBAL_OFFSET_TABLE_+16>:  addis   r11,r11,4097
0x10010850 <_GLOBAL_OFFSET_TABLE_+20>:  lwz     r11,2220(r11)
0x10010854 <_GLOBAL_OFFSET_TABLE_+24>:  mtctr   r11
0x10010858 <_GLOBAL_OFFSET_TABLE_+28>:  bctr
0x1001085c <_GLOBAL_OFFSET_TABLE_+32>:  .long 0x0
0x10010860 <_GLOBAL_OFFSET_TABLE_+36>:  .long 0x0
0x10010864 <_GLOBAL_OFFSET_TABLE_+40>:  addis   r11,r11,-4097
0x10010868 <_GLOBAL_OFFSET_TABLE_+44>:  addi    r11,r11,-2220
0x1001086c <_GLOBAL_OFFSET_TABLE_+48>:  rlwinm  r12,r11,1,0,30
0x10010870 <_GLOBAL_OFFSET_TABLE_+52>:  add     r11,r12,r11
0x10010874 <_GLOBAL_OFFSET_TABLE_+56>:  li      r12,-20344
0x10010878 <_GLOBAL_OFFSET_TABLE_+60>:  addis   r12,r12,16385
0x1001087c <_GLOBAL_OFFSET_TABLE_+64>:  mtctr   r12 
//此时ctr中存放_dl_runtime_resolve的地址
0x10010880 <_GLOBAL_OFFSET_TABLE_+68>:  li      r12,22904
0x10010884 <_GLOBAL_OFFSET_TABLE_+72>:  addis   r12,r12,16386
 //此时r12存放/lib/libc.so.6的symbol表的地址
0x10010888 <_GLOBAL_OFFSET_TABLE_+76>:  bctr
0x1001088c <_GLOBAL_OFFSET_TABLE_+80>:  .long 0x0
0x10010890 <_GLOBAL_OFFSET_TABLE_+84>:  .long 0x0
0x10010894 <__libc_start_main>: b       0xfed8ff0 <__libc_start_main>
0x10010898 <__libc_start_main+4>: b     0x1001086c <_GLOBAL_OFFSET_TABLE_+48>
0x1001089c <printf>:    li      r11,4
0x100108a0 <printf+4>:  b       0x1001086c <_GLOBAL_OFFSET_TABLE_+48>
0x100108a4 <printf+8>:  li      r11,8
0x100108a8 <printf+12>: b       0x1001086c <_GLOBAL_OFFSET_TABLE_+48>
……………………..
End of assembler dump.

(gdb) b *0x10010888
Breakpoint 2 at 0x10010888
(gdb) i r ctr
ctr            0x4000b088       1073787016
(gdb) disassemble 0x4000b088
Dump of assembler code for function _dl_runtime_resolve:
0x4000b088 <_dl_runtime_resolve>:       stwu    r1,-64(r1)
……………………..
0x4000b0c4 <_dl_runtime_resolve+60>:    stw     r0,8(r1)
0x4000b0c8 <_dl_runtime_resolve+64>:    bl      0x4000ad3c <fixup>
0x4000b0cc <_dl_runtime_resolve+68>:    mtctr   r3
……………………..
0x4000b104 <_dl_runtime_resolve+124>:   addi    r1,r1,64
0x4000b108 <_dl_runtime_resolve+128>:   bctr
End of assembler dump.

我们可以通过查看glibc的源代码来看_dl_runtime_resolve是如何得到symbol的地址值的,它在文件glibc-2.2/sysdeps/powerpc/dl-machine.h中定义。

(gdb) ni
Hello, world!
0x10000474 in main ()

6.再次反汇编0x1001089c

(gdb) disassemble 0x1001089c
Dump of assembler code for function printf:
0x1001089c <printf>:    b       0xff0ec9c <printf>
0x100108a0 <printf+4>:  b       0x1001086c <_GLOBAL_OFFSET_TABLE_+48>
……………………..
End of assembler dump.

对比上述第四个步骤,0x1001089c <printf>: li r11,4已经被修正为 0x1001089c <printf>: b 0xff0ec9c <printf>了,以后若还有调用printf的语句,控制权就会直接转到0xff0ec9c,0xff0ec9c是printf函数的真正入口点。




回页首


五. 总结

从以上的分析和演示过程中我们可以看到32 位PowerPC的函数符号动态解析和i386 Linux体系结构的函数符号动态过程大体一致,它们都是先跳转到对应的.PLTi入口,保存Relocation偏移量,然后跳转到_dl_runtime_resolve,由它来完成寻找函数符号的任务。不同的地方是:1. 由于b命令的限制(b 命令可能第一次跳转不到函数入口点),所以对于不能一次跳到函数入口点的情况就需要两次跳转(先将跳转地址存到.PLTtable+4*(i-1),然后将.PLTi处的b .PLTresolve修改为b .PLTcall,由它用bctr命令来完成跳转到函数入口点的任务); 2. i386 Linux将跳转地址存入到GOT[x+i]中,PowerPC则根本不使用GOT表,而是通过直接修改.PLTi处的代码来达到相同的功能。


From:http://yuxu9710108.blog.163.com/blog/static/2375153420082421213370/


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
反汇编符号表的32位/64位ELF目标文件,CPU类型:ARM PowerPC MIPS 操作菜单选择:文件解析 Alx+P ELF文件解析 Alt+E 另有文本比较等杂项功能。V1.25.05相对上一版本,增加内存数据按数据类型解析功能;V1.25.04相对上一版本,完善X86反汇编,修复小BUG;V1.25.02相对上一版本,COREDUMP统计、匹配目标文件等相关功能优化,修复小BUG;V1.25.00相对上一版本,相关功能支持动态库文件,查询代码支持无符号目标文件+有符号目标文件,COREDUMP统计、与问题单关联、目标文件/CORE文件/问题单同步;V1.24.02相对上一版本,针对进程主动捕捉异常的信息定制处理进一步完善COREDUMP文件解析与应用,增强软件管理;V1.24.01相对上一版本,进一步完善COREDUMP文件解析与应用,提供部分ARM Thumb指令反汇编;V1.24.00相对上一版本,进一步完善COREDUMP文件解析与应用,提供堆栈调用关系分析;V1.23.03相对上一版本,提供32位X86反汇编;V1.23.02相对上一版本,提供源代码行查询指令地址,OBJ/COREDUMP文件解析,sprintf函数参数特定检查,完善文件拖放操作,修复小BUG;V1.23.01相对上一版本,提供ELF文件指令修改,修复ARM MLS指令反汇编错误等BUG;V1.23.00相对上一版本,提供程序地址对应源代码行查询,修复MIPS调试信息错误;V1.22相对上一版本,修复MIPS小端字节序反汇编错误,网上最新版本提示;另部分增强功能的菜单操作设有密码,如有兴趣欢迎咨询。欢迎大家反馈相关软件使用过程中的问题!
反汇编符号表的32位/64位ELF目标文件,CPU类型:ARM PowerPC MIPS X86 操作菜单选择:文件解析 Alx+P ELF文件解析 Alt+E 另有文本比较等杂项功能。V1.26.01相对上一版本,增加ARM64反汇编、64位core文件解析;V1.26.00相对上一版本,增强EXE反汇编,增加dbx老邮件解析、二维码功能;V1.25.07相对上一版本,增加二进制反汇编、IQ数据解析功能,完善MIPS反汇编,修复小BUG;V1.25.05相对上一版本,增加内存数据按数据类型解析功能;V1.25.04相对上一版本,完善X86反汇编,修复小BUG;V1.25.02相对上一版本,COREDUMP统计、匹配目标文件等相关功能优化,修复小BUG;V1.25.00相对上一版本,相关功能支持动态库文件,查询代码支持无符号目标文件+有符号目标文件,COREDUMP统计、与问题单关联、目标文件/CORE文件/问题单同步;V1.24.02相对上一版本,针对进程主动捕捉异常的信息定制处理进一步完善COREDUMP文件解析与应用,增强软件管理;V1.24.01相对上一版本,进一步完善COREDUMP文件解析与应用,提供部分ARM Thumb指令反汇编;V1.24.00相对上一版本,进一步完善COREDUMP文件解析与应用,提供堆栈调用关系分析;V1.23.03相对上一版本,提供32位X86反汇编;V1.23.02相对上一版本,提供源代码行查询指令地址,OBJ/COREDUMP文件解析,sprintf函数参数特定检查,完善文件拖放操作,修复小BUG;V1.23.01相对上一版本,提供ELF文件指令修改,修复ARM MLS指令反汇编错误等BUG;V1.23.00相对上一版本,提供程序地址对应源代码行查询,修复MIPS调试信息错误;V1.22相对上一版本,修复MIPS小端字节序反汇编错误,网上最新版本提示;V1.21相对上一版本,菜单调整,完善64位ELF文件解析解析调试信息;另部分增强功能的菜单操作设有密码,如有兴趣欢迎咨询。欢迎大家反馈相关软件使用过程中的问题!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值