0. 简介

在Linux系统的可执行文件(ELF文件)中,开头是一个文件头,用来描述程序的布局,整个文件的属性等信息,包括文件是否可执行、静态还是动态链接及入口地址等信息;如下图所示:

程序文件中包含了程序头,程序的入口地址等信息不需要写死,调用代码可以通用,根据实际情况加载;此时的文件不是纯碎的二进制可执行文件了,因为包含的程序头不是可执行代码;将这种包含程序头的文件读入到内存后,从程序头中读取入口地址,跳转到入口地址执行;

0.1 文件格式

Linux环境中,目标文件是源代码编译后未链接的中间文件,如:gcc编译生成的.o文件;可执行文件(.o)、动态链接库(.so)、静态链接库(.a)文件都是按照ELF可执行文件格式存储的;

ELF指:Executable and Linkable Format,可执行链接格式;本文中的目标文件指各种类型符合ELF规范的我呢见,如:二进制可执行文件、Linux下的.o目标文件和.so动态库文件;

可执行文件(Executable file):经过编译链接后,可以直接执行的程序文件,如:ELF文件;

共享目标文件(Shared object file):动态链接库,在可执行文件被加载的过程中动态链接,成为程序代码的一部分;

可重定位文件(Relocatable file):可重定位文件即目标文件和静态库文件,是源文件编译后但未完成链接的半成品,被用于与其他目标文件合并链接,以构建出二进制可执行文件;

核心转储文件(Core dump file):当进程意外终止时,系统可以将该进程的地址空间的内容及终止时的一些信息转储到核心转储文件;

0.2 段和节

程序中的段(Segment)和节(Secton)是真正的程序体;段包括代码段和数据段等,段是由节组成的,多个节经过链接后被合并为一个段;

段和节的信息用header描述,程序头是program header,节头是section header;段和节的大小和数量都是不固定的,需要用专门的数据结构来描述,即程序头表(program header table)和节头表(section header table),这是两个数组,元素分别是程序头(program header)和节头(section header);程序头表(program header table)中的元素全是程序头(program header),节头表(section header table)中的元素全是节头(section header);程序头表是用来描述段(Segment)的,成为段头表;段是程序本身的组成部分;

由于段和节的大小和数量都是不固定的,程序头表和节头表的大小也不固定,两个表在程序文件中的位置也不固定;需要在一个固定的位置,用一个固定大小的数据结构来描述程序头表和节头表的大小和位置信息,即位于文件最开始部分的ELF header;

1. ELF header

ELF目标文件的最开始是ELF文件头(ELF Header),包含了描述整个文件的基本属性;ELF文件分为文件头和文件体两部分;先用ELF header从文件全局概要出程序中程序头表、节头表的位置和大小等信息;然后从程序头表和节头表中分别解析出各个段和节的位置和大小等信息;

可执行文件和待重定位文件,文件最开头的部分是ELF header;程序头表对于可执行文件是必须的,而对于待重定位文件是可选的;

ELF文件头在32位系统中用Elf32_Ehdr结构体表示,在64位系统中用Elf64_Ehdr结构体表示;两者内容是一样的,区别是有些成员的大小不同;

// include/uapi/linux/elf.h
#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;

e_ident[EI_NIDENT]是16字节大小的数组,用来表示ELF字符等信息,开头四个字节是固定不变的elf文件魔数,0x7f 0x45 0x4c 0x46;

e_type:2字节,用来指定ELF目标文件的类型;

// include/uapi/linux/elf.h
/* These constants define the different elf file types */
#define ET_NONE   0     // 未知目标文件格式
#define ET_REL    1     // 可重定位文件
#define ET_EXEC   2     // 可执行文件
#define ET_DYN    3     // 动态共享目标文件
#define ET_CORE   4     // core文件,程序崩溃时内存映像的转储格式
#define ET_LOPROC 0xff00    // 特定处理器文件的扩展下界
#define ET_HIPROC 0xffff    // 特定处理器文件的扩展上界

