General Reg

目录

入门汇编指令

Assembly_Intro

ARM64寄存器

什么是寄存器?

寄存器是CPU内部的小型、超高速存储单元,用于临时存放数据和指令。可以把它想象成工作台——CPU直接在寄存器上进行计算,比访问内存快得多。

ARM64寄存器分类(AArch64)

1. 通用寄存器(X0-X30)

ARM64有31个64位通用寄存器,编号X0到X30:

寄存器 别名 主要用途 说明
X0 - 函数参数1 / 返回值 第一个参数和函数返回值
X1-X7 - 函数参数2-8 额外的函数参数
X8 - 间接结果寄存器 特殊用途
X9-X15 - 临时寄存器 调用时不保存,随意使用
X16-X17 - 内部临时寄存器 系统使用,避免使用
X18 - 平台寄存器 保留给平台
X19-X28 - 被保存寄存器 调用时必须保存原值
X29 FP 帧指针 指向当前栈帧底部
X30 LR 链接寄存器 存储函数返回地址(保护现场)

2. 特殊功能寄存器

寄存器 名称 作用
XZR 零寄存器 读取总是0,写入无效果
SP 栈指针 指向当前栈顶
PC 程序计数器 指向下一条要执行的指令

寄存器大小访问

每个64位寄存器都可以按不同大小访问:

X0  // 访问完整的64位寄存器
W0  // 只访问低32位(高32位清零)

示例:

MOV X0, 0x123456789ABCDEF0  @ X0 = 0x123456789ABCDEF0
MOV W1, 0x12345678          @ W1 = 0x0000000012345678
ADD W0, W1, W1              @ X0 = 0x000000002468ACF0

关键寄存器详解

1. 参数和返回值寄存器(X0-X7)

函数调用示例:

// C代码
int result = add(10, 20, 30);

// 对应的寄存器使用:
// X0 = 10 (第一个参数)
// X1 = 20 (第二个参数) 
// X2 = 30 (第三个参数)
// 返回值通过 X0 返回

2. 链接寄存器 LR(X30)

存储函数调用的返回地址:

BL my_function    @ 跳转到my_function,同时将返回地址保存到LR
                  @ 在my_function中:
RET               @ 等同于 MOV PC, LR,跳回到调用处

3. 栈指针 SP

管理函数调用的栈空间:

SUB SP, SP, #16   @ 在栈上分配16字节空间
ADD SP, SP, #16   @ 释放栈空间

4. 零寄存器 XZR

提供常数值0,很有用:

MOV X0, XZR       @ X0 = 0
CMP X1, XZR       @ 比较X1是否等于0

实际编程中的寄存器使用

函数调用约定

