可重定位目标文件的解析

从源代码到可执行目标文件要经过以下几个步骤:

main.c-----预处理(cpp)-------->main.i------编译(ccl)----->main.s-----汇编(as)----->main.o-----链接(ld)----->main(可执行文件)

      可重定位目标文件是.o文件,包含二进制的代码和数据,其形式可以在编译时和其它的可重定位目标文件合并为可执行目标文件,每个.o文件由对应.c文件生成,每个.o文件代码和数据的地址都是从0开始。各个系统的目标文件格式各不相同,现在x86-64 Liinux和Unix系统使用可执行可链接格式(Executable and Linlable Format,ELF)。

        符号是指全局变量或用static修饰的变量(注意局部变量不是符号),在链接时链接器会做两件事情:符号解析和重定位。符号解析时编译器将程序中定义的符号放在符号表中(symbol table),链接器将每个符号引用和都与一个确定的符号定义建立关联。重定位是将多个代码段和数据段合并为一个单独的代码段和数据段,计算每个符号在虚拟空间中的绝对地址,将可执行文件中符号引用处的地址修改为重定位后的地址信息。

ELF中各节存储的信息如下:

ELF头:包括十六字节标识信息、文件类型(.o,exec,.so)、机器类型、节头表的偏移、节头表的表项大小以及表项个数。

.text:已编译程序的机器代码。

.rodate:只读数据,比如printf语句中的格式串和switch的跳转表。

.data:已经初始化的全局变量和静态C变量。

.bss:未初始化的全局变量和静态C变量,以及所有被初始化为0的全局或静态变量,仅是占位符,不占据任何实际磁盘空间。区分初始化和非初始化是为了空间效率。

.symtab:一个符号表,它存放在程序中定义和引用的函数和全局变量静态变量的信息,不包括局部变量。

.rel.text:一个.text节中位置的列表,当链接器把这个目标文件和其他文件组合时,需修改这些位置。

.rel.data:被模块引用或定义的所有全局变量的重定位信息。

.debug:一个调试符号(gcc -g)。

.line:原始C源程序中的行号和.text节中机器指令之间的映射。

.strtab:一个字符串表包含symtab和debug节中符号及节名。

Section header table(节头表):每个节的节名、偏移和大小。

 

下面着重解析main.o和sum.o可重定位目标文件的ELF格式(main函数里调用sum函数):

main.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源代码

/* 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 */

首先将main.c和sum.c文件转为main.o和sum.o文件在Linux中用下列命令:

$gcc -c main mian.c

$gcc -c sum sum.c

于是在源文件的目录下就能看到main.o和sum.o文件,接下来在命令行中输入readelf -h 文件名就能看到ELF的头部信息啦!

此处插入一个小知识:readelf一般用于查看ELF格式的文件信息,想要看某个命令的参数只要man一下即可,下面就是我用man查看的readelf的参数呀!

NAME
       readelf - Displays information about ELF files.

SYNOPSIS
       readelf [-a|--all]
               [-h|--file-header]                               //头部信息
               [-l|--program-headers|--segments]
               [-S|--section-headers|--sections]     //节头信息
               [-g|--section-groups]
               [-t|--section-details]
               [-e|--headers]
               [-s|--syms|--symbols]                       //符号信息
               [--dyn-syms]
               [-n|--notes]
               [-r|--relocs]
               [-u|--unwind]
               [-d|--dynamic]
               [-V|--version-info]
               [-A|--arch-specific]
               [-D|--use-dynamic]
               [-x <number or name>|--hex-dump=<number or name>]
               [-p <number or name>|--string-dump=<number or name>]
               [-R <number or name>|--relocated-dump=<number or name>]
               [-z|--decompress]
               [-c|--archive-index]

上面注释的都是要重点讲解的,有兴趣的小伙伴可以去看看其它的命令的作用呦(一定要有好奇心!)

