前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux ELF笔记

Linux ELF笔记

作者头像
用户6414482
发布2022-08-28 08:59:07
2K0
发布2022-08-28 08:59:07
举报
文章被收录于专栏:技闲说

最近在研究ARM cpu 32 bit转码 64bit的事情,以用于在64bit的服务器上可以更快的运行32bit的Android ELF文件。

特意写篇东西做一下笔记。

对于Android的应用程序来说,最常见的ELF就是so动态库了。它其实类似Windows上的.dll文件。

ELF文件结构

ELF格式的文件中的“数据”实际上是以“段”(节,英文:Section)的形式存储的。 其顺序大致符合下面的排序:

  • ELF Header:ELF头部
  • .dynsym:保存动态链接相关符号,记录其偏移值
  • .dynstr.dynsym 的辅助段
  • .hash:快速查找符号的哈希表,类似 .dynstr
  • .rel.got:数据引用修正,修正到 .got
  • .rel.plt:函数引用修正,修正到 .got.plt
  • .text:代码段
  • 其他自定义的代码段
  • .rodata:字符串常量段
  • .fini_array:终止函数段
  • .init_array:初始化函数段
  • .dynamic:动态链接库特有,存储动态链接用到的表信息
  • .got:函数的绝对地址
  • .data:存放已经初始化的全局变量,静态内存分配相关
  • .bss:存放未初始化的全局变量,静态内存分配相关

ELF Header

其中,一切的起点都在ELF头部,其偏移量(offset)为 0。 ELF头部的结构体为 elf32_hdrelf64_hdr, 在Android系统源代码的 /bionic/libc/kernel/uapi/linux/elf.h 可以找到。

以32位程序的ELF头部为例:

代码语言:javascript
复制
#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;
  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;

接下来,分别解释这些变量的含义:

#e_ident

ELF格式文件的识别区域,固定为 16Bytes 的字符串。 下面是这串字符串各个字节的含义。

EI_MAG
  • Offset: 0 - 3 (EI_MAG0)
  • Length: 4 (SELFMAG)
  • Type: String

ELF Identification 的前四个字节为魔数(Magic Number), 内容为 {0x7F, 'E', 'L', 'F'}

如果魔数不一致,在Android系统会报错“has bad ELF magic”, 终止 load_library并APP闪退。

EI_CLASS
  • Offset: 4 (EI_CLASS)
  • Length: 1
  • Type: Number

判断 ELF 文件是 32位的 还是 64位的。

常量定义:

  • ELFCLASSNONE = 0:无定义【非法】
  • ELFCLASS32 = 1:32位
  • ELFCLASS64 = 2:64位
  • ELFCLASSNUM = 3:未知【非法】

在Android <5 的系统上,由于当年的系统不支持64位的指令集, 因此只要不是32位,就输出错误 “not 32-bit”,并APP闪退。

在Android >=5 系统上,已经出现了64位的指令集 ,如 arm64_v8ax86_64。 若32位的指令集遇到64位的SO库, 会输出错误 “is 64-bit instead of 32-bit”, 并APP闪退; 若32位的指令集遇到64位的SO库, 会输出错误 “is 32-bit instead of 64-bit”, 并APP闪退; 若出现非法的 ELFCLASS, 会输出错误 “has unknown ELF class: ?”, 并APP闪退。

EI_DATA
  • Offset: 5 (EI_DATA)
  • Length: 1
  • Type: Number (unsigned char)

这个是判断 ELF文件是 LSB(Little-endian,低字节序) 还是 MSB(Big-endian,高字节序)。

常量定义:

  • ELFDATANONE = 0:无定义【非法】
  • ELFDATA2LSB = 1:LSB
  • ELFDATA2MSB = 2:MSB【非法】

安卓系统只允许 LSB,因此只要不是 1, 就输出错误 “not little-endian”, 并APP闪退。

EI_VERSION
  • Offset: 6
  • Length: 1
  • Type: Number (unsigned char)

顾名思义,是 ELF 文件格式的版本号,默认是 EV_CURRENT(= 1)。

Android系统不从这里检测 Version,而在 e_version 上检测,因此修改无影响。

EI_OSABI
  • Offset: 7
  • Length: 1
  • Type: Number (unsigned char)

指出来该 ELF 文件可以在什么操作系统运行,参考:

代码语言:javascript
复制
#define ELFOSABI_NONE       0   /* UNIX System V ABI */
#define ELFOSABI_SYSV       0   /* Alias.  */
#define ELFOSABI_HPUX       1   /* HP-UX */
#define ELFOSABI_NETBSD     2   /* NetBSD.  */
#define ELFOSABI_LINUX      3   /* Linux.  */
#define ELFOSABI_SOLARIS    6   /* Sun Solaris.  */
#define ELFOSABI_AIX        7   /* IBM AIX.  */
#define ELFOSABI_IRIX       8   /* SGI Irix.  */
#define ELFOSABI_FREEBSD    9   /* FreeBSD.  */
#define ELFOSABI_TRU64      10  /* Compaq TRU64 UNIX.  */
#define ELFOSABI_MODESTO    11  /* Novell Modesto.  */
#define ELFOSABI_OPENBSD    12  /* OpenBSD.  */
#define ELFOSABI_ARM_AEABI  64  /* ARM EABI */
#define ELFOSABI_ARM        97  /* ARM */
#define ELFOSABI_STANDALONE 255 /* Standalone (embedded) application */

Android系统下的 SO文件 此处的值默认为 0, 而且加载时不检测,修改无影响。

EI_ABIVERSION
  • Offset: 8
  • Length: 1
  • Type: Number (unsigned char)

指出该 ELF 文件可以在哪个API版本下运行,Android下的默认值是 0

此处加载时不检测,修改无影响。

EL_PAD
  • Offset: 9
  • Length: 7

填充位,无检测,修改无影响。

Android >= 6 的系统版本只针对 0x00 - 0x05 之间的字节进行检测, 0x06 - 0x0F 之间的字节(10 Bytes)无检测, 因此可以任意修改,存放想要存放的数据。

e_type

  • Offset: 0x10
  • Length: 2
  • Type: unsigned short

ELF文件类型,如:

代码语言:javascript
复制
#define ET_NONE 0 /* 无定义【非法】 */
#define ET_REL 1 /* 已编译未链接 */
#define ET_EXEC 2 /* 已编译已链接的可执行程序 */
#define ET_DYN 3 /* 已编译已链接的动态链接库 */

ET_REL 指的是 Relocatable file, 缺少 Program Header, 不可以加载到内存中, gcc 编译时候生成的 .o 文件就是 REL文件。

ET_EXEC 指的是可执行程序, 存在程序入口, 有 Program Header, 可以加载到内存中运行, 在 Linux 下的可执行程序都是这样的。

ET_DYN 特指动态链接库。

由于Android的SO库本质就是动态链接库, 因此SO库编译后 e_type = ET_DYN

Android系统会检测 e_type。 若不为 ET_DYN,则抛出错误 has unexpected e_type, 并APP闪退。

e_machine

  • Offset: 0x12
  • Length: 2
  • Type: unsigned short

ELF 文件的CPU平台属性(指令集)。

代码语言:javascript
复制
#if defined(__arm__)
  return EM_ARM;
#elif defined(__aarch64__)
  return EM_AARCH64;
#elif defined(__i386__)
  return EM_386;
#elif defined(__mips__)
  return EM_MIPS;
#elif defined(__x86_64__)
  return EM_X86_64;
#endif

Android系统会使用 GetTargetElfMachine 函数 检测SO库的 e_machine 和现在的系统是否一致。 若不一致,则抛出错误 has unexpected e_machine, 并APP闪退。

e_version

  • Offset: 0x14
  • Length: 4
  • Type: unsigned int

顾名思义,是 ELF 文件格式的版本号,默认是 EV_CURRENT(= 1)。

Android系统会检测 e_version。 若不为 EV_CURRENT,则抛出错误 has unexpected e_version, 并APP闪退。

