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寄存器储存的地址的位置的值。比较绕,其实就是将 memory[pc] 的值赋值给 r0 寄存器。

我们使用 ida 来观察一下:

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

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

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

这个指令也好理解,就是读取 memory[pc-4] 的值,由于 pc = pc + 8,所以最终的结果应该是 memory[pc+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

相关推荐
用户47949283569157 分钟前
Safari 中文输入法的诡异 Bug:为什么输入 @ 会变成 @@? ## 开头 做 @ 提及功能的时候,测试同学用 Safari 测出了个奇怪的问题
前端·javascript·浏览器
没有故事、有酒19 分钟前
Ajax介绍
前端·ajax·okhttp
朝新_23 分钟前
【SpringMVC】详解用户登录前后端交互流程:AJAX 异步通信与 Session 机制实战
前端·笔记·spring·ajax·交互·javaee
裴嘉靖25 分钟前
Vue 生成 PDF 完整教程
前端·vue.js·pdf
毕设小屋vx ylw28242628 分钟前
Java开发、Java Web应用、前端技术及Vue项目
java·前端·vue.js
冴羽1 小时前
今日苹果 App Store 前端源码泄露,赶紧 fork 一份看看
前端·javascript·typescript
蒜香拿铁1 小时前
Angular【router路由】
前端·javascript·angular.js
brzhang2 小时前
读懂 MiniMax Agent 的设计逻辑,然后我复刻了一个MiniMax Agent
前端·后端·架构
西洼工作室2 小时前
高效管理搜索历史:Vue持久化实践
前端·javascript·vue.js
广州华水科技2 小时前
北斗形变监测传感器在水库安全中的应用及技术优势分析
前端