GCC编译链接过程
编译链接过程
代码
#cat main.c
#include <stdio.h>
int add(int x, int y);
int sub(int x, int y);
int mul(int x, int y);
int div(int x, int y);
int main(void)
{
printf("add:%d\n", add(1,2));
printf("sub:%d\n", sub(10,100));
printf("mul:%d\n", mul(5,10));
printf("div:%d\n", div(200,100));
return 0;
}
===
#cat math.c
#include <stdio.h>
int add(int x, int y)
{
return (x + y);
}
int sub(int x, int y)
{
return (x - y);
}
int mul(int x, int y)
{
return (x * y);
}
int div(int x, int y)
{
return (x/y);
}
预处理:
#gcc -E main.c -o main.i
编译: 生成.s 文件
#gcc -c main.c math.c
#ls main.s math.s
main.s math.s
汇编:生成.o 文件(可重定位目标文件)
#gcc -c main.c math.c
#ls main.o math.o
main.o math.o
链接:生成 (可执行目标文件)
#gcc -o main.out main.o math.o
目标文件
分三种:
- 可重定位目标文件 (Relocatable file) (.o 文件,没有被链接的)
- 可执行目标文件 (Executable file)(.out文件 最终二进制文件)
- 可被共享目标文件 (Shared object file) (.so 结尾的)
看ELF的常见命令:
ELF文件格式需要知道;
#readelf -h main.out 看ELF文件的header部分
#readelf -S main.out 看ELF文件的Section header
#readelf -h main.out
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file) // 可执行文件
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x400430 // 函数入口地址
Start of program headers: 64 (bytes into file)
Start of section headers: 6720 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 30
Section header string table index: 27
#readelf -h main.o
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file) // 这是一个重定向文件! 还没有做链接
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x0 // 所以,这里看函数入口地址为0
Start of program headers: 0 (bytes into file)
Start of section headers: 1152 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 13
Section header string table index: 10
静态库:
静态库: (.a 结尾的) 从 .o 文件而来
//生成静态库 : 使用ar命令,将.o 生成.a 文件。这里名字有讲究的, lib + math + .a 中间的才是库名字。
#ar rcs libmath.a math.o
// 使用静态库: -L 表示路径, -l 表示库的名字
#gcc main.o -L. -l math -o main.out
#./main.out
add:3
sub:-90
mul:50
div:2
其实和.o 文件差距不大,都是 重定向文件,只不过做了归档。
#readelf -h libmath.a
File: libmath.a(math.o)
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 840 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 11
Section header string table index: 8
共享库:
共享库: (.so 结尾的)
#gcc --shared -fPIC -o libmath.so math.c
#ll libmath.so
-rwxr-xr-x 1 root root 7864 Feb 1 16:21 libmath.so
类型: Shared object file
#readelf -h libmath.so
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x5b0
Start of program headers: 64 (bytes into file)
Start of section headers: 6216 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 7
Size of section headers: 64 (bytes)
Number of section headers: 27
Section header string table index: 24
gcc 不是一个简单的命令,里面有很多库
[root@r10n04359.sqa.zmf /usr/lib/gcc/x86_64-redhat-linux/6.4.0]
#ls
32 crtendS.o finclude libcaf_single.a libgcc_s.so libgomp.so libmpx.spec libstdc++.so
crtbegin.o crtfastmath.o include libcilkrts.so libgcov.a libgomp.spec libmpxwrappers.so libtsan.so
crtbeginS.o crtprec32.o libasan_preinit.o libcilkrts.spec libgfortran.so libitm.spec libquadmath.so libubsan.so
crtbeginT.o crtprec64.o libasan.so libgcc.a libgfortran.spec liblsan.so libsanitizer.spec rpmver
crtend.o crtprec80.o libatomic.so libgcc_eh.a libgomp.a libmpx.so libstdc++fs.a
编译阶段
分析ELF文件
ELF文件的格式,可以通过readelf -a xxx.o
看到。
包含几个主要部分: 1. ELF header 2. Section header 3. symble table
其中比较重要的是symble table。
那么, 符号表 是什么时候产生的? compile? assemble? 其实是,两个阶段都会产生一份,但是目的是不同的。
汇编阶段, 汇编器会扫描 汇编源文件 生成各种表(包含符号表)。
链接阶段,将各个目标文件合并之后,重新修改符号表中各个符号的地址。
ELF文件中的符号表
几个简单命令,不要混淆:
#readelf -S math.o // 查看Section header
#readelf -s math.o // 查看symble table
#readelf -h math.o // 查看ELF 的header(主要存放一些,ELF文件的类型,架构之类的)
ELF文件中的Section header
一个ELF 的section 有哪些?
大家都知道的.text, .data, .bss 等section
#readelf -S math.o
There are 11 section headers, starting at offset 0x348:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
000000000000004c 0000000000000000 AX 0 0 1
[ 2] .data PROGBITS 0000000000000000 0000008c
0000000000000000 0000000000000000 WA 0 0 1
[ 3] .bss NOBITS 0000000000000000 0000008c
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .comment PROGBITS 0000000000000000 0000008c
000000000000002d 0000000000000001 MS 0 0 1
[ 5] .note.GNU-stack PROGBITS 0000000000000000 000000b9
0000000000000000 0000000000000000 0 0 1
[ 6] .eh_frame PROGBITS 0000000000000000 000000c0
0000000000000098 0000000000000000 A 0 0 8
[ 7] .rela.eh_frame RELA 0000000000000000 00000290
0000000000000060 0000000000000018 I 9 6 8
[ 8] .shstrtab STRTAB 0000000000000000 000002f0 // 保存 section name,比如:.bss,.text,.data
0000000000000054 0000000000000000 0 0 1
[ 9] .symtab SYMTAB 0000000000000000 00000158 //表
0000000000000120 0000000000000018 10 8 8
[10] .strtab STRTAB 0000000000000000 00000278 // 字符名字, 比如这里的:add, mul, sub, div..
0000000000000018 0000000000000000 0 0 1
#vim include/uapi/linux/elf.h
typedef struct elf64_sym {
Elf64_Word st_name; /* Symbol name, index in string tbl */
unsigned char st_info; /* Type and binding attributes */
unsigned char st_other; /* No defined meaning, 0 */
Elf64_Half st_shndx; /* Associated section index */
Elf64_Addr st_value; /* Value of the symbol */
Elf64_Xword st_size; /* Associated symbol size */
} Elf64_Sym;
strtab:
#readelf -x .strtab math.o
Hex dump of section '.strtab':
0x00000000 006d6174 682e6300 61646400 73756200 .math.c.add.sub.
0x00000010 6d756c00 64697600 mul.div.
#readelf -x .symtab math.o
Hex dump of section '.symtab':
0x00000000 00000000 00000000 00000000 00000000 ................
0x00000010 00000000 00000000 01000000 0400f1ff ................
0x00000020 00000000 00000000 00000000 00000000 ................
0x00000030 00000000 03000100 00000000 00000000 ................
0x00000040 00000000 00000000 00000000 03000200 ................
0x00000050 00000000 00000000 00000000 00000000 ................
0x00000060 00000000 03000300 00000000 00000000 ................
0x00000070 00000000 00000000 00000000 03000500 ................
0x00000080 00000000 00000000 00000000 00000000 ................
0x00000090 00000000 03000600 00000000 00000000 ................
0x000000a0 00000000 00000000 00000000 03000400 ................
0x000000b0 00000000 00000000 00000000 00000000 ................
0x000000c0 08000000 12000100 00000000 00000000 ................
0x000000d0 14000000 00000000 0c000000 12000100 ................
0x000000e0 14000000 00000000 12000000 00000000 ................
0x000000f0 10000000 12000100 26000000 00000000 ........&.......
0x00000100 13000000 00000000 14000000 12000100 ................
0x00000110 39000000 00000000 13000000 00000000 9...............
查看Symbol table
虽然,你可以看到 math.c 中的add, sub, mul, div 这些符号表的名字,但是,这个符号表不存在这里,是通过索引获取的。 实际上是存在.strtab
这个section中的。
#readelf -s math.o
Symbol table '.symtab' contains 12 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS math.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 SECTION LOCAL DEFAULT 3
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 6
7: 0000000000000000 0 SECTION LOCAL DEFAULT 4
8: 0000000000000000 20 FUNC GLOBAL DEFAULT 1 add
9: 0000000000000014 18 FUNC GLOBAL DEFAULT 1 sub
10: 0000000000000026 19 FUNC GLOBAL DEFAULT 1 mul
11: 0000000000000039 19 FUNC GLOBAL DEFAULT 1 div
链接过程
链接过程分 3个阶段: 1. 组装新的ELF,创建全局符号表,收集各个符号表地址 2.
通过 链接脚本(linker script) 来指定: 代码段 起始地址, 数据段 起始地址
#ld -verbose
重定位:
在main.c 中调用了外部定义的函数,或者 变量,在没有链接之前,汇编器生成这个main.o 的同时,会记录下来,哪些 符号表(函数,变量) 是没有找到的,需要 等待 链接过程 去找一下。
#readelf -r main.o
Relocation section '.rela.text' at offset 0x328 contains 14 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000006 000a00000002 R_X86_64_PC32 0000000000000000 i - 8
000000000010 000b00000002 R_X86_64_PC32 0000000000000000 j - 5
000000000020 000c00000002 R_X86_64_PC32 0000000000000000 add - 4
000000000027 00050000000a R_X86_64_32 0000000000000000 .rodata + 0
000000000031 000d00000002 R_X86_64_PC32 0000000000000000 printf - 4
000000000040 000e00000002 R_X86_64_PC32 0000000000000000 sub - 4
000000000047 00050000000a R_X86_64_32 0000000000000000 .rodata + 8
000000000051 000d00000002 R_X86_64_PC32 0000000000000000 printf - 4
000000000060 000f00000002 R_X86_64_PC32 0000000000000000 mul - 4
000000000067 00050000000a R_X86_64_32 0000000000000000 .rodata + 10
000000000071 000d00000002 R_X86_64_PC32 0000000000000000 printf - 4
000000000080 001000000002 R_X86_64_PC32 0000000000000000 div - 4
000000000087 00050000000a R_X86_64_32 0000000000000000 .rodata + 18
000000000091 000d00000002 R_X86_64_PC32 0000000000000000 printf - 4
Relocation section '.rela.eh_frame' at offset 0x478 contains 1 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000020 000200000002 R_X86_64_PC32 0000000000000000 .text + 0
反汇编到.s
#objdump -D main.o
Disassembly of section .text:
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: c7 05 00 00 00 00 0a movl $0xa,0x0(%rip) # e <main+0xe>
b: 00 00 00
e: c6 05 00 00 00 00 61 movb $0x61,0x0(%rip) # 15 <main+0x15>
15: be 02 00 00 00 mov $0x2,%esi
1a: bf 01 00 00 00 mov $0x1,%edi
1f: e8 00 00 00 00 callq 24 <main+0x24>
24: 89 c6 mov %eax,%esi
26: bf 00 00 00 00 mov $0x0,%edi
2b: b8 00 00 00 00 mov $0x0,%eax
30: e8 00 00 00 00 callq 35 <main+0x35>
35: be 64 00 00 00 mov $0x64,%esi
3a: bf 0a 00 00 00 mov $0xa,%edi
3f: e8 00 00 00 00 callq 44 <main+0x44>
44: 89 c6 mov %eax,%esi
46: bf 00 00 00 00 mov $0x0,%edi
4b: b8 00 00 00 00 mov $0x0,%eax
50: e8 00 00 00 00 callq 55 <main+0x55>
55: be 0a 00 00 00 mov $0xa,%esi
5a: bf 05 00 00 00 mov $0x5,%edi
5f: e8 00 00 00 00 callq 64 <main+0x64>
64: 89 c6 mov %eax,%esi
66: bf 00 00 00 00 mov $0x0,%edi
6b: b8 00 00 00 00 mov $0x0,%eax
70: e8 00 00 00 00 callq 75 <main+0x75>
75: be 64 00 00 00 mov $0x64,%esi
7a: bf c8 00 00 00 mov $0xc8,%edi
7f: e8 00 00 00 00 callq 84 <main+0x84>
84: 89 c6 mov %eax,%esi
86: bf 00 00 00 00 mov $0x0,%edi
8b: b8 00 00 00 00 mov $0x0,%eax
90: e8 00 00 00 00 callq 95 <main+0x95>
95: b8 00 00 00 00 mov $0x0,%eax
9a: 5d pop %rbp
9b: c3 retq
程序的运行
可执行文件(ELF文件)
program-headers 表
program-headers 表,只有.out 可执行文件才有,.o 文件是没有的(因为.o文件还没有经过链接)。
另一个重要的概念是“程序的入口地址”, 如下case中,通过program-headers查看 程序的入口地址: 0x400430
#readelf -l ./main.out
Elf file type is EXEC (Executable file)
Entry point 0x400430 // 可执行文件 的 程序入口地址, 程序入口地址 = 链接地址+偏移
There are 9 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000001f8 0x00000000000001f8 R E 8
INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238
0x000000000000001c 0x000000000000001c R 1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x000000000000086c 0x000000000000086c R E 200000
LOAD 0x0000000000000e08 0x0000000000600e08 0x0000000000600e08
0x0000000000000228 0x0000000000000230 RW 200000
DYNAMIC 0x0000000000000e20 0x0000000000600e20 0x0000000000600e20
0x00000000000001d0 0x00000000000001d0 RW 8
NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x00000000000006a4 0x00000000004006a4 0x00000000004006a4
0x0000000000000054 0x0000000000000054 R 4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 10
GNU_RELRO 0x0000000000000e08 0x0000000000600e08 0x0000000000600e08
0x00000000000001f8 0x00000000000001f8 R 1
通过汇编也可以看到,程序的入口地址:
#readelf -s main.out
...
59: 0000000000400680 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
60: 0000000000400600 101 FUNC GLOBAL DEFAULT 13 __libc_csu_init
61: 0000000000601038 0 NOTYPE GLOBAL DEFAULT 25 _end
62: 0000000000400430 43 FUNC GLOBAL DEFAULT 13 _start // 程序的入口地址
63: 0000000000601030 0 NOTYPE GLOBAL DEFAULT 25 __bss_start
64: 0000000000400526 139 FUNC GLOBAL DEFAULT 13 main
65: 00000000004005d7 19 FUNC GLOBAL DEFAULT 13 mul
...
为什么没有地址冲突呢?
因为我们操作的是虚拟地址,MMU会帮我们完成虚拟地址 和 物理地址的 映射关系。
加载器
- 如果执行一个可执行文件, 加载器 将ELF文件 映射到内存中, 主要实现是通过 execv 系统调用
- 加载器拷贝数据完成后,当执行的时候,就会直接跳转到程序的入口地址。
BSS段的处理
在可执行文件中,是不占用空间的,只有在ELF文件执行的时候,当映射到内存中才开辟呢。原因和历史有关系,早期的内存比较珍贵。
BSS段的大小,起始地址,存储在哪里?
- Section header tables.
例子1:
这个例子中,全局变量,global_a 在.data section, global_b 在.bss section中。 但是, 另外两个local_a , local_b 因为都是函数内的,都存在于stack中,所以,不在.data, .bss中。
#cat aa.c
#include <stdio.h>
int global_a = 1;
int global_b;
int main(void)
{
int local_a = 2;
int local_b;
return 0;
}
#gcc -o aa.out aa.c
#readelf -S aa.out
There are 28 section headers, starting at offset 0x1980:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[22] .data PROGBITS 0000000000601018 00001018
0000000000000014 0000000000000000 WA 0 0 8
[23] .bss NOBITS 000000000060102c 0000102c
000000000000000c 0000000000000000 WA 0 0 4
#readelf -s aa.out | grep global
Num: Value Size Type Bind Vis Ndx Name
45: 0000000000601030 4 OBJECT GLOBAL DEFAULT 23 global_b
48: 0000000000601028 4 OBJECT GLOBAL DEFAULT 22 global_a // 可见,global_a 在 22号(.bss section)
例子2:
如果我们给局部变量 添加上 static ,那么就会存放在 .data, .bss中。
#cat aa.c
#include <stdio.h>
int global_a = 1;
int global_b;
int main(void)
{
static int local_a = 2;
static int local_b;
return 0;
}
#readelf -s aa.out | grep local_
35: 0000000000601034 4 OBJECT LOCAL DEFAULT 23 local_b.2214
36: 000000000060102c 4 OBJECT LOCAL DEFAULT 22 local_a.2213
#readelf -S aa.out
[22] .data PROGBITS 0000000000601018 00001018
0000000000000018 0000000000000000 WA 0 0 8
[23] .bss NOBITS 0000000000601030 00001030
0000000000000010 0000000000000000 WA 0 0 4
解读汇编代码实现 .bss 的操作
#gcc -S aa.c -o aa.s
#gcc -c aa.s -o aa.o
#gcc aa.o -o aa.out
#cat aa.s
.file "aa.c"
.globl global_a // global_a 放在.data中
.data
.align 4
.type global_a, @object // global_a 类型是object
.size global_a, 4
global_a:
.long 1
.comm global_b,4,4 // .comm 指令,在.bss段 给global_b 分配 4个字节的大小
.text
.globl main // main放在.text中
.type main, @function // main 类型是 function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.local local_b.2214
.comm local_b.2214,4,4 // .comm 指令,在.bss段 给local_b 分配 4个字节的大小
.data
.align 4
.type local_a.2213, @object
.size local_a.2213, 4
local_a.2213:
.long 2
.ident "GCC: (GNU) 6.4.0 20170704 (Red Hat 6.4.0-1)"
.section .note.GNU-stack,"",@progbits
例子3:
#cat aa.c
#include <stdio.h>
int global_a = 1;
int global_b;
int main(void)
{
static int local_a = 2;
static int local_b;
printf("global_a:%lx\n", &global_a);
printf("global_b:%lx\n", &global_b);
printf("local_a:%lx\n", &local_a);
printf("local_b:%lx\n", &local_b);
return 0;
}
在ELF中的看到的地址,其实就是,运行结果看到的地址。(都是在虚拟地址中)
打印变量地址:
#./aa.out
global_a:601030
global_b:601040
local_a:601034
local_b:60103c
查看ELF中的符号表:
#readelf -s aa.out | grep global_
49: 0000000000601040 4 OBJECT GLOBAL DEFAULT 25 global_b
52: 0000000000601030 4 OBJECT GLOBAL DEFAULT 24 global_a
#readelf -s aa.out | grep local_
37: 0000000000601034 4 OBJECT LOCAL DEFAULT 24 local_a.2213
38: 000000000060103c 4 OBJECT LOCAL DEFAULT 25 local_b.2214
#readelf -S aa.out | grep -w 24
[24] .data PROGBITS 0000000000601020 00001020
#readelf -S aa.out | grep -w 25
[25] .bss NOBITS 0000000000601038 00001038
main函数的执行
编译器 对程序入口的规定:
- 编译器默认的程序入口是
_start
符号,而不是main. - 符号
main
是被C标准库调用的符号,它用来告诉编译器,一个项目里,哪里是程序的入口
在执行main之前的“暗箱操作”:
#objdump -D aa.out | grep start -A 30
0000000000400400 <_start>:
400400: 31 ed xor %ebp,%ebp
400402: 49 89 d1 mov %rdx,%r9
400405: 5e pop %rsi
400406: 48 89 e2 mov %rsp,%rdx
400409: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
40040d: 50 push %rax
40040e: 54 push %rsp
40040f: 49 c7 c0 d0 05 40 00 mov $0x4005d0,%r8
400416: 48 c7 c1 60 05 40 00 mov $0x400560,%rcx
40041d: 48 c7 c7 f6 04 40 00 mov $0x4004f6,%rdi
400424: ff 15 c6 0b 20 00 callq *0x200bc6(%rip) # 600ff0 <_DYNAMIC+0x1d0>
40042a: f4 hlt
40042b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
#rpm -ql glibc-devel-2.24-3.1.alios7.x86_64
/usr/include/gnu/lib-names-64.h
/usr/include/gnu/stubs-64.h
/usr/lib64/Mcrt1.o
/usr/lib64/Scrt1.o
/usr/lib64/crt1.o
/usr/lib64/crti.o
/usr/lib64/crtn.o
/usr/lib64/gcrt1.o
/usr/lib64/libBrokenLocale.so
/usr/lib64/libanl.so
/usr/lib64/libc.so
/usr/lib64/libc_nonshared.a
/usr/lib64/libcidn.so
/usr/lib64/libcrypt.so
/usr/lib64/libdl.so
/usr/lib64/libg.a
/usr/lib64/libieee.a
/usr/lib64/libm.so
/usr/lib64/libmcheck.a
/usr/lib64/libmvec.so
glibc下的 /usr/lib64/ 下面的.o 会被gcc默认链接使用。
#objdump -D /usr/lib64/crt1.o
0000000000000000 <_start>:
0: 31 ed xor %ebp,%ebp
2: 49 89 d1 mov %rdx,%r9
5: 5e pop %rsi
6: 48 89 e2 mov %rsp,%rdx
9: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
d: 50 push %rax
e: 54 push %rsp
f: 49 c7 c0 00 00 00 00 mov $0x0,%r8
16: 48 c7 c1 00 00 00 00 mov $0x0,%rcx
1d: 48 c7 c7 00 00 00 00 mov $0x0,%rdi
24: ff 15 00 00 00 00 callq *0x0(%rip) # 2a <_start+0x2a>
2a: f4 hlt
#ldd /usr/bin/ls
linux-vdso.so.1 (0x00007fff63bfd000)
libselinux.so.1 => /lib64/libselinux.so.1 (0x00007f514133f000)
libcap.so.2 => /lib64/libcap.so.2 (0x00007f514113a000)
libc.so.6 => /lib64/libc.so.6 (0x00007f5140d74000)
libpcre.so.1 => /lib64/libpcre.so.1 (0x00007f5140b13000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007f514090f000)
/lib64/ld-linux-x86-64.so.2 (0x00007f5141566000)
libattr.so.1 => /lib64/libattr.so.1 (0x00007f514070a000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f51404ec000)
#objdump -D /lib64/libc.so.6 | grep libc_start_main
0000000000020310 <__libc_start_main>:
2033a: 0f 84 c8 00 00 00 je 20408 <__libc_start_main+0xf8>
20356: 74 0c je 20364 <__libc_start_main+0x54>
20370: 0f 85 d1 00 00 00 jne 20447 <__libc_start_main+0x137>
20379: 74 15 je 20390 <__libc_start_main+0x80>
203a1: 0f 85 f4 00 00 00 jne 2049b <__libc_start_main+0x18b>
203a9: 0f 85 c9 00 00 00 jne 20478 <__libc_start_main+0x168>
203bb: 75 52 jne 2040f <__libc_start_main+0xff>
2040a: e9 38 ff ff ff jmpq 20347 <__libc_start_main+0x37>
20441: 74 20 je 20463 <__libc_start_main+0x153>
20445: eb ba jmp 20401 <__libc_start_main+0xf1>
2045e: e9 13 ff ff ff jmpq 20376 <__libc_start_main+0x66>
20476: eb f8 jmp 20470 <__libc_start_main+0x160>
20496: e9 14 ff ff ff jmpq 203af <__libc_start_main+0x9f>
204bd: 74 05 je 204c4 <__libc_start_main+0x1b4>
204d3: 75 e1 jne 204b6 <__libc_start_main+0x1a6>
204d5: e9 cd fe ff ff jmpq 203a7 <__libc_start_main+0x97>
下载glibc的 源代码, 找找看 __libc_start_main
的实现,其实就会调用到 main.
静态链接
- 生成的可执行文件体积比较大,相同公共代码浪费空间。
- 要一次性加载到内存中
静态库: (.a 结尾的) 从 .o 文件而来
//生成静态库 : 使用ar命令,将.o 生成.a 文件。这里名字有讲究的, lib + math + .a 中间的才是库名字。
#ar rcs libmath.a math.o
// 使用静态库: -L 表示路径, -l 表示库的名字
#gcc main.o -L. -l math -o main.out
#./main.out
add:3
sub:-90
mul:50
div:2
动态链接(1) - 与位置无关的代码
动态链接(2) - 全局符号表
共享库: (.so 结尾的)
#cat main.c
#include <stdio.h>
int add(int x, int y);
int sub(int x, int y);
int mul(int x, int y);
int div(int x, int y);
int main(void)
{
printf("add:%d\n", add(1,2));
printf("sub:%d\n", sub(10,100));
printf("mul:%d\n", mul(5,10));
printf("div:%d\n", div(200,100));
return 0;
}
#cat math.c
#include <stdio.h>
int add(int x, int y)
{
return (x + y);
}
int sub(int x, int y)
{
return (x - y);
}
int mul(int x, int y)
{
return (x * y);
}
int div(int x, int y)
{
return (x/y);
}
#gcc --shared -fPIC -o libmath.so math.c
#ll libmath.so
-rwxr-xr-x 1 root root 7864 Feb 1 16:21 libmath.so
#gcc -o main.out main.c -L. -lmath
#ldd main.out
linux-vdso.so.1 (0x00007ffe12ec8000)
libmath.so => not found
libc.so.6 => /lib64/libc.so.6 (0x00007f05295b5000)
/lib64/ld-linux-x86-64.so.2 (0x00007f052997b000)
#./main.out
./main.out: error while loading shared libraries: libmath.so: cannot open shared object file: No such file or directory
#cp libmath.so /usr/lib/
动态链接-全局符号表
动态链接-全局符号表
- 静态链接的符号表
3. .symtab section - 动态链接的符号表
4. .dynsym section - 查看动态链接符号表:#readelf -s main.out
#readelf -s main.out
// 动态符号表.dynsym
Symbol table '.dynsym' contains 16 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND add
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND div
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
6: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND mul
8: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
9: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
10: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sub
11: 0000000000601050 0 NOTYPE GLOBAL DEFAULT 24 _edata
12: 0000000000601058 0 NOTYPE GLOBAL DEFAULT 25 _end
13: 0000000000601050 0 NOTYPE GLOBAL DEFAULT 25 __bss_start
14: 0000000000400600 0 FUNC GLOBAL DEFAULT 11 _init
15: 0000000000400884 0 FUNC GLOBAL DEFAULT 14 _fini
// 静态符号表.symtab
Symbol table '.symtab' contains 70 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000400238 0 SECTION LOCAL DEFAULT 1
2: 0000000000400254 0 SECTION LOCAL DEFAULT 2
3: 0000000000400274 0 SECTION LOCAL DEFAULT 3
4: 0000000000400298 0 SECTION LOCAL DEFAULT 4
5: 00000000004002d0 0 SECTION LOCAL DEFAULT 5
6: 0000000000400450 0 SECTION LOCAL DEFAULT 6
7: 0000000000400518 0 SECTION LOCAL DEFAULT 7
8: 0000000000400538 0 SECTION LOCAL DEFAULT 8
9: 0000000000400558 0 SECTION LOCAL DEFAULT 9
10: 0000000000400588 0 SECTION LOCAL DEFAULT 10
11: 0000000000400600 0 SECTION LOCAL DEFAULT 11
12: 0000000000400620 0 SECTION LOCAL DEFAULT 12
13: 0000000000400680 0 SECTION LOCAL DEFAULT 13
14: 0000000000400884 0 SECTION LOCAL DEFAULT 14
15: 0000000000400890 0 SECTION LOCAL DEFAULT 15
16: 00000000004008b4 0 SECTION LOCAL DEFAULT 16
17: 00000000004008e8 0 SECTION LOCAL DEFAULT 17
18: 0000000000600df8 0 SECTION LOCAL DEFAULT 18
19: 0000000000600e00 0 SECTION LOCAL DEFAULT 19
20: 0000000000600e08 0 SECTION LOCAL DEFAULT 20
21: 0000000000600e10 0 SECTION LOCAL DEFAULT 21
22: 0000000000600ff0 0 SECTION LOCAL DEFAULT 22
23: 0000000000601000 0 SECTION LOCAL DEFAULT 23
24: 0000000000601040 0 SECTION LOCAL DEFAULT 24
25: 0000000000601050 0 SECTION LOCAL DEFAULT 25
26: 0000000000000000 0 SECTION LOCAL DEFAULT 26
27: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
28: 0000000000600e08 0 OBJECT LOCAL DEFAULT 20 __JCR_LIST__
29: 00000000004006b0 0 FUNC LOCAL DEFAULT 13 deregister_tm_clones
30: 00000000004006f0 0 FUNC LOCAL DEFAULT 13 register_tm_clones
31: 0000000000400730 0 FUNC LOCAL DEFAULT 13 __do_global_dtors_aux
32: 0000000000601050 1 OBJECT LOCAL DEFAULT 25 completed.6917
33: 0000000000600e00 0 OBJECT LOCAL DEFAULT 19 __do_global_dtors_aux_fin
34: 0000000000400750 0 FUNC LOCAL DEFAULT 13 frame_dummy
35: 0000000000600df8 0 OBJECT LOCAL DEFAULT 18 __frame_dummy_init_array_
36: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.c
37: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
38: 00000000004009d8 0 OBJECT LOCAL DEFAULT 17 __FRAME_END__
39: 0000000000600e08 0 OBJECT LOCAL DEFAULT 20 __JCR_END__
40: 0000000000000000 0 FILE LOCAL DEFAULT ABS
41: 0000000000600e00 0 NOTYPE LOCAL DEFAULT 18 __init_array_end
42: 0000000000600e10 0 OBJECT LOCAL DEFAULT 21 _DYNAMIC
43: 0000000000600df8 0 NOTYPE LOCAL DEFAULT 18 __init_array_start
44: 00000000004008b4 0 NOTYPE LOCAL DEFAULT 16 __GNU_EH_FRAME_HDR
45: 0000000000601000 0 OBJECT LOCAL DEFAULT 23 _GLOBAL_OFFSET_TABLE_
46: 0000000000400880 2 FUNC GLOBAL DEFAULT 13 __libc_csu_fini
47: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
48: 0000000000601040 0 NOTYPE WEAK DEFAULT 24 data_start
49: 0000000000000000 0 FUNC GLOBAL DEFAULT UND add
50: 0000000000601050 0 NOTYPE GLOBAL DEFAULT 24 _edata
51: 0000000000400884 0 FUNC GLOBAL DEFAULT 14 _fini
52: 0000000000000000 0 FUNC GLOBAL DEFAULT UND div
53: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@@GLIBC_2.2.5
54: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_
55: 0000000000601040 0 NOTYPE GLOBAL DEFAULT 24 __data_start
56: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
57: 0000000000601048 0 OBJECT GLOBAL HIDDEN 24 __dso_handle
58: 0000000000400890 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
59: 0000000000400810 101 FUNC GLOBAL DEFAULT 13 __libc_csu_init
60: 0000000000601058 0 NOTYPE GLOBAL DEFAULT 25 _end
61: 0000000000400680 43 FUNC GLOBAL DEFAULT 13 _start
62: 0000000000601050 0 NOTYPE GLOBAL DEFAULT 25 __bss_start
63: 0000000000400776 139 FUNC GLOBAL DEFAULT 13 main
64: 0000000000000000 0 FUNC GLOBAL DEFAULT UND mul
65: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
66: 0000000000601050 0 OBJECT GLOBAL HIDDEN 24 __TMC_END__
67: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
68: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sub
69: 0000000000400600 0 FUNC GLOBAL DEFAULT 11 _init
#readelf -S main.out
There are 30 section headers, starting at offset 0x1a40:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000400238 00000238
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000400254 00000254
0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-i NOTE 0000000000400274 00000274
0000000000000024 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000400298 00000298
0000000000000038 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 00000000004002d0 000002d0 // 动态链接符号表
0000000000000180 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 0000000000400450 00000450 // 动态链接符号表
00000000000000c8 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 0000000000400518 00000518
0000000000000020 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000400538 00000538
0000000000000020 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 0000000000400558 00000558
0000000000000030 0000000000000018 A 5 0 8
[10] .rela.plt RELA 0000000000400588 00000588
0000000000000078 0000000000000018 AI 5 23 8
[11] .init PROGBITS 0000000000400600 00000600
0000000000000017 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 0000000000400620 00000620
0000000000000060 0000000000000010 AX 0 0 16
[13] .text PROGBITS 0000000000400680 00000680
0000000000000202 0000000000000000 AX 0 0 16
[14] .fini PROGBITS 0000000000400884 00000884
0000000000000009 0000000000000000 AX 0 0 4
[15] .rodata PROGBITS 0000000000400890 00000890
0000000000000024 0000000000000000 A 0 0 4
[16] .eh_frame_hdr PROGBITS 00000000004008b4 000008b4
0000000000000034 0000000000000000 A 0 0 4
[17] .eh_frame PROGBITS 00000000004008e8 000008e8
00000000000000f4 0000000000000000 A 0 0 8
[18] .init_array INIT_ARRAY 0000000000600df8 00000df8
0000000000000008 0000000000000000 WA 0 0 8
[19] .fini_array FINI_ARRAY 0000000000600e00 00000e00
0000000000000008 0000000000000000 WA 0 0 8
[20] .jcr PROGBITS 0000000000600e08 00000e08
0000000000000008 0000000000000000 WA 0 0 8
[21] .dynamic DYNAMIC 0000000000600e10 00000e10
00000000000001e0 0000000000000010 WA 6 0 8
[22] .got PROGBITS 0000000000600ff0 00000ff0
0000000000000010 0000000000000008 WA 0 0 8
[23] .got.plt PROGBITS 0000000000601000 00001000
0000000000000040 0000000000000008 WA 0 0 8
[24] .data PROGBITS 0000000000601040 00001040
0000000000000010 0000000000000000 WA 0 0 8
[25] .bss NOBITS 0000000000601050 00001050
0000000000000008 0000000000000000 WA 0 0 1
[26] .comment PROGBITS 0000000000000000 00001050
000000000000002c 0000000000000001 MS 0 0 1
[27] .shstrtab STRTAB 0000000000000000 00001935 // 静态链接符号表
0000000000000108 0000000000000000 0 0 1
[28] .symtab SYMTAB 0000000000000000 00001080 // 静态链接符号表
0000000000000690 0000000000000018 29 46 8
[29] .strtab STRTAB 0000000000000000 00001710 // 静态链接符号表
0000000000000225 0000000000000000 0 0 1
动态连接器
section .interp 段存放一个字符串,用于指明“动态连接器”的路径: /lib64/ld-linux-x86-64.so.2, 其实“动态连接器” 也是一个共享库。 使用 objdump 可以查看。
查看section .interp 段 的内容:
“动态连接器” 牛掰的地方在于,他是一个 共享库,但是,他可以给自己重定位,然后运行。
#objdump -s main.out
main.out: file format elf64-x86-64
Contents of section .interp:
400238 2f6c6962 36342f6c 642d6c69 6e75782d /lib64/ld-linux-
400248 7838362d 36342e73 6f2e3200 x86-64.so.2.
Contents of section .note.ABI-tag:
400254 04000000 10000000 01000000 474e5500 ............GNU.
400264 00000000 02000000 06000000 20000000 ............ ...
Contents of section .note.gnu.build-id:
400274 04000000 14000000 03000000 474e5500 ............GNU.
400284 c9750717 6241288d 5147bdd9 e0795409 .u..bA(.QG...yT.
400294 36d5e600 6...
Contents of section .gnu.hash:
400298 03000000 0b000000 01000000 06000000 ................
4002a8 88c02001 00044009 0b000000 0d000000 .. ...@.........
4002b8 0f000000 4245d5ec bbe3927c d871581c ....BE.....|.qX.
4002c8 b98df10e ebd3ef0e ........
Contents of section .dynsym:
4002d0 00000000 00000000 00000000 00000000 ................
4002e0 00000000 00000000 0c000000 20000000 ............ ...
4002f0 00000000 00000000 00000000 00000000 ................
400300 69000000 12000000 00000000 00000000 i...............
400310 00000000 00000000 6d000000 12000000 ........m.......
.dynamic 段
section .dynamic: 保存了“动态链接器”所需要的信息,比如:
- 依赖哪些共享库
- 动态链接符号表位置
- 动态链接字符串表的位置
查看.dynamic段,用:#readelf -d
#readelf -d main.out
Dynamic section at offset 0xe10 contains 25 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libmath.so] // 动态链接器 依赖的共享库
0x0000000000000001 (NEEDED) Shared library: [libc.so.6] // 动态链接器 依赖的共享库
0x000000000000000c (INIT) 0x400600
0x000000000000000d (FINI) 0x400884
0x0000000000000019 (INIT_ARRAY) 0x600df8
0x000000000000001b (INIT_ARRAYSZ) 8 (bytes)
0x000000000000001a (FINI_ARRAY) 0x600e00
0x000000000000001c (FINI_ARRAYSZ) 8 (bytes)
0x000000006ffffef5 (GNU_HASH) 0x400298
0x0000000000000005 (STRTAB) 0x400450 // 字符串表
0x0000000000000006 (SYMTAB) 0x4002d0 // 符号表
0x000000000000000a (STRSZ) 200 (bytes)
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000015 (DEBUG) 0x0
0x0000000000000003 (PLTGOT) 0x601000
0x0000000000000002 (PLTRELSZ) 120 (bytes)
0x0000000000000014 (PLTREL) RELA
0x0000000000000017 (JMPREL) 0x400588
0x0000000000000007 (RELA) 0x400558
0x0000000000000008 (RELASZ) 48 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x000000006ffffffe (VERNEED) 0x400538
0x000000006fffffff (VERNEEDNUM) 1
0x000000006ffffff0 (VERSYM) 0x400518
0x0000000000000000 (NULL) 0x0
符号哈希表
To complete
动态链接重定位表
动态链接重定位表 分为 两个section:
- .rela.dyn : 数据段重定位信息
- .rela.plt : 代码段重定位信息
#readelf -r main.out
Relocation section '.rela.dyn' at offset 0x558 contains 2 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000600ff0 000500000006 R_X86_64_GLOB_DAT 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
000000600ff8 000600000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
Relocation section '.rela.plt' at offset 0x588 contains 5 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000601018 000200000007 R_X86_64_JUMP_SLO 0000000000000000 add + 0
000000601020 000300000007 R_X86_64_JUMP_SLO 0000000000000000 div + 0
000000601028 000400000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0
000000601030 000700000007 R_X86_64_JUMP_SLO 0000000000000000 mul + 0
000000601038 000a00000007 R_X86_64_JUMP_SLO 0000000000000000 sub + 0
过程链接表
To complete
动态链接(3) - 共享库
配置: /etc/ld.so.conf.d/*.conf
缓存在: /etc/ld.so.cache,当新增或者删除一个库的时候,执行一下ldconfig , 更新一下缓存(/etc/ld.so.cache)
#./main.out
./main.out: error while loading shared libraries: libmath.so: cannot open shared object file: No such file or directory
自定义库,一般放在这个目录:
#cp libmath.so /usr/local/lib/
增加lib查找路径, 指定链接器 去哪里查找:
#cat /etc/ld.so.conf.d/jianyi.conf
/usr/local/lib
重新生成 cache文件:
#ldconfig
运行:
#./main.out
add:3
sub:-90
mul:50
div:2
使用环境变量 LD_LIBRARY_PATH
:
#echo $LD_LIBRARY_PATH
#export LD_LIBRARY_PATH=.
#./main.out
add:3
sub:-90
mul:50
div:2