衡阳派盒市场营销有限公司

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Linux程序編譯過程分析

jf_78858299 ? 來源:CSDN ? 作者:CRMEB定制開發(fā) ? 2023-05-12 14:55 ? 次閱讀

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

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

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

圖片

GCC 工具鏈介紹

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

GCC

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

Binutils

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

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

C運行庫

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

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

準(zhǔn)備工作

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

#include  

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

編譯過程

1.預(yù)處理

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

  • 將所有的#define刪除,并且展開所有的宏定義,并且處理所有的條件預(yù)編譯指令,比如#if #ifdef #elif #else #endif等。
  • 處理#include預(yù)編譯指令,將被包含的文件插入到該預(yù)編譯指令的位置。
  • 刪除所有注釋“//”和“/* */”。
  • 添加行號和文件標(biāo)識,以便編譯時產(chǎn)生調(diào)試用的行號及編譯錯誤警告行號。
  • 保留所有的#pragma編譯器指令,后續(xù)編譯過程需要使用它們。

使用gcc進行預(yù)處理的命令如下:

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

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

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

使用gcc進行編譯的命令如下:

$ gcc -S hello.i -o hello.s // 將預(yù)處理生成的hello.i文件編譯生成匯編程序hello.s
                        // GCC的選項-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)用對匯編代碼進行處理,生成處理器能識別的指令,保存在后綴為.o的目標(biāo)文件中。由于每一個匯編語句幾乎都對應(yīng)一條處理器指令,因此,匯編相對于編譯過程比較簡單,通過調(diào)用Binutils中的匯編器as根據(jù)匯編指令和處理器指令的對照表一一翻譯即可。

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

使用gcc進行匯編的命令如下:

$ gcc -c hello.s -o hello.o // 將編譯生成的hello.s文件匯編生成目標(biāo)文件hello.o
                        // GCC的選項-c使GCC在執(zhí)行完匯編后停止,生成目標(biāo)文件
