2.2 寄存器组全景


2.2 寄存器组全景

本节摘要:AArch64 的编程模型可以压缩成一张名册:三十一个通用寄存器 x0x30(各可按 64 位 x 或 32 位 w 访问)、恒零寄存器 xzr、按异常级别换影子的 sp、几乎不可直接寻址的 pc、携带条件标志的 pstate,以及一大片以 msrmrs 访问的系统寄存器。本节按"通用、专用、系统"三层把名册过一遍,并做一个寄存器现场核对实验。

上一节我们从时间轴看完了楼层怎么加盖,本节回到空间轴:走进今天的楼,把房间里每一张牌认一遍。认牌是后面一切章节的前提——第 3 章的编码字段全是寄存器编号,第 5 章的栈帧手推全程围着 spx29x30 转。先说清一个阅读口径:教材与手册里"通用寄存器"与"特殊寄存器"的分界略有出入,本节采用"通用、专用、系统"三分法,把旧教材里分开讲的两组合并到一张图上看,因为它们本来就长在同一套编号体系里。

通用寄存器:三十一张牌与两个马甲

三十一个通用寄存器按调用约定划成四段。参数与返回值段 x0x7:函数调用时前八个参数从这里进,返回值从 x0 出,这是第 5 章 AAPCS 的主角。临时寄存器段 x8x15:可以随手用,但跨函数调用不保证存活——准确说 x8 有个间接结果寄存器的副业,x16x17 被链接器与虚拟平台动用保留。被调方保存段 x19x28:谁想用谁负责保存,函数序言里成排的 stp x19, x20, [sp, -16]! 就是在安顿它们。帧指针与链接寄存器 x29x30:一个给栈帧当锚,一个存返回地址。

每个寄存器有两个马甲:x 名按 64 位访问,w 名按 32 位访问。写 w0 只改 x0 的低 32 位,高 32 位清零而不是保留——这是 AArch64 与许多旧架构不同的地方,也是初学者高频踩坑点:想在循环里对 64 位计数器用 w 后缀做加法,等于每次把高位清掉。另有第 32 号"编外"寄存器 xzr:读它得零,写它等于丢进下水道,mov x0, xzr 因此是清零的惯用法。

图 2-2:AArch64 通用寄存器版图

图 2-2:AArch64 通用寄存器版图

专用与系统寄存器:不占编号的一圈

sppcpstate 这三位是"专用寄存器"。sp 值得单独说:它按异常级别各有一份影子(sp_el0sp_el3),且 AArch64 允许 EL0 用户态选用 x30 之外的独立栈指针,切换时用 mov sp, x29 这类指令搬运。pc 则最特殊——它不能作为普通操作数参与运算,读它的间接办法是 adr x0, .(取当前地址)之类,改它的唯一正路是跳转与 reteret。这与 x86 可以把 rip 当数据用截然不同,是从"你能拿到 PC"到"你只能按规矩走"的收紧。

pstate 是处理器状态的总包:NZCV 四个条件标志(负、零、进位、溢出)、中断屏蔽位、当前异常级别等都在里面。日常打交道最多的是 NZCV,第 5 章条件码一节会逐个标志做实验;访问它的指令不是 mov 而是 mrs x0, nzcv(读)与 msr daifset, #2(改中断屏蔽)这类专用通道。

系统寄存器是最大的一圈:页表基址、异常向量基址、计时器、性能计数器、缓存配置……每一个都有编号如 sctlr_el1ttbr0_el1vbar_el1。它们只能用 mrsmsr 访问,且多数要求 EL1 以上权限——这正是 2.3 节异常级别的用武之地。

实验:一次寄存器现场核对

纸上认牌不如现场点名。用上一章的调试环境,在调试器里把关键寄存器逐个核对:

(gdb) info registers x0 0x7ffffff000 x1 0x5 x2 0x0 x29 0x7ffffff0a0 x30 0x4007c4 sp 0x7ffffff0a0 pc 0x400780 cpsr 0x60000000 // 此行即 pstate 的传统视角 (gdb) p/x $x0 & 0xffffffff // 演示 w0 口径:低 32 位 $1 = 0xfffff000 (gdb) set $x2 = $xzr (gdb) p/d $x2 $2 = 0 // xzr 读零验证

三处值得盯住:x29sp 恰好相等——函数序言刚把帧指针设成栈顶,这是标准 prologue 的指纹;cpsr 显示 0x60000000,二进制下正是 Z 标志与 C 标志同时为 1 的形态,说明此前某条比较指令比较出了"相等";pc 停在 sum 入口而 x30 指向调用方,返回路径已经铺好。变式实验:在函数中途打断点再看 sp,数一数它与入口值的差,再对照反汇编里的 stp 指令条数——你会亲手推出栈帧大小,这正是第 5 章 5.2 节手推栈帧的预热。

延伸案例:一次 w 口径清零引发的真实事故

把"w 写入清零高位"这条规矩再钉一次,用一段真实事故。某驱动里有个 64 位地址累加循环,原作者为省事写了 add w2, w2, #16——他心想"增量只有 16,32 位够了"。单步调试时一切正常,跑到第 4 GiB 之后地址突然折返:w2 的写入把 x2 高 32 位清零,而 x2 里存的是完整地址。症状是"跑一段时间后设备读写错位",日志里看不到任何越权,因为根本没有越权——地址被合法地算错了。

排查这套路数值得记住:在调试器里对地址寄存器设 watchpoint(watch x2),捕获到清零发生的那一条指令,反汇编对准 w 后缀,事故定性只用了十分钟。变式:反过来利用这个特性也是合法技巧——mov w0, #1 正是"构造低 32 位为 1、高 32 位归零"的最短写法,很多库代码靠它省一条 movk。规矩本身无所谓好坏,关键是写的人要清楚它同时在干两件事。

常见问答

问:xzr 既然恒零,为什么寄存器编号还要占用一个编号位? 答:它不占——31 个通用寄存器用满 1 到 31 的五位编号,编号 31 在指令里按上下文解释成 xzrsp:多数数据处理指令里是 xzr,访存与部分系统指令里是 sp。同一个编号两副面孔,这正是把两个"特殊值"塞进有限编码空间的巧办法,第 3 章拆编码时会再碰到它。

问:调试器里怎么一次看清所有寄存器的两种口径? 答:info registers 默认按 x 口径显示;要看 w 口径,逐个打印 p/x $w3 即可——理解了"w 只是低 32 位的视图",两种显示就都成了同一份数据的不同切面。

本节要点回顾

  • 四段分工x0x7 传参返回、x8x15 随手用、x19x28 用完要还、x29x30 管栈帧与返回。
  • w 口径清零:写 w 寄存器把高 32 位清零,不保留——与直觉相反的硬规定。
  • xzr 与 sp:恒零寄存器是清零惯用法的来源;sp 按异常级别有影子副本。
  • pc 不可算:程序计数器不能当数据运算,只能被跳转指令改变。
  • 系统寄存器走专用门mrsmsr 是唯一通道,多数门禁在 EL1 及以上。

名册认完,还剩最后一问:这么大的权限差,处理器靠什么分桌管理?下一节讲异常级别与运行模式,把 EL0 到 EL3 的权限台阶搭起来。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U