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相同的软链接。