摘要:对于C++的初学者,经常在程序的编译或者加载过程中遇到很多错误,类似undefined reference to ...GLIBCXX_3.4.20 not found 等。这些错误都涉及到编译器、连接器、加载器的相关知识。本系列文章,将通过一个实例程序,详细介绍一个程序的编译、链接、加载的过程。为了弄清这个过程,本文会简要介绍文本代码到可执行二进制文件的大致过程,同时介绍x86平台上linux系统下ELF文件格式,方便后续详细探讨编译-链接-加载的详细过程。

1. 程序的编译与链接过程

对于编译型的程序,代码需要经过编译-链接的过程才会生成可执行程序,具体过程如下

=====> COMPILATION PROCESS <======||---->  Input is Source file(.c)|V+=================+|                 || C Preprocessor  ||                 |+=================+|| ---> Pure C file ( comd:cc -E <file.name> )|V+=================+|                 || Lexical Analyzer||                 |+-----------------+|                 || Syntax Analyzer ||                 |+-----------------+|                 || Semantic Analyze||                 |+-----------------+|                 || Pre Optimization||                 |+-----------------+|                 || Code generation ||                 |+-----------------+|                 || Post Optimize   ||                 |+=================+||--->  Assembly code (comd: cc -S <file.name> )|V+=================+|                 ||   Assembler     ||                 |+=================+||--->  Object file (.obj) (comd: cc -c <file.name>)|V+=================+|     Linker      ||      and        ||     loader      |+=================+||--->  Executable (.Exe/a.out) (com:cc <file.name> ) |VExecutable file(a.out)

  1. 预处理:C语言预处理器展开 宏定义、#include、#deine 生成纯C的代码
  2. 编译
  • 词法分析
  • 语法分析
  • 语义分析
  • 源代码优化:循环优化、无用代码删除等
  • 代码生成
    3. 链接:符号解析、重定位等。注意连接器和加载器的功能区分并不是那么清晰,对于loader而言,也会处理一些链接的工作。

后文用到的main.cpp内容如下,其他代码都在这里 https://github.com/yukun89/draft/tree/master/hello_world/chapter1

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include "func.h"
int global_b = 1;
const int global_c = 1;
int global_d[10];
static int global_e[10];int main(){static char *p = "Begin printf ";int *ip = (int *)malloc(4);*ip = 1;global_b = func(*ip);printf("%s the value of func is %dn", p, func(1));return global_b + global_c;
}

2.ELF文件格式

编译-链接-加载相关的ELF文件主要有两种格式:可重定位目标文件(后缀名为.o) 与 可执行目标文件。(另外还有两种是共享库文件 和 coredump文件。)

分析数据结构之前,我们秉承一个基本原则:结构决定功能;反过来说也成立,设计ELF文件结构,是为了满足特定的功能。这里我们先简要梳理一下,ELF文件应该提供哪些功能?简单来说,ELF文件需要满足可链接、可加载、可执行三大类基本功能,具体来说,包含以下详细功能。

  • 可执行的角度讲,程序需要包含指令与数据,也就是说ELF文件中需要存储程序对应的指令和数据
  • 可链接的角度讲,需要处理不同编译单元之间的引用问题,所以需要符号解析与重定位相关信息
  • 内容组织的角度讲,ELF文件中包含代码、数据、重定位信息等多个section,同时包含这些数据的元数据信息(每个section在文件的起始地址是什么,有多大)。另外,ELF文件格式和其他的任何二进制文件一样,还应该包含一个header,作为所有ELF文件中信息的元数据
  • 可加载的角度讲,ELF文件需要指定将那些代码、数据映射到虚拟内存的什么位置

综上,ELF的文件大致格式如图所示

注意:Section Headers并不在ELF文件的末尾;Program Header table并不存在于每一种ELF文件格式之中。下面我们用linux下的两个命令工具readelfobjdump来详细分析ELF文件中的各个部分。

2.1 ELF文件头

ELF文件的相关定义在/usr/include/elf.h文件之中,具体ELF文件头的信息如下

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;

这里,我们用readelf -h分别查看main.omain两种不同格式ELF文件的文件头,得到的结果如下

