x86 Assembly on MacOS
在 MacOS x86 上编写一个简单的 Hello World 汇编程序。
CPU 指令集架构
通过 uname -a
来查看 CPU 指令集架构,常见的 有:
$ uname -a
Darwin xxx-mac13 22.1.0 Darwin Kernel Version 22.1.0: Sun Oct 9 20:14:54 PDT 2022; root:xnu-8792.41.9~2/RELEASE_X86_64 x86_64
编写最简单的 x86 汇编
参考 Guide to x86。
新建 main.s
文件,并编写以下内容
.globl _main
_main:
mov $0x1, %rax
mov $0x2, %rbx
add %rbx, %rax
ret
带注释版本
.globl _main
# 主入口
_main:
# 设置寄存器 a 的值为 1
mov $0x1, %rax
# 设置寄存器 b 的值为 2
mov $0x2, %rbx
# 将寄存器 a 与 b 的值相加
# 并将最终值赋值给 a
add %rbx, %rax
# 默认返回寄存器 a 的值
ret
将以上汇编代码编译之后,可以得到以下内容:
// 编译代码
$ gcc main.s
// 执行代码
$ ./a.out
// 查看程序输出,应该会输出 3
$ echo $?
3
调试程序
LLDB 调试技巧
参考 lldb, lldb cheat sheet
# 编译代码
$ gcc main.c
# 调试程序
$ lldb a.out
# 设置断点 setbreakpoint
(lldb) b main
# 运行
(lldb) r
# 下一步 next
(lldb) n
# 或使用 step-over 命令
# (lldb) thread step-over
# 读取内存信息
(lldb) x $rbp
# 读取寄存器信息
(lldb) re r
# 或制定读取
# (lldb) register read $rbp
# (lldb) re r -a
# 退出 lldb
(lldb) exit
调试程序
通过以下命令开始调试程序:
# 开始调试程序
$ lldb a.out
(lldb) target create "a.out"
Current executable set to '/xxx/a.out' (x86_64).
# 设置 main 为断点
(lldb) b main
Breakpoint 1: where = a.out`main, address = 0x0000000100003fa6
# 运行
(lldb) r
Process 6600 launched: '/xxx/a.out' (x86_64)
Process 6600 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000100003fa6 a.out`main
a.out`main:
-> 0x100003fa6 <+0>: movq $0x1, %rax
0x100003fad <+7>: movq $0x2, %rbx
0x100003fb4 <+14>: addq %rbx, %rax
0x100003fb7 <+17>: retq
Target 0: (a.out) stopped.
可以看到最终的执行代码中,以上汇编代码被编译成了
0x100003fa6 <+0>: movq $0x1, %rax
0x100003fad <+7>: movq $0x2, %rbx
0x100003fb4 <+14>: addq %rbx, %rax
0x100003fb7 <+17>: retq
参考 movq
MOVD/MOVQ — Move Doubleword/Move Quadword
其实 mov
与 movq
的区别就在于 32 位与 64 位,在此程序中可看成同等。
继续执行程序。
# 下一步
(lldb) n
Process 7248 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step over
frame #0: 0x0000000100003fb4 a.out`main + 14
a.out`main:
-> 0x100003fb4 <+14>: addq %rbx, %rax
0x100003fb7 <+17>: retq
0x100003fb8: addl %eax, (%rax)
0x100003fba: addb %al, (%rax)
Target 0: (a.out) stopped.
# 执行完赋值之后,查看一下寄存器的状态
# 可以看到 rax 为 1,rbx 为 2,符合预期
(lldb) re r
General Purpose Registers:
rax = 0x0000000000000001
rbx = 0x0000000000000002
# 下一步,执行加法逻辑
(lldb) n
Process 7248 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step over
frame #0: 0x0000000100003fb7 a.out`main + 17
a.out`main:
-> 0x100003fb7 <+17>: retq
0x100003fb8: addl %eax, (%rax)
0x100003fba: addb %al, (%rax)
0x100003fbc: sbbb $0x0, %al
Target 0: (a.out) stopped.
# 执行完加法逻辑后,rax 为 3
(lldb) re r
General Purpose Registers:
rax = 0x0000000000000003
rbx = 0x0000000000000002
# 执行完成所有程序
# 可以看到最终的返回状态为 3,符合预期
(lldb) thread continue
Resuming thread 0x4c614d in process 7248
Process 7248 resuming
Process 7248 exited with status = 3 (0x00000003)
操作指令
通过查看 a.out
的二进制代码:
$ xxd -s 0x3fa6 -c 7 a.out
00003fa6: 48c7 c001 0000 00 H......
00003fad: 48c7 c302 0000 00 H......
00003fb4: 4801 d8c3 0100 00 H......
00003fbb: 001c 0000 0000 00 .......
00003fc2: 0000 1c00 0000 00 .......
...
可以看到,最终的二进制码如下:
|----- 运行时代码 ------------------| |-- 二进制源码 --| |------ 前 32 位的二进制指令 --------|
0x100003fa6 <+0>: movq $0x1, %rax 48c7 c001 0000 00 01001000 11000111 11000000 00000001
0x100003fad <+7>: movq $0x2, %rbx 48c7 c302 0000 00 01001000 11000111 11000011 00000010
0x100003fb4 <+14>: addq %rbx, %rax 4801 d8 01001000 00000001 11011000
0x100003fb7 <+17>: retq c3 11000011
0x100003fb8: ... ...
对于 0x48c7 c00
, 参考 x86-64-assembly-opcode
0x48
为 long mode 也就是 64 位 rax 寄存器。0xc7
为 mov 指令0xc0
为 rax 寄存器- 同时可以看到
0xc1
,0xc2
,0xc3
分别是rcx
,rdx
,rbx
寄存器。
- 同时可以看到