2. ARMv8寄存器初探
任务目标
编写一段ARMv8 AArch64汇编代码,将X0-X30共31个通用寄存器的当前值保存到内存缓冲区,并通过C函数打印输出,以此直观理解通用寄存器的编号规则与用途。
核心实现
本案例使用GCC交叉编译器,通过内联汇编将寄存器值存入数组,再由printf输出。无需操作系统,纯静态编译即可运行。
// regs_dump.c
#include <stdio.h>
#include <stdint.h>
#define REG_COUNT 31
// 将X0-X30的值依次存入buf数组
static void capture_regs(uint64_t *buf) {
register uint64_t x0 asm("x0") = 0;
register uint64_t x1 asm("x1") = 1;
register uint64_t x2 asm("x2") = 2;
register uint64_t x3 asm("x3") = 3;
register uint64_t x4 asm("x4") = 4;
register uint64_t x5 asm("x5") = 5;
register uint64_t x6 asm("x6") = 6;
register uint64_t x7 asm("x7") = 7;
register uint64_t x8 asm("x8") = 8;
register uint64_t x9 asm("x9") = 9;
register uint64_t x10 asm("x10") = 10;
register uint64_t x11 asm("x11") = 11;
register uint64_t x12 asm("x12") = 12;
register uint64_t x13 asm("x13") = 13;
register uint64_t x14 asm("x14") = 14;
register uint64_t x15 asm("x15") = 15;
register uint64_t x16 asm("x16") = 16;
register uint64_t x17 asm("x17") = 17;
register uint64_t x18 asm("x18") = 18;
register uint64_t x19 asm("x19") = 19;
register uint64_t x20 asm("x20") = 20;
register uint64_t x21 asm("x21") = 21;
register uint64_t x22 asm("x22") = 22;
register uint64_t x23 asm("x23") = 23;
register uint64_t x24 asm("x24") = 24;
register uint64_t x25 asm("x25") = 25;
register uint64_t x26 asm("x26") = 26;
register uint64_t x27 asm("x27") = 27;
register uint64_t x28 asm("x28") = 28;
register uint64_t x29 asm("x29") = 29;
register uint64_t x30 asm("x30") = 30;
asm volatile(
"stp x0, x1, [%0, #0]\n"
"stp x2, x3, [%0, #16]\n"
"stp x4, x5, [%0, #32]\n"
"stp x6, x7, [%0, #48]\n"
"stp x8, x9, [%0, #64]\n"
"stp x10, x11, [%0, #80]\n"
"stp x12, x13, [%0, #96]\n"
"stp x14, x15, [%0, #112]\n"
"stp x16, x17, [%0, #128]\n"
"stp x18, x19, [%0, #144]\n"
"stp x20, x21, [%0, #160]\n"
"stp x22, x23, [%0, #176]\n"
"stp x24, x25, [%0, #192]\n"
"stp x26, x27, [%0, #208]\n"
"stp x28, x29, [%0, #224]\n"
"str x30, [%0, #240]\n"
:
: "r"(buf)
: "memory"
);
// 防止编译器优化掉上面的寄存器赋值
(void)x0; (void)x1; (void)x2; (void)x3;
(void)x4; (void)x5; (void)x6; (void)x7;
(void)x8; (void)x9; (void)x10; (void)x11;
(void)x12; (void)x13; (void)x14; (void)x15;
(void)x16; (void)x17; (void)x18; (void)x19;
(void)x20; (void)x21; (void)x22; (void)x23;
(void)x24; (void)x25; (void)x26; (void)x27;
(void)x28; (void)x29; (void)x30;
}
int main() {
uint64_t regs[REG_COUNT] = {0};
capture_regs(regs);
const char *names[REG_COUNT] = {
"X0", "X1", "X2", "X3", "X4", "X5", "X6", "X7",
"X8", "X9", "X10", "X11", "X12", "X13", "X14", "X15",
"X16", "X17", "X18", "X19", "X20", "X21", "X22", "X23",
"X24", "X25", "X26", "X27", "X28", "X29", "X30"
};
for (int i = 0; i < REG_COUNT; i++) {
printf("%-4s = 0x%016lx\n", names[i], regs[i]);
}
return 0;
}
编译与运行
# 安装交叉编译器(Ubuntu/Debian)
sudo apt install gcc-aarch64-linux-gnu qemu-user
# 编译(静态链接避免动态库依赖)
aarch64-linux-gnu-gcc -static -o regs_dump regs_dump.c
# 在x86主机上通过QEMU运行
qemu-aarch64 ./regs_dump
验证方法
程序输出31行寄存器值。由于代码在进入capture_regs前将X0-X30分别赋值为0~30,因此预期输出中每行应满足:X0=0x0,X1=0x1,…,X30=0x1e。若输出与上述一一对应,则证明寄存器读取与存储逻辑正确。
寄存器用途速查
| 寄存器 | AT&T别名 | 主要用途 |
|---|---|---|
| X0-X7 | 参数/返回值 | 传参、返回值、临时变量 |
| X8 | 间接结果 | 间接寻址/系统调用号 |
| X9-X15 | 临时 | 调用者保存的临时寄存器 |
| X16-X17 | IP0/IP1 | 过程内调用临时(veneer) |
| X18 | 平台寄存器 | 平台相关,通常不用 |
| X19-X28 | 被调用者保存 | 需由被调函数保存/恢复 |
| X29 | FP | 帧指针 |
| X30 | LR | 链接寄存器(返回地址) |
注意:X16/X17可能被链接器生成的veneer代码修改,X18为平台保留,实际编程时应避免长期持有这些寄存器的值。