网站公告列表

  没有公告

加入收藏
设为首页
联系站长
您现在的位置: 61IC中国电子在线 >> 技术文库 >> 嵌入式 >> 文章正文
  [图文]PowerPC上ELF可执行文件的符号解析(二)           ★★★ 【字体:
PowerPC上ELF可执行文件的符号解析(二)
作者:61IC录入    文章来源:本站原创    点击数:    更新时间:2006-10-5    

作者首先介绍了在64位环境下PowerPC区别于32位的两个概念,接着讲述了在64位环境下PowerPC Linux是如何作变量符号动态解析,然后重点讲解函数符号动态符号解析,并辅以实例详细说明,最后总结了32位和64位实现函数符号动态解析的异同之处。
一. 概念
在上一篇文章中介绍了符号解析的基本概念,象PLT表、Symbol表、Relocation表等,这些概念在64位环境仍然存在,但PLT表的意义在64位环境下发生了变化。在讲解64位环境下PowerPC符号解析的过程之前,先说64位和32位环境在汇编语言这个层次上的两个不同之处。 

1
 TOCTable of Contents

我们知道在32位环境下GOT表的地址是必须在汇编程序中计算出来的,具体的计算方法读者有兴趣可以自己写一个简单的程序,然后用gcc -fPIC -S来编译后察看汇编代码即可。64位环境下PowerPC引入了一个新的概念:TOC。它是可执行文件和共享库的一段数据,包括.got.tocsmall 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基址 
第三个双字是为其他语言,比如PascalPL/1准备的环境指针 
64位环境下,和函数名相同的symbol名是函数描述符的地址,函数入口点symbol名是函数名前面加个点。比如我们定义了一个函数void Func(),那么用汇编表示,Func是它的函数描述符,.Func是它的函数入口点。 

二. 变量符号动态解析过程
变量符号的动态解析过程和32位的情况相比没有太多的变化,系统在载入程序过程中将变量symbol地址存入到TOC中,当需要引用变量symbol时就赋该变量所属的TOC基址到r2中,以r2作为基址加上(变量symbolTOC中的偏移量)就可以从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
funcTOC基址,刚开始时为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 linkerTOC地址到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为例,演示64PowerPC 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
 samplelinkage函数中设置断点并运行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_resolveTOC指针,赋给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 ()

 


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


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

 


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

五. 总结
从以上的分析和演示过程中我们可以看到64 PowerPC的函数符号动态解析和32位的函数符号动态解析过程有下列几点不一样: 164位有函数描述符的概念;32位没有; 2 64位每个需要重定位的函数有自己的<linkage_for_func>代码,这段代码调用.GLINKi入口,接着是.GLINK0入口,再调用_dl_runtime_resolve函数;而32位则是先调用.PLTi入口,接着是.PLTresolve,再调用_dl_runtime_resolve函数; 364位的.PLTi是函数描述符存放的地方;32位的.PLTi是一段汇编代码,它在功能上相当于64位的.GLINKi 464位的.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 

关于作者 
陈剑,IBM软件工程师,在IBM中国软件开发中心从事Linux相关软件的测试和开发工作。你可以通过 chenjian@cn.ibm.com 和他联系。
金戈,IBM高级软件工程师,在IBM中国软件开发中心主持Linux集群系统开发工作。你可以通过 jinge@cn.ibm.com 和他联系。  

               欢迎点击进入:TI德州中文网   (国内唯一针对TI应用的中文技术网站)    文章录入:admin    责任编辑:admin 
  • 上一篇文章:

  • 下一篇文章:
  • 发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口
    最新热点 最新推荐 相关文章
    基于PowerPC的嵌人式系统设计
    用PowerPC实现高带宽 TCP/IP…
    PowerPC 的仿真和交叉开发
    嵌入式处理器MPC8250与CF卡的…
    基于嵌入式Linux的MPC850 US…
    PowerPC上ELF可执行文件的符…
      网友评论:(只显示最新10条。评论内容只代表网友观点,与本站立场无关!)
    站长:61IC 湘ICP备05002478号