当前位置:首页 > 公众号精选 > 裸机思维

1. 前言

对 ELF 格式的理解,是成熟程序员的标志之一。

然而对于大多数想学习 ELF 格式的朋友来说,枯燥的书籍、文章根本读不下去,读下去了没有需求或项目实践也很快遗忘,只残留了字符串表、节区表之类的空洞概念。

本文从一个简单的 ELF 格式解析应用项目入手,带读者从实操角度掌握一个成熟程序员应该掌握的大多数有关 ELF 格式的知识。实现一个完整的 linker & loader 对理解 ELF 格式并无过多裨益,反而徒增旁枝末节(如果对完整的 linker & loader 感兴趣的话,可以阅读 TencentOS tiny 中的 elfloader 实现,代码紧凑、易读)。

本文适宜的读者对象:对 ELF 格式已经有所了解,但对于其中各个细节的理解尚做不到游刃有余,且希望能对 ELF 格式做进一步掌握的同学。本文不会啰里啰嗦的从 ELF 格式的细节着手,这方面推荐《程序员的自我修养》一书。

本文的翻译约定:section - "节区",segment - "段"。

2. 引子

2.1 什么是 ELF 格式

ELF 格式的本质就是一种描述可执行/可重定位文件信息的表的组织形式的约定。通过这张表,linker & loader 可以获取在装载、链接一个可执行文件时必要的信息。仅此而已。

2.2 项目需求

这个需求是我的好朋友提出的:希望通过在应用程序中添加特定的字段,这些字段中在编译期给应用程序添加一些类似校验的信息。这个应用程序在运行前需要先经过一个装载程序的校验,校验成功才继续运行此应用程序。

2.3 思路

这个需求包含了两个方面:

  1. 给应用程序(下文简称 "app")添加特定的字段
  2. 解析程序(下文简称 "parser")定位到 app 中的该字段,取出其中的内容并校验。

    给 app 添加特定的字段又有两种思路:

  1. 定义一个变量,此变量位于某个特殊的节区(section)。

这个节区要视情况而定:

  • 如果是在操作系统环境下开发,需要确保此节区的不会被 ld 自动 discard 掉(不在默认 linker script 的 discard 范围内)
  • 如果是非操作系统环境下,一般 linker script 是由 app 开发者维护,此时需开发者自行确保 linker script 中不会 discard 掉此段。
  1. 定义一个变量,此变量有一个特殊的变量名。

此方案要注意变量名不要与其他变量产生冲突。

    根据 app 中添加特定字段的手法不同,parser 的实现思路也对应两个不同的方向:

  1. parser 中定位到这个特殊节区之所在,然后从 app 中取出字段并校验。
  2. parser 中定位到 app 中这个特殊变量名的变量之所在,然后从 app 中取出字段并校验。

2.4 项目说明

    本项目非常简单,这里直接贴出全部代码,读者想要验证的可自行把代码拷贝到磁盘上并按如下目录组织:

$ tree
.
|-- section
| |-- 1.sh
| |-- app.c
| |-- include
| | |-- __elf.h
| | |-- compiler.h
| | `-- version.h
| `-- parser.c
`-- symbol
|-- 1.sh
|-- app.c
|-- include
| |-- __elf.h
| |-- compiler.h
| `-- version.h
`-- parser.c

    app.c、compiler.h、__elf.h、1.sh,以及 parser.c 的部分函数在 方案1 和 方案2 中是通用的,为不影响行文的连贯性,这部分代码直接贴在第 6 章。

3. 方案1:节区方案

3.1 app 侧

    对 app 里的校验字段,我们使用 version_descriptor 来定义。    这是 version.h:

#ifndef __VERSION_H__#define __VERSION_H__
#include "stdint.h"#include "compiler.h"
// 这是向 app 中添加校验字段的数据结构struct version_descriptor { char str[16]; uint32_t num0; uint32_t num1;};
#define __stringify_1(x...) #x#define __stringify(x...) __stringify_1(x)
#define DECLARE_VERSION(__str, __num0, __num1) \ static const struct version_descriptor ____version \ __used __section(.__version) \ = { \ .str = __stringify(__str), \ .num0 = __num0, \ .num1 = __num1, \ }
#endif

除了定义了描述字段的结构体 struct version_descriptor,还提供了 DECLARE_VERSION 宏,用于编译期生成一个位于 .__version_section 节区的 struct version_descriptor 类型变量(变量名不重要)。

DECLARE_VERSION 宏的实现是 gcc 中比较基础的 section 属性应用,这里不赘述。

    6.1 节 app.c 中定义了一个如下的 struct version_descriptor 变量:

.str  = "version_x".num0  = 0xdead.num1  = 0xbeef

3.2 parser 侧

1. 垫话

    parser 侧要做的事情就是定位到 .__version_section 在 app 中的位置,然后读出来。

    先说明一下 6.2 节 parser.c 中定义的几个函数的作用:

// 这里假定 app 程序的大小不会超过 10k 字节#define FILE_SIZE_MAX           (1024 * 10)// 内存中定义一片 buffer 来存放 app 程序内容char __buffer[FILE_SIZE_MAX];
// 此函数的功能是,将 path 指定的文件内容,读到 __buffer 缓冲里。int read_file2buffer(char *path);
/* * 一个 fake 的 read 函数,作用是将 app 程序中 offset 偏移处,长 size 的内容, * 读到 buf 指定的地方。 */int fake_read(void *buf, int offset, int size);
/* 一个 fake 的 read 函数,作用是读取 app 程序中 offset 偏移处的内容到 buf 中 * 一直读到 '\0' */int fake_read_till_nil(char *buf, int offset);

2. 总体流程

配合 图1 食用:

int main() {    int rc;    // 最终读出来的那个 version_descriptor 变量    struct version_descriptor __version;
// ELF 文件头 struct elfhdr __elfhdr; // .__version_section 节区的节区头 struct elf_shdr __version_section; // 字符串表的节区头 struct elf_shdr __shstr_tbl_section;
#if ELF_CLASS == ELFCLASS32 // 32 位架构 char *app_name = "./app_32";#elif ELF_CLASS == ELFCLASS64 // 64 位架构 char *app_name = "./app_64";#else#error "invalid arch"#endif
// app 文件读到 __buffer 中 rc = read_file2buffer(app_name); if (rc) exit(rc);
// 从 app 中读出其 ELF 文件头 rc = read_elf_header(&__elfhdr); if (rc) exit(rc);
// 读取字符串表节区头 rc = read_shstring_table(&__elfhdr, &__shstr_tbl_section); if (rc) exit(rc);
// 定位到 .__version 节区头 rc = read_section_by_name(".__version", sizeof(".__version"), &__elfhdr, &__shstr_tbl_section, &__version_section); if (rc) exit(rc);
// 以 .__version 节区在文件中的偏移,以及大小,读出其内容 fake_read(&__version, __version_section.sh_offset, __version_section.sh_size);
// 做后续的校验逻辑即可 printf("dumping the version:\n"); printf("%s\n", __version.str); printf("0x%x\n", __version.num0); printf("0x%x\n", __version.num1);
return 0;}

    2.1 节就说了,ELF 格式本质上就是一种表组织形式的约定。    根据 图1,我相信下面不贴代码你也能自己写出来上面每个关键函数的实现了。    此乃好图,值得收藏!

图1

3. 关键函数实现

    有了 图1,直接贴代码加注释了:

int read_elf_header(struct elfhdr *elfhdr){    // elf 文件头就在 0 偏移处,直接读 sizeof(struct elfhdr) 长度即可。    fake_read(elfhdr, 0, sizeof(*elfhdr));
// 这里校验一下魔术字 if (strncmp(elfhdr->e_ident, ELFMAG, sizeof(ELFMAG) - 1) == 0) { printf("load elf header ok\n"); return 0; } return -1;}
int read_shstring_table(struct elfhdr *elfhdr, struct elf_shdr *shstr_tbl_section){ struct elf_shdr shdr; elf_addr_t shdr_offset; // section header offset Elf_Half shnum, shentsize;
Elf_Half shstrndx;
// 这是节区头表在文件中的偏移 shdr_offset = elfhdr->e_shoff; // 节区头表中节区头的数量 shnum = elfhdr->e_shnum; // 节区头个数 shentsize = elfhdr->e_shentsize;
// 此乃该 elf 所使用的字符串表节区头在节区表中的下标 shstrndx = elfhdr->e_shstrndx;
assert(shstrndx < shnum);
// 去节区头表对应下标处,读出字符串表的节区头 fake_read(&shdr, shdr_offset + shstrndx * shentsize, sizeof(shdr)); assert(shdr.sh_type == SHT_STRTAB);
memcpy(shstr_tbl_section, &shdr, sizeof(shdr));
return 0;}
// 定位到 .__version 节区在文件中的偏移是关键技术int read_section_by_name(char *section_name, int name_size, struct elfhdr *elfhdr, struct elf_shdr *shstr_tbl_section, struct elf_shdr *the_shdr){#define SHDR_NAME_MAX 64 char shdr_name[SHDR_NAME_MAX]; struct elf_shdr shdr; elf_addr_t shdr_offset; // section header offset Elf_Half shnum, shentsize; int i;
// 如上文所述 shdr_offset = elfhdr->e_shoff; shnum = elfhdr->e_shnum; shentsize = elfhdr->e_shentsize;
// 这里挨个遍历所有的节区头,找到名字叫 .__version 的那个节区头 for (i = 0; i < shnum; ++i) { // 把节区头读出来 fake_read(&shdr, shdr_offset, sizeof(shdr));
/* 节区头的 sh_name 域,存的是该节区头的名字,在字符串表中的偏移 * 而上一步中已经定位到了字符串表节区头,shstr_tbl_section->sh_offset 就是字符串表在文件中的偏移 * shstr_tbl_section->sh_offset + shdr.sh_name 就是该节区名的字符串 */ fake_read_till_nil(shdr_name, shstr_tbl_section->sh_offset + shdr.sh_name);
// 如果能匹配上,证明找到了 if (strncmp(shdr_name, section_name, name_size) == 0) { assert(shdr.sh_type == SHT_PROGBITS && shdr.sh_flags == SHF_ALLOC); memcpy(the_shdr, &shdr, sizeof(shdr)); return 0; }
shdr_offset += shentsize; }
return -1;}

4. 方案2:符号方案

4.1 app 侧

    app 侧改动不大,因为这个方案是基于符号查找的,而不是基于节区查找的,因此并不要求 __version 变量位于一个特殊的节区。    相对于方案1,去除掉了 __section(.__version) 限制:

#ifndef __VERSION_H__#define __VERSION_H__
#include "stdint.h"#include "compiler.h"
// 这是向 app 中添加校验字段的数据结构struct version_descriptor { char str[16]; uint32_t num0; uint32_t num1;};
#define __stringify_1(x...) #x#define __stringify(x...) __stringify_1(x)
#define DECLARE_VERSION(__str, __num0, __num1) \ static const struct version_descriptor ____version \ __used \ = { \ .str = __stringify(__str), \ .num0 = __num0, \ .num1 = __num1, \ }
#endif

4.2 parser 侧

1. 垫话

    方案2 的实现相对来说要复杂一些,此方案依赖于符号的查找,逻辑会更多一些。    但算法本质还是查 ELF 格式这张表。

2. 总体流程

配合  图2 食用:

int main() {    int rc;    // 最终读出来的那个 version_descriptor 变量    struct version_descriptor __version;
// ELF 文件头 struct elfhdr __elfhdr; // app 中的那个 ____version 变量所在的节区(一般来说就是 .data) struct elf_shdr __version_section; // 字符串表节区头 struct elf_shdr __str_tbl_section; // 符号表节区头 struct elf_shdr __sym_tbl_section;
#if ELF_CLASS == ELFCLASS32 char *app_name = "./app_32"; // app 符号表中描述 ____version 变量的符号,32位 arch struct elf32_sym ____version_sym;#elif ELF_CLASS == ELFCLASS64 char *app_name = "./app_64"; // app 符号表中描述 ____version 变量的符号,64位 arch struct elf64_sym ____version_sym;#else#error "invalid arch"#endif
// app 文件读到 __buffer 中 rc = read_file2buffer(app_name); if (rc) exit(rc);
// 从 app 中读出其 ELF 文件头 rc = read_elf_header(&__elfhdr); if (rc) exit(rc);
// 读取符号表节区头 rc = read_section_by_type(SHT_SYMTAB, &__elfhdr, &__sym_tbl_section); if (rc) exit(rc);
// 读取字符串表节区头 rc = read_string_table(&__elfhdr, &__str_tbl_section, __sym_tbl_section.sh_link); if (rc) exit(rc);
// 读取 ____version 变量在符号表中的符号 rc = read_symbol_by_name("____version", &__sym_tbl_section, &__str_tbl_section, &____version_sym); if (rc) exit(rc);
// 读取 ____version 变量所在节区(如前所言,一般是 .data) rc = read_section_by_ndx(&__elfhdr, ____version_sym.st_shndx, &__version_section); if (rc) exit(rc);
// 计算出符号在文件中的偏移,读取之 fake_read(&__version, __version_section.sh_offset + ____version_sym.st_value - __version_section.sh_addr, ____version_sym.st_size);
printf("dumping the version:\n"); printf("%s\n", __version.str); printf("0x%x\n", __version.num0); printf("0x%x\n", __version.num1);
return 0;}

    方案2 要需要对符号表有一定的认识。    我们要找到这个符号在文件中的位置,就需要先到符号表中找到对应 ____version 变量的符号项。    找到符号项后,关键一步是怎么算出这个符号在文件中的偏移。    具体算法我们在下一小节中对着代码注释讲。


图2

3. 关键函数实现

/* 方案2 要借助符号表,所以先定位到符号表 * 这里封装了一个按照 type 类型来查找节区头的逻辑 * 上层调用此函数时,传入符号表的 type(SHT_SYMTAB) 就可以定位到符号表节区头 */int read_section_by_type(Elf_Word sh_type, struct elfhdr *elfhdr, struct elf_shdr *the_shdr){    struct elf_shdr shdr;    elf_addr_t shdr_offset; // section header offset    Elf_Half shnum, shentsize;    int i;
shdr_offset = elfhdr->e_shoff; shnum = elfhdr->e_shnum; shentsize = elfhdr->e_shentsize;
// 遍历所有的节区头 for (i = 0; i < shnum; ++i) { fake_read(&shdr, shdr_offset, sizeof(shdr));
// type 能匹配上,则返回该节区头。 if (shdr.sh_type == sh_type) { memcpy(the_shdr, &shdr, sizeof(shdr)); return 0; }
shdr_offset += shentsize; }
return -1;}
/* 同按名称定位节区头类似,这里需要借助字符串表 * 符号表节区头中的 sh_link 域记录着该符号表所依赖的字符串表节区头下标 * 再获取到符号表节区头后,只是使用其 sh_link 即可获取到对应字符串表节区头 */int read_string_table(struct elfhdr *elfhdr, struct elf_shdr *str_tbl_section, Elf_Half ndx){ struct elf_shdr shdr; elf_addr_t shdr_offset; // section header offset Elf_Half shnum, shentsize;
shdr_offset = elfhdr->e_shoff; shnum = elfhdr->e_shnum; shentsize = elfhdr->e_shentsize;
assert(ndx < shnum);
// 读取特定下标的节区头 fake_read(&shdr, shdr_offset + ndx * shentsize, sizeof(shdr)); assert(shdr.sh_type == SHT_STRTAB);
memcpy(str_tbl_section, &shdr, sizeof(shdr));
return 0;}
// 此函数根据要查找的符号名称(这里就是 "____version"),找到其在符号表中对应的符号项#if ELF_CLASS == ELFCLASS32int read_symbol_by_name(char *sym_name, struct elf_shdr *sym_tbl_section, struct elf_shdr *str_tbl_section, struct elf32_sym *the_sym)#elif ELF_CLASS == ELFCLASS64int read_symbol_by_name(char *sym_name, struct elf_shdr *sym_tbl_section, struct elf_shdr *str_tbl_section, struct elf64_sym *the_sym)#endif{#define SYM_NAME_MAX 64 char __sym_name[SYM_NAME_MAX]; Elf_Word symtab_size, symtab_entsize; elf_addr_t symtab_offset;
#if ELF_CLASS == ELFCLASS32 struct elf32_sym sym;#elif ELF_CLASS == ELFCLASS64 struct elf64_sym sym;#endif
int i;
// 符号表节区头中存着符号表的信息 // 符号表在文件中的偏移 symtab_offset = sym_tbl_section->sh_offset; // 符号表的大小 symtab_size = sym_tbl_section->sh_size; // 一个符号项的大小 symtab_entsize = sym_tbl_section->sh_entsize;
// 遍历所有的符号 for (i = 0; i < symtab_size / symtab_entsize; ++i) { // 读出符号项 fake_read(&sym, symtab_offset, sizeof(sym)); if (sym.st_name) { // 在字符串表中查找该符号项对应符号的名字 fake_read_till_nil(__sym_name, str_tbl_section->sh_offset + sym.st_name);
// 如果名字能匹配上,证明找到了 if (strcmp(__sym_name, sym_name) == 0) {#if ELF_CLASS == ELFCLASS32 assert(ELF32_ST_TYPE(sym.st_info) == STT_OBJECT);#elif ELF_CLASS == ELFCLASS64 assert(ELF64_ST_TYPE(sym.st_info) == STT_OBJECT);#endif memcpy(the_sym, &sym, sizeof(sym)); return 0; } }
symtab_offset += symtab_entsize; }}
/* 一个符号根据其类型,会位于不同的节区中,比如变量会位于数据段(一般是 .data),函数会位于代码段(一般是 .text) * 上一个函数中找到的符号对应的符号项中,st_shndx 记录的是该符号所在节区的节区头表中的下标 */int read_section_by_ndx(struct elfhdr *elfhdr, Elf_Half ndx, struct elf_shdr *the_shdr){ struct elf_shdr shdr; elf_addr_t shdr_offset; // section header offset Elf_Half shnum, shentsize; int i;
shdr_offset = elfhdr->e_shoff; shnum = elfhdr->e_shnum; shentsize = elfhdr->e_shentsize;
assert(ndx < shnum);
// 直接出特定下标的该节区即可 fake_read(&shdr, shdr_offset + ndx * shentsize, sizeof(shdr)); assert(shdr.sh_type == SHT_PROGBITS);
memcpy(the_shdr, &shdr, sizeof(shdr));
return 0;}

    只剩下最后一跃,如何定位该符号在文件中的偏移?    因为符号项中并没有记录该变量在文件中的偏移信息。符号属于运行时的概念,不同于段、节区是加载、链接时的概念(严格意义上来说,段是加载时,节区是链接时),所以符号项中没必要记录其加载域的地址。

    但是仍然有办法算出来:

  • 节区的 sh_addr 是该节区将被加载到虚拟地址空间中的起始地址
  • 符号的 st_value 是该符号将被加载到虚拟地址空间中的起始地址
  • 那么 st_value  - sh_addr 即是该符号相对其所在节区起始地址的偏移
  • 而节区在文件中的偏移是 sh_offset
  • 所以符号在文件中的偏移就是 sh_offset + (st_value - sh_addr)

代码:

fake_read(&__version, __version_section.sh_offset + ____version_sym.st_value - __version_section.sh_addr, ____version_sym.st_size);

5. 写在后面

    本文通过一个简单的小项目,帮对 ELF 格式只有零碎概念的同学将相关知识串起来。

    其实本文还有很多细节问题没有展开,比如:

  • 共享目标文件中还有一种叫“动态符号表(SHT_DYNSYM)”的符号表,它与本文中的符号表有何区别?
  • 查找符号表时,为何不通过遍历节区头表,匹配 SHT_STRTAB 类型节区的方式来做?
  • 段和节区有何区别?
  • 方案2 中定位符号所在的节区,为何不通过匹配节区名 ".data" 来匹配?
  • 方案2 中的符号名出现冲突怎么办?

    诸如此类,凡此种种。    本文在这里就不做过多解答,意在起抛砖引玉之功效,旨在引导对 ELF 格式感兴趣的同学快速入门。

    至于更多细节的学习,推荐《程序员的自我修炼》一书,结合 linker & loader 实现的源码阅读(推荐 TencentOS tiny 的 elfloader 组件)。

6. 项目代码文件

6.1 app.c

#include "stdio.h"#include "version.h"
// use DECLARE_VERSION to add a version descriptor into our appDECLARE_VERSION("version_x", 0xdead, 0xbeef);
int main(void) { printf("hello world\n");}

6.2 parser.c

#include "stdio.h"#include "stdlib.h"#include "string.h"#include "assert.h"
/* the c library has its own elf.h, we define our elf.h here, * so rename it to __elf.h */#include "__elf.h"
#include "version.h"
#define FILE_SIZE_MAX (1024 * 10)char __buffer[FILE_SIZE_MAX];
int read_file2buffer(char *path){ FILE *file = NULL; int size;
if (!(file = fopen(path, "r"))) { printf("open error\n"); goto errout; }
fseek(file, 0, SEEK_END); size = ftell(file); if (size > FILE_SIZE_MAX) { printf("file size exceed limit\n"); goto errout; }
fseek(file, 0, SEEK_SET); fread(__buffer, sizeof(char), size, file);
fclose(file); return 0;
errout: if (file) { fclose(file); }
return -1;}
// read from the `app` file to `buf` at offset of `offset`, with size of `size`int fake_read(void *buf, int offset, int size){ // you can replace this logic with a fread memcpy(buf, __buffer + offset, size);}
int fake_read_till_nil(char *buf, int offset){ int i;
for (i = offset; ; ++i) { buf[i - offset] = *((char *)__buffer + i);
if (buf[i - offset] == '\0') return 0; }
return -1;}
int read_elf_header(struct elfhdr *elfhdr){ fake_read(elfhdr, 0, sizeof(*elfhdr));
if (strncmp(elfhdr->e_ident, ELFMAG, sizeof(ELFMAG) - 1) == 0) { printf("load elf header ok\n"); return 0; } return -1;}

6.3 compiler.h

#ifndef __COMPILER_H__#define __COMPILER_H__
#define __used __attribute__((__used__))#define __section(S) __attribute__((__section__(#S)))
typedef __signed__ char __s8;typedef unsigned char __u8;
typedef __signed__ short __s16;typedef unsigned short __u16;
typedef __signed__ int __s32;typedef unsigned int __u32;
#ifdef __GNUC____extension__ typedef __signed__ long long __s64;__extension__ typedef unsigned long long __u64;#elsetypedef __signed__ long long __s64;typedef unsigned long long __u64;#endif
#endif

6.4 __elf.h

#ifndef __ELF_H__#define __ELF_H__
#include "compiler.h"
/* 32-bit ELF base types. */typedef __u32 Elf32_Addr;typedef __u16 Elf32_Half;typedef __u32 Elf32_Off;typedef __s32 Elf32_Sword;typedef __u32 Elf32_Word;
/* 64-bit ELF base types. */typedef __u64 Elf64_Addr;typedef __u16 Elf64_Half;typedef __s16 Elf64_SHalf;typedef __u64 Elf64_Off;typedef __s32 Elf64_Sword;typedef __u32 Elf64_Word;typedef __u64 Elf64_Xword;typedef __s64 Elf64_Sxword;
/* This info is needed when parsing the symbol table */#define STB_LOCAL 0#define STB_GLOBAL 1#define STB_WEAK 2
#define STT_NOTYPE 0#define STT_OBJECT 1#define STT_FUNC 2#define STT_SECTION 3#define STT_FILE 4#define STT_COMMON 5#define STT_TLS 6
#define ELF_ST_BIND(x) ((x) >> 4)#define ELF_ST_TYPE(x) (((unsigned int) x) & 0xf)#define ELF32_ST_BIND(x) ELF_ST_BIND(x)#define ELF32_ST_TYPE(x) ELF_ST_TYPE(x)#define ELF64_ST_BIND(x) ELF_ST_BIND(x)#define ELF64_ST_TYPE(x) ELF_ST_TYPE(x)
typedef struct elf32_sym { Elf32_Word st_name; Elf32_Addr st_value; Elf32_Word st_size; unsigned char st_info; unsigned char st_other; Elf32_Half st_shndx;} Elf32_Sym;
typedef struct elf64_sym { Elf64_Word st_name; /* Symbol name, index in string tbl */ unsigned char st_info; /* Type and binding attributes */ unsigned char st_other; /* No defined meaning, 0 */ Elf64_Half st_shndx; /* Associated section index */ Elf64_Addr st_value; /* Value of the symbol */ Elf64_Xword st_size; /* Associated symbol size */} Elf64_Sym;
#define EI_NIDENT 16
typedef struct elf32_hdr { unsigned char e_ident[EI_NIDENT]; Elf32_Half e_type; Elf32_Half e_machine; Elf32_Word e_version; Elf32_Addr e_entry; /* Entry point */ 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;
typedef struct elf64_hdr { unsigned char e_ident[EI_NIDENT]; /* ELF "magic number" */ Elf64_Half e_type; Elf64_Half e_machine; Elf64_Word e_version; Elf64_Addr e_entry; /* Entry point virtual address */ Elf64_Off e_phoff; /* Program header table file offset */ Elf64_Off e_shoff; /* Section header table file offset */ Elf64_Word e_flags; Elf64_Half e_ehsize; Elf64_Half e_phentsize; Elf64_Half e_phnum; Elf64_Half e_shentsize; Elf64_Half e_shnum; Elf64_Half e_shstrndx;} Elf64_Ehdr;
typedef struct elf32_shdr { 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;
typedef struct elf64_shdr { Elf64_Word sh_name; /* Section name, index in string tbl */ Elf64_Word sh_type; /* Type of section */ Elf64_Xword sh_flags; /* Miscellaneous section attributes */ Elf64_Addr sh_addr; /* Section virtual addr at execution */ Elf64_Off sh_offset; /* Section file offset */ Elf64_Xword sh_size; /* Size of section in bytes */ Elf64_Word sh_link; /* Index of another section */ Elf64_Word sh_info; /* Additional section information */ Elf64_Xword sh_addralign; /* Section alignment */ Elf64_Xword sh_entsize; /* Entry size if section holds table */} Elf64_Shdr;
/* sh_type */#define SHT_NULL 0#define SHT_PROGBITS 1#define SHT_SYMTAB 2#define SHT_STRTAB 3#define SHT_RELA 4#define SHT_HASH 5#define SHT_DYNAMIC 6#define SHT_NOTE 7#define SHT_NOBITS 8#define SHT_REL 9#define SHT_SHLIB 10#define SHT_DYNSYM 11#define SHT_NUM 12#define SHT_LOPROC 0x70000000#define SHT_HIPROC 0x7fffffff#define SHT_LOUSER 0x80000000#define SHT_HIUSER 0xffffffff
/* sh_flags */#define SHF_WRITE 0x1#define SHF_ALLOC 0x2#define SHF_EXECINSTR 0x4#define SHF_RELA_LIVEPATCH 0x00100000#define SHF_RO_AFTER_INIT 0x00200000#define SHF_MASKPROC 0xf0000000
#define ELFMAG "\177ELF"
#define ELFCLASS32 1#define ELFCLASS64 2
// #define ELF_CLASS ELFCLASS64
#if ELF_CLASS == ELFCLASS32
#define elfhdr elf32_hdr#define elf_phdr elf32_phdr#define elf_shdr elf32_shdr#define elf_note elf32_note#define elf_addr_t Elf32_Off#define Elf_Half Elf32_Half#define Elf_Word Elf32_Word
#else
#define elfhdr elf64_hdr#define elf_phdr elf64_phdr#define elf_shdr elf64_shdr#define elf_note elf64_note#define elf_addr_t Elf64_Off#define Elf_Half Elf64_Half#define Elf_Word Elf64_Word
#endif
#endif

6.5 1.sh

#define ELFCLASS32  1#define ELFCLASS64  2
# for x86_64gcc app.c -m64 -o app_64 -I ./includegcc parser.c -o parser_64 -I./include -DELF_CLASS=2./parser_64
# for x86# gcc app.c -m32 -o app_32 -I ./include# gcc parser.c -o parser_32 -I./include -DELF_CLASS=1# ./parser_32
本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