//或者直接調(diào)用as進行匯編
$ 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)鏈接和動態(tài)鏈接,其要點如下:

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

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

  • 如果使用命令“gcc hello.c -o hello”則會使用動態(tài)庫進行鏈接,生成的ELF可執(zhí)行文件的大小(使用Binutils的size命令查看)和鏈接的動態(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í)行文件鏈接了很多其他動態(tài)庫,主要是Linux的glibc動態(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”則會使用靜態(tài)庫進行鏈接,生成的ELF可執(zhí)行文件的大小(使用Binutils的size命令查看)和鏈接的動態(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 //說明沒有鏈接動態(tài)庫

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

分析ELF文件

1.ELF文件的段

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

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

圖片

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

使用objdump -D對其進行反匯編如下:

$ 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

使用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

鏈接:https://blog.csdn.net/qq_39221436/article/details/125638972

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • matlab
    +關(guān)注

    關(guān)注

    185

    文章

    2981

    瀏覽量

    231017
  • 匯編語言
    +關(guān)注

    關(guān)注

    14

    文章

    410

    瀏覽量

    35962
  • python
    +關(guān)注

    關(guān)注

    56

    文章

    4807

    瀏覽量

    85041
  • 機器語言
    +關(guān)注

    關(guān)注

    0

    文章

    35

    瀏覽量

    10776
  • ruby
    +關(guān)注

    關(guān)注

    0

    文章

    44

    瀏覽量

    3467
收藏 人收藏

    評論

    相關(guān)推薦

    Linux編譯驅(qū)動、內(nèi)核及應(yīng)用程序分析

    作為一名嵌入式Linux新手,在學(xué)習(xí)的過程中會遇到很多問題。寫了一個驅(qū)動程序怎么編譯?怎么加載進內(nèi)核?
    的頭像 發(fā)表于 01-17 13:46 ?6706次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>編譯</b>驅(qū)動、內(nèi)核及應(yīng)用<b class='flag-5'>程序</b><b class='flag-5'>分析</b>

    如何編譯Linux Kernel

    整個Linux內(nèi)核編譯過程非常簡單,但是內(nèi)核編譯需要花費很長的時間。因為Linux內(nèi)核的代碼非常多。當(dāng)然,如果你的計算機性能強勁,時間會短
    發(fā)表于 06-07 16:26 ?1.1w次閱讀
    如何<b class='flag-5'>編譯</b><b class='flag-5'>Linux</b> Kernel

    C語言的編譯過程

    C語言的編譯鏈接過程要把我們編寫的一個C程序源代碼,轉(zhuǎn)換成可以在硬件上運行的程序(可執(zhí)行代碼),需要進行編譯和鏈接。
    發(fā)表于 06-25 10:36 ?435次閱讀
    C語言的<b class='flag-5'>編譯</b><b class='flag-5'>過程</b>

    Linux內(nèi)核的編譯主要過程

    Linux內(nèi)核的編譯主要過程: 配置、編譯、安裝 。
    發(fā)表于 08-08 16:02 ?768次閱讀
    <b class='flag-5'>Linux</b>內(nèi)核的<b class='flag-5'>編譯</b>主要<b class='flag-5'>過程</b>

    C程序的完整編譯過程

    本文討論了C程序的完整編譯過程,分別講述了預(yù)處理、編譯、匯編、鏈接各階段完成的編譯任務(wù)。然后通過一個編譯
    的頭像 發(fā)表于 11-15 17:14 ?3664次閱讀
    C<b class='flag-5'>程序</b>的完整<b class='flag-5'>編譯</b><b class='flag-5'>過程</b>

    如何構(gòu)建linux開發(fā)環(huán)境和編譯軟件工程、應(yīng)用程序

    前文介紹了如何使用官方提供的鏡像文件啟動開發(fā)板,本文將說明如何構(gòu)建linux開發(fā)環(huán)境和編譯軟件工程、應(yīng)用程序
    的頭像 發(fā)表于 01-03 12:31 ?2190次閱讀
    如何構(gòu)建<b class='flag-5'>linux</b>開發(fā)環(huán)境和<b class='flag-5'>編譯</b>軟件工程、應(yīng)用<b class='flag-5'>程序</b>

    Linux使用VScode編譯調(diào)試C/C++程序過程是怎樣的

    如何安裝VScode?Linux使用VScode編譯調(diào)試C/C++程序過程是怎樣的
    發(fā)表于 12-24 06:49

    Linux內(nèi)核編譯過程詳解

    Linux內(nèi)核編譯過程詳解(kernel2.6.7) 花了幾天才編譯成功kernel2.6.7,其過程真可謂艱辛。古語有云:苦盡甘來!現(xiàn)在終
    發(fā)表于 11-07 11:16 ?4次下載

    Linux系統(tǒng)ELF程序的執(zhí)行過程

    我們知道在linux系統(tǒng)中可以通過諸如"./debug"方式執(zhí)行一個程序,那么這個程序的執(zhí)行過程linux系統(tǒng)都做了什么?
    發(fā)表于 04-27 19:48 ?3489次閱讀

    Linux內(nèi)核配置編譯分析的設(shè)計方案

    Linux內(nèi)核配置編譯分析的設(shè)計方案
    發(fā)表于 07-08 16:53 ?18次下載
    <b class='flag-5'>Linux</b>內(nèi)核配置<b class='flag-5'>編譯</b><b class='flag-5'>分析</b>的設(shè)計方案

    嵌入式linux編譯 ko,嵌入式linux:編譯linux驅(qū)動模塊

    是如何別被構(gòu)造的。模塊的構(gòu)造過程與用戶空間的應(yīng)用程序的構(gòu)造過程有顯著不同;內(nèi)核是一個大的、獨立的程序,對于它的各個部分如何組合在一起有詳細(xì)的明確的要求。
    發(fā)表于 11-01 16:31 ?9次下載
    嵌入式<b class='flag-5'>linux</b><b class='flag-5'>編譯</b> ko,嵌入式<b class='flag-5'>linux</b>:<b class='flag-5'>編譯</b><b class='flag-5'>linux</b>驅(qū)動模塊

    嵌入式linux一個簡單的hello程序編譯及運行示例

    linux系統(tǒng)上面,這種在pc上編譯嵌入式linux程序的方式稱為交叉編譯,嵌入式linux
    發(fā)表于 11-01 16:56 ?9次下載
    嵌入式<b class='flag-5'>linux</b>一個簡單的hello<b class='flag-5'>程序</b><b class='flag-5'>編譯</b>及運行示例

    Linux編譯燒錄51單片機程序

    Linux編譯燒錄51單片機程序
    發(fā)表于 11-21 14:36 ?14次下載
    <b class='flag-5'>Linux</b><b class='flag-5'>編譯</b>燒錄51單片機<b class='flag-5'>程序</b>

    Linux使用gcc編譯程序的語法

    01. 調(diào)試相關(guān)的宏 在Linux使用gcc編譯程序的時候,對于調(diào)試的語句還具有一些特殊的語法。 gcc編譯過程中,會生成一些宏,可以使用這些宏分別打印當(dāng)前源文件的信息,主要內(nèi)容是當(dāng)
    的頭像 發(fā)表于 06-22 10:51 ?758次閱讀

    linux驅(qū)動程序編譯方法是什么

    Linux驅(qū)動程序編譯方法主要包括兩種: 與內(nèi)核一起編譯編譯成獨立的內(nèi)核模塊 。以下是對這兩種方法的介紹: 一、與內(nèi)核一起
    的頭像 發(fā)表于 08-30 14:46 ?765次閱讀
    金塔县| 百利宫百家乐的玩法技巧和规则| 百家乐官网打印程序| 扑克百家乐赌器| 澳门百家乐官网必胜看路| 全讯网博客| 乐天百家乐官网的玩法技巧和规则| 七匹狼娱乐城开户| 百家乐投注技巧建议| 百家乐官网网上最好网站| 免费百家乐过滤| 博彩百家乐官网字谜总汇二丹东| 志丹县| 华盛顿百家乐的玩法技巧和规则 | 百家乐官网娱乐城代理| 百家乐园试玩| 至尊百家乐官网2012| 大发888娱乐真钱游戏下载| 网上百家乐哪里开户| 百家乐官网网络赌博地址| 百家乐官网群shozo权威| 香河县| 大发888组件下载| 任你博百家乐官网的玩法技巧和规则| 新沂市| 新塘太阳城巧克力| 网站百家乐博彩| 百家乐官网baccarat| 凯时百家乐官网技巧| 碧桂园太阳城二手房| 百家乐视频多开器| 百家乐官网之三姐妹赌博机| 赌博的危害| 沙龙百家乐代理| 永利高百家乐会员| 百家乐官网平六亿财富网| 百家乐代理| 大发888怎么申请账号| 赌场百家乐代理| 网上现金棋牌| 大发888手机版客户端|