最近在研究ARM cpu 32 bit转码 64bit的事情,以用于在64bit的服务器上可以更快的运行32bit的Android ELF文件。
特意写篇东西做一下笔记。
对于Android的应用程序来说,最常见的ELF就是so动态库了。它其实类似Windows上的.dll文件。
ELF文件结构
ELF格式的文件中的“数据”实际上是以“段”(节,英文:Section)的形式存储的。 其顺序大致符合下面的排序:
.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_hdr
或 elf64_hdr
,
在Android系统源代码的 /bionic/libc/kernel/uapi/linux/elf.h
可以找到。
以32位程序的ELF头部为例:
#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
EI_MAG0
)SELFMAG
)String
ELF Identification 的前四个字节为魔数(Magic Number),
内容为 {0x7F, 'E', 'L', 'F'}
。
如果魔数不一致,在Android系统会报错“has bad ELF magic”,
终止 load_library
并APP闪退。
EI_CLASS
EI_CLASS
)判断 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_v8a
、x86_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
EI_DATA
)这个是判断 ELF
文件是 LSB
(Little-endian,低字节序)
还是 MSB
(Big-endian,高字节序)。
常量定义:
ELFDATANONE
= 0:无定义【非法】ELFDATA2LSB
= 1:LSB
ELFDATA2MSB
= 2:MSB
【非法】安卓系统只允许 LSB
,因此只要不是 1
,
就输出错误 “not little-endian”,
并APP闪退。
EI_VERSION
顾名思义,是 ELF
文件格式的版本号,默认是 EV_CURRENT
(= 1)。
Android系统不从这里检测 Version
,而在 e_version
上检测,因此修改无影响。
EI_OSABI
指出来该 ELF
文件可以在什么操作系统运行,参考:
#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
指出该 ELF
文件可以在哪个API版本下运行,Android下的默认值是 0
。
此处加载时不检测,修改无影响。
EL_PAD
填充位,无检测,修改无影响。
Android >= 6
的系统版本只针对 0x00 - 0x05
之间的字节进行检测,
0x06 - 0x0F
之间的字节(10 Bytes)无检测,
因此可以任意修改,存放想要存放的数据。
e_type
unsigned short
ELF文件类型,如:
#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
unsigned short
ELF
文件的CPU平台属性(指令集)。
#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
unsigned int
顾名思义,是 ELF
文件格式的版本号,默认是 EV_CURRENT
(= 1)。
Android系统会检测 e_version
。
若不为 EV_CURRENT
,则抛出错误 has unexpected e_version
,
并APP闪退。
e_entry
unsigned int
ELF程序的入口虚拟地址。仅用于可执行程序加载完成后,从此处开始执行进程指令。
动态链接库不存在入口地址,所以Android系统不检测。
e_phoff
unsigned int
程序头表的偏移,涉及“连接视图”和“执行视图”。
与实际SO库中代码的指令执行相关,不允许修改。
e_shoff
unsigned int
段表在文件中的偏移,涉及读取段表。
Android <7
时,读取段表依靠视图,使用的是 e_phoff
,而非 e_shoff
,
因此可以随意修改。
e_flags
unsigned int
ELF标志位,用来标志一些ELF文件平台相关的属性。
Android 系统不使用也不检测此参数。
e_ehsize
unsigned short
ELF头部的长度。
Android 系统不使用也不检测此参数。
e_phentsize
unsigned short
程序头的大小。
Android 系统不使用也不检测此参数。
e_phnum
unsigned short
在执行视图中,Segments的数量。
Android 系统对其进行检测,并且严格到实际的数目。 若不一致,则抛出错误 “has invalid e_phnum”、“has invalid phdr offset/size” 或者 “phdr mmap failed”等。
涉及函数 ElfReader::ReadProgramHeaders
和 ElfReader::ReadProgramHeader
。
e_shentsize
unsigned short
段表描述符的大小,= sizeof(ElfW(Shdr))
。
Android <= 6
系统不使用也不检测此参数。
Android >= 7
系统检测此参数。
若与 sizeof(ElfW(Shdr))
不相等,
则抛出错误 “has unsupported e_shentsize”。
e_shnum
unsigned short
段表描述符的数量。这个值等于ELF文件中拥有的段(section)的数量。
Android <= 6
系统不使用也不检测此参数。
Android >= 7
系统检测并使用此参数。
若为0,则抛出错误 “has no section headers”。
若超出文件大小范围,则抛出错误 “has invalid shdr offset/size”。
参考函数
ElfReader::ReadSectionHeaders
。
e_shstrndx
unsigned short
段表字符串表所在的段在段表中的下标,一般是 = e_shnum - 1
。
Android <= 6
系统不使用也不检测此参数。
Android >= 7
系统检测此参数。
若为0,则抛出错误 “has invalid e_shstrndx”。
参考函数
ElfReader::VerifyElfHeader
。
注意点
Android为了提升so库的加载速度,会在ELF的头部预存一部分字节数据,这些数据会随着so的加载而加载,可以在内存直接读取。
0x06 - 0x0F
合计 10Bytes0x18 - 0x1B
合计 4Bytes0x24 - 0x2B
合计 8Bytes存储空间合计为 22Bytes。
作者的话
个人喜欢计算机技术,主要涉及的领域包括:Android系统,Linux内核,嵌入式软/硬件,机器人和智能硬件。同时也对其他的各个技术栈都感兴趣。
同时也很喜欢生活,喜欢享受生活,喜欢用拍照和视频的方式来记录生活。
如果你也是个爱学习爱技术的人,欢迎一起探讨。没准,咱们能称为好朋友。如果觉得本文有哪些不对的地方,欢迎指出,大家一起学习进步。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有