《程序員的自我修養》(一)——編譯與靜態連接

簡介

溫故而知新

  • 計算機科學領域的任何問題均可以經過增長一個間接地中間層來解決。
  • 在UNIX中,硬件設備的訪問形式跟訪問普通的文件形式同樣;在Windows系統中,圖形硬件被抽象成了GDI,聲音和多媒體設備被抽象成了DirectX對象,磁盤被抽象成了普通文件系統,等等。
  • 如何將計算機上有限的物理內存分配給多個程序使用。整個想法是這樣的,咱們把程序給出的地址看做是一種虛擬地址,而後經過某些映射的方法,將這個虛擬地址轉換成實際的物理地址。
  • 進程的映射方式:算法

    • 分段,基本思路是把一段與程序須要的內存大小的虛擬空間映射到某個地址空間。
    • 分頁,基本方法是把地址空間人爲地等分紅固定大小的頁,每一頁的大小又硬件決定,或硬件支持多種大小的頁,由操做系統選擇決定頁的大小。目前幾乎全部的PC上的操做系統都使用4KB大小的頁。幾乎全部的硬件都是採用一個叫MMU的部件來進行頁映射。
    • 在頁映射模式下,CPU發出的Virtual Address,即咱們的程序看到的是虛擬地址。通過MMU轉換之後就變成了Physical Address。通常MMU都集成在CPU內部了,不會以獨立的部件存在。

靜態連接

編譯和連接

  • 預編譯(預處理):預編譯過程主要處理那些源代碼文件中的以「#」開始的預編譯指令。C文件預編譯後造成.i文件,C++文件編譯後擴展名是.ii。
  • 編譯:編譯過程就是把預處理完的文件進行一系列詞法分析、語法分析、語義分析及優化後生產相應的彙編代碼文件,編譯後生成.s文件。
  • 彙編:彙編器是將彙編代碼轉變成機器能夠執行的指令,通過彙編後生成.o文件。
  • 連接:連接的主要內容就是把各個模塊之間相互引用的部分都處理好,使得各個模塊之間可以正確地銜接。連接過程主要包括了地址和空間分配、符號決議和重定位等步驟。最終生成可執行文件。

  • 編譯過程通常能夠分爲6步:掃描、語法分析、語義分析、源代碼優化、代碼生成和目標代碼優化。數組

    • 詞法分析:首先源代碼程序被輸入到掃描器,掃描器的任務很簡單,它只是簡單地進行詞法分析,運用一種相似於有限狀態機的算法能夠很輕鬆地將源代碼的字符序列分割成一系列的記號。
    • 語法分析:接下來語法分析器將對由掃描器產生的記號進行語法分析,從而產生語法樹(以表達式爲節點的樹)。
    • 語義分析:編譯器所能分析的語義是靜態語義,所謂靜態語義是指在編譯期能夠肯定的語義,靜態語義一般包括聲明和類型的匹配,類型的轉換。
    • 中間語言生成:現代的編譯器有着不少層次的優化,每每在源代碼級別會有一個優化過程。源代碼優化器每每將整個語法樹轉換成中間代碼,它是語法樹的順序表示,其實它已經很是接近目標代碼了。
    • 目標代碼生成與優化:代碼生成器將中間代碼轉換成目標機器代碼,這個過程十分依賴於目標機器,由於不一樣的機器有着不一樣的字長、寄存器、整數數據類型和浮點數數據類型等。最後目標代碼優化器對上述的目標代碼進行優化,好比選擇合適的尋址方式、使用位移來代替乘法運算、刪除多餘的指令等。

目標文件裏有什麼

  • 如今PC平臺流行的可執行文件格式主要是Windows下的PE(Portable Executable)和Linux的ELF(Executable Linkable Format),它們都是COFF(Common file format)格式的變種。
  • 目標文件中包括機器指令代碼、數據、符號表、調試信息、字符串等。通常目標文件將這些信息按不一樣屬性,以「段」的形式存儲。
  • 程序源代碼編譯後的機器指令常常被放在代碼段裏,代碼段常見的名字有「.code」或「.text」;全局變量和局部靜態變量數據常常放在數據段,數據段的通常名字都叫「.data」。
  • .bss段只是爲未初始化的全局變量和局部靜態變量預留位置而已,它並無內容,因此它在文件中也不佔據空間

  • ELF目標文件的文件頭描述了整個文件的基本屬性,包括文件是否可執行、是靜態連接仍是動態連接及入口地址、目標硬件、目標操做系統、程序入口地址等。緊接着就是ELF文件各個段,後面是與段有關的重要結構——段表,段表實際上是一個描述文件中各個段的數組。段表描述了文件中各個段的段名、段的長度、在文件中的偏移位置、讀寫權限及段的其餘屬性等。

