文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>PowerPC上ELF可执行文件的符号解析(二)

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

时间:2009-04-30  来源:engine_chen

一. 概念

在上一篇文章中介绍了符号解析的基本概念,象PLT表、Symbol表、Relocation表等,这些概念在64位环境仍然存在,但PLT表的意义在64位环境下发生了变化。在讲解64位环境下PowerPC符号解析的过程之前,先说64位和32位环境在汇编语言这个层次上的两个不同之处。

1. TOC(Table of Contents)

我们知道在32位环境下GOT表的地址是必须在汇编程序中计算出来的,具体的计算方法读者有兴趣可以自己写一个简单的程序,然后用gcc -fPIC -S来编译后察看汇编代码即可。64位环境下PowerPC引入了一个新的概念:TOC。它是可执行文件和共享库的一段数据,包括.got、.toc和small data area(用来存放local的小于指定大小的数据),最大可以有64K字节。PowerPC 64 ABI规定了寄存器r2专门用来存放TOC基址,通常是(TOC的起始地址+0x8000)。一般而言,TOC中各个section的顺序是.gotàtocàsmall data area,所以TOC的起始地址就等于.got的地址。

2. 函数描述符(Function Descriptor)

64位和32位一个很大的不同点就是函数描述符的引入。一个函数描述符是三个双字组成的结构:

  • 第一个双字是该函数的真正入口点
  • 第二个双字是该函数的TOC基址
  • 第三个双字是为其他语言,比如Pascal和PL/1准备的环境指针

在64位环境下,和函数名相同的symbol名是函数描述符的地址,函数入口点symbol名是函数名前面加个点。比如我们定义了一个函数void Func(),那么用汇编表示,Func是它的函数描述符,.Func是它的函数入口点。





回页首


二. 变量符号动态解析过程

变量符号的动态解析过程和32位的情况相比没有太多的变化,系统在载入程序过程中将变量symbol地址存入到TOC中,当需要引用变量symbol时就赋该变量所属的TOC基址到r2中,以r2作为基址加上(变量symbol在TOC中的偏移量)就可以从TOC中取得该symbol的实际地址。所以仅仅是以r2替代GOT地址,相当于是换了个基址表示。





回页首


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

当调用函数和被调用函数的TOC不同时,就会发生动态解析。ld在生成可执行文件时会发现这点,就会为他们生成一段代码,由它来负责完成函数符号动态解析过程。在32位情况下,这段代码放在PLT表中,通常是以下形式:

 .PLTi: addi r11,r0,4*(i-1) b .PLTresolve 

在64位情况下,这段代码通常是以下形式:

 <linkage_for_func>: ld r12,func@got@plt(r2) /* func@got@plt(r2)表示func的.PLT入口,它实际是func的函数描述符 */ std r2,40(r1) ld r0,0(r12) //r0是func的入口地址,刚开始时为func的.GLINKi入口 ld r2,8(r12) //r2是func的TOC基址,刚开始时为0 mtctr r0 bctr 

当第一次调用func函数时,它传输控制到<linkage_for_func>中,<linkage_for_func>会从func的.PLT入口处取得func的入口地址(该值在载入程序时由dynamic linker初始化为func的.GLINKi入口),所以接着控制就会转到.GLINKi。下面的代码显示了dynamic linker是如何初始化GLINK表和PLT表的:

 .GLINK: .GLINK0: ld r2, 40(r1) addis r12,r2,.PLT0@toc@ha addi r12,r12,.PLT0@toc@l ld r11,0(r12) ld r2, 8(r12) mtctr r11 ld r11,16(r12) bctr .GLINK1: li r0,0 b .GLINK0 .GLINKi: // i <= 32768 li r0,i - 1 b .GLINK0 .GLINKN: // N > 32768 lis r0,(N - 1) >> 16 ori r0,r0,(N - 1) & 0xffff b .GLINK0 ... .PLT: .PLT0: .quad ld_so_fixup_func .quad ld_so_toc .quad ld_so_ident .PLT1: .quad .GLINK1 .quad 0 .quad 0 ... .PLTi: .quad .GLINKi .quad 0 .quad 0 ... .PLTN: .quad .GLINKN .quad 0 .quad 0 

