汇编运行与调试 on macOS

本文介绍了如何在MacOS环境下使用SublimeText3、GDB和Nasm工具编写并调试汇编程序,涉及数据区、文本区的设置,系统调用write和exit的使用,以及GDB命令行操作,包括设置断点、查看寄存器值和内存地址的技巧。

声明:本文优快云作者原创投稿文章,未经许可禁止任何形式的转载,原文链接

0. 工具准备

  • 工具:Sublime Text 3;GDB;Nasm;
  • 环境:macOS 10.15.4
  • 处理器:Intel Core i5

上面👆三个工具下载好配置好就行,只说一点我踩的坑:gdb调试需要关闭SIP。

1. 编写程序代码

SECTION .data
 
; 在显示器上输出hello, world
; _main的前段系统调用了write,后半段系统调用了exit
; write(int fd, const void *buffer, size_t nbytes)
; exit(int status)


msg: db "HelloWorld!", 0x0a ;0x0a就是C语言的'\0'字符串结束符
len: equ $-msg ;变量len等于msg的长度
 
SECTION .text
global _main
 
_main:
    mov rax,0x2000004  ;0x2000004 表示 syscall 调用号 write
    mov rdi,1          ;第一个参数:文件描述符(fd),1代表标准输出stdout,也就是fd的值
    mov rsi,msg        ;第二个参数:要输出的字节序列(buffer),syscall 调用会到 rsi 来获取字符
    mov rdx,len        ;第三个参数:字节序列的长度
    syscall
 
    mov rax,0x2000001  ;0x2000001 表示退出 syscall
    mov rdi,0
    syscall
; 通过 syscall 指令进行系统调用时,约定的传递参数的寄存器依次为 rdi 、 rsi 、 rdx 、 rcx 。
; 根据位数不同,寄存器也有不一样的名字。16位寄存器有ax,bx,cx,dx,bp,si,sp,ip等,32位在这些前面加了前缀e即eax等等,64位用的不是e而是r即rax等等。

保存为test.asm。

2. 编译、链接、运行

在终端将工作目录移动到test.asm下,进行编译:

nasm -f macho64 -o test.o -g test.asm

编译完成以后会生成test.o链接文件,下面进行链接:

ld -o test -e _main test.o -macosx_version_min 10.15 -static

链接完成以后会生成test二进制可执行文件。

直接执行test文件,可以看到结果:

f@fdeMBP test % ./test 
HelloWorld!
f@fdeMBP test % 

3. 调试

调试需要工具GDB。

依然将工作目录移动到test.asm下,执行以下命令进入调试环节:

gdb test

顺利的话,可以看到(gdb)的标识符。

先在main函数处设置一个断点:

(gdb) b main

可以得到输出

Breakpoint 1 at 0x100000fd9

说明main函数的地址就在0x100000fd9。

接下来运行程序:

(gdb) r

程序会在我们设置的端点处停下,并得到如下输出:

Starting program: /path/to/test

[New Thread 0x2703 of process 8812]

[New Thread 0x1b03 of process 8812]

Thread 2 hit Breakpoint 1, 0x0000000100000fd9 in main ()

0x0000000100000fd9 in main ()说明运行到main函数在内存中的0x0000000100000fd9处指令停止,此指令并未运行。

此时可以查看寄存器的值:

(gdb) info registers

得到如下输出:

rax 0x0 0

rbx 0x0 0

rcx 0x0 0

rdx 0x0 0

rsi 0x0 0

rdi 0x0 0

rbp 0x0 0x0

rsp 0x7ffeefbffa10 0x7ffeefbffa10

r8 0x0 0

r9 0x0 0

r10 0x0 0

r11 0x0 0

r12 0x0 0

r13 0x0 0

r14 0x0 0

r15 0x0 0

rip 0x100000fd9 0x100000fd9

eflags 0x202 [ IF ]

cs 0x2b 43

ss

ds

es

fs 0x0 0

gs 0x0 0

