2

I am using Visual studio 2019 in Windows 10, and I want to compile in x86 using MSVC(platform toolset 142) and NASM(version 2.14.02) the next code:

foo.asm

section .text

global foo
foo:
    mov eax, 123
    ret

main.cpp

extern "C" int foo(void);

int main()
{
    int x = foo();
    return 0;
}

But I got the error:

enter image description here

In x64 works well, in x86 the generated file main.obj adds a leading underscore to the function name foo, resulting in _foo. This does not happen in x64 but keeps the symbol as foo, not _foo.

So, is there any solution that works for both x86 and x64 platforms (preferably without modify source code, maybe some compiler/linker flag for MSVS compiler)?

I really appreciate any help.

rustyx
  • 80,671
  • 25
  • 200
  • 267
b0c0pv3zz3
  • 43
  • 6
  • The solution is to write a macro that prepends the underscore when generating 32 bit code but not when generating 64 bit code. – fuz Jul 06 '20 at 10:22
  • For 32 bit MASM (ML.EXE), using the directive `.model C` will eliminate the need to use _ prefix, but 64 bit MASM (ML64.EXE) assumes `.model C` and doesn't include the `.model` directive. – rcgldr Jul 06 '20 at 16:25

2 Answers2

7

The _ prefix is a result of name mangling, which depends on the target platform ABI (OS, bitness, calling convention).

According to Microsoft, the _ prefix is used in a 32-bit Windows cdecl calling convention, but not in 64-bit (source):

Format of a C decorated name

The form of decoration for a C function depends on the calling convention used in its declaration, as shown in the following table. This is also the decoration format that is used when C++ code is declared to have extern "C" linkage. The default calling convention is __cdecl. Note that in a 64-bit environment, functions are not decorated.

Calling convention  —  Decoration
__cdecl   Leading underscore (_)
__stdcall   Leading underscore (_) and a trailing at sign (@) followed by the number of bytes in the parameter list in decimal
__fastcall   Leading and trailing at signs (@) followed by a decimal number representing the number of bytes in the parameter list
__vectorcall   Two trailing at signs (@@) followed by a decimal number of bytes in the parameter list

The reason behind could be that 32/64-bit Windows calling conventions aren't really compatible. For example, function arguments in 64-bit mode are passed differently and different registers have to be preserved between calls.

Anyway, to answer the question, I can think of a couple of workaround solutions:

Solution 1

The code that generates the .asm should add the leading _ only in 32-bit mode (the generated assembly will probably have to differ in other ways too, especially if pointers are involved).

Solution 2

Use a preprocessor to add the leading _ in 64-bit mode:

#ifdef _WIN64
#  define foo _foo
#endif

extern "C" int foo(void);

int main()
{
    int x = foo();
    return 0;
}
rustyx
  • 80,671
  • 25
  • 200
  • 267
  • For Visual Studio, ML64.exe (used for 64 bit assembly) assumes `.model C` (no _ prefix needed, and there is no .model directive). With ML.EXE (used for 32 bit assembly), using the directive `.model C` eliminates the need for the _ prefix. I've also written assembly functions for C++, I do a test build so the linker will output the mangled names that I need for assembly functions. The main advantage of this is that in the C++ source file, I can declare the assembly functions as extern "C++" functions taking references, which will pass pointers to objects to my assembly code. – rcgldr Jul 06 '20 at 16:21
  • @rcgldr: This is a `nasm` question, using that assembler. You could write a NASM DECLARE macro or something to emit the `global x` / `x:` lines, and additionally to prepend a leading `_` for win32 and MacOS, but not for ELF targets or Win64. (Either detecting based on output file format with `%ifidn __OUTPUT_FORMAT__, win32` text matching or something, or just based on something passed passed in from a build script.) – Peter Cordes Jul 07 '20 at 04:53
  • But yes, an asm macro seems preferable to changing the C++ name of your function, which will be visible when debugging. (And could in theory create name clashes between your name and names that are part of the implementation; leading-underscore names are reserved for use by the implementation.) – Peter Cordes Jul 07 '20 at 05:05
  • @PeterCordes - he mentioned Visual Studio, so I was thinking he was using both MASM and NASM. I don't know NASM. – rcgldr Jul 07 '20 at 15:22
  • @rcgldr : They are using Visual Studio as their IDE but using NASM for their assembler rather than MASM – Michael Petch Jul 07 '20 at 15:25
  • @MichaelPetch - one question would be why use NASM with Visual Studio, but there are some NASM macros like [this one](https://stackoverflow.com/a/45503589/3282056). I seem to recall something similar in Intels github repositories for doing builds in different environments. – rcgldr Jul 07 '20 at 15:30
2

Correct solution is to have different asm files for each architecture: x86, amd64, etc, because you will use different assembler instruction sets at all. And select which file will be compiled by make file or environment configuration depends on build architecture. So you can use '_foo' function name for x86 and 'foo' for x86_64

alexb
  • 169
  • 6