本文正在参加「金石计划」
项目要求
了解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?
项目完成思路
- 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文件
(运行时会打印这两句)
调用 Parse_ELF_Executable()函数。
并进行一些相关检查。
如果一切顺利的话,最终还会打印出这两个句子:
注意事项
正如完成记录(一)所说,如果遇到了打印出exception 13 的问题,记得在Makefile文件中,将CC_GENERAL_OPTS := $(GENERAL_OPTS)后加个-O0,表示不进行任何优化
参考资料
- geekos项目源码中的doc
- w3cschool-带你认识Linux中的ELF文件