The important Linux facts are:
1. _start is not a function
It can't be returned from, the exit system call must be issued before execution terminates.
2. Arguments and environment are on the stack
Argument count and vector can be simply popped off the stack into appropriate registers, in that order.
The environment vector is located after the NULL terminator of the argument vector, or argument count + 1.
The auxiliary vector is located after the NULL terminator of the environment vector. No count is provided for that, so code must loop through the environment looking for the sentinel in order to find it.
The auxiliary vector is really interesting. I don't usually see software making direct use of it. It contains interesting information such as CPU identifier and capabilities, page size, the location of the Linux vDSO, some random bytes, program file name, user and group IDs, among other things.
https://github.com/torvalds/linux/blob/master/include/uapi/l...
https://github.com/torvalds/linux/blob/master/arch/x86/inclu...
https://github.com/torvalds/linux/blob/master/Documentation/...
This is the data the Linux kernel passes to programs. After organizing these parameters, the program is free to do whatever it wants. The libc entry point will naturally start setting up libc. In particular, it seems to spend a lot of time setting up the init and fini insanity that's probably better off forgotten.
https://blogs.oracle.com/solaris/post/init-and-fini-processi...
It's not necessary. After this, you can just run your program directly. I used to develop a liblinux that illustrates all this with much simpler code:
https://github.com/matheusmoreira/liblinux/blob/master/start...
https://github.com/matheusmoreira/liblinux/blob/master/start...
The entry point code passes the stack pointer to a C function which gathers all kernel parameters and starts the program with no further setup. I made several example programs, including one which outputs all these variables.
https://github.com/matheusmoreira/liblinux/blob/master/examp...
I stopped developing this because I discovered the Linux itself has a better solution that they use for their own tools:
https://github.com/torvalds/linux/blob/master/tools/include/...
The entry point code for all supported architectures is present as inline assembly code!