“分段错误”,x86_64程序集,AT&T语法

我在64位Linux环境中运行我的代码,其中Linux内核是在禁用 IA32_EMULATIONX86_X32的情况下构build的

在“ 从头开始编程 ”一书中第一个程序除了产生一个段错误之外不会做任何事情:

.section .data .section .text .globl _start _start: movl $1, %eax movl $0, %ebx int $0x80 

我将代码转换为使用x86-64指令,但它也是段错误:

 .section .data .section .text .globl _start _start: movq $1, %rax movq $0, %rbx int $0x80 

组装了这两个这样的程序:

 as exit.s -o exit.o ld exit.o -o exit 

运行./exit会给两者Segmentation fault 。 我究竟做错了什么?

PS我已经看到了很多与gcc组装代码的教程,但是我想用gas

更新

结合评论和答案,下面是代码的最终版本:

 .section .data .section .text .globl _start _start: movq $60, %rax xor %rbx, %rbx syscall 

int $0x80是32位ABI。 在正常的内核上(使用IA32仿真编译),它在64位进程中可用,但不应该使用它,因为它只支持32位指针,而某些结构体具有不同的布局。

有关制作64位Linux系统调用的信息,请参阅x86标记wiki。 (也是ZX485对这个问题的回答)。 有很多不同之处,包括syscall指令破坏%rcx%r11 ,不像int $0x80 ABI。

在没有IA32仿真的内核中,像你的一样,运行int $0x80可能与运行任何其他无效的软件中断(如int $0x79 。 在gdb中单步执行该指令(在包括IA32仿真功能的64位4.2内核上)会导致该指令发生段错误。

它不返回并继续执行垃圾字节作为指令(这也会导致SIGSEGV或SIGILL),或者继续执行,直到跳转到(或正常达到)未映射的页面。 如果是这样的话,那将是隔离的机制。

您可以在strace下运行一个进程,例如strace /bin/true --version ,以确保它正在进行系统调用。 您也可以使用gdb来查看程序段错误的位置。 使用调试器是必不可少的 ,而不是在大多数语言中,因为asm中的失败模式通常只是一个段错误。

第一个观察是,在你的例子中的代码有效地做同样的事情,但编码不同。 x86-64.org网站为那些从x86-64开始的人提供了一些很好的信息。 使用32位寄存器的第一个代码片段由于Implicit Zero Extend而相当于第二个代码片段:

隐式零扩展

32位操作的结果隐含地被零扩展为64位值。 这与16位和8位操作不同,不影响寄存器的上半部分。 在某些情况下,这可以用于代码大小优化,例如:

 movl $1, %eax # one byte shorter movq $1, %rax xorq %rax, %rax # three byte equivalent of mov $0,%rax andl $5, %eax # equivalent for andq $5, %eax 

问题是,为什么这段代码段错误? 如果你已经在一个典型的x86-64 Linux发行版上运行这个代码,你的代码可能已经按照预期退出了,而不会产生段错误。 您的代码失败的原因是因为您正在使用关闭IA32仿真的自定义内核。

Linux内核中的IA32仿真允许您使用传统的32位系统调用机制来使用32位int 0x80中断进行调用。 这是一个仿真层,不支持不能在32位寄存器中表示的传递指针。 基于堆栈指针的情况就是这样,因为它们不在4GB地址空间之内,并且不能用32位指针访问。

您的系统关闭IA32仿真,并因为该int 0x80不存在向后兼容性。 结果是int 0x80中断将引发分段错误,并且您的应用程序将失败。

在x86-64代码中,最好使用syscall指令对64位Linux内核进行系统调用。 该机制在必要时支持64位操作数和指针。 Ryan Chapman的站点在64位SYSCALL接口上有一些很好的信息,它与32位int 0x80机制有很大不同。

您的代码可以用这种方式编写,无需IA32仿真即可在64位环境中工作:

 .section .text .globl _start _start: mov $60, %eax xor %ebx, %ebx syscall 

关于64位开发的其他有用信息可以在64位System V ABI中找到 。 本文档还更好地描述了Linux内核使用的一般syscall约定,包括A.2节中的副作用。 如果您还希望与第三方库和模块(如C库等)进行交互,这个文档也是非常丰富的。

原因是x86_64的Linux系统调用表与x86的表不同。

在x86中,SYS_EXIT是1 。 在x64中,SYS_EXIT是601是SYS_WRITE的值,如果调用,则需要%RSI中的const char *buf 。 如果该缓冲区指针无效,则可能是段错误。

 %rax System call %rdi %rsi %rdx %r10 %r8 %r9 1 sys_write unsigned int fd const char *buf size_t 60 sys_exit int error_code