.GLINKi入口将偏移量(就是Relocation表的index)置入r0,转入到.GLINK0处执行;.GLINK0取得dynamic linker的函数描述符地址,并赋dynamic linker的入口地址到寄存器ctr,赋dyanmic linker的TOC地址到r2,赋第三项ld_so_ident(是一个唯一识别调用函数的信息,由dynamic linker初始化)到r11,然后调用dynamic linker的解析函数_dl_runtime_resolve;_dl_runtime_resolve会根据r0取得和该PLT entry对应的Relocation entry,得到symbol index后就可以找到该函数符号的函数描述符,并从Relocation entry中得到.PLTi的地址,将.PLTi处的函数描述符修改为找到的函数描述符(也就是将找到的函数描述符拷贝到.PLTi处),完成该次符号解析。以后在该文件中若还有调用该函数的语句,就会在<linkage_for_func>中载入真正的TOC和真正的入口地址,正确执行该函数。

下面将以程序为例,演示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 printf("Another Hello, world!\n"); 7 return 0; 8 } 





回页首


四. 过程演示

下面以SUSE Linux Enterprise Server 8.1 for IBM pSeries为例,演示64位PowerPC Linux下函数符号的动态解析过程。在PowerPC上调试64位程序,我们必须安装cross-ppc64-gdb包。

1. Run gdb:/opt/cross/bin/powerpc64-linux-gdb sample

2. 反汇编main函数

 (gdb) disassemble main Dump of assembler code for function main: …………………… 0x10000668 <main+40>: bl 0x10000460 <_init+40> // printf函数的linkage入口点 0x1000066c <main+44>: ld r2,40(r1) //恢复main函数的TOC指针 …………………… (gdb) disassemble 0x10000460 0x10000460 <_init+40>: addis r12,r2,0 0x10000464 <_init+44>: std r2,40(r1) //保存main函数的TOC指针到栈中 0x10000468 <_init+48>: ld r11,-32648(r12) /* 取得printf的.PLT入口地址,赋给r11,初始为printf的.GLINKi入口 */ 0x1000046c <_init+52>: ld r2,-32640(r12) //刚开始时为0 0x10000470 <_init+56>: mtctr r11 0x10000474 <_init+60>: ld r11,-32632(r12) 0x10000478 <_init+64>: bctr //跳到printf的.GLINKi入口 …………………… (gdb) b *0x10000478 Breakpoint 1 at 0x10000478 

3. 在sample的linkage函数中设置断点并运行sample

 (gdb) r Starting program: /home/essl/program/GOT/sample Breakpoint 1, 0x0000000010000478 in _init () (gdb) i r ctr r2 //察看printf的.GLINKi的入口地址 ctr 0x10000778 268437368 //0x10000778就是printf的.GLINKi入口地址 r2 0x0 0 

4. 反汇编printf的.GLINKi入口

 (gdb) disassemble 0x10000778 0x10000778 <call___do_global_ctors_aux+72>: li r0,1 //将relocation偏移量付给r0 0x1000077c <call___do_global_ctors_aux+76>: b 0x10000750 //跳到.GLINK0 /* 0x10000750就是GLINK0的入口地址 */ …………………… 

5. 反汇编.GLINK0

 (gdb) disassemble 0x10000750 0x10000750 <call___do_global_ctors_aux+32>: ld r2,40(r1) /* 取得main函数的TOC指针,赋给r2 */ 0x10000754 <call___do_global_ctors_aux+36>: addis r12,r2,0 0x10000758 <call___do_global_ctors_aux+40>: ld r11,-32696(r12) /* 取得_dl_runtime_resolve的入口地址,赋给r11 */ 0x1000075c <call___do_global_ctors_aux+44>: ld r2,-32688(r12) /* 取得_dl_runtime_resolve的TOC指针,赋给r2 */ 0x10000760 <call___do_global_ctors_aux+48>: mtctr r11 /* ctr中保存着_dl_runtime_resolve的入口地址 */ 0x10000764 <call___do_global_ctors_aux+52>: ld r11,-32680(r12) 0x10000768 <call___do_global_ctors_aux+56>: bctr // 跳到_dl_runtime_resolve函数 (gdb) b *0x10000768 Breakpoint 2 at 0x10000768 (gdb) c Continuing. Breakpoint 2, 0x0000000010000768 in call___do_global_ctors_aux () (gdb) i r ctr //察看_dl_runtime_resolve的入口地址 ctr 0x7fe001051c 549219009820 

