7
votes

I wrote a simple C program which just calls the exit() function, however strace says that the binary is actually calling exit_group, is exit() a exit_group() wrapper? Are these two functions equivalent? If so why would the compiler choose exit_group() over exit()?

1
So you load 1 in eax and called the kernel interrupt and strace shows an exit_group call (252 0xfc)? Which compiler? What were your compile and link strings? (it may also be a strace issue, though you would think they would have the lookup table right)David C. Rankin
@DavidC.Rankin As far as I understand the question correctly the user is calling the exit() function in the libc library.Martin Rosenau
IIRC, that comes from standard C (and/or POSIX?) becoming thread-aware, and mandating that exit() should terminate all the running threads; on Linux the plain exitsyscall would just terminate the current thread, so at some point the exit() libc function was remapped to the exit_group syscall, which does what is required by the standard.Matteo Italia
@MartinRosenau, you may be correct, the assembly tag threw me off. (since assembly does have both and exit and exit_group syscall.David C. Rankin
I just did a short C file and used gcc to dump to assembler (e.g. gcc -S -masm=intel -o exitcall.asm exitcall.c and the assembler instruction is call exit@PLT, Running trace through, you are correct, I find exit_group(0). I then wrote the file in assembly nasm and ran strace on the executable and it calls exit(0) = ?; +++ exited with 0 +++ Strange, I don't have the answer, but I now fully understand the question. Are you using gcc as the compiler?David C. Rankin

1 Answers

12
votes

The Linux and glibc man pages document all of this (See especially the "C library/kernel differences" in the NOTES section).

  • _exit(2): In glibc 2.3 and later, this wrapper function actually uses the Linux SYS_exit_group system call to exit all threads. Before glibc2.3, it was a wrapper for SYS_exit to exit just the current thread.

  • exit_group(2): glibc wrapper for SYS_exit_group, which exits all threads.

  • exit(3): The ISO C89 function which flushes buffers and then exits the whole process. (It always uses exit_group() because there's no benefit to checking if the process was single-threaded and deciding to use SYS_exit vs. SYS_exit_group). As @Matteo points out, recent ISO C / POSIX standards are thread-aware and one or both probably require this behaviour.

    But apparently exit(3) itself is not thread-safe (in the C library cleanup parts), so I guess don't call it from multiple threads at once.

  • syscall / int 0x80 with SYS_exit: terminates just the current thread, leaving others running. AFAIK, modern glibc has no thin wrapper function for this Linux system call, but I think pthread_exit() uses it if this isn't the last thread. (Otherwise exit(3) -> exit_group(2).)

Only exit(), not _exit() or exit_group(), flushes stdout, leading to "printf doesn't print anything" problems in newbie asm programs if writing to a pipe (which makes stdout full-buffered instead of line-buffered), or if you forgot the \n in the format string. For example, How come _exit(0) (exiting by syscall) prevents me from receiving any stdout content?. If you use any buffered I/O functions, or at_exit, or anything like that, it's usually a good idea to call the libc exit(3) function instead of the system call directly. But of course you can call fflush before SYS_exit_group.

(Also related: On x64 Linux, what is the difference between syscall, int 0x80 and ret to exit a program? - ret from main is equivalent to calling exit(3))


It's not of course the compiler that chose anything, it's libc. When you include headers and write read(fd, buf, 123) or exit(1), the C compiler just sees an ordinary function call.

Some C libraries (e.g. musl, but not glibc) may use inline asm to inline a syscall instruction into your binary, but still the headers are part of the C library, not the compiler.