gcc與obj檔案,動態連結檔案和ELF檔案

來源:互聯網
上載者:User

1、obj檔案

      程式員編寫程式,其實就是編寫出一個2進位(binary)檔案。假如我們聲明一個變數char c,也就是聲明需要一個8bit的空間,那麼就需要向系統聲明豫留8bit的空間,怎麼做到這一點呢?就是編譯一個特殊的2進位檔案--obj檔案,用gcc編譯的C語言得到的執行檔案,裡面不僅包含CPU指令,還有很多別的資訊在裡面,它有很多格式COFF、ELF……等等,在最後一道編譯過程中,連結器(linker)ld會載入一堆資訊進入可執行檔。例如,當有多個編譯後等待連結的.o這種可重定位(relocatable)檔案,既然這些檔案裡面參數或者函數名的相對位置只是本身所在.o檔案的相對位置,就有一些資訊要告訴連結編輯器(link
editor)怎麼修改section的內容,來做relocate,也就是做地址的重新參照以便合成一個新的可執行檔。

      一個obj檔案有兩個重要時期,一個是正在連結(link)的時候,也就是處在

硬碟(disk)裡的時候;一個是正在執行的時候,當然這時它位於記憶體裡。我們平時說的ld linker其實叫link editor,最後編譯的步驟ld把該有的資訊寫進可執行檔。如果是static link就會去找libxxx.a的函數庫檔案,把想要的程式碼片段拷貝一份進可執行檔,並且做成relocation後,把跳來跳去的參照寫進可執行檔,這個檔案就可以執行。

2、動態連結檔案

      相對於靜態連結(static link)拷貝原有的程式碼進可執行檔,動態連結不那樣做,link editor把一些資訊寫進可執行檔而已。例如,需要的程式庫名、函數名等,最後執行的時候,必須呼叫dynamic linker來做program

intepreter,dynamic linker會根據需要的函數庫名稱,把想要的函數名字創造一個可執行檔image放到記憶體,所以執行有動態連結的執行檔案,最後通常都是由OS的exec系列的system call與dynamic linker如ld.so聯合完成。

dynamic linker通常會做如下工作:

(1)把可執行檔的內容載入到process image

(2)把shared obj需要的東西載入到process image

(3)完成relocation

       本來這些obj檔案裡面的虛擬位址應該和檔案的地址有相對應的位移(offset),而檔案首地址通常是0x08040800,這是絕對虛擬位址,但它只適合可執行檔,例如Linux extuable file通常是:

          file offset         virtual  address

          -----------         ----------------

          0x0                 0x08048000

          0x100             0x08048100

       shared obj函數庫裡的程式碼必須為位置無關代碼Position

Independent Code (PIC),也就是說它的地址可能會隨不同process而有不同,例如,一個程式只用了libc.so、ld-linux.so,通常這時候lib.so是從0x40017000開始的,但如果另一個程式多用一個libm.so,那麼libc.so從0x40034000開始兩個的printf參照(reference),就會有不同的地址,所以這種動態函數庫的內部資料就要說明這些code是PIC。

3、ELF檔案

(1)簡介

      現在最常用的是一種叫ELF格式(executable and linkable format)的執行檔案,ELF定義了一些變數與資訊使得動態連結更有彈性,一個ELF的2進位檔案按照spec 1.1版的說法有6種,下列是較常見的:


relocatable:它就是編譯時間產生的.o檔案,包含了代碼和資料(這些資料是和其 他 重定位檔案和共用的object檔案一起串連時使用的)


executable:它就是最後的可執行檔,包含了代碼和資料shared obj:它就是在/lib /usr/lib下那些可動態連結的函數庫檔案,包含了代碼和資料(這些資料是在串連時候被連接器ld和運行時動態連接器使用的)


core:Core Dump時產生的檔案,包含了一堆garbage資料


      注意:這些ELF檔案已經是廣義的2進位檔案,不單指可執行檔。

    

