偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Linux程序編譯過(guò)程的來(lái)龍去脈

系統(tǒng) Linux
本文將介紹如何將高層的C/C++語(yǔ)言編寫(xiě)的程序轉(zhuǎn)換成為處理器能夠執(zhí)行的二進(jìn)制代碼的過(guò)程。

 [[376957]]

大家肯定都知道計(jì)算機(jī)程序設(shè)計(jì)語(yǔ)言通常分為機(jī)器語(yǔ)言、匯編語(yǔ)言和高級(jí)語(yǔ)言三類(lèi)。高級(jí)語(yǔ)言需要通過(guò)翻譯成機(jī)器語(yǔ)言才能執(zhí)行,而翻譯的方式分為兩種,一種是編譯型,另一種是解釋型,因此我們基本上將高級(jí)語(yǔ)言分為兩大類(lèi),一種是編譯型語(yǔ)言,例如C,C++,Java,另一種是解釋型語(yǔ)言,例如Python、Ruby、MATLAB 、JavaScript。

本文將介紹如何將高層的C/C++語(yǔ)言編寫(xiě)的程序轉(zhuǎn)換成為處理器能夠執(zhí)行的二進(jìn)制代碼的過(guò)程,包括四個(gè)步驟:

  •  預(yù)處理(Preprocessing)
  •  編譯(Compilation)
  •  匯編(Assembly)
  •  鏈接(Linking)

GCC 工具鏈介紹

通常所說(shuō)的GCC是GUN Compiler Collection的簡(jiǎn)稱(chēng),是Linux系統(tǒng)上常用的編譯工具。GCC工具鏈軟件包括GCC、Binutils、C運(yùn)行庫(kù)等。

GCC

GCC(GNU C Compiler)是編譯工具。本文所要介紹的將C/C++語(yǔ)言編寫(xiě)的程序轉(zhuǎn)換成為處理器能夠執(zhí)行的二進(jìn)制代碼的過(guò)程即由編譯器完成。

Binutils

一組二進(jìn)制程序處理工具,包括:addr2line、ar、objcopy、objdump、as、ld、ldd、readelf、size等。這一組工具是開(kāi)發(fā)和調(diào)試不可缺少的工具,分別簡(jiǎn)介如下:

  • addr2line:用來(lái)將程序地址轉(zhuǎn)換成其所對(duì)應(yīng)的程序源文件及所對(duì)應(yīng)的代碼行,也可以得到所對(duì)應(yīng)的函數(shù)。該工具將幫助調(diào)試器在調(diào)試的過(guò)程中定位對(duì)應(yīng)的源代碼位置。
  •  as:主要用于匯編,有關(guān)匯編的詳細(xì)介紹請(qǐng)參見(jiàn)后文。
  •  ld:主要用于鏈接,有關(guān)鏈接的詳細(xì)介紹請(qǐng)參見(jiàn)后文。
  •  ar:主要用于創(chuàng)建靜態(tài)庫(kù)。為了便于初學(xué)者理解,在此介紹動(dòng)態(tài)庫(kù)與靜態(tài)庫(kù)的概念:
    •  如果要將多個(gè).o目標(biāo)文件生成一個(gè)庫(kù)文件,則存在兩種類(lèi)型的庫(kù),一種是靜態(tài)庫(kù),另一種是動(dòng)態(tài)庫(kù)。
    •  在windows中靜態(tài)庫(kù)是以 .lib 為后綴的文件,共享庫(kù)是以 .dll 為后綴的文件。在linux中靜態(tài)庫(kù)是以.a為后綴的文件,共享庫(kù)是以.so為后綴的文件。
    •  靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)的不同點(diǎn)在于代碼被載入的時(shí)刻不同。靜態(tài)庫(kù)的代碼在編譯過(guò)程中已經(jīng)被載入可執(zhí)行程序,因此體積較大。共享庫(kù)的代碼是在可執(zhí)行程序運(yùn)行時(shí)才載入內(nèi)存的,在編譯過(guò)程中僅簡(jiǎn)單的引用,因此代碼體積較小。在Linux系統(tǒng)中,可以用ldd命令查看一個(gè)可執(zhí)行程序依賴(lài)的共享庫(kù)。
    •  如果一個(gè)系統(tǒng)中存在多個(gè)需要同時(shí)運(yùn)行的程序且這些程序之間存在共享庫(kù),那么采用動(dòng)態(tài)庫(kù)的形式將更節(jié)省內(nèi)存。
  •  ldd:可以用于查看一個(gè)可執(zhí)行程序依賴(lài)的共享庫(kù)。
  •  objcopy:將一種對(duì)象文件翻譯成另一種格式,譬如將.bin轉(zhuǎn)換成.elf、或者將.elf轉(zhuǎn)換成.bin等。
  •  objdump:主要的作用是反匯編。有關(guān)反匯編的詳細(xì)介紹,請(qǐng)參見(jiàn)后文。
  •  readelf:顯示有關(guān)ELF文件的信息,請(qǐng)參見(jiàn)后文了解更多信息。
  •  size:列出可執(zhí)行文件每個(gè)部分的尺寸和總尺寸,代碼段、數(shù)據(jù)段、總大小等,請(qǐng)參見(jiàn)后文了解使用size的具體使用實(shí)例。

