1. 程式人生 > 其它 >如何用程式碼實現tablewidget的表頭_ELF檔案程式表頭和程式碼實現ELF檔案載入

如何用程式碼實現tablewidget的表頭_ELF檔案程式表頭和程式碼實現ELF檔案載入

技術標籤:如何用程式碼實現tablewidget的表頭

前面章節我們瞭解了ELF檔案的頭部結構,這次我們深入瞭解另一個非常重要的資料結構,那就是程式表頭。作業系統嚴重依賴該結構來載入ELF檔案或是實現動態連結。程式表頭反映的是當ELF載入到記憶體後所形成的“檢視”或結構,也就是說ELF檔案存在硬碟上或者被載入到記憶體,它展現出來的形態不一致。

我們先看程式表頭的資料結構:

typedef  struct {
unit32_t p_type; #資料型別
uint332_t p_flags; #標誌位
uint64_t p_offset; #在ELF檔案中的偏移
uint64_t p_vaddr; #虛擬地址
uint64_t p_paddr; #實體地址
uint64_t p_fllesz; #在硬碟上的大小
uint64_t p_memsz; #在記憶體中大小
uint64_t p_align; #記憶體對齊方式
} Elf64_Phdr;

使用命令 readelf —wide —segments a.out可以讀取程式表頭內容資訊:

8f6c5371c8a8c1b9b98ea25d9b9ad727.png這裡需要注意的是,程式表頭其實沒有什麼新意,它其實對應前面說過的若干個段所形成的集合。接下來我們看每個欄位的含義。

p_type對應表頭的型別,常用的數值有PT_LOAD, PT_DYNAMIC, PT_INTER。如果取值PT_LOAD,意味著表頭對應的段需要加裝到記憶體中;從上圖看到有兩個表頭的型別為PT_LOAD,分別為第3和4,而第3個表頭對應段的集合為.init_array .fini_array等,第4個表頭對應段集合為.dynamic,這意味著這些段需要載入到記憶體中,同時每個表頭對應的段都要合成一個整體載入到表頭中所指定的位置。

PT_FLAGS對應段載入到記憶體後的讀寫許可權,常用的值有PF_X,PF_W,PF_R。PF_X表示表頭對應的段可以被執行,PF_W對應載入的那些段可以被修改,PT_R表示載入的段可以被讀取。p_offset表示表頭對應那些段的起始地址,p_vaddr表示表頭對應段該載入的虛擬位置,p_filesz表示表頭對應段在硬碟上的大小,p_memsz表示表頭對應段在載入到記憶體後的大小。

你可能會困惑,為何p_filesz和p_memsz的值不一樣。這是因為有些段在硬碟上不佔據容量,只有載入到記憶體時才分配容量。最後p_align表示記憶體對齊方式,它的取值為2的指數,同時p_vaddr必須等於(p_offset % p_align)

瞭解了ELF二進位制內部原理後,我們需要實現手動載入ELF檔案,實現這個目標,我們需要依賴一個庫叫libbfd,這個庫提供很多功能讓呼叫者能解讀X86架構下的通用二進位制可執行檔案。其安裝可以使用如下命令:

sudo apt-get install -y libbfd-dev

基本上所有版本的Linux都會附帶這個程式碼庫,該程式碼庫提供了一個類叫Binary,用於對可執行二進位制檔案的抽象,同時還有Section類,它是對前面我們提到的段資料結構的抽象;同時它還提供Symbol類,這是對符號表的抽象,接下來我們先看看其基本使用方法:

#include 
#include
#include
#include "../inc/loader.h"
int main(int argc, char *argv[]) {
size_t i;
Binary bin; //represent elf file
Section *sec;
Symbol *sym;
std::string fname;
if (argc < 2) {
printf("need to set binary file name");
return 1;
}
fname.assign(argv[1]);
if (load_binary(fname, &bin, Binary::BIN_TYPE_AUTO) < 0) {
printf("load binary fail!");
return 1;
}
printf("loaded binary file name:%s", bin.filename.c_str());
printf("loaded binary file type: %s", bin.type_str.c_str());
printf("loaded binary file [email protected]%016jx\n: ", bin.entry);
printf("loaded binary file bits: %u", bin.bits);
for(i = 0; i < bin.sections.size(); i++) {
sec = &bin.sections[i];
printf(" 0x%16jx %-8ju %-20s %s\n", sec->vma, sec->size, sec->name.c_str(),
sec->type == Section::SEC_TYPE_CODE?"CODE":"DATA");
}
if (bin.symbols.size() > 0) {
printf("scanned symbol tables:\n");
for (i = 0; i < bin.symbols.size(); i++) {
sym = &bin.symbols[i];
printf(" %-40s 0x%016jx %s\n", sym->name.c_str(), sym->addr,
(sym->type & Symbol::SYM_TYPE_FUNC)? "FUNC":"");
}
}
unload_binary(&bin);
return 0;
}

程式碼中需要注意的是,loader.h是來自libbfd庫的標頭檔案,讀者需要修改程式碼中該檔案的路徑以對應你電腦上libbfd的安裝路徑。Binary類用於對整個elf檔案的抽象,通過它可以訪問ELF檔案相關資訊,Section是對前面章節描述的段物件的抽象,Symbol是對前面章節符號表物件的抽象。

load_binary是來自libbfd庫提供的函式,它將elf檔案載入到記憶體中。上面程式碼編譯時對應的Makefile內容為:

CXX=g++
OBJ=my_loader
.PHONY: all clean
all: $(OBJ)
loader.o: ../inc/loader.cc
$(CXX) -std=c++11 -c ../inc/loader.cc
my_loader: loader.o my_loader.cc
$(CXX) -std=c++11 -o my_loader my_loader.cc loader.o -lbfd
clean:
rm -f $(OBJ) *.o

執行make命令編譯後,在本地目錄會有my_loader可執行檔案,使用命令./my_load a.out即可讓程式載入a.out檔案並輸出一系列資訊:939cb25e0a10139e094196bf10341bcd.png對於libbfd更加詳細的使用方法,我們在後續章節會詳細介紹。