搜尋
首頁系統教程LinuxLinux 程式編譯過程詳解

Linux 程式編譯過程詳解

Feb 05, 2024 pm 01:09 PM
linuxlinux教程linux系統編譯錯誤linux指令shell腳本標準函式庫嵌入式linux良許linux入門linux學習

電腦程式設計語言通常分為機器語言、組合語言和高階語言三類。高階語言需要透過翻譯成機器語言才能執行,而翻譯的方式分為兩種,一種是編譯型,另一種是解釋型。

因此我們基本上將高階語言分為兩大類,一種是編譯型語言,例如C,C ,Java,另一種是解釋型語言,例如Python、Ruby、MATLAB 、JavaScript。

本文將介紹如何將高層的C/C 語言編寫的程式轉換成為處理器能夠執行的二進位程式碼的過程,包括四個步驟:

  • 預處理(Preprocessing)
  • # 編譯(Compilation)
  • 彙編(Assembly)
  • # 連結(Linking)
Linux 程序编译过程详解

GCC 工具鏈介紹

#通常所說的GCC是GUN Compiler Collection的簡稱,是Linux系統上常用的編譯工具。 GCC工具鏈軟體包括GCC、Binutils、C運作庫等。

GCC

GCC(GNU C Compiler)是編譯工具。本文所要介紹的將C/C 語言編寫的程式轉換成為處理器能夠執行的二進位程式碼的過程即由編譯器完成。

Binutils

#一組二進位處理工具,包括:addr2line、ar、objcopy、objdump、as、ld、ldd、readelf、size等。這一組工具是開發和除錯不可或缺的工具,分別簡介如下:

  • addr2line:用來將程式位址轉換成其對應的程式原始檔及對應的程式碼行,也可以得到所對應的函數。該工具將幫助調試器在調試的過程中定位對應的源代碼位置。
  • as:主要用於彙編,有關彙編的詳細介紹請參閱後文。
  • ld:主要用於鏈接,有關鏈接的詳細介紹請參見後文。
  • ar:主要用於建立靜態庫。為了方便初學者理解,在此介紹動態函式庫與靜態函式庫的概念:
  • # 如果要將多個.o目標文件產生一個庫文件,則存在兩種類型的庫,一種是靜態庫,另一種是動態庫。
  • 在windows中靜態函式庫是以 .lib 為後綴的文件,共享函式庫是以 .dll 為後綴的文件。在linux中靜態庫是以.a為後綴的文件,共享庫是以.so為後綴的文件。
  • 靜態庫和動態庫的不同點在於程式碼被載入的時刻不同。靜態函式庫的程式碼在編譯過程中已經被載入可執行程序,因此體積較大。共享庫的程式碼是在可執行程式運行時才載入記憶體的,在編譯過程中僅簡單的引用,因此程式碼體積較小。在Linux系統中,可以用ldd指令查看一個可執行程式所依賴的共用程式庫。
  • 如果一個系統中存在多個需要同時運行的程式且這些程式之間存在共享庫,那麼採用動態庫的形式將更節省記憶體。
  • ldd:可以用來檢視一個可執行程式所依賴的共用程式庫。
  • objcopy:將一種物件檔案翻譯成另一種格式,譬如將.bin轉換成.elf、或將.elf轉換成.bin等。
  • objdump:主要的功能是反彙編。有關反彙編的詳細介紹,請參閱後文。
  • readelf:顯示有關ELF文件的信息,請參見後文以了解更多信息。
  • size:列出可執行檔每個部分的尺寸和總尺寸,程式碼段、資料段、總大小等,請參考後文了解使用size的具體使用實例。

C運行庫

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的字符串。
int main(void)
{
  printf("Hello World! \n");
  return 0;
}

编译过程

1.预处理

预处理的过程主要包括以下过程:

  • 将所有的#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代码片段

extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 942 "/usr/include/stdio.h" 3 4

# 2 "hello.c" 2