6. 反汇编_dl_runtime_resolve

 (gdb) disassemble 0x7fe001051c Dump of assembler code for function _dl_runtime_resolve: 0x7fe001051c <_dl_runtime_resolve>: stdu r1,-128(r1) …………………… (gdb)c //_dl_runtime_resolve会完成符号解析过程,并调用printf("Hello, World!\n") Continuing. Hello, world! Breakpoint 1, 0x0000000010000478 in _init () 

/* 程序打印完Hello,World!后会接着调用printf("Another Hello, World!\n"),然后在我们设的第一个断点停住,让我们用i r ctr r2察看printf的linkage function所取得的printf的入口地址和printf的TOC指针 */

 (gdb) i r ctr r2 ctr 0x7fe0210738 549221107512 r2 0x7fe033a998 549222328728 

/* 我们发现printf的linkage函数取出来的printf的函数入口点和TOC指针值较第一次运行已经改变,只是由于printf所属的共享库此时已经被载入到内存中了,所以printf函数入口点的地址和它的TOC指针值已经确定下来了,所以可以说明printf的.PLT入口的函数描述符在_dl_runtime_resolve做解析完后就被修正为真正的printf的函数描述符了 */





回页首


五. 总结

从以上的分析和演示过程中我们可以看到64 位PowerPC的函数符号动态解析和32位的函数符号动态解析过程有下列几点不一样: 1)64位有函数描述符的概念;32位没有; 2) 64位每个需要重定位的函数有自己的<linkage_for_func>代码,这段代码调用.GLINKi入口,接着是.GLINK0入口,再调用_dl_runtime_resolve函数;而32位则是先调用.PLTi入口,接着是.PLTresolve,再调用_dl_runtime_resolve函数; 3)64位的.PLTi是函数描述符存放的地方;32位的.PLTi是一段汇编代码,它在功能上相当于64位的.GLINKi; 4)64位的.GLINK0功能上相当于32位的.PLTresolve和.PLTcalll,并且解析到符号值后不再需要像32位那样修改.PLTi处的代码来完成跳转,而是通过修改.PLTi处的函数描述符的函数入口部分,然后再取出值来完成跳转,这一点倒是和i386的做法有点类似,只不过i386是将解析到的符号值放在变量GOT[x+n]中。





回页首


参考资料

1. ELF1.1规范中文版
http://elfhack.whitecell.org/mydocs/ELF_chinese.txt

2. ELF1.2规范英文版
Tool Interface Standard (TIS) Executable and Linking Format Specification
http://x86.ddj.com/ftp/manuals/tools/elf.pdf

3. SYSTEM V APPLICATION BINARY INTERFACE PowerPC Processor Supplement
http://www.cloudcaptech.com/MPC555%20Resources/Programming%20Environment/SVR4abippc.pdf

4. 64-bit PowerPC ELF ABI Supplement
ftp://ftp.penguinppc64.org/pub/people/amodra/PPC-elf64abi.txt.gz

5. POWERPC汇编参考手册
HTML文件: http://publib16.boulder.ibm.com/pseries/en_US/aixassem/alangref/alangreftfrm.htm
PDF文件: http://publib16.boulder.ibm.com/doc_link/en_US/a_doc_lib/aixassem/alangref/alangref.pdf

相关阅读 更多 +
排行榜 更多 +
2025pubg国际服最新版本

2025pubg国际服最新版本

飞行射击 下载
pubg国际服地铁逃生最新版本2025

pubg国际服地铁逃生最新版本2025

飞行射击 下载
火柴人大乱斗4游戏

火柴人大乱斗4游戏

飞行射击 下载