(2)ELF組成

      一個ELF obj檔案隨它存在的時期有不一樣的需求和組成名字,在要連結linking時期位於硬碟,包含了:

ELF header

program header table (可以不要)

section 0

section 1

section 2

section 3

section ...

section n

section header table 

      ELF header放了ELF定義的一些ELF格式識別字串(俗稱magic number),還有obj檔案(shared obj,relocatable或者executable)這些一般(general)的資訊;program header table是描述了段(segment)資訊的結構數組和一些為程式運行準備的資訊。segement和section不大一樣,是屬於程式執行時期的元素,所以在程式執行時期是必要的,在連結時期是不必要的,所以如果程式不做連結動作,只要有program header
table就可以;section header table就是一個索引表,來記錄各個section的索引,sections就是把需要的資料根據屬性用途分門別類後的小集合,有.bss .data .init .debug .dynamic .fini .text………,其中比較重要的有:

.text

       裡面儲存真的CPU指令

.bss

      儲存沒有initialize的data

      主要是聲明的global與static變數

.data

      儲存initialize的data


      寫程式用到的函數名,變數名分布在多個source code目錄裡時,需要一個

參照(reference)的資訊做串連這些名字,symbol是著被給linker來做串連用的,因為obj檔案分散存在,要把這些obj檔案的代碼集合起來,就要靠symbol

來辨別,string table存有很多行字串,每行字串用NULL來分開,每行字串就是symbol和section的名字。symbol table是一張表,存有將來要定址或重新定址所要的symbol定義和參照資訊。shared lib的obj檔案還有.dynsym這個section,裡面存有dynamic symbol table,動態連結的時候使用。另外,如果將來的程式要用debug工具調試,編譯時間要加-g這個選項,它會根據sumbol

和string table放進debug多需要的資訊給obj檔案,這樣的資訊現在大都用一種叫stab的格式存放,這同時也會讓執行檔案大小增加到將近3倍。

      在ELF不同的檔案型態裡,ELF定義的資訊該有的都有,header section……只是裡面的值或有不同而已。

      Unix/Linux通常從一個_start函數開始而不是從main開始,_start後來會調用main,所以如果要精簡程式,就不要用gcc編譯,直接彙編用_start就可以了(^_^)。另外像section header table如果不需要做連結也可以不要,還有可執行檔的symbol table等,其實這些可以全部不要,不過要用彙編並同GAS來產生可執行檔。其實還有很多東西,這就是為什麼即使根本沒有調用任何函數,做成的動態檔案,用ldd看一定有ld-linux.so libc.so了。

      而一個存在記憶體中的process image,如下所示:

ELF header

program header table

segment 0

segment 1

segment 2

segment ...

segment n

section header table (可以不要)

      Segment有Text,Data等,根據OS定義不同,Text根據存在硬碟檔案裡的.txt .fini等section來的,Data段根據.data .bss等section來的,一個segment通常包含了 一個或一個以上的section,這些section在程式員角度來看更顯的重要。

      在支援ELF的系統上,一個程式是由可執行檔或者加上一些shared obj檔案組成。為了執行這樣的程式,系統使用那些檔案建立進程的記憶體映像。為了使一個ELF檔案裝載到記憶體,必須有一個program header table(該program header table 是一個描述段資訊的結構數組和一些為程式運行準備的資訊)。這裡有幾個在ELF文檔中定義的比較特別的sections。以下這些是對程式特別有用的:

.fini

     儲存進程終止代碼指令

     因此,當一個程式正常退出時,系統安排執行這個section中的代碼

.init

     儲存可執行指令,它構成了進程的初始化代碼

     因此,當一個程式開始運行時,在main函數被調用前(C語言稱為main),

系統安排執行這個section中的代碼


      .init和.fini sections的存在有著特別的目的。假如一個函數放到.init section,在main函數執行前系統就會執行它。同理,假如一個函數放到.fini section,在main函數返回後該函數就會執行。該特性被C++編譯器使用,完成全域的構造和解構函式功能。

      當ELF可執行檔被執行,系統將在把控制權交給可執行檔前裝載所以相關

