``` 0000000000000000 <_start>: 0: 48 89 e5 mov %rsp,%rbp 3: 48 8b 3c 24 mov (%rsp),%rdi 7: 48 8b 74 24 08 mov 0x8(%rsp),%rsi c: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp 10: e8 00 00 00 00 callq 15 <_start+0x15> 15: c3 retq ```
I only see one byte of inefficiency, the `retq`. Otherwise, it's exactly as I've specified it.
What I found is (by using gdb) is that the stack contains, in order , `argc` `[RSP+0]`, `argv` `[RSP+8]`, & `envp` `[RSP+16]`. I verified this using 'frame' in gdb using the source (RSP) and dest addresses. Honestly, I was surprised since it matched exactly what was presented to the ELF image on i386.
Most of the libc's I've surveyed did something like you've specified for __init. However, gcc generated different code for -O3 & -Os, often breaking one or the other optimization args, by modifying what was stored/pointed to for envp and/or *argv. While argc, argv, envp, and envpc are soecified tge
according to the abi (http://www.x86-64.org/documentation/abi.pdf), the stack frame is set up like this:
argc = [RSP+0]
argv = RSP+8
envp = RSP+8+8*argc+8
the same for x86 but replace 8 with 4. your code mirrors this but retrieves argv by taking the address of the second function argument (because you pass [RSP+8] to __init(), which is actually argv[0]). C provides no guarantees on the stability of addresses of passed argument values between caller and callee, so this makes your code subject to non-standard behavior. i can see this working for x86 since values as passed through the stack but not for x86_64 where values are passed through registers.if you're seeing bugs between gcc -O3 and -Os it's likely due to that, or it's due to improper use of inline assembly/clobbering registers.
And you're right about me having issues with calculating envp. There's multiple examples of envp=argv+argc+1. But when I examined the stack frames, that lead to miss calculations and with -Os envp was getting clobbered. Try running t/test.exe, what I have now works.
char **argv = &stack;
char **envp = __environ = argv + ( 1 * sizeof( void* ) );
let's say argv = 0x8, then according to this code, on x86_64, envp = 0x48 (0x8 + sizeof(void * ) * sizeof(char *)). here's a sample stack where argc = 1: stack:
[0x0] = 1 (argc)
[0x8] = "program path"
[0x10] = 0
[0x18] = "FOO1=FOO1"
[0x20] = "FOO2=FOO2"
[0x28] = "FOO3=FOO3"
[0x30] = "FOO4=FOO4"
[0x38] = "FOO5=FOO5"
[0x40] = "FOO6=FOO6"
[0x48] = "FOO7=FOO7"
...
since you set envp to 0x48 it now points to FOO7=FOO7, you've inadvertently skipped FOO1-FOO6. if argc = 2, then envp would point to FOO6 and you skipped FOO1-FOO5.try this with your code, pass 8 arguments to your test. the environment will point to the last element in argv and then terminate, completely missing the actual environment. again, that's only the behavior on x86, on x86_64, passing any argument will cause a segfault.
[0] https://github.com/lpsantil/rt0/blob/master/lib/00_start.c
__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: