自制x86 Bootloader开发笔记(4)——— 编写ELF Loader
前言
我们的Bootloader目标是加载64位的ELF可执行文件,因此需要理解64位ELF文件的结构,并且支持运行ELF文件。
ELF文件结构
ELF文件的结构如下图所示:
它包含了ELF头部,一个可选的Program Header Table,多个Section和一个Section Header Table。
其中ELF头部包含了整个ELF文件的一些信息。Section是ELF文件的一个一个的”节“,如代码段.code
,数据段.data
。Section Header Table则是记录了各个段的偏移和大小等信息的一样表。ELF_HEAD的结构如下:
struct elf_head {
unsigned char e_ident[EI_NIDENT];
uint16 e_type;
uint16 e_machine;
uint32 e_version;
uint64 e_entry;
uint64 e_phoff;
uint64 e_shoff;
uint32 e_flags;
uint16 e_ehsize;
uint16 e_phentsize;
uint16 e_phnum;
uint16 e_shentsize;
uint16 e_shnum;
uint16 e_shstrndx;
} ;
各个字段的具体含义可见引用1,我们只介绍其中最关键的e_entry
,e_shoff
,e_shnum
字段。e_entry
是程序的入口点,e_shoff
是Section Header Table在文件中的偏移,e_shnum
表示总共多少个Section Header,通过e_shoff
和e_shnum
,我们就可以遍历Section Header Table中的各个表项,表项的结构如下:
struct elf_shdr {
uint32 sh_name;
uint32 sh_type;
uint64 sh_flags;
uint64 sh_addr; // 节的虚拟地址
uint64 sh_offset; // 如果该节存在于文件中,则表示该节在文件中的偏移;否则无意义,如sh_offset对于BSS 节来说是没有意义的
uint64 sh_size; // 节大小
uint32 link;
uint32 sh_info;
uint64 sh_addralign;
uint64 sh_entsize;
} ;
通过sh_offset
和sh_addr
以及sh_size
这三个字段,我们就可以吧ELF文件的一个个节搬到指定的内存地址,然后跳转到程序入口点e_entry
即可。代码如下
void load_elf_kernel(uint8* kaddr) {
uint8* tmp = (uint8*) KERNEL_FILE_BASE;
struct elf_head* ehead = (struct elf_head*) KERNEL_FILE_BASE;
// 检测是否是elf文件
if (ehead->e_ident[0] != 0x7f || ehead->e_ident[1] != 'E' || ehead->e_ident[2] != 'L' || ehead->e_ident[3] != 'F') {
ld_print("Unsupported ABI! %d %d %d %d", ehead->e_ident[0], ehead->e_ident[1], ehead->e_ident[2], ehead->e_ident[3]);
asm("hlt");
}
struct elf_shdr* shdr_ptr = (struct elf_shdr*)(ehead->e_shoff + tmp);
// 复制各个段到指定的内存地址
for (int i = 0; i < ehead->e_shnum; i++) {
uint8* target = (uint8*)shdr_ptr[i].sh_addr;
uint8* src = tmp + shdr_ptr[i].sh_offset;
for (int k = 0; k < shdr_ptr[i].sh_size; k++) {
target[k] = src[k];
}
}
// 跳转到elf文件入口
jump_to_kernel(ehead->e_entry);
}
总结一下,整个流程如下:
- 读取ELF Header,获取程序入口点和Section Header Table位置。
- 读取Section Header Table。
- 遍历Section Header Table各个表项,将文件的各个节的内容复制到指定的内存地址。
- 跳转到程序入口点。
项目地址: https://github.com/basic60/ARCUS