ARM64汇编03 - PC寄存器

本文主要讨论 pc 寄存器的一些行为。

我们使用前文编译的 arm32 程序来做实验,由于 arm64 里面已经没有了 mov pc r0 这样的指令,无法直接对 pc 寄存器进行读写操作,所以只能采用 arm32 来演示。

PC寄存器的读写 - arm模式

arduino 复制代码
#include <stdio.h>

int main()
{

    while (1)
    {
        getchar();
        printf("hello\n");
    }

    return 0;
}

在 IDA 上调试程序,我们测试一下写入 PC 的值:

此时,调试窗口与寄存器窗口的 PC值为0xE9DAF4E0。

我们 patch 程序,将断点位置的指令改为 mov pc, r0 ,在这之前给 r0 赋值 0xE9DAF4EC,也就是往下数3条指令的位置:

当我们按 F7 执行完这条指令后,按照道理来说,程序应该会跳转到 0xE9DAF4EC 这个位置来执行。我们试一下:

程序确实跳转到了这个位置。

我们再来测试一下读取 PC 寄存器的值 ,此时,PC 的值是 0xE9DAF4EC:

patch 一下程序,将指令改成 mov r0, pc :

理论上来说,当我们 F7 单步执行这条指令后,r0 的值应该是 0xE9DAF4EC 才对。我们试一下,看看效果:

单步之后,发现 r0 的值,不是0xE9DAF4EC 而是 0xE9DAF4F4 ,这就很让人疑惑了。

这个玩意,我之前也是有所耳闻的。现在网上查的资料,对这个现象的解释都比较统一,就说arm是3级的流水线,根据指令循环来算,当一条指令开始执行的时候,已经开始fetch第3条指令的,所以会导致 pc = pc + 8。

之前,我是比较相信这个说法的,但是现在有点存疑。看下面这个实验:

MOV 指令下面有一个 B 操作,是无条件跳转。按照道理来说,fetch 操作应该取的是sub_E9DE237C处的第一条指令,所以此时 PC 的值应该是 0xE9DE237C:

但是,我们单步过去发现 pc 的值依然是 pc + 8:

除非arm没有分支预测,不然也太蠢了。

还有一个就是,x86 的 pc 也有类似的行为,pc 是指向下一条指令,但是你要说它的流水线只有2级,那显然不对。不管如何,只需要在读取 PC 的值时,注意即可。

结论:arm模式下,pc = pc + 8。

PC 寄存器读取 - Thumb 模式

单步过去,查看 r0 寄存器的值:

可以看到,在 thumb 模式下, pc = pc + 4。

由于 thumb 是变长指令,那么这个 4 是固定值还是说是下两条指令呢?

我们做一个实验,将当前指令的下面的指令改成4字节长度的:

单步过去,查看 r0 寄存器的值:

发现,r0 的值是 0xB005E616。这就说明了,thumb 模式下 pc 的值就是 pc = pc + 4。不论指令长度,这也从侧面说明了,pc 的值跟流水线没啥关系。流水线不可能读指令读一半。

结论:thumb 模式下 pc 的值就是 pc = pc + 4。

IDA中调试LDR指令读取PC的奇怪行为 - arm 模式

比如,LDR R0, [PC],这条指令的意思是读取内存中pc寄存器储存的地址的位置的值。比较绕,其实就是将 memorypc 的值赋值给 r0 寄存器。

我们使用 ida 来观察一下:

此时,pc 的值是 0xF2E914E0 ,由于 pc = pc + 8,pc 的值就是 01 0A 70 E3 ,单步过去,看下 r0 的值:

与指令表达的意思一致,非常的合理。

我们再看另一个例子(arm指令集中立即数使用 # 前缀):LDR R0, [PC, #-4]

这个指令也好理解,就是读取 memorypc-4 的值,由于 pc = pc + 8,所以最终的结果应该是 memorypc+4 的值,在 IDA 中观察一下:

我们期望单步后,R0 寄存器中的值是 0xE3700A01,单步看看结果:

发现,r0 寄存器的值变成了一个没见过的东西:0xE7F001F0 。

要是能立马想到这个数字是什么原因导致的,就说明对 IDA 非常熟了。

我们,将这个值 patch 到 pc 位置,让 ida 翻译一下:

发现,这个值代表的指令的意思是 UND。

之前,我们在 IDA 的系列中,讲到过IDA断点的实现逻辑:

当我们在IDA中对代码的某一行设置断点时,调试器会先把这里的本来指令的第一个字节保存起来,然后写入一条INT 3指令,因为INT 3指令的机器码为11001100b(0xCC)当运行到这的时候CPU会捕获一条异常,转去处理异常,CPU会保留上上下文环境,然后中断到调试器,大多数调试器的做法是在被调试程序中断到调试器时,会先将所有断点位置被替换为INT 3的指令恢复成原来的指令,然后再把控制权交给用户。

这个的 UND 就相当于 x86 的 cc 断点。所以,我们单步的时候,读出来的数据是被 IDA 改过的数据。

IDA中调试LDR指令读取PC的奇怪行为 - Thumb 模式

与 arm 一样,在 thumb 中也是使用 UND 指令来实现断点LDR r0, [pc]:

步过之后,期望得到 0x00000122 ,但是却得到了 0x0000DE10,其原因就是 DE10 就是 thumb 模式下的 UND 指令。

还有一个特殊的地方,那就是 LDR 会强制访问的地址 4 字节对齐。

比如,我们访问一个不是4字节对齐的地址LDR r0, [pc, #4]:

将下面一条指令也改成LDR r0, [pc, #4]:

神奇的事情出现了,发现 ida 给我们自动算出来的地址是一样的。说明在 B438C624处与在 B438C626,使用 ldr 访问 pc, #4 处的地址是一样的结果。

这就是因为 LDR 指令会将最终计算出来的地址向下进行 4 字节对齐,B438C626 + 8 = B438C62E,对齐后得到 B438C62C 。

相关推荐
福兮说4 小时前
设计稿是 #4A7C6F,页面量出来是 #4B7C6F:HEX、HSL、透明度、canvas 来回转的七个坑
前端·javascript·css·canvas
郑州光合科技余经理5 小时前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程
小呆呆6665 小时前
副业搞起来,小说,漫画,漫剧的成本优化思路
前端·后端·面试
Dovis(誓平步青云)6 小时前
浇水提醒刚弹出又消失,植物状态别只存一个百分比
开发语言·前端·javascript·pdf·ecmascript·电脑
码艺-Alimjan6 小时前
Vben Admin 新增维吾尔语 Vben-Modal的关键坑之一
前端·javascript·vue.js
可乐鸡翅yeah_7 小时前
hls.js 手动自定义 http 请求 loader,修改请求头实战
开发语言·前端·javascript·网络协议·http·ecmascript·m3u8在线
IT_陈寒7 小时前
Vite静态资源导入这个坑我帮你们踩过了
前端·人工智能·后端
广州华水科技7 小时前
大坝安全监测解决方案:单北斗GNSS形变监测系统应用与维护
前端
yivifu8 小时前
中文古籍电子书注释集成
前端·javascript·python·beautifulsoup·epub
默_笙9 小时前
🛴 从散件到整机:DeepAgents 与 Agent 身上预留的那些"插槽"(前置介绍)
前端·javascript