<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Linux 程序編譯過程的來龍去脈

          共 7229字,需瀏覽 15分鐘

           ·

          2022-06-08 23:53

          在公眾號(hào)后臺(tái)回復(fù):JGNB,可獲取杰哥原創(chuàng)的 PDF 手冊(cè)。

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


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

          • 預(yù)處理(Preprocessing)

          • 編譯(Compilation)

          • 匯編(Assembly)

          • 鏈接(Linking)


          GCC 工具鏈介紹

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


          GCC

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


          Binutils

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

          • addr2line:用來將程序地址轉(zhuǎn)換成其所對(duì)應(yīng)的程序源文件及所對(duì)應(yīng)的代碼行,也可以得到所對(duì)應(yīng)的函數(shù)。該工具將幫助調(diào)試器在調(diào)試的過程中定位對(duì)應(yīng)的源代碼位置。

          • as:主要用于匯編,有關(guān)匯編的詳細(xì)介紹請(qǐng)參見后文。

          • ld:主要用于鏈接,有關(guān)鏈接的詳細(xì)介紹請(qǐng)參見后文。

          • ar:主要用于創(chuàng)建靜態(tài)庫。為了便于初學(xué)者理解,在此介紹動(dòng)態(tài)庫與靜態(tài)庫的概念:

            • 如果要將多個(gè).o目標(biāo)文件生成一個(gè)庫文件,則存在兩種類型的庫,一種是靜態(tài)庫,另一種是動(dòng)態(tài)庫。

            • 在windows中靜態(tài)庫是以 .lib 為后綴的文件,共享庫是以 .dll 為后綴的文件。在linux中靜態(tài)庫是以.a為后綴的文件,共享庫是以.so為后綴的文件。

            • 靜態(tài)庫和動(dòng)態(tài)庫的不同點(diǎn)在于代碼被載入的時(shí)刻不同。靜態(tài)庫的代碼在編譯過程中已經(jīng)被載入可執(zhí)行程序,因此體積較大。共享庫的代碼是在可執(zhí)行程序運(yùn)行時(shí)才載入內(nèi)存的,在編譯過程中僅簡單的引用,因此代碼體積較小。在Linux系統(tǒng)中,可以用ldd命令查看一個(gè)可執(zhí)行程序依賴的共享庫。

            • 如果一個(gè)系統(tǒng)中存在多個(gè)需要同時(shí)運(yùn)行的程序且這些程序之間存在共享庫,那么采用動(dòng)態(tài)庫的形式將更節(jié)省內(nèi)存。

          • ldd:可以用于查看一個(gè)可執(zhí)行程序依賴的共享庫。

          • objcopy:將一種對(duì)象文件翻譯成另一種格式,譬如將.bin轉(zhuǎn)換成.elf、或者將.elf轉(zhuǎn)換成.bin等。

          • objdump:主要的作用是反匯編。有關(guān)反匯編的詳細(xì)介紹,請(qǐng)參見后文。

          • readelf:顯示有關(guān)ELF文件的信息,請(qǐng)參見后文了解更多信息。

          • size:列出可執(zhí)行文件每個(gè)部分的尺寸和總尺寸,代碼段、數(shù)據(jù)段、總大小等,請(qǐng)參見后文了解使用size的具體使用實(shí)例。


          C運(yùn)行庫

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

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

          準(zhǔn)備工作

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

          #include  

          //此程序很簡單,僅僅打印一個(gè)Hello World的字符串。
          int main(void)
          {
          ?printf("Hello World! \n");
          ?return 0;
          }


          編譯過程

          1.預(yù)處理

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

          • 將所有的#define刪除,并且展開所有的宏定義,并且處理所有的條件預(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ù)編譯過程需要使用它們。
            使用gcc進(jìn)行預(yù)處理的命令如下:

          $ gcc -E hello.c -o hello.i // 將源文件hello.c文件預(yù)處理生成hello.i
          ? ? ? ? ? ? ? ? ? ? ? ?// GCC的選項(xiàng)-E使GCC在進(jìn)行完預(yù)處理后即停止

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

          // 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.編譯

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

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

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

          上述命令生成的匯編程序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.匯編

          匯編過程調(diào)用對(duì)匯編代碼進(jìn)行處理,生成處理器能識(shí)別的指令,保存在后綴為.o的目標(biāo)文件中。由于每一個(gè)匯編語句幾乎都對(duì)應(yīng)一條處理器指令,因此,匯編相對(duì)于編譯過程比較簡單,通過調(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)行匯編的命令如下:

          $ gcc -c hello.s -o hello.o // 將編譯生成的hello.s文件匯編生成目標(biāo)文件hello.o
          ? ? ? ? ? ? ? ? ? ? ? ?// GCC的選項(xiàng)-c使GCC在執(zhí)行完匯編后停止,生成目標(biāo)文件
          //或者直接調(diào)用as進(jìn)行匯編
          $ 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)庫加入到可執(zhí)行文件中去,這樣可執(zhí)行文件會(huì)比較大。鏈接器將函數(shù)的代碼從其所在地(不同的目標(biāo)文件或靜態(tài)鏈接庫中)拷貝到最終的可執(zhí)行程序中。為創(chuàng)建可執(zhí)行文件,鏈接器必須要完成的主要任務(wù)是:符號(hào)解析(把目標(biāo)文件中符號(hào)的定義和引用聯(lián)系起來)和重定位(把符號(hào)定義和內(nèi)存地址對(duì)應(yīng)起來然后修改所有對(duì)符號(hào)的引用)。

          • 動(dòng)態(tài)鏈接則是指鏈接階段僅僅只加入一些描述信息,而程序執(zhí)行時(shí)再從系統(tǒng)中把相應(yīng)動(dòng)態(tài)庫加載到內(nèi)存中去。

            • 在Linux系統(tǒng)中,gcc編譯鏈接時(shí)的動(dòng)態(tài)庫搜索路徑的順序通常為:首先從gcc命令的參數(shù)-L指定的路徑尋找;再從環(huán)境變量LIBRARY_PATH指定的路徑尋址;再從默認(rèn)路徑/lib、/usr/lib、/usr/local/lib尋找。

            • 在Linux系統(tǒng)中,執(zhí)行二進(jìn)制文件時(shí)的動(dòng)態(tài)庫搜索路徑的順序通常為:首先搜索編譯目標(biāo)代碼時(shí)指定的動(dòng)態(tài)庫搜索路徑;再從環(huán)境變量LD_LIBRARY_PATH指定的路徑尋址;再從配置文件/etc/ld.so.conf中指定的動(dòng)態(tài)庫搜索路徑;再從默認(rèn)路徑/lib、/usr/lib尋找。

            • 在Linux系統(tǒng)中,可以用ldd命令查看一個(gè)可執(zhí)行程序依賴的共享庫。


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

          • 如果使用命令“gcc hello.c -o hello”則會(huì)使用動(dòng)態(tài)庫進(jìn)行鏈接,生成的ELF可執(zhí)行文件的大小(使用Binutils的size命令查看)和鏈接的動(dòng)態(tài)庫(使用Binutils的ldd命令查看)如下所示:

            $ gcc hello.c -o hello
            $ size hello ?//使用size查看大小
            ? text ? ?data ? ? bss ? ? dec ? ? hex filename
            ? 1183 ? ? 552 ? ? ? 8 ? ?1743 ? ? 6cf ? ? hello
            $ ldd hello //可以看出該可執(zhí)行文件鏈接了很多其他動(dòng)態(tài)庫,主要是Linux的glibc動(dòng)態(tài)庫
            ? ? ? ?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”則會(huì)使用靜態(tài)庫進(jìn)行鏈接,生成的ELF可執(zhí)行文件的大小(使用Binutils的size命令查看)和鏈接的動(dòng)態(tài)庫(使用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 //說明沒有鏈接動(dòng)態(tài)庫

          鏈接器鏈接后生成的最終文件為ELF格式可執(zhí)行文件,一個(gè)ELF可執(zhí)行文件通常被鏈接為不同的段,常見的段譬如.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的信息如下:

          $ 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文件無法被當(dāng)做普通文本文件打開,如果希望直接查看一個(gè)ELF文件包含的指令和數(shù)據(jù),需要使用反匯編的方法。

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

          $ objdump -D hello
          ……
          0000000000400526
          : ?// main標(biāo)簽的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選項(xiàng)
          $ 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)
          ……

          來源:人人都是極客?

          鏈接:mp.weixin.qq.com/s/rLRXNY4EccBnJ5XVyjhjAA

          推薦閱讀:

          Linux 操作必備 150 個(gè)命令,速度收藏~


          怎樣才能讓 Linux 文檔的顏色高亮?


          如何在 Bash 腳本中使用強(qiáng)大的 Linux test 命令


          如何用 10 行 bash shell 腳本監(jiān)控 Linux?


          如何監(jiān)測 Linux 的磁盤 I/O 性能

          瀏覽 28
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲日韩网站在线观看 | 91日韩无码 | 国产在线一区视频 | 91免费三级片 | 色先锋AV |