Whenever you wonder where functions are coming from, you can instruct your linker to generate a map file, and then simply look up what object files satisfied these symbols.
Calls to literally 0 look to me like those were weak references that went unsatisfied at the end. So now you have to pray that the code never gets there, or else it will crash. But you don't have to pray. See at address 0x240f9, it is setting EAX to zero, then testing to see if it was zero? And if it is zero, it jumps over the invalid call instruction. I bet you anything that 0 would be replaced by an actual address if the symbol had been there. And frame_dummy has the same code right at the start.
8infy wrote:
In the cxa-at-exit version, the global class constructor does `call 20000 <__cxa_atexit>`, which makes sense, however in the no-cxa version it doesn't, so how does do_global_dtors know to call the destructor?
Ironically, you have already marked one of them in the files you listed. Look at the code following address 0x240c8. What is it doing there? It is performing some calculations with constants. If you have looked at some disassembly, you start to notice patterns, and this very clearly looks like someone is performing pointer subtraction on two symbols that were known at link time, but not at assembly time. Anyway, following that there is a loop (you have the cmp followed by jae, and if you follow where the jae goes to,directly above that you have a jb back to address e0. This is a loop rotation: The compiler is performing the loop test once above the loop to jump over it, and once at the end to jump back up again), and in that loop you have the instruction "call *(%esi, %eax, 4)". That very clearly means that ESI points to an array of function pointers and EAX is an index into that array, and here all of them are called.
To stop teasing any further, the usual way functions are located is with the fini_array mechanism: The compiler generates whatever code is needed to call those destructors, writes the pointers to those stubs into a section called ".fini_array", and the linker assembles these sections into one large array bounded by the symbols "__fini_array_start" and "__fini_array_end". Then your __do_global_dtors only has to iterate over all of these and call them all in turn. Interestingly, it looks like they are called with a forward loop, not a backward one. Interesting, because musl is doing it backwards, and Rich said something to the effect of that being ABI on the mailing list once.
8infy wrote:
Why did passing -fno-use-cxa-atexit make my global destructors work correctly? (my implementation of __cxa_atexit is just ret so I know this wouldn't work).
That is now also explained by the code: With you disabling cxa_atexit, GCC was forced to emit the destructor call as part of fini_array. That's why __fini_array_end is 4 bytes greater in the no-cxa-atexit version than in the cxa-atexit version. And since that is called in the loop below that, your destructor was called.