ELF目标文件概述

ELF目标文件概述

目标文件分为三种:可重定位目标文件,可执行目标文件和共享目标文件。Linux中使用ELF格式
(举例的两个.c文件:)

/* main.c */
/* $begin main */
int sum(int *a, int n);

int array[2] = {1, 2};

int main() 
{
    int val = sum(array, 2);
    return val;
}
/* $end main */

/* sum.c */
/* $begin sum */
int sum(int *a, int n)
{
    int i, s = 0;
	for (i = 0; i < n; i++) { 
        s += a[i];
    }
    return s;
}        
/* $end sum */

  1. 可重定位目标文件

在这里插入图片描述

  • 编译生成可重定位目标文件:
    $ gcc -c main.c sum.c
    在这里插入图片描述
    由于main.c模块与sum.c模块的ELF可重定位目标文件的格式类似,这里只给出main.o的ELF头,节头部表,符号表里的信息。
  • 看ELF头:
    $ readelf -h main.o
    $ readelf -h sum.o在这里插入图片描述
    ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。还描述了ELF头的大小,目标文件的类型(REL),机器类型(x86-64),节头部表的文件偏移(11),以及节头部表中条目的大小(64字节)和数量(12)。
  • 看节头表:
    $ readelf -S main.o
    $ readelf -S sum.o
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述
    节头部表描述了不同节的位置和大小,其中目标文件的每个节都有一个固定大小的条目。
    .text:已编译程序的机器代码。
    .rodata:只读数据,如printf语句中的格式串和开关语句的跳转表。
    .data:已初始化的全局和静态C变量。局部变量运行时被保存在栈中。
    .bss:未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量,目标文件中这个节不占实际的空间。
    .symtab:符号表,存放在程序中定义和引用的函数和全局变量的信息。
    .rel.text:一个位置的列表,链接器把这个目标文件和其他目标文件组合时需要修改这些位置 。任何调用外部函数或者引用全局变量的指令都需修改。
    .rel.data:引用或定义的所有全局变量的重定位信息。
    (只有可重定位目标文件才有,可执行目标文件没有)节头部表中有三个特殊的伪节在节头部表中是没有条目的:ABS,UNDEF,COMMON。
    区分COMMON和.bss:未初始化的全局变量被分配在COMMON中,未初始化的静态变量,以及初始化为0的全局或静态变量被分配在.bss中。
  • 看符号表:
    $ readelf -s main.o
    $ readelf -s sum.o
    在这里插入图片描述
    符号表示汇编器构造的,使用编译器输出到汇编语言.s文件中的符号。
    .symtab节中包含ELF符号表,这张符号表中包含一个条目的数组。
    开始的8个条目是链接器内部使用的局部符号。(Bind为LOCAL)
    value:距定义目标的节的起止位置的偏移,对于可执行目标文件来说这里是一个绝对运行时的地址。
    size:目标的大小(字节)。
    type:数据或函数。
    bind:表示符号是本地还是全局的。
    name:字符串表(.strtab)中的字节偏移。
    看到全局符号main定义的条目,它是一个位于.text节(Ndx=1)中偏移量为0(value的值)处的33字节(size)的函数。array是一个位于.data节(Ndx=3)中偏移量为0(value的值)的8字节(size)条目。最后一个条目是对外部符号sum的引用,它不在该模块被定义(UND)。
  • 反汇编:
    $objdump -dx main.o
    在这里插入图片描述
    $objdump -dx -j .data main.o
    在这里插入图片描述
    -j name
    –section=name
    仅仅显示指定名称为name的section的信息

  1. 可执行目标文件
    在这里插入图片描述
    可执行目标文件的格式类似与可重定位目标文件格式。它还包括程序的入口点,也就是程序运行时要执行第一条指令的地址。相比于可重定位目标文件能看到它含有.init节不含.rel节。
  • 链接生成可执行目标文件:
    $ gcc -o main main.o sum.o/gcc -o main main.c sum.c
    在这里插入图片描述
  • 看ELF头:
    $ readelf -h main
    在这里插入图片描述
  • 看节头表:
    $ readelf -S main
  • 看符号表:
    $ readelf -s main
  • 看程序头表:
    $ readelf -l main
    在这里插入图片描述
  • 反汇编:
    $objdump -dx main(仅贴出main和sum部分)
    在这里插入图片描述
    在这里插入图片描述
    ELF可执行文件被设计得很容易加载到内存,可执行文件的连续的片被映射到连续的内存段。程序头部表描述了这种映射关系。是由OBJDUMP显示的。
    在这里插入图片描述
    可执行目标文件的内容初始化两个内存段。
    r-x说明代码段有读/执行访问权限,开始于内存地址0x000000处,总共的内存大小是0x850字节,并且被初始化可执行目标文件的头0x850个字节,其中包括ELF头、程序头部表以及.init、.text和.rodata节。
    rw-说明数据段由读/写访问权限,开始于内存地址0x200df0处,总的内存大小为0x230个字节,并用从目标文件中偏移的0xdf0处开始的.data节中的0x228个字节初始化。
    $objdump -dx -j .data main
    在这里插入图片描述