@ 函数定义
my_function:
    SUB SP, SP, #16       @ 1. 在栈上分配空间
    STR X30, [SP, #8]     @ 2. 保存返回地址(LR)
    STR X29, [SP, #0]     @ 3. 保存帧指针(FP)

    @ 函数体...
    ADD X0, X0, X1        @ 使用X0,X1(参数寄存器)

    LDR X29, [SP, #0]     @ 恢复FP
    LDR X30, [SP, #8]     @ 恢复LR  
    ADD SP, SP, #16       @ 释放栈空间
    RET                   @ 返回

简单计算示例

@ 计算 (a + b) * c
@ 假设: a在X0, b在X1, c在X2

ADD X3, X0, X1    @ X3 = a + b (使用临时寄存器X3)
MUL X0, X3, X2    @ X0 = (a+b) * c,结果放在X0返回
RET

寄存器使用规则

调用者保存的寄存器(临时寄存器)

  • X0-X18:在函数调用中可能被修改,调用者需要保存重要数据

被调用者保存的寄存器

  • X19-X28:如果函数要使用这些寄存器,必须先在栈上保存原值,返回前恢复

与C语言的对应关系

C函数 ↔ 汇编寄存器

// C代码
int calculate(int a, int b, int c) {
    int temp = a + b;
    return temp * c;
}

对应的汇编寄存器使用: - aX0 - bX1 - cX2 - tempX3(临时寄存器) - 返回值 → X0

重要提示

  1. 寄存器是稀缺资源:只有31个通用寄存器,要高效使用
  2. 理解数据流:观察数据如何在寄存器间流动
  3. 栈的重要性:当寄存器不够时,使用栈来存储临时数据
  4. 调用约定:遵循寄存器使用规则,确保函数间正确协作

记住:寄存器是CPU的工作台,所有计算都在这里发生!

ARM32寄存器

ARM32架构有16个32位核心寄存器,所有函数都共享这同一组寄存器

通用寄存器:

  • R0-R3:参数/结果寄存器(调用者保存)- 像"临时工作台",用于传递前4个参数
  • R4-R11:被调用者保存寄存器 - 像"个人工作台",用完后必须恢复原样
  • R12:内部调用暂存寄存器

特殊功能寄存器:

  • R13 (SP):栈指针 - 总管理员座位,也用于传递第5个及以后的参数
  • R14 (LR):链接寄存器 - 电话机,记录返回地址
  • R15 (PC):程序计数器 - 当前任务指示牌

寄存器分配给函数的规则(AAPCS调用约定)

所有函数共用寄存器,但通过明确规则协作:

参数传递规则:

// 所有函数都遵守统一的参数传递规则
void func(int a, int b, int c, int d, int e, int f);
// 调用时:
// R0 = a, R1 = b, R2 = c, R3 = d (前4个参数)
// [SP+0] = e, [SP+4] = f (第5、6个参数通过栈传递)

返回值规则: - 所有函数都通过R0返回结果

参数数量不同的处理方式

情况1:4个及以下参数(全部用寄存器)

; 调用 func(1, 2, 3, 4)
MOV R0, #1      ; 参数1 → R0
MOV R1, #2      ; 参数2 → R1  
MOV R2, #3      ; 参数3 → R2
MOV R3, #4      ; 参数4 → R3
BL func         ; 调用函数

情况2:超过4个参数(混合使用寄存器和栈)

; 调用 func(1, 2, 3, 4, 5, 6)
MOV R0, #1      ; 参数1 → R0
MOV R1, #2      ; 参数2 → R1
MOV R2, #3      ; 参数3 → R2  
MOV R3, #4      ; 参数4 → R3
SUB SP, SP, #8  ; 为额外参数分配栈空间
MOV R4, #5
STR R4, [SP, #0] ; 参数5 → [SP+0]
MOV R4, #6
STR R4, [SP, #4] ; 参数6 → [SP+4]
BL func         ; 调用函数
ADD SP, SP, #8  ; 清理栈空间

函数如何使用这些共享寄存器

函数开场典型模式:

func:
    PUSH {R4-R6, LR}    ; 如果要使用R4-R11必须先保存

    ; 访问参数
    ; 前4个参数直接在R0-R3中
    MOV R4, R0          ; 参数1保存到R4
    MOV R5, R1          ; 参数2保存到R5

    ; 访问第5个及以后的参数需要计算栈偏移
    LDR R6, [SP, #16]   ; 参数5在[SP+16]跳过PUSH的寄存器

寄存器使用策略:

  • R0-R3:临时计算,任何函数都可以直接用,也用于参数传递
  • R4-R11:任何函数想用就必须先保存再恢复
  • :用于传递额外参数和保存局部变量
  • 所有函数都遵守同样的规则

各个函数如何通过共享寄存器联系

1. 参数和返回值的传递(考虑多参数情况)

; main函数准备参数6个参数
main:
    MOV R0, #10         ; 参数1  R0
    MOV R1, #20         ; 参数2  R1
    MOV R2, #30         ; 参数3  R2
    MOV R3, #40         ; 参数4  R3
    SUB SP, SP, #8      ; 为额外参数分配空间
    MOV R4, #50
    STR R4, [SP, #0]    ; 参数5  [SP+0]
    MOV R4, #60         
    STR R4, [SP, #4]    ; 参数6  [SP+4]
    BL complex_calc     ; 调用函数

; complex_calc函数接收所有参数
complex_calc:
    PUSH {R4-R6, LR}
    ; 接收寄存器中的参数
    MOV R4, R0          ; 参数1 = 10
    MOV R5, R1          ; 参数2 = 20
    ; 接收栈中的参数
    LDR R6, [SP, #16]   ; 参数5 = 50
    ; ... 计算 ...
    MOV R0, R4          ; 结果通过R0返回
    POP {R4-R6, PC}

2. 调用者保存寄存器(R0-R3, R12)的协作

main:
    MOV R4, R0              ; 如果R0有重要数据,先保存到R4
    BL other_func           ; 调用其他函数
    ; 我知道other_func可能会改动R0-R3,所以不依赖它们
    MOV R0, R4              ; 恢复我原来的数据

3. 被调用者保存寄存器(R4-R11)的协作

; 任何函数如果想用R4-R11都必须
my_func:
    PUSH {R4-R6}        ; 先保存原来的值
    ; ... 使用R4-R6 ...  ; 随便使用
    POP {R4-R6}         ; 恢复原样不影响其他函数
    BX LR

完整的协作示例(包含多参数)

// 所有函数共享同一套寄存器,但通过规则协作
int add(int a, int b) {
    return a + b;       // 用R0,R1接收参数,R0返回结果
}

int complex_calc(int a, int b, int c, int d, int e, int f) {
    // 前4个参数在寄存器,后2个在栈中
    int sum = a + b + c + d + e + f;
    return add(sum, 100); // 继续调用其他函数
}
; 所有函数共用寄存器但各司其职
add:
    ; 我知道R0=a, R1=b来自调用者
    ADD R0, R0, R1      ; 计算a+b
    ; 我知道调用者期望结果在R0
    BX LR               ; 返回

complex_calc:
    PUSH {R4-R6, LR}    ; 我要用R4-R6所以先保存
    ; 接收前4个参数R0=a, R1=b, R2=c, R3=d
    MOV R4, R0
    MOV R5, R1
    ADD R4, R4, R5      ; a + b
    ADD R4, R4, R2      ; + c
    ADD R4, R4, R3      ; + d

    ; 接收栈中的第56个参数
    LDR R5, [SP, #16]   ; 参数e (跳过PUSH的4个寄存器)
    LDR R6, [SP, #20]   ; 参数f
    ADD R4, R4, R5      ; + e
    ADD R4, R4, R6      ; + f

    ; 调用add函数
    MOV R0, R4          ; 第一个参数 = sum
    MOV R1, #100        ; 第二个参数 = 100
    BL add

    ; 我知道add的结果在R0中
    POP {R4-R6, PC}     ; 恢复寄存器返回

关键理解点

  1. 不是每个函数有专属寄存器,而是所有函数共用16个寄存器
  2. 通过明确的使用规则来避免冲突
  3. 参数传递
  4. 前4个:R0-R3寄存器(最快)
  5. 第5个及以后:栈空间(稍慢)
  6. 返回值:始终通过R0返回
  7. 长期数据要么用R4-R11(但要保存恢复),要么用栈

这种设计既高效(大多数调用用寄存器)又灵活(支持任意多参数),在性能和实用性间完美平衡!