图文并茂,讲透C语言静态链接,ELF文件篇

原创懒人利器2021-01-24 16:25:55

gcc生成执行文件过程为:

  1. 源文件(*.c文件)编译成对象文件(*.o文件);

  2. 链接程序ld,把对象文件(*.o文件)链接成可执行程序。

因此要透彻链接的过程, 需要先了解对象文件(*.o文件)是怎样构成的?


下面用个简单的例子来说明:

#include<stdio.h>int global_var=5;extern int other_file_var;int main(){ int a=1; int b=a other_file_var; return 0;}
  1. gcc -c test.c -o test.o 生成test.o;

  2. 查看test.o内容(vim test.o):

图1

看到的是一堆乱码,因为查看方式不对, 就像mp3格式的文件需要用音乐播放器才能播放一样,对象文件(*.o)是elf格式的, 需要用objdump, readelf 工具来查看。

elf格式的官方文档,可以了解到elf格式文件的结构如下图所示:

图2

下面将一个个部分来分析。

1. ELF文件头(Header)分析

readelf -h test.o 查看elf文件头部信息

  1. 主要字段的含义已在图中标识;

  2. Size of this headers可知,头部占用了64字节;

  3. hexdump -n 64 test.o 查看头部64字节数据内容(16进制格式);图中红框里的数据就是test.o文件的前64字节,也就是elf头部,对比上面两图,(图1)中的魔数7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 刚好与(图2)中的前16字节(小段编码)对应,后面每个字段的含义也是一一对应的;

  4. 头部信息结构可以参考/usr/include/elf.hELf64_Ehdr, 32位的可以参考ELf32_Ehdr结构

typedef struct{  unsigned char e_ident[EI_NIDENT];     /* Magic number and other info */  Elf64_Half    e_type;                 /* Object file type */  Elf64_Half    e_machine;              /* Architecture */  Elf64_Word    e_version;              /* Object file version */  Elf64_Addr    e_entry;                /* Entry point virtual address */  Elf64_Off     e_phoff;                /* Program header table file offset */  Elf64_Off     e_shoff;                /* Section header table file offset */  Elf64_Word    e_flags;                /* Processor-specific flags */  Elf64_Half    e_ehsize;               /* ELF header size in bytes */  Elf64_Half    e_phentsize;            /* Program header table entry size */  Elf64_Half    e_phnum;                /* Program header table entry count */  Elf64_Half    e_shentsize;            /* Section header table entry size */  Elf64_Half    e_shnum;                /* Section header table entry count */  Elf64_Half    e_shstrndx;             /* Section header string table index */} Elf64_Ehdr;
  1. 由字段Start Of Section Header可知,在test.o文件的656字节处有一个'段的头部表'。

2. ELF段头部表(Section Header)分析

readelf -S test.o查看'段的头部表',在这个表里保存了文件里所有段的属性信息,如段的名字,段在文件的开始位置, 段的长度等:

图4

由图可知

  • 主要字段的含义已从图中标识;

  • 段表的数据在偏离文件开始的0x290处,跟ELF Header Start Of Section Header字段保持一致;

  • 这个段表里总共有12项,不同的项描述了不同段的属性;

  • 2.1 text段

    text段在偏离test.o文件开头0x40字节处,长度为0x20字节,用hexdump -s 0x40 -n 0x20 test.o,查看text段的16进制内容。

    图5

    然后objdump -d test.o 打印出程序的反汇编代码,

    图6

    由上面两图可知,text部分的数据恰好是main函数的机器码,也就是text段里保存的是代码段。

    2.2 data段

    data段在偏离test.o文件开头0x60字节处,长度为0x4字节,用hexdump -s 0x60 -n 0x4 test.o,查看data段的16进制内容,

    图7

    4个字节刚好是个int的长度,里面保存的数值是5,也就是全局变量global_var的值,验证了已初始化的全局变量保存在data段。

    2.3 bss段

    详细讲解见:融会贯通C与汇编,以汇编的视角庖丁解牛C语言:变量篇

    2.4 .rela.text (text的重定位段)

    main函数里的global_var定义在别的文件,后面链接需要根据别的文件来确定它的虚拟内存地址,由于text中有需要重定位的变量,所以就有了.rela.text段。readelf -r test.o

    图8

  • offset列表示需要重定位的符号在该段中的偏移,这里表示偏离text段0xd处;

  • info列高4个字节是该符号在.symtab中的索引,见下图;

  • 图9

    2.5 .symtab 符号表

    图9

  • ndx列是该符号在段表中的索引:(1)比如global_var的索引为3,图4中索引3表示data段,

  • 也就是说global_var这个符号在data段;(2)如果是“UND“则表示该符号定义在别的文件,需要重定位,重定位信息见“2.4 .rela.text”。

    图10

(0)

相关推荐