一个C语言源程序(.c文件)经过汇编以后生成目标文件(.o文件),目标文件再经过链接生成可执行文件。在linux系统中,目标文件和可执行文件都是ELF格式的,了解ELF文件的结构对于理解程序的编译、链接和装载运行至关重要。ELF文件的格式如下图所示,以文件头(ELF Header)开始,后面跟着代码段(.text)、数据段(.data)等。
ELF Header |
.text |
.data |
.bss |
.rodata |
… Other sections |
Section header table |
String Tables |
Symbol Tables |
… |
文件头中保存着文件的基本属性,要解析整个ELF文件,必须从文件头开始。下面自己编写一个小程序来读取ELF文件头,它的功能等价于Linux提供的命令readelf -h xxx.o。自己写代码实现这些命令能加深对ELF文件结构的理解。
ELF文件头的结构定义在/usr/include/elf.h文件中,分32位和64位两种:
32位:
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 entrycount */
Elf32_Half e_shentsize; /* Section header table entry size*/
Elf32_Half e_shnum; /* Section header table entrycount */
Elf32_Half e_shstrndx; /* Section header string tableindex */
} Elf32_Ehdr;
64位:
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf64_Half e_type; /*Object file type */
Elf64_Half e_machine; /*Architecture */
Elf64_Word e_version; /* Object file version */
Elf64_Addr e_entry; /* Entry point virtual address */
Elf64_Off e_phoff; /* Programheader table file offset */
Elf64_Off e_shoff; /* Sectionheader table file offset */
Elf64_Word e_flags; /*Processor-specific flags */
Elf64_Half e_ehsize; /* ELF header sizein bytes */
Elf64_Half e_phentsize; /* Program header table entry size */
Elf64_Half e_phnum; /* Program header table entry count */
Elf64_Half e_shentsize; /* Section header table entry size */
Elf64_Half e_shnum; /* Section header table entry count */
Elf64_Half e_shstrndx; /* Section header string table index */
} Elf64_Ehdr;
因为我的ubuntulinux是64位的操作系统,所以下面的程序中,我用的是Elf64_Ehdr类型的结构体elfheader来存储文件头。其中,Elf64_Half是unsigned short int类型(2字节),Elf64_Word是unsigned int类型(4字节),Elf64_Addr是unsigned long int类型(8字节),Elf64_Off是unsigned long int类型(8字节)。程序以只读方式(r或者rb模式)打开目标文件elftest2.o,然后用fread函数从文件中读出一块大小为sizeof(Elf64_Ehdr)(64字节)的内容到elfheader中,最后把文件头中每一部分打印输出。
//read_ELFheader.c (ubuntu)
#include “stdio.h”
#include “/usr/include/elf.h”
int main()
{
FILE * fp;
int i;
Elf64_Ehdr elfheader;
fp= fopen(“elftest2.o”,”r”);
fread(&elfheader,sizeof(Elf64_Ehdr),1,fp);
for(i=0;i<16; i++)
printf(“%x”,elfheader.e_ident[i]);
printf(“\n”);
printf(“%hx\n”, elfheader.e_type);
printf(“%hx\n”, elfheader.e_machine);
printf(“%x\n”, elfheader.e_version);
printf(“%lx\n”, elfheader.e_entry);
printf(“%lx\n”, elfheader.e_phoff);
printf(“%lx\n”, elfheader.e_shoff);
printf(“%x\n”, elfheader.e_flags);
printf(“%hx\n”, elfheader.e_ehsize);
printf(“%hx\n”, elfheader.e_phentsize);
printf(“%hx\n”, elfheader.e_phnum);
printf(“%hx\n”, elfheader.e_shentsize);
printf(“%hx\n”, elfheader.e_shnum);
printf(“%hx\n”, elfheader.e_shstrndx);
return 0;
}
读取了文件头以后,就可以利用elfheader.e_shoff (段表的偏移)来进一步读取段表,从而对各个段进行解析。