不知道你注意到沒,在過去的幾節,咱們經過gcc生成的文件和objdump獲取到的彙編指令都有些小小的問題,咱們先把前面的函數示例,拆分紅兩個文件add_lib.c 和 link_example.c。linux
一、add_lib.csass
[root@luoahong c]# cat add_lib.c int add(int a, int b) { return a+b; }
二、link_example.cbash
[root@luoahong c]# cat link_example.c #include <stdio.h> int main() { int a = 10; int b = 5; int c = add(a, b); printf("c = %d\n", c); }
[root@luoahong c]# gcc -g -c add_lib.c link_example.c
objdump -d -M intel -S add_lib.o函數
[root@luoahong c]# objdump -d -M intel -S add_lib.o add_lib.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <add>: int add(int a, int b) { 0: 55 push rbp 1: 48 89 e5 mov rbp,rsp 4: 89 7d fc mov DWORD PTR [rbp-0x4],edi 7: 89 75 f8 mov DWORD PTR [rbp-0x8],esi return a+b; a: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8] d: 8b 55 fc mov edx,DWORD PTR [rbp-0x4] 10: 01 d0 add eax,edx } 12: 5d pop rbp 13: c3 ret
objdump -d -M intel -S link_example.o測試
[root@luoahong c]# objdump -d -M intel -S link_example.o link_example.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <main>: #include <stdio.h> int main() { 0: 55 push rbp 1: 48 89 e5 mov rbp,rsp 4: 48 83 ec 10 sub rsp,0x10 int a = 10; 8: c7 45 fc 0a 00 00 00 mov DWORD PTR [rbp-0x4],0xa int b = 5; f: c7 45 f8 05 00 00 00 mov DWORD PTR [rbp-0x8],0x5 int c = add(a, b); 16: 8b 55 f8 mov edx,DWORD PTR [rbp-0x8] 19: 8b 45 fc mov eax,DWORD PTR [rbp-0x4] 1c: 89 d6 mov esi,edx 1e: 89 c7 mov edi,eax 20: b8 00 00 00 00 mov eax,0x0 25: e8 00 00 00 00 call 2a <main+0x2a> 2a: 89 45 f4 mov DWORD PTR [rbp-0xc],eax printf("c = %d\n", c); 2d: 8b 45 f4 mov eax,DWORD PTR [rbp-0xc] 30: 89 c6 mov esi,eax 32: bf 00 00 00 00 mov edi,0x0 37: b8 00 00 00 00 mov eax,0x0 3c: e8 00 00 00 00 call 41 <main+0x41> } 41: c9 leave 42: c3 ret
[root@luoahong c]# ./link_example.o -bash: ./link_example.o: Permission denied
[root@luoahong c]# chmod +x link_example.o [root@luoahong c]# ll link_example.o -rwxr-xr-x 1 root root 3408 May 16 17:06 link_example.o [root@luoahong c]# ./link_example.o -bash: ./link_example.o: cannot execute binary file
咱們再仔細看一下 objdump 出來的兩個文件的代碼,會發現兩個程序的地址都是從 0 開始的。若是地址是同樣的,程序若是須要經過 call 指令調用函數的話,它怎麼知道應該跳轉到哪個文件裏呢?spa
這麼說吧,不管是這裏的運行報錯,仍是 objdump 出來的彙編代碼裏面的重複地址,都是由於add_lib.o 以及 link_example.o 並不一個可執行文件(Executable Program),而是目標文操作系統
件(Object File)。只有經過連接器(Linker)把多個目標文件以及調用的各類函數庫連接起來,咱們才能獲得一個可執行文件。3d
咱們經過 gcc 的 -o 參數,能夠生成對應的可執行文件,對應執行以後,就能夠獲得這個簡單的加法調用函數的結果。orm
[root@luoahong c]# gcc -o link-example add_lib.o link_example.o [root@luoahong c]# ll total 44 -rw-r--r-- 1 root root 43 May 16 17:04 add_lib.c -rw-r--r-- 1 root root 2576 May 16 17:38 add_lib.o -rw-r--r-- 1 root root 140 May 16 14:21 function_example.c -rw-r--r-- 1 root root 3288 May 16 14:22 function_example.o -rwxr-xr-x 1 root root 9912 May 16 17:39 link-example -rw-r--r-- 1 root root 115 May 16 17:05 link_example.c -rw-r--r-- 1 root root 3408 May 16 17:38 link_example.o -rw-r--r-- 1 root root 98 May 14 17:42 test.c -rw-r--r-- 1 root root 2632 May 14 17:42 test.o [root@luoahong c]# ./link-example c = 15
實際上,「C 語言代碼 - 彙編代碼 - 機器碼」 這個過,在咱們的計算機上進行的時候是由兩部分組成的。blog
第一個部分由編譯(Compile)、彙編(Assemble)以及連接(Link)三個階段組成。在這三個階段完成以後,咱們就生成了一個可執行文件。
第二部分,咱們經過裝載器(Loader)把可執行文件裝載(Load)到內存中。CPU 從內存中讀取指令和數據,來開始真正執行程序。
程序最終是經過裝載器變成指令和數據的,因此其實咱們生成的可執行代碼也並不只僅是一條條的指令。咱們仍是經過objdump指令,把可執行文件內容拿出來看看
[root@luoahong c]# objdump -d -M intel -S link-example link-example: file format elf64-x86-64 Disassembly of section .init: 00000000004003c8 <_init>: 4003c8: 48 83 ec 08 sub rsp,0x8 4003cc: 48 8b 05 25 0c 20 00 mov rax,QWORD PTR [rip+0x200c25] # 600ff8 <__gmon_start__> 4003d3: 48 85 c0 test rax,rax 4003d6: 74 05 je 4003dd <_init+0x15> 4003d8: e8 43 00 00 00 call 400420 <.plt.got> 4003dd: 48 83 c4 08 add rsp,0x8 4003e1: c3 ret Disassembly of section .plt: 00000000004003f0 <.plt>: 4003f0: ff 35 12 0c 20 00 push QWORD PTR [rip+0x200c12] # 601008 <_GLOBAL_OFFSET_TABLE_+0x8> 4003f6: ff 25 14 0c 20 00 jmp QWORD PTR [rip+0x200c14] # 601010 <_GLOBAL_OFFSET_TABLE_+0x10> 4003fc: 0f 1f 40 00 nop DWORD PTR [rax+0x0] 0000000000400400 <printf@plt>: 400400: ff 25 12 0c 20 00 jmp QWORD PTR [rip+0x200c12] # 601018 <printf@GLIBC_2.2.5> 400406: 68 00 00 00 00 push 0x0 40040b: e9 e0 ff ff ff jmp 4003f0 <.plt> 0000000000400410 <__libc_start_main@plt>: 400410: ff 25 0a 0c 20 00 jmp QWORD PTR [rip+0x200c0a] # 601020 <__libc_start_main@GLIBC_2.2.5> 400416: 68 01 00 00 00 push 0x1 40041b: e9 d0 ff ff ff jmp 4003f0 <.plt> Disassembly of section .plt.got: 0000000000400420 <.plt.got>: 400420: ff 25 d2 0b 20 00 jmp QWORD PTR [rip+0x200bd2] # 600ff8 <__gmon_start__> 400426: 66 90 xchg ax,ax Disassembly of section .text: 0000000000400430 <_start>: 400430: 31 ed xor ebp,ebp 400432: 49 89 d1 mov r9,rdx 400435: 5e pop rsi 400436: 48 89 e2 mov rdx,rsp 400439: 48 83 e4 f0 and rsp,0xfffffffffffffff0 40043d: 50 push rax 40043e: 54 push rsp 40043f: 49 c7 c0 f0 05 40 00 mov r8,0x4005f0 400446: 48 c7 c1 80 05 40 00 mov rcx,0x400580 40044d: 48 c7 c7 31 05 40 00 mov rdi,0x400531 400454: e8 b7 ff ff ff call 400410 <__libc_start_main@plt> 400459: f4 hlt 40045a: 66 0f 1f 44 00 00 nop WORD PTR [rax+rax*1+0x0] 0000000000400460 <deregister_tm_clones>: 400460: b8 37 10 60 00 mov eax,0x601037 400465: 55 push rbp 400466: 48 2d 30 10 60 00 sub rax,0x601030 40046c: 48 83 f8 0e cmp rax,0xe 400470: 48 89 e5 mov rbp,rsp 400473: 77 02 ja 400477 <deregister_tm_clones+0x17> 400475: 5d pop rbp 400476: c3 ret 400477: b8 00 00 00 00 mov eax,0x0 40047c: 48 85 c0 test rax,rax 40047f: 74 f4 je 400475 <deregister_tm_clones+0x15> 400481: 5d pop rbp 400482: bf 30 10 60 00 mov edi,0x601030 400487: ff e0 jmp rax 400489: 0f 1f 80 00 00 00 00 nop DWORD PTR [rax+0x0] 0000000000400490 <register_tm_clones>: 400490: b8 30 10 60 00 mov eax,0x601030 400495: 55 push rbp 400496: 48 2d 30 10 60 00 sub rax,0x601030 40049c: 48 c1 f8 03 sar rax,0x3 4004a0: 48 89 e5 mov rbp,rsp 4004a3: 48 89 c2 mov rdx,rax 4004a6: 48 c1 ea 3f shr rdx,0x3f 4004aa: 48 01 d0 add rax,rdx 4004ad: 48 d1 f8 sar rax,1 4004b0: 75 02 jne 4004b4 <register_tm_clones+0x24> 4004b2: 5d pop rbp 4004b3: c3 ret 4004b4: ba 00 00 00 00 mov edx,0x0 4004b9: 48 85 d2 test rdx,rdx 4004bc: 74 f4 je 4004b2 <register_tm_clones+0x22> 4004be: 5d pop rbp 4004bf: 48 89 c6 mov rsi,rax 4004c2: bf 30 10 60 00 mov edi,0x601030 4004c7: ff e2 jmp rdx 4004c9: 0f 1f 80 00 00 00 00 nop DWORD PTR [rax+0x0] 00000000004004d0 <__do_global_dtors_aux>: 4004d0: 80 3d 55 0b 20 00 00 cmp BYTE PTR [rip+0x200b55],0x0 # 60102c <_edata> 4004d7: 75 11 jne 4004ea <__do_global_dtors_aux+0x1a> 4004d9: 55 push rbp 4004da: 48 89 e5 mov rbp,rsp 4004dd: e8 7e ff ff ff call 400460 <deregister_tm_clones> 4004e2: 5d pop rbp 4004e3: c6 05 42 0b 20 00 01 mov BYTE PTR [rip+0x200b42],0x1 # 60102c <_edata> 4004ea: f3 c3 repz ret 4004ec: 0f 1f 40 00 nop DWORD PTR [rax+0x0] 00000000004004f0 <frame_dummy>: 4004f0: 48 83 3d 28 09 20 00 cmp QWORD PTR [rip+0x200928],0x0 # 600e20 <__JCR_END__> 4004f7: 00 4004f8: 74 1e je 400518 <frame_dummy+0x28> 4004fa: b8 00 00 00 00 mov eax,0x0 4004ff: 48 85 c0 test rax,rax 400502: 74 14 je 400518 <frame_dummy+0x28> 400504: 55 push rbp 400505: bf 20 0e 60 00 mov edi,0x600e20 40050a: 48 89 e5 mov rbp,rsp 40050d: ff d0 call rax 40050f: 5d pop rbp 400510: e9 7b ff ff ff jmp 400490 <register_tm_clones> 400515: 0f 1f 00 nop DWORD PTR [rax] 400518: e9 73 ff ff ff jmp 400490 <register_tm_clones> 000000000040051d <add>: int add(int a, int b) { 40051d: 55 push rbp 40051e: 48 89 e5 mov rbp,rsp 400521: 89 7d fc mov DWORD PTR [rbp-0x4],edi 400524: 89 75 f8 mov DWORD PTR [rbp-0x8],esi return a+b; 400527: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8] 40052a: 8b 55 fc mov edx,DWORD PTR [rbp-0x4] 40052d: 01 d0 add eax,edx } 40052f: 5d pop rbp 400530: c3 ret 0000000000400531 <main>: #include <stdio.h> int main() { 400531: 55 push rbp 400532: 48 89 e5 mov rbp,rsp 400535: 48 83 ec 10 sub rsp,0x10 int a = 10; 400539: c7 45 fc 0a 00 00 00 mov DWORD PTR [rbp-0x4],0xa int b = 5; 400540: c7 45 f8 05 00 00 00 mov DWORD PTR [rbp-0x8],0x5 int c = add(a, b); 400547: 8b 55 f8 mov edx,DWORD PTR [rbp-0x8] 40054a: 8b 45 fc mov eax,DWORD PTR [rbp-0x4] 40054d: 89 d6 mov esi,edx 40054f: 89 c7 mov edi,eax 400551: b8 00 00 00 00 mov eax,0x0 400556: e8 c2 ff ff ff call 40051d <add> 40055b: 89 45 f4 mov DWORD PTR [rbp-0xc],eax printf("c = %d\n", c); 40055e: 8b 45 f4 mov eax,DWORD PTR [rbp-0xc] 400561: 89 c6 mov esi,eax 400563: bf 10 06 40 00 mov edi,0x400610 400568: b8 00 00 00 00 mov eax,0x0 40056d: e8 8e fe ff ff call 400400 <printf@plt> } 400572: c9 leave 400573: c3 ret 400574: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0] 40057b: 00 00 00 40057e: 66 90 xchg ax,ax 0000000000400580 <__libc_csu_init>: 400580: 41 57 push r15 400582: 41 89 ff mov r15d,edi 400585: 41 56 push r14 400587: 49 89 f6 mov r14,rsi 40058a: 41 55 push r13 40058c: 49 89 d5 mov r13,rdx 40058f: 41 54 push r12 400591: 4c 8d 25 78 08 20 00 lea r12,[rip+0x200878] # 600e10 <__frame_dummy_init_array_entry> 400598: 55 push rbp 400599: 48 8d 2d 78 08 20 00 lea rbp,[rip+0x200878] # 600e18 <__init_array_end> 4005a0: 53 push rbx 4005a1: 4c 29 e5 sub rbp,r12 4005a4: 31 db xor ebx,ebx 4005a6: 48 c1 fd 03 sar rbp,0x3 4005aa: 48 83 ec 08 sub rsp,0x8 4005ae: e8 15 fe ff ff call 4003c8 <_init> 4005b3: 48 85 ed test rbp,rbp 4005b6: 74 1e je 4005d6 <__libc_csu_init+0x56> 4005b8: 0f 1f 84 00 00 00 00 nop DWORD PTR [rax+rax*1+0x0] 4005bf: 00 4005c0: 4c 89 ea mov rdx,r13 4005c3: 4c 89 f6 mov rsi,r14 4005c6: 44 89 ff mov edi,r15d 4005c9: 41 ff 14 dc call QWORD PTR [r12+rbx*8] 4005cd: 48 83 c3 01 add rbx,0x1 4005d1: 48 39 eb cmp rbx,rbp 4005d4: 75 ea jne 4005c0 <__libc_csu_init+0x40> 4005d6: 48 83 c4 08 add rsp,0x8 4005da: 5b pop rbx 4005db: 5d pop rbp 4005dc: 41 5c pop r12 4005de: 41 5d pop r13 4005e0: 41 5e pop r14 4005e2: 41 5f pop r15 4005e4: c3 ret 4005e5: 90 nop 4005e6: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0] 4005ed: 00 00 00 00000000004005f0 <__libc_csu_fini>: 4005f0: f3 c3 repz ret Disassembly of section .fini: 00000000004005f4 <_fini>: 4005f4: 48 83 ec 08 sub rsp,0x8 4005f8: 48 83 c4 08 add rsp,0x8 4005fc: c3 ret [root@luoahong c]#
一、和以前的目標代碼長的差很少,可是長了不少,由於在linux下,可執行文件和目標文件所使用的都是一種叫ELF的文件格式,中文名字叫可執行與可連接文件格式,
二、這裏面不只存放了編譯成彙編指令,還保留了不少別的數據
好比咱們過去全部 objdump 出來的代碼裏,你均可以看到對應的函數名稱,像 add、main 等等,乃至你本身定義的全局能夠訪問的變量名稱,都存放在這個 ELF 格式文件裏。
這些名字和它們對應的地址,在 ELF 文件裏面,存儲在一個叫做符號表的位置裏。符號表至關於一個地址簿,把名字和地址關聯了起來。
三、main 函數裏調用add 的跳轉地址,再也不是下一條指令的地址了,而是 add 函數的入口地址了
咱們先只關注和咱們的 add 以及 main 函數相關的部分。你會發現,這裏面,main 函數裏調用add 的跳轉地址,再也不是下一條指令的地址了,
而是 add 函數的入口地址了,這就是 EFL 格式和連接器的功勞。
ELF 文件格式把各類信息,分紅一個一個的 Section 保存起來。ELF 有一個基本的文件頭(File Header),用來表示這個文件的基本屬性,好比是不是可執行文件,對應的CPU、操做系統等等,除了這些基本屬性以外,大部分程序還有這麼一些Section:
連接器會掃描全部輸入的目標文件,而後把全部的符號表裏的信息收集起來,構成一個全局的符號表,而後再根據重定位表把全部不肯定要跳轉地址的代碼,
根據符號表裏面的存儲地址,進行一次修正,最後,把全部的目標文件的對應進行一次合併,變成了最終的可執行代碼,這也是爲何,可執行文件裏面的函數調用的地址都是正確的
在連接器把程序變成可執行文件以後,要裝載器去執行程序就容易多了。裝載器再也不須要考慮地址跳轉的問題,只須要解析 ELF 文件,把對應的指令和數據,加載到內存裏面供 CPU 執行就能夠了。
講到這裏,相信你已經猜到,爲何一樣一個程序,在Linux下能夠執行而Windows下不能執行了?
一、其中一個很是重要的緣由就是,兩個操做系統下可執行文件的格式不同
二、咱們今天講的事Linux下的ELF文件格式,而Windows的可執行文件格式是一種叫作PE的文件格式Linux下的裝飾器只能解析ELF格式而不能解析PE格式而不能解析PE格式
三、若是咱們有一個能夠可以解析PE格式的裝載器,咱們就有可能在下Linux運行Windows裏面也提供了WSL也就是Windows Subsystem for Linux,能夠.解析和加載ELF格式的文件
咱們去寫可用的程序,也不只僅是把全部代碼放在一個文件裏來編譯執行,而是能夠拆分紅不一樣的函數庫,最後經過一個靜態連接的機制,
使得不一樣的文件之間既有分工有能經過靜態連接來「合做「,變成了一個可執行的程序
四、對於ELF格式的文件,爲了可以實現這樣一個靜態連接的機制,裏面不僅是簡單羅列了程序所須要執行的指令,還會包括連接所須要的重定位表和符號表