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-X17IP0/IP1过程内调用临时(veneer)
X18平台寄存器平台相关,通常不用
X19-X28被调用者保存需由被调函数保存/恢复
X29FP帧指针
X30LR链接寄存器(返回地址)

注意:X16/X17可能被链接器生成的veneer代码修改,X18为平台保留,实际编程时应避免长期持有这些寄存器的值。