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;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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