ykhuang@0062a6cb7e5e: readelf -h main.o
ELF Header:Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00Class:                             ELF64Data:                              2's complement, little endianVersion:                           1 (current)OS/ABI:                            UNIX - System VABI Version:                       0Type:                              REL (Relocatable file)Machine:                           Advanced Micro Devices X86-64Version:                           0x1Entry point address:               0x0Start of program headers:          0 (bytes into file)Start of section headers:          1112 (bytes into file)Flags:                             0x0Size of this header:               64 (bytes)Size of program headers:           0 (bytes)Number of program headers:         0Size of section headers:           64 (bytes)Number of section headers:         14Section header string table index: 1ykhuang@0062a6cb7e5e : readelf -h main
ELF Header:Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00Class:                             ELF64Data:                              2's complement, little endianVersion:                           1 (current)OS/ABI:                            UNIX - System VABI Version:                       0Type:                              EXEC (Executable file)Machine:                           Advanced Micro Devices X86-64Version:                           0x1Entry point address:               0x400550Start of program headers:          64 (bytes into file)Start of section headers:          4536 (bytes into file)Flags:                             0x0Size of this header:               64 (bytes)Size of program headers:           56 (bytes)Number of program headers:         9Size of section headers:           64 (bytes)Number of section headers:         30Section header string table index: 27

通过以上的内容,我们不难分析,header的主要作用是标识ELF文件中section headers和program headers的位置与大小。header中各个其他字段的解释,我们主要关注以下几点

  • Type表示这个ELF文件属于上文说到的哪种(可重定位还是可执行)ELF文件
  • 程序入口地址Entry point address这一项对于可执行文件才有意义
  • 因为loader只会加载可执行文件,将文件中的代码和数据映射到虚拟MM,所以只有可执行文件的program headers相关信息才有意义。

2.2 ELF文件section

ELF文件中的section主要包括:代码段、数据段、重定位段等信息,section对应的数据结构如下

typedef struct
{Elf64_Word  sh_name;    /* Section name (string tbl index) */Elf64_Word  sh_type;    /* Section type */Elf64_Xword sh_flags;   /* Section flags */Elf64_Addr  sh_addr;    /* Section virtual addr at execution */Elf64_Off sh_offset;    /* Section file offset */Elf64_Xword sh_size;    /* Section size in bytes */Elf64_Word  sh_link;    /* Link to another section */Elf64_Word  sh_info;    /* Additional section information */Elf64_Xword sh_addralign;   /* Section alignment */Elf64_Xword sh_entsize;   /* Entry size if section holds table */
} Elf64_Shdr;

下面,让我们来分别查看可重定位目标文件与可执行目标文件的section信息

