在x64Linux上,syscall、int0x80和ret之间有什么区别来退出程序?
经过多年的 C++ 和 Python,我昨天决定学习汇编(NASM 语法),我已经对退出程序的方式感到困惑。它主要是关于 ret 因为它是 SASM IDE 上的建议指令。
我显然是在为主要说话。我不关心 x86 向后兼容性。只有 x64 Linux 的最佳方式。我很好奇。
回答
如果您使用printf或 其他 libc 函数,最好ret从 main 或call exit. (它们是等效的;main 的调用者将调用 libcexit函数。)
如果没有,如果您只进行其他原始系统调用,例如writewith syscall,那么以这种方式退出也是适当且一致的,但无论哪种方式,或者call exit在 main 中 100% 都可以。
如果你想工作而不在的libc所有,如把你的代码下_start:,而不是main:他连接ld或gcc -static -nostdlib,那么你就不能使用ret。使用mov eax, 231(__NR_exit_group) / syscall。
main是一个真正的和正常的函数,就像任何其他函数(用有效的返回地址调用),但_start(进程入口点)不是。在进入 时_start,堆栈持有argc和argv,因此尝试ret将设置 RIP=argc,然后代码提取将在该未映射地址上出现段错误。 _start 中 RET 上的 Nasm 分段错误
系统调用 vs. ret-from-main
通过系统调用退出就像_exit()在 C 中调用- skip atexit()和 libc 清理,特别是不刷新任何缓冲的 stdout 输出(在终端上缓冲的行,否则为全缓冲)。这会导致诸如在汇编中使用 printf 导致在管道时输出为空但在终端上工作(或者如果您的输出不以 结尾n,即使在终端上)等症状。
main是一个函数,从 CRT 启动代码(间接)调用。(假设您正常链接您的程序,就像您链接 C 程序一样。)您的手写 main 的工作方式与编译器生成的 Cmain函数完全一样。它的 caller( __libc_start_main) 确实做了一些类似的事情int result = main(argc, argv); exit(result);,
例如call rax(传递的指针_start)mov edi, eax// call exit。
所以从 main 返回就像调用1一样exit。
-
系统调用实现退出()中的有关C函数的比较,
exit对_exit与exit_group和底层ASM系统调用。 -
C题:exit和return有什么区别?主要是关于
exit()vs.return,虽然提到了_exit()直接调用,即只是进行系统调用。它是适用的,因为 C main 编译为 asm main 就像您手动编写一样。
脚注 1:您可以发明一个假设的故意奇怪的案例,其中它是不同的。例如,您使用/ / ... /将堆栈空间main用作 stdio 缓冲区。然后从 main 返回将涉及将 RSP 置于该缓冲区之上,而 __libc_start_main 对 exit 的调用可能会在执行到达 fflush 清理之前用返回地址和局部变量覆盖该缓冲区中的一些。这个错误在 asm 中比在 C 中更明显,因为您需要或或或某些东西将 RSP 指向您的返回地址。sub rsp, 1024mov rsi, rspcall setvbufleavemov rsp, rbpadd rsp, 1024
在 C++ 中,从主运行析构函数返回其局部变量(在全局/静态退出之前),exit不会。但这只是意味着编译器在实际运行ret.
另一个区别当然是 asm / 调用约定的详细信息:EAX(返回值)或 EDI(第一个 arg)中的退出状态,当然ret您必须让 RSP 指向您的返回地址,就像它在函数入口上一样. 有了call exit你不这样做,你甚至可以做到像出口的条件尾调用jne exit。由于它是一个 noreturn 函数,因此您实际上不需要指向有效返回地址的 RSP。(不过,RSP应该在 call 之前对齐 16,或者在尾调用之前RSP%16 = 8,在 call 推送返回地址之后匹配对齐。退出 / fflush 清理不太可能执行任何需要对齐的存储/加载到堆栈,但正确使用它是一个好习惯。)
(整个脚注都是关于retvs. call exit,而不是syscall,所以它与答案的其余部分有点相切。你也可以在syscall不关心堆栈指针指向的位置的情况下运行。)
SYS_exit对比SYS_exit_group原始系统调用
原始SYS_exit系统调用用于退出当前线程,例如pthread_exit().
(eax=60 / syscall,或 eax=1 / int 0x80)。
SYS_exit_group用于退出整个程序,例如_exit.
(eax=231 / syscall,或 eax=252 / int 0x80)。
在单线程程序中,您可以使用任何一个,但如果您要使用原始系统调用,那么从概念上讲,exit_group 对我来说更有意义。glibc 的_exit()包装函数实际上使用了exit_group系统调用(从 glibc 2.3 开始)。有关更多详细信息,请参阅exit() 的系统调用实现。
然而,几乎所有你见过的手写 asm 都使用SYS_exit1。这不是“错误”,SYS_exit对于没有启动更多线程的程序来说是完全可以接受的。特别是如果您尝试使用xor eax,eax/ inc eax(32 位模式下的3 个字节)或push 60/ pop rax(64 位模式下的 3 个字节)来节省代码大小,而push 231/pop rax甚至会更大,mov eax,231因为它不适合带符号的 imm8 .
注 1:(通常实际上是对数字进行硬编码,而不是使用__NR_... 常量 fromasm/unistd.h或它们的SYS_... 名称 from sys/syscall.h)
从历史上看,这就是全部。请注意,在 unistd_32.h 中,__NR_exit调用号为 1,但__NR_exit_group直到多年后内核获得对与其父共享虚拟地址空间的任务(即由clone(2). 这是SYS_exit概念上变成“退出当前线程”的时候。(但一个可以很容易地和令人信服地认为,在一个单线程程序,SYS_exit 确实还是平均排出的整个程序,因为它只是不同于exit_group如果有多个线程。)
老实说,我从来没有在任何东西中使用过 eax=252 / int 0x80,只使用过 eax=1。它只是在我经常使用的 64 位代码中,mov eax,231而不是mov eax,60因为这两个数字都不像1 那样“简单”或令人难忘,所以不妨成为一个很酷的人,并exit_group在我的单线程玩具程序中使用“现代”方式/实验/微基准测试/SO 答案。:P(如果我不喜欢在风车上倾斜,我就不会花太多时间在组装上,尤其是在 SO 上。)
顺便说一句,我通常使用 NASM 进行一次性实验,因此使用预定义的符号常量作为电话号码很不方便;使用 GCC.S在运行 GAS 之前对 a 进行预处理,您可以使您的代码自我记录,#include <sys/syscall.h>以便您可以使用mov $SYS_exit_group, %eax(或$__NR_exit_group),或mov eax, __NR_exit_group使用.intel_syntax noprefix.
不要int 0x80在 64 位代码中使用 32 位ABI:
如果在 64 位代码中使用 32 位 int 0x80 Linux ABI,会发生什么?解释了如果int 0x80在 64 位代码中使用 COMPAT_IA32_EMULATION ABI会发生什么。
退出完全没问题,只要您的内核编译了该支持,否则它会像任何其他随机整数一样出现段错误int 0x7f。(例如在 WSL1 上,或者构建自定义内核并禁用该支持的人。)
但是您在 asm 中这样做的唯一原因是您可以使用nasm -felf32或构建相同的源文件nasm -felf64。(您不能syscall在 32 位代码中使用,除非在某些具有 32 位版本的 AMD CPU 上syscall。而且 32 位 ABI 无论如何使用不同的调用号,因此这不会让相同的源对两者都有用模式。)
有关的:
- 为什么我可以使用 ret 退出 main?(CRT 启动代码调用 main,您不会直接返回内核。)
- _start 中 RET 上的 Nasm 分段错误- 你不能
ret从_start - 在装配中使用 printf 在管道时会导致空输出,但在终端标准输出缓冲区上工作(不是)用原始系统调用退出刷新
- exit()
call exitvs.mov eax,60/syscall(_exit) vs.mov eax,231/syscall(exit_group)的系统调用实现。 - 无法从汇编 (yasm) 代码中调用 64 位 Linux 上的 C 标准库函数- 现代 Linux 发行版以一种
call exit或call puts不会与nasm -felf64 foo.asm&&链接的方式配置 GCCgcc foo.o。 - main() 真的是 C++ 程序的开始吗?- Ciro 的回答深入探讨了 glibc + 其 CRT 启动代码如何实际调用 main(包括 GDB 中的 x86-64 asm 反汇编),并展示了 __libc_start_main 的 glibc 源代码。
- Linux x86 程序启动或 - 我们如何进入 main()?32 位 asm,以及比您可能想要的更多细节,直到您对 asm 更加熟悉为止这是使用 GDB 的几个步骤
starti(停在进程入口点,例如在动态链接器的_start),stepi直到您获得自己的_start或main. - https://stackoverflow.com/tags/x86/info很多关于这个和其他一切的好链接。