应用程序二进制接口(ABI)之elf header
前言:
由于这部分知识比较庞杂,所以单独拿出来讲述,否则应用程序二进制接口(ABI)部分过于庞杂。
首先还是得回顾下elf实际的组成部分。
ELF 二进制文件
elf内部结构主要包括四个部分:
1. elf header - roadmap
2. program header table(主要描述的segments,它和程序加载相关)
3. section header table(主要描述的(section)文件内容)
包括三大类
3.1 .text .data .rodata .bbs
3.2 symbol table(.symtab和.dynsym)
3.3 其它
4.data
如下表逻辑关系
| elf header | 说明 | ||
| program header table | |||
| section header table | |||
| .text .data .bss .rodata | 对于可执行文件 ,主要有四个部分:.text、.data、.rodata和.bss |
||
| symbol table section | .symtab .dynsym .strtab | ||
| data |
我们这章节主要讲解的就是elf header 部分
一、内核代码
linux系统中涉及的部分为:
1、linux elf头文件,E:\linux内核\linux-2.6.38.5\\include\linux\elf.h
E:\linux内核\linux-2.6.38.5\linux-2.6.38.5\include\linux\elfcore.h
E:\linux内核\linux-2.6.38.5\linux-2.6.38.5\include\linux\elf-em.h
E:\linux内核\linux-2.6.38.5\linux-2.6.38.5\include\linux\elfnote.h
E:\linux内核\linux-2.6.38.5\linux-2.6.38.5\include\linux\elf-fdpic.h
2、E:\linux内核\linux-2.6.38.5\linux-2.6.38.5\fs\binfmt_elf.c
该文件定义内核如何解析ELF文件,文件 binfmt_elf.c的load_elf_binary函数负责加载elf程序,其功能是为了检测ELF文件的魔数,就是文件开始处的前四个字节 - 7F 45 4C 46。 那么我们先来看一下elfhdr这个结构体是干什么的?在这个include/linux/elf.h文件中,根据系统使用的框架结构来决定使用 32 位还是 64 位的elf header结构。
代码如下:
static int load_elf_binary(struct linux_binprm *bprm, struct pt_regs *regs) { ... //下面会详细介绍
};
static struct linux_binfmt elf_format = {
.module = THIS_MODULE,
.load_binary = load_elf_binary,
.load_shlib = load_elf_library,
.core_dump = elf_core_dump,
.min_coredump = ELF_EXEC_PAGESIZE,
};
static struct linux_binfmt elf_format = {
.module = THIS_MODULE,
.load_binary = load_elf_binary,
.load_shlib = load_elf_library,
.min_coredump = ELF_EXEC_PAGESIZE,
.core_dump = elf_core_dump,
//包括
interp_load_addr ,elf文件的加载地址
elf_interpreter ,elf解析器
}
3、程序是如何执行的
这个主题比elf header 大得多,但是为了更好解释elf header,我说下程序如何执行的,从用户的角度来看,有许多不同的方法可以启动应用程序,应用程序不一定就是elf 格式的二进制文件,但是这里我主要讨论的是elf 格式的二进制程序。
[root@aozhejin /usr/local/src/vdso]$file -v vdsotest file-5.11 magic file from /etc/magic:/usr/share/misc/magic [root@aozhejin /usr/local/src/vdso]$file -b ./vdsotest ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=3851d3fbf12766d89762cf3db1a7e50074fc4a70, not stripped
我将考虑从 shell 启动vdsotest。并且观察执行一个可执行程序的过程
利用shell bash(交互式shell)以及任何用C编程语言编写的程序都从main函数开始。
如果您查看 shell.c(bash源码) 的源代码,您会在shell.c源代码文件找到该函数
E:\shell\bash-4.2.53\shell.c
#if defined (NO_MAIN_ENV_ARG) /* systems without third argument to main() */ int main (argc, argv) int argc; char **argv; #else /* !NO_MAIN_ENV_ARG */ int main (argc, argv, env) int argc; char **argv, **env; #endif /* !NO_MAIN_ENV_ARG */ { .......
reader_loop() //main函数的最后一步会调用,reader_loop函数定义在eval.c中 }
E:\shell\bash-4.2.53\eval.c
int reader_loop ()
{
... 读取执行的命令
当该reader_loop函数进行所有检查并读取给定的程序名称和参数时,它会调用execute_command(execute_cmd.c定义)。
}
E:\shell\bash-4.2.53\execute_cmd.c
execute_command
{
......
}
execute_command函数调用链条:
execute_command
--> execute_command_internal
----> execute_simple_command
------> execute_disk_command
--------> shell_execve
这些函数都在execute_cmd.c中定义,所以查看也比较方便。
上面的在这个过程结束时,shell_execve 函数就会调用execve系统调用:
execve (command, args, env);
系统execve调用具有以下签名:
int execve(const char *filename, char *const argv [], char *const envp[]);
//execute_cmd.c这个文件中也定义了execute_shell_script,也即使说我们平时执行的类似ls命令和shell脚本的执行是分开执行的
下面我们利用strace 跟踪下,起码在linux系统上进行类似的实验还是很简单的
root@aozhejin /usr/local/src/vdso]$strace ./vdsotest execve("./vdsotest", ["./vdsotest"], 0x7ffefadf5af0 /* 32 vars */) = 0 brk(NULL) = 0xa70000 ......
你可以 strace ls,道理是一样的
在C库中,execl、execlp、execle、execv、execvp、execvpe都有支持的 syscall execve,调用链为 SyS_execve-> do_execve-> do_execveat_common。它的的主体 do_execveat_common 是:
retval = bprm_mm_init(bprm); retval = prepare_binprm(bprm); retval = copy_strings_kernel(1, &bprm->filename, bprm); retval = copy_strings(bprm->envc, envp, bprm); retval = exec_binprm(bprm); //解析 retval = copy_strings(bprm->argc, argv, bprm);
exec函数一共6个,其中execve为内核级别系统调用,其他(execl, execle, execlp, execv, execvp)都是调用execve的库函数
int execl(const char *path, const char *arg, ...);
int execlp(const char *path, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, const char *argv[]);
int execvp(const char *file, const char *argv[])
E:\linux内核\linux-2.6.38.5\include\linux\syscalls.h 定义syscall内核调用函数
...
#define SYSCALL_TRACE_ENTER_EVENT(sname) \ static struct syscall_metadata __syscall_meta_##sname; \ static struct ftrace_event_call __used \ event_enter_##sname = { \ .name = "sys_enter"#sname, \ .class = &event_class_syscall_enter, \ .event.funcs = &enter_syscall_print_funcs, \ .data = (void *)&__syscall_meta_##sname,\ }; \ static struct ftrace_event_call __used \ __attribute__((section("_ftrace_events"))) \ *__event_enter_##sname = &event_enter_##sname; \ __TRACE_EVENT_FLAGS(enter_##sname, TRACE_EVENT_FL_CAP_ANY) #define SYSCALL_TRACE_EXIT_EVENT(sname) \ static struct syscall_metadata __syscall_meta_##sname; \ static struct ftrace_event_call __used \ event_exit_##sname = { \ .name = "sys_exit"#sname, \ .class = &event_class_syscall_exit, \ .event.funcs = &exit_syscall_print_funcs, \ .data = (void *)&__syscall_meta_##sname,\ }; \ static struct ftrace_event_call __used \ __attribute__((section("_ftrace_events"))) \ *__event_exit_##sname = &event_exit_##sname; \ __TRACE_EVENT_FLAGS(exit_##sname, TRACE_EVENT_FL_CAP_ANY) #define SYSCALL_METADATA(sname, nb) \ SYSCALL_TRACE_ENTER_EVENT(sname); \ SYSCALL_TRACE_EXIT_EVENT(sname); \ static struct syscall_metadata __used \ __syscall_meta_##sname = { \ .name = "sys"#sname, \ .nb_args = nb, \ .types = types_##sname, \ .args = args_##sname, \ .enter_event = &event_enter_##sname, \ .exit_event = &event_exit_##sname, \ .enter_fields = LIST_HEAD_INIT(__syscall_meta_##sname.enter_fields), \ }; \ static struct syscall_metadata __used \ __attribute__((section("__syscalls_metadata"))) \ *__p_syscall_meta_##sname = &__syscall_meta_##sname; #define SYSCALL_DEFINE0(sname) \ SYSCALL_TRACE_ENTER_EVENT(_##sname); \ SYSCALL_TRACE_EXIT_EVENT(_##sname); \ static struct syscall_metadata __used \ __syscall_meta__##sname = { \ .name = "sys_"#sname, \ .nb_args = 0, \ .enter_event = &event_enter__##sname, \ .exit_event = &event_exit__##sname, \ .enter_fields = LIST_HEAD_INIT(__syscall_meta__##sname.enter_fields), \ }; \ static struct syscall_metadata __used \ __attribute__((section("__syscalls_metadata"))) \ *__p_syscall_meta_##sname = &__syscall_meta__##sname; \ asmlinkage long sys_##sname(void) #else #define SYSCALL_DEFINE0(name) asmlinkage long sys_##name(void) #endif #define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__) #define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__) #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__) #define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__) #define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__) #define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__) #ifdef CONFIG_PPC64 #define SYSCALL_ALIAS(alias, name) \ asm ("\t.globl " #alias "\n\t.set " #alias ", " #name "\n" \ "\t.globl ." #alias "\n\t.set ." #alias ", ." #name) #else #if defined(CONFIG_ALPHA) || defined(CONFIG_MIPS) #define SYSCALL_ALIAS(alias, name) \ asm ( #alias " = " #name "\n\t.globl " #alias) #else #define SYSCALL_ALIAS(alias, name) \ asm ("\t.globl " #alias "\n\t.set " #alias ", " #name) #endif #endif #ifdef CONFIG_FTRACE_SYSCALLS #define SYSCALL_DEFINEx(x, sname, ...) \ static const char *types_##sname[] = { \ __SC_STR_TDECL##x(__VA_ARGS__) \ }; \ static const char *args_##sname[] = { \ __SC_STR_ADECL##x(__VA_ARGS__) \ }; \ SYSCALL_METADATA(sname, x); \ __SYSCALL_DEFINEx(x, sname, __VA_ARGS__) #else #define SYSCALL_DEFINEx(x, sname, ...) \ __SYSCALL_DEFINEx(x, sname, __VA_ARGS__) #endif #ifdef CONFIG_HAVE_SYSCALL_WRAPPERS #define SYSCALL_DEFINE(name) static inline long SYSC_##name #define __SYSCALL_DEFINEx(x, name, ...) \ asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__)); \ static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__)); \ asmlinkage long SyS##name(__SC_LONG##x(__VA_ARGS__)) \ { \ __SC_TEST##x(__VA_ARGS__); \ return (long) SYSC##name(__SC_CAST##x(__VA_ARGS__)); \ } \ SYSCALL_ALIAS(sys##name, SyS##name); \ static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__)) #else /* CONFIG_HAVE_SYSCALL_WRAPPERS */ #define SYSCALL_DEFINE(name) asmlinkage long sys_##name #define __SYSCALL_DEFINEx(x, name, ...) \ asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__)) #endif /* CONFIG_HAVE_SYSCALL_WRAPPERS */
...
int kernel_execve(const char *filename, const char *const argv[], const char *const envp[]);
E:\linux内核\linux-3.12.37\include\linux\syscalls.h 定义syscall内核调用函数
系统调用对外暴露的是从这里定义.
E:\linux内核\linux-3.12.37\linux-3.12.37\include\uapi\asm-generic\unistd.h
x86是从
E:\linux内核\linux-3.12.37\linux-3.12.37\arch\x86\syscalls\syscall_64.tbl 这里暴露的
syscalltbl.sh脚本 从syscall_64.tbl表 生成arch/x86/include/generated/asm/syscalls_64.h
execve入口函数sys_execve
|
描述 |
定义 |
文件位置 |
|---|---|---|
|
系统调用号(体系结构相关) |
类似如下形式 #define __NR_execve_117
|
arch/(体系结构)/include/uapi/asm/unistd.h |
|
入口函数声明 |
|
linux-3.12.37\include\linux\syscalls.h |
|
系统调用实现 |
|
内核源码 fs/exec.c
E:\linux内核\linux-3.12.37\linux-3.12.37\fs\exec.c
SYSCALL_DEFINE3(execve, const char __user *, filename, const char __user *const __user *, argv, const char __user *const __user *, envp) { struct filename *path = getname(filename); int error = PTR_ERR(path); if (!IS_ERR(path)) { error = do_execve(path->name, argv, envp); putname(path); } return error; }
....
// linux_binfmt在 E:\linux内核\linux-2.6.38.5\linux-2.6.38.5\fs\binfmt_elf.c 中定义
int __register_binfmt(struct linux_binfmt * fmt, int insert) { if (!fmt) return -EINVAL; write_lock(&binfmt_lock); insert ? list_add(&fmt->lh, &formats) : list_add_tail(&fmt->lh, &formats); write_unlock(&binfmt_lock); return 0; } void unregister_binfmt(struct linux_binfmt * fmt) { write_lock(&binfmt_lock); list_del(&fmt->lh); write_unlock(&binfmt_lock); } int do_execve(struct filename *filename, const char __user *const __user *__argv, const char __user *const __user *__envp) { struct user_arg_ptr argv = { .ptr.native = __argv }; struct user_arg_ptr envp = { .ptr.native = __envp }; return do_execveat_common(AT_FDCWD, filename, argv, envp, 0); }
exec_binprm将调用search_binary_handler(bprm)以查找每种二进制格式的相应处理程序。search_binary_handler将搜索名为 的全局列表formats。这__register_binfmt会将 linux_binfmt 结构添加到列表中。linux_binfmt 包含三个重要的函数指针,load_binary, load_shlib, 和core_dump。
对于syscall SyS_execve,调用链为SyS_execve -> do_execve -> do_execve_common -> exec_binprm -> search_binary_handler -> load_elf_binary -> start_thread。在start_thread中,pt_regs中的pc会被设置为elf中的入口点。
do_execve_common 定义在 E:\linux内核\linux-3.12.37\linux-3.12.37\fs\exec.c
exec_binprm 定义在 E:\linux内核\linux-3.12.37\linux-3.12.37\fs\exec.c
search_binary_handler 定义在 E:\linux内核\linux-3.12.37\linux-3.12.37\fs\exec.c
我们重点看下:
static int load_elf_binary(struct linux_binprm *bprm, struct pt_regs *regs) { //开始启动线程 start_thread(regs, elf_entry, bprm->p); } /* 加载 a.out 的带有ELF header的库 . */ static int load_elf_library(struct file *file) { ... }
二、linux系统中运行时
ELF的结构声明位于系统头文件 elf.h 中,在/usr/include目录下,ELF格式分为32位与64位两种
1. /usr/include/linux/elf.h
[root@aozhejin /usr/local/src/vdso]$locate elf.h /usr/include/elf.h /usr/include/linux/elf.h [root@aozhejin /usr/local/src/vdso]$cat /usr/include/linux/elf.h | grep MAG #define EI_MAG0 0 /* e_ident[] indexes */ #define EI_MAG1 1 #define EI_MAG2 2 #define EI_MAG3 3 #define ELFMAG0 0x7f /* EI_MAG,强制的定义 */ #define ELFMAG1 'E' //0x45的ascii码值 #define ELFMAG2 'L' //0x4c的ascii码值 #define ELFMAG3 'F' //0x46的ascii码值 #define ELFMAG "\177ELF" /*177代表的是八进制数,十六进制数=7F*/ #define SELFMAG 4
2. /usr/share/misc/magic 文件里面也有\177ELF的说明
三、elf文件开始的文件头信息
1. readelf
选项 -h(elf header),显示elf文件开始的文件头信息(可读性比较强)
[root@aozhejin /usr/local/src/vdso]$readelf -h vdsotest
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 #\177 和 'E' 'L' 'F'
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: 0x400490
Start of program headers: 64 (bytes into file)
Start of section headers: 6496 (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: 30
Section header string table index: 29
ELF header 结构里面字段代表:
| 字段 | 解释 |
|---|---|
| Magic | 这些是 ELF 标头中的第一个字节。它们将文件标识为 ELF 并包含处理器可用于解释文件的信息。 |
| Class | 类字段中的值指示文件的体系结构。因此,ELF 可以是 32 位或 64 位的。 |
| Data | 该字段指定数据编码。这对于帮助处理器解释传入的指令很重要。最常见的数据编码是小端和大端。 |
| Version | 标识 ELF 文件版本(设置为 1) |
| OS/ABI | ABI 是应用程序二进制接口的缩写。在这种情况下,它定义了如何在程序中访问函数和数据结构。 |
| ABI version | 该字段指定 ABI 版本。 |
| Type | 此字段中的值指定目标文件类型。例如,2 表示可执行文件,3表示共享对象,4表示核心文件。 REL=1,EXEC=2,DYN=3,CORE=4 |
| Machine | 这指定了文件所需的体系结构。 |
| Version | 标识目标文件版本。 |
| Entry point address | 这表示程序应该开始执行的地址。如果文件不是可执行文件,则此字段中的值设置为0。 |
| Start of program headers | 这是文件中程序头开始的偏移量。 |
| Start of section headers | 这是一个指示section head开始位置的偏移量。 |
| Flags | 这包含文件的标志。 |
| Size of this header | 这指定了 ELF 标头的大小。 |
| Number of program headers | 这表示有多少个独立的程序头。 |
| Size of section headers | 此字段中的值显示单个section header的大小。 |
| Number of section headers | 这表明有多少个独立的section header。 |
| Section header string table index | 表示段名字符串表的条目的段表索引 |
详细分析,下面是使用headdump显示完整的十六进制头文件(hexdump -C -n 64 vdsotest)
1) elf header
前 4 个字节(十六进制数即7f 45 4c 46)定义这是一个 ELF 文件(ASCII 编码 中 45= E ,4c= L ,46= F),前缀为 0x7f值,这个 ELF 头是强制性的。它确保在链接或执行期间正确解释数据。为了更好地理解 ELF 文件的内部工作,了解使用此头信息是很有用的。
我们使用od工具,只打印前4字节转换字节为ASCII 字符(或 hexdump -c -n 4)
[root@aozhejin /usr/local/src/vdso]$od -N 4 -c vdsotest
0000000 177 E L F
0000004 <---这个不表示偏移4个字节
或
[root@aozhejin /usr/local/src/vdso]$hexdump -c -n 4 vdsotest
0000000 177 E L F
0000004
2.hexdump
同时我们结合着 hexdump 分析 ELF file header(十六进制数字方式显示)
1) -C 每个字节显示为半个字节,即两个16进制数和相应的ASCII字符,使用-C参数,显示结果分为三列(文件偏移量、字节的十六进制、ASCII字符)
2)-n legth 表示仅仅输出length长度的字节(bytes)数。
下面的7f 即占据一个字节(7对应0111即占据4位,f即1111占据4位),8个字节占据一列,同时一行占据16个字节。
整个头是由64个字节构成。
[root@aozhejin /usr/local/src/vdso]$hexdump -C -n 64 vdsotest 00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............| 00000010 02 00 3e 00 01 00 00 00 90 04 40 00 00 00 00 00 |..>.......@.....| 00000020 40 00 00 00 00 00 00 00 60 19 00 00 00 00 00 00 |@.......`.......| 00000030 00 00 00 00 40 00 38 00 09 00 40 00 1e 00 1d 00 |....@.8...@.....| 00000040
00000040转换成十进制就是64,表示偏移量为64字节。
小知识点: 1字节可以表示成2个连续的16进制数字(比如7f),8位二进制数称为1个字节,半个字节(英文nibble表示半个字节)为1个16进制的字符
下面来自wiki,我们继续更详细的介绍:
| 偏移量(16进制) | 字节大小 | 字段 | 解释 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 32-bit | 64-bit | 32-bit | 64-bit | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0x00 | 4 | e_ident[EI_MAG0] through e_ident[EI_MAG3] | 0x7F 后面跟着 ELF(45 4c 46) ,这4个字节组成了 magic number. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0x04 | 1 | e_ident[EI_CLASS] | 这个字节设置 0x01 或 0x02 来标识 32位 或 64-bit 格式 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0x05 | 1 | e_ident[EI_DATA] | This byte is set to either 1 or 2 to signify little or big endianness, respectively. This affects interpretation of multi-byte fields starting with offset 0x10. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0x06 | 1 | e_ident[EI_VERSION] | Set to 1 for the original and current version of ELF. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0x07 | 1 | e_ident[EI_OSABI] | Identifies the target operating system ABI.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0x08 | 1 | e_ident[EI_ABIVERSION] | Further specifies the ABI version. Its interpretation depends on the target ABI. Linux kernel (after at least 2.6) has no definition of it, so it is ignored for statically-linked executables. In that case, offset and size of EI_PAD are 8.
glibc 2.12+ in case e_ident[EI_OSABI] == 3 treats this field as ABI version of the dynamic linker:[7] it defines a list of dynamic linker's features,[8] treats e_ident[EI_ABIVERSION] as a feature level requested by the shared object (executable or dynamic library) and refuses to load it if an unknown feature is requested, i.e. e_ident[EI_ABIVERSION] is greater than the largest known feature.[9] |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0x09 | 7 | e_ident[EI_PAD] | Reserved padding bytes. Currently unused. Should be filled with zeros and ignored when read. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0x10 | 2 | e_type | Identifies object file type.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0x12 | 2 | e_machine | Specifies target instruction set architecture. Some examples are:
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0x14 | 4 | e_version | Set to 1 for the original version of ELF. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0x18 | 4 | 8 | e_entry | This is the memory address of the entry point from where the process starts executing. This field is either 32 or 64 bits long, depending on the format defined earlier (byte 0x04). If the file doesn't have an associated entry point, then this holds zero. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0x1C | 0x20 | 4 | 8 | e_phoff | Points to the start of the program header table. It usually follows the file header immediately following this one, making the offset 0x34 or 0x40 for 32- and 64-bit ELF executables, respectively. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0x20 | 0x28 | 4 | 8 | e_shoff | Points to the start of the section header table. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0x24 | 0x30 | 4 | e_flags | Interpretation of this field depends on the target architecture. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0x28 | 0x34 | 2 | e_ehsize | Contains the size of this header, normally 64 Bytes for 64-bit and 52 Bytes for 32-bit format. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0x2A | 0x36 | 2 | e_phentsize | Contains the size of a program header table entry. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0x2C | 0x38 | 2 | e_phnum | Contains the number of entries in the program header table. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0x2E | 0x3A | 2 | e_shentsize | Contains the size of a section header table entry. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0x30 | 0x3C | 2 | e_shnum | Contains the number of entries in the section header table. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0x32 | 0x3E | 2 | e_shstrndx | Contains index of the section header table entry that contains the section names. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0x34 | 0x40 | End of ELF Header (size). | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
讲解下偏移量,需要转成十进制整型数,例如: e_type ,需偏移0x10=16(十进制数),e_type占用2个字节
使用下面命令就是 hexdump -C -n 18 vdsotest ,正好把后面的e_type内容显示出来。
1) elf header
前 4 个字节(十六进制数即7f 45 4c 46)定义这是一个 ELF 文件(ASCII 编码 中 45= E ,4c= L ,46= F),前缀为 0x7f值,这个 ELF 头是强制性的。它确保在链接或执行期间正确解释数据。为了更好地理解 ELF 文件的内部工作,了解使用此头信息是很有用的。
[root@aozhejin /usr/local/src/vdso]$hexdump -C vdsotest 00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............| 00000010 02 00 3e 00 01 00 00 00 90 04 40 00 00 00 00 00 |..>.......@.....|
....
2) class字段
在 ELF 类型声明之后,定义了一个 Class 字段。此值确定文件的体系结构。它可以是32 位(=01) 或64 位(=02) 架构。魔术数字显示一个 02,它被readelf命令翻译成一个 ELF64 文件。换句话说,一个使用 64 位架构的 ELF 文件。
[root@aozhejin /usr/local/src/vdso]$hexdump -C -n 64 vdsotest
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 02 00 3e 00 01 00 00 00 90 04 40 00 00 00 00 00 |..>.......@.....|
00000020 40 00 00 00 00 00 00 00 60 19 00 00 00 00 00 00 |@.......`.......|
00000030 00 00 00 00 40 00 38 00 09 00 40 00 1e 00 1d 00 |....@.8...@.....|
00000040
解释下 00000010-->是0x10即十六进制数-->对应十进制的16,即16个字节偏移
3)data
data知道两个选项:01 表示LSB(Least Significant Bit),也称为 little-endian。然后是MSB (最高有效位,big-endian)的值 02 。此特定值有助于正确解释文件中的其余对象。这很重要,因为不同类型的处理器以不同方式处理传入的指令和数据结构。在这种情况下,使用 LSB,这在 AMD64 类型处理器中很常见。
[root@aozhejin /usr/local/src/vdso]$hexdump -C -n 64 vdsotest
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 02 00 3e 00 01 00 00 00 90 04 40 00 00 00 00 00 |..>.......@.....|
00000020 40 00 00 00 00 00 00 00 60 19 00 00 00 00 00 00 |@.......`.......|
00000030 00 00 00 00 40 00 38 00 09 00 40 00 1e 00 1d 00 |....@.8...@.....|
00000040
4)version
magic number中的另一个数字是“01”,即版本号。目前,只有一种版本类型:currently,即值“01”。
[root@aozhejin /usr/local/src/vdso]$hexdump -C -n 7 vdsotest 00000000 7f 45 4c 46 02 01 01 |.ELF...| 00000007
5)os/abi
每个操作系统在通用功能上都有很大的重叠。此外,它们中的每一个都有特定的,或者至少它们之间存在细微差别。右集的定义是通过 应用程序二进制接口( ABI ) 完成的。通过这种方式,操作系统和应用程序都知道会发生什么,并且可以正确转发功能。这两个字段描述了使用的是什么 ABI 以及相关的版本。在这种情况下,该值为 00,这意味着没有使用特定的扩展名。输出将其显示为System V。
[root@aozhejin /usr/local/src/vdso]$hexdump -C -n 64 vdsotest
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 02 00 3e 00 01 00 00 00 90 04 40 00 00 00 00 00 |..>.......@.....|
00000020 40 00 00 00 00 00 00 00 60 19 00 00 00 00 00 00 |@.......`.......|
00000030 00 00 00 00 40 00 38 00 09 00 40 00 1e 00 1d 00 |....@.8...@.....|
00000040
6)ABI version
7) type
type字段告诉我们文件的用途。有几种常见的文件类型
REL=1,EXEC=2,DYN=3,CORE=4
值 3e 是十进制的 62,等于 AMD64(https://opensource.apple.com/source/dtrace/dtrace-90/sys/elf.h 中是这样)
[root@aozhejin /usr/local/src/vdso]$hexdump -C -n 18 vdsotest 00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............| 00000010 02 00 |..| 00000012
elf.h中 Elf64_Half e_type; /* Object file type */代表文件类型
|
名称 |
十六进制值 |
介绍 |
|---|---|---|
|
ET_NONE |
0x00 |
不是文件类型 |
|
ET_REL |
0x01 |
Relocatable file |
|
ET_EXEC |
0x02 |
可执行文件 |
|
ET_DYN |
0x03 |
共享对象文件 |
|
ET_CORE |
0x04 |
Core file |
|
ET_LOPROC |
0xff00 |
Processor-specific |
|
ET_HIPROC |
0xffff |
Processor-specific |
1. https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
2. https://www.sco.com/developers/gabi/latest/ch4.eheader.html
https://docs.oracle.com/cd/E19683-01/816-1386/chapter6-43405/index.html
https://wenboshen.org/posts/2016-09-15-kernel-execve
https://elixir.bootlin.com/linux/v4.9.35/source/fs/exec.c#L1910
https://0xax.gitbooks.io/linux-insides/content/SysCall/linux-syscall-4.html
https://en.wikipedia.org/wiki/System_call
https://github.com/bminor/bash/blob/bc007799f0e1362100375bb95d952d28de4c62fb/shell.c#L357
https://elixir.bootlin.com/linux/0.01/source/init/main.c linux各个版本的内核在线查询

浙公网安备 33010602011771号