在x64 Linux上,syscall,int 0x80和ret退出程序之间有什么区别? [英] On x64 Linux, what is the difference between syscall, int 0x80 and ret to exit a program?
问题描述
昨天,我决定在学习了多年的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
.
-
exit()的系统调用实现,用于比较相关的C函数,
exit
与_exit
与exit_group
以及底层的asm系统调用.
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.)
相关:
- 为什么我可以退出主窗口使用ret吗?(CRT启动代码调用main,您不是直接将 返回到内核.)
- 在_start中RET上的nasm分段错误-您可以t
ret
来自_start
- 在装配体中使用printf会在管道连接时导致输出为空,但可在终端上运行 stdout缓冲区(而不是使用原始系统调用出口冲洗)
- exit()的系统调用实现
调用出口
与mov eax,60
/syscall
(_exit)与mov eax,231
/syscall
(exit_group)./li> - main()真的是C ++程序的开始吗?-Ciro的答案深入探讨了glibc及其CRT启动代码如何实际调用main(包括GDB中的x86-64 asm反汇编),并显示了__libc_start_main的glibc源代码.
- Linux x86程序启动或-我们到底该如何使用main()? 32位asm,并且比您可能需要的更多细节,直到您对asm更加熟悉为止,但是是否曾经想过为什么CRT在进入main之前会运行大量代码,涵盖了从使用GDB与
starti
(在过程入口点停止,例如在动态链接器的中停止)几步之后的情况._start
)和stepi
,直到您到达自己的_start
或main
. - https://stackoverflow.com/tags/x86/info 与此相关的许多很好的链接.
- 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
orcall puts
won't link withnasm -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
) andstepi
until you get to your own_start
ormain
. - https://stackoverflow.com/tags/x86/info lots of good links about this and everything else.
这篇关于在x64 Linux上,syscall,int 0x80和ret退出程序之间有什么区别?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!