`i686-w64-mingw32-gcc` and relative data addressing (PIC)

I've been attempting to compile position-independent code for i686-pc-windows-gnu (although I am using a custom target due to Compiling `no_std` for `i686-pc-windows-gnu` ignores `panic=abort` · Issue #133826 · rust-lang/rust · GitHub ). However, references to the .bss and .data sections generate absolute addresses despite my best combinations of -fPIC, --disable-large-memory-addresses etc.

For example, the following rust code to use an external linker-generated symbol will result in the use of an absolute address.

extern "C" {
    static mut _data_offset: usize;
}

pub fn data_offset() -> usize {
    #[allow(unused_unsafe)]
    unsafe {
        let offset_addr = &raw mut _data_offset;
        offset_addr as usize
    }
}

Calling data_offset() results in the generation of the following assembly:

add    esi,DWORD PTR ds:0x928

ds:0x928 is the location of .refptr.__data_offset which can be view in the symbol map:

.rdata$.refptr.__data_offset
                0x00000928        0x4 /home/irate-walrus/Source/stardust-rs/target/i686-custom-windows-gnu/release/deps/stardust-2c9480055904d2d5.stardust.594aeda9d04a4488-cgu.0.rcgu.o
                0x00000928                .refptr.__data_offset

Global static variables will also result in absolute addresses.

pub static INSTANCE: AtomicPtr<Instance> = AtomicPtr::new(ptr::null_mut());
mov    DWORD PTR ds:0x1000,ebx
mov    esi,DWORD PTR ds:0x1000

ds:0x1000 is the location of the .bss section where INSTANCE is stored.

.bss           0x00001000        0x5

When targeting i686-unknown-linux-gnu, x86_64-unknown-linux-gnu, and x86_64_pc-windows-gnu relative addressing works as expected. i686-unknown-linux-gnu produces the following assembly when accessing the same INSTANCE global.

 lea    ecx,[ebx-0x144f]

I don't know if what I am attempting to do is even possible, but it must be in theory as it is working on the i686 linux target. The project this is for is GitHub - Irate-Walrus/stardust-rs: An i686 & x86_64 position independent implant template for Rust 🦀 . Your help is much appreciated and if this fails I guess I'm going to have get deep into some mingw forums.

as far as I know, Windows PE doesn't use a GOT (global offset table), and PE DLLs are not truly position "independent" in the same sense as ELF shared libraries: a PE DLL is relocated at load time if the loader cannot (or decided not to, e.g. for security reasons) put it at the target base address.

and I don't remember msvc to have a -fpic equivalent, so I did some searching and many sources seem to indicate this is the case. here's some examples:

1 Like

This was my hypothesis as windows does indeed not use a GOT. The way i686 linux does it is by using __i686.get_pc_thunk.* as the address of the GOT is encoded relative to the instruction pointer. So I'm glad to have sanity checked this.

This being said, how possible would it be to compile a this binary (intended for windows) with a hypothetical i686-unknown-none-elf? This would have a GOT and therefore allow PIC, while specifying the calling conventions of the extern functions in the code such as extern "stdcall" fn.

I don't know, but I would guess it is probably very hard. the thing is, the code generator, the linker, and the os loader are intertwined in many ways, personally I won't expect a PE binary linked using a bunch of ELF objects would run without problems at all.

So I tried it, using rustc -Z unstable-options --print target-spec-json --target i686-unknown-linux-gnu as a base. Actually manages to execute and make a successful call to KERNEL32!WriteFile before it crashes. Wild. Either way I think you previous answer is what I'll mark as the solution to this topic. Thank you very much for your time and for humoring this.

I remember once I did some experiments (for fun) to objcopy from some ELF objects (generated by a linux compiler) into COFF objects, and then linked them into a windows program. amusingly, it worked for the trivial cases, but I stopped there because any "real" non trivial code would involve more than just the .text section (and maybe .data and .bss) to work, which I had no idea how the windows linker would handle, let alone the references to external symbols from glibc, libgcc, or other linux specific libraries.

In this case all sections are being slammed into .text and there is only one true global variable. No external libraries are linked including crt0.

given the difference between ELF and PE, especially how they handle relocations, even if it might be possible in theory, I doubt that you can make it work with the llvm backend, much less in the rust front end.

essentially you must trick llvm to generate code as if for ELF, but in the PE/COFF container format.

Can confirm it 100% works in the context of my project, I manually patch the GOT so no relocations are required and I'm treating the produced binary as shellcode.

1 Like