objdump -h # 显示段结构
objdump -s # 将所有段以16进制打印出来
objdump -d # 反汇编
objdump -D # 反汇编更多信息
objdump -t # 查看符号
objdump -R # 查看目标文件重定位表
readelf -S # 显示段结构
readelf -h # 查看文件头
readelf -s # 查看符号表
readelf -l # 查看ELF加载时的Segment
readelf -d
第二章 编译和链接
2.1.1 预处理
gcc –E hello.c –o hello.i
- 展开宏定义
- 处理预编译指令 “#if”、“#ifdef”等等
- 处理 “#include ”预编译指令
- 删除注释
- 添加行号和文件名标识
- 保留所有的 #pragma 编译器指令
2.1.2 编译
编译:gcc –S hello.i –o hello.s
预编译+编译:gcc –S hello.c –o hello.s
2.1.3 汇编
汇编:gcc –c hello.c –o hello.o
-c表示只编译不链接。
2.1.4 链接
第三章 目标文件里有什么
3.1 目标文件的格式
- 可重定位文件 .o, .a
- 可执行文件
- 共享目标文件 .so
- 核心转储文件
file
命令可以查看文件格式
3.3 挖掘SimpleSection.o
查看obj文件内部的段表:
objdump -h SimpleSection.o
(只显示关键的段)
readelf -S SimpleSection.o
(显示所有段)
CONTENTS: 表示该段在文件中存在。可以看到.bss没有CONTENTS,说明在ELF文件中其实不存在,在加载到虚拟地址空间时才会分配空间。
ALLOC: 表示该段在进程空间需要分配空间。
WRITE: 该段在进程空间可写。
objdump -s SimpleSection.o
objdump -d SimpleSection.o
or objdump -s -d SimpleSection.o
-s参数:将所有段内容以16进制方式打印出来
-d参数:将所有包含指令的段反汇编
size命令用来查看ELF文件的代码段、数据段、BSS段的长度
size u-boot
自定义段:
在全局变量或函数之前加上“__ attribute__((section(“name”))) ”属性就可以把相应的变量或函数放到以“ name ”作为段名的段中。
__attribute__((section("FOO"))) int global = 42;
__attribute__((section("BAR"))) void foo()
3.4 ELF文件结构描述
查看ELF文件头:
readelf -h u-boot
ELF Header |
---|
.text |
.data |
.bss |
…other sections |
Section header Table段表 |
.strtab(String tables)字符串表,保存字符串。 |
.symtab(Symbol tables)符号表,保存变量和函数的地址。 |
.shstrtab(Section string tables)段名表,保存段表的段名。 |
3.4.1 文件头
/usr/include/elf.h
3.4.2 段表
/usr/include/elf.h
段表位置可以通过readelf -h 查看elf文件头的start of section headers获得。
readelf输出的结果就是ELF文件段表的内容。以ELF32_Shdr
为结构体的数组。数组元素的个数等于段的个数。
3.4.3 重定位表
readelf -S 中.rel.xxx的Link对应.symtab下表,Info对应.xxx段的下标。
3.4.4 字符串表
字符串集中存放到的一个表,使用字符串在表中的偏移来引用字符串。
3.5 链接的接口-符号
- 定义在本文件的全局符号,可以被其他目标文件引用。
- 在本文件中引用的全局符号,但没有在本文件中定义。称为外部符号。
- 段名。
- 局部符号。
- 行号信息。
nm
指令可以查看ELF文件的符号表。
3.5.1 ELF符号表结构
readelf -s SimpleSection.o
也可以查看符号表。
Size: 符号大小
Type: 符号类型
- NOTYPE
- OBJECT(数据对象,变量/数组)
- FUNC(函数)
- SECTION(段)
- FILE(文件名)
Bind: 符号绑定信息
- LOCAL(局部符号)
- GLOBAL(全局符号)
- WEAK(弱引用)
Vis不用管。
Ndx: 符号所在段
- ABS(表示符号包含一个绝对值,比如文件名的符号)
- COMMON(未初始化的全局符号)
- UNDEF(本目标文件引用,但定义在其他文件)
3.6 调试信息
strip
可以去掉ELF文件中的调试信息。
第四章 静态连接
4.1 空间与地址分配
Linux下,ELF可执行文件默认从地址0x08048000开始分配。
4.2 符号解析和重定位
4.2.2 重定位表
.text ⇒ .rel.text
.data ⇒ .rel.data
查看目标文件重定位表:objdump -r a.o
4.2.4 指令修正方式
- 绝对寻址修正,得到的是该符号的实际地址。
- S+A 符号的实际地址+被修正位置的值
- 相对寻址修正,得到的是符号距离被修正的地址差。
- S+A-P 符号的实际地址+被修正位置的值-被修正的位置的地址
4.3 COMMON块
把未初始化的全局变量标记为一个COMMON类型的变量。
链接完成后,在最终输出文件的BSS段为其分配空间。
第六章 可执行文件的装载和进程
6.1 进程虚拟地址空间
0xC0000000~0xFFFFFFFF: 内核使用
0x0~0xC0000000: 进程使用
32位CPU使用的空间能否超过4GB?
mmap(), 可以将进程的一部分虚拟地址空间映射到不同的物理空间去。
6.4 进程虚存空间分布
6.4.1 ELF文件链接视图和执行视图
对于相同权限的段(Section),把它们合并到一起当作一个段映射。引入新的概念Segment。相当于从装载的角度重新划分了ELF文件的各个段(Section)。这样的好处是可以减少页面内部碎片。
查看ELF的Segmentreadelf -l SectionMapping.elf
Type: LOAD/DYNAMIC/INTERP…
offset: Segment在文件中的偏移。
VirtAddr:Segment的第一个字节在进程虚拟地址空间的起始位置。
PhysAddr: 物理装载地址,一般和Virtaddr相同。
FileSize:Segment在ELF文件中所占的长度。
MemSize:Segment在进程虚拟地址空间所占的长度。
MemSize可能比FileSize大的原因是,在进程虚拟地址空间中分配了bss段,而bss段在elf文件中其实是不存在的。
6.4.2 堆和栈
cat /proc/<pid_num>/maps
可以查看进程的虚拟空间分布。
第七章 动态链接
gcc -fPIC -shared -o Lib.so Lib.c
生成动态链接库。
gcc -o program1 program1.c ./Lib.so
编译program1.c,依赖于lib.so。
gcc -l 小写的L参数,用来指定共享库。或者更简洁的方式-lxxx,xxx为libxxx.so.2.6.1中的名字。
gcc -L 用来指定共享库目录。
7.3 位置无关码
-fPIC
对共享文件编译生成位置无关码。-fPIE
对可执行文件编译生成位置无关码。
.got段
第八章 Linux共享库的组织
8.1.2 共享库版本命名
libname.so.x.y.z
主版本号x表示库的重大升级,不同主版本号的库之间不兼容。
次版本号y表示库的增量升级,增加一些新的接口符号,且保持原来的符号不变,向后兼容的。
发布版本号z表示库的一些错误的修正、性能的改进,不添加任何新的接口,也不对接口进行更改。
8.1.3 SO-NAME
比如libfoo.so.2.6.1的SO-NAME为libfoo.so.2。Linux中,会在每个共享库目录中创建和SO-NAME相同的软链接。