__init() itself also needs some work. the argument list is weird, linux pushes all of argc, argv, and environ on the stack. why special case argc? also your method of deriving argv and environ from the function argument's address is extremely brittle, and i don't think it actually works on x86_64 (if it does, that's really lucky). you aren't calculating envp using argc, so it's probably wrong. you could get more efficient code from using __attribute__((noreturn)). this would be better:
/* called from _start */
void __init(void *initial_stack) __attribute__((noreturn));
void __init(void *initial_stack) {
int argc = *(int *) initial_stack;
char **argv = ((char **) initial_stack) + 1;
/* assert(!argv[argc]); */
char **envp = __environ = argv + argc + 1;
_exit(main(argc, argv, envp));
}