深入浅出c语言最后一题 (图文并茂通俗解读)

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):

c语言fixed函数的使用方法,typedef用法详解C语言

图1

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

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

c语言fixed函数的使用方法,typedef用法详解C语言

图2

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

1. ELF文件头(Header)分析

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

c语言fixed函数的使用方法,typedef用法详解C语言

图3

  1. 主要字段的含义已在图中标识;
  2. Size of this headers 可知,头部占用了64字节;
  3. hexdump -n 64 test.o 查看头部64字节数据内容(16进制格式);

c语言fixed函数的使用方法,typedef用法详解C语言

图4

图中红框里的数据就是test.o文件的前64字节,也就是elf头部,对比上面两图,(图3)中的魔数7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 刚好与(图4)中的前16字节(小段编码)对应,后面每个字段的含义也是一一对应的。

  1. 头部信息结构可以参考 /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 查看"段的头部表",在这个表里保存了文件里所有段的属性信息,如段的名字,段在文件的开始位置, 段的长度等:

c语言fixed函数的使用方法,typedef用法详解C语言

图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进制内容。

    c语言fixed函数的使用方法,typedef用法详解C语言

    图5

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

    c语言fixed函数的使用方法,typedef用法详解C语言

    图6

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

    2.2 data段

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

    c语言fixed函数的使用方法,typedef用法详解C语言

    图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

    c语言fixed函数的使用方法,typedef用法详解C语言

    图8

  • offset列表示需要重定位的符号在该段中的偏移,这里表示偏离text段0xd处;
  • info列高4个字节是该符号在.symtab中的索引,见下图;
  • c语言fixed函数的使用方法,typedef用法详解C语言

    图9

    2.5 .symtab 符号表

    c语言fixed函数的使用方法,typedef用法详解C语言

    图9

  • ndx列是该符号在段表中的索引:(1)比如global_var的索引为3,图4中索引3表示data段,
  • 也就是说global_var这个符号在data段;(2)如果是“UND“则表示该符号定义在别的文件,需要重定位,重定位信息见“2.4 .rela.text”。

    c语言fixed函数的使用方法,typedef用法详解C语言

    图10

    站在C语言的肩膀上学汇编(1):栈

    站在C语言的肩膀上学汇编(2):栈

    站在C语言的肩膀上学汇编(3):栈

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

    创造不易,如果帮助到了您,求点赞。谢谢!