天天看點

obj檔案,動态連結檔案和ELF檔案

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,則都一樣。

繼續閱讀