3.重定位
当汇编器生成一个目标模块时,它并不知道数据和代码最终放在内存中的什么位置,也不知道这个模块引用的任何外部定义的函数或全局变量的位置。
所以当汇编器遇到对未知位置的引用时就会生成一个重定位条目,告诉链接器在目标文件合并时如何修改这个引用。
代码的重定位条目在.rel.text中,已初始化数据的重定位条目在.rel.data中。

看重定位条目:
$ readelf -r main.o
$ readelf -r sum.o
在这里插入图片描述
在这里插入图片描述
offset时需要被修改的引用的节偏移。
type告知链接器如何修改新的引用(R_X86_64_PC32是重定位PC相对地址的引用,R_X86_64_32是重定位绝对地址的引用)。这里显示的PLT32。
symbol标识被修改引用应该指向的符号。
addend是一个有符号常数,一些类型的重定位要使用它对被修改引用的值做偏移调整。反汇编main.o也能看到这部分数据。

重定位相对引用:

$ objdump -dx main
00000000000005fa <main>:
 5fa:	55                   	push   %rbp
 5fb:	48 89 e5             	mov    %rsp,%rbp
 5fe:	48 83 ec 10          	sub    $0x10,%rsp
 602:	be 02 00 00 00       	mov    $0x2,%esi
 607:	48 8d 3d 02 0a 20 00 	lea    0x200a02(%rip),%rdi        # 201010 <array>
 60e:	e8 08 00 00 00       	callq  61b <sum>
 613:	89 45 fc             	mov    %eax,-0x4(%rbp)
 616:	8b 45 fc             	mov    -0x4(%rbp),%eax
 619:	c9                   	leaveq 
 61a:	c3                   	retq   

000000000000061b <sum>:
 61b:	55                   	push   %rbp
 61c:	48 89 e5             	mov    %rsp,%rbp
 61f:	48 89 7d e8          	mov    %rdi,-0x18(%rbp)
 623:	89 75 e4             	mov    %esi,-0x1c(%rbp)
 626:	c7 45 fc 00 00 00 00 	movl   $0x0,-0x4(%rbp)
 62d:	c7 45 f8 00 00 00 00 	movl   $0x0,-0x8(%rbp)
 634:	eb 1d                	jmp    653 <sum+0x38>
 636:	8b 45 f8             	mov    -0x8(%rbp),%eax
 639:	48 98                	cltq   
 63b:	48 8d 14 85 00 00 00 	lea    0x0(,%rax,4),%rdx
 642:	00 
 643:	48 8b 45 e8          	mov    -0x18(%rbp),%rax
 647:	48 01 d0             	add    %rdx,%rax
 64a:	8b 00                	mov    (%rax),%eax
 64c:	01 45 fc             	add    %eax,-0x4(%rbp)
 64f:	83 45 f8 01          	addl   $0x1,-0x8(%rbp)
 653:	8b 45 f8             	mov    -0x8(%rbp),%eax
 656:	3b 45 e4             	cmp    -0x1c(%rbp),%eax
 659:	7c db                	jl     636 <sum+0x1b>
 65b:	8b 45 fc             	mov    -0x4(%rbp),%eax
 65e:	5d                   	pop    %rbp
 65f:	c3                   	retq   

objdump -dx main.o
0000000000000000 <main>:
   0:	55                   	push   %rbp
   1:	48 89 e5             	mov    %rsp,%rbp
   4:	48 83 ec 10          	sub    $0x10,%rsp
   8:	be 02 00 00 00       	mov    $0x2,%esi
   d:	48 8d 3d 00 00 00 00 	lea    0x0(%rip),%rdi        # 14 <main+0x14>
			10: R_X86_64_PC32	array-0x4
  14:	e8 00 00 00 00       	callq  19 <main+0x19>
			15: R_X86_64_PLT32	sum-0x4
  19:	89 45 fc             	mov    %eax,-0x4(%rbp)
  1c:	8b 45 fc             	mov    -0x4(%rbp),%eax
  1f:	c9                   	leaveq 
  20:	c3                   	retq   

r.offset = 0x15
r.symbol = sum
r.type = R_X86_64_PLT32(书上的是PC32)
r.addend = -4
  1. 计算引用的运行时地址
    ADDR(s) = ADDR(.text) ; ADDR(r.symbol) = ADDR(sum)
  2. 更新该引用,使它在运行时指向sum程序
    refaddr = ADDR(s) + r.offset = 0x5fa + 0x15 = 0x60f
    *refptr = (unsigned)(ADDR(r.symbol) + r.addend - refaddr)
                = (unsigned)(0x61b + (-4) - 0x60f)
                = (unsigned)0x8
  3. 在得到的可执行文件中,call指令的重定位形式:
    60e: e8 08 00 00 00        callq 61b < sum >

在运行时,call指令将存放在0x60e处,当CPU执行call指令时,PC的值为0x613(0x5fa+19)即call指令的下一条指令的地址。为执行这条指令,CPU将PC压入栈中,然后PC<—PC+0x8 = 0x61b,这样下一条指令就是sum例程的第一条指令。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值