如何调试 NASM 在点击 _start 之前产生段错误的程序集
How to debug NASM Assembly that produces segfault before hitting _start
我正在编写以下汇编 (NASM) 代码,将鼠标指针设置为位置 100,100:
section .text
global _start:
_start:
mov eax, 4
mov ecx, 100
mov edx, 100
int 33h
mov eax, 1
int 0x80
我用下面的 make 文件编译它:
DEPS = Mouse.asm
Mouse: $(DEPS)
nasm -f elf $(DEPS)
ld -m elf_i386 -s -o Mouse Mouse.o
然后我使用 $sudo gdb 鼠标在 GDB 中打开它。然后我输入"break _start"然后按"r"到运行。当我这样做时,我得到以下输出:
"启动程序:/home/tyler/ASM/Mouse/Mouse
程序接收到信号 SIGSEGV,分段错误。
0x0804806f 在 ?? ()
“
在我看来,它甚至从未在 _start 处遇到断点,所以我不知道我可能做错了什么。我该如何调试呢?
更新:现代 GDB 有一个 starti
命令使这个 hack 过时了。 Stopping at the first machine code instruction in GDB
TL:DR: b _start
找不到设置断点的符号的原因是您在链接时删除了符号。 (所以这就是为什么它在加载动态库时处于“等待”状态,但在这种情况下没有任何动态库。)
为了回答您提出的问题(而不是您实际遇到的问题),请使用@Ped7g 的建议“滥用”GDB 的错误处理,使其在不知道正确地址的情况下在第一条指令之前停止:
b *0
在地址零处设置断点(这是不可能的)。
r
运行s 程序,此时 GDB 尝试实际设置断点。它失败了,在执行任何指令之前停止。
d 1
删除无效断点
si
(或stepi
)将从那里单步执行。
这是调试没有符号的程序的有用技巧,即使它是使用 ASLR (gcc -pie
) 动态链接的,所以您无法使用 [=20 获取真正的 ELF 入口点地址=].
你做了什么:
Then I input "break _start" and press "r" to run.
当我使用与您使用的相同命令构建后执行此操作时,我得到
Reading symbols from ./Mouse...(no debugging symbols found)...done.
(gdb) b _start
No symbol table is loaded. Use the "file" command.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (_start) pending.
我必须输入 y
或 n
才能输入 r
到 运行 程序。
所以我实际上并没有在 asm 源代码中标记为 _start:
的指令处设置断点。我只有一个 pending 断点,如果我给 GDB 一个符号 table,它将被设置在某处。 (可以将调试符号剥离到一个单独的文件中,因此这种 gdb 行为是有道理的。但不幸的是,在这种情况下它让你感到困惑。:/)
您的二进制文件已被删除,因为您是使用 ld -s
构建的。这与您想要调试的相反。使用 nasm -g -Fdwarf
以使用现代 dwarf
调试信息格式而不是 STAS 以获得更好的符号信息。
(gdb) r
Starting program: /home/peter/src/SO/Mouse
Program received signal SIGSEGV, Segmentation fault.
0x0804806f in ?? ()
0x0804806f
处的指令是您的 int 33h
。
它在 _start
之前没有出现段错误,它只是没有停止,因为您从未 实际上 设置断点。在静态链接的二进制文件中,user-space 中 运行 的第一条指令是您的 ELF 入口点处的指令。 (在动态链接的二进制文件中,ELF 解释器 运行 首先跳转到您的 _start
,或者您称之为入口点的任何内容。)
使用layout reg
显示寄存器和反汇编。使用 set disassembly-flavor intel
获得 GNU 的 MASM-like 语法,当您知道它应该是什么时,它足够接近 NASM 来阅读。将这些放入您的 ~/.gdbinit
。有关 gdb
和 strace
.
的更多调试提示,另请参阅 x86 标签 wiki 的底部
正如评论者所指出的,您的程序无法在 Linux 下原生地 运行。 Linux 本身不支持 BIOS int 33h
ABI,仅支持其自身的系统调用 ABI。 What are the calling conventions for UNIX & Linux system calls (and user-space functions) on i386 and x86-64。这就是 int 33h
段错误的原因。
如果你想编写 MS-DOS 或 PC-BIOS 代码,请使用像 BOCHS 这样的模拟器(它有一个内置的调试器,可以让你单步执行任何东西,甚至是引导加载程序)。
我正在编写以下汇编 (NASM) 代码,将鼠标指针设置为位置 100,100:
section .text
global _start:
_start:
mov eax, 4
mov ecx, 100
mov edx, 100
int 33h
mov eax, 1
int 0x80
我用下面的 make 文件编译它:
DEPS = Mouse.asm
Mouse: $(DEPS)
nasm -f elf $(DEPS)
ld -m elf_i386 -s -o Mouse Mouse.o
然后我使用 $sudo gdb 鼠标在 GDB 中打开它。然后我输入"break _start"然后按"r"到运行。当我这样做时,我得到以下输出:
"启动程序:/home/tyler/ASM/Mouse/Mouse
程序接收到信号 SIGSEGV,分段错误。 0x0804806f 在 ?? () “
在我看来,它甚至从未在 _start 处遇到断点,所以我不知道我可能做错了什么。我该如何调试呢?
更新:现代 GDB 有一个 starti
命令使这个 hack 过时了。 Stopping at the first machine code instruction in GDB
TL:DR: b _start
找不到设置断点的符号的原因是您在链接时删除了符号。 (所以这就是为什么它在加载动态库时处于“等待”状态,但在这种情况下没有任何动态库。)
为了回答您提出的问题(而不是您实际遇到的问题),请使用@Ped7g 的建议“滥用”GDB 的错误处理,使其在不知道正确地址的情况下在第一条指令之前停止:
b *0
在地址零处设置断点(这是不可能的)。r
运行s 程序,此时 GDB 尝试实际设置断点。它失败了,在执行任何指令之前停止。d 1
删除无效断点si
(或stepi
)将从那里单步执行。
这是调试没有符号的程序的有用技巧,即使它是使用 ASLR (gcc -pie
) 动态链接的,所以您无法使用 [=20 获取真正的 ELF 入口点地址=].
你做了什么:
Then I input "break _start" and press "r" to run.
当我使用与您使用的相同命令构建后执行此操作时,我得到
Reading symbols from ./Mouse...(no debugging symbols found)...done.
(gdb) b _start
No symbol table is loaded. Use the "file" command.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (_start) pending.
我必须输入 y
或 n
才能输入 r
到 运行 程序。
所以我实际上并没有在 asm 源代码中标记为 _start:
的指令处设置断点。我只有一个 pending 断点,如果我给 GDB 一个符号 table,它将被设置在某处。 (可以将调试符号剥离到一个单独的文件中,因此这种 gdb 行为是有道理的。但不幸的是,在这种情况下它让你感到困惑。:/)
您的二进制文件已被删除,因为您是使用 ld -s
构建的。这与您想要调试的相反。使用 nasm -g -Fdwarf
以使用现代 dwarf
调试信息格式而不是 STAS 以获得更好的符号信息。
(gdb) r
Starting program: /home/peter/src/SO/Mouse
Program received signal SIGSEGV, Segmentation fault.
0x0804806f in ?? ()
0x0804806f
处的指令是您的 int 33h
。
它在 _start
之前没有出现段错误,它只是没有停止,因为您从未 实际上 设置断点。在静态链接的二进制文件中,user-space 中 运行 的第一条指令是您的 ELF 入口点处的指令。 (在动态链接的二进制文件中,ELF 解释器 运行 首先跳转到您的 _start
,或者您称之为入口点的任何内容。)
使用layout reg
显示寄存器和反汇编。使用 set disassembly-flavor intel
获得 GNU 的 MASM-like 语法,当您知道它应该是什么时,它足够接近 NASM 来阅读。将这些放入您的 ~/.gdbinit
。有关 gdb
和 strace
.
正如评论者所指出的,您的程序无法在 Linux 下原生地 运行。 Linux 本身不支持 BIOS int 33h
ABI,仅支持其自身的系统调用 ABI。 What are the calling conventions for UNIX & Linux system calls (and user-space functions) on i386 and x86-64。这就是 int 33h
段错误的原因。
如果你想编写 MS-DOS 或 PC-BIOS 代码,请使用像 BOCHS 这样的模拟器(它有一个内置的调试器,可以让你单步执行任何东西,甚至是引导加载程序)。