的共用object檔案。構造正確的.init和.fini sections,建構函式和解構函式將以正確的次序被調用。

      Unix/Linux的虛擬記憶體使用有這樣的範圍:


user area


0x0 ~ 0x0bffffff  ->; 3GB


kernel area


0x0c000000 ~ 0xffffffff  ->; 1GB


以下面程式碼為例:

int global;


static int func1 (void)

{

                static int b;

                int *c;

                int d;

                func2();

                return 1;

}

int func2 (void)

{

                int c;

                static int d;

                return 2;

}

int main(void)

{

                int a;

                static int b;

                int init = 3;

                func1();

                return 3;

}


那麼從一個Linux執行檔案在記憶體中看起來是這個樣子:


i386 Linux的執行image


            Virtual   Address  Allocation


              |----------------------------------|0x0   

              | |-----------------------------|  |

              | |                                     |  | 

              | |  Thread  stack              |  |  

              | |------------------------------|  |       

              |                                           |

              | |------------------------------|  |0x08048000 Text                           

              | |  executable                  |  |                    Data            

              | |                                      |  |                     ……

              | |                                      |  | 

              | |                                      |  | 

              | |                                      |  | 

              | |------------------------------|  |

              |                                           |

              | |------------------------------|  |0x40000000 ld-linux.so

              | |                                     |  |                     libm.so

              | |  shared    LIB               |  |                     libc.so

              | |                                     |  |           

              | |  Stack                           |  |

              | |                                     |  |

        3GB| |----------------------------- |  |

              |                                           |

              | |------------------------------|  |0xc0000000

              | |                                      |  |

              | | Kernel Code and Data  |  |

              | |                                      |  |

              | |-------------------------------|  |

        4GB|---------------------------------   |0xffffffff


其中0x08048000 ~ 0x40000000 ~ 0xc0000000是這樣存在的。


從C角度看的image:


       0x08048000

     |--------------------------------------------------------|

     |   |--------------------------------------------------|  |

     |   |   main()                                                 |  |                              

     |   |           xxxx                    Text                 |  |

     |   |   func1                           (instrction)   |  |   

     |   |           xxxx                                            |  |

     |   |   func2                                                  |  |   

     |   |           xxxx                                           |  |

     |   |-------------------------------------------------|  |

     |                                                                     |

     |   |-------------------------------------------------|  |

     |   |   int global                       Data             |  |

     |   |   static int b(main)  static int b(func1)  |  |

     |   |   static int c(func2)                               |  |

     |   |-------------------------------------------------|  |

     |                                                                     |

     |   |-------------------------------------------------|  |

     |   |   malloc(int)                      Heap            |  |

     |   |-------------------------------------------------|  |

     |                      |                                              |    

     |                      |                                              |

     |                     \|/                                             |                             

     |--------------------------------------------------------|

     |    0x40000000                                              |

     |                                                                      |

     |                                                                      |

     |--------------------------------------------------------|

     |                     /|\                                             |

     |                      |                                               |

     |                      |                                               |

     |    |-------------------------------------------------|  |

     |    |  func2 int c                     Stack  2          |  |

     |    |-------------------------------------------------|  |

     |                                                                      |

     |    |-------------------------------------------------|  |

     |    |  func1 int b                     Stack  1         |  |

     |    |-------------------------------------------------|  |

     |                                                                      |

     |    |-------------------------------------------------|  |

     |    |  main()  argv[0]  argv[1]  …                  |  |

     |    |-------------------------------------------------|  |

     |--------------------------------------------------------|

       0xbfffffff


       所以可以清楚的知道不同變數(global,static or auto)的生命週期(storage class),和不同變數的有效範圍(scope)。

       Kernel code和data當然存在記憶體中,所以實際上都還要經過page table

轉成實際地址。在0x0~ 0xbfffffff中的page table,每個process有不同page

table,但在0xc0000000以下的page table,則都一樣。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.