C運(yùn)行庫(kù)

C語(yǔ)言標(biāo)準(zhǔn)主要由兩部分組成:一部分描述C的語(yǔ)法,另一部分描述C標(biāo)準(zhǔn)庫(kù)。C標(biāo)準(zhǔn)庫(kù)定義了一組標(biāo)準(zhǔn)頭文件,每個(gè)頭文件中包含一些相關(guān)的函數(shù)、變量、類(lèi)型聲明和宏定義,譬如常見(jiàn)的printf函數(shù)便是一個(gè)C標(biāo)準(zhǔn)庫(kù)函數(shù),其原型定義在stdio頭文件中。

C語(yǔ)言標(biāo)準(zhǔn)僅僅定義了C標(biāo)準(zhǔn)庫(kù)函數(shù)原型,并沒(méi)有提供實(shí)現(xiàn)。因此,C語(yǔ)言編譯器通常需要一個(gè)C運(yùn)行時(shí)庫(kù)(C Run Time Libray,CRT)的支持。C運(yùn)行時(shí)庫(kù)又常簡(jiǎn)稱(chēng)為C運(yùn)行庫(kù)。與C語(yǔ)言類(lèi)似,C++也定義了自己的標(biāo)準(zhǔn),同時(shí)提供相關(guān)支持庫(kù),稱(chēng)為C++運(yùn)行時(shí)庫(kù)。

準(zhǔn)備工作

由于GCC工具鏈主要是在Linux環(huán)境中進(jìn)行使用,因此本文也將以Linux系統(tǒng)作為工作環(huán)境。為了能夠演示編譯的整個(gè)過(guò)程,本節(jié)先準(zhǔn)備一個(gè)C語(yǔ)言編寫(xiě)的簡(jiǎn)單Hello程序作為示例,其源代碼如下所示: 

  1. #include <stdio.h>   
  2. //此程序很簡(jiǎn)單,僅僅打印一個(gè)Hello World的字符串。  
  3. int main(void)  
  4.    printf("Hello World! \n");  
  5.   return 0;  

編譯過(guò)程

1.預(yù)處理

預(yù)處理的過(guò)程主要包括以下過(guò)程:

  • 將所有的#define刪除,并且展開(kāi)所有的宏定義,并且處理所有的條件預(yù)編譯指令,比如#if #ifdef #elif #else #endif等。
  •  處理#include預(yù)編譯指令,將被包含的文件插入到該預(yù)編譯指令的位置。
  •  刪除所有注釋“//”和“/* */”。
  •  添加行號(hào)和文件標(biāo)識(shí),以便編譯時(shí)產(chǎn)生調(diào)試用的行號(hào)及編譯錯(cuò)誤警告行號(hào)。
  •  保留所有的#pragma編譯器指令,后續(xù)編譯過(guò)程需要使用它們。
  •  使用gcc進(jìn)行預(yù)處理的命令如下: 
  1. $ gcc -E hello.c -o hello.i // 將源文件hello.c文件預(yù)處理生成hello.i  
  2.                         // GCC的選項(xiàng)-E使GCC在進(jìn)行完預(yù)處理后即停止 

hello.i文件可以作為普通文本文件打開(kāi)進(jìn)行查看,其代碼片段如下所示: 

  1. // hello.i代碼片段  
  2. extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));  
  3. # 942 "/usr/include/stdio.h" 3 4  
  4. # 2 "hello.c" 2  
  5. # 3 "hello.c"  
  6. int  
  7. main(void)  
  8.  
  9.   printf("Hello World!" "\n");  
  10.   return 0;  

