Unable to export unmangled dll entry points for stdcall

91a4269
Opened by Benjamin Podszun at 2020-07-15 20:26:18

Hi.

After some questions on IRC I think this might be a bug or at least a missing feature: I'd like to export functions with a name of my choosing. I knew about #[no_mangle] and learned about #[export_name] on IRC, but both fail to work in my case.

Code/test.rs:

pub extern "stdcall" fn stdcall() {
}

#[no_mangle]
pub extern "stdcall" fn stdcall_nomangle() {
}

#[export_name="stdcall_export"]
pub extern "stdcall" fn stdcall_exportname() {
}

pub extern "C" fn c() {
}

#[no_mangle]
pub extern "C" fn c_nomangle() {
}

#[export_name="c_export"]
pub extern "C" fn c_exportname() {
}

Build with rustc --cratetype dylib test.rs

Result: rustdllexports

So, every single stdcall entry point follows the name@sizeofarguments convention. I have a number of DLLs right in front of me (not based on rust) that export stdcall entries and DON'T use that convention. In fact, I'm reasonably sure that most of the windows API is both using stdcall and exporting 'unmangled' names.

Can I create something similar with rust? Am I missing another attribute? Should no_mangle work in these cases? Should export_name?

  1. All dllexport'ed functions are usually "mangled" in that way. It's not rust-specific; gcc does same and I think msvc link.exe does too. It's usually not an issue, but it becomes an issue when you want to use GetProcAddress or want to provide platform-agnostic symbol names. In this case, you have to create .def file then pass it to linker. See this example. However there is a bug: dllexport doesn't work well if there is "explicit" dllexport.

    klutzy/defunct at 2014-10-23 15:27:58

  2. This is a LLVM issue - it mangles stdcall symbols so that the @N suffix reflects the correct number of bytes in arguments (for MSVC compatibility, I guess). It is however possible to bypass mangling by starting symbol name with \x01. Try this:

    #[export_name="\x01_stdcall_export"]
    pub extern "stdcall" fn stdcall_exportname() {
    }
    

    vadimcn at 2014-11-12 03:34:41

  3. An updated command line, --crate-type, gives

    $ nm libhello.so | grep stdcall
    0000000000086630 T stdcall_export
    0000000000086610 T stdcall_nomangle
    0000000000086620 t _ZN16stdcall_nomangle10__rust_abiE
    0000000000086640 t _ZN18stdcall_exportname10__rust_abiE
    0000000000086600 t _ZN7stdcall10__rust_abiE
    00000000000865f0 T _ZN7stdcall20h92816bbf6494be08eaaE
    

    I am not an expert on stdcall, but it would seem that the @ stuff is gone now. I guess this got fixed/modified upstream in the last year?

    EDIT: Also, I did do this on Linux, as I assumed that the ABI is the important part here, not Windows specifically. That could be a wrong assumption, I am terrible at Windows stuff :(

    Steve Klabnik at 2015-12-31 19:10:29

  4. I am not an expert on stdcall, but it would seem that the @ stuff is gone now.

    The @ stuff seems still there for me with rustc 1.7.0 (a5d1e7a59 2016-02-29) on Windows. @vadimcn's \x01 trick worked.

    Bruno Bigras at 2016-03-18 19:36:30

  5. What would be cool is if, for the cdylib crate type, Rust would automatically generate a .def file that mapped from the decorated versions to the undecorated versions so the import library would have the decorated versions as the linker expects, while the DLL itself has the undecorated versions making GetProcAddress easy.

    Peter Atashian at 2016-06-27 00:11:33

  6. I have inverse problem: I try to set exported name with "@" symbol. In my case I want to export function with name: "_AbortCompilerPass@4" on x86 and "AbortCompilerPass" on amd64.

    So, with gnu toolchain I create fork like:

    #[cfg(target_pointer_width = "32")]
    #[export_name = "_AbortCompilerPass"]
    pub extern "stdcall" fn abort_compiler_pass_extern(how: winapi::DWORD) { abort_compiler_pass(how)}
    #[cfg(target_pointer_width = "64")]
    #[export_name = "AbortCompilerPass"]
    pub extern "stdcall" fn abort_compiler_pass_extern(how: winapi::DWORD) { abort_compiler_pass(how)}
    

    And it works correctly. This code also works with msvc amd64 toolchain.

    But on msvc x86 toolchain I can't find way to get exported name like "_AbortCompilerPass@4".

    I try to use:

    • #[export_name = "_AbortCompilerPass@4"]
    • #[link_args = "/EXPORT:_AbortCompilerPass@4=_AbortCompilerPass"]

    But MS linker ignored all @4 in all variants :(

    Artem V. Navrotskiy at 2016-09-22 13:20:49

  7. I created simple example with my issue: https://github.com/bozaro/rust-msvcdll It also contains test, so you can run:

    cargo test
    

    For toolchains:

    • stable-i686-pc-windows-msvc - FAIL
    • stable-i686-pc-windows-gnu - OK
    • stable-x86_64-pc-windows-msvc - OK
    • stable-x86_64-pc-windows-gnu - OK

    I try to implement wrapper for Visual Studio compiler plugin: %VS120COMNTOOLS%\..\..\VC\bin\c2.dll

    Also looks like removing @4 is much simple, then adding:

    • link /dll test.obj /export:"_foobar=_foo@4" - add export _foo@4 as _foobar
    • link /dll test.obj /export:"_foobar@4=_foo@4" - add export _foo@4 as _foobar@4
    • link /dll test.obj /export:"_foo@4=_foobar" - add export _foobar as _foo (without @4)

    Artem V. Navrotskiy at 2016-09-22 13:41:21

  8. Also in ideal world I want one of two options:

    • Set base name in export_name (like: #[export_name = "AbortCompilerPass"]) and it works as expected on all toolchains (on x86_64 and i686);
    • Set exactly full name in export_name (like: #[export_name = "_AbortCompilerPass@4"]) for x86_64 and i686 separately without dark magic.

    At this moment:

    • I can't set correct export name on msvc i686 toolchain because I can't set full name to export_name option;
    • I need set before-@ prefix "_AbortCompilerPass" for i686 platform instead base function name.

    Artem V. Navrotskiy at 2016-09-22 14:06:15

  9. @bozaro I think the @4 is added by LLVM automatically (or maybe the linker?). To work around this you could try:

    #[export_name = "\x01AbortCompilerPass"]
    

    The leading "1 byte" tells LLVM to disable all name mangling.

    Alex Crichton at 2016-09-26 20:55:28

  10. @alexcrichton: @bozaro problem is the opposite. Currently (rust 1.11) @N is not added by stable-i686-pc-windows-msvc toolchain, though 32-bit stdcall calling convention expects it.

    Marat Radchenko at 2016-09-26 21:04:12

  11. Here's an update on what the current situation is for the example in the original post using i686-pc-windows-msvc. This is the resulting import library:

      Version      : 0
      Machine      : 14C (x86)
      TimeDateStamp: 57E9902F Mon Sep 26 17:16:31 2016
      SizeOfData   : 00000015
      DLL name     : cdylib.dll
      Symbol name  : _c_export
      Type         : code
      Name type    : no prefix
      Hint         : 9
      Name         : c_export
    
      Version      : 0
      Machine      : 14C (x86)
      TimeDateStamp: 57E9902F Mon Sep 26 17:16:31 2016
      SizeOfData   : 00000017
      DLL name     : cdylib.dll
      Symbol name  : _c_nomangle
      Type         : code
      Name type    : no prefix
      Hint         : 10
      Name         : c_nomangle
    
      Version      : 0
      Machine      : 14C (x86)
      TimeDateStamp: 57E9902F Mon Sep 26 17:16:31 2016
      SizeOfData   : 0000001D
      DLL name     : cdylib.dll
      Symbol name  : _stdcall_export@0
      Type         : code
      Name type    : undecorate
      Hint         : 11
      Name         : stdcall_export
    
      Version      : 0
      Machine      : 14C (x86)
      TimeDateStamp: 57E9902F Mon Sep 26 17:16:31 2016
      SizeOfData   : 0000001F
      DLL name     : cdylib.dll
      Symbol name  : _stdcall_nomangle@0
      Type         : code
      Name type    : undecorate
      Hint         : 12
      Name         : stdcall_nomangle
    

    This is what is exported from the DLL itself: This looks like the correct behavior to me, aside from those __rust functions which really shouldn't be exported. The original issue as stated has been fixed.

    Now in @bozaro 's case, c2.dll does have a decorated name, and I'm not really sure how to make it work.

    Peter Atashian at 2016-09-26 21:33:08

  12. Oh! Sorry I misinterpreted this issue totally. This is probably because we don't use dllexport but instead pass a list of symbols to the linker that need to get exported. In that list we don't include anything with @N, and we may need to do that to get these symbols exported.

    Alex Crichton at 2016-09-27 00:40:29

  13. @alexcrichton If we want to do this properly, we'll need to distinguish between the name exported from the import library and the name exported from the DLL itself, and unfortunately #[export_name] is only a single attribute (and I don't really have any faith in Rust doing things properly). I'd argue our current situation where we pass a DEF file to the linker with undecorated symbols is the best default as it results in decorated names in the import library for linking purposes while the DLL itself exports undecorated names to make GetProcAddress easy.

    Peter Atashian at 2016-09-27 00:51:53

  14. Triage: no changes I'm aware of

    Steve Klabnik at 2017-09-30 16:14:01

  15. was this ever fixed?

    snek at 2018-06-19 00:21:31

  16. ^ having this issue and cant find a working workaround, tried everything from renaming to using \x01 am at a loss spent too long on this ;-;

    Ahriana Jackson at 2018-06-19 00:45:34

  17. Just got bitten by this.

    Nico OrrĂ¹ at 2018-10-02 20:40:27

  18. If you need to do some very specific renaming (adding the "@12", etc), you can use this workaround in .cargo/config

    If overrides the .def file that rustc generates with a custom one (see https://docs.microsoft.com/en-us/cpp/build/exporting-from-a-dll-using-def-files?view=vs-2019).

    This isn't without downsides: It'll override any transitive exports from dependencies your library has, unless you explicitly re-add them. As such, this might not work for every use case. This is undoubtedly an unstable hack around the problem. It also only works with MSVC

    [target.i686-pc-windows-msvc]
    rustflags = [
        # Allows us to export the correct stdcall symbol names for Windows 32-bit binaries.
        # Rust has no way to explicitly export a symbol named "_RVExtension@12", it cuts off the @12.
        # This overrides the linker's /DEF argument, to force it to export the symbols we want.
        "-Clink-arg=/DEF:Win32.def",
        # Generate a map file so we can see what symbols exist, and what we're exporting. (NOT REQUIRED)
        "-Clink-arg=/MAP:SymbolInfo.map",
        # Add exported symbol info to the map file (NOT REQUIRED)
        "-Clink-arg=/MAPINFO:EXPORTS"
    ]
    

    Contents of Win32.def are exports which exactly match the private symbols rustc produced (visible in that map file that gets dumped)

    LIBRARY
    EXPORTS
        _RVExtensionVersion@8
        _RVExtension@12
        _RVExtensionArgs@20
    

    Spoffy at 2020-07-15 20:25:10