8

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()?

Trey
  • 474
  • 2
  • 9
  • 32
  • 2
    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 Oct 24 '17 at 06:02
  • 1
    @DavidC.Rankin As far as I understand the question correctly the user is calling the `exit()` function in the `libc` library. – Martin Rosenau Oct 24 '17 at 06:05
  • 2
    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 `exit`syscall 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 Oct 24 '17 at 06:06
  • @DavidC.Rankin I just fixed the question, – Trey Oct 24 '17 at 06:07
  • 1
    @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 Oct 24 '17 at 06:07
  • @DavidC.Rankin, More interestingly I also tried to use the _exit function from unistd.h, but nothing changed the program is still calling exit_group() – Trey Oct 24 '17 at 06:11
  • 1
    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 Oct 24 '17 at 06:35
  • @DavidC.Rankin, yep, version 7.2 – Trey Oct 24 '17 at 06:55
  • 1
    @Trey -- did you compile to object with `gas` and then link with `ld`? ... or did you let `gcc` handle the link? If so, it would be interesting to know what options `gcc` used by default. For my x86 example, I compiled with `nasm`, e.g. `nasm -f elf -o ./obj/exitcall32.o exitcall32.asm` and linked with `ld -m elf_i386 -o ./bin/exitcall32 ./obj/exitcall32.o`. There are differences in entry point (`main` v. `_start`) which may pull in the libc `exit_group` if you compiled and let `gcc` do the link. – David C. Rankin Oct 24 '17 at 06:56
  • @DavidC.Rankin I used **as** and then **ld**, without any flags, and i'm using a 64-bit machine – Trey Oct 24 '17 at 06:57
  • 1
    @Trey: Did you use `eax=1` / `int $0x80` in 64-bit code? If so, strace decodes it wrong, but it really is `sys_exit()`. See https://stackoverflow.com/questions/46087730/what-happens-if-you-use-the-32-bit-int-0x80-linux-abi-in-64-bit-code. Works for me with `cat > foo.S` / copy-paste your code / `gcc -m32 foo.S -nostdlib -static` && `strace a.out`. I get `exit(0)`. In 64-bit a executable, strace decodes it as `write(0, NULL, 0)` and then says `+++ exited with 0 +++`. So I can't reproduce your `exit_group` from eax=1 / `int $0x80` – Peter Cordes Oct 24 '17 at 06:57
  • Either change your question back to the old one, or update it with a [mcve] for strace decoding eax=1 / `int $0x80` as `exit(0)`. It's just weird now, because that's a pretty different question from asking about what the C library wrapper functions do (and why). – Peter Cordes Oct 24 '17 at 07:42

1 Answers1

16

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.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • To be honest, you beat me to the man pages.... and again I learn how much I've still to learn. Thanks Peter. – David C. Rankin Oct 24 '17 at 06:44
  • 4
    @DavidC.Rankin: I didn't have to look this up just now; I was curious about it years ago. :P I learned a *lot* of systems programming stuff just from reading Linux man pages and seeing what various programs did using `strace`. – Peter Cordes Oct 24 '17 at 06:49
  • @PeterCordes: This is the way. The complexity is reflected by the evolution of threading on Linux **systems** over time and the fact that the syscall mechanics remain in the API even after a better mechanism is found. I say **system** to emphasize the interplay of kernel, libc and compilers. – artless noise Dec 18 '20 at 15:05