/*sum.c 的ELF头部信息         在Linux中输入命令readelf -h sum.o*/ 
ELF 头:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00  //魔数:文件开头的几个字节通常用来确定文件的类型和格式
  类别:                              ELF64
  数据:                              2 补码,小端序 (little endian)           //数据以小端模式存储
  版本:                              1 (current)                                        
  OS/ABI:                            UNIX - System V                           //操作系统平台
  ABI 版本:                          0
  类型:                              REL (可重定位文件)                         //目标文件的类型
  系统架构:                          Advanced Micro Devices X86-64 //机器结构类型
  版本:                              0x1
  入口点地址:               0x0                                                    //程序执行的入口地址
  程序头起点:          0 (bytes into file)                                    //程序头表的起始位置
  Start of section headers:          576 (bytes into file)               //节头表的起始位置
  标志:             0x0                                                                 //程序执行时的第一条指令
  本头的大小:       64 (字节)                                                    //程序头表的长度
  程序头大小:       0 (字节)
  Number of program headers:         0
  节头大小:         64 (字节)                                                      //节头表的长度
  节头数量:         11
  字符串表索引节头: 10

ps:加载文件时可用魔数确认文件类型是否正确。

/*sum.c 的节头信息          Linux中输入的命令是readelf-S sum.c*/
There are 11 section headers, starting at offset 0x240:               
节头:
  [号] 名称                               类型                              地址                           偏移量
       大小                                全体大小                         旗标            链接   信息   对齐
  [ 0]                                       NULL                              0000000000000000  00000000
       0000000000000000       0000000000000000                                0     0     0
  [ 1] .text                               PROGBITS                     0000000000000000  00000040
       0000000000000045       0000000000000000         AX                  0     0     1
  [ 2] .data                              PROGBITS                     0000000000000000  00000085
       0000000000000000       0000000000000000         WA                 0     0     1
  [ 3] .bss                               NOBITS                         0000000000000000  00000085
       0000000000000000       0000000000000000         WA                 0     0     1
  [ 4] .comment                      PROGBITS                   0000000000000000  00000085
       000000000000002b       0000000000000001         MS                 0     0     1
  [ 5] .note.GNU-stack           PROGBITS                   0000000000000000  000000b0
       0000000000000000      0000000000000000                                0     0     1
  [ 6] .eh_frame                     PROGBITS                   0000000000000000  000000b0
       0000000000000038      0000000000000000            A                  0     0     8
  [ 7] .rela.eh_frame              RELA                            0000000000000000  000001d0
       0000000000000018      0000000000000018            I                   8     6     8
  [ 8] .symtab                        SYMTAB                       0000000000000000  000000e8
       00000000000000d8      0000000000000018                                9     8     8
  [ 9] .strtab                           STRTAB                       0000000000000000  000001c0
       000000000000000b     0000000000000000                                 0     0     1
  [10] .shstrtab                      STRTAB                       0000000000000000  000001e8
       0000000000000054     0000000000000000                                 0     0     1

节头表描述每个节的节名,在文件中的偏移量、大小、访问属性、对齐方式等。

 

/*main.o的符号表信息   在Linux命令中输入readelf -s main.o*/

Symbol table '.symtab' contains 12 entries:
   Num:    Value                   Size       Type         Bind        Vis         Ndx           Name
     0: 0000000000000000     0       NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0            FILE    LOCAL  DEFAULT  ABS           main.c
     2: 0000000000000000     0      SECTION LOCAL  DEFAULT    1 
     3: 0000000000000000     0      SECTION LOCAL  DEFAULT    3 
     4: 0000000000000000     0      SECTION LOCAL  DEFAULT    4 
     5: 0000000000000000     0      SECTION LOCAL  DEFAULT    6 
     6: 0000000000000000     0      SECTION LOCAL  DEFAULT    7 
     7: 0000000000000000     0      SECTION LOCAL  DEFAULT    5 
     8: 0000000000000000     8      OBJECT  GLOBAL DEFAULT    3               array
     9: 0000000000000000    33       FUNC    GLOBAL DEFAULT    1               main
    10: 0000000000000000     0     NOTYPE  GLOBAL DEFAULT  UND     _GLOBAL_OFFSET_TABLE_
    11: 0000000000000000     0     NOTYPE  GLOBAL DEFAULT  UND           sum

8:array是main.o中.data偏移量为0的符号,是全局变量数组中有两个元素占8B。

9:main是main.o中.text偏移量为0的符号,是全局函数占33B.

11:sum是未定义的符号,是在其它模块定义的全局符号。

最后,贴图为证:

 

