应用程序二进制接口(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.cload_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

__SYSCALL(117,sys_execve,3)

arch/(体系结构)/include/uapi/asm/unistd.h

入口函数声明

asmlinkage long sys_execve(const char __user *filename

,const char __user *const __user *argv, const char __user *const __user *envp

linux-3.12.37\include\linux\syscalls.h

系统调用实现

SYSCALL_DEFINE3(execve,

const char __user, file_name,

const cahr __user *const __usr argv


 内核源码 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_binaryload_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 此字段中的值指定目标文件类型。例如,表示可执行文件,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,我们继续更详细的介绍:

ELF header
偏移量(16进制)字节大小字段解释
32-bit64-bit32-bit64-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.
ValueABI
0x00 System V
0x01 HP-UX
0x02 NetBSD
0x03 Linux
0x04 GNU Hurd
0x06 Solaris
0x07 AIX (Monterey)
0x08 IRIX
0x09 FreeBSD
0x0A Tru64
0x0B Novell Modesto
0x0C OpenBSD
0x0D OpenVMS
0x0E NonStop Kernel
0x0F AROS
0x10 FenixOS
0x11 Nuxi CloudABI
0x12 Stratus Technologies OpenVOS
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.
ValueTypeMeaning
0x00 ET_NONE Unknown.
0x01 ET_REL Relocatable file.
0x02 ET_EXEC Executable file.
0x03 ET_DYN Shared object.
0x04 ET_CORE Core file.
0xFE00 ET_LOOS Reserved inclusive range. Operating system specific.
0xFEFF ET_HIOS
0xFF00 ET_LOPROC Reserved inclusive range. Processor specific.
0xFFFF ET_HIPROC
0x12 2 e_machine Specifies target instruction set architecture. Some examples are:
ValueISA
0x00 No specific instruction set
0x01 AT&T WE 32100
0x02 SPARC
0x03 x86
0x04 Motorola 68000 (M68k)
0x05 Motorola 88000 (M88k)
0x06 Intel MCU
0x07 Intel 80860
0x08 MIPS
0x09 IBM System/370
0x0A MIPS RS3000 Little-endian
0x0B - 0x0D Reserved for future use
0x0E Hewlett-Packard PA-RISC
0x0F Reserved for future use
0x13 Intel 80960
0x14 PowerPC
0x15 PowerPC (64-bit)
0x16 S390, including S390x
0x17 IBM SPU/SPC
0x18 - 0x23 Reserved for future use
0x24 NEC V800
0x25 Fujitsu FR20
0x26 TRW RH-32
0x27 Motorola RCE
0x28 Arm (up to Armv7/AArch32)
0x29 Digital Alpha
0x2A SuperH
0x2B SPARC Version 9
0x2C Siemens TriCore embedded processor
0x2D Argonaut RISC Core
0x2E Hitachi H8/300
0x2F Hitachi H8/300H
0x30 Hitachi H8S
0x31 Hitachi H8/500
0x32 IA-64
0x34 Motorola ColdFire
0x35 Motorola M68HC12
0x36 Fujitsu MMA Multimedia Accelerator
0x37 Siemens PCP
0x38 Sony nCPU embedded RISC processor
0x39 Denso NDR1 microprocessor
0x3A Motorola Star*Core processor
0x3B Toyota ME16 processor
0x3C STMicroelectronics ST100 processor
0x3D Advanced Logic Corp. TinyJ embedded processor family
0x3E AMD x86-64
0x41 Digital Equipment Corp. PDP-11
0x42 Siemens FX66 microcontroller
0x43 STMicroelectronics ST9+ 8/16 bit microcontroller
0x44 STMicroelectronics ST7 8-bit microcontroller
0x45 Motorola MC68HC16 Microcontroller
0x46 Motorola MC68HC11 Microcontroller
0x47 Motorola MC68HC08 Microcontroller
0x48 Motorola MC68HC05 Microcontroller
0x4A STMicroelectronics ST19 8-bit microcontroller
0x4B Digital VAX
0x4C Axis Communications 32-bit embedded processor
0x4D Infineon Technologies 32-bit embedded processor
0x4E Element 14 64-bit DSP Processor
0x4F LSI Logic 16-bit DSP Processor
0x8C TMS320C6000 Family
0xAF MCST Elbrus e2k
0xB7 Arm 64-bits (Armv8/AArch64)
0xDC Zilog Z80
0xF3 RISC-V
0xF7 Berkeley Packet Filter
0x101 WDC 65C816
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各个版本的内核在线查询

posted @ 2023-04-03 22:01  jinzi  阅读(3)  评论(0)    收藏  举报