There are a few issues here.
Your main.S
has a stack alignment bug: on x86_64
, you must realign the stack to 16-byte boundary before calling any other function (you can assume 8-byte alignment on entry).
Without this, I get a crash inside printf
due to movaps %xmm0,0x40(%rsp)
with misaligned $rsp
.
Your link order is wrong: crt1.o
should be linked before main.o
When you don't leave SIZEOF_HEADERS == 0xe8
space before starting your .text
section, you are leaving it up to the linker to put program headers elsewhere, and it does. The trouble is: musl (and a lot of other code) assumes that the file header and program headers are mapped in (but the ELF format doesn't require this). So they crash.
The right way to specify start address:
ENTRY(_start)
SECTIONS
{
. = 0x10000 + SIZEOF_HEADERS;
}
Update:
Why does the order matter?
Linkers (in general) will assemble initializers (constructors) left to right. When you call standard C library routines from main()
, you expect the standard library to have initialized itself before main()
was called. Code in crt1.o
is responsible for performing such initialization.
If you link in the wrong order: crt1.o
after main.o
, construction may not happen correctly. Whether you'll be able to observe this depends on implementation details of the standard library, and exactly what parts of it you are using. So your binary may appear to work correctly. But it is still better to link objects in the correct order.
I'm leaving 0x10000 space, isn't it enough for headers?
You are interfering with the built-in default linker script, and instead giving it incomplete specification of how to lay out your program in memory. When you do so, you need to know how the linker will react. Different linkers will react differently.
The binutils ld
reacts by not emitting a LOAD
segment covering program headers. The ld.lld
reacts differently -- it actually moves .text
past program headers.
The resulting binaries still crash though, because the binary layout is not what the kernel expects, and the kernel-supplied AT_PHDR
address in the aux vector is wrong.
It looks like the kernel expects the first LOAD
segment to be the one which contains program headers. Arguably that is a bug in the kernel -- nothing in the ELF spec requires this. But all normal binaries do have program headers in the first LOAD
segment, so you'll just have to do the same (or convince kernel developers to add code to handle your weird binary layout).