函數

  • 編譯器還會將一些輔助性的信息,諸如符號、重定位信息等也按照段的方式存放在目標文件中。

靜態連接

  • 如今的連接器空間分配的策略都是採用一種叫兩步連接的方法。佈局

    • 第一步,空間與地址分配。掃描全部的輸入目標文件,得到它們的各個段的長度、屬性和位置,而且將輸入目標中的符號表中全部的符號定義和符號引用收集起來,統一放到一個全局符號表。
    • 第二步,符號解析與重定位。使用上面第一步中收集到的全部信息,讀取輸入文件中段的數據、重定位信息,而且進行符號解析與重定位、調整代碼中的地址等。這一步是連接過程的核心,特別是重定位過程。
  • 對於可重定位的ELF文件來講,它必須包含有重定位表,用來描述如何修改相應的段裏的內容。對於每一個要重定位的ELF段都有一個對應的重定位表,而一個重定位表每每就是ELF文件中的一個段,因此其實重定位表也能夠叫重定位段。
  • 連接器的COMMON塊機制解決同一個符號定義在多個文件中的問題(目前的連接器自己並不支持符號的類型,即變量類型對於連接器來講是透明的,它只知道一個符號的名字,並不知道類型是否一致)。這個問題解決的規則是,若是是弱符號(未初始化的全局變量)則在最終連接後的輸出文件中,該符號所指的變量大小以輸入文件中最大的那個爲準。若是其中有一個符號爲強符號,那麼最終輸出結果中的符號所佔空間與強符號相同。
  • 若是要使兩個編譯器編譯出來的目標文件可以互相連接,那麼這兩個目標文件必須知足下面這些條件:採用一樣的目標文件格式、擁有一樣的符號修飾標準、變量的內存分佈方式相同、函數的調用方式相同,等等。其中咱們把符號修飾標準、變量內存佈局、函數調用方式等這些跟可執行代碼二進制兼容性相關的內容稱爲ABI(Application Binary Interface)。
  • 一個靜態庫能夠簡單地當作一組目標文件的集合,即不少目標文件通過壓縮打包後造成的一個文件。
  • VISUAL C++容許使用腳原本控制整個連接過程,這種控制腳本叫作模塊定義文件,它們的擴展名通常爲.def。

Windows PE/COFF

  • 在Windows平臺,VISUAL C++編譯器產生的目標文件使用COFF格式,而可執行文件爲PE格式。微軟對64位Windows平臺上的PE文件結構稍微作了一些修改,這個新的文件格式叫作PE32+。新的PE32+並無添加任何結構,最大的變化就是把那些原來32位的字段變成64位。
  • COFF文件是由文件頭及後面的若干個段組成,再加上文件末尾的符號表、調試信息的內容,就構成了COFF文件的基本結構。COFF文件的文件頭部包括了兩部分,一個是描述文件整體結構和屬性的映像頭,另一個是描述該文件中包含的段屬性的段表。
  • 「.drectve 段」其實是「Directive」的縮寫,它的內容是編譯器傳遞給連接器的指令,即編譯器但願告訴連接器應該怎樣連接這個目標文件。
  • COFF文件中全部以「.debug」開始的段都包含着調試信息。好比「.debug$S」表示包含的是符號相關的調試信息段;「.debug$P」表示包含預編譯頭文件相關的調試信息段;「.debug$T」表示包含類型相關的調試信息段。
  • PE文件是基於COFF的擴展,它比COFF文件多了幾個結構。最主要的變化有兩個:第一個是文件最開始的部分不是COFF文件頭,而是DOS MS可執行文件格式的文件頭和樁代碼;第二個變化是原來的COFF文件頭中的「IMAGE_FILE_HEADER」部分擴展成了PE文件頭結構「IMAGE_NT_HEADERS」,這個結構包括了原來的「Image Header」及新增的PE擴展頭部結構。

相關文章
相關標籤/搜索