<think>嗯,用户问的是“可重定位目标文件是什么意思”,我需要先回忆一下相关知识。可重定位目标文件应该和编译链接的过程有关吧。记得编译器将源代码编译成目标文件,链接器再把这些目标文件合并成可执行文件。那“可重定位”具体指什么呢? 首先,可重定位目标文件应该是一种中间文件,还没经过链接。里面可能有代码段、数据段,还有符号表之类的信息。符号表里记录着函数和变量的地址,但这时候的地址可能还是相对偏移或者未确定的,因为链接的时候才会确定最终地址。所以“可重定位”可能指的是这些地址可以被重新定位,调整到最终的内存位置。 用户可能需要知道这个文件的作用,为什么需要它。比如,在大型项目中,不同模块分开编译,生成多个目标文件,链接的时候把它们合并。这时候每个目标文件的地址需要调整,确保合并后不会冲突。可能还要提到重定位表,记录哪些位置需要调整,这样链接器知道怎么处理。 另外,可重定位目标文件的结构应该符合某种标准格式,比如ELF(Executable and Linkable Format)在Linux系统里。需要解释各个部分,如.text存放代码,.data存放已初始化的全局变量,.bss是未初始化的,符号表记录全局符号,重定位条目告诉链接器哪些地方需要修改地址。 可能还要比较一下其他类型的目标文件,比如可执行文件和共享库。可重定位文件还没链接,地址未确定;可执行文件已经链接,地址固定;共享库则是位置无关的,可以在加载时重定位。 需要确保用中文解释清楚这些概念,结构清晰,分点说明。同时注意用户可能不是专业人士,所以例子要具体,比如多个目标文件链接的过程,地址调整的必要性。还要检查术语是否正确,比如“重定位条目”、“符号解析”这些关键词是否准确。 最后,总结一下可重定位目标文件的作用:模块化编译、链接阶段合并、地址调整,以及它在软件开发中的重要性,比如代码复用,分模块开发等。这样用户就能全面理解这个概念了。</think>可重定位目标文件Relocatable Object File)是编译器和汇编器生成的中间文件,它是程序编译过程中的关键产物。以下通过分层结构逐步解释其核心概念: --- ### 一、基本定义 可重定位目标文件是 **未经链接的二进制文件**,包含: 1. **机器指令**(如函数代码) 2. **数据段**(如全局变量) 3. **符号表**(记录函数、变量等符号的引用信息) 4. **重定位信息**(标记需要地址修正的位置) 例如,通过 `gcc -c` 编译 `.c` 文件会生成 `.o` 文件(即可重定位目标文件)。 --- ### 二、核心特性:可重定位性 - **地址未绑定**:代码和数据中的地址是 **相对偏移量** 或 **占位符**,而非最终内存地址。 - **重定位表**:包含需要修改的地址位置(例如 `call` 指令调用的函数地址),供链接器后续修正。 示例: ```text .text 段中的指令: call 0x00000000 // 函数地址未确定,占位为0 .data 段中的变量: .long global_var // global_var的地址未确定 ``` --- ### 三、文件结构(以ELF格式为例) 典型的可重定位目标文件包含以下部分: | 段名 | 作用 | |------------|----------------------------------------------------------------------| | `.text` | 存储编译后的机器指令 | | `.data` | 存储已初始化的全局变量和静态变量 | | `.bss` | 存储未初始化的全局变量(仅占位,不占用文件空间) | | `.symtab` | 符号表,记录全局符号(如函数名、变量名)及其相对地址 | | `.rel.text`| 重定位条目,标记 `.text` 段中需要地址修正的位置 | | `.rel.data`| 重定位条目,标记 `.data` 段中需要地址修正的位置 | --- ### 四、链接器的作用 链接器通过以下步骤处理可重定位目标文件: 1. **符号解析**:将符号引用与定义匹配(例如确定 `printf` 的实现位置)。 2. **重定位**: - 合并所有 `.o` 文件的相同段(如将所有 `.text` 合并)。 - 根据最终内存布局,修正代码和数据的绝对地址。 例如,若链接后 `.text` 段基地址为 `0x400000`,则原占位地址 `0x00000000` 会被修正为实际偏移量。 --- ### 五、对比其他文件类型 | 文件类型 | 特点 | |--------------------|----------------------------------------------------------------------| | 可重定位目标文件 | 地址未绑定,需链接器处理 | | 可执行文件 | 地址已绑定,可直接加载运行 | | 共享库文件 | 位置无关代码(PIC),支持动态链接 | --- ### 六、实际意义 1. **模块化开发**:允许单独编译多个源文件,再链接为最终程序。 2. **代码复用**:静态库(如 `.a` 文件)本质是多个可重定位目标文件的集合。 通过理解可重定位目标文件,可以更深入掌握编译、链接机制及操作系统加载程序的底层逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值