ARM 汇编入门程序

32 阅读4分钟

🎯 程序功能:

计算 (10 - 3) * 4 = 28,然后输出 "Result: 28"

平台:ARM32 Linux(树莓派 / QEMU)
编译:gcc -o calc calc.s
输出:Result: 28


✅ 完整代码 + 超详细逐行注释

@ =============================================
@ 文件名: calc.s
@ 功能: 计算 (10 - 3) * 4 = 28,调用子函数完成,输出结果
@ 演示新指令: sub, mul, push/pop, bl, bx, ldr/str, cmp/bne
@ =============================================

.section .data
    output_template:
        .ascii "Result:  \n"   @ 模板:"Result:  __\n"(预留两位放结果)
        template_len = . - output_template

.section .text
    .global _start

_start:
    @ ========== 主程序:准备参数并调用函数 ==========
    mov r0, #10                @ 第一个参数 a = 10(按ABI,r0~r3传参)
    mov r1, #3                 @ 第二个参数 b = 3
    mov r2, #4                 @ 第三个参数 c = 4

    bl calculate_expression    @ 调用子函数!bl 会自动把返回地址存入 lr (r14)
                               @ 相当于高级语言:result = calculate_expression(10, 3, 4)

    @ ========== 函数返回后,r0 中是结果(28),现在转换并输出 ==========
    mov r4, r0                 @ 把结果从 r0 保存到 r4(因为 r0 待会要用于系统调用)

    @ --- 把数字 28 转成两个字符 '2''8' ---
    mov r5, #10                @ 除数 = 10
    udiv r6, r4, r5            @ r6 = 28 / 10 = 2(十位)
    mls r7, r6, r5, r4         @ r7 = 28 - (2*10) = 8(个位)

    add r6, r6, #'0'           @ 十位数字 2 → 字符 '2'
    add r7, r7, #'0'           @ 个位数字 8 → 字符 '8'

    @ --- 存入输出模板 ---
    ldr r1, =output_template   @ r1 = 字符串地址
    strb r6, [r1, #8]          @ 把 '2' 存到第9个字节("Result:  " 第一个空格)
    strb r7, [r1, #9]          @ 把 '8' 存到第10个字节(第二个空格)

    @ --- 调用 write 输出 ---
    mov r0, #1                 @ fd = stdout
    mov r2, #template_len      @ 输出长度
    mov r7, #4                 @ sys_write
    svc #0

    @ --- 退出程序 ---
    mov r0, #0
    mov r7, #1                 @ sys_exit
    svc #0

@ =============================================
@ 子函数: calculate_expression(a, b, c) → return (a - b) * c
@ 参数: r0=a, r1=b, r2=c
@ 返回: r0 = (a - b) * c
@ =============================================
calculate_expression:
    @ ========== 函数开头:保存会用到的寄存器到栈(保护现场) ==========
    push {r4, r5, lr}          @ 把 r4, r5, lr 压入栈(lr 必须保存,因为函数内可能再调用别人)

    @ ========== 执行计算:result = (a - b) * c ==========
    sub r4, r0, r1             @ r4 = a - b (新指令!sub = subtract 减法)
    mul r5, r4, r2             @ r5 = r4 * c (新指令!mul = multiply 乘法)
    mov r0, r5                 @ 把结果放到 r0(返回值按ABI放在r0)

    @ ========== 函数结尾:恢复寄存器,返回调用者 ==========
    pop {r4, r5, lr}           @ 从栈弹出,恢复之前保存的寄存器
    bx lr                      @ 跳转到 lr 保存的地址(即返回到主程序)→ 新指令!bx = branch and exchange

@ =============================================
@ 可选:加一个“死循环”演示 bne(不等于则跳转)
@ =============================================
    @ 下面这段是额外演示,当前程序不会执行到这里
    @ 你可以注释掉上面的 bx lr,取消注释下面代码来测试

/*
loop_demo:
    mov r8, #0                 @ 计数器 = 0
check_loop:
    cmp r8, #5                 @ 比较 r8 和 5(新指令组合!cmp + bne)
    bne keep_looping           @ 如果不相等(r8 != 5),则跳转 → 新指令!bne = branch if not equal
    b exit_loop                @ 如果相等,退出循环

keep_looping:
    add r8, r8, #1             @ r8++
    b check_loop               @ 跳回去继续检查

exit_loop:
    bx lr                      @ 返回(演示用)
*/

📘 重点新指令详解(前面没出现过的)

指令全称作用示例说明
subSubtract减法sub r4, r0, r1 → r4 = r0 - r1和 add 对应
mulMultiply乘法mul r5, r4, r2 → r5 = r4 × r2注意:不能用立即数,只能寄存器×寄存器
pushPush压栈(保存寄存器)push {r4, lr}一次可保存多个寄存器,用 {} 包裹
popPop出栈(恢复寄存器)pop {r4, lr}顺序要和 push 对应!
blBranch with Link跳转并保存返回地址到 lrbl function用于调用函数,返回用 bx lr
bxBranch and eXchange跳转到 lr(或其他寄存器)bx lr通用返回指令,也支持切换 Thumb 模式
cmp + bneCompare + Branch if Not Equal比较 + 条件跳转cmp r0, #5 + bne label实现 while(x != 5) 逻辑

💡 关键概念说明

1. 函数调用规范(ARM AAPCS)

  • 参数通过 r0~r3 传递
  • 返回值通过 r0 返回
  • r4~r11 是“被调用者保存寄存器”,函数内若使用,必须 push/pop
  • lr (r14) 必须保存,因为 bl 会覆盖它

2. 栈(Stack)操作

  • push {r4, lr} → 把寄存器值存入栈(内存),栈指针 sp 自动减小
  • pop {r4, lr} → 从栈恢复值,sp 自动增大
  • 保证函数调用前后寄存器值不变 → 程序稳定!

3. 条件跳转 cmp + bne

  • cmp r8, #5 → 计算 r8 - 5,但不保存结果,只更新标志位(Z=Zero, N=Negative...)
  • bne label → 如果 Zero 标志位 没置位(即不相等),则跳转
  • 类似 if (r8 != 5) goto label;

✅ 编译 & 运行

# 保存为 calc.s
gcc -o calc calc.s
./calc

输出:

Result: 28

🧩 如果你想看到 bne 循环演示:

  1. 注释掉 bx lr
  2. 取消注释 loop_demo 那一段
  3. 在 calculate_expression 最后加一行:b loop_demo

程序会进入死循环(你可以用 Ctrl+C 终止),用于观察跳转行为。