目的:熟悉ELF文件格式,了解GeekOS系统如何将ELF格式的可执行程序装入到内存,建立内核进程并运行的实现技术
要求:修改/geekos/elf.c文件:在函数Parse_ELF_Executable( )中添加代码,分析ELF格式的可执行文件(包括分析得出ELF文件头、程序头,获取可执行文件长度,代码段、数据段等信息),并填充Exe_Format数据结构中的域值
一般执行操作:(若有权限限制问题,输出前带上sudo)
1、在~/project0/build下编写bochsrc配置文件
2、(可多一步骤,清除缓存,make clean)生成系统镜像文件,make depend
3、编译,make
4、运行镜像文件,bochs -f bochsrc(有的是直接可以bochs)
实现效果
原理探究
内核线程怎样建立起的?---~include\geekos\lprog.c
Spawn_Init_Process()->Staet_Kernel_Thread()->Spawner()->Read_Fully()->Parse_ELF_Excutable()->Spawn_Program()
Spawn_Init_Process函数:调用Staet_Kernel_Thread函数,而Staet_Kernel_Thread以Spawner函数作为进程体(主体)来建立核心级进程
Staet_Kernel_Thread函数:创建内核进程,使用Kernel_Thread结构体
Spawner函数:调用Read_Fully函数读ELF可执行文件,调用Spawn_Program函数执行ELF文件
——Spawn_Program函数:根据Exe_Format结构体中Exe_Segment结构体,计算内存大小以及空间,并复制到用户内存空间,再通过Trampoline函数执行用户进程---lprog.c文件
Exe_Format和Exe_Segment结构体
struct Exe_Format{
struct Exe_Segment segmentList[EXE_MAX_SEGMENTS];
int numSegments; //定义了ELF文件中段的个数
ulong_t entryAddr; //代码入口地址
};
struct Exe_Segment{
ulong_t offsetInFile; //段在可执行文件中的偏移值
ulong_t lengthInFile; //段在可执行文件中的长度
ulong_t startAddress; //段在内存中的起始地址
ulong_t sizeInMemory; //段在内存中的大小
int protFlags; //保护标志
};
编写修改代码
1、修改/project1/src/geekos/elf.c,编写Parse_ELF_Executable函数
——参数:
exeFileData 已转入内存的可执行文件所占用的空间起始地址
exeFileLength 可执行文件的长度
exeFormat 保存分析elf文件信息的结构体指针
根据exeFileData 指向可得到ELF文件头,继而可得到程序多种信息
int Parse_ELF_Executable(char *exeFileData, ulong_t exeFileLength,struct Exe_Format *exeFormat)
{
//利用ELF头部结构体指向可执行文件头部,便于获取相关信息
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;
}
附加说明:
Start_Kernel_Thread函数的结构体:
struct Kernel_Thread* Start_Kernel_Thread(
Thread_Start_Func startFunc, //线程函数地址
ulong_t arg, //线程函数参数
int priority, //优先级
bool detached //线程属性,false为内核线程,true为用户线程
);
————~/src/geekos/elf.h
//ELF头部结构体——elfHeader
typedef struct {
unsigned char ident[16];
unsigned short type;
unsigned short machine;
unsigned int version;
unsigned int entry; //代码入口地址
unsigned int phoff; //偏移地址
unsigned int sphoff;
unsigned int flags;
unsigned short ehsize;
unsigned short phentsize;
unsigned short phnum; //定义了ELF文件中段的个数
unsigned short shentsize;
unsigned short shnum;
unsigned short shstrndx;
} elfHeader;//ELF头部表结构体——programHeader
typedef struct {
unsigned int type;
unsigned int offset; //段在可执行文件中的偏移值
unsigned int vaddr; //段在内存中的起始地址
unsigned int paddr;
unsigned int fileSize; //段在可执行文件中的长度
unsigned int memSize; //段在内存中的大小unsigned int flags; //保护标志
unsigned int alignment;
} programHeader;
2、修改/project1/src/geekos/lprog.c
将virtSize局部变量修改为静态全局变量,即文件头部添加static unsigned long virtSize;
修改Printrap_Handler函数
static void Printrap_Handler( struct Interrupt_State* state )
{
char *msg;
/* 此处修改以下内容以正确显示 a.c 中的局部变量 */
if (state->eax <= virtSize)
msg = (char *)virtSpace + state->eax;
else
msg = (char *)state->eax;
print(msg);
g_needReschedule = true;
return;
}
3、修改~src/geekos/main.c中的Spawn_Init_Process函数,创建内核进程
Start_Kernel_Thread( Spawner, 0, PRIORITY_NORMAL, true );
补充说明
————~\src\geekos\lprog文件中
图像界面输出的前两句为a.c变量的输出
后两句为spawner函数输出内容
其中spawner函数引用了a.exe,即可输出a.c的内容
spawner函数的顺利执行,则表示ELF在内核建立并完成运行!
参考文献