####可重定位目标文件的信息ykhuang@0062a6cb7e5e  ~/project/draft/hello_world/chapter1   master ●  readelf -S -W main.o
There are 14 section headers, starting at offset 0x1c0:Section Headers:[Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al[ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0[ 1] .text             PROGBITS        0000000000000000 000040 000063 00  AX  0   0  4[ 2] .rela.text        RELA            0000000000000000 000750 0000c0 18     12   1  8[ 3] .data             PROGBITS        0000000000000000 0000a8 000010 00  WA  0   0  8[ 4] .rela.data        RELA            0000000000000000 000810 000018 18     12   3  8[ 5] .bss              NOBITS          0000000000000000 0000c0 000068 00  WA  0   0 32[ 6] .rodata           PROGBITS        0000000000000000 0000c0 00002e 00   A  0   0  4[ 7] .comment          PROGBITS        0000000000000000 0000ee 00002d 01  MS  0   0  1[ 8] .note.GNU-stack   PROGBITS        0000000000000000 00011b 000000 00      0   0  1[ 9] .eh_frame         PROGBITS        0000000000000000 000120 000038 00   A  0   0  8[10] .rela.eh_frame    RELA            0000000000000000 000828 000018 18     12   9  8[11] .shstrtab         STRTAB          0000000000000000 000158 000066 00      0   0  1[12] .symtab           SYMTAB          0000000000000000 000540 0001b0 18     13  11  8[13] .strtab           STRTAB          0000000000000000 0006f0 00005a 00      0   0  1
Key to Flags:W (write), A (alloc), X (execute), M (merge), S (strings), l (large)I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)O (extra OS processing required) o (OS specific), p (processor specific)

其中type字段的含义如下:

  • PROGBITS: 程序内容,包含代码、数据、调试相关信息
  • NOBITS:和PROGBITS类似,唯一不同的是在文件中不占空间,对应的进行内存空间是加载的时候申请的
  • SYSTAM/DYNSYM: SYSTAM 表用于普通链接;DYNSYM用于动态链接
  • STRTAB:string table,用于section名称、普通的符号名称、动态链接的符号名称。 据此,我们绘制出main.o文件的布局如下:

可执行文件的信息比较繁琐,我们大致给出,后续再分析具体每个section的含义与作用。

####可执行文件的section信息ykhuang@0062a6cb7e5e  ~/project/draft/hello_world/chapter1   master ●  readelf -S -W main
There are 30 section headers, starting at offset 0x11b8:Section Headers:[Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al[ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0[ 1] .interp           PROGBITS        0000000000400238 000238 00001c 00   A  0   0  1[ 2] .note.ABI-tag     NOTE            0000000000400254 000254 000020 00   A  0   0  4[ 3] .note.gnu.build-id NOTE            0000000000400274 000274 000024 00   A  0   0  4[ 4] .gnu.hash         GNU_HASH        0000000000400298 000298 00001c 00   A  5   0  8[ 5] .dynsym           DYNSYM          00000000004002b8 0002b8 0000c0 18   A  6   1  8[ 6] .dynstr           STRTAB          0000000000400378 000378 0000b7 00   A  0   0  1[ 7] .gnu.version      VERSYM          0000000000400430 000430 000010 02   A  5   0  2[ 8] .gnu.version_r    VERNEED         0000000000400440 000440 000020 00   A  6   1  8[ 9] .rela.dyn         RELA            0000000000400460 000460 000018 18   A  5   0  8[10] .rela.plt         RELA            0000000000400478 000478 000060 18   A  5  12  8[11] .init             PROGBITS        00000000004004d8 0004d8 00001a 00  AX  0   0  4[12] .plt              PROGBITS        0000000000400500 000500 000050 10  AX  0   0 16[13] .text             PROGBITS        0000000000400550 000550 000224 00  AX  0   0 16[14] .fini             PROGBITS        0000000000400774 000774 000009 00  AX  0   0  4[15] .rodata           PROGBITS        0000000000400780 000780 00003e 00   A  0   0  8[16] .eh_frame_hdr     PROGBITS        00000000004007c0 0007c0 000044 00   A  0   0  4[17] .eh_frame         PROGBITS        0000000000400808 000808 000134 00   A  0   0  8[18] .init_array       INIT_ARRAY      0000000000600de0 000de0 000008 00  WA  0   0  8[19] .fini_array       FINI_ARRAY      0000000000600de8 000de8 000008 00  WA  0   0  8[20] .jcr              PROGBITS        0000000000600df0 000df0 000008 00  WA  0   0  8[21] .dynamic          DYNAMIC         0000000000600df8 000df8 000200 10  WA  6   0  8[22] .got              PROGBITS        0000000000600ff8 000ff8 000008 08  WA  0   0  8[23] .got.plt          PROGBITS        0000000000601000 001000 000038 08  WA  0   0  8[24] .data             PROGBITS        0000000000601038 001038 000018 00  WA  0   0  8[25] .bss              NOBITS          0000000000601050 001050 000038 00  WA  0   0 16[26] .comment          PROGBITS        0000000000000000 001050 000060 01  MS  0   0  1[27] .shstrtab         STRTAB          0000000000000000 0010b0 000108 00      0   0  1[28] .symtab           SYMTAB          0000000000000000 001938 0006d8 18     29  47  8[29] .strtab           STRTAB          0000000000000000 002010 00028f 00      0   0  1
Key to Flags:W (write), A (alloc), X (execute), M (merge), S (strings), l (large)I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)O (extra OS processing required) o (OS specific), p (processor specific)

2.2.1 代码段(txt)-数据段-只读数据段

代码段的信息,我们可以用objdump -s -d main.o具体查看代码段的信息,此处不展开讨论。

数据段信息如下:

ykhuang@0062a6cb7e5e  ~/project/draft/hello_world/chapter1   master ●  objdump -s -d main.omain.o:     file format elf64-x86-64Contents of section .text:0000 554889e5 4883ec10 bf040000 00e80000  UH..H...........0010 00004889 45f8488b 45f8c700 01000000  ..H.E.H.E.......0020 488b45f8 8b0089c7 e8000000 00890500  H.E.............0030 000000bf 01000000 e8000000 0089c248  ...............H0040 8b050000 00004889 c6bf0000 0000b800  ......H.........0050 000000e8 00000000 8b050000 000083c0  ................0060 01c9c3                               ...
Contents of section .data:0000 01000000 00000000 00000000 00000000  ................
Contents of section .rodata:0000 01000000 25732074 68652076 616c7565  ....%s the value0010 206f6620 66756e63 20697320 25640a00   of func is %d..0020 42656769 6e207072 696e7466 2000      Begin printf .

查看符号表信息如下 objdump -x main.o

SYMBOL TABLE:
0000000000000000 g     O .data  0000000000000004 global_b
0000000000000000 g     O .rodata    0000000000000004 global_c
0000000000000000 g     O .bss   0000000000000028 global_d
0000000000000000 g     F .text  0000000000000063 main

从这里我们可以看出,我们只有依赖符号表,才能知道某个变量存放的具体数值信息。

3.其他

需要指出的是,ELF文件格式之所以是现在这种结构,是由体系结构和操作系统来决定的。在一些其他的系统上(例如MS-DOS或者IBM system V),编译-链接的中间文件具有完全不同的结构。总体来说,这些二进制文件主要需要满足可链接、可加载、可执行。这里,我们简要列出了另外两种编译-链接-加载相关的文件结构:

  • COM(component object model)文件:MS-DOS系统上的可执行文件。只有二进制代码,不包含其他任何信息,代码会自动load到0x100,只支持一个代码段。
  • a.out 文件:unix上可执行文件的一种,包含header、代码段、数据段、其他段。程序执行的过程主要是“读取文件头; map代码段;map私有数据段; 创建进行栈; 设置寄存器然后跳转到程序开头”

ELF文件是目前linux平台上最通用的一种可链接-加载-执行的文件结构,对于不同的语言,例如C/C++,他们对应的ELF文件格式略微有所不同:C++相对于C编译而成的ELF文件格式有自己独特的section。了解ELF文件格式有利于我们后续详细理解程序的链接-加载-执行过程

最后放一下blog地址,欢迎来玩

编译-链接-加载:ELF文件格式解析 | 优孚​www.uufool.com

参考:

  1. linker && loader
  2. https://stackoverflow.com/questions/3996651/what-is-compiler-linker-loader

cfile清空文件内容_编译-链接-加载 :ELF文件格式解析相关推荐

  1. cfile清空文件内容_体育老师学编程(第11天)python常用的文件读写操作

    学习内容:python文件处理 一.什么是文件: 前边学习了计算机的存储设备分为内存和硬盘两种,内存容量小,断电就丢失,我们若想长期存储一段内容,就需要存到硬盘中,那么存入的方式就是以文件形式存入的. ...

  2. cfile清空文件内容_电脑C盘文件夹哪些可以删除?教你如何快速清理,旧电脑还能用3年...

    电脑用久了,便会越来越卡顿,不少电脑卡顿的原因就在于C盘快满了.今天小编所说的就是教大家如何快速删除这些文件夹,释放我们的电脑空间,从而保证我们电脑的流畅性.下面我们就一起来看看,哪些文件夹可以删除吧 ...

  3. Linux下C/C++程序编译链接加载过程中的常见问题及解决方法

    Linux下C/C++程序编译链接加载过程中的常见问题及解决方法 1 头文件包含的问题 报错信息 该错误通常发生在编译时,常见报错信息如下: run.cpp:2:10: fatal error: dl ...

  4. C++程序编译-链接-加载过程初探-符号表

    文章目录 前言 符号表 nm指令 看一下hello world 变量和函数在符号表中的位置 看看类的定义 把类和主程序一起打包测试 符号表角度看编译与链接 加载程序到内存 前言 从自己接触计算机程序设 ...

  5. go 清空文件内容_回收站清空了怎么恢复?恢复回收站以前的文件

    获取专业数据恢复软件: 专注硬盘U盘误删文件数据恢复软件免费下载​dl-next.aunbox.cn 回收站清空了怎么恢复?很多时候为了保障电脑有足够的存储空间,我们会把不需要的电脑文件删除,这些文件 ...

  6. go 清空文件内容_回收站不小心清空后怎么恢复里面文件

    日常操作中常用的操作中,对于一些不重要不想要的文件会临时删除,也有删除错误想找回的,怎么办呢,也有人为了电脑运行速度快习惯性经常性的清除一下电脑垃圾,一不小心就可能把其他的文件删了,相信电脑高手要找回 ...

  7. idea overlays文件夹_使用IDEA加载maven项目没有出现overlays目录的解决方法

    使用IDEA加载maven项目没有出现overlays目录的解决方法 欢迎使用Markdown编辑器 你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页.如果你想学习如何使用Markdo ...

  8. go 清空文件内容_玩转Go单元测试,你只需要掌握这5点

    最近在给项目代码完善单元测试,发现go语言单元测试相关的资料都是零零散散的,所以在这儿整理总结一下.项目中使用的是goconvey+monkey+sqlmock (项目的web框架为gin, 持久层框 ...

  9. ofstream清空文件内容_回收站被删除的文件怎么恢复 回收站清空了怎么恢复

    现在经常有很多粗心的网友将重要的数据文件删除后,还不小心把回收站清空了,怎么恢复成了一个大问题,其实还是有方法可以恢复的,大家可以看看下面的回收站清空后怎么恢复的教程,帮你找回需要的文件. 回收站被清 ...

最新文章

  1. 面试官:原生GAN都没复现过,自己走还是我送你?
  2. java lazy loading_java – Spring,@Transactional和Hibernate Lazy Loading
  3. cnn池化层输入通道数_(pytorch-深度学习系列)CNN中的池化层-学习笔记
  4. 思维模型篇:数据分析的本质是什么?
  5. Java中Thread类的方法简介
  6. 模型压缩与加速:Octave Convolution
  7. 索尼申请“Pregius S”商标 或用于CMOS图像传感器
  8. 已知后序与中序输出前序(先序)
  9. python和java哪个好学-Python和Java发展前景哪个好?老男孩python高级编程
  10. Linked List Cycle | ||
  11. Linux下make -j加快编译速度
  12. 远程服务RMI源码解析(二)
  13. Ubuntu内核升级导致显卡冲突,升级显卡并禁用自动更新教程
  14. Mybatis注解开发笔记
  15. 一篇今日头条百万阅读量爆文怎么来的,自媒体月收入暴涨10万+
  16. 变量命名规范--匈牙利命名法,骆驼命名法,帕斯卡命名法
  17. Android GoogleMap 接入
  18. 量子统计:玻尔兹曼分布、玻色分布、费米分布
  19. 那些年,我所接触过的采集器...
  20. select简单2级联动

热门文章

  1. 2017年度最值得读的AI论文 | NLP篇 · 评选结果公布
  2. java 加法 溢出_java实现两个大数相加,可能出现溢出错误
  3. javascript基础教程_JavaScript基础教程(九)对象、类的定义与使用
  4. SpringBoot 集成 druid 监控数据库报错 Failed to bind properties under ‘xxxx‘ to javax.sql.DataSource 解决(含配置源码)
  5. idea webapp目录404问题,war包方式运行
  6. 回溯法(其实是递归)
  7. springboot不能加载https的证书文件(二)
  8. The POM for com.ruifeng.tjtaxiqy:shiro:jar:0.0.1-SNAPSHOT is missing, no dependency information avai
  9. Visual C++——Visual C++ 6.0 转 Visual Studio[Visual C++]编译错误[错误 D8016 “/ZI”和“/Gy-”命令行选项不兼容]解决方案
  10. JavaScript——易班优课YOOC课群在线测试自动答题解决方案(八)功能面板