为了能用qemu进行C代码级别的调试,babyos2不同于babyos,babyos采用的是纯二进制格式,babyos2的kernel是elf格式。
#define ELF_MAGIC 0x464c457FU // "\x7FELF"
// File header
typedef struct elf_hdr_s {
uint32 magic; // must equal ELF_MAGIC
uint8 elf[12];
uint16 type;
uint16 machine;
uint32 version;
uint32 entry;
uint32 phoff;
uint32 shoff;
uint32 flags;
uint16 ehsize;
uint16 phentsize;
uint16 phnum;
uint16 shentsize;
uint16 shnum;
uint16 shstrndx;
} elf_hdr_t;
// Program section header
typedef struct prog_hdr_s {
uint32 type;
uint32 off;
uint32 vaddr;
uint32 paddr;
uint32 filesz;
uint32 memsz;
uint32 flags;
uint32 align;
} prog_hdr_t;
主要参考了xv6相关的代码,但不同于xv6,xv6是根据elf格式一步一步地通过读扇区加载elf格式内核,babyos2是boot中将内核所有数据加载到一个临时位置,然后从内核加载elf格式到elf中各个section指定的加载位置。
extern "C"
void loadmain(void)
{
elf_hdr_t *elf = (elf_hdr_t *) (ELF_BASE_ADDR);
uint8 *base = (uint8 *) elf;
// check if it's a valid elf file
if (elf->magic != ELF_MAGIC) {
return;
}
// load program segments
prog_hdr_t *ph = (prog_hdr_t *)(base + elf->phoff);
prog_hdr_t *end_ph = ph + elf->phnum;
for (; ph < end_ph; ph++) {
uint8 *pa = (uint8 *)ph->paddr;
//memcpy(pa, base + ph->off, ph->filesz);
movsb(pa, base + ph->off, ph->filesz);
if (ph->memsz > ph->filesz) {
stosb(pa + ph->filesz, 0, ph->memsz - ph->filesz);
}
}
// find entry from elf, and call
void (*entry)(void) = (void(*)(void))(elf->entry);
entry();
}
首先检测magic,是不是一个正确的elf格式文件,
然后依次读取各个program segment,并拷贝到prog_hdr指定的加载位置,因为这时未开启分页,所以加载地址是根据内核代码段的基地址+偏移,babyos2采用flat内存模式,各个段基地址都是0,所以这里逻辑地址=物理地址。
加载完成后,跳转到elf->entry去执行。
Makefile:
boot: boot.S
${CPP} ${CFLAGS} -c boot.S
${LD} ${LDFLAGS} --oformat binary -N -Ttext 0x7c00 -o boot boot.o
loader: load.S loadmain.cc
${CPP} ${CFLAGS} -c load.S
${CPP} ${CFLAGS} -O -c loadmain.cc
${LD} --oformat binary -N -Ttext 0x0000 -o loader load.o loadmain.o
到此为止,boot loader的使命完成,elf格式的内核被解析并加载到指定的位置,然后跳转过去开始执行内核代码。
内核代码的加载位置及功能,后续再描述。