# 3 "hello.c"
int
main(void)
{
  printf("Hello World!" "\n");
  return 0;
}

2.编译

编译过程就是对预处理完的文件进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码。

使用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_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $.LC0, %edi
    call    puts
    movl    $0, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc

3.汇编

汇编过程调用对汇编代码进行处理,生成处理器能识别的指令,保存在后缀为.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)格式的可重定向文件。

4.链接

链接也分为静态链接和动态链接,其要点如下:

  • 静态链接是指在编译阶段直接把静态库加入到可执行文件中去,这样可执行文件会比较大。链接器将函数的代码从其所在地(不同的目标文件或静态链接库中)拷贝到最终的可执行程序中。为创建可执行文件,链接器必须要完成的主要任务是:符号解析(把目标文件中符号的定义和引用联系起来)和重定位(把符号定义和内存地址对应起来然后修改所有对符号的引用)。
  • 动态链接则是指链接阶段仅仅只加入一些描述信息,而程序执行时再从系统中把相应动态库加载到内存中去。
  • 在Linux系统中,gcc编译链接时的动态库搜索路径的顺序通常为:首先从gcc命令的参数-L指定的路径寻找;再从环境变量LIBRARY_PATH指定的路径寻址;再从默认路径/lib、/usr/lib、/usr/local/lib寻找。
  • 在Linux系统中,执行二进制文件时的动态库搜索路径的顺序通常为:首先搜索编译目标代码时指定的动态库搜索路径;再从环境变量LD_LIBRARY_PATH指定的路径寻址;再从配置文件/etc/ld.so.conf中指定的动态库搜索路径;再从默认路径/lib、/usr/lib寻找。
  • 在Linux系统中,可以用ldd命令查看一个可执行程序依赖的共享库。

由于链接动态库和静态库的路径可能有重合,所以如果在路径中有同名的静态库文件和动态库文件,比如libtest.a和libtest.so,gcc链接时默认优先选择动态库,会链接libtest.so,如果要让gcc选择链接libtest.a则可以指定gcc选项-static,该选项会强制使用静态库进行链接。以Hello World为例:

  • 如果使用命令“gcc hello.c -o hello”则会使用动态库进行链接,生成的ELF可执行文件的大小(使用Binutils的size命令查看)和链接的动态库(使用Binutils的ldd命令查看)如下所示:
$ 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”则会使用静态库进行链接,生成的ELF可执行文件的大小(使用Binutils的size命令查看)和链接的动态库(使用Binutils的ldd命令查看)如下所示:

$ 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文件

1.ELF文件的段

ELF文件格式如下图所示,位于ELF Header和Section Header Table之间的都是段(Section)。一个典型的ELF文件包含下面几个段:

  • .text:已编译程序的指令代码段。
  • .rodata:ro代表read only,即只读数据(譬如常数const)。
  • .data:已初始化的C程序全局变量和静态局部变量。
  • .bss:未初始化的C程序全局变量和静态局部变量。
  • .debug:调试符号表,调试器用此段的信息帮助调试。
Linux 程序编译过程详解

可以使用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
……

2.反汇编ELF

由于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  400400 
  400534:    b8 00 00 00 00          mov    $0x0,%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 :
#include 

int
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  400400 
  return 0;
  400534:    b8 00 00 00 00          mov    $0x0,%eax
}
  400539:    5d                          pop    %rbp
  40053a:    c3                          retq   
  40053b:    0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)
……

以上是Linux 程式編譯過程詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:良许Linux教程网。如有侵權,請聯絡admin@php.cn刪除
如何在Linux中自動重新啟動失敗的服務如何在Linux中自動重新啟動失敗的服務Apr 28, 2025 am 09:39 AM