2.編譯

編譯過(guò)程就是對(duì)預(yù)處理完的文件進(jìn)行一系列的詞法分析,語(yǔ)法分析,語(yǔ)義分析及優(yōu)化后生成相應(yīng)的匯編代碼。

使用gcc進(jìn)行編譯的命令如下: 

  1. $ gcc -S hello.i -o hello.s // 將預(yù)處理生成的hello.i文件編譯生成匯編程序hello.s  
  2.                         // GCC的選項(xiàng)-S使GCC在執(zhí)行完編譯后停止,生成匯編程序 

上述命令生成的匯編程序hello.s的代碼片段如下所示,其全部為匯編代碼。 

  1. // hello.s代碼片段  
  2. main:  
  3. .LFB0:  
  4.     .cfi_startproc  
  5.     pushq   %rbp  
  6.     .cfi_def_cfa_offset 16  
  7.     .cfi_offset 6, -16  
  8.     movq    %rsp, %rbp  
  9.     .cfi_def_cfa_register 6  
  10.     movl    $.LC0, %edi  
  11.     call    puts  
  12.     movl    $0, %eax  
  13.     popq    %rbp  
  14.     .cfi_def_cfa 7, 8  
  15.     ret  
  16.     .cfi_endproc 

3.匯編

匯編過(guò)程調(diào)用對(duì)匯編代碼進(jìn)行處理,生成處理器能識(shí)別的指令,保存在后綴為.o的目標(biāo)文件中。由于每一個(gè)匯編語(yǔ)句幾乎都對(duì)應(yīng)一條處理器指令,因此,匯編相對(duì)于編譯過(guò)程比較簡(jiǎn)單,通過(guò)調(diào)用Binutils中的匯編器as根據(jù)匯編指令和處理器指令的對(duì)照表一一翻譯即可。

當(dāng)程序由多個(gè)源代碼文件構(gòu)成時(shí),每個(gè)文件都要先完成匯編工作,生成.o目標(biāo)文件后,才能進(jìn)入下一步的鏈接工作。注意:目標(biāo)文件已經(jīng)是最終程序的某一部分了,但是在鏈接之前還不能執(zhí)行。

使用gcc進(jìn)行匯編的命令如下: 

  1. $ gcc -c hello.s -o hello.o // 將編譯生成的hello.s文件匯編生成目標(biāo)文件hello.o  
  2.                         // GCC的選項(xiàng)-c使GCC在執(zhí)行完匯編后停止,生成目標(biāo)文件  
  3. //或者直接調(diào)用as進(jìn)行匯編  
  4. $ as -c hello.s -o hello.o //使用Binutils中的as將hello.s文件匯編生成目標(biāo)文件 

注意:hello.o目標(biāo)文件為ELF(Executable and Linkable Format)格式的可重定向文件。

4.鏈接

鏈接也分為靜態(tài)鏈接和動(dòng)態(tài)鏈接,其要點(diǎn)如下:

  •  靜態(tài)鏈接是指在編譯階段直接把靜態(tài)庫(kù)加入到可執(zhí)行文件中去,這樣可執(zhí)行文件會(huì)比較大。鏈接器將函數(shù)的代碼從其所在地(不同的目標(biāo)文件或靜態(tài)鏈接庫(kù)中)拷貝到最終的可執(zhí)行程序中。為創(chuàng)建可執(zhí)行文件,鏈接器必須要完成的主要任務(wù)是:符號(hào)解析(把目標(biāo)文件中符號(hào)的定義和引用聯(lián)系起來(lái))和重定位(把符號(hào)定義和內(nèi)存地址對(duì)應(yīng)起來(lái)然后修改所有對(duì)符號(hào)的引用)。
  •  動(dòng)態(tài)鏈接則是指鏈接階段僅僅只加入一些描述信息,而程序執(zhí)行時(shí)再?gòu)南到y(tǒng)中把相應(yīng)動(dòng)態(tài)庫(kù)加載到內(nèi)存中去。
    •  在Linux系統(tǒng)中,gcc編譯鏈接時(shí)的動(dòng)態(tài)庫(kù)搜索路徑的順序通常為:首先從gcc命令的參數(shù)-L指定的路徑尋找;再?gòu)沫h(huán)境變量LIBRARY_PATH指定的路徑尋址;再?gòu)哪J(rèn)路徑/lib、/usr/lib、/usr/local/lib尋找。
    •   在Linux系統(tǒng)中,執(zhí)行二進(jìn)制文件時(shí)的動(dòng)態(tài)庫(kù)搜索路徑的順序通常為:首先搜索編譯目標(biāo)代碼時(shí)指定的動(dòng)態(tài)庫(kù)搜索路徑;再?gòu)沫h(huán)境變量LD_LIBRARY_PATH指定的路徑尋址;再?gòu)呐渲梦募?etc/ld.so.conf中指定的動(dòng)態(tài)庫(kù)搜索路徑;再?gòu)哪J(rèn)路徑/lib、/usr/lib尋找。
    •   在Linux系統(tǒng)中,可以用ldd命令查看一個(gè)可執(zhí)行程序依賴(lài)的共享庫(kù)。

