在x64 Linux上,syscall,int 0x80和ret退出程序之间有什么区别? [英] On x64 Linux, what is the difference between syscall, int 0x80 and ret to exit a program?

查看:149
本文介绍了在x64 Linux上,syscall,int 0x80和ret退出程序之间有什么区别?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

昨天,我决定在学习了多年的C ++和Python之后学习汇编语言(NASM语法),并且我对退出程序的方式感到困惑.它主要是关于ret的,因为这是SASM IDE上的建议说明.

I decided yesterday to learn assembly (NASM syntax) after years of C++ and Python and I'm already confused about the way to exit a program. It's mostly about ret because it's the suggested instruction on SASM IDE.

显然,我是在说Main.我不在乎x86向后兼容.只有x64 Linux最好的方法.我很好奇.

I'm speaking for main obviously. I don't care about x86 backward compatibility. Only the x64 Linux best way. I'm curious.

推荐答案

如果使用 printf 或其他libc函数,最好从main或 ret call exit .(这是等效的; main的调用者将调用libc exit 函数.)

If you use printf or other libc functions, it's best to ret from main or call exit. (Which are equivalent; main's caller will call the libc exit function.)

如果没有,如果您只用 syscall 进行其他原始系统调用,例如 write ,则以这种方式退出也是合适且一致的,但是无论哪种方式,或者呼叫退出在主站点上的罚款为100%.

If not, if you were only making other raw system calls like write with syscall, it's also appropriate and consistent to exit that way, but either way, or call exit are 100% fine in main.

如果您想完全不使用libc进行工作,例如将您的代码放在 _start:下而不是 main:下,并与 ld gcc -static -nostdlib 链接,然后您不能使用 ret .使用 mov eax,231 (__NR_exit_group)/ syscall .

If you want to work without libc at all, e.g. put your code under _start: instead of main: and link with ld or gcc -static -nostdlib, then you can't use ret. Use mov eax, 231 (__NR_exit_group) / syscall.

main 是真实的&和其他函数一样正常(使用有效的返回地址调用),但 _start (过程入口点)却不是.在进入 _start 时,堆栈会保存 argc argv ,因此尝试 ret 将设置RIP = argc,然后代码提取将对该未映射地址进行段错误处理.在_start中RET上的nasm分段错误

main is a real & normal function like any other (called with a valid return address), but _start (the process entry point) isn't. On entry to _start, the stack holds argc and argv, so trying to ret would set RIP=argc, and then code-fetch would segfault on that unmapped address. Nasm segmentation fault on RET in _start

通过系统调用退出就像在C中调用 _exit()-跳过 atexit()和libc清理,尤其不是>刷新所有缓冲的stdout输出(行缓冲在终端上,否则全缓冲).这会导致诸如

