先來解釋一下名詞,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;