由于鏈接動(dòng)態(tài)庫(kù)和靜態(tài)庫(kù)的路徑可能有重合,所以如果在路徑中有同名的靜態(tài)庫(kù)文件和動(dòng)態(tài)庫(kù)文件,比如libtest.a和libtest.so,gcc鏈接時(shí)默認(rèn)優(yōu)先選擇動(dòng)態(tài)庫(kù),會(huì)鏈接libtest.so,如果要讓gcc選擇鏈接libtest.a則可以指定gcc選項(xiàng)-static,該選項(xiàng)會(huì)強(qiáng)制使用靜態(tài)庫(kù)進(jìn)行鏈接。以Hello World為例:

  •  如果使用命令“gcc hello.c -o hello”則會(huì)使用動(dòng)態(tài)庫(kù)進(jìn)行鏈接,生成的ELF可執(zhí)行文件的大?。ㄊ褂肂inutils的size命令查看)和鏈接的動(dòng)態(tài)庫(kù)(使用Binutils的ldd命令查看)如下所示:   
  1. $ gcc hello.c -o hello  
  2.     $ size hello  //使用size查看大小  
  3.        text    data     bss     dec     hex filename  
  4.        1183     552       8    1743     6cf     hello  
  5.     $ ldd hello //可以看出該可執(zhí)行文件鏈接了很多其他動(dòng)態(tài)庫(kù),主要是Linux的glibc動(dòng)態(tài)庫(kù)  
  6.             linux-vdso.so.1 =>  (0x00007fffefd7c000)  
  7.             libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fadcdd82000)  
  8.             /lib64/ld-linux-x86-64.so.2 (0x00007fadce14c000) 
  •  如果使用命令“gcc -static hello.c -o hello”則會(huì)使用靜態(tài)庫(kù)進(jìn)行鏈接,生成的ELF可執(zhí)行文件的大小(使用Binutils的size命令查看)和鏈接的動(dòng)態(tài)庫(kù)(使用Binutils的ldd命令查看)如下所示: 
  1. $ gcc -static hello.c -o hello  
  2.   $ size hello //使用size查看大小  
  3.        text    data     bss     dec     hex filename  
  4.    823726    7284    6360  837370   cc6fa     hello //可以看出text的代碼尺寸變得極大  
  5.   $ ldd hello  
  6.          not a dynamic executable //說(shuō)明沒(méi)有鏈接動(dòng)態(tài)庫(kù) 

鏈接器鏈接后生成的最終文件為ELF格式可執(zhí)行文件,一個(gè)ELF可執(zhí)行文件通常被鏈接為不同的段,常見(jiàn)的段譬如.text、.data、.rodata、.bss等段。

分析ELF文件

1.ELF文件的段

ELF文件格式如下圖所示,位于ELF Header和Section Header Table之間的都是段(Section)。一個(gè)典型的ELF文件包含下面幾個(gè)段:

  •  .text:已編譯程序的指令代碼段。
  •  .rodata:ro代表read only,即只讀數(shù)據(jù)(譬如常數(shù)const)。
  •  .data:已初始化的C程序全局變量和靜態(tài)局部變量。
  •  .bss:未初始化的C程序全局變量和靜態(tài)局部變量。
  •  .debug:調(diào)試符號(hào)表,調(diào)試器用此段的信息幫助調(diào)試。