Exiting via a system call is like calling _exit() in C - skip atexit() and libc cleanup, notably not flushing any buffered stdout output (line buffered on a terminal, full-buffered otherwise). This leads to symptoms such as Using printf in assembly leads to empty output when piping, but works on the terminal (or if your output doesn't end with \n, even on a terminal.)

main 是一个函数,从CRT启动代码中间接调用.(假定您像C程序一样正常链接程序.)手写的main的工作原理与编译器生成的C main 函数的工作完全相同.它的调用方( __ libc_start_main )确实做了类似 int result = main(argc,argv);的操作.exit(result);
例如 call rax (由 _start 传递的指针)/ mov edi,eax / call exit .
因此,从main返回正好是 1 ,就像调用 exit 一样.

main is a function, called (indirectly) from CRT startup code. (Assuming you link your program normally, like you would a C program.) Your hand-written main works exactly like a compiler-generate C main function would. Its caller (__libc_start_main) really does do something like int result = main(argc, argv); exit(result);,
e.g. call rax (pointer passed by _start) / mov edi, eax / call exit.
So returning from main is exactly1 like calling exit.

  • Syscall implementation of exit() for a comparison of the relevant C functions, exit vs. _exit vs. exit_group and the underlying asm system calls.

C问题:退出和退出之间有什么区别返回?主要是关于 exit() return 的关系,尽管有人提到直接调用 _exit(),即进行系统调用.之所以适用,是因为C main就像您手动编写的一样可以编译为asm main.

C question: What is the difference between exit and return? is primarily about exit() vs. return, although there is mention of calling _exit() directly, i.e. just making a system call. It's applicable because C main compiles to an asm main just like you'd write by hand.

脚注1:您可以发明一个与众不同的假想情况.例如您将 main 中的堆栈空间用作带有 sub rsp,1024 / mov rsi,rsp /.../ <代码>调用setvbuf .然后从main返回会涉及将RSP放在该缓冲区上方,而__libc_start_main的exit调用可能会在执行执行fflush cleanup之前用返回地址和本地变量覆盖该缓冲区中的某些缓冲区.这个错误在asm中比C更明显,因为您需要 leave mov rsp,rbp add rsp,1024 或其他将RSP指向的东西您的退货地址.

Footnote 1: You can invent a hypothetical intentionally weird case where it's different. e.g. you used stack space in main as your stdio buffer with sub rsp, 1024 / mov rsi, rsp / ... / call setvbuf. Then returning from main would involve putting RSP above that buffer, and __libc_start_main's call to exit could overwrite some of that buffer with return addresses and locals before execution reached the fflush cleanup. This mistake is more obvious in asm than C because you need leave or mov rsp, rbp or add rsp, 1024 or something to point RSP at your return address.

在C ++中,从主运行析构函数返回其本地变量(在全局/静态退出之前),而 exit 则不然.但这只是意味着编译器会使asm在实际运行 ret 之前执行更多工作,因此,这在asm中都是手动的,就像在C中一样.

In C++, return from main runs destructors for its locals (before global/static exit stuff), exit doesn't. But that just means the compiler makes asm that does more stuff before actually running the ret, so it's all manual in asm, like in C.

另一个区别当然是asm/呼叫约定详细信息:EAX(返回值)或EDI(第一个参数)的退出状态,当然,要 ret ,您必须具有RSP指向在您的寄信人地址,就像在函数输入时一样.有了 call exit ,您就不需要了,甚至可以像 jne exit 那样有条件地进行出口的尾声调用.由于它是一个noreturn函数,因此您实际上并不需要RSP指向有效的返回地址.(RSP

The other difference is of course the asm / calling-convention details: exit status in EAX (return value) or EDI (first arg), and of course to ret you have to have RSP pointing at your return address, like it was on function entry. With call exit you don't, and you can even do a conditional tailcall of exit like jne exit. Since it's a noreturn function, you don't really need RSP pointing at a valid return address. (RSP should be aligned by 16 before a call, though, or RSP%16 = 8 before a tailcall, matching the alignment after call pushes a return address. It's unlikely that exit / fflush cleanup will do any alignment-required stores/loads to the stack, but it's a good habit to get this right.)

(整个脚注是关于 ret call exit 而不是 syscall 的,因此与其余部分有点切线答案.您也可以运行 syscall 而不关心堆栈指针指向的位置.)

(This whole footnote is about ret vs. call exit, not syscall, so it's a bit of a tangent from the rest of the answer. You can also run syscall without caring where the stack-pointer points.)

What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code? explains what happens if you use the COMPAT_IA32_EMULATION int 0x80 ABI in 64-bit code.

只要内核已经编译了该支持,就退出就可以了,否则就像任何其他随机int数(如 int 0x7f )一样,它会出现段错误.(例如,在WSL1上,或者构建了自定义内核并禁用了该支持的人.)

It's totally fine for just exiting, as long as your kernel has that support compiled in, otherwise it will segfault just like any other random int number like int 0x7f. (e.g. on WSL1, or people that built custom kernels and disabled that support.)

但是在asm中这样做的唯一原因是,您可以使用 nasm -felf32 nasm -felf64 构建相同的源文件.(除了在某些具有32位版本的 syscall 的AMD CPU上,您不能在32位代码中使用 syscall .32位ABI使用不同的无论如何都要拨打电话号码,这样就不会让相同的来源对两种模式都有用.)

But the only reason you'd do it that way in asm would be so you could build the same source file with nasm -felf32 or nasm -felf64. (You can't use syscall in 32-bit code, except on some AMD CPUs which have a 32-bit version of syscall. And the 32-bit ABI uses different call numbers anyway so this wouldn't let the same source be useful for both modes.)

相关:

  • Why am I allowed to exit main using ret? (CRT startup code calls main, you're not returning directly to the kernel.)
  • Nasm segmentation fault on RET in _start - you can't ret from _start
  • Using printf in assembly leads to empty output when piping, but works on the terminal stdout buffer (not) flushing with raw system call exit
  • Syscall implementation of exit() call exit vs. mov eax,60/syscall (_exit) vs. mov eax,231/syscall (exit_group).
  • Can't call C standard library function on 64-bit Linux from assembly (yasm) code - modern Linux distros config GCC in a way that call exit or call puts won't link with nasm -felf64 foo.asm && gcc foo.o.
  • Is main() really start of a C++ program? - Ciro's answer is a deep dive into how glibc + its CRT startup code actually call main (including x86-64 asm disassembly in GDB), and shows the glibc source code for __libc_start_main.
  • Linux x86 Program Start Up or - How the heck do we get to main()? 32-bit asm, and more detail than you'll probably want until you're a lot more comfortable with asm, but if you've ever wondered why CRT runs so much code before getting to main, that covers what's happening at a level that's a couple steps up from using GDB with starti (stop at the process entry point, e.g. in the dynamic linker's _start) and stepi until you get to your own _start or main.
  • https://stackoverflow.com/tags/x86/info lots of good links about this and everything else.

这篇关于在x64 Linux上,syscall,int 0x80和ret退出程序之间有什么区别?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