加载ELF可执行文件

视频讲解可以看这一个课程

使用ELF文件的原因

在这里把文件转化为二进制文件格式, 这里面没有其他的附加的信息, 但是把文件名改为elf结尾, 实际上是bin文件

SECTIONS { . = 0x100000; .text : { *(.text) } .rodata : { *(.rodata) } . = 0x200000; .data : { *.(data) } .bss : { *.(bss) }}

如果链接文件的时候有一大块的空白区域

转换前elf文件的大小

转换后bin文件的大小(这一个后缀不对)

这样会有一个问题, 当把文件的数据段代码段分开存放, 通过链接脚本进行更改, 这时候两段数据之间的数据就也会初始化为0之后被保存在elf文件中, 会导致elf文件变大, 加载的时候还会修改中间的文件, 并且不容易设置文件的权限, 因为没有记录段配置的信息, 这时候就需要使用elf文件了, 相当于exe文件

而且内核加载这一种文件的时候只能全部进行加载, 如果没有使用的内存位置有数据的话会被覆盖, 并且不利于权限的处理(不知道各个段的位置)

elf文件

ELF文件用于存放二进制文件, 可执行文件, 目标代码, 共享库和核心转储存文件的文件格式

主要有三个字段

An ELF header resides at the beginning and holds a ‘‘road map’’ describing the file’s organization. Sections hold the bulk of object file information for the linking view: instructions, data, symbol table, relocation information, and so on. Descriptions of special sections appear later in Part 1. Part 2 discusses segments and the program execution view of the file

A program header table, if present, tells the system how to create a process image. Files used to build a process image (execute a program) must have a program header table; relocatable files do not need one. A section header table contains information describing the file’s sections. Every section has an entry in the table; each entry gives information such as the section name, the section size, etc. Files used during linking must have a section header table; other object files may or may not have one

大致的意思是有一个文件头记录一些基础信息, program header table记录生成一个可执行文件的时候需要的信息, section header table记录的是各个段的信息, 一般用于链接文件的时候使用, 其他的文件可能没有

左边是给链接器使用的, 我们只需要关心右侧的数据

文件头

ELF格式, 有一个文件头, 相当于一个结构体

头部里面保存有指针, 指向一个表, 表里面有需要加载的数据的位置等信息

p_offset文件记录的是这一块内容在elf文件里面的偏移, 将数据加载到对应的指定的位置p_addr, 大小是p_filesz

文件加载用的表

p_offset记录实际加载的时候的信息相对ELF的偏移位置(原地址)

p_paddr: 实际加载的位置(目标地址)

p_filesz: 文件的大小

p_memsz: 加上需要清零的bss段的大小

实际的实现(数据格式)

把这一个生成为二进制的参数删去, 可以加上参数-S把调试使用的代码进行删去

这个时候就不能直接进行跳转然后进行执行了, 所以设置为在解析之后把数据放到64K的位置, 然后进行运行

主要是修改链文件把数据放到0x10000的位置, 之后就是获取函数跳转的地址, 在1M地址的数据是暂时加载到的位置

ELF数据手册的文件里面有各种数据大小的描述

为了不让编译器进行字节对齐, 添加一行指令#pragma pack(1)

这一个指令会让结构体里面的对齐方式是所有的项是相连的

