使用C语言加载一个ELF格式可执行文件并执行

加载ELF可执行文件

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

使用ELF文件的原因

image-20231020223202664

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

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

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

image-20240215132859178

转换前elf文件的大小

image-20240215133156701

转换后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记录的是各个段的信息, 一般用于链接文件的时候使用, 其他的文件可能没有

image-20231020224403287

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

文件头

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

image-20231020224452753

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

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

文件加载用的表

image-20240215144828523

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

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

p_filesz: 文件的大小

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

实际的实现(数据格式)

image-20231020225133496

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

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

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

image-20231020230307421

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         1

typedef 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

image-20240215145406347

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文件

image-20231020232315988

读取前几个字节进行比较, 看一看是不是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);
	```
	
  • 16
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值