11 ms·
00_start.c is too hacked on x86_64. it'll work but you're getting a less efficient binary since gcc has to assume _start is called like a normal C function (e.g
by rian 12y ago
00_start.c is too hacked on x86_64. it'll work but you're getting a less efficient binary since gcc has to assume _start is called like a normal C function (e.g. it creates a preamble). you should just implement it in assembly.
__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));
}
- oso2k 12y agoWhat do you mean 00_start.c is too hacked on x86_64? As for efficiency of _start, `objdump -d lib/00_start.o` yields ``` 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
- rian 12y agogood point, the gcc optimizer is smart enough to omit the preamble. it's still hacked though. inline assembly is one thing, messing with compiler-owned registers is another especially without a clobber list. btw why do you prefix your x84_64 start code with "mov %rsp,%rbp"? "xor %ebp, %ebp" is more idiomatic and efficient. according to the abi (http://www.x86-64.org/documentation/abi.pdf 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.
- oso2k 12y agoPoint taken. I'll fix up the clobbers and noreturns. 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.
- rian 12y agoit only works because you're skipping over environment variables: 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.