1. elf文件基本概念
elf(Executable and Linkable Format)文件是一种目标文件格式,是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件。
2. elf文件结构组成
ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。
实际上,一个文件中不一定包含全部内容,而且它们的位置也未必如同所示这样安排,只有ELF头的位置是固定的,其余各部分的位置、大小等信息由ELF头中的各项值来决定。
ELF文件格式提供了两种视图,分别是链接视图和执行视图。(加载过程主要关注执行视图)
链接视图是以节(section)为单位,执行视图是以段(segment)为单位。链接视图就是在链接时用到的视图,而执行视图则是在执行时用到的视图。上图左侧的视角是从链接来看的,右侧的视角是执行来看的。
- ELF header: 描述整个文件的组织。
- Program Header Table: 描述文件中的各种segments,用来告诉系统如何创建进程映像的。
- sections 或者 segments:segments是从运行的角度来描述elf文件,sections是从链接的角度来描述elf文件,也就是说,在链接阶段,我们可以忽略program header table来处理此文件,在运行阶段可以忽略section header table来处理此程序(所以很多加固手段删除了section header table)。从图中我们也可以看出,segments与sections是包含的关系,一个segment包含若干个section。
- Section Header Table: 包含了文件各个segction的属性信息。
2.1 ELF文件结构-elf header
这里以32位的ELF header结构体为例
#define EI_NIDENT 16
typedef struct{
unsigned char e_ident[EI_NIDENT]; /* MagicNum和其他信息 */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
}Elf32_Ehdr;
e_ident : ELF的一些标识信息,前四位为.ELF,其他的信息比如大小端等(16字节)
e_machine : 文件的目标体系架构,比如ARM(2字节)
e_version : 0为非法版本,1为当前版本(4字节)
e_entry : 程序入口的虚拟地址(32位4字节,64位8字节)
e_phoff : 程序头部表偏移地址(32位4字节,64位8字节)
e_shoff : 节区头部表偏移地址(32位4字节,64位8字节)
e_flags :保存与文件相关的,特定于处理器的标志(4字节)
e_ehsize :ELF头的大小(2字节,对于32位这个值是52,64位是64)
e_phentsize : 每个程序头部表的大小(2字节)
e_phnum :程序头部表的数量(2字节)
e_shentsize:每个节区头部表的大小(2字节)
e_shnum : 节区头部表的数量(2字节)
e_shstrndx:节区字符串表位置(2字节)
在Linux环境下可以通过”readelf -h <elf file name>“获取ELF文件header信息。
可以通过执行命令”readelf -S <elf file name>”来查看该可执行文件中有哪些section。
通过执行命令"readelf –segments <elf file name>",可以查看该文件的执行视图。
3. ELF文件装载
由上图的程序头可知,该elf文件有3个LOAD类型的segment,因为只有LOAD类型是需要被映射的。
我们需要做的是找到这些segment在文件中的位置,并将其加载到对应的内存空间,对于memsiz大于filesiz的部分全部填充为0,加载完之后让程序跳转到入口地址
4. 代码实现
/* Xilinx Header */
#include "xil_io.h"
/* Project Header */
#include "load.h"
#include "mRam.h"
#include "ffu.h"
Elf32_Ehdr hdr = {0};
uint32_t load_exec(void)
{
uint32_t i = 0;
uint8_t *mRamBuffer = (uint8_t *)(APP_ELF_ADDR);
Elf32_Phdr phdr = {0};
/* 确认是否为elf格式 */
memcpy(&hdr, mRamBuffer, sizeof(Elf32_Ehdr));
if ((hdr.ident[0] != 0x7f) || (hdr.ident[1] != 'E') || (hdr.ident[2] != 'L') || (hdr.ident[3] != 'F'))
{
xil_printf("Invalid ELF header\n\r");
return -1;
}
else
{
xil_printf("hdr.phnum = %d\n\r", hdr.phnum);
for (i = 0; i < hdr.phnum; i++)
{
memcpy(&phdr, (mRamBuffer + hdr.phoff + i * sizeof(phdr)), sizeof(Elf32_Phdr));
if (phdr.type == PT_LOAD)
{
memcpy((void*)phdr.paddr, (uint8_t *)(mRamBuffer + phdr.offset) , phdr.filesz);
}
/* 多余的空间写0,做BSS段 */
if (phdr.memsz > phdr.filesz)
{
memset((void*)(phdr.paddr + phdr.filesz), 0, phdr.memsz - phdr.filesz);
}
}
}
xil_printf("Move elf to DDR OK\n\r");
xil_printf("entryAddr = 0x%x\n\r", hdr.entry);
((void (*)())hdr.entry)();
return 0;
}
5. 问题思考
【memset效率太低】从实测过程发现加载elf文件到内存过程中,多余的空间写0会耗费大量时间,memset 15MB空间耗时约为7s。
【BSS段为什么要初始化】