200 likes | 476 Views
GeekOS 操作系统实验 设计项目 1. 西北工业大学 计算机学院 刘尊 史豪斌 潘炜. 课程博客: http://hi.baidu.com/geekos/. 设计目的. 熟悉 ELF 文件格式,了解 GeekOS 系统如何将 ELF 格式的用户可执行程序装入到内存,建立内核进程并运行的实现技术。. 设计要求.
E N D
GeekOS操作系统实验设计项目1 西北工业大学 计算机学院 刘尊 史豪斌 潘炜 课程博客:http://hi.baidu.com/geekos/
设计目的 • 熟悉ELF文件格式,了解GeekOS系统如何将ELF格式的用户可执行程序装入到内存,建立内核进程并运行的实现技术。
设计要求 • 修改/geekos/elf.c文件:在函数Parse_ELF_Executable()中添加代码,分析ELF格式的可执行文件(包括分析得出ELF文件头、程序头,获得可执行文件长度,代码段、数据段等信息),并填充Exe_Format数据结构中的域值。 • 掌握GeekOS在核心态运行用户程序的原理,为项目2的实现做准备。
ELF文件格式 • 可执行文件 对于可执行文件有三个重要的概念:编译、连接、加载。源程序文件首先被编译成目标文件,多个目标文件被连接成一个可执行文件,最后可执行文件被加载到内存运行。
ELF头 • ELF头也叫ELF文件头,它位于文件中最开始的地方。 #define EI_NIDENT 16 typedef struct elf32_hdr{ unsigned char e_ident[EI_NIDENT]; Elf32_Half e_type; Elf32_Half e_machine; Elf32_Word e_version; Elf32_Addr e_entry; /* Entry point */ Elf32_Off e_phoff; Elf32_Off e_shoff; Elf32_Word e_flags; Elf32_Half e_ehsize; Elf32_Half e_phentsize; Elf32_Half e_phnum; Elf32_Half e_shentsize; Elf32_Half e_shnum; Elf32_Half e_shstrndx; } Elf32_Ehdr;
Elf32_Ehdr->e_ident[] (Magic) • 这个字段是ELF头结构中的第一个字段,在elf.h中EI_NIDENT被定义为16,因此它占用16个字节。e_ident的前四个字节顺次应该是0x7f、 0x45、 0x4c、 0x46,也就是"\177ELF"。这是ELF文件的标志,任何一个ELF文件这四个字节都完全相同。 • 16进制 8进制 字母 • 0x7f 0177 • 0x45 E • 0x4c L • 0x46 F • 第5个字节标志了ELF格式是32位还是64位,32位是1,64位是2。 • 第6个字节,在0x86系统上是1,表明数据存储方式为低字节优先。 • 第7个字节,ELF文件头的版本信息
ELF头部分析 • Elf32_Ehdr->e_type (Type) • ELF文件的类型,1表示此文件是重定位文件,2表示可执行文件,3表示此文件是一个动态连接库。 • Elf32_Ehdr->e_machine (Machine) • CPU类型,它指出了此文件使用何种指令集。如果是Intel 0x386 CPU此值为3,如果是AMD 64 CPU此值为62也就是16进制的0x3E。 • Elf32_Ehdr->e_version (Version) • ELF文件版本,为1。 • Elf32_Ehdr->e_entry (Entry point address) • 可执行文件的入口虚拟地址。此字段指出了该文件中第一条可执行机器指令在进程被正确加载后的内存地址! Elf32_Ehdr->e_phoff (Start of program headers) • 程序头在ELF文件中的偏移量。如果程序头不存在此值为0。
ELF头部分析 • Elf32_Ehdr->e_shoff (Start of section headers) • 节头在ELF文件中的偏移量。如果节头不存在此值为0。 • Elf32_Ehdr->e_ehsize (Size of -ELF header) • 它描述了“ELF头”自身占用的字节数。 • Elf32_Ehdr->e_phentsize (Size of program headers) • 程序头中的每一个结构占用的字节数。程序头也叫程序头表,可以被看做一个在文件中连续存储的结构数组,数组中每一项是一个结构,此字段给出了这个结构占用的字节大小。e_phoff指出程序头在ELF文件中的起始偏移。 • Elf32_Ehdr->e_phnum (Number of program headers) • 此字段给出了程序头中保存了多少个结构。如果程序头中有3个结构则程序头(程序头表)在文件中占用了(3×e_phentsize)个字节的大小。
ELF头部分析 • Elf32_Ehdr->e_shentsize (Size of section headers) • 节头中每个结构占用的字节大小。节头与程序头类似也是一个结构数组,关于这两个结构的定义将分别在讲述程序头和节头的时候给出。 • Elf32_Ehdr->e_shnum (Number of section headers) • 节头中保存了多少个结构。 • Elf32_Ehdr->e_shstrndx (Section header string table index) • 这是一个整数索引值。节头可以看作是一个结构数组,用这个索引值做为此数组的下标,它在节头中指定的一个结构进一步给出了一个“字符串表”的信息,而这个字符串表保存着节头中描述的每一个节的名称,包括字符串表自己也是其中的一个节。
程序头(程序头表) -- Program Header • 程序头有时也叫程序头表,它保存了一个结构数组(结构Elf32_Phdr的数组)。程序头是从加载执行的角度看待ELF文件的结果,从它的角度ELF文件被分成许多个段。每个段保存着用于不同目的的数据,有的段保存着机器指令,有的段保存着已经初始化的变量;有的段会做为进程映像的一部分被操作系统读入内存,有的段则只存在于文件中。
typedef struct elf32_phdr{ Elf32_Word p_type; Elf32_Off p_offset; Elf32_Addr p_vaddr; Elf32_Addr p_paddr; Elf32_Word p_filesz; Elf32_Word p_memsz; Elf32_Word p_flags; Elf32_Word p_align; } Elf32_Phdr;
段结构分析 • Elf32_Phdr->p_type • 段的类型,它能告诉我们这个段里存放着什么用途的数据。此字段的值是在elf.h中定义了一些常量。例如1(PT_LOAD)表示是可加载的段,这样的段将被读入程序的进程空间成为内存映像的一部分。段的种类再不断增加,例如7(PT_TLS)在以前就没有定义,它表示用于线程局部存储。 • Elf32_Phdr->p_flags • 段的属性。它用每一个二进制位表示一种属,相应位为1表示含有相应的属性,为0表示不含那种属性。其中最低位是可执行位,次低位是可写位,第三低位是可读位。如果这个字段的最低三位同时为1那就表示这个段中的数据加载以后既可读也可写而且可执行的。同样在elf.h文件中也定义了一此常量(PF_X、 PF_W、PF_R)来测试这个字段的属性,做为一个好习惯应该尽量使用这此常量。 • Elf32_Phdr->p_offset • 该段在文件中的偏移。这个偏移是相对于整个文件的。
段结构分析 • Elf32_Phdr->p_vaddr • 该段加载后在进程空间中占用的内存起始地址。 • Elf32_Phdr->p_paddr • 该段的物理地地址。这个字段被忽略,因为在多数现代操作系统下物理地址是进程无法触及的。 • Elf32_Phdr->p_filesz • 该段在文件中占用的字节大小。有些段可能在文件中不存在但却占用一定的内存空间,此时这个字段为0。 • Elf32_Phdr->p_memsz • 该段在内存中占用的字节大小。有些段可能仅存在于文件中而不被加载到内存,此时这个字段为0。 • Elf32_Phdr->p_align • 对齐。现代操作系统都使用虚拟内存为进程序提供更大的空间,分页技术功不可没,页就成了最小的内存分配单位,不足一页的按一页算。所以加载程序数据一般也从一页的起始地址开始,这就属于对齐。
节头(节头表) -- Section Header • 节头也叫节头表。ELF头的e_shoff字段给出了节头在整个文件中的偏移(如果节头存在的话),节头可看做一个在文件中连续存储的结构数组(Elf32_Shdr结构的数组),数组的长度由ELF头的e_shnum字段给出,数组中每个结构的字节大小由ELF头的e_shentsize字段给出。把文件指针移到在ELF头中e_shoff给出的位置,然后读出的内容就是节头了。节头表是从连接角度看待ELF文件的结果,所以从节头的角度ELF文件分成了许多的节,每个节保存着用于不同目的的数据,这些数据可能被前面提到的程序头重复引用。
typedef struct { Elf32_Word sh_name; Elf32_Word sh_type; Elf32_Word sh_flags; Elf32_Addr sh_addr; Elf32_Off sh_offset; Elf32_Word sh_size; Elf32_Word sh_link; Elf32_Word sh_info; Elf32_Word sh_addralign; Elf32_Word sh_entsize; } Elf32_Shdr;
设计提示 • 在/src/geekos/main.c文件中,用户可以看到函数调用Spawn_Init_Process,该函数的功能是调用Start_Kernel_Thread(),它以Spawner函数为进程体建立一个核心级进程,并使之准备就绪。函数Spawner主要功能是从PFAT文件系统读入一个用户执行程序,为之建立相应的进程影像,然后调用Spawn_Program函数建立相应的进程。 Spawner函数代码在目录:/src/geekos/lprog.c。 • 函数Spawn_Program在/src/geekos/lprog.c文件中已经实现,参数的数据结构Exe_Format和Exe_Segment的定义在elf.h文件中。
设计提示 • Spawn_Program函数完成如下的工作。 (1)根据Exe_Format中的Exe_Segment结构提供的用户程序段信息,及用户进程堆栈大小计算用户进程所需的最大内存空间,即要分配给用户进程的内存空间。 (2)为用户程序分配内存空间,并全部初始化为零,否则系统后面运行可能出错。 (3)根据段信息将用户程序中的各段内容复制到分配的用户内存空间。 (4)根据Exe_Segment提供的用户段信息初始化代码段、数据段、以及堆栈段的段描述符和段选择子。
设计提示 • 由于系统已经完成了内核的所有函数,用户在完成本项目设计时要完成的任务比较简单,只需要完成函数Parse_ELF_Executable即可。函数Parse_ELF_Executable声明为: int Parse_ELF_Executable(char *exeFileData, ulong_t exeFileLength,Struct Exe_Format *exeFormat) 参数:exeFileData-已装入内存的可执行文件所占用空间的起始地址;exeFileLength-可执行文件长度;exeFormat-保存分析得到的ELF文件信息的结构体指针。
程序运行结果 Hi ! This is the first string Hi ! This is the second string Hi ! This is the last string If you see this you're happy