本指南詳細介紹瞭如何使用SystemD配置自動服務在Linux中重新啟動,從而增強了系統的可靠性並最大程度地減少停機時間。 系統管理員通常依靠此功能來確保關鍵服務,例如Web服務器(APA

10個隱藏的Linux命令每個系統都應該知道10個隱藏的Linux命令每個系統都應該知道Apr 28, 2025 am 09:35 AM

作為Linux用戶,我們經常依賴常用的命令ls、grep、awk、sed和find來完成工作。但Linux擁有大量鮮為人知的命令,可以節省時間、自動化任務並簡化工作流程。 本文將探討一些被低估但卻功能強大的Linux命令,它們值得更多關注。 rename – 高效批量重命名文件 當您需要一次重命名多個文件時,rename命令是救星。無需使用包含mv的循環,rename允許您輕鬆應用複雜的重命名模式。 將所有.txt文件更改為.log。 rename 's/\.txt$/\.log/' *

如何在Linux中的SystemD下列出所有運行服務如何在Linux中的SystemD下列出所有運行服務Apr 28, 2025 am 09:29 AM

Linux 系統提供各種系統服務(例如進程管理、登錄、syslog、cron 等)和網絡服務(例如遠程登錄、電子郵件、打印機、Web 託管、數據存儲、文件傳輸、域名解析(使用 DNS)、動態 IP 地址分配(使用 DHCP)等等)。 從技術上講,服務是在後台持續運行的進程或進程組(通常稱為 守護進程),等待傳入請求(尤其來自客戶端)。 Linux 支持不同的方式來管理(啟動、停止、重啟、啟用系統啟動時的自動啟動等)服務,通常通過進程或服務管理器。幾乎所有現代 Linux 發行版現在都使用相同的進

Crossover 25:在Linux上運行Windows軟件和遊戲Crossover 25:在Linux上運行Windows軟件和遊戲Apr 28, 2025 am 09:27 AM

使用Crossover 25運行Windows軟件和遊戲 由於CodeWeavers的Crossover 25,在Linux上運行Windows應用程序和遊戲現在比以往任何時候都容易。 這個商業軟件解決方案讓Linux用戶運行各種各樣的風

PCLOUD-最安全的雲存儲[優惠50%]PCLOUD-最安全的雲存儲[優惠50%]Apr 28, 2025 am 09:26 AM

使用PCLOUD保護數據:Linux安裝的綜合指南 領先的安全雲存儲服務PCloud提供了一個可靠的平台來管理您的文件和數據。本指南詳細介紹了Linux系統上的安裝過程。 關於

MANGOHUD-監視FPS,Linux遊戲中的CPU和GPU使用情況MANGOHUD-監視FPS,Linux遊戲中的CPU和GPU使用情況Apr 28, 2025 am 09:25 AM

MangoHud:實時監控Linux遊戲性能的利器 MangoHud是一款功能強大且輕量級的工具,專為遊戲玩家、開發者以及任何希望實時監控系統性能的用戶而設計。它作為Vulkan和OpenGL應用程序的疊加層,顯示FPS、CPU和GPU使用率、溫度等重要信息。本文將探討MangoHud的功能、工作原理以及使用方法,並提供在Linux系統上安裝和配置MangoHud的分步說明。 MangoHud是什麼? MangoHud是一個開源項目,可在GitHub上獲取,旨在提供一種簡單且可自定義的方式來監

5必不可少的Linux命令行檔案工具 - 第1部分5必不可少的Linux命令行檔案工具 - 第1部分Apr 28, 2025 am 09:23 AM

管理存檔文件是Linux中的常見任務。本文是兩部分系列中的第一篇,探討了五種強大的命令行檔案工具,詳細介紹了他們的功能和示例的用法。 1。焦油命令:多功能存檔實用程序 t

在Linux中比較文件的前7個工具(示例)在Linux中比較文件的前7個工具(示例)Apr 28, 2025 am 09:21 AM

本指南探討了用於比較Linux中文本文件的各種方法,Linux是系統管理員和開發人員的關鍵任務。 我們將介紹命令行工具和視覺差異工具,突出顯示其優勢和適當的用例。 假設

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!

Dreamweaver Mac版

Dreamweaver Mac版

視覺化網頁開發工具

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。