现在的位置: 首页 > 综合 > 正文

ELF文件解析

2019年05月20日 ⁄ 综合 ⁄ 共 3720字 ⁄ 字号 评论关闭

ELF,全称Executable and Linkable Format,可执行链接格式,是UNIX系统实验室(USL)作为应用程序二进制接口(Application Binary Interface,ABI)而开发和发布的。扩展名为elf。ELF 标准的目的是为软件开发人员提供一组二进制接口定义,这些接口可以延伸到多种操作环境,从而减少重新编码、重新编译程序的需要。接口的内容包括目标模块格式、可执行文件格式以及调试记录信息与格式等。
目标文件由三种类型:
1)可重定位文件,包含适合于与其他目标文件连接来创建可执行文件或者共享目标文件的代码和数据。
2)可执行文件,包含适合于执行的一个程序,此文件规定了exec()如何创建一个程序的进程映像。
3)共享目标文件,包含可在两种上下文中链接的代码和数据。首先链接编辑器可以将它和其它可重定位文件和共享目标文件一起处理,生成另外一个目标文件;其次,动态链接器(Dynamic Linker)可能将它与某个可执行文件以及其它共享目标一起组合,创建进程映像。
目标文件全部是程序的二进制表示,目的是直接在某种处理器上直接执行。

ELF文件由ELF头,Section和Section头三部分组成,其中section中记录了函数地址相关信息,下面我们着重看一下ELF文件结构:

文件开始处是一个ELF 头部(ELF Header),用来描述整个文件的组织。节区部分包含链接视图的大量信息:指令、数据、符号表、重定位信息等等。
ELF Header之后可能会有一个程序头部表(Program Header Table),如果存在的话,告诉系统如何创建进程映像。用来构造进程映像的目标文件必须具有程序头部表,可重定位文件不需要这个表。
节区头部表(Section Heade Table)包含了描述文件节区的信息,每个节区在表中都有一项,每一项给出诸如节区名称、节区大小这类信息。用于链接的目标文件必须包含节区头部表,其他目标文件可以有,也可以没有这个表。
另外,Sections是文件节区,它包含不同的节区,且节区没有规定的顺序。

ELF Header
文件的最开始几个字节给出如何解释文件的提示信息。这些信息独立于处理器,也独立于文件中的其余内容。
ELF Header结构体定义:
  #define EI_NIDENT       16
  typedef struct {
      unsigned char       e_ident[EI_NIDENT];
      Elf32_Half          e_type;
      Elf32_Half          e_machine;
      Elf32_Word          e_version;
      Elf32_Addr          e_entry;
      Elf32_Off           e_phoff;
      Elf32_Off           e_shoff;
      Elf32_Word          e_flags;
      Elf32_Half          e_ehsize;
      Elf32_Half          e_phentsize;
      Elf32_Half          e_phnum;
      Elf32_Half          e_shentsize;
      Elf32_Half          e_shnum;
      Elf32_Half          e_shstrndx;
  } Elf32_Ehdr;
其中e_ident定义:
 e_ident[] Identification Indexes
  Name           Value  Purpose
  ====           =====  =======
  EI_MAG0      0  File identification
  EI_MAG1      1  File identification
  EI_MAG2      2  File identification
  EI_MAG3      3  File identification
  EI_CLASS      4  File class
  EI_DATA      5  Data encoding
  EI_VERSION      6  File version
  EI_PAD      7  Start of padding bytes
  EI_NIDENT     16  Size of e_ident[ ]

以某elf文件为例,头部如下:
00h: 7F 45 4C 46 01 02 01 00 00 00 00 00 00 00 00 00
10h: 00 02 BA AB 00 00 00 01 00 00 00 00 00 00 00 34
20h: 00 00 21 C4 00 00 00 00 00 34 00 20 00 02 00 28
30h: 00 0A 00 07
7F: elf文件的第一个字节,固定为7F;
45: 'E'的ASCII码;
4C: 'L'的ASCII码;
46: 'F'的ASCII码;
前4个字节叫做一个魔术数(magic number),用来确定该文件是否为ELF的目标文件,所有ELF文件的魔数是相同的,这点在接下来的b.ko实例中可以得到验证
01: EI_CLASS,01表示是32-bit objects,02是64bit,00非法;
02: EI_DATA,编码方式;02表示big endian, 01表示little endian;
01: EI_VERSION,ELF头的版本号,目前只能设置为‘1’;
00 00 00 00 00:该变量标识了在e_ident中开始的未使用的字节。保留并被设置为0;
00 00 00 00 00: EI_NIDENT 16 SIZE
00 02: e_type,该成员确定该object的类型。2表示可执行文件;1可重定位文件;3共享目标文件;
BA AB: e_machine,该成员变量指出了运行该程序需要的体系结构。BA AB应该表示Xilinx的microblaze吧。
00 00 00 01: e_version, 这个成员确定object文件的版本。1表示当前版本。
00 00 00 00: e_entry, 程序入口虚地址。
00 00 00 34: e_phoff, 文件头偏移,表明文件头紧接在elf head后面。
00 00 21 C4: e_shoff, 节头表文件偏移;
00 00 00 00: e_flags, 处理器相关的标志
00 34: e_ehsize, 该成员保存着ELF头大小(以字节计数)。52个字节。
00 20: e_phentsize, 该成员保存着在文件的程序头表(program header table)中一个入口的大小(以字节计数)。所有的入口都是同样的大小。
00 02: e_phnum, 该成员保存着在程序头表中入口的个数。
00 28: e_shentsize,  该成员保存着section头的大小(以字节计数)。一个section头是在section头表(section header table)的一个入口;所有的入口都是同样的大小。
00 0A: e_shnum,该成员保存着在section header table中的入口数目.
00 07: e_shstrndx,  该成员保存着跟section名字字符表相关入口的section头表(section header table)索引。

Section Header
节区头部结构如下:


节区头是节区的索引,程序执行时先通过ELF Header找到Section Header,再通过这一索引找到对应的节区。

举例
我们写一段代码"b.c"

#include <linux/init.h>
#include <linux/module.h>

extern int printMSG(int n);

int __init hello_init(void){
	printk(KERN_ALERT "hello world");
	printMSG(3);
	return 0;
}
 
void __exit hello_exit(void){ 
	    printk(KERN_ALERT "byebye,hello world "); 
} 

module_init(hello_init); 
module_exit(hello_exit);

再为之写一个Makefile

obj-m := b.o
modules-objs:= mymodules.o 


KDIR := /lib/modules/`uname -r`/build
PWD := $(shell pwd)

default:
	make -C $(KDIR) M=$(PWD) modules

clean:
	rm -rf *.o  *.cmd *.ko *.mod.c *.tmp_versions

在终端make一下,即可编译生成elf文件b.ko
然后读取elf文件,执行命令:readelf -a b.ko | less即可对文件进行读取,结构如下图所示:

以上便是b.ko对应elf文件的ELF头以及Section头结构,节区头每行对应一个节区索引,如“.text”对应代码段节区。至于具体指令代码,以及各节区内容,可在终端执行命令:objdump -D b.ko | less查看之,仍以本程序为例,部分节区内容如下:

以上便是特定节区的内容,如.exit.text节区、.init.text节区等。研究过Linux内核的童鞋相信对vmlinux都不陌生,vmlinux文件其实就是一个巨大的ELF文件,感兴趣的话可以将其导出到文件进行仔细研究(文件比较大),你会从中得到很多内核参数信息哦!

抱歉!评论已关闭.