e_machine:2字节,用来描述ELF目标文件的体系结构类型,即要在哪种硬件平台运行;

// include/uapi/linux/elf-em.h
/* These constants define the various ELF target machines */
#define EM_NONE     0
#define EM_M32      1
#define EM_SPARC    2
#define EM_386      3
#define EM_68K      4
#define EM_88K      5
#define EM_486      6   /* Perhaps disused */
#define EM_860      7
#define EM_MIPS     8   /* MIPS R3000 (officially, big-endian only) *//* Next two are historical and binaries andmodules of these types will be rejected byLinux.  */
#define EM_MIPS_RS3_LE  10  /* MIPS R3000 little-endian */
#define EM_MIPS_RS4_BE  10  /* MIPS R4000 big-endian */
​
#define EM_PARISC   15  /* HPPA */
#define EM_SPARC32PLUS  18  /* Sun's "v8plus" */
#define EM_PPC      20  /* PowerPC */
......

2. 程序头表

程序头表(也称为段表)是一个描述文件中各个段的数组,程序头表描述了文件中各个段在文件中的偏移位置及段的属性等信息;从程序头表里可以得到每个段的所有信息,包括代码段和数据段等;各个段的内容紧跟ELF文件头保存;程序头表中各个段用Elf32_Phdr或Elf64_Phdr结构体表示;

