二十一 elf檔案格式解析

2021-08-20 03:10:49 字數 3595 閱讀 3917

我們的程式是通過gcc編譯的,在linux下,gcc編譯出來的可執行檔案是elf格式的二進位制檔案。那麼肯定要elf檔案進行解析才能正確的得到程序可執行資料的位置。

下面介紹一下elf格式的幾個基本概念

乙個程式中最重要的部分是段和節,他們是真正的程式體,儲存程式執行所需要的資料,程式中有很多段,常見的有**段和資料段,段是由節組成的。多個節經過鏈結之後被合併成乙個段。

段和節的資訊用header來描述,程式頭是program header,節頭是section header。

程式中段的大小和數量不固定,節也是如此,因此需要乙個專門的資料結構來描述他們,這個就是程式頭表和節頭表,他們用來儲存多個程式頭和節頭,相當於陣列的概念。

由於程式中段和節的數量不固定,程式頭表和節頭表的大小也就不固定。並且各表在程式中儲存的先後順序不同,所有這些表在程式中儲存的位置也是不固定了,為了能方便的找到這些表的位置,獲取其資訊,需要乙個固定的結構來描述他們,記錄其儲存的位置和大小等資訊,這個結構就是elf header

elf格式的作用體現在兩方面,一是鏈結階段,二是執行階段。下圖是這兩方面elf格式資料的布局

下面重點說一下elf header中的資料

/* 32位elf頭 */

struct elf32_ehdr

;

上面是elf header中儲存的資料,裡面涉及這幾種資料型別,資料型別的本質是其所佔的位元組數,賦予資料型別意義是為了方便裡面,下圖是這幾種型別代表的意義。

接下來是e_ident這個成員所表示的意義,見下圖

下面是e_type所代表的意義

elf目標檔案型別

取值意義

et_none

0未知目標檔案格式

et_rel

1可重定位檔案

et_exec

2可執行檔案

et_dyn

3動態共享目標檔案

et_core

4core檔案

et_loproc

0xff00

特定處理器檔案的擴充套件下邊界

et_hiproc

0xffff

特定處理器檔案的擴充套件上邊界

雖然這裡有很多的型別,但我們使用的只有et_exec.

接下來是e_machine,它表示該檔案在哪種硬體平台上才能執行

後面還有的資料不一一描述,這裡從書上截圖來說明

程式頭是專門用來描述段資訊的,這個段不是記憶體中的段,記憶體中的段是記錄在全域性描述符表中的。程式頭描述的段是磁碟上程式中的乙個段,常見的如**段和資料段,下面是其結構

struct elf32_phdr

;

p_type所表示的意義如下

p_offset表示本段在檔案的偏移量

p_vaddr表示本段在記憶體中起始的虛擬位址

p_paddr僅用於與實體地址相關的系統中

p_fiesz表示本段在檔案中的大小

p_memsz表示本段在記憶體中的大小

p_flags的意義見下圖

p_align表示本段在檔案和記憶體中的對齊方式。

目標檔案在鏈結之後**和資料等資源都是在段中,有了上面這些結構來記錄相關資訊,程式在載入的時候就根據這些資訊從磁碟的某個位置將程式執行所需的資源載入到記憶體中,接下來通過乙個例項對elf檔案進行分析一下。

這是我隨便找的乙個可執行檔案檢視的資料,用路線標出來的屬於elf header中的資料,紅線標出來的屬於乙個program header的資料,具體的意義可以對照著上面結構的字段去看。

elf header和program header的資料結構

typedef uint32_t elf32_word, elf32_addr, elf32_off;

typedef uint16_t elf32_half;

/* 32位elf頭 */

struct elf32_ehdr

;/* 程式頭表program header.就是段描述頭 */

struct elf32_phdr

;/* 段型別 */

enum segment_type

;

載入乙個段

/* 將檔案描述符fd指向的檔案中,偏移為offset,大小為filesz的段載入到虛擬位址為vaddr的記憶體 */

static

bool segment_load(int32_t fd, uint32_t offset, uint32_t filesz, uint32_t vaddr)

else

/* 為程序分配記憶體 */

uint32_t page_idx = 0;

uint32_t vaddr_page = vaddr_first_page;

while (page_idx < occupy_pages)

} // 如果原程序的頁表已經分配了,利用現有的物理頁,直接覆蓋程序體

vaddr_page += pg_size;

page_idx++;

}sys_lseek(fd, offset, seek_set);

sys_read(fd, (void *)vaddr, filesz);

return

true;

}

/* 從檔案系統上載入使用者程式pathname,成功則返回程式的起始位址,否則返回-1 */

static int32_t load(const

char *pathname)

if (sys_read(fd, &elf_header, sizeof(struct elf32_ehdr)) != sizeof(struct elf32_ehdr))

/* 校驗elf頭 */

if (memcmp(elf_header.e_ident, "\177elf\1\1\1", 7) || elf_header.e_type != 2 || elf_header.e_machine != 3 || elf_header.e_version != 1 || elf_header.e_phnum > 1024 || elf_header.e_phentsize != sizeof(struct elf32_phdr))

elf32_off prog_header_offset = elf_header.e_phoff;

elf32_half prog_header_size = elf_header.e_phentsize;

/* 遍歷所有程式頭 */

uint32_t prog_idx = 0;

while (prog_idx < elf_header.e_phnum)

/* 如果是可載入段就呼叫segment_load載入到記憶體 */

if (pt_load == prog_header.p_type)

}/* 更新下乙個程式頭的偏移 */

prog_header_offset += elf_header.e_phentsize;

prog_idx++;

}ret = elf_header.e_entry;

done:

sys_close(fd);

return ret;

}

ELF檔案格式

在介紹elf格式之前,先簡單說明一下可執行檔案的生成流程 1 編寫c原始檔,或彙編原始檔 2 準備共享庫格式的目標檔案 shared object file 如數學庫 標準庫 2 用編譯器 compiler 將c編譯成可重定位格式的目標檔案 relocatable object file 用彙編器 ...

ELF檔案格式

1.目標檔案 編譯器和彙編器生成可重定位目標檔案 包括共享目標檔案 鏈結器生成可執行目標檔案。2.可重定位目標檔案和可執行目標檔案的格式 可重定位目標檔案格式 可執行目標檔案格式 3.下面我們開始分析上面 而對於未被初始化的全域性變數和靜態區域性變數,編譯的時候並未被分配空間,而是僅僅在.bss段中...

ELF檔案格式

elf指executable and linking format,不僅包含可執行檔案,也包含庫檔案,包括靜態庫和動態庫。準備的說,也就是三種 這不廢話嗎 可執行檔案 靜態鏈結庫 動態鏈結庫 要觀察elf的具體資訊,可以用以下幾個工具 nm lists symbols from object fil...