综合示例(Examples)
示例 1:Hello World(64 位,纯系统调用)
.section .rodata
msg: .ascii "Hello, AT&T asm!\n"
.set len, . - msg
.section .text
.globl _start
_start:
movq $1, %rax # write
movq $1, %rdi # stdout
leaq msg(%rip), %rsi
movq $len, %rdx
syscall
movq $60, %rax # exit
xorq %rdi, %rdi
syscall
as hello.s -o hello.o && ld hello.o -o hello && ./hello
示例 2:求 1..N 之和(函数 + 调用)
.section .text
.globl _start
_start:
movq $100, %rdi
call sum_to_n
# 结果在 rax
movq %rax, %rdi # 作为 exit code
movq $60, %rax
syscall
# long sum_to_n(long n) { long s=0; for(long i=1;i<=n;i++) s+=i; return s; }
sum_to_n:
xorq %rax, %rax # s = 0
movq $1, %rcx # i = 1
1:
cmpq %rdi, %rcx # 计算 rcx - rdi
jg 2f # if i > n 结束
addq %rcx, %rax
incq %rcx
jmp 1b
2:
ret
1b 表示向后(backward)找标号 1,2f 表示向前(forward)找标号 2。
示例 3:数组最大值
# long max(long *a, long n)
# rdi = a, rsi = n
max:
movq (%rdi), %rax # result = a[0]
movq $1, %rcx # i = 1
1:
cmpq %rsi, %rcx # rcx - rsi
jge 2f # if i >= n 结束
movq (%rdi,%rcx,8), %rdx
cmpq %rax, %rdx # rdx - rax
jle 3f
movq %rdx, %rax # result = a[i]
3:
incq %rcx
jmp 1b
2:
ret
示例 4:C 内联汇编求最大值
#include <stdio.h>
int main(void) {
long a = 7, b = 12, m;
asm ("movq %1, %0\n\t" /* m = a */
"cmpq %2, %0\n\t" /* m - b */
"cmovl %2, %0" /* 若 a < b,则 m = b */
: "=r"(m)
: "r"(a), "r"(b));
printf("max=%ld\n", m);
return 0;
}
逻辑:先把 a 放入 m;比较 m - b,若 m < b(cmovl)则 m = b,最终 m = max(a,b)。
注意 m 必须先初始化——cmov 条件不成立时不会写目的寄存器。
示例 5:递归阶乘(栈帧实战)
.section .text
.globl _start
_start:
movq $10, %rdi
call fact
movq %rax, %rdi # 10! = 3628800
movq $60, %rax
syscall
# long fact(long n) { return n<=1 ? 1 : n*fact(n-1); }
fact:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movq %rdi, -8(%rbp) # 保存 n(rdi 是 caller-saved,递归会破坏)
cmpq $1, %rdi
jg 1f
movq $1, %rax # n <= 1 返回 1
jmp 2f
1: decq %rdi
call fact # rax = fact(n-1)
imulq -8(%rbp), %rax # rax = n * fact(n-1)
2: leave # = movq %rbp,%rsp; popq %rbp
ret
要点:%rdi 是 caller-saved,递归调用会覆盖它,所以先把 n 存到自己的栈帧(-8(%rbp)),调用返回后再取出来用。
示例 6:汇编函数 + C 主程序混合编译
汇编文件 add.s(导出函数给 C 调用):
.section .note.GNU-stack,"",@progbits
.text
.globl add
.type add, @function
add:
leaq (%rdi,%rsi), %rax # return a + b
ret
.size add, . - add
C 文件 main.c:
#include <stdio.h>
long add(long a, long b);
int main(void) {
printf("3+4=%ld\n", add(3, 4));
return 0;
}
编译链接(gcc 同时驱动 C 编译与汇编):
gcc main.c add.s -o prog && ./prog # 3+4=7
要点:C 侧只需声明函数原型;汇编侧保证遵守调用约定(参数寄存器、返回 %rax、不破坏 callee-saved),.type/.size 让工具链生成更好的调试/回溯信息。
示例 7:打印整数(十进制转换)
纯系统调用程序无法用 printf,输出数字需要自己把整数转成十进制字符串:
.section .rodata
ten: .quad 10
.section .bss
.lcomm outbuf, 24 # 64 位十进制最长 20 位 + 换行,留足余量
.section .text
.globl _start
_start:
movq $123456789, %rdi
call print_int
movq $60, %rax
xorq %rdi, %rdi
syscall
# void print_int(unsigned long n) —— rdi = n,打印十进制数 + 换行
print_int:
movq %rdi, %rax # 被除数 = n
leaq outbuf+23(%rip), %rcx # 从缓冲区末尾向前写
movb $10, (%rcx) # 末尾先放换行符
decq %rcx
1: xorq %rdx, %rdx # div 前必须清 rdx(被除数高位)
divq ten(%rip) # rax /= 10,余数在 rdx
addb $'0', %dl # 余数 0-9 -> ASCII '0'-'9'
movb %dl, (%rcx)
decq %rcx
testq %rax, %rax # 商为 0 则结束
jnz 1b
incq %rcx # 回退到第一个数字字符
leaq outbuf+23(%rip), %rax
subq %rcx, %rax # 输出长度(含换行)
movq %rax, %rdx
movq %rcx, %rsi
movq $1, %rax # write(1, buf, len)
movq $1, %rdi
syscall
ret
要点:从低位向高位生成数字、从缓冲区末尾向前写,写完再整体输出;div 前清 %rdx 的老规矩又见面了。
调试建议
- 用
gdb单步:gdb ./hello,layout asm,stepi,info registers。详见模块十四。 - 用
objdump -d -M intel反查生成的机器码。 - 用
strace ./hello观察系统调用。 - 编译时加
-g保留调试信息(仅 GAS 配合gcc驱动时)。
练习清单(按难度递进)
每个练习都建议在 GDB 里单步过一遍(模块十四),卡住时查模块十八:
- 改 Hello World:打印自己的名字;把
.ascii换成.asciz观察长度计算的差异。 - 求 1..N 和:N 用
.set定义,结果作为退出码(注意低 8 位限制,用示例 7 的print_int打印验证)。 - 手写
strlen:给定%rdi指向的字符串,循环找结尾 0,返回长度;与 libc 的strlen对比(模块十六)。 - 手写
memcpy/memset:分别用普通循环和rep movsb/rep stosb实现,体会模块十一。 - 数组遍历:定义
.long数组,求和、求最大值(对照示例 3),把结果打印出来。 - 冒泡排序:对
.long数组排序后逐个打印;体会双层循环与下标计算。 - 递归:阶乘(对照示例 5)或斐波那契;用 GDB
bt观察递归调用栈。 - 读输入回显:用
read从 stdin 读一块数据再write回去(模块七),注意处理返回值。 - 跳转表菜单:读入一个数字,用跳转表(模块五)分发到不同处理分支。
- 与 C 互操作:把第 3、4 题的函数做成独立
.s文件,写 C 主程序调用并比对结果(模块十六)。
完成 1–5 题即掌握基本语法与调试;完成 6–10 题后可以认为已经上手 AT&T 汇编。