Nasm在_start中的RET指令导致分段错误

3
section .text
     global _start
_start:
     nop
main:
     mov eax, 1
     mov ebx, 2
     xor eax, eax
     ret

我使用以下命令编译:

nasm -f elf main.asm
ld -melf_i386 -o main main.o

当我运行代码时,Linux会抛出一个段错误(segmentation fault)。
(我正在使用Linux Mint Nadia 64位版本)。为什么会产生这个错误呢?

1
应该返回的代码不应该像这样(Linux):mov eax,1 mov ebx,0 int 80h我认为你在最后一行使用了ret,导致出现了分段错误。 - pearcoding
1个回答

20

因为在Linux、Windows或者Mac中,ret不是退出程序的正确方式!!!

_start不是一个函数,在栈上没有返回地址,因为没有用户空间的调用者可以返回。程序执行从这里开始(在静态可执行文件中),在进程入口点。(或者在动态链接时,它在动态链接器完成后跳转到这里,但结果相同)。

在Linux / OS X中,进程启动时,栈指针指向 _startargc 参数(有关进程启动环境的更多详细信息,请参见 i386 或 x86-64 系统 V ABI 文档);内核在启动用户空间之前将命令行参数放入用户空间堆栈内存中。(所以如果你尝试使用 ret,EIP/RIP = argc = 一个小整数,而不是一个有效地址。如果你的调试器显示地址为 0x00000001 或其他值的故障,那就是原因。)


在 Windows 中,使用 ExitProcess,在 Linux 中使用系统调用 - 使用 sys_exit,对于 x86 可以使用 int 80H,对于 64 位可以使用 syscall 并使用 60,或者如果你链接到 C 库,可以调用 exit

32 位 Linux (i386)

%define  SYS_exit  1   ; call number __NR_exit from <asm/unistd_32.h>

mov     eax, SYS_exit  ; use the NASM macro we defined earlier
xor     ebx, ebx       ; ebx = 0  exit status
int     80H            ; _exit(0)

64位 Linux (amd64)

mov     rax, 60        ; SYS_exit aka __NR_exit from asm/unistd_64.h
xor     rdi, rdi       ; edi = 0  first arg to 64-bit system calls
syscall                ; _exit(0)

(在GAS中,您实际上可以使用#include <sys/syscall.h><asm/unistd.h>来获取您为之汇编.S文件的模式所需的正确数字,但NASM无法轻松使用C预处理器。请参见nasm/yasm和C的多语言包含文件获取提示。)

32位Windows(x86)

push    0
call    ExitProcess

链接C库的Windows/Linux


; pass an int exit_status as appropriate for the calling convention
; push 0   /  xor edi,edi  /  xor ecx,ecx
call    exit

对于32位x86 Windows平台,应该使用call _exit,因为C函数名前会加一个下划线而不像在x86-64 Windows上那样没有。如果Windows平台有POSIX的_exit函数,它将是call __exit

Windows x64调用约定包括阴影空间,调用者必须预留这个空间,但exit函数不会返回,所以让它占用返回地址上面的空间是可以的。在call exit之前除了32位Windows外,调用约定要求16字节栈对齐,但对于像exit()这样的简单函数,通常不会出现问题。


call exit(与原始的退出系统调用或libc库的_exit不同)将首先刷新stdio缓冲区。如果您从_start使用printf,请使用exit确保在退出之前打印所有输出,即使stdout被重定向到文件(将stdout设置成全缓冲而不是行缓冲)。

通常建议如果您使用libc函数,编写一个main函数并链接gcc,以便普通CRT启动函数调用它,您可以通过ret返回。

另请参阅:

main定义为_start可以跳转到的内容并不使它特殊,如果不像C main函数那样由准备在main返回后退出的_start调用,则使用main标签是令人困惑的。


2
@maxiperez 这不是一个愚蠢的问题 :). 它表明程序不仅仅是“主”函数,而且以不同的方式与操作系统交互。 - Guido
在你的x64 Linux和Windows示例中,我想你返回了一个0退出代码?如果它等同于x86版本,那么应该是mov rdi, sys_exit? 同样,在Windows版本中,使用push sys_exit - odalet
1
@odalet:是的,前三个示例都返回了0。但是sys_exit是调用号,而不是退出状态。(这是一个非标准的宏名称,sys_exit是实现它的Linux内核函数的名称;SYS_exit__NR_exit是具有实际数字的CPP宏。)我再次编辑了这个答案,并添加了更多/更好的注释,包括在call exit之前设置退出状态的注释。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接