fs_base

gs_base

寄存器名字和16位寄存器稍有不同,这个在最上面的程序注释中已经说明,主要是因为64位寄存器在前面加了个r。

得到的输出中,第一个是寄存器名字,第二个是寄存器的16进制值,第三个是其10进制值。

接下来运行一条指令:

(gdb) nexti

0x0000000100000fde in main ()

再查看寄存器的值:

rax 0x2000004 33554436

rbx 0x0 0

rcx 0x0 0

rdx 0x0 0

rsi 0x0 0

rdi 0x0 0

rbp 0x0 0x0

rsp 0x7ffeefbffa10 0x7ffeefbffa10

r8 0x0 0

r9 0x0 0

r10 0x0 0

r11 0x0 0

r12 0x0 0

r13 0x0 0

r14 0x0 0

r15 0x0 0

rip 0x100000fde 0x100000fde <main+5>

eflags 0x302 [ TF IF ]

cs 0x2b 43

ss

ds

es

fs 0x0 0

gs 0x0 0

fs_base

gs_base

可以看到rax已经变成了汇编代码#16行传入的0x2000004。

可以使用指令来查看某一寄存器的值,如:

(gdb) print/x $rax

$1 = 0x2000004

同样,可以使用指令查看内存的值:

(gdb) x/17xb 0x100000fd9

0x100000fd9 <main>: 0xb8 0x04 0x00 0x00 0x02 0xbf 0x01 0x00

0x100000fe1 <main+8>: 0x00 0x00 0x48 0xbe 0x00 0x10 0x00 0x00

0x100000fe9 <main+16>: 0x01

指令用法会在后面提到。

接下来就调试或者直接运行结束即可,这里直接运行结束:

(gdb) continue

Continuing.

HelloWorld!

[Inferior 1 (process 8812) exited normally]

4. GDB中命令x的用法

  • 命令 x :查看内存地址中的值

格式:x/<n/f/u> <addr>

  • n:正整数,表示需要显示的内存单元的个数,即从当前地址向后显示n个内存单元的内容,一个内存单元的大小由第三个参数u定义。

  • f:addr指向的内存内容的输出格式,s对应输出字符串,此处需特别注意输出整型数据的格式:

    • x 按十六进制格式显示变量
    • d 按十进制格式显示变量
    • u 按十六进制格式显示无符号整型
    • o 按八进制格式显示变量
    • t 按二进制格式显示变量
    • a 按十六进制格式显示变量
    • c 按字符格式显示变量
    • f 按浮点数格式显示变量
  • u:以多少个字节作为一个内存单元,默认为4。当然u还可以用被一些字符表示,如b=1 byte, h=2 bytes,w=4 bytes,g=8 bytes.

  • <addr>:内存地址。

整合这个命令的诠释:就是以addr为起始地址,返回n个单元的值,每个单元对应u个字节,输出格式是f。

如:x/17xb 0x100000fd9 表示:以地址0x100000fd9为起始地址,返回17个单元的值,每个单元有一个字节,输出格式为无符号十六进制。

也就是说返回了17个字节的数据,以十六进制输出,这17个字节的数据,每一个字节为一个单元输出,共输出17个单元。

(gdb) x/17xb 0x100000fd9

0x100000fd9 <main>: 0xb8 0x04 0x00 0x00 0x02 0xbf 0x01 0x00

0x100000fe1 <main+8>: 0x00 0x00 0x48 0xbe 0x00 0x10 0x00 0x00

0x100000fe9 <main+16>: 0x01

以字节编址,故0x100000fd9为0xb8,0x100000fda为0x04等。

5. macOS的系统调用(Syscall)

可以看到在上面的汇编代码中用了两个系统调用,比如0x2000004表示 syscall 调用号 write,这个Apple已经在这里开源了macOS所有的系统调用表,在里面可以看到write函数的系统调用:

4 AUE_NULL ALL { user_ssize_t write(int fd, user_addr_t cbuf, user_size_t nbyte); }