/** * ELF相关头文件及配置 * * 作者:李述铜 * 联系邮箱: 527676163@qq.com */#ifndef OS_ELF_H#define OS_ELF_H#include "types.h"// ELF相关数据类型typedef uint32_t Elf32_Addr;typedef uint16_t Elf32_Half;typedef uint32_t Elf32_Off;typedef uint32_t Elf32_Sword;typedef uint32_t Elf32_Word;#pragma pack(1)// ELF Header#define EI_NIDENT 16#define ELF_MAGIC 0x7F//这一个是ELF的文件头部的数据结构, 在文件的开头, 从这里可以获取文件的各个段的位置以及其他信息typedef struct {char e_ident[EI_NIDENT];//一些标志位, 是一个字符串Elf32_Half e_type;//文件的类型Elf32_Half e_machine; //使用的机器的类型 Elf32_Word e_version;//文件的版本号Elf32_Addr e_entry;//虚拟地址, 实际程序需要跳转的位置(可执行入口地址)Elf32_Off e_phoff;//这一点是文件的第一段的数据表的位置, 虚拟地址Elf32_Off e_shoff;//这一个是段表记录的偏移Elf32_Word e_flags;Elf32_Half e_ehsize;//记录一下ELF头的大小Elf32_Half e_phentsize;//单个段表的大小Elf32_Half e_phnum;//表的数量Elf32_Half e_shentsize;//记录段表的信息Elf32_Half e_shnum;Elf32_Half e_shstrndx;}Elf32_Ehdr;#define PT_LOAD 1typedef struct {Elf32_Word p_type;Elf32_Off p_offset;//偏移位置Elf32_Addr p_vaddr;//虚拟地址用于使用虚拟地址的操作系统Elf32_Addr p_paddr;//要加载的地址(物理地址)Elf32_Word p_filesz;//记录文件需要拷贝的大小Elf32_Word p_memsz;//记录全部的大小(bss段未初始化的全局变量也加上了, 需要清零)Elf32_Word p_flags;//记录了可不可以加载的信息, 是PT_LOAD的时候是可以加载的Elf32_Word p_align;} Elf32_Phdr;#pragma pack()#endif //OS_ELF_H

PT_LOAD The array element specifies a loadable segment, described by p_filesz and p_memsz. The bytes from the file are mapped to the beginning of the memory segment. If the segment’s memory size (p_memsz) is larger than the file size (p_filesz), the ‘‘extra’’ bytes are defined to hold the value 0 and to follow the segment’s initialized area. The file size may not be larger than the memory size. Loadable segment entries in the program header table appear in ascending order, sorted on the p_vaddr member

实际的处理

检测是不是elf文件

读取前几个字节进行比较, 看一看是不是elf文件

获取数据以及进行转移

从e_phoff段里面获取Program header Table的位置

之后获取数据具体位置进行拷贝, bss段由于没有初始化所以不需要进行拷贝, 只需要初始化为0, 判断p_filesz和p_memsz的大小, p_memsz = p_filesz + 清零的区域

//加载elf文件, 同时返回需要跳转的文件的地址static uint32_t reload_elf_file(uint8_t * file_buffer){Elf32_Ehdr *elf_hdr = (Elf32_Ehdr *)file_buffer;//检测文件类型if((elf_hdr->e_ident[0] != 0x7f) || (elf_hdr->e_ident[1] != 'E') || (elf_hdr->e_ident[2] != 'L') || (elf_hdr->e_ident[3] != 'F')){return 0;}for(int i=0;i<elf_hdr->e_phnum;i++){//获取第i个表的位置Elf32_Phdr *phdr = (Elf32_Phdr *)(file_buffer + elf_hdr->e_phoff) + i;if(phdr->p_type != PT_LOAD){//内容不能加载continue;}//获取源文件地址以及需要加载的位置uint8_t *src = file_buffer + phdr->p_offset;uint8_t *dest = (uint8_t *)phdr->p_paddr;for(int j=0; j < phdr->p_filesz;j++)//进行文件的复制{*dest++ = *src++; }//计算结束地址, 对bss区域进行清零dest = (uint8_t *)phdr->p_paddr + phdr->p_filesz;for(int j=0;j<phdr->p_memsz-phdr->p_filesz;j++){*dest++ = 0;}}//返回进入的地址return elf_hdr->e_entry;}

执行

kernel_entery = reload_elf_file((uint8_t *)SYS_KERNEL_LOAD_ADDR);if(kernel_entery == 0){die(-1);}((void (*)(boot_info_t *))kernel_entery)(&boot_info);```