1. 程式人生 > >Linux C目標檔案

Linux C目標檔案

LinuxC目標檔案

宗旨:技術的學習是有限的,分享的精神是無限的。

一、目標檔案格式(ELF格式)

編譯器編譯原始碼後生成的檔案叫做目標檔案。目標檔案是已經編譯後的可執行檔案,只是還沒有經過連結的過程。

PC平臺流行的可執行檔案格式:windows下的PE和Linux下的ELF。

動態連結庫和靜態連結庫也是按照可執行檔案儲存的。

1ELF檔案歸於4類:

ELF文講型別

說明

舉例

可重定位檔案

目標檔案.o

Linux的.o, windows下的.obj

可執行檔案

直接可執行的檔案

/bin/bash   windows的.exe

共享目標檔案

.so   DLL

核心轉儲檔案

程序意外終止

core dump

Linux下的file命令檢視相應的檔案格式:


2、目標檔案

編譯後的機器指令程式碼、資料、符號表、除錯資訊、字串等。

一般目標檔案將這些檔案資訊按不同的屬性,以“節”的形式儲存。

機器指令放在程式碼段.text,已初始化全域性變數和靜態變數放在資料段裡.data,未初始化全域性變數和靜態變數放在資料段裡.bss。.bss只是為變數預留的位置而已,並沒有內容,不佔據空間。

二、剖析目標檔案section.o

// section.c:
#include<stdio.h>

int init_var = 84;
int uninit_var;

void fun(int i)
{
    printf(" %d \n", i);
}

int main(void)
{
    static int static_var = 85;
    static int static_var2;
    int a = 1;
    int b;

    fun(init_var + uninit_var + a + b);

    return 0;
}

gcc  -c  section.c生成section.o——只編譯不連結 

objdump  -h  section.o// ELF檔案的各個段的基本資訊打印出來。


除了最基本的程式碼段、資料段、BSS段,還有三個段:只讀資料段(.rodata)、註釋資訊段(.comment)和堆疊提示段(.note.GNU-stack)。

段的屬性:最容易理解的就是段的長度Size和段所在的位置File off(偏移量)。每個段第二行的“CONTENTS”表示該段在檔案中存在——BSS段沒有“CONTENTS “,實際上在ELF中不存在;”note.GNU-stack“有“CONTENTS”但大小為0,奇怪。

size命令檢視ELF檔案中的程式碼段、資料段和BSS段長度。

[email protected]:~/mystudy# size section.o

   text    data    bss     dec     hex filename

   88      8       4     100     64 section.o

1、程式碼段

         objdump的“-s”十六進位制方式列印,“-d”反彙編。提取出程式碼段的內容:

 

“Contents of section.text”就是.text的資料以十六進位制方式打印出來的內容,0x58位元組,與size命令的長度符合。對照反彙編結果,.text包含兩個函式,fun()和main()。.text的第一個位元組就是”0x55”就是fun()函式的第一條“push   %ebp”指令,而最後一個位元組0xc3正是main()函式的最後一條指令“ret”。

2、資料段和只讀資料段

         .data段儲存的是初始化的全域性變數和靜態變數,section.c中有這樣兩個變數init_var和static_var,都是int型,剛好8位元組。所以.data的大小是8位元組。

Contents of section .data:

 0000 54000000 55000000                    T...U...       

Contents of section .rodata:

 0000 20256420 0a00                         %d ..  

.data前四個位元組,0x54、0x00、0x00、0x00 —— 0x54 = 84;——大端機

3BSS

         .bss段儲存的是未化的全域性變數和靜態變數,section.c中有這樣兩個變數uninit_var和static_var2。但是通過size命令看到.bss只有4位元組。通過符號表(後面說)看到,只有static_var2被放入了.bss段,uninit_var沒有。與不同的語言和不同的編譯器有關。

4、其他段        

常用段名

說明

.rodata

只讀資料,如字串常量,const只讀變數

.comment

編譯器版本資訊,

.debug

除錯資訊

.dynamic

動態連結資訊

.hash

符號雜湊表

.line

行號表

.note

