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:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x400670
  Start of program headers:          64 (bytes into file)
  Start of section headers:          7032 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         31
  Section 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;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章