程序的靜態鏈接——鏈接和目標文件格式


今天,手機壞了,這部分的課後題忘交了,沒心情學習怎麼辦,那就聽《Artist》吧,小鬼在加油,鬼姐姐也得加油!(上週用這首歌榮獲英語展示小組第一)

這部分內容會利用gdb調試展示內部表示,所有的linux命令行在另一篇博客中展示

(由於我也是新手,寫的東西僅僅是筆記,大家還是參考網上更權威的教程進行學習)
首先我們提到了鏈接這個詞,在程序開發中,我們往往會將多個源文件和標準庫函數鏈接成一個可執行文件,這種可執行文件是一個統一的整體,可以被加載到存儲器中執行,鏈接器是將多個可重定位目標文件合成一個可執行文件的工具。

可執行文件生成概述

這部分內容主要講述如何從.c文件轉化爲可執行目標文件,以及鏈接器的主要功能

可執行文件的生成

對於hello.c文件:
在這裏插入圖片描述
前三種文件都是文本語言,都是用ASCII碼書寫的文件,機器不能識別。
😈 預處理命令
$gcc -E hello.c-o hello.i
$cpp hello.c >hello.i(cpp是預處理命令)

  • 刪除“#define"並展開定義的宏
    簡單介紹宏定義命令:類似於c++中const,用標識符代替常量或語句,預處理時將有相應標識符的地方進行替換。
#define N 100;
int t=N;//N會被直接換成100
  • 處理所有條件預編譯指令
    “#if” “#ifdef” “#endif”
    相當於if語句
  • 插入頭文件到”#include"處,採用遞歸方式處理
  • 刪除所有註釋
  • 添加行號和文件名標識,便於編譯產生調試的行號信息
  • 保留編譯指令#pragma:設定編譯器的狀態或者是指示編譯器完成一些特定的動作
    此時的文件依然是可讀的文本文件,但不包含任何宏定義

😈 編譯
通過語法分析,詞法分析,優化,生成彙編代碼,這個過程往往需要多次對文件進行掃描
$gcc -S hello.i-o hello.s
也可以直接用一個步驟
$gcc -S hello.c-o hello.s
cc1命令
😈 彙編
彙編語言源程序->二進制代碼(機器語言序列)(生成可重定位目標文件,機器不能識別)
區分機器代碼和機器級代碼
$gcc -c hello.s-o hello.o
$as hello.s-o hello.o
每個文件都要經歷上述過程
😈 鏈接
多個可重定位合併生成可執行目標文件
$gcc -static -o myproc main.o test.o
$ld -static -o myproc main.o test.o
靜態鏈接static,如果不指定-o選項,可執行文件名爲a.out

鏈接

鏈接發展史

鏈接的概念從機器語言一開始就存在
鏈接的關鍵是找出各個數據或變量之間的關係
😈 很久很久以前的機器代碼時期
跳轉時的地址直接用二進制代碼表示,如果增加指令,就需要修改很多指令的地址,紙帶就得重做,真麻煩!
😈後來啊,出現了彙編語言
用符號表示轉移目標,另一個地方引用這個標號,就能建立標號之間的關係
鏈接也就是找到各個標號的關係,再將對應的值填入具體的位置。
😈 高級!程序語言
符號定義:子程序和變量的起始地址都使用符號表示,確定定義和引用關係,然後將定義填入引入。
模塊中的定義可以被另一個模塊引用
符號定義和符號引用

鏈接操作的步驟

靜態鏈接器可以將多個可重定位目標文件合成一個可執行文件

😈 符號解析:將符號定義和符號引用建立關聯
這裏的符號指的是全局靜態變量和函數名,非靜態局部變量不需要考慮與其他重定位文件的關係,局部變量不會被函數外被引用,不屬於符號
編譯器能將所有符號放在可重定位目標文件的符號表中
以下爲重定位操作:
😈 合併代碼(可執行文件中每種類型的數據都有自己該去的代碼段)
😈 確定地址
😈 將定義處的地址填入引用的地方:這是虛擬地址空間,代碼區和數據區的地址都是從0開始的

鏈接的好處

😈 模塊化編程
😈 效率高
在可執行文件運行對應的內存中不需要包含整個共享庫,提高空間利用率。

深入討論鏈接

鏈接的本質:合併相同的節
每個可重定位目標文件中的數據和代碼都有自己的分類,鏈接就是將相應的部分合成一個新的節,然後分配地址
在這裏插入圖片描述
程序頭表:描述在磁盤上的內容如何映射到內存中,會映射到一個虛擬地址空間(地址實際上是虛擬地址)
虛擬地址中有讀寫數據段和只讀代碼段,就是由磁盤中的可執行文件相應的節組成的
在這裏插入圖片描述

目標文件格式

  • 目標代碼是機器語言代碼,目標文件是存放目標代碼的文件
  • 可執行文件存儲映像
    在這裏插入圖片描述
    開始鏈接生成文件在磁盤中。
    段頭表描述如何映射,如何將磁盤中內容映射到虛擬地址空間中。
    先映射到虛擬地址空間

三類目標文件

😈 可重定位目標文件(鏈接對象)
代碼和數據地址都是從0開始
😈 可執行目標文件
Linux:默認爲a.out window默認爲.exe
代碼和數據被複制到內存中執行
對應地址是虛擬地址空間中的地址
😈 共享目標文件:可以動態轉入內存,自動鏈接到可執行文件

ELF目標文件格式概述

鏈接視圖:(可重定位目標文件)
節是ELF文件中具有相同特徵的最小可處理單位
在這裏插入圖片描述
執行視圖(可執行目標文件)
在這裏插入圖片描述

ELF可重定位目標文件格式

主要結構 ELF頭+代碼部分+數據部分+節頭表
😈 節頭表:包含文件中各節的說明信息節的節名,偏移和大小
定義節頭表所用的是struct類型,可以根據單個struct類型字節數和定義的節數確定節頭表所佔字節數
節頭表不一定在磁盤最後,根據ELF頭中的數據判斷節頭表的首地址
在這裏插入圖片描述
跳轉順序:elf頭→節頭表→各種節
😈 ELF頭:

  • 位於目標文件的起始位置,包含文件結構說明信息,分爲32位對應結構和64位對應結構。
  • 佔52個字節在這裏插入圖片描述
  • 文件開始的幾個字節爲魔數,確定文件類型和格式,加載文件時通過魔數判斷文件類型是否正確
    在ELF格式下,最開始有個16字節序列,最開始的4字節爲魔數

Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00

  • e_type表明了文件的類型(可重定位?還是可執行)
  • 定義了程序頭表和節頭表的位置和長度

😈 .text
😈 rodata:跳轉表
😈 .data:已初始化的全局變量,有具體的值,需要磁盤空間
😈 .bss:未初始化的全局變量和靜態局部變量,默認值爲0,僅僅是一個佔位符,會在內存中分配空間,但是磁盤中不會有空間,節頭表中說明需要預留的空間
😈其他節

  • symtab:符號表節:函數名和全局變量信息,不包括非靜態局部變量
  • rel.text節:用於修改text的地址值
  • rel-data節:修改data的地址值

ELF可執行目標文件格式

在這裏插入圖片描述

  • 在ELF頭中給出執行程序第一條指令的地址,在可重定位中該值爲0
  • 程序頭表(段頭表):結構數組
    將節分段,描述節和段的關係
    說明段的信息
  • .init:定義init函數,對執行目標文件進行初始化
  • 沒有.rel節:無需重定位
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章