【操作系统】GeekOS完成记录(三)了解ELF格式并加载可执行文件

977 阅读2分钟

本文正在参加「金石计划」

项目要求

image.png

image.png 了解ELF(Executable and Linkable Format)可执行文件的格式

什么是ELF?

Executable and Linkable Format,这是一类可执行可链接的文件的概称,在Linux下较为常见的ELF文件有:

  • xx.out 如gcc -o默认生成的a.out文件。其代码和数据都有固定的地址 (或相对于基地址的偏移 ),系统可根据这些地址信息把程序加载到内存执行。
  • xx.o 代码和数据没有指定绝对地址
  • xx.so(shared object),动态库文件。被链接器(ld)和运行时动态链接器使用

ELF格式

一个ELF文件一般包括如下部分:

  • ELF header
  • Program header table
  • Section
  • Section header table

后三部分不是确定的,而ELF头是固定的

如何使用ELF?

image.png

项目完成思路

  • elf.c初始所给的信息是这样的:
/**
 * From the data of an ELF executable, determine how its segments
 * need to be loaded into memory.
 * @param exeFileData buffer containing the executable file
 * @param exeFileLength length of the executable file in bytes
 * @param exeFormat structure describing the executable's segments
 *   and entry address; to be filled in
 * @return 0 if successful, < 0 on error
 */
int Parse_ELF_Executable(char *exeFileData, ulong_t exeFileLength,
    struct Exe_Format *exeFormat)
{
    TODO("Parse an ELF executable image");
}

其中:

  • exeFileData是一个char类型的指针,可以用它找到ELF header;
  • exeFileLength是文件长度,可用于确定范围
  • exeFormat是一个与可执行文件格式相关的结构体指针,我们可以定位到elf头,再把相关信息赋值给它 该结构体组成如下(见elf.h):
struct Exe_Format
{
    struct Exe_Segment segmentList[EXE_MAX_SEGMENTS]; /* Definition of segments */
    int numSegments;                                  /* 定义了ELF文件中段的个数 Number of segments contained in the executable */
    ulong_t entryAddr;                                /* 代码入口地址 Code entry point address */
};

由以上思路,可以写出如下代码:

int Parse_ELF_Executable(char *exeFileData, ulong_t exeFileLength,
                         struct Exe_Format *exeFormat)
{
    // TODO("Parse an ELF executable image");
    elfHeader *ehdr = (elfHeader *)exeFileData;
    //段的个数
    exeFormat->numSegments = ehdr->phnum;
    //代码入口地址
    exeFormat->entryAddr = ehdr->entry;
    //获取头部表在文件中的地址
    programHeader *phdr = (programHeader *)(exeFileData + ehdr->phoff);
    //填充Exe_Segment
    unsigned int i;
    for (i = 0; i < exeFormat->numSegments; i++, phdr++)
    {
        struct Exe_Segment *segment = &exeFormat->segmentList[i];
        //获取该段在文件中的偏移量*
        segment->offsetInFile = phdr->offset;
        //获取该段的数据在文件中的长度
        segment->lengthInFile = phdr->fileSize;
        //获取该段在用户内存中的起始地址
        segment->startAddress = phdr->vaddr;
        //获取该段在内存中的大小
        segment->sizeInMemory = phdr->memSize;
        //获取该段的保护标志位
        segment->protFlags = phdr->flags;
    }
    return 0;
}

我们所需要完成的编码部分就结束了,接下来我们再看看这整个过程是如何执行的。

代码执行流程

在main.c文件中,会新建一个线程并调用spawner()函数

static void Spawn_Init_Process(void)
{
  /* this thread will load&run  ELF files, see the rest in lprog.c */
  Print("Starting the Spawner thread...\n");    
  Start_Kernel_Thread( Spawner, 0, PRIORITY_NORMAL, true );
}

而这个Spawner()会读取a.c文件

image.png (运行时会打印这两句)

调用 Parse_ELF_Executable()函数。 image.png

并进行一些相关检查。

如果一切顺利的话,最终还会打印出这两个句子:

image.png

注意事项

正如完成记录(一)所说,如果遇到了打印出exception 13 的问题,记得在Makefile文件中,将CC_GENERAL_OPTS := $(GENERAL_OPTS)后加个-O0,表示不进行任何优化

参考资料