從原始檔到可執行檔案:原始檔的預處理、編譯、彙編、連結

husterzxh發表於2022-05-03

從原始檔到可執行檔案:原始檔的預處理、編譯、彙編、連結

當我們寫完了C語言程式碼後,通過gcc將其編譯成可執行檔案執行,這中間具體經過的步驟包括預處理、編譯、彙編、連結四個步驟。

處理、編譯、彙編、連結四個步驟

最簡單的hello.c原始檔內容如下:

# include <stdio.h>

// 這是一行註釋
int main(void)
{
    printf("hello world!\n");
    printf("%s\n", __DATE__);
    return 0;
}

預處理

處理原始檔中以“#”開頭的元素,比如#include #define,將其轉換後直接插入原始檔中,處理後的檔案通常以.i作為副檔名。這一步具體包括:

  • 展開標頭檔案:#include
  • 巨集替換: #define
  • 條件編譯:#if #elif else ifdef ifndef
  • 刪除註釋
  • 新增行號
  • 預定義符號常量:__DATE__ __TIME__ __TIMESTAMP__ __LINE__ __FILE__ __STDC__

gcc可以通過如下指令得到預處理後的檔案:gcc -E hello.c -o hello.ihello.i檔案很長,這裡擷取一小部分:

# 4 "hello.c"
int main(void)
{
    printf("hello world!\n");
    printf("%s\n", "May  3 2022");
    return 0;
}

可以看到註釋已經被刪除了,符號常量__DATE__也已經被展開。

編譯

編譯階段包括詞法分析、語法分析、語義分析、中間程式碼生成、目的碼生成與優化,編譯完成後會生成彙編程式碼,通常副檔名為.s

gcc可以通過如下指令得到編譯後的彙編程式碼:gcc -S hello.c -o hello.s

預設生成的彙編程式碼是AT&T格式的,可採用如下指令得到intel格式的彙編程式碼:gcc -S hello.c -o hello.s -masm=intel,intel格式的hello.s內容如下:

    .file   "hello.c"
    .intel_syntax noprefix
    .text
    .section    .rodata
.LC0:
    .string    "hello world!"
.LC1:
    .string    "May  3 2022"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    endbr64
    push    rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    mov rbp, rsp
    .cfi_def_cfa_register 6
    lea rdi, .LC0[rip]
    call    puts@PLT
    lea rdi, .LC1[rip]
    call    puts@PLT
    mov eax, 0
    pop rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0"
    .section    .note.GNU-stack,"",@progbits
    .section    .note.gnu.property,"a"
    .align 8
    .long    1f - 0f
    .long    4f - 1f
    .long    5
0:
    .string  "GNU"
1:
    .align 8
    .long    0xc0000002
    .long    3f - 2f
2:
    .long    0x3
3:
    .align 8
4:

彙編

彙編是根據彙編指令與機器指令的對應關係將彙編檔案翻譯成目標檔案,如果從原始檔開始,gcc命令是gcc -c hello.c -o hello.o,如果從彙編檔案開始,gcc命令是gcc -c hello.s -o hello.o。通過file命令檢視目標檔案hello.ofile hello.o,終端顯示為:hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped,說明這是一個ELF檔案,關於ELF檔案的內容將在下一篇部落格中介紹。

hello.o檔案內容無法直接在編輯器中顯示,但可以通過objdump顯示:objdump -sd hello.o -M intel

hello.o:     檔案格式 elf64-x86-64

Contents of section .text:
 0000 f30f1efa 554889e5 488d3d00 000000e8  ....UH..H.=.....
 0010 00000000 488d3d00 000000e8 00000000  ....H.=.........
 0020 b8000000 005dc3                      .....].         
Contents of section .rodata:
 0000 68656c6c 6f20776f 726c6421 004d6179  hello world!.May
 0010 20203320 32303232 00                   3 2022.       
Contents of section .comment:
 0000 00474343 3a202855 62756e74 7520392e  .GCC: (Ubuntu 9.
 0010 342e302d 31756275 6e747531 7e32302e  4.0-1ubuntu1~20.
 0020 30342e31 2920392e 342e3000           04.1) 9.4.0.    
Contents of section .note.gnu.property:
 0000 04000000 10000000 05000000 474e5500  ............GNU.
 0010 020000c0 04000000 03000000 00000000  ................
Contents of section .eh_frame:
 0000 14000000 00000000 017a5200 01781001  .........zR..x..
 0010 1b0c0708 90010000 1c000000 1c000000  ................
 0020 00000000 27000000 00450e10 8602430d  ....'....E....C.
 0030 065e0c07 08000000                    .^......        

Disassembly of section .text:

0000000000000000 <main>:
   0:   f3 0f 1e fa             endbr64 
   4:   55                      push   rbp
   5:   48 89 e5                mov    rbp,rsp
   8:   48 8d 3d 00 00 00 00    lea    rdi,[rip+0x0]        # f <main+0xf>
   f:   e8 00 00 00 00          call   14 <main+0x14>
  14:   48 8d 3d 00 00 00 00    lea    rdi,[rip+0x0]        # 1b <main+0x1b>
  1b:   e8 00 00 00 00          call   20 <main+0x20>
  20:   b8 00 00 00 00          mov    eax,0x0
  25:   5d                      pop    rbp
  26:   c3                      ret    

此時由於還未連結,目標檔案中符號的虛擬地址無法確定。此時,如果執行hello.o會報錯:可執行檔案格式錯誤。

連結

連結包括靜態連結和動態連結兩種,gcc預設使用動態連結,新增編譯選項-static可以進行靜態連結,這一階段將目標檔案與其依賴庫進行連結,主要包括地址和空間分配(Address and Storage Allocation)、符號繫結(Symbol Binding)、重定位(Relocation)等。gcc命令:gcc hello.c -o hello。經過objdump後,部分內容如下:

0000000000001149 <main>:
    1149:   f3 0f 1e fa             endbr64 
    114d:   55                      push   rbp
    114e:   48 89 e5                mov    rbp,rsp
    1151:   48 8d 3d ac 0e 00 00    lea    rdi,[rip+0xeac]        # 2004 <_IO_stdin_used+0x4>
    1158:   e8 f3 fe ff ff          call   1050 <puts@plt>
    115d:   48 8d 3d ad 0e 00 00    lea    rdi,[rip+0xead]        # 2011 <_IO_stdin_used+0x11>
    1164:   e8 e7 fe ff ff          call   1050 <puts@plt>
    1169:   b8 00 00 00 00          mov    eax,0x0
    116e:   5d                      pop    rbp
    116f:   c3                      ret  

跟未經過連結的目標檔案相比,虛擬地址已經確定了,執行hello便可以得到結果:

hello world!
May  3 2022

參考資料

CTF競賽權威指南(Pwn篇)(楊超 編著,吳石 eee戰隊 審校,電子工業出版社)

相關文章