When cross-compiling a #![no_std] crate for i686-pc-windows-gnu it is linked with the following flags the following flags: "-lgcc_eh" "-l:libpthread.a" "-lmsvcrt" "-lmingwex" "-lmingw32" "-lgcc" "-lmsvcrt" "-lmingwex" "-luser32" "-lkernel32".
And I have the mingw toolchain installed for other projects. (No idea when I installed or what apt packages, but I have mingw-w64 version 9.0.0-1ppa3, FWIW.)
Ok, I did a little digging. The issue is unrelated to the linker flags. Those are always passed even for the 64-bit target.
The linker needs leading underscores for some symbols. This patch will make it compile for i686-pc-windows-gnu:
diff --git a/stardust/scripts/windows.ld b/stardust/scripts/windows.ld
index 595ac24..d8ee602 100644
--- a/stardust/scripts/windows.ld
+++ b/stardust/scripts/windows.ld
@@ -43,7 +43,7 @@ SECTIONS
*/
. = ALIGN(0x1000);
- _data_offset = .;
+ __data_offset = .;
*(.global*)
*(.data*)
*(.bss*)
diff --git a/stardust/src/stcore/arch/i686/i686.asm b/stardust/src/stcore/arch/i686/i686.asm
index 8264e12..34df2a6 100644
--- a/stardust/src/stcore/arch/i686/i686.asm
+++ b/stardust/src/stcore/arch/i686/i686.asm
@@ -24,7 +24,7 @@
mov esi, esp // store current esp in esi
and esp, 0xFFFFFFF0 // align esp to 16 bytes
sub esp, 0x10 // allocate stack space for alignment
- call stmain // call the main function
+ call _stmain // call the main function
mov esp, esi // restore esp
pop esi // restore esi
ret
You may need to separate the linker script for 32-bit and 64-bit Windows builds. Because this patch as-is breaks x86_64 (__data_offset will not be found). This is a requirement for the __cdecl calling convention. Or, maybe find a way to change the calling convention, if that is compatible with what you are trying to do...
You're a wizard! Thank you! Successfully compiles, although still crashes so time time fire up xdbg XD. Annoyingly this naming convention only holds for x32 so you're right on the requirement for i686-specific linker script. What I'm trying to do? Mainly be stubborn as a donkey and prove to a colleague that it can be done while learning a bunch in the process.
For completeness, llvm has a flag to opt-out: --no-leading-underscore. (This does change the calling convention, making it incompatible with other libraries.)
I think it can be passed through the environment (haven't tried this myself):
It appears I may have been too hasty here, I'm seeing a bunch of libraries being included in the map which I've never seen linked in previous targets. This is leading to significantly larger binary.
This includes a bunch of addition unwinding code in the .text section and references to strlen, malloc etc. from libmsvcrt. I've been trying to avoid these by using compiler-builtins. An import table is also being generated.
At the moment the program is crashing as it is generating a .rdata$.refptr.__data_offset which holds at hard coded memory address leading to an exception. Unfortuantly the initial question still stands.
Hmm, it's using identical instructions to x86_64 but I tried the link optimization stuff. Amusingly the -Zlocation-detail=none results in a SEGV during compilation. Comparing the output linker maps I think this is the main difference:
You may have to use -Z build-std to disable unwinding within the standard library. (min-sized-rust also covers this.) That is probably why you are seeing those references.
Another awesome tool to help out with this kind of analysis is cargo-show-asm.
No luck with -Z build-std, still included. The crate doesn't use the standard library anyhow so I don't know how much that flag would affect things. cargo-show-asm looks cool, I've just been using radare2 as that's what I've become comfortable with.
Note that the windows-gnu tries to emulate a unix-ish target in some ways so it includes a lot of stuff to make that work. You might have more luck with windows-gnullvm which is closer to a native Windows target.
Using -Z build-std will implicitly compile the stable crates core , std , alloc , and proc_macro .
I don't know if it will help, but if you are using any of those libraries, you might want to build with -Z build-std=core,alloc,panic_abort -Z build-std-features=panic_immediate_abort.
Including these arguments still results in libgcc_eh.a and others being linked. It also gives an error regarding compiler_builtins being included twice but I worked around this by removing the dependency.
My current hypothesis is that panic=abort is being ignored leading to rsbegin.o (__register_frame_info) being linked. This then has a dependency on libgcc_eh.a(unwind-dw2-fde.o) (for unwinding).
libgcc_eh.a(unwind-dw2-fde.o) then has dependencies on libmsvcrt.a for malloc, free, abort, and so on and so forth.
Even when building with the following flags, this is still the case.
Regarding -nostartfiles it stops gcc from passing object files such as rsbegin.o to the linker. However in the case of rustc, it is rustc itself which passes it's own version of those objects unconditionally. -nostartfiles doesn't have an effect on this.
The solution was to rustc +nightly -Zunstable-options --print target-spec-json --target i686-pc-windows-gnu and edit the output json removed the pre-link-* and post-link-* attributes.