e_entry

  • Offset: 0x18
  • Length: 4 (32bits)
  • Type: unsigned int

ELF程序的入口虚拟地址。仅用于可执行程序加载完成后,从此处开始执行进程指令。

动态链接库不存在入口地址,所以Android系统不检测。

e_phoff

  • Offset: 0x1C
  • Length: 4 (32bits)
  • Type: unsigned int

程序头表的偏移,涉及“连接视图”和“执行视图”。

与实际SO库中代码的指令执行相关,不允许修改。

e_shoff

  • Offset: 0x20
  • Length: 4 (32bits)
  • Type: unsigned int

段表在文件中的偏移,涉及读取段表。

Android <7 时,读取段表依靠视图,使用的是 e_phoff,而非 e_shoff, 因此可以随意修改。

e_flags

  • Offset: 0x24
  • Length: 4 (32bits)
  • Type: unsigned int

ELF标志位,用来标志一些ELF文件平台相关的属性。

Android 系统不使用也不检测此参数。

e_ehsize

  • Offset: 0x28
  • Length: 2
  • Type: unsigned short

ELF头部的长度。

Android 系统不使用也不检测此参数。

e_phentsize

  • Offset: 0x2A
  • Length: 2
  • Type: unsigned short

程序头的大小。

Android 系统不使用也不检测此参数。

e_phnum

  • Offset: 0x2C
  • Length: 2
  • Type: unsigned short

在执行视图中,Segments的数量。

Android 系统对其进行检测,并且严格到实际的数目。 若不一致,则抛出错误 “has invalid e_phnum”、“has invalid phdr offset/size” 或者 “phdr mmap failed”等。

涉及函数 ElfReader::ReadProgramHeadersElfReader::ReadProgramHeader

e_shentsize

  • Offset: 0x2E
  • Length: 2
  • Type: unsigned short

段表描述符的大小,= sizeof(ElfW(Shdr))

Android <= 6 系统不使用也不检测此参数。

Android >= 7 系统检测此参数。 若与 sizeof(ElfW(Shdr)) 不相等, 则抛出错误 “has unsupported e_shentsize”。

e_shnum

  • Offset: 0x30
  • Length: 2
  • Type: unsigned short

段表描述符的数量。这个值等于ELF文件中拥有的段(section)的数量。

Android <= 6 系统不使用也不检测此参数。

Android >= 7 系统检测并使用此参数。 若为0,则抛出错误 “has no section headers”。 若超出文件大小范围,则抛出错误 “has invalid shdr offset/size”。

参考函数 ElfReader::ReadSectionHeaders

e_shstrndx

  • Offset: 0x32
  • Length: 2
  • Type: unsigned short

段表字符串表所在的段在段表中的下标,一般是 = e_shnum - 1

Android <= 6 系统不使用也不检测此参数。

Android >= 7 系统检测此参数。 若为0,则抛出错误 “has invalid e_shstrndx”。

参考函数 ElfReader::VerifyElfHeader

注意点

Android为了提升so库的加载速度,会在ELF的头部预存一部分字节数据,这些数据会随着so的加载而加载,可以在内存直接读取。

  1. 0x06 - 0x0F 合计 10Bytes
  2. 0x18 - 0x1B 合计 4Bytes
  3. 0x24 - 0x2B 合计 8Bytes

存储空间合计为 22Bytes。

作者的话

个人喜欢计算机技术,主要涉及的领域包括:Android系统,Linux内核,嵌入式软/硬件,机器人和智能硬件。同时也对其他的各个技术栈都感兴趣。

同时也很喜欢生活,喜欢享受生活,喜欢用拍照和视频的方式来记录生活。

如果你也是个爱学习爱技术的人,欢迎一起探讨。没准,咱们能称为好朋友。如果觉得本文有哪些不对的地方,欢迎指出,大家一起学习进步。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-09-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 技闲说 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • EI_MAG
  • EI_CLASS
  • EI_DATA
  • EI_VERSION
  • EI_OSABI
  • EI_ABIVERSION
  • EL_PAD
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档