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

相关推荐
前端(从入门到入土)3 分钟前
前端请求后端服务403(Invalid CORS request)
前端
蓝天白云下遛狗30 分钟前
goole chrome变更默认搜索引擎为百度
前端·chrome
come112341 小时前
Vue 响应式数据传递:ref、reactive 与 Provide/Inject 完全指南
前端·javascript·vue.js
musk12122 小时前
electron 打包太大 试试 tauri , tauri 安装打包demo
前端·electron·tauri
万少3 小时前
第五款 HarmonyOS 上架作品 奇趣故事匣 来了
前端·harmonyos·客户端
OpenGL3 小时前
Android targetSdkVersion升级至35(Android15)相关问题
前端
rzl023 小时前
java web5(黑马)
java·开发语言·前端
Amy.Wang3 小时前
前端如何实现电子签名
前端·javascript·html5
今天又在摸鱼3 小时前
Vue3-组件化-Vue核心思想之一
前端·javascript·vue.js
蓝婷儿3 小时前
每天一个前端小知识 Day 21 - 浏览器兼容性与 Polyfill 策略
前端