您的位置:首页 >要闻 >

【图片+代码】:GCC 链接过程中的【重定位】过程分析

时间:2022-03-07 12:16:32 来源:

目录

· 示例代码

· sub.o 文件内容分析

· 段信息

·符号表信息

· main.o 文件分析

· 段信息

· 符号表信息

· 绝对寻址

· 相对寻址

· 重定位表信息

· 可执行程序 main

· 段信息

· 符号表信息

· 绝对地址重定位

· 相对地址重定位

· 总结

别人的经验,我们的阶梯!

最近因为项目上的需要,利用动态链接库来实现一个插件系统,顺便就复习了一下关于Linux中一些编译、链接相关的内容。

在链接的过程中,符号重定位是比较麻烦的事情,特别是在动态链接的过程中,因为需要考虑到很多不同的情况。

这篇文章作为第一篇,先来聊一聊静态链接中的重定位过程。

按照惯例,还是以一个简短的示例代码作为载体,看一看GCC在链接的过程中,是如何根据目标文件(.o文件)来进行重定位,生成最终的可执行文件的。

示例代码

示例代码很简单,一共有2个源文件main.c和 sub.c。

在sub.c中定义了一个全局变量和一个全局函数,然后在main.c中使用这个全局变量和全局函数。代码如下:

sub.c

main.c

在一般的开发过程中,都是使用GCC工具,直接把这2个源文件编译得到可执行文件。

但是,为了探究编译、链接过程中的一些内部情况,我们需要把编译、链接的过程拆开,从中间过程中产生的目标文件(.o 文件)中,来查看一些详细信息。

先把这2个源文件编译成目标文件sub.o和main.o:

$ gcc -m32 -c sub.c

$ gcc -m32 -c main.c

这样就得到了两个目标文件,先来初步看一下这2个目标文件中的一些信息。

以上这两个编译过程是各自独立的,虽然main.o中使用了两个符号(全局变量和全局函数),但是此时main.o并不知道这2个符号是在哪个文件中定义的。

当链接器把所有的.o文件链接成可执行文件的过程中,才能确定这2个符号是在哪里。

在Linux系统中,目标文件(.o) 和可执行文件都是ELF格式的,因此如何查看ELF格式文件的一些工具指令就非常有帮助。

很久之前总结过这篇文章:《Linux系统中编译、链接的基石-ELF文件:扒开它的层层外衣,从字节码的粒度来探索》,里面详细总结了ELF文件的内部结构,以及一些相关的工具。

sub.o 文件内容分析段信息

首先来简单瞄一眼一下sub.o中的一些信息。

sub.o中的段信息如下(指令:$ readelf -S sub.o):

我们主要关心黄色的代码段和数据段就可以了,可以看出:

1. 代码段(.text):地址Addr是 0x0000_0000(因为这是目标文件,不是可执行文件,所以不会安排地址),它在 sub.o 文件中的偏移量(Off)是 0x34,长度是 0x0C 字节;

2. 数据段(.data):地址Addr是 0x0000_0000,它在 sub.o 文件中的偏移量(Off)是 0x40,长度是 0x04 字节;

简单算一下:sub.o的开始部分是ELF的 header,通过 readelf -h sub.o 指令可以看出来header部分是52个字节(即:0x34),如下:

因此可以得到:

1. 代码段(.text)是紧接在 header 之后,长度是 0x0C 个字节,在文件中占据着 0x34 ~ 0x3F 这部分空间(0x3F = 0x34 + 0x0C - 1);

2. 数据段(.data)是进阶在代码段之后,在文件中占据着 0x40 ~ 0x43 这部分空间;

符号表信息

下面再来说说符号表的事情。

简单来说,符号表就是一个文件中定义的所有符号、引用的外部符号(在其它文件中定义),包括:变量名、函数名、段名等等,都属于符号。

当然了,在ELF文件中会详细的说明每一个符号的类型、大小、可见性等信息。如果对ELF文件格式有过了解的话,一定知道每一条符号信息,都是通过一个结构体来描述具体含义的,描述符号表的结构体如下:

// Symbol table entries for ELF32.

struct Elf32_Sym {

Elf32_Word st_name; // Symbol name (index into string table)

Elf32_Addr st_value; // Value or address associated with the symbol

Elf32_Word st_size; // Size of the symbol

unsigned char st_info; // Symbol's type and binding attributes

unsigned char st_other; // Must be zero; reserved

Elf32_Half st_shndx; // Which section (header table index) it's defined in

};

再来看一下sub.o中的符号表,下面这张图(指令:readelf -s sub.o):

关注上图中黄色矩形中的两个符号:SubData和SubFunc,很明显它们就是sub.c中定义的两个符号:全局变量和全局函数。

对于SubData符号来说:

1. Size=4: 长度是 4 个字节;

2. Type=OBJECT:说明这是一个数据对象;

3. Bind=GLOBAL:说明这个符号是全局可见的,也就是在其他文件中可以使用;

4. Ndx=2:说明这个符号是属于第 2 个 段中,就是数据段(.data);

同样的道理,对于SubFunc符号来说:

1. Size=12: 长度是 12 个字节;

2. Type=FUNC:说明这是一个函数;

3. Bind=GLOBAL:说明这个符号是全局可见的,也就是在其他文件中可以调用;

4. Ndx=1:说明这个符号是属于第 1 个 段中,就是代码段(.text);

main.o 文件分析

按照上面的步骤,把main.o中的这几个信息也查看一下。

段信息

指令:readelf -S main.o

可以看出:

1. 代码段(.text):地址Addr是 0x0000_0000(因为这是目标文件,不是可执行文件,所以不会安排地址),它在 sub.o 文件中的偏移量(Off)是 0x34,长度是 0x32 字节;

2. 数据段(.data):地址Addr是 0x0000_0000,它在 sub.o 文件中的偏移量(Off)是 0x66,长度是 0 个字节,因为它没有定义变量;

在文件中的布局如下所示:

123下一页>


郑重声明:文章仅代表原作者观点,不代表本站立场;如有侵权、违规,可直接反馈本站,我们将会作修改或删除处理。