x86_64 汇编 Linux 系统调用混淆

Posted

技术标签:

【中文标题】x86_64 汇编 Linux 系统调用混淆【英文标题】:x86_64 Assembly Linux System Call Confusion 【发布时间】:2012-01-20 13:45:06 【问题描述】:

我目前正在 Linux 上学习汇编语言。我一直在使用“从头开始编程”一书,所有示例都是 32 位的。我的操作系统是 64 位的,我一直在尝试在 64 位中完成所有示例。但是我遇到了麻烦:

.section .data

.section .text
.global _start
_start:
movq $60, %rax
movq $2, %rbx
int $0x80

这只是调用 Linux 退出系统调用,或者它应该调用。相反,它会导致 SEG FAULT,而当我这样做时

.section .data

.section .text
.global _start
_start:
movq $1, %rax
movq $2, %rbx
int $0x80

它有效。显然问题是我移动到 %rax 的值。我在第二个示例中使用的值 $1 是“从头开始编程”所说的使用,但是 Internet 上的多个消息来源说 64 位系统调用号是 60 美元。 Reference 我究竟做错了什么?我还应该注意哪些其他问题以及我应该使用什么作为参考?以防万一你需要知道,我在从头开始编程的第 5 章。

【问题讨论】:

基本上是重复的:What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code? - int $0x80 仍然调用 32 位 ABI,使用 32 位寄存器和索书号。真的只是使用 Assembling 32-bit binaries on a 64-bit system (GNU toolchain) 来学习 32 位教程。 【参考方案1】:

您在 i386 和 x86_64 之间遇到了一个令人惊讶的区别:它们不使用相同的系统调用机制。正确的代码是:

movq $60, %rax
movq $2,  %rdi   ; not %rbx!
syscall

中断0x80 总是调用 32 位系统调用。它用于允许 32 位应用程序在 64 位系统上运行。

出于学习的目的,您可能应该尝试完全按照本教程进行操作,而不是即时翻译为 64 位 - 您可能会遇到其他一些重要的行为差异。一旦你熟悉了 i386,那么你就可以单独拿起 x86_64 了。

【讨论】:

我可能最终会这样做。感谢您的回复。 第一个系统调用参数应该使用%rdi,而不是%rbx 感谢您的关注 - 已修复。 sysenter 在我的系统上不起作用。正确的指令是syscall @hpsMouse,syscall 是源自 AMD 的版本,而 sysenter 是源自 Intel 的版本。更多细节在这里:wiki.osdev.org/SYSENTER【参考方案2】:

请阅读What are the calling conventions for UNIX & Linux system calls on x86-64

请注意,在 x64 系统上使用int 0x80 进行系统调用是一个旧的兼容层。您应该在 x64 系统上使用 syscall 指令。

您仍然可以使用这种旧方法,但您需要在 x86 模式下编译您的二进制文件,有关详细信息,请参阅您的编译器/汇编器手册。

【讨论】:

很高兴看到有人指出 x86_64 Linux 实际上使用的是syscall 而不是sysenter!我写了一个longer answer to explain the confusion between the two。【参考方案3】:

duskwuff 的answer 正确指出 64 位 x86 Linux 与 32 位 Linux 的系统调用机制不同。

但是,由于以下几个原因,此答案不完整且具有误导性:

实际上是在64 位系统流行之前引入了更改,其动机是observation that int 0x80 was very slow on Pentium 4。 Linus Torvalds coded up a solution using the SYSENTER/SYSEXIT instructions(英特尔在 Pentium Pro 时代推出,但有缺陷,并没有带来任何实际好处)。所以现代 32 位 Linux 系统实际上使用 SYSENTER,而不是 int 0x8064 位 x86 Linux 内核实际上并不使用 SYSENTERSYSEXIT。他们actually use 非常相似SYSCALL/SYSRET 指令。

作为pointed out in the comments,SYSENTER 实际上并不适用于许多 64 位 Linux 系统——即 64 位 AMD 系统.

这是一个令人困惑的情况。 gory details are here,但归根结底是这样的:

对于 32 位内核,SYSENTER/SYSEXIT 是唯一兼容的对[在 AMD 和 Intel CPU 之间]

仅对于长模式下的 64 位内核……SYSCALL/SYSRET 是唯一兼容的对[在 AMD 和 Intel CPU 之间]

似乎在 64 位模式的 Intel CPU 上,您可以使用 SYSENTER,因为它与 SYSCALL 做同样的事情,但情况并非如此AMD 系统。

底线:在 64 位 x86 系统上的 Linux 上始终使用 SYSCALL。这是 x86-64 ABI 实际指定的内容。 (有关更多详细信息,请参阅这个伟大的 wiki answer。)

【讨论】:

我的理解是现代 AMD CPU 从 32 位用户空间(到 64 位内核)支持sysenter。但无论如何,你不应该直接使用sysenter;它不受官方支持。对简单的初学者代码使用int $0x80,或者在VDSO 中使用call 让内核提供的代码运行sysenter 或任何对您的CPU 最有效的东西。 blog.packagecloud.io/eng/2016/04/05/…【参考方案4】:

i386 和 x86_64 之间发生了很多变化,包括用于进入内核的指令和用于携带系统调用参数的寄存器。这是与您的代码等效的代码:

.section .data

.section .text
.global _start
_start:
movq $60, %rax
movq $2, %rdi
syscall

引用this answer 的相关问题:

系统调用号位于 Linux 源代码中 arch/x86/include/asm/unistd_64.h 下。系统调用号在 rax 寄存器中传递。参数在rdi, rsi, rdx, r10, r8, r9。使用“syscall”指令调用该调用。系统调用会覆盖 rcx 寄存器。回报是 rax。

【讨论】:

我也在做 shellcoder 手册 2 中的内容。但是,我的操作系统也是 64 位的。我使用 nasm-option -f elf32 进行组装,并且还与 -melf_i386 链接。我使用gdb和evans调试器(但是,错误的是它被编译为64,它在加载二进制文件后提示,但在edb内部,在gdb内部和执行中,错误是一样的):segfault after an instruction AFTER int 0x80.And我只想退出。我已经看到,(r)ax寄存器必须包含0x3c而不是0x01(因为使用了64位的系统调用文件),但我无法想象,为什么调用int 0x80之后的指令。任何人都知道,为什么?【参考方案5】:

如果您检查/usr/include/asm/unistd_32.h 退出对应于1 但在 /usr/include/asm/unistd_64.h出口对应60

【讨论】:

是的,int 0x80 始终使用 unistd_32 索书号(和 32 位寄存器)。只有 syscall 在 64 位代码中使用 unistd_64.h 调用号和 RDI、RSI、... arg 寄存器。

以上是关于x86_64 汇编 Linux 系统调用混淆的主要内容,如果未能解决你的问题,请参考以下文章

Linux系统调用表(x86_64)

x86_64 Linux 上定义的 ioctl 系统调用的用户空间包装器在哪里?

LINUX系统调用

使用 GNU 汇编器在 x86_64 中调用 printf

深入理解系统调用

深入理解系统调用