庞大资源库的计算机教程网站!
设为首页
加入收藏
总编信箱
投稿或申请专栏请先 [登 陆]
首页 操作系统 程序设计 图形图像 媒体动画 机械电子 WEB开发 数 据 库 办公系列 路由技术 网络原理 网络应用
认证考试 安全技术
首页>操作系统>Linux>系统管理>正文
资料搜索
Google搜索
Google
返回上级列表

推荐文章

快速保存网页中所有图片的方法
Windows中让光驱巧妙“隐身”技
防范非法用户入侵Win 2000/XP系
两款比较典型的ASP木马防范方法
有关表格边框的css语法整理
Windows XP中可以被禁用的服务
SQL Server导出导入数据方法
Javascript所有对象的属性的获
网页(HTML)中的特殊字符
与篮球共舞,尽显模式本色
QQ病毒的手工清除方法
Photoshop为极品美女打造性感睫
天衣无缝:IIS与PHP水火也相容
SQL Server存储过程编写和优化

解读 ELF 文件

 作者:wangdb    日期:2005-4-12
字号选择〖 〗/ 双击滚屏 单击停止   
本文叙述如何解读 ELF 文件。

打开一个 ELF 文件解读时,我们首先遇到的是一个 ELF 文件头。ELF 文件头
给出解读整个 ELF 文件的路径图,它是一个固定的结构。文件头的结构在系统
头文件 elf.h 中定义,如果是 32 位的二进制文件,它是一个 Elf32_Ehdr
结构,如果是 64 位的二进制文件,则是一个 Elf64_Ehdr 结构。无论是何种
结构,结构的第一个成员是一个 16 字节的 e_ident,它给出了整个 ELF 文
件的解读方式。究竟是 32 位的 Elf32_Ehdr 结构还是 64 位的 Elf64_Ehdr
结构,就看 e_ident[4] 的内容了。从文件偏移的角度来说,也就是文件偏移
为 4 的字节确定了 ELF 文件究竟是 32 位的还是 64 位的。这里我们遵从习
惯把文件开头的起始第一个字节的文件偏移约定为 0,下面的所有叙述都遵从
这个约定。

于是我们要做的第一件事是解读这个 e_ident,确定 ELF 文件是 32 位的还
是 64 位的,或者是其他位数的,从而确定 ELF 文件头的结构。为此,假定
打开ELF 文件时返回的文件描述符是 fd,

lseek(fd, 0, SEEK_SET);
read(fd, buf, 16);

读出的 buf 里前四个字节是 Magic Number(对应文件偏移 0-3)。如果

buf[0] = 0x7f、buf[1] = 'E'、buf[2] = 'L'、buf[3] = 'F'

则表明这是一个 ELF 格式的二进制文件,否则不是。如前面所述,我们首先关
注的是 buf[4]。如果 buf[4] 的值是 1,则是 32 位的;如果是 2,则是 64
位的。接下来是 buf[5],它给出字节序特性。如果它的值是 1,则是 LSB 的;
如果是 2,则是 MSB 的。对 Intel x86 机器,buf[5] = 1;对 Sun Sparc,
buf[5] = 2。跟着 buf[5] 的 buf[6] 给出 ELF 文件头的版本信息,当前它
的值是 EV_CURRENT(参见 elf.h 中的宏定义)。对 buf[6] = EV_CURRENT
的 ELF 文件头,从 buf[7] 开始,也即 e_ident 后面的 9 个字节全部为零,
暂时没有使用。

现在确定了文件头的结构,我们就可以解读文件头了。下文中我们以 32 位的
ELF 文件为例来说明。对 64 位的,大同小异,把所有 Elf32_*** 结构换成
对应的 Elf64_*** 结构,看看 elf.h 就什么都清楚了。32 位的 ELF 文件头
结构定义如下:

#define EI_NIDENT (16)

typedef uint16_t Elf32_Half;
typedef uint32_t Elf32_Word;
typedef uint32_t Elf32_Addr;
typedef uint32_t Elf32_Off;

typedef struct {
unsigned char e_ident[EI_NIDENT]; /* 上文所说的 e_ident */
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; /* ELF 文件头大小 */
Elf32_Half e_phentsize; /* 程序头表每个表项的大小 */
Elf32_Half e_phnum; /* 程序头表的表项数目 */
Elf32_Half e_shentsize; /* 节头表每个表项的大小*/
Elf32_Half e_shnum; /* 节头表的表项数目 */
Elf32_Half e_shstrndx; /* 节名字符串的节头表表项索引 */
} Elf32_Ehdr;

结构的各个成员的含义如注释中所解释的。对 ELF 文件,有两个视图,一个是
从装载运行角度的,另一个是从连接角度的。从装载运行角度,我们关注的是程
序头表,由程序头表的指引把 ELF 文件加载进内存运行它。从连接的角度,我
们关注节头表,由节头表的指引把各个节连接组装起来。e_type 的值与这两个
视图相联系,由它我们可以知道能够从哪个视图去解读。如果 e_type = 1,表
明它是重定位文件,可以从连接视图去解读它;如果 e_type = 2,表明它是可
执行文件,至少可以从装载运行视图去解读它;如果 e_type = 3,表明它是共
享动态库文件,同样可以至少从装载运行视图去解读它;如果 e_type = 4,表
明它是 Core dump 文件,可以从哪个视图去解读依赖于具体的实现。

按照这两个视图,整个 ELF 文件的内容这样来组织:首先是 ELF 文件头,也
就是上面的 Elf32_Ehdr 结构。或者对 64 位的 ELF 文件,是 Elf64_Ehdr
结构。ELF 文件头位于文件开始处,无论 e_type 的值是什么,它是必须有的。
其次是程序头表,对可执行文件(e_type = 2)和动态库文件(e_type = 3),它
是必须有的。对重定位文件(e_type = 1),程序头表的有无是可选的。例如用
gcc 的 -c 选项生成的 .o 文件,就没有程序头表。但无论如何,e_phoff 和
e_phnum、e_phentsize 给出了 ELF 文件的程序头表信息。没有程序头表时它
们的值为零。然后就是就是节头表,对可执行文件和动态库文件,它的有无是
可选的,对重定位文件,它是必须有的。e_shoff 和 e_shnum、e_shentsize
给出节头表信息。最后就是文件的代码和数据这些具体内容了。如果有节头表,
从连接视图去解读,ELF 文件的具体代码和数据内容是以节为单位组织的。所
有的代码和数据都分属于某一节,并且不能同时属于两个节。各个节不能交叉,
不能有同时两个节覆盖同一内容。每一节在节头表中有一个表项与之对应,给
出该节的相关信息。如果有程序头表,从装载运行视图去解读,所有代码和数
据都分属于某一程序段。与连接视图不同,此时有交叉的情况。某些内容可能
同时属于几个程序段,也即可能有几个段覆盖同一内容。同时,从程序头表来
看,可能某些段不包含任何具体的代码和数据内容。例如,给出动态连接信息
的程序段的所有内容都同时数据段。注意不要把这里所说的程序段与我们通常
所说的文本段、数据段和堆栈段这几个概念相混淆,虽然它们有联系。程序加
载进内存时,根据程序头表信息来就解读。

从连接视图来解读,其中有一节的内容是一些以零结尾的字符串。e_shstrndx
给出该节在节头表中的表项索引。这些字符串是各节的名字。

了解了这些后,我们可以分别从两个视图来解读 ELF 文件了。先看连接视图,
于是我们

Elf32_Ehdr e_hdr;
void *SecHdrTbl;

lseek(fd, 0, SEEK_SET);
read(fd, &e_hdr, sizeof(e_hdr));
SecHdrTbl = malloc(e_hdr.e_shnum * e_hdr.e_shentsize);
lseek(fd, e_hdr.e_shoff, SEEK_SET);
read(fd, SecHdrTbl, e_hdr.e_shnum * e_hdr.e_shentsize);

我们看看节头表是什么样的,因为节头表的各个表项给出了如何从连接视图
解读 ELF 文件的路径图。节头表的每个表项是一个如下的结构:

typedef struct
{
Elf32_Word sh_name; /* 节名索引 */
Elf32_Word sh_type; /* 节类型 */
Elf32_Word sh_flags; /* 加载和读写标志 */
Elf32_Addr sh_addr; /* 执行时的虚地址 */
Elf32_Off sh_offset; /* 在文件中的偏移 */
Elf32_Word sh_size; /* 字节大小 */
Elf32_Word sh_link; /* 与其他节的关联 */
Elf32_Word sh_info; /* 其他信息 */
Elf32_Word sh_addralign; /* 字节对齐 */
Elf32_Word sh_entsize; /* 如果由表项组成,每个表项的大小 */
} Elf32_Shdr;

再看装载运行视图:

void *ProHdrTbl;

ProHdrTbl = malloc(e_hdr.e_phnum * e_hdr.e_phentsize);
lseek(fd, e_hdr.e_phoff, SEEK_SET);
read(fd, SecHdrTbl, e_hdr.e_phnum * e_hdr.e_phentsize);

每个程序头表的每个表项的结构为:

typedef struct
{
Elf32_Word p_type; /* 段类型 */
Elf32_Off p_offset; /* 在文件中的偏移 */
Elf32_Addr p_vaddr; /* 执行时的虚地址 */
Elf32_Addr p_paddr; /* 执行时的物理地址 */
Elf32_Word p_filesz; /* 在文件中的字节数 */
Elf32_Word p_memsz; /* 在内存中的字节数 */
Elf32_Word p_flags; /* 标志 */
Elf32_Word p_align; /* 字节对齐 */
} Elf32_Phdr;

我们看一看这两个视图之间的相互关联,对动态库文件,共有三个程序段,如
果是用 gcc 编译生成的,按文件偏移和虚地址增长次序排列,文本段包含如下
这些节:

.hash
.dynsym
.dynstr
.gnu.version
.gnu.version_d
.gnu.version_r
.rel.data
.rel.got
.rel.plt
.init
.plt
.text
.fini
.rodata

同样是按文件偏移和虚地址增长次序排列,数据段包含如下这些节:

.data
.eh_frame
.ctors
.dtors
.got
.dynamic
.bss:

另外还有一个程序段,它给出动态连接信息,它只包含有一节

.dynamic

我们看到,这一段与数据段有交叉了。此外还有一些节它们不属于任何一个程
序段,这些节是:

.comment
.note
.shstrtab
.symtab
.strtab

对可执行文件,共有六个程序段,如果是用 gcc 编译生成的,按文件偏移和虚
地址增长次序排列,文本段包含如下这些节:

.interp
.note.ABI-tag
.hash
.dynsym
.dynstr
.gnu.version
.gnu.version_r
.rel.got
.rel.plt
.init
.plt
.text
.fini
.rodata

同样是按文件偏移和虚地址增长次序排列,可执行文件的数据段包含如下这些
节:

.data
.eh_frame
.ctors
.dtors
.got
.dynamic
.bss

程序解释段(INTERP)与文本段相交叉,只包含 .interp 一节。给出动态连接
信息的程序段同样与数据段相交叉,只包含 .dynamic 节。另一个程序段,与
文本段相交叉,包含 .note.ABI-tag 节,它给出辅助信息。此外,还有一个
程序段,它指程序头表自身。同动态库文件一样,下面的一些节不属于任何程
序段:

.stab
.stabstr
.comment
.note
.shstrtab
.symtab
.strtab

[未完待续]
上一篇:Samba服务器的设置    下一篇:Linux 故障恢复技巧  
[发送给好友]  [关闭窗口]  [返回顶部]   转载请注明来源:www.it00.com   
特别声明: 本站除部分特别声明禁止转载的专稿外的其他文章可以自由转载,但请务必注明出处和原始作者。文章版权归文章原始作者所有。对于被本站转载文章的个人和网站,我们表示深深的谢意。如果本站转载的文章有版权问题请联系编辑人员,我们尽快予以更正。
责任编辑: 原点 投稿作者: wangdb
信息来源: 网络 录入时间: 2005-4-12
关于我们 - 广告服务 - 版权申明 - 网站地图 - 联系方式 - 总编信箱 - 会员投稿