LOADING

加载过慢请开启缓存 浏览器默认开启

NEMU PA2 阶段四(实现loader)

2024/9/13

实现loader

这一阶段可能没有什么要学习和补充的知识点,所以就跟着手册做就行了

首先按照要求修改makefile文件以及testcase中的makefile.part文件,之后make run运行,在实现了该执行的指令之后发现竟然HIT BAD TRAP,为什么?

并不是实现的指令有错,这是本阶段第一个要做的任务。通过阅读手册,我们可知kernel会调用loader()来加载用户程序,而我们转到这个函数,发现了确实要在这里完成一些内容。

uint32_t loader() {
    Elf32_Ehdr *elf;
    Elf32_Phdr *ph = NULL;

    uint8_t buf[4096];

#ifdef HAS_DEVICE
    ide_read(buf, ELF_OFFSET_IN_DISK, 4096);
#else
    ramdisk_read(buf, ELF_OFFSET_IN_DISK, 4096);
#endif

    elf = (void*)buf;

    /* TODO: fix the magic number with the correct one */
    const uint32_t elf_magic = 0xBadC0de;
    uint32_t *p_magic = (void *)buf;
    nemu_assert(*p_magic == elf_magic);
...

我们要填入正确的“魔数”才行。

指导手册中给出了ramdisk_read()函数的功能:

理解上述过程后, 你需要在kernel/src/elf/elf.c的loader()函数中定义正确 ELF 文件魔数, 然后编写加载segment的代码, 完成加载用户程序的功能。你需 要使用ramdisk_read()函数来读出 ramdisk 中的内容, ramdisk_read()函数的 原型如下:

void ramdisk_read(uint8_t *buf, uint32_t offset, uint32_t len);

它负责把从ramdisk中offset偏移处的len字节读入到buf中。

在这里ELF_OFFSET_IN_DISK是一个宏定义,为0。而这个魔数,是用户程序ELF的,我这里makefile里面的用户程序是add,那么就用readelf读出来。

注意这是小端序因为架构是intel 80386,所以魔数就是0x464c457f

测试之后HIT BAD TRAP的eip地址变大了,应该是说明这个魔数填对了。

接下来的if语句中是我们下一步做的

if(ph->p_type == PT_LOAD) {
    /* TODO: read the content of the segment from the ELF file 
     * to the memory region [VirtAddr, VirtAddr + FileSiz)
     */
     
    /* TODO: zero the memory region 
     * [VirtAddr + FileSiz, VirtAddr + MemSiz)
     */
...

其中ph的类型是Elf32_Phdr,根据ppt中的内容,它是一个程序头表,是用来管理segment的

typedef struct
{
  Elf32_Word	p_type;			/* Segment type */
  Elf32_Off	p_offset;		/* Segment file offset */
  Elf32_Addr	p_vaddr;		/* Segment virtual address */
  Elf32_Addr	p_paddr;		/* Segment physical address */
  Elf32_Word	p_filesz;		/* Segment size in file */
  Elf32_Word	p_memsz;		/* Segment size in memory */
  Elf32_Word	p_flags;		/* Segment flags */
  Elf32_Word	p_align;		/* Segment alignment */
} Elf32_Phdr;

同时手册里面也说了

同时我们也可以看到, 加载一个可执行文 件并不是加载它所包含的所有内容, 只要加载那些与运行时刻相关的内容就可 以了(Type 为 LOAD)

所以类型为LOAD,这也符合上面if语句的条件。

这里再次贴出手册里面的引用和ppt截图,以帮助理解。

你需要找出每一个 program header 的 Offset, VirtAddr, FileSiz 和 MemSiz这些参数。其中相对文件偏移Offset指出相应segment的内容从ELF 文件的第 Offset 字节开始, 在文件中的大小为 FileSiz, 它需要被分配到以 VirtAddr 为首地址的虚拟内存位置, 在内存中它占用大小为 MemSiz。但现在 NEMU还没有虚拟地址的概念, 因此你只需要把VirtAddr当做物理地址来使用 就可以了, 也就是说, 这个 segment 使用的内存就是[VirtAddr, VirtAddr +MemSiz)这一连续区间, 然后将segment 的内容从ELF文件中读入到这一内 存区间, 并将[VirtAddr + FileSiz, VirtAddr + MemSiz)对应的物理区间清零。

这里你可能感到奇怪的就是为什么没有输出“please implement me”,我们查看定义,发现panic这个函数是Log输出的,答案在手册里也已经准备好了。

这是由于现在 NEMU还不能提供输出的功能, 因此现在kernel中的Log()宏并不能成功输出。

外面还有一个for循环,我们也要写出这个循环的条件。

typedef struct
{
  unsigned char	e_ident[EI_NIDENT];	/* Magic number and other info */
  Elf32_Half	e_type;			/* Object file type */
  Elf32_Half	e_machine;		/* Architecture */
  Elf32_Word	e_version;		/* Object file version */
  Elf32_Addr	e_entry;		/* Entry point virtual address */
  Elf32_Off	e_phoff;		/* Program header table file offset */
  Elf32_Off	e_shoff;		/* Section header table file offset */
  Elf32_Word	e_flags;		/* Processor-specific flags */
  Elf32_Half	e_ehsize;		/* ELF header size in bytes */
  Elf32_Half	e_phentsize;		/* Program header table entry size */
  Elf32_Half	e_phnum;		/* Program header table entry count */
  Elf32_Half	e_shentsize;		/* Section header table entry size */
  Elf32_Half	e_shnum;		/* Section header table entry count */
  Elf32_Half	e_shstrndx;		/* Section header string table index */
} Elf32_Ehdr;//这是elf的类型的定义
//e_phnum成员为循环的次数,遍历所有的table,查找其中是LOAD类型的。
int i = 0;
ph = (void *)(buf + elf->e_phoff);
for(; i < elf->e_phnum; i++){
    if(ph->type == PI_LOAD){
        ...
    }
}

查找成功后,我们要将p_vaddr那个位置开始,写入p_filesz个字节,从p_offset位置开始

并且还要将一段空间初始化为0

ph移动到下一个

ramdisk_read ((void *)ph->p_vaddr, ph->p_offset, ph->p_filesz);
memset ((void *)(ph->p_vaddr+ph->p_filesz), 0, ph->p_memsz - ph->p_filesz);
ph++

那么我们就完成loader了。

输入

make test

进行测试

会有三个失败的:hello,hello-inline-asm,printf-FLOAT这三个在第二阶段没有要求通过,所以这很正常,但是除了这三个,应该全成功。