//main.c
void swap(); // UND
int data[] = {11,22}; // .data
int main() { // .text
swap();
static int a = 1; // .data
return a;
}
//swap.c
extern int data[]; // UND
int * bufp0 = &data[0]; // .data
int * bufp1; // .bss
void swap() { // .text
int temp; // stack
bufp1 = &data[0];
temp = * bufp0;
* bufp0 = * bufp1;
* bufp1 = temp;
}
编译
gcc -v main.c swap.c -o b&& ./b
编译器
main.c -> /tmp/ccVSPuud.s
/usr/lib/gcc/x86_64-linux-gnu/5/cc1
-quiet # Do not display functions compiled or elapsed time
-v # Enable verbose output
-imultiarch x86_64-linux-gnu # Set x86_64-linux-gnu to be the multiarch include subdirectory
main.c
-quiet
-dumpbase main.c # Set the file basename to be used for dumps
-mtune=generic # Schedule code for given CPU
-march=x86-64 # Generate code for given CPU
-auxbase main
-version # Display the compiler's version
-fstack-protector-strong # Use a smart stack protection method for certain functions
-Wformat # Warn about printf/scanf/strftime/strfmon format string anomalies
-Wformat-security # Warn about possible security problems with format functions
-o /tmp/ccVSPuud.s # Place output into /tmp/ccVSPuud.s
汇编器
ccVSPuud.s -> cc8urR1R.o
as -v --64 -o /tmp/cc8urR1R.o /tmp/ccVSPuud.s
链接器
cc8urR1R.o (main.c)
ccxPHKDw.o (swap.c)
-> b
/usr/lib/gcc/x86_64-linux-gnu/5/collect2
-plugin /usr/lib/gcc/x86_64-linux-gnu/5/liblto_plugin.so # 加载插件程序
-plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper # 发送参数给最后加载的插件程序
-plugin-opt=-fresolution=/tmp/ccAO3sgb.res
-plugin-opt=-pass-through=-lgcc
-plugin-opt=-pass-through=-lgcc_s
-plugin-opt=-pass-through=-lc
-plugin-opt=-pass-through=-lgcc
-plugin-opt=-pass-through=-lgcc_s
--sysroot=/ # 强制覆写缺省的 sysroot 位置
--build-id # Generate build ID note
--eh-frame-hdr # Create .eh_frame_hdr section
-m elf_x86_64 # 设定仿真
--hash-style=gnu # Set hash style to sysv, gnu or both
--as-needed # 如果使用的话,只有设置 DT_NEEDED 于下述的动态函数库
-dynamic-linker /lib64/ld-linux-x86-64.so.2 # 将/lib64/ld-linux-x86-64.so.2设为要使用的动态链接器
-z relro # Create RELRO program header
-o b
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o
-L/usr/lib/gcc/x86_64-linux-gnu/5 # 目录
-L/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu # 目录
-L/usr/lib/gcc/x86_64-linux-gnu/5/../../../../lib # 目录
-L/lib/x86_64-linux-gnu # 目录
-L/lib/../lib # 目录
-L/usr/lib/x86_64-linux-gnu # 目录
-L/usr/lib/../lib # 目录
-L/usr/lib/gcc/x86_64-linux-gnu/5/../../.. # 目录
/tmp/cc8urR1R.o #main.c
/tmp/ccxPHKDw.o #swap.c
-lgcc --as-needed # gcc库 main函数开始时自动会调用__main,而__main在gcc标准库中
-lgcc_s --no-as-needed # gcc_s库
-lc # c库
-lgcc --as-needed
-lgcc_s --no-as-needed
/usr/lib/gcc/x86_64-linux-gnu/5/crtend.o
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o
Collect2在编译器安装目录中,当作ld。
- 符号解析:文件间变量名对应
- 重定位:把链接文件的相对地址变成绝对地址
- 可重定位目标文件:ccVSPuud.s cc8urR1R.o
- 可执行目标文件:b
ELF
Acronyms relevant to Executable and Linkable Format (ELF)
ELF Header
readelf main.o -h
ELF 头:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
类别: ELF32
数据: 2 补码,小端序 (little endian)
版本: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: REL (可重定位文件)
系统架构: Intel 80386
版本: 0x1
入口点地址: 0x0
程序头起点: 0 (bytes into file)
节头起点: 560 (bytes into file)
标志: 0x0
本头的大小: 52 (字节)
程序头大小: 0 (字节)
程序头数量: 0
节头大小: 40 (字节)
节头数量: 12
字符串表索引节头: 9
Section Header Table
readelf main.o -S
ELF头0x34字节,故[1]节头偏移0x34
[9]节尾偏移量 0x22f,故table从0x230开始
共有 12 个节头,从偏移量 0x230 开始:
节头:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034 000024 00 AX 0 0 1
[ 2] .rel.text REL 00000000 0001c0 000010 08 I 10 1 4
[ 3] .data PROGBITS 00000000 000058 00000c 00 WA 0 0 4
[ 4] .bss NOBITS 00000000 000064 000000 00 WA 0 0 1
[ 5] .comment PROGBITS 00000000 000064 000035 01 MS 0 0 1
[ 6] .note.GNU-stack PROGBITS 00000000 000099 000000 00 0 0 1
[ 7] .eh_frame PROGBITS 00000000 00009c 000044 00 A 0 0 4
[ 8] .rel.eh_frame REL 00000000 0001d0 000008 08 I 10 7 4
[ 9] .shstrtab STRTAB 00000000 0001d8 000057 00 0 0 1
[10] .symtab SYMTAB 00000000 0000e0 0000c0 10 11 9 4
[11] .strtab STRTAB 00000000 0001a0 00001e 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
.symtab
readelf main.o -s
main函数在[1].text,data在[3].data偏移0,a在[3]偏移8
main.c不被重定位,swap引用外部
Symbol table '.symtab' contains 12 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS main.c
2: 00000000 0 SECTION LOCAL DEFAULT 1
3: 00000000 0 SECTION LOCAL DEFAULT 3
4: 00000000 0 SECTION LOCAL DEFAULT 4
5: 00000008 4 OBJECT LOCAL DEFAULT 3 a.1482
6: 00000000 0 SECTION LOCAL DEFAULT 6
7: 00000000 0 SECTION LOCAL DEFAULT 7
8: 00000000 0 SECTION LOCAL DEFAULT 5
9: 00000000 8 OBJECT GLOBAL DEFAULT 3 data
10: 00000000 36 FUNC GLOBAL DEFAULT 1 main
11: 00000000 0 NOTYPE GLOBAL DEFAULT UND swap
符号解析
链接器
- Rule 1: Multiple strong symbols are not allowed.
- Rule 2: Given a strong symbol and multiple weak symbols, choose the strong symbol.
- Rule 3: Given multiple weak symbols, choose any of the weak symbols.
静态库
- 把addvec.o和multvec.o打包成libvector.a,ibvector.a称为静态库
ar rcs libvector.a addvec.o multvec.o - 链接静态库要放在尾部
gcc -static main.o ./libvector.a - 链接器会从左到右扫描,先main,再到libvector,维持E D U集合
E: 需要用到的模块集
D: 已经定义的符号集
U: 未定义的符号集
一直扫描 libvector至EUD不再变化 - 结束时U非空就报错,否则链接E
- 库内可以相互依赖,因为一直扫描,库间依赖要重复库
gcc -static main.o x.a y.a x.a - p依赖x,x依赖y,y依赖x,x依赖a
gcc p.o libx.a liby.a libx.a
尾部无需再链p,因为p是个块,而xy是集合。块是全入,集合是按需入。
EDU是根据新来的 块 来更新,即使y依赖a,但a已定义的符号集早已全部入D
而x仅仅把被p依赖的块的符号集入D,并不能确保y依赖的那个块有没有入
重定位
objdump -D main.o
00000000 <main>:
0: 8d 4c 24 04 lea 0x4(%esp),%ecx
4: 83 e4 f0 and $0xfffffff0,%esp
7: ff 71 fc pushl -0x4(%ecx)
a: 55 push %ebp
b: 89 e5 mov %esp,%ebp
d: 51 push %ecx
e: 83 ec 04 sub $0x4,%esp
11: e8 fc ff ff ff call 12 <main+0x12>
16: a1 08 00 00 00 mov 0x8,%eax
1b: 83 c4 04 add $0x4,%esp
1e: 59 pop %ecx
1f: 5d pop %ebp
20: 8d 61 fc lea -0x4(%ecx),%esp
23: c3 ret
objdump -D b
080483db <main>:
80483db: 8d 4c 24 04 lea 0x4(%esp),%ecx
80483df: 83 e4 f0 and $0xfffffff0,%esp
80483e2: ff 71 fc pushl -0x4(%ecx)
80483e5: 55 push %ebp
80483e6: 89 e5 mov %esp,%ebp
80483e8: 51 push %ecx
80483e9: 83 ec 04 sub $0x4,%esp
80483ec: e8 0e 00 00 00 call 80483ff <swap>
80483f1: a1 20 a0 04 08 mov 0x804a020,%eax
80483f6: 83 c4 04 add $0x4,%esp
80483f9: 59 pop %ecx
80483fa: 5d pop %ebp
80483fb: 8d 61 fc lea -0x4(%ecx),%esp
80483fe: c3 ret
080483ff <swap>:
80483ff: 55 push %ebp
节和符号
同类节合并,比如main.o和swap.o的[1].text节。 合并后就有新的符号表,也定下[1].text存储地址,根据新符号表的main的偏移量就能确定其存储地址。
同样.data节也会合并再确定存储地址
节中符号
main.o地址0x11处
11: e8 fc ff ff ff call 12 <main+0x12>
这条指令执行后PC会指向0x16,加上0xfffffffc就是0x12,也就是CPU会去执行fc ff ff ff。
fc ff ff ff显然这不对,因为main.o没有链接swap.o,这里fc ff ff ff仅是汇编器临时设的,方便日后链接器操作。
实际上,main中的swap在.rel.text而并.text,objdump为了方便才拼在一起。
swap就是节中符号
重定位条目
readelf main.o -R 2
“.rel.text”节的十六进制输出: 0x00000000 12000000 020b0000 17000000 01030000 …
从Section Header Table中可以看出“.rel.text”的size为16
0x00000000是节内偏移,也就是节内地址。
“.rel.text”节实际为0x12000000 020b0000 17000000 01030000
main+0x12中的0x12就是第一个字段
链接器根据swap存储地址就能算出call后面具体要跳多远。
但为什么是-4呢?很容易想到这里地址长度是4字节,32位,如果是64位,应该是-8。
取指call后,pc就变了,指明-4有利于链接器判断PC+4还是+8
swap.c中
int * bufp0 = &data[0];
从swap.o的.symbol表中看出bufp0在.data中,偏移0。
查看下swap.o的.data节: readelf swap.o -R 3
“.data”节的十六进制输出:
0x00000000 00000000 ….
bufp0的值就是00000000
如果int * bufp0 = &data[1];
“.data”节的十六进制输出:
0x00000000 40000000 ….
链接器合并.data节后找到data地址,根据swap.o的.data的偏移给bufp0值
前面swap的重定位是.text中,是相对定位
可执行目标文件
readelf b -l
Elf 文件类型为 EXEC (可执行文件)
入口点 0x80482e0
共有 9 个程序头,开始于偏移量 52
程序头:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4
INTERP 0x000154 0x08048154 0x08048154 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08048000 0x08048000 0x005e0 0x005e0 R E 0x1000
LOAD 0x000f08 0x08049f08 0x08049f08 0x0011c 0x00124 RW 0x1000
DYNAMIC 0x000f14 0x08049f14 0x08049f14 0x000e8 0x000e8 RW 0x4
NOTE 0x000168 0x08048168 0x08048168 0x00044 0x00044 R 0x4
GNU_EH_FRAME 0x0004c0 0x080484c0 0x080484c0 0x00034 0x00034 R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x10
GNU_RELRO 0x000f08 0x08049f08 0x08049f08 0x000f8 0x000f8 R 0x1
Section to Segment mapping:
段节...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame
03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
08 .init_array .fini_array .jcr .dynamic .got
LOAD第二段size为0x0011c,实际内存大小0x00124,多出来的是给.bss留的。
实际上.bss为空,其中的变量仅在.symtab中保留着。
动态链接
共享库
gcc -shared -fPIC -o libvector.so addvec.c multvec.c -m32
加载时链接
gcc -o p main.c ./libvector.so -m32
加载时(也就是刚执行P时)遇到.interp节会去调用动态链接器,完成以下操作:
- Relocating the text and data of libc.so into some memory segment.
- Relocating the text and data of libvector.so into another memory segment.
- Relocating any references in p to symbols defined by libc.so and libvector.so.
运行时链接
gcc -rdynamic -O2 -o p3 dll.c -ldl -m32
全局变量偏移表(Global Offset Table, GOT),位于.data前的.got.plt,纯数据
过程链接表(Procedure Linkage Table, PLT),位于.text前的.plt,实际为代码块
运行(到某个地方)时,调用某个外部函数,这些函数在plt中有记录。
第一次会先调用_dl_runtime_resolve进行重定位,把重定位的地址写到got中。
第二次运行到此时就直接用got中的记录。
//dll.c
include <dlfcn.h>
void (* addvec)(...);
handle = dlopen("./libvector.so", RTLD_LAZY);
addvec = dlsym(handle, "addvec");
addvec(x, y, z, 2);
dlclose(handle)
dlerror();
延迟绑定
"""PLT"""
Disassembly of section .plt:
# plt[0]
08048690 <dlclose@plt-0x10>:
8048690: ff 35 04 a0 04 08 pushl 0x804a004
8048696: ff 25 08 a0 04 08 jmp * 0x804a008
804869c: 00 00 add %al,(%eax)
...
# plt[1]
080486a0 <dlclose@plt>:
80486a0: ff 25 0c a0 04 08 jmp * 0x804a00c
80486a6: 68 00 00 00 00 push $0x0
80486ab: e9 e0 ff ff ff jmp 8048690
# plt[2]
080486b0 <dlerror@plt>:
80486b0: ff 25 10 a0 04 08 jmp * 0x804a010
80486b6: 68 08 00 00 00 push $0x8
80486bb: e9 d0 ff ff ff jmp 8048690
...
".got.plt"节的十六进制输出:
0x0804a000 0c9f0408 00000000 00000000 a6860408 ................
0x0804a010 b6860408 c6860408 d6860408 e6860408 ................
0x0804a020 f6860408 06870408 16870408 ............
0c9f0408 # .dynamic 地址
00000000 # 链接器标识
00000000 # 动态链接器入口
a6860408 # plt[1] dlclose
b6860408 # plt[2] dlerror
c6860408 # plt[3]
d6860408 # plt[4]
e6860408 # plt[5]
f6860408 # plt[6]
06870408 # plt[7]
16870408 # plt[8]
动态链接器根据push的两个标识确定实际位置,改got并执行函数。