可是这里系统调用号是4而不是我们在代码里面所写的0x2000004,在这里Apple也给出了解释,再通俗的说一下。

在macOS系统中,所有系统调用都通过syscall指令进入系统内核,但是macOS有不同类型的系统调用,比如Mach系统调用,BSD系统调用等等。严格定义如下:

#define SYSCALL_CLASS_NONE  0   /* Invalid */
#define SYSCALL_CLASS_MACH  1   /* Mach */  
#define SYSCALL_CLASS_UNIX  2   /* Unix/BSD */
#define SYSCALL_CLASS_MDEP  3   /* Machine-dependent */
#define SYSCALL_CLASS_DIAG  4   /* Diagnostics */

上面的标记称为系统调用的类枚举标记。

macOS系统中有一个SYSCALL_CLASS_SHIFT,就是将系统调用的类枚举标记左移24位得到的值,所以进行不同的系统调用需要加上那个值。

所以,我们进行BSD类型的系统调用需要2<<24+4就是0x2000004。

### 配置 VSCode 进行汇编语言调试 为了能够在 Visual Studio Code (VSCode) 中顺利进行汇编语言程序的编写调试,需安装必要的扩展并配置 launch.json 文件来支持特定于汇编环境的需求[^1]。 #### 安装必备工具链和插件 确保已安装 NASM 或 MASM 编译器以及 GDB 调试器。对于 Windows 用户来说可能还需要 MinGW-w64 版本的 GCC 工具集以便能够运行 GNU 的调试工具。另外,在 VSCode 内通过 Extensions Viewlet (`Ctrl+Shift+X`) 来查找并安装 "Assembly" 和其他任何有助于提高开发效率的相关扩展包[^2]。 #### 创建项目结构 建立一个新的文件夹作为工作区,并在此目录下创建源码文件(例如 `main.asm`)。接着打开该文件夹让其成为当前的工作空间,这一步骤非常重要因为后续会涉及到相对路径设置等问题[^3]。 #### 设置启动配置 在 `.vscode/launch.json` 中定义调试配置项如下所示: ```json { "version": "0.2.0", "configurations": [ { "name": "(gdb) Launch asm", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/a.exe", // 输出可执行文件的位置 "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": true, "internalConsoleOptions": "neverOpen", "MIMode": "gdb", "miDebuggerPath": "/path/to/gdb", // Linux/MacOS 下GDB路径;Windows则应指向MinGW下的gdb路径 "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true } ], "preLaunchTask": "build_asm" } ] } ``` 上述 JSON 对象中的 `"preLaunchTask"` 字段指定了构建任务名称为 `build_asm`,这意味着每次启动调试前都会先尝试执行这个名为 build_asm 的任务来进行代码组装操作[^4]。 #### 构建任务设定 同样位于 .vscode 文件夹内新建 tasks.json 并加入下面的内容用于指定如何调用 nasm 命令完成汇编过程: ```json { "version": "2.0.0", "tasks": [ { "label": "build_asm", "command": "nasm", "args": [ "-fwin64", // 根据操作系统调整此参数, 如Linux可用elf64代替win64 "${file}", // 当前编辑器中打开的文件名(含后缀) "-o", "${fileDirname}/${fileBasenameNoExtension}.obj"// 生成的目标文件位置 ], "group": { "kind": "build", "isDefault": true }, "problemMatcher": [] }, { "label": "link_obj_to_exe", "dependsOn": ["build_asm"], "command": "gcc", "args": [ "-nostartfiles", "${fileDirname}/${fileBasenameNoExtension}.obj", "-o", "${workspaceFolder}/a.exe" ], "group": { "kind": "build", "isDefault": false }, "problemMatcher": [] } ] } ``` 这里包含了两个任务:一个是负责将 ASM 源文件转换为目标文件的任务(build_asm),另一个则是链接目标文件生成最终可执行文件(link_obj_to_exe)[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值