• ELF学习--重定位文件


    add.c

    int data = 1;
    int bss;
    const int rodata = 1;
    int add(int num1, int num2)
    {
      int sum = 0;
      sum = num1 + num2;
      return sum;
    }

    编译add.c成.o文件

    gcc -c add.c(-c表示只编译不链接)

    file add.o输出结果如下:

    此结果表明add.o是个重定位文件。

    查看elf header可查看到更详细信息:readelf -h add.o

    由于是重定位文件,ELF中并没有program header,entry point为0x0, section header的字符串表在section 中的索引为9(在下面的section header中对应.shstrtable section).ELF header的size为52. 有12个section,每个section header的 size为40。

    重定位文件的ELF格式的布局如下。

    通过readelf -S add.o 查看section header。

    Off这一列是表示section在ELF文件中的偏移量,.text的偏移量是0x34,转换成十进制正好是52(ELF header大小),说明header后面紧接着是.text。

    .text内容可以通过objdump来查看:objdump -s -d add.o(-s表示将内容以16进制打印出来,-d表示反汇编)

    .text只包含add函数,其大小为0x1d.

    .data保存了初始化了的全局变量和静态变量。在add.c中.data只包含data这一个int型变量,所以其size为4。

    .data的偏移量为0x34+0x1d=0x51,由于.data是4字节对其的,所以offset为0x54.

    .rodata保存的是只读变量(如const修饰)和字符串常量。在add.c中只读变量为int型,大小为4.

    .bss保存的是未初始化的全局变量和局部静态变量。按道理int bss是应该存在.bss段中,但是.bss的size是0.这其实与编译器相关,有些编译器并不会将未初始化的全局变量放在.bss中,只是预留一个未定义的全局变量符号,等到最终链接成可执行文件的时候再分配.bss分配空间。这与强符号和弱符号相关。

    我们在通过readelf -s add.o看一下ELF的符号表:

    其中我们看到符号的value大部分是0;如果符号是函数和变量,那么符号的value就是函数和变量的地址。

    符号的value分为以下几种情况:

    1.如果ELF文件是目标文件,符号不是“COMMON”类型,函数和变量的地址是不确定的,要等到链接后才知道,所以value值是符号其所在section中的偏移。

    2.如果ELF文件是目标文件,符号是“COMMON”类型的,value表示该符号的对其属性。

    3.如果ELF文件是可执行文件,value是符号的虚拟地址。

    add符号是在代码段,所以Ndx项是1,从前面的section header可一看到.text的下标正好是1.其他符号如data,rodata以此类推。

    其中名字没有显示,type是SECTION的符号,表示下标为Ndx段的section名字。比如Num 2的Ndx为1,那么他表示.text section名称,即".text".可以通过objdump -t查看符号表,可以获取所有符号名,即其说在的section名字。

    我们在写一个简单的程序,准备调用add.c中的add函数,

    #include <stdio.h>
    int main()
    {
      int sum = add(1,2);
      printf("sum is: %d ", sum);
      return 0;
    }

    gcc -c main.c

    查看section header:

    多了一个section .rel.text,说明.text中有需要重定位的部分。

    查看符号表和重定位表:

    readelf -s main.o

     重定位表:objdump -r main.o

    符号add和printf是undefined,需要重定位以确定地址。

    add和printff分别在main.o的0x19和0x31的位置需要重新定位。

    objdump -d main.o反编译一下:

    sum和printf的指令码都为e8 fc ff ff ff.

    e8是call的指令码,通过file命令可以看出main.o是小端,所以在重定位前sum和printf的地址均为0xfffffffc,他是常量-4的补码。

    我们再将main.c和add.c静态链接起来,

    gcc main.c add.c -o test

    可以看到形成可执行文件后,main.o中调用add函数的地址被重新修正。

    现在的add函数的指令码为e8 1f 00 00 00;

    R_386_PC32是相对寻址。对于需要重定位的符号,他修正后的结果为S+A-P.

    S是add的实际地址0x08048459

    A是修正前的值0xfffffffc,即-4;

    P是需要修正的位置,当链接成可执行文件后,这个值是被修正位置的虚拟地址。0x804841d +0x19;

    0x8048459-4 -(0x804841d+0x19) = 0x1f 与上述修正后的指令码“e8 0x1f 00 00 00”对应。

     在重定位表中,我们还可以看到有个R_386_32类型的重定位项。

    R_386_32是绝对寻址方式,其修正后的值是S+A.

    S为符号的实际地址,A是被修正的位置。

    对于.rodata这个需要重新定位的项,是printf中的常量字符串。S为0x000000

    我们查看可执行文件的16进制内容,objdump -x test

    那么修正后的值即为0x8048510

  • 相关阅读:
    消息(5)——WSE增强的web服务套件,MTOM附件
    消息(4)——WS附件传输,包体中的base64编码附件
    WCF消息之XmlDictionaryWriter
    Aptana使用技巧—Aptana2.0系列教程
    [f]获取URL中的参数[转]
    [f]获取元素在页面的位置getPos
    手机wap2.0网页缩放设置
    WPF SnapsToDevicePixels作用
    Silverlight获取控件绝对位置
    浏览器: F5 和 Ctrl+F5的区别
  • 原文地址:https://www.cnblogs.com/fellow1988/p/6158240.html
Copyright © 2020-2023  润新知