The C runtime

Even though C is a relatively low-level language, some assembly code is needed to get the process in an acceptable state to the language standard.

First of all, main isn't the standard entry point, _start is. This function is provided by your compiler, typically from a file called crt*.o. This function:

  1. aligns the stack
  2. reads argc, argv, environ and the auxiliary vector from the stack (see the links to LWN in Process creation for stack content details)
  3. sets up TLS, locales, and other crap
  4. calls __libc_start_user, which first calls the constructors of all global (C++) variables, then calls main, and exit automatically as well.

Of course, it's huge, but we can provide our own: step 3 can be skipped, 1 and 2 are really small to implement in assembly, and 4 is replaced by calling main. Of course, the second step can be skipped as well when you don't need args/environ.

However, when external libs need to read from the environment (eg. X11 needs $DISPLAY), you still have to call __libc_start_user; it's a portable interface that asks for argc, argv, envp and main. With smol, this isn't exactly a hassle anymore.

For exiting the program when not using __libc_start_main, you can just use a bare syscall, or int3.