Skip to content

Unable to export unmangled dll entry points for stdcall #17806

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
darklajid opened this issue Oct 5, 2014 · 18 comments
Open

Unable to export unmangled dll entry points for stdcall #17806

darklajid opened this issue Oct 5, 2014 · 18 comments
Labels
A-linkage Area: linking into static, shared libraries and binaries C-enhancement Category: An issue proposing an enhancement or a PR with one. O-windows Operating system: Windows

Comments

@darklajid
Copy link

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?

@kmcallister kmcallister added O-windows Operating system: Windows A-linkage Area: linking into static, shared libraries and binaries C-enhancement Category: An issue proposing an enhancement or a PR with one. labels Oct 6, 2014
@klutzy
Copy link
Contributor

klutzy commented Oct 23, 2014

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.

@vadimcn
Copy link
Contributor

vadimcn commented Nov 12, 2014

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() {
}

@steveklabnik
Copy link
Member

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 :(

@bbigras
Copy link

bbigras commented Mar 18, 2016

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.

@retep998
Copy link
Member

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.

@bozaro
Copy link

bozaro commented Sep 22, 2016

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 :(

@bozaro
Copy link

bozaro commented Sep 22, 2016

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)

@bozaro
Copy link

bozaro commented Sep 22, 2016

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.

@alexcrichton
Copy link
Member

@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.

@slonopotamus
Copy link

slonopotamus commented Sep 26, 2016

@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.

@retep998
Copy link
Member

retep998 commented Sep 26, 2016

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.

@alexcrichton
Copy link
Member

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.

@retep998
Copy link
Member

retep998 commented Sep 27, 2016

@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.

@steveklabnik
Copy link
Member

Triage: no changes I'm aware of

@devsnek
Copy link
Contributor

devsnek commented Jun 19, 2018

was this ever fixed?

@Ahriana
Copy link

Ahriana commented Jun 19, 2018

^ 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 ;-;

@norru
Copy link

norru commented Oct 2, 2018

Just got bitten by this.

@Spoffy
Copy link

Spoffy commented Jul 15, 2020

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-linkage Area: linking into static, shared libraries and binaries C-enhancement Category: An issue proposing an enhancement or a PR with one. O-windows Operating system: Windows
Projects
None yet
Development

No branches or pull requests