上一篇程序只会「说」,不会「听」。真实程序几乎都要处理输入——命令行参数、用户键入、网络数据,本质都是往缓冲区里塞字节。这一篇我们学 sys_read,并认识汇编里的第三个地盘:.bss 段。

这是「x86 汇编入门」系列的第 2 篇。上一篇用 sys_write 输出了 Hello World。这一篇通过 02_input.asm,实现读取键盘输入并回显。

一、三段式内存布局

到本篇为止,汇编程序的「地盘」凑齐了:

用途 类比
.data 已初始化的常量(字符串、数字) 写死在程序里的便签
.bss 未初始化的变量(缓冲区) 运行时用的空白草稿纸
.text 可执行指令 操作步骤

.bss 里的空间在程序加载时自动清零,用 resb N 预留 N 个字节:

1
2
section .bss
name_buf resb 64 ; 预留 64 字节缓冲区

二、sys_read 怎么用?

sys_readsys_write 的镜像操作:

寄存器 含义
rax 0(调用号)
rdi 文件描述符(0 = stdin)
rsi 缓冲区地址
rdx 最多读取的字节数
返回值 rax 实际读到的字节数

注意:用户按回车后,读到的内容包含换行符

三、程序流程

02_input.asm 做三件事:

  1. 打印提示 请输入你的名字:
  2. 从 stdin 读入最多 64 字节到 name_buf
  3. 打印 你好, + 用户输入

核心代码片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
; 输出提示
mov rax, 1
mov rdi, 1
mov rsi, prompt
mov rdx, prompt_len
syscall

; 读取输入
mov rax, 0 ; sys_read
mov rdi, 0 ; stdin
mov rsi, name_buf
mov rdx, 64
syscall

mov r12, rax ; 保存实际读取长度

; 输出 "你好, "
mov rax, 1
mov rdi, 1
mov rsi, greeting
mov rdx, greet_len
syscall

; 输出用户输入
mov rax, 1
mov rdi, 1
mov rsi, name_buf
mov rdx, r12 ; 用实际长度,不是固定 64
syscall

四、为什么要保存 rax?

syscall 返回后,rax 里是读到的字节数。但下一次 sys_write 又会把 rax 改成 1。所以读到长度后要立刻存到 r12 这种不会被 syscall 乱改的寄存器里。

这里顺带引出 x86_64 的一个约定:

  • syscall 会破坏 raxrcxr11
  • r12r15rbxrbp 是 callee-saved,适合当长期变量

后面循环、函数篇会反复用到这个区别。

五、运行示例

1
2
make
./build/02_input

交互:

1
2
请输入你的名字: Alex
你好, Alex

六、小结

本篇要点:

  • .bss 段用 resb 声明缓冲区
  • sys_read 从 fd 读数据,返回值是实际字节数
  • 输出用户输入时要用实际长度,不能写死缓冲区大小
  • 用 callee-saved 寄存器保存 syscall 的返回值

x86 汇编入门系列第 2 篇完。下一篇进入算术运算——寄存器里做加减乘除,并把数字转成能打印的 ASCII 字符串。