// include/uapi/linux/elf.h
typedef struct elf32_phdr{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;
​
typedef struct elf64_phdr {Elf64_Word p_type;Elf64_Word p_flags;Elf64_Off p_offset;       /* Segment file offset */Elf64_Addr p_vaddr;       /* Segment virtual address */Elf64_Addr p_paddr;       /* Segment physical address */Elf64_Xword p_filesz;     /* Segment size in file */Elf64_Xword p_memsz;      /* Segment size in memory */Elf64_Xword p_align;      /* Segment alignment, file & memory */
} Elf64_Phdr;

Elf32_Phdr或Elf64_Phdr结构体用来描述位于磁盘上的程序中的一个段;

p_type:4字节,用来指明程序中该段的类型;

// include/uapi/linux/elf.h
/* These constants are for the segment types stored in the image headers */
#define PT_NULL    0 // 忽略
#define PT_LOAD    1 // 可加载程序段
#define PT_DYNAMIC 2 // 动态链接信息
#define PT_INTERP  3 // 动态加载器名称
#define PT_NOTE    4 // 辅助的附加信息
#define PT_SHLIB   5 // 保留
#define PT_PHDR    6 // 程序头表
#define PT_TLS     7               /* Thread local storage segment */
#define PT_LOOS    0x60000000      /* OS-specific */
#define PT_HIOS    0x6fffffff      /* OS-specific */
#define PT_LOPROC  0x70000000
#define PT_HIPROC  0x7fffffff

p_flags:4字节,用来指明与本段相关的标志;

// include/uapi/linux/elf.h
/* These constants define the permissions on sections in the program    header, p_flags. */
#define PF_R        0x4  // 可读
#define PF_W        0x2  // 可写
#define PF_X        0x1  // 可执行

3. 节头表

节头表中各个节用Elf32_Shdr或Elf64_Shdr结构体表示;

// include/uapi/linux/elf.h
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;

Elf32_Shdr或Elf64_Shdr结构体用来描述位于磁盘上的程序中的一个节;

成员32位64位说明sh_name44节名称,值是字符串的一个索引;节名称字符串以'0'结尾,统一存储在字符串表中,使用该字段存储节名称字符串在字符串表中的索引位置;sh_type44节的类型;sh_flags48节的标志;sh_addr48节在内存中的起始地址,指定节映射到虚拟地址空间中的位置;sh_offset48节在文件中的起始位置;sh_size48节的大小;sh_link44引用另一个节头表项,根据节类型有不同的解释;sh_info44节的附加信息,与sh_link联合使用;sh_addralign48节数据在内存中的对齐方式;sh_entsize48指定节中各数据项的长度,各数据项长度要相同;

sh_name

节名称sh_name的值是字符串的一个索引,节名称字符串以'0'结尾,字符串统一存放在字符串表中,使用sh_name的值作为字符串表的索引,找到对应的字符串即为节名称;

字符串表中包含多个以'0'结尾的字符串;在目标文件中,这些字符串通常是符号的名字或节的名字,需要引用某些字符串时,只需要提供该字符串在字符串表中的序号即可;

字符串表中的第一个字符串(序号为0)是空串,即'0',可以用于表示没有名字或一个空的名字;如果字符串表为空,节头中的sh_size值为0;

sh_type:节的类型;

// include/uapi/linux/elf.h
/* 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:节的标志;

// include/uapi/linux/elf.h
/* sh_flags */
#define SHF_WRITE   0x1  // 可写
#define SHF_ALLOC   0x2  // 该节包含内存在进程运行时需要占用内存单元
#define SHF_EXECINSTR   0x4 // 该节内容是指令代码
#define SHF_MASKPROC    0xf0000000

4. readelf命令

readelf命令用于查看ELF格式的文件信息;

$ readelf -H
Usage: readelf <option(s)> elf-file(s)Display information about the contents of ELF format filesOptions are:-a --all               Equivalent to: -h -l -S -s -r -d -V -A -I-h --file-header       Display the ELF file header-l --program-headers   Display the program headers--segments          An alias for --program-headers-S --section-headers   Display the sections' header--sections          An alias for --section-headers-e --headers           Equivalent to: -h -l -S......

命令如下:

$ readelf -h file // 显示ELF文件头
$ readelf -l file // 显示所有的程序头
$ readelf -S file // 显示所有的节头
$ readelf -e file // 显示ELF文件头、程序头、节头全部的头信息,相当于同时运行参数-h、-l、-S

5. xxd命令

由于ELF文件是二进制文件,和普通文件不同,不能使用一般的vim等编辑工具直接打开;而xxd命令主要用来查看文件的十六进制格式,同样也能够以二进制格式查看文件;

$ man xxd
NAMExxd - make a hexdump or do the reverse.SYNOPSISxxd -h[elp]xxd [options] [infile [outfile]]
......

xxd命令常用的选项:

-a: 自动跳过空白内容,默认关闭;
-u: 使用大写字符的十六进制,默认小写;
-c: 后边加上数字,表示每行显示多少字节的十六进制数,默认16字节,最大256字节;
-g: 设定以多少个字节为一块,默认2字节;
-l: 显示多少字节内容;
-b: 表示以二进制的形式查看文件内容;
-s: 后面接[+-]和address,+表示从address处开始的内容,-表示从距末尾address处开始的内容;

6. 总结

在Linux系统的可执行文件(ELF文件)中,开头是一个文件头,用来描述程序的布局,整个文件的属性等信息,包括文件是否可执行、静态还是动态链接及入口地址等信息;生成的文件不是纯碎的二进制可执行文件了,因为包含的程序头不是可执行代码;将这种包含程序头的文件读入到内存后,从程序头中读取入口地址,跳转到入口地址执行;可以参考《linux中ELF二进制程序解析》,使用中的实例可以参考《linux中ELF二进制文件解析----ELF文件实例解析》;

参考资料

《操作系统真象还原》

程序编译-汇编-链接的理解!—03-ELF头和节头表

ELF文件-节和节头

回到目录

linux 可执行文件_linux中ELF二进制程序解析相关推荐

  1. Linux源码中的mktime算法解析

    Linux源码中的mktime算法解析 我们知道,从CMOS中读出来的系统时间并不是time_t类型,而是类似于struct tm那样,年月日时分秒是分开存储的. 那么,要把它转化为系统便于处理的ti ...

  2. eclipse中linux打包,Eclipse中Maven打包程序并在Linux中运行

    Eclipse中Maven打包程序并在Linux中运行 1 在Eclipse中新建Maven工程 新建后的maven工程如下: 新建Maven工程的默认pom.xml如下,不需要修改: 4.0.0 T ...

  3. linux命令行界面_Linux中的命令行界面

    linux命令行界面 If you are like most people, you are probably most familiar with using a Graphical User I ...

  4. linux 信号_Linux中的信号处理机制 [四]

    信号与线程 Unix的信号机制在诞生之初,生活在只有进程(process)的相对单纯的环境中.自从Unix世界有了线程(thread)的概念,信号就被赋予了发往进程中某个特定线程的能力,当然,这也增加 ...

  5. linux运算_linux中的计算【转】

    shell中的赋值和操作默认都是字符串处理,在此记下shell中进行数学运算的几个特殊方法,以后用到的时候可以来看,呵呵 1.错误方法举例 a) var=1+1 echo $var 输出的结果是1+1 ...

  6. linux关机_Linux中shutdown,halt,poweroff,init 0区别

    1)shutdown命令 我们较常使用的是shutdown这个命令,这个命令可以安全地关闭或重启Linux系统,它在系统关闭之前给系统上的所有登录用户提示一条警告信息.该命令还允许用户指定一个时间参数 ...

  7. java windows linux 乱码_Linux 中 Windows 中文乱码

    Linux 下 Windows 源代码中文乱码 由于 windows 和 linux 对文本的编码方式不同,所以经常会有 windows 中生成的文本在 linux 中打开乱码的情况. 比如: 我面临 ...

  8. linux 软链接_Linux 中软链接和硬链接的区别 | Linux 中国

    链接(Link)是一种快捷访问机制,它通过一个文件指向原始文件或目录的方式实现快捷访问,同时还记录了原始文件或目录的一些信息.https://linux.cn/article-12270-1.html ...

  9. linux正则表达式_Linux 中几个正则表达式的用法

    公众号关注"杰哥的IT之旅",选择"星标",重磅干货,第一时间送达! 链接:https://www.linuxmi.com/linux-zhengzebiaod ...

最新文章

  1. 虚拟机服务器503解决,(图文)解决重启VCSA 6.0提示:503 Service Unavailable错误
  2. pandas中drop用法_python进行数据清理之pandas中的drop用法
  3. 工程制图 (装配图)
  4. SQLite AND/OR 运算符(http://www.w3cschool.cc/sqlite/sqlite-and-or-clauses.html)
  5. 判断浏览器是否支持某个css属性
  6. Android之app引导页(背景图片切换加各个页面动画效果)
  7. input文件上传代码片段
  8. leetcode520. py解字符串真是太残暴了
  9. 去哪儿-11-city-components
  10. spring boot 定时间任务
  11. linux下运行python unitest_Python单元测试unittest代码详解
  12. VisualStudio移动开发(C#、VB.NET)Smobiler开发平台——VoiceRecorder控件的使用方式.Net移动开发...
  13. 「专题训练」Collecting Bugs(POJ-2096)
  14. Lucene实践之Query
  15. java 过滤器 中文_javaweb — 过滤器(附实现中文乱码问题)
  16. 土木/岩土期刊版面费/审稿费统计列表
  17. altf4不管用.是因为未使用内置管理员账户.
  18. mysql执行存储过程报错1366_花花蘑菇
  19. HDU 6148 Valley Numer (数位dp)
  20. 用什么工具可以查询App日活量

热门文章

  1. 非聚集索引和聚集索引
  2. Alpha 冲刺报告(3/10)
  3. Spring boot 使用
  4. JavaWeb开发环境配置
  5. 堆垛管理,下料管理系统
  6. 【C语言】单链表的所有操作的实现(包括PopBack、PushBack、PopFront、PushFront、Insert)...
  7. 帮助创建未来的 .NET 客户端开发
  8. CSP认证201712-4 行车路线[C++题解]:单源最短路变型、拆点、好题!
  9. 算法提高课-搜索-Flood fill算法-AcWing 1098. 城堡问题:flood fill、bfs
  10. PAT甲级1010 Radix :[C++题解]进制位、秦九韶算法、二分(PAT通过率最低的一道题0.11)