編譯鏈接過程
代碼
#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/
動態鏈接-全局符號表
動態鏈接-全局符號表
- 靜態鏈接的符號表
- .symtab section
- 動態鏈接的符號表
- .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