上章所生成的可执行文件是一种ELF文件。所以这次来分析下什么是ELF。
一、什么是ELF
定义:ELF 英文全称为Executable and Linking Format即可执行链接格式,作为应用程序二进制接口(Application Binary Interface,ABI)的一部分。
那么什么又是ABI?
ABI描述了应用程序与OS之间的底层接口。ABI涉及了程序的各个方面,比如:目标文件格式、数据类型、数据对齐、函数调用约定以及函数如何传递参数、如何返回值、系统调用号、如何实现系统调用等。
ELF一共有三种类型:
-
可重定位文件(Relocatable File) .o或.a)包含适合于与其他目标文件静态链接来创建可执行文件或者共享目标文件的代码和数据。
-
可执行文件(Executable File) .out或.exe) 包含适合于执行的一个程序,此文件规定了exec() 如何创建一个程序的进程映像。
-
共享目标文件(Shared Object File) .so) 包含可在两种上下文中链接的代码和数据。首先链接编辑器可以将它和其它可重定位文件和共享目标文件一起处理, 生成另外一个目标文件。其次动态链接器(Dynamic Linker)可能将它与某 个可执行文件以及其它共享目标一起组合,创建进程映像,如libc.so。
小结:通过上章的 《进程分析之旅01之C程序的可执行文件形成过程》,gcc再生成.o文件和.out文件时都是严格遵循ELF的,所以elf就是为了程序员写的代码最终能在操作系统中执行起来,定义的一种格式规范。包括生成在磁盘中的可执行文件格式,以及如何加载在运行内存中的格式。可见如果一个程序发生错误一定会涉及到elf所定义到模块字段格式,理解elf对我们分析程序有着最为本质的作用。
二、ELF格式
EFL格式需要从两个视角分析,一个是链接视角即非运行时存储在磁盘中的格式,是静态的,还有一个程序运行时在运行内存中的执行视角格式,是动态。如下图:
从链接视角格式到可执行视角格式需要经过加载,解析,内存映射。涉及到许多非常复杂的转换机制。linux这一过程是由内核完成的,所以如果要分析elf的成员可以分析linux在解析可执行文件时所涉及到的结构体。这里首先对这两种格式进行分析下。
1.链接视角格式
主要包括ELF Header,program header table (可选),section、section header table。
- ELF header在文件开始处描述了整个文件的组织,
- Section提供了目标文件的各项信息(如指令、数据、符号表、重定位信息等),用于linker 链接
- Program header table指出怎样创建进程映像,含有每个program header的入口,用于loader加载,可重定位文件不需要这个表
- section header table包含每一个section的入口,给出名字、大小等信息。
下面从格式字段说明表、linux内核数据结构、实际文件格式三方面一一分析上述数据段。
i.ELF Header
作用:ELF header在文件开始处描述了整个文件的组织结构。
linux 内核中的数据结构:
实际可执行文件中的内容:
执行命令 readelf -h a.out
ii.Section
作用:目标文件的各项信息(如指令、数据、符号表、重定位信息等)
字段说明表:
linux内核数据结构:
实际执行文件分析:
执行 readelf -S helloworld.out
总共28个section
各个section官方解释:
.bss This section holds uninitialized data that contribute to the program’s memory image. By
definition, the system initializes the data with zeros when the program begins to run. The
section occupies no file space, as indicated by the section type, SHT_NOBITS.
.comment This section holds version control information.
.data and .data1
These sections hold initialized data that contribute to the program’s memory image.
.debug This section holds information for symbolic debugging. The contents are unspecified.
.dynamic This section holds dynamic linking information. The section’s attributes will include the
SHF_ALLOC bit. Whether the SHF_WRITE bit is set is processor specific. See Part 2 for
more information.
.dynstr This section holds strings needed for dynamic linking, most commonly the strings that
represent the names associated with symbol table entries. See Part 2 for more information.
.dynsym This section holds the dynamic linking symbol table, as ‘‘Symbol Table’’ describes. See
Part 2 for more information.
.fini This section holds executable instructions that contribute to the process termination code.
That is, when a program exits normally, the system arranges to execute the code in this
section.
.got This section holds the global offset table. See ‘‘Special Sections’’ in Part 1 and ‘‘Global
Offset Table’’ in Part 2 for more information.
.hash This section holds a symbol hash table. See ‘‘Hash Table’’ in Part 2 for more information.
.init This section holds executable instructions that contribute to the process initialization code.
That is, when a program starts to run, the system arranges to execute the code in this section before calling the main program entry point (called main for C programs).
.interp This section holds the path name of a program interpreter. If the file has a loadable segment that includes the section, the section’s attributes will include the SHF_ALLOC bit; otherwise, that bit will be off. See Part 2 for more information.
.line This section holds line number information for symbolic debugging, which describes the
correspondence between the source program and the machine code. The contents are
unspecified.
.note This section holds information in the format that ‘‘Note Section’’ in Part 2 describes.
.plt This section holds the procedure linkage table. See ‘‘Special Sections’’ in Part 1 and ‘‘Procedure Linkage Table’’ in Part 2 for more information.
.relname and .relaname
These sections hold relocation information, as ‘‘Relocation’’ below describes. If the file has
a loadable segment that includes relocation, the sections’ attributes will include the
SHF_ALLOC bit; otherwise, that bit will be off. Conventionally, name is supplied by the
section to which the relocations apply. Thus a relocation section for .text normally
would have the name .rel.text or .rela.text.
.rodata and .rodata1
These sections hold read-only data that typically contribute to a non-writable segment in
the process image. See ‘‘Program Header’’ in Part 2 for more information.
.shstrtab This section holds section names.
.strtab This section holds strings, most commonly the strings that represent the names associated
with symbol table entries. If the file has a loadable segment that includes the symbol string
table, the section’s attributes will include the SHF_ALLOC bit; otherwise, that bit will be off.
.symtab This section holds a symbol table, as ‘‘Symbol Table’’ in this section describes. If the file
has a loadable segment that includes the symbol table, the section’s attributes will include
the SHF_ALLOC bit; otherwise, that bit will be off.
.text This section holds the ‘‘text,’’ or executable instructions, of a program
如何单独查看每个section 如 .text
readelf -x .text helloworld.out
section flags:
看每个Section的Flags我们也可以得到一些信息,比如.text section的Flags是AX,表示要分配内存,并且是可执行的,这一节代码无疑了。.data 和 .bss的Flags的Flags都是WA,表示可写,需分配内存,这都是数据段的特征。
iii.程序头部program header
作用:可执行文件或者共享目标文件的程序头部是一个结构数组,每个结构描述了一个segment段 或者系统准备程序执行所必需的其它信息。目标文件的“segment”包含一个或者多个“section节区”, 也就是“段内容(Segment Contents)”。程序头部仅对于可执行文件和共享目标文件 有意义。
linux内核数据结构:
实际可执行文件分析:
执行 readelf -l helloworld.out
由图可见: 一个program header对应一个segment,而一个segment包含0个或一个或多个section,segment 会按照program header 中的值映射到加载到虚拟内存中去。
iv.符号表
作用:符号表分定义和引用符号
1. 定义符号:定义在本文件中定义好的函数或者全局变量或者静态变量,在链接时需要重定位
2.引用符号:定义在其他模块中的函数,或者使用extern 关键字声明的,在链接时需要定位到相应的模块,然后再重定位。
字段说明表:
实际可执行文件分析:
执行 readelf -s a.out
type: 字段中st_type 中包含符号类型:
bind : 字段中的st_info
三、实例分析(仅测试用):
#include <stdio.h>
#include <stdlib.h>
char bbs_test[100];
char init_test[100] = {0};
char* heap_test;
void main(){
char stack_test[10] = {0};
heap_test = (char*)malloc(100);
printf("\nbbs_test = 0x%x, init_test = 0x%x heap_test = %0x, statck_test = 0x%x \n",bbs_test,init_test,heap_test,stack_test);
printf("hello world\n");
}
执行该程序,打印结果为:
执行命令:readelf -a a.out |grep test 只能搜到三项:
疑问: 堆地址为什么跟symbol table中的地址值不一致,这可能涉及到虚拟内存的映射、以及分配机制的问题以后有时间再分析。
四、总结
本文主要分析了gcc 的生成 elf 文件,也就是elf文件的链接视角,如可执行文件(a.out)、可重定位对象文件(*.o)、共享动态库(*.so)。然后分析elf header、section、program header、symbol table 字段,字段中成员的含义。然后分析了section 和 program header 以及 segment的对应关系。segment是elf在执行时的重要字段,那么segment又是如何加载到虚拟内存中变成一个进程运行起来的呢? 下一章将会详细分析。
五、参考:
https://segmentfault.com/a/1190000007103522?utm_source=tuicool&utm_medium=referral
<<Executable and Linkable Forma>>