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 thatint 0x80
was very slow on Pentium 4。 Linus Torvalds coded up a solution using the SYSENTER
/SYSEXIT
instructions(英特尔在 Pentium Pro 时代推出,但有缺陷,并没有带来任何实际好处)。所以现代 32 位 Linux 系统实际上使用 SYSENTER
,而不是 int 0x80
。
64 位 x86 Linux 内核实际上并不使用 SYSENTER
和 SYSEXIT
。他们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 系统调用混淆的主要内容,如果未能解决你的问题,请参考以下文章