可以使用readelf -S查看其各個(gè)section的信息如下: 

  1. $ readelf -S hello  
  2. There are 31 section headers, starting at offset 0x19d8:  
  3. Section Headers:  
  4.   [Nr] Name              Type             Address           Offset  
  5.        Size              EntSize          Flags  Link  Info  Align  
  6.   [ 0]                   NULL             0000000000000000  00000000  
  7.        0000000000000000  0000000000000000           0     0     0  
  8. ……  
  9.   [11] .init             PROGBITS         00000000004003c8  000003c8  
  10.        000000000000001a  0000000000000000  AX       0     0     4  
  11. ……  
  12.   [14] .text             PROGBITS         0000000000400430  00000430  
  13.        0000000000000182  0000000000000000  AX       0     0     16  
  14.   [15] .fini             PROGBITS         00000000004005b4  000005b4  
  15. …… 

2.反匯編ELF

由于ELF文件無(wú)法被當(dāng)做普通文本文件打開(kāi),如果希望直接查看一個(gè)ELF文件包含的指令和數(shù)據(jù),需要使用反匯編的方法。

使用objdump -D對(duì)其進(jìn)行反匯編如下: 

  1. $ objdump -D hello  
  2. ……  
  3. 0000000000400526 <main>:  // main標(biāo)簽的PC地址  
  4. //PC地址:指令編碼                  指令的匯編格式  
  5.   400526:    55                          push   %rbp   
  6.   400527:    48 89 e5                mov    %rsp,%rbp  
  7.   40052a:    bf c4 05 40 00          mov    $0x4005c4,%edi  
  8.   40052f:    e8 cc fe ff ff          callq  400400 <puts@plt>  
  9.   400534:    b8 00 00 00 00          mov    $0x0,%eax  
  10.   400539:    5d                      pop    %rbp  
  11.   40053a:    c3                          retq    
  12.   40053b:    0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)  
  13. …… 

使用objdump -S將其反匯編并且將其C語(yǔ)言源代碼混合顯示出來(lái): 

  1. $ gcc -o hello -g hello.c //要加上-g選項(xiàng)  
  2. $ objdump -S hello  
  3. ……  
  4. 0000000000400526 <main> 
  5. #include <stdio.h>  
  6. int  
  7. main(void)  
  8.  
  9.   400526:    55                          push   %rbp  
  10.   400527:    48 89 e5                mov    %rsp,%rbp  
  11.   printf("Hello World!" "\n");  
  12.   40052a:    bf c4 05 40 00          mov    $0x4005c4,%edi  
  13.   40052f:    e8 cc fe ff ff          callq  400400 <puts@plt>  
  14.   return 0;  
  15.   400534:    b8 00 00 00 00          mov    $0x0,%eax  
  16.  
  17.   400539:    5d                          pop    %rbp  
  18.   40053a:    c3                          retq     
  19.   40053b:    0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)  
  20. ……  

 

責(zé)任編輯:龐桂玉 來(lái)源: 良許Linux
相關(guān)推薦

2022-06-09 09:20:40

Linux語(yǔ)言編寫(xiě)代碼

2022-05-18 07:58:21

Linux程序編譯代碼

2009-10-16 09:45:41

Linux內(nèi)核操作系統(tǒng)

2022-06-28 18:32:45

物聯(lián)網(wǎng)IoT

2009-06-26 08:44:57

2018-01-09 04:59:59

VLANTag網(wǎng)絡(luò)技術(shù)

2011-01-06 11:36:00

linuxGCC編譯器

2017-12-28 14:51:01

AndroidView焦點(diǎn)

2019-10-31 08:36:59

線程內(nèi)存操作系統(tǒng)

2021-05-13 10:12:55

Kubernetes 微服務(wù)軟件開(kāi)發(fā)

2022-08-02 09:02:17

虛擬內(nèi)存操作系統(tǒng)

2009-10-20 14:58:15

Javascript事

2020-04-12 22:23:45

Kubernetes容器網(wǎng)絡(luò)

2018-02-24 13:21:02

2011-08-30 16:26:34

Hadoop

2009-09-16 13:05:32

C#組件開(kāi)發(fā)

2020-04-15 22:18:55

架構(gòu)負(fù)載均衡分布式

2019-08-02 08:59:21

Token認(rèn)證服務(wù)器

2017-04-11 08:36:09

iOS編譯應(yīng)用

2010-06-09 11:17:34

openSUSE Mp
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)