本文主要是介绍lef文件的深入研究,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
先来解释一下名词,ELF的英文全称是Executable and Linkable Format。可执行和可链接的文件。
和elf文件对应的是bin文件,bin文件是直接加载到内存中执行的文件,用uboot直接把bin文件拷贝到bin文件的运行地址,(注意,一定要拷贝到运行地址)这时使用go命令就能够执行刚才拷贝的bin文件。
elf文件需要用加载器进行加载,由于elf文件已经包含了程序的加载地址,因此可以把elf文件复制到内存中的非bin文件加载地址。(这里所说的bin文件是加载器解析elf完成以后的bin文件内容)。
了解elf文件的结构可以使用readelf命令 ,先用readelf -h 来看一下elf头信息。
jiefang@jiefang-virtual-machine:/home/yhl/worktest$ readelf -h a.out
ELF Header:Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64Data: 2's complement, little endianVersion: 1 (current)OS/ABI: UNIX - System VABI Version: 0Type: EXEC (Executable file)Machine: Advanced Micro Devices X86-64Version: 0x1Entry point address: 0x400670Start of program headers: 64 (bytes into file)Start of section headers: 7032 (bytes into file)Flags: 0x0Size of this header: 64 (bytes)Size of program headers: 56 (bytes)Number of program headers: 9Size of section headers: 64 (bytes)Number of section headers: 31Section header string table index: 28
jiefang@jiefang-virtual-machine:/home/yhl/worktest$
可以看到elf文件包含的头信息,这里注意一下几点信息,
第一是幻数的第2,3,4个,他们是'E' ,'L','F'三个字母,在解析的时候可以通过他们来判断文件的类型是否正确。
第二个是Entry point address,这个就是解析出来的bin文件需要存放的地址。
接下来我们通过C代码自己来解析一下elf文件的头信息,在/usr/include/目录下找到elf.h文件。
typedef struct
{unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */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;
可以找到这个结构,注意,在结构体的上方有这么一句话,
/* The ELF file header. This appears at the start of every ELF file. */
这说明,我们可以把elf文件定位到0位置,然后用该结构体去对齐就好。下面是部分实现代码:
Elf32_Ehdr *elf_head; //elf 头文件 大小为52个字节Elf32_Phdr *prg_head; //程序头int fd = open("./bootrom",O_RDWR);if(fd<0){printf("open file error\n");}//开始解析elf头elf_head = (Elf32_Ehdr *)malloc(sizeof(Elf32_Ehdr));read(fd,elf_head,(sizeof(Elf32_Ehdr)));if(elf_head->e_ident[0]==0x7f){printf("this is a elf file\n");}else{printf("this isn't a elf file\n");goto elf_head_err;}printf("p_shnum = %d\n",elf_head->e_phnum);printf("p_shoff = %d\n",elf_head->e_phoff);printf("e_phentsize = %d\n",elf_head->e_phentsize);
这里只解析了程序头的一些信息,为下面elf转bin文件做好铺垫。
这里先来看一张图,来说明elf文件的结构:
链接视图是以节(section)为单位,执行视图是以段(segment)为单位。链接视图就是在链接时用到的视图,而执行视图则是在执行时用到的视图。上图左侧的视角是从链接来看的,右侧的视角是执行来看的。
我们的目的是C语言实现elf文件的加载,因此这里不关注左侧的。首先找到程序头的开始位置,在efl文件头中已经有,我们只需要在头文件中找到程序头对应的结构体,然后对应一下就可以了。
typedef struct
{Elf32_Word p_type; /* Segment type */Elf32_Off p_offset; /* Segment file offset */Elf32_Addr p_vaddr; /* Segment virtual address */Elf32_Addr p_paddr; /* Segment physical address */Elf32_Word p_filesz; /* Segment size in file */Elf32_Word p_memsz; /* Segment size in memory */Elf32_Word p_flags; /* Segment flags */Elf32_Word p_align; /* Segment alignment */
} Elf32_Phdr;
下面的实现代码来源于uboot源代码中的do_bootvx()函数:
/** A very simple ELF loader, assumes the image is valid, returns the* entry point address.** The loader firstly reads the EFI class to see if it's a 64-bit image.* If yes, call the ELF64 loader. Otherwise continue with the ELF32 loader.*/
static unsigned long load_elf_image_phdr(unsigned long addr)
{Elf32_Ehdr *ehdr; /* Elf header structure pointer */Elf32_Phdr *phdr; /* Program header structure pointer */int i;ehdr = (Elf32_Ehdr *)addr;if (ehdr->e_ident[EI_CLASS] == ELFCLASS64)return load_elf64_image_phdr(addr);phdr = (Elf32_Phdr *)(addr + ehdr->e_phoff);/* Load each program header */for (i = 0; i < ehdr->e_phnum; ++i) {void *dst = (void *)(uintptr_t)phdr->p_paddr;void *src = (void *)addr + phdr->p_offset;debug("Loading phdr %i to 0x%p (%i bytes)\n",i, dst, phdr->p_filesz);if (phdr->p_filesz)memcpy(dst, src, phdr->p_filesz);if (phdr->p_filesz != phdr->p_memsz)memset(dst + phdr->p_filesz, 0x00,phdr->p_memsz - phdr->p_filesz);flush_cache((unsigned long)dst, phdr->p_filesz);++phdr;}return ehdr->e_entry;
}
在for循环中,需要循环e_phnum次,这个参数只elf头中说明段的个数的结构成员。也就是说需要把每个段从自己的偏移地址拷贝到物理地址中去。两个if是在判断段中的内容,具体现在还说不清,以后补充,留下一个?。
以上内容就实现了一个elf文件加载器,但是只能在物理内存上面运行,虚拟内存不能给固定的地址写数据,以后在探索一下。
暂时留下两个问题
1、段中数据内容,.text .data .bbs 是如何在内存中分布的。链接脚本??
2、虚拟内存怎么给固定的地址上些数据??类似于裸机程序 int addr = 0x12345678; *(int *)addr=123;
这篇关于lef文件的深入研究的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!