額外的編譯器資訊:公司名,釋出版本號等

.strtab

字串表

.symtab

符號表

.shstrtab

段名錶

.plt   .got

動態連結的跳轉表和全域性入口表

.init  .fini

程式初始化與終結程式碼段

三、ELF檔案結構

提取重要的結構:ELFHeaderELF檔案頭)、.text.data.bss、其他段、段表、字串表、符號表等。

ELF檔案頭——描述了整個檔案的檔案屬性:是否可執行、是靜態還是動態連線及入口地址、目標硬體、目標作業系統等資訊。

段表——所有段的資訊:段名、段的長度、在檔案中的偏移、讀寫許可權及段的其他屬性。

1、檔案頭(readelf命令) 


ELF檔案頭定義:ELF魔數、檔案機器位元組長度、檔案儲存方式、版本、執行平臺、ABI版本、ELF重定位型別、硬體平臺、硬體平臺版本、入口地址、程式頭入口地址和長度、段表的位置和長度及段的數量等。

ELF檔案頭結構及相關常數被定義在”/usr/include/elf.h”,32位“ELF32_Ehdr

#define EI_NIDENT(16)

typedef struct
{
  unsigned char e_ident[EI_NIDENT];     /* Magic number and other info */
  Elf32_Half   e_type;                 /* Objectfile type */
  Elf32_Half   e_machine;              /*Architecture */
  Elf32_Word   e_version;              /* Object file version */
  Elf32_Addr   e_entry;                /* Entrypoint virtual address */
  Elf32_Off    e_phoff;                /* Programheader table file offset */
  Elf32_Off    e_shoff;                /* Sectionheader table file offset */
  Elf32_Word   e_flags;                /*Processor-specific flags */
  Elf32_Half   e_ehsize;               /* ELFheader size in bytes */
  Elf32_Half   e_phentsize;            /* Programheader table entry size */
  Elf32_Half   e_phnum;                /* Program header table entrycount */
  Elf32_Half   e_shentsize;            /* Sectionheader table entry size */
  Elf32_Half   e_shnum;                /* Sectionheader table entry count */
  Elf32_Half   e_shstrndx;             /* Sectionheader string table index */
} Elf32_Ehdr;

結構與readelf輸出的ELF檔案頭資訊相比:只有e_ident對應了readelf輸出中的“Class  Data  Version OS/ABI  ABI Version”5個引數,剩下的引數一一對應。

ELF魔數:Magic:  7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00  

16位元組對應了Elf32_Ehdr的e_ident這個16位元組成員。這16位元組被ELF標準規定來標識ELF檔案的平臺屬性:如字長、位元組序、版本等。16位元組含義:

前4位元組是所有ELF檔案都必須相同的標識碼:0x7F、0X45、0X4C、0X46(這四個位元組就是ELF檔案的魔數)。幾乎所有的可執行檔案的開始的幾個位元組都是魔數,如a.out最開始兩個位元組是0x01、0x07;PE最開始兩個位元組是0x4d、0x5a。這個魔數用來確認檔案型別。第5個位元組用來標識ELF檔案類的,0x01表示是32位的,0x02表示64位的。第6位元組是位元組序,規定ELF檔案是大端的還是小端的。第7位元組規定ELF檔案的主版本號,一般是1.後面的九個位元組ELF標準沒有定義,一般寫0。

e_type檔案型別:ET_REL—— 1 ——可重定位檔案,一般是.o檔案;ET_EXEC—— 2 —— 可執行檔案; ET_DYN —— 2 ——共享目標檔案,一般是.so檔案。

e_machine機器型別:ELF檔案的平臺屬性,EM_386 —— 3 —— x86

2、段表

ELF的段結構就是由段表決定的,編譯器、聯結器和裝載器都是靠段表來定位和訪問各個段的屬性的。段表在ELF檔案中的位置由ELF檔案頭Elf32_Ehdr結構中的” e_shoff” 成員決定。section.o中,段表位於偏移0x104(260位元組)處。

前面用”objdump -h”檢視ELF檔案中的段,此命令只是把ELF檔案中的關鍵段顯示出來了,省略了其他輔助性的段:符號表、字串表、重定位表等。

readelf -S命令


段表是以“Elf32_Shdr”結構體為元素的陣列,陣列元素的個數等於段的個數,每個“Elf32_Shdr”結構體對應一個段。section.o:11個元素的陣列。/usr/include/elf.h:

typedef struct
{
  Elf32_Word    sh_name;                /* Section name (string tblindex) */
  Elf32_Word    sh_type;                /* Section type */
  Elf32_Word    sh_flags;               /* Section flags */
  Elf32_Addr     sh_addr;                /* Section virtual addr atexecution */
  Elf32_Off       sh_offset;              /* Section file offset */
  Elf32_Word    sh_size;                /* Section size in bytes */
  Elf32_Word    sh_link;                /* Link to another section */
  Elf32_Word    sh_info;                /* Additional sectioninformation */
  Elf32_Word    sh_addralign;           /* Section alignment */
  Elf32_Word    sh_entsize;             /* Entry size if section holdstable */
} Elf32_Shdr;

總結section.o段表的位置

起始地址

大小

ELF Header  e_shoff = 0x104

0

0x34

.text

0x34

0x52

.data

0x88

0x08

.rodata

0x90

0x06

.comment

0x96

0x1d

.shstrtab

0xB3

0x51

Section Table

0x104

0x1b8

.symtab

0x2bc

0xf0

.rel.text

0x3fc

0x28

長度為0x424 = 1060,這個長度正好是section.o檔案的大小。

段的型別(sh_type):段的名字只有在編譯和連結的過程中有意義。SHT_NULL – 0 – 無效段, SHT_PROGBITS– 1 – 程式段, SHT_SYMTAB – 2 – 表示該段的內容為符號表, SHT_STRTAB – 3 – 字串表, SHT_RELA –4 – 重定位表, SHT_HASH – 5 – 符號表的雜湊表,SHT_DYNAMIC – 6 – 動態連結資訊, SHT_NOTE – 7 – 提示性資訊, SHT_NOBITS– 8 –該段在檔案中沒內容, SHT_REL – 9 –該段包含了重定位資訊,SHT_SHLIB – 10 – 保留,SHT_DNYSYM – 11 – 動態連結的符號表。

段的標誌位(sh_flag):表示該段在程序虛擬地址空間中的屬性,可寫可執行等。SHF_WRITE – 1 – 該段在程序空間中可寫; SHF_ALLOC– 2 – 在程序空間中要分配空間; SHF_EXECINSTR– 4 –該段在程序空間中可以被執行,一般指程式碼段。

         段的連結資訊(sh_link、sh_info):

sh_type

sh_link

sh_info

SHT_DYNAMIC

字串表在段表的下標

0

SHT_HASH

符號表在段表中的下標

0

SHT_REL

相應符號表在段表中的下標

該重定位表所作用的段在段表中的下標

SHT_RELA

SHT_SYMTAB

作業系統相關

作業系統相關

SHTDYNSYM

other

SHN_UNDEF

0

3、重定位表

         section.o中有一個“rel.text”的段,型別是“SHT_REL”——重定位表。

程式碼段和資料段中那些對絕對地址的引用的位置——相應的重定位表。section.o中的“rel.text”就是對“.text”段的重定位表——printf函式的呼叫;而“.data”段沒有對絕對地址的引用,只包含了幾個常量,故沒有“.rel.data”。

4、字串表——段名,變數名等

四、連結的介面——符號

可以使用很多工具檢視ELF檔案的符號表,readelfobjdumpnm等;

1ELF符號表結構

         ELF符號表是檔案中的一個段“.symtab”

/* Symbol table entry.  */
typedef struct
{
  Elf32_Word    st_name;                /* Symbol name (string tblindex) */
  Elf32_Addr    st_value;               /* Symbol value */
  Elf32_Word    st_size;                /* Symbol size */
  unsigned char st_info;                /* Symbol type and binding */
  unsigned char st_other;               /* Symbol visibility */
  Elf32_Section st_shndx;               /* Section index */
} Elf32_Sym;