詳解Linux 程式編譯過程
導讀 | 大家肯定都知道計算機程式設計語言通常分為機器語言、組合語言和高階語言三類。高階語言需要透過翻譯成機器語言才能執行,而翻譯的方式分為兩種,一種是編譯型,另一種是解釋型,因此我們基本上將高階語言分為兩大類,一種是編譯型語言,例如C,C++,Java,另一種是解釋型語言,例如Python、Ruby、MATLAB 、JavaScript。 |
本文將介紹如何將高層的C/C++語言編寫的程式轉換成為處理器能夠執行的二進位制程式碼的過程,包括四個步驟
- 預處理(Preprocessing)
- 編譯(Compilation)
- 彙編(Assembly)
- 連結(Linking)
- as:主要用於彙編,有關彙編的詳細介紹請參見後文。
- ld:主要用於連結,有關連結的詳細介紹請參見後文。
- ar:主要用於建立靜態庫。為了便於初學者理解,在此介紹動態庫與靜態庫的概念:
- 如果要將多個.o目標檔案生成一個庫檔案,則存在兩種型別的庫,一種是靜態庫,另一種是動態庫。
- 在windows中靜態庫是以 .lib 為字尾的檔案,共享庫是以 .dll 為字尾的檔案。在 中靜態庫是以.a為字尾的檔案,共享庫是以.so為字尾的檔案。
- 靜態庫和動態庫的不同點在於程式碼被載入的時刻不同。靜態庫的程式碼在編譯過程中已經被載入可執行程式,因此體積較大。共享庫的程式碼是在可執行程式執行時才載入記憶體的,在編譯過程中僅簡單的引用,因此程式碼體積較小。在Linux系統中,可以用ldd 檢視一個可執行程式依賴的共享庫。
- 如果一個系統中存在多個需要同時執行的程式且這些程式之間存在共享庫,那麼採用動態庫的形式將更節省記憶體。
- ldd:可以用於檢視一個可執行程式依賴的共享庫。
- objcopy:將一種物件檔案翻譯成另一種格式,譬如將.bin轉換成.elf、或者將.elf轉換成.bin等。
- objdump:主要的作用是反彙編。有關反彙編的詳細介紹,請參見後文。
- readelf:顯示有關ELF檔案的資訊,請參見後文瞭解更多資訊。
- size:列出可執行檔案每個部分的尺寸和總尺寸,程式碼段、資料段、總大小等,請參見後文瞭解使用size的具體使用例項。
通常所說的GCC是GUN Compiler Collection的簡稱,是 上常用的編譯工具。GCC工具鏈軟體包括GCC、Binutils、C執行庫等。
GCC(GNU C Compiler)是編譯工具。本文所要介紹的將C/C++語言編寫的程式轉換成為處理器能夠執行的二進位制程式碼的過程即由編譯器完成。
一組二進位制程式處理工具,包括:addr2line、ar、objcopy、objdump、as、ld、ldd、readelf、size等。這一組工具是開發和除錯不可缺少的工具,分別簡介如下:
addr2line:用來將程式地址轉換成其所對應的程式原始檔及所對應的程式碼行,也可以得到所對應的函式。該工具將幫助偵錯程式在除錯的過程中定位對應的原始碼位置。
C語言標準主要由兩部分組成:一部分描述C的語法,另一部分描述C標準庫。C標準庫定義了一組標準標頭檔案,每個標頭檔案中包含一些相關的函式、變數、型別宣告和宏定義,譬如常見的printf函式便是一個C標準庫函式,其原型定義在stdio標頭檔案中。
C語言標準僅僅定義了C標準庫函式原型,並沒有提供實現。因此,C語言編譯器通常需要一個C執行時庫(C Run Time Libray,CRT)的支援。C執行時庫又常簡稱為C執行庫。與C語言類似,C++也定義了自己的標準,同時提供相關支援庫,稱為C++執行時庫。
由於GCC工具鏈主要是在Linux環境中進行使用,因此本文也將以Linux系統作為工作環境。為了能夠演示編譯的整個過程,本節先準備一個C語言編寫的簡單Hello程式作為示例,其原始碼如下所示:
#include //此程式很簡單,僅僅列印一個Hello World的字串。 intmain(void) { printf("Hello World! \n"); return; }
預處理的過程主要包括以下過程:
將所有的#define刪除,並且展開所有的宏定義,並且處理所有的條件預編譯指令,比如#if #ifdef #elif #else #endif等。
處理#include預編譯指令,將被包含的檔案插入到該預編譯指令的位置。
刪除所有註釋“//”和“/* */”。
新增行號和檔案標識,以便編譯時產生除錯用的行號及編譯錯誤警告行號。
保留所有的#pragma編譯器指令,後續編譯過程需要使用它們。
使用gcc進行預處理的
如下:
$gcc -E hello.c -o hello.i // 將原始檔hello.c檔案預處理生成hello.i // GCC的選項-E使GCC在進行完預處理後即停止
hello.i檔案可以作為普通文字檔案開啟進行檢視,其程式碼片段如下所示:
// hello.i程式碼片段 externvoidfunlockfile(FILE *__stream)__attribute__((__nothrow__ , __leaf__)); #942"/usr/include/stdio.h"34 #2"hello.c"2 #3"hello.c" int main(void) { printf("Hello World!""\n"); return; }
編譯過程就是對預處理完的檔案進行一系列的詞法分析,語法分析,語義分析及最佳化後生成相應的彙編程式碼。
使用gcc進行編譯的命令如下:
$gcc -S hello.i -o hello.s // 將預處理生成的hello.i檔案編譯生成彙編程式hello.s // GCC的選項-S使GCC在執行完編譯後停止,生成彙編程式
上述命令生成的彙編程式hello.s的程式碼片段如下所示,其全部為彙編程式碼。
// hello.s程式碼片段 main: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset16 .cfi_offset6,-16 movq %rsp, %rbp .cfi_def_cfa_register6 movl $.LC0, %edi call puts movl $, %eax popq %rbp .cfi_def_cfa7,8 ret .cfi_endproc
彙編過程呼叫對彙編程式碼進行處理,生成處理器能識別的指令,儲存在字尾為.o的目標檔案中。由於每一個彙編語句幾乎都對應一條處理器指令,因此,彙編相對於編譯過程比較簡單,透過呼叫Binutils中的彙編器as根據彙編指令和處理器指令的對照表一一翻譯即可。
當程式由多個原始碼檔案構成時,每個檔案都要先完成彙編工作,生成.o目標檔案後,才能進入下一步的連結工作。注意:目標檔案已經是最終程式的某一部分了,但是在連結之前還不能執行。
使用gcc進行彙編的命令如下:
$gcc -c hello.s -o hello.o // 將編譯生成的hello.s檔案彙編生成目標檔案hello.o // GCC的選項-c使GCC在執行完彙編後停止,生成目標檔案 //或者直接呼叫as進行彙編 $as -c hello.s -o hello.o //使用Binutils中的as將hello.s檔案彙編生成目標檔案
注意:hello.o目標檔案為ELF(Executable and Linkable Format)格式的可重定向檔案。
連結也分為靜態連結和動態連結,其要點如下:
$gcc hello.c -o hello $size hello //使用size檢視大小 text data bss dec hex filename 1183 552 8 1743 6cf hello $ldd hello //可以看出該可執行檔案連結了很多其他動態庫,主要是Linux的glibc動態庫 linux-vdso.so.1 => (0x00007fffefd7c000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fadcdd82000) /lib64/ld-linux-x86-64.so.2 (0x00007fadce14c000)
$gcc -static hello.c -o hello $size hello //使用size檢視大小 text data bss dec hex filename 823726 7284 6360 837370 cc6fa hello //可以看出text的程式碼尺寸變得極大 $ldd hello not a dynamic executable //說明沒有連結動態庫
連結器連結後生成的最終檔案為ELF格式可執行檔案,一個ELF可執行檔案通常被連結為不同的段,常見的段譬如.text、.data、.rodata、.bss等段。
ELF檔案格式如下圖所示,位於ELF Header和Section Header Table之間的都是段(Section)。一個典型的ELF檔案包含下面幾個段:
可以使用readelf -S檢視其各個section的資訊如下:
$readelf -S hello There are 31 section headers, starting at offset 0x19d8: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 …… [11] .init PROGBITS 00000000004003c8 000003c8 000000000000001a 0000000000000000 AX 0 0 4 …… [14] .text PROGBITS 0000000000400430 00000430 0000000000000182 0000000000000000 AX 0 0 16 [15] .fini PROGBITS 00000000004005b4 000005b4 ……
由於ELF檔案無法被當做普通文字檔案開啟,如果希望直接檢視一個ELF檔案包含的指令和資料,需要使用反彙編的方法。
使用objdump -D對其進行反彙編如下
$ objdump -D hello …… 0000000000400526: // main標籤的PC地址 //PC地址:指令編碼 指令的彙編格式 400526: 55 push %rbp 400527: 48 89 e5 mov %rsp,%rbp 40052a: bf c4 05 40 00 mov $0x4005c4,%edi 40052f: e8 cc fe ff ff callq 400400400534: b8 00 00 00 00 mov $0x,%eax 400539: 5d pop %rbp 40053a: c3 retq 40053b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1) ……
使用objdump -S將其反彙編並且將其C語言原始碼混合顯示出來:
$ gcc -o hello -g hello.c //要加上-g選項 $ objdump -S hello …… 0000000000400526: #includeint main(void) { 400526: 55 push %rbp 400527: 48 89 e5 mov %rsp,%rbp printf("Hello World!" "\n"); 40052a: bf c4 05 40 00 mov $0x4005c4,%edi 40052f: e8 cc fe ff ff callq 400400return ; 400534: b8 00 00 00 00 mov $0x0,%eax } 400539: 5d pop %rbp 40053a: c3 retq 40053b: f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
原文來自:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2910545/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Hive SQL 編譯過程詳解HiveSQL編譯
- [轉]:xmake編譯配置過程詳解編譯
- C/C++編譯過程詳解C++編譯
- 圖解Java程式編譯解釋過程圖解Java編譯
- Hive SQL的底層編譯過程詳解HiveSQL編譯
- .NET 程式碼編譯過程編譯
- Linux 程式編譯過程的來龍去脈Linux編譯
- 編譯過程編譯
- C程式編譯過程淺析C程式編譯
- 編譯C++ 程式的過程編譯C++
- Javac編譯過程Java編譯
- 編譯核心過程編譯
- 編譯器的編譯基本過程編譯
- Linux啟動過程詳解Linux
- 編譯連結過程編譯
- 編譯過程簡介編譯
- C++ 編譯過程C++編譯
- Oracle 編譯儲存過程卡死解決方法Oracle編譯儲存過程
- Linux上安裝GCC編譯器過程(轉)LinuxGC編譯
- Ubuntu20.04linux核心(5.4.0版本)編譯準備與實現過程-編譯過程(2)UbuntuLinux編譯
- ios底層 編譯過程iOS編譯
- 編譯器的工作過程編譯
- EVC編譯TCPMP的過程編譯TCP
- glade 編譯過程 (轉)編譯
- Linux下nginx編譯安裝教程和編譯引數詳解LinuxNginx編譯
- lamp編譯詳解LAMP編譯
- GCC編譯過程(預處理->編譯->彙編->連結)GC編譯
- MDK編譯過程及檔案型別全解編譯型別
- Oracle儲存過程編譯卡死的解決方法Oracle儲存過程編譯
- iOS學習之深入理解程式編譯過程iOS編譯
- PHP Socket 程式設計過程詳解PHP程式設計
- 原始碼編譯安裝MySQL5.6.12詳細過程原始碼編譯MySql
- GCC編譯和連結過程GC編譯
- JavaScript的預編譯過程分析JavaScript編譯
- go語言編譯過程概述Go編譯
- 預編譯過程(AO+GO)編譯Go
- Android 專案編譯過程Android編譯
- Android Makefile 編譯過程分析Android編譯