在實際的硬體上,開機後執行的第一道指令已經燒錄在ROM上面,對於CPU來說程式在開機時就已經位於位址空間的某處,但對於我們的模擬器來說,開機後的記憶體是一片空白,因此是必須要有人先把程式碼放到記憶體中,CPU才能開始執行,這就是這次要實作的loader的主要功能。
由於目前開發的平台以Linux為主,因此選用的執行檔格是為Linux系統上常見的ELF檔。完整的loader程式碼如下:
use goblin::elf;
use std::fs;
fn load_elf(mem: &mut memory_model::MemoryModel, path: &str) -> AddressType {
let bytes = fs::read(path).unwrap();
let elf = elf::Elf::parse(&bytes).unwrap(); //呼叫goblin函式庫提供的parse function
for ph in elf.program_headers {
if ph.p_type == goblin::elf::program_header::PT_LOAD {
for offset in 0..ph.p_filesz {
mem.write_byte(
(ph.p_paddr + offset) as AddressType,
bytes[(ph.p_offset + offset) as usize],
);
}
}
}
elf.entry as AddressType
}
這邊使用了goblin函式庫來處理elf檔,elf檔格式在此不細談,只要知道所有的code以及data會分成多個section存放,而相同屬性的section又會被合併成為一個個segment,這些segment資訊就存放在program header中。
需要特別注意的是目前只需要將type為PT_LOAD的segment載入,可能的type如下:
#define PT_NULL 0 /* Program header table entry unused */
#define PT_LOAD 1 /* Loadable program segment */
#define PT_DYNAMIC 2 /* Dynamic linking information */
#define PT_INTERP 3 /* Program interpreter */
#define PT_NOTE 4 /* Auxiliary information */
#define PT_SHLIB 5 /* Reserved */
#define PT_PHDR 6 /* Entry for header table itself */
#define PT_TLS 7 /* Thread-local storage segment */
#define PT_NUM 8 /* Number of defined types */
#define PT_LOOS 0x60000000 /* Start of OS-specific */
#define PT_GNU_EH_FRAME 0x6474e550 /* GCC .eh_frame_hdr segment */
#define PT_LOSUNW 0x6ffffffa
#define PT_SUNWBSS 0x6ffffffa /* Sun Specific segment */
#define PT_SUNWSTACK 0x6ffffffb /* Stack segment */
#define PT_HISUNW 0x6fffffff
#define PT_HIOS 0x6fffffff /* End of OS-specific */
#define PT_LOPROC 0x70000000 /* Start of processor-specific */
#define PT_HIPROC 0x7fffffff /* End of processor-specific */
一般程式的code與data都會屬於PT_LOAD type的segment,故目前僅需載入此類segment。