API 调用如何在 Android (NDK) 上工作?

逆向工程 linux 安卓 手臂 系统调用 接口
2021-06-25 08:37:11

在windows平台下,应用程序通常会引用它的IAT(Import Access Table)来获取它想要的API的地址,然后调用它。然后一些机制就完成了,正如这里很好地展示的那样

但是,我无法了解 API 调用如何在 Android NDK 上工作。我认为 ELF 文件中没有 IAT。谁能告诉我在汇编级别上 android 上的 API 调用是如何工作的?

例如

当我打电话时,ALooper_acquire(&mylooper)它组装为

 mov r4, 0x2000  ; address of mylooper
 bl 0x7777fff0      ;address of ALooper_acauire
 ... after ALooper_acquire()

然后在0x7777fff0

Blah blah happens to call system api, ...
1个回答

为了回答您的问题,让我们首先在实体和定义方面打下坚实的基础。

ELF代表“可执行和可链接格式”。
也就是说,它定义了两种类型文件的结构和形状:

  • 可执行文件(共享对象 *.so 和独立可执行文件)
  • 可链接(目标文件 *.o)

让我们专注于可执行文件。

可执行文件的依赖解析

其中,ELF定义了一种描述和解决可执行文件依赖项的方法。

依赖关系

简单来说,依赖是需要外部符号的。符号是命名(标识)的内存块。一些块是数据块(全局变量),而另一些是代码数据块(全局函数)。由于符号是模块(又名共享对象)的一部分,因此任何需要的符号都与模块耦合。

总之,依赖是需要符号和模块的。

请注意,作为 OS API 一部分的函数可以并且通常是外部符号。然而,情况并非总是如此。

依赖描述

ELF 定义了一个称为 Dynamic Segment 的结构,用于在可执行文件的加载过程中存储加载器(也称为动态链接器)所需的信息。可执行文件的依赖项描述存储在其动态段中。

需要的符号被组织在一个称为动态符号表的表中,该表由动态段引用:

] 引用加载程序指令下的符号表 - https://elfy.io/KYze4

动态符号表是一个连续的符号描述符数组:

在此处输入图片说明 Symbols 下的 .dynsym - https://elfy.io/KYze4

另一方面,需要的模块直接用 DT_NEEDED 条目描述:

在此处输入图片说明 Loader 指令下需要的模块 - https://elfy.io/KYze4

动态链接

现在我们准备讨论一旦加载器解析可执行文件就可以访问其依赖项的连接机制我们将按照外部函数调用的步骤来完成。

我们以调用__android_log_print为例(ARM 32 位)。

...
   1d21a:       f7fa e8e8       blx     173ec ; __android_log_print@plt
...

上面是一个调用 __android_log_print 的程序集,它将文本打印到 Android Logcat。但实际上,该blx指令分支到称为过程链接表(PLT)的特殊区域中的特定代码存根PLT 中有一个代码存根,用于每个需要的外部功能。

这是 __android_log_print 的存根:

...
000173ec __android_log_print@plt:
   173ec:       e28fc600        add     ip, pc, #0, 12
   173f0:       e28cca11        add     ip, ip, #69632   
   173f4:       e5bcf9f4        ldr     pc, [ip, #2548]! 
000173f8 sleep@plt:
   173f8:       e28fc600        add     ip, pc, #0, 12
   173fc:       e28cca11        add     ip, ip, #69632
   17400:       e5bcf9ec        ldr     pc, [ip, #2540]!
...

存根中的三个指令执行以下操作:(伪代码)

JUMP *(GOT_ADDRESS + GOT_OFFSET_OF(__android_log_print))

所述全局偏移表(GOT)是指针的表。对于每个外部功能,GOT 中都有一个单元格。也就是说,每个外部函数在 GOT 中都有自己的单元格。加载过程完成后,函数 X 的单元格包含函数 X 的内存地址。

  • 由于编码限制,GOT 中正确单元格的地址计算被拆分为 3。例如:不能在单个指令中编码大偏移量。

根据前面讨论的信息,使用正确的内存地址初始化 GOT 是 OS 加载程序的责任。

PLT 和 GOT 是 ELF 规范的一部分。