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

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

將C/C++語言編寫的程序轉換成為處理器能夠執行的二進制代碼的過程

馬哥Linux運維 ? 來源:人人都是極客 ? 作者:布道師Peter ? 2022-05-18 13:30 ? 次閱讀

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

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

  • 預處理(Preprocessing)

  • 編譯(Compilation)

  • 匯編(Assembly)

  • 鏈接(Linking)

b103fe62-d5e9-11ec-bce3-dac502259ad0.png

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! 
");
 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!" "
");
 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:調試符號表,調試器用此段的信息幫助調試。

b1145460-d5e9-11ec-bce3-dac502259ad0.jpg

可以使用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!" " "); 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 程序編譯過程詳解

文章出處:【微信公眾號:馬哥Linux運維】歡迎添加關注!文章轉載請注明出處。

審核編輯:湯梓紅
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 處理器
    +關注

    關注

    68

    文章

    19409

    瀏覽量

    231195
  • Linux
    +關注

    關注

    87

    文章

    11345

    瀏覽量

    210405
  • 程序
    +關注

    關注

    117

    文章

    3796

    瀏覽量

    81418

原文標題:Linux 程序編譯過程詳解

文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    二進制加權數模轉換器講解

    二進制加權數模轉換器是一種數字二進制轉換成與數字數值成比例的等效模擬輸出信號的數據轉換器
    發表于 06-29 11:34 ?1745次閱讀
    <b class='flag-5'>二進制</b>加權數模<b class='flag-5'>轉換器</b>講解

    鴻蒙二進制數組創建

    背景 c++層數據都是二進制,需要轉換成arrayBuffer透傳到ets層給業務使用,但是鴻蒙的使用下面兩個api創建出來的二進制數組數據都是錯誤的。 接口
    的頭像 發表于 01-31 15:24 ?1342次閱讀

    用FPGA設計BCD碼轉換成二進制數電路

    用FPGA設計BCD碼轉換成二進制數電路
    發表于 03-11 20:37

    怎么16位二進制轉換成進制數的函數?

    有沒有人建立一個16位二進制轉換成進制數的函數?謝謝,JB 以上來自于谷歌翻譯 以下為原文Has anyone built a function(s) that convert
    發表于 03-26 11:21

    如何把十進制轉換成二進制

    轉換成二進制,然后二進制字符串與從時鐘輸入的二進制字符串進行比較。引腳12將是一個蜂鳴器的輸出,而引腳13將是復位引腳。誰能告訴我正確的方
    發表于 10-14 11:28

    如何利用codermatlab中的程序轉換成C/C++

    利用codermatlab中的程序轉換成C/C++眾所周知,matlab的功能是非常強大的,簡便易于操作的工具包更是非常的方便。為機器學習
    發表于 08-17 06:56

    二進制轉十六進制中間是如何轉換的呢

    。我們使用的方法只是把如同:00011101這類的端口數據直接轉換成了16進制,中間是如何轉換的呢,就是編程語言中最常見的二進制轉十六
    發表于 02-24 07:40

    二進制與BCD碼轉換資料

    6位二進制轉換成BCD碼的的快速算法-51單片機2010-02-18 00:43在做而論道上篇博文中,回答了一個16位二進制轉換成BCD碼的問題,給出了一個網上廣泛流傳的經典
    發表于 08-19 17:42 ?96次下載

    具有X86到ARM二進制翻譯和執行功能的SoC系統設計

      二進制翻譯是一種直接翻譯可執行二進制程序的技術,能夠把一種處理器上的二進制程序翻譯到另外一種
    發表于 09-07 10:22 ?2186次閱讀
    具有X86到ARM<b class='flag-5'>二進制</b>翻譯和<b class='flag-5'>執行</b>功能的SoC系統設計

    二進制加法程序C語言版】

    二進制加法程序C語言版】二進制加法程序C
    發表于 12-29 11:03 ?0次下載

    二進制加法程序C語言+匯編版】

    二進制加法程序C語言+匯編版】,多種集合,符合同時愛好C語言+匯編的學習者的胃口。
    發表于 01-06 11:10 ?0次下載

    C語言教程之十進制轉換二進制

    C語言教程之十進制轉換二進制,很好的C語言資料,快
    發表于 04-22 11:06 ?0次下載

    如何高層的C/C++語言編寫轉換成二進制代碼編譯

    靜態庫和動態庫的不同點在于代碼被載入的時刻不同。靜態庫的代碼在編譯過程中已經被載入可執行程序,因此體積較大。共享庫的代碼是在可
    發表于 02-02 11:02 ?1451次閱讀

    二進制編碼-十進制編碼講解

    信息(如數和字符等)轉換成符合一定規則的二進制代碼
    的頭像 發表于 03-21 11:55 ?1.4w次閱讀
    <b class='flag-5'>二進制</b>編碼<b class='flag-5'>器</b>和<b class='flag-5'>二</b>-十<b class='flag-5'>進制</b>編碼<b class='flag-5'>器</b>講解

    C語言生成可執行二進制文件的具體過程

    C語言源碼到生成可執行文件的過程通常包括預處理(Preprocessing)、編譯(Compilation)、匯編(Assembly)、鏈接
    的頭像 發表于 10-21 14:30 ?697次閱讀
    <b class='flag-5'>C</b><b class='flag-5'>語言</b>生成可<b class='flag-5'>執行</b><b class='flag-5'>二進制</b>文件的具體<b class='flag-5'>過程</b>
    游戏百家乐官网押金| 真人百家乐技巧| 网上百家乐娱乐网| 天水市| Bet百家乐官网娱乐城| 威尼斯人娱乐场安全吗| 澳门百家乐官网大小| 做生意什么花招财| 波克棋牌官网| 大三元百家乐官网的玩法技巧和规则 | 百家乐3式打法微笑心法| 澳门百家乐官网真人版| 威尼斯人娱乐城003| 揭秘百家乐官网百分之50| 大发888娱乐场解码器| 女神百家乐官网的玩法技巧和规则| bet365体育在线下载| 百家乐免费破解外挂| 大发888游戏平台| 24山天机申山寅向择日| 澳门百家乐官方网站| 游戏房百家乐赌博图片| 百家乐官网国际娱乐城| 正品百家乐的玩法技巧和规则 | 长乐市| 百家乐能破解| 百家乐官网客户端软件| 网上百家乐内| 网络百家乐官网漏洞| 大发888亚洲| 杨公风水24山分金水法| 来博百家乐官网游戏| 百家乐和怎么算输赢| KTV百家乐官网的玩法技巧和规则| 百家乐全自动分析软件| 百家乐官网真人娱乐注册| 大发888娱乐城ipad| 百家乐视频麻将| 百家乐官网庄闲的冷热| 大发888线上娱乐城| 最新百家乐游戏机|