3

I am trying to call external C++ function from NASM. As I was searching on google I did not find any related solution.
C++

void kernel_main()
{
    char* vidmem = (char*)0xb8000;
    /* And so on... */
}

NASM

;Some calls before
section     .text
    ;nothing special here

global start
extern kernel_main ;our problem

After running compiling these two files I am getting this error: kernel.asm(.text+0xe): undefined reference to kernel_main' What is wrong here? Thanks.

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
Ondřej Langr
  • 43
  • 1
  • 4
  • 2
    `void kernel_main()` should probably be `extern "C" void kernel_main()` to avoid c++ name mangling. `extern kernel_main` should probably be `extern _kernel_main` because of C code generation. – Sid S Apr 14 '18 at 00:29
  • 2
    AFAIK it's impossible on a fundamental level. I.e. c++ does not have standardized ABI, the names (symbols) are mangled. You need to specifically mark them to be exported in some standard way e.g. c-style via `extern "C"`. Read this one: https://stackoverflow.com/questions/1041866/what-is-the-effect-of-extern-c-in-c BTW. what I meant by impossible is that you can't interface c++ on a binary level in a pure c++-way as there is none, you need to defer to some other mechanism, which is very possible. – luk32 Apr 14 '18 at 00:29
  • No standardised language that I'm aware of has a standard ABI - certainly C doesn't: https://stackoverflow.com/questions/4489012/does-c-have-a-standard-abi –  Apr 14 '18 at 00:35
  • `extern _kernel_main` would be dependent on the tool chain being used. If using a generic elf g++ compiler you wouldn't have leading underscores. If the toolchain targeted native 32-bit Windows or MacOS then that would be different. One of the reasons I suggest a generic elf cross compiler for OSDev work is for exactly that reason - avoid the nuances of a tool chain that generates native code for a particular platform. Generic ELF executables don't require `_` prefix for globally accessible symbols. – Michael Petch Apr 14 '18 at 01:13

1 Answers1

5

There is no standardized method of calling C++ functions from assembly, as of now. This is due to a feature called name-mangling. The C++ compiler toolchain does not emit symbols with the names exactly written in the code. Therefore, you don't know what the name will be for the symbol representing the function coded with the name kernel_main or kernelMain, whatever.

Why is name-mangling required?

You can declare multiple entities (classes, functions, methods, namespaces, etc.) with the same name in C++, but under different parent namespaces. This causes symbol conflicts if two entities with the name local name (e.g. local name of class SomeContainer in namespace SymbolDomain is SomeContainer but global name is SymbolDomain::SomeContainer, atleast to talk in this answer, okay) have the same symbol name.

Conflicts also occur with method overloading, therefore, the types of each argument are also emitted (in some form) for methods of classes. To cope with this, the C++ toolchain will somehow mangle the actual names in the ELF binary object.

So, can't I use the C++ mangled name in assembly?

Yes, this is one solution. You can use readelf -s fileName with the object-file for kernel_main. You'll have to search for a symbol having some similarity with kernel_main. Once you think you got it, then confirm that with echo _ZnSymbolName | c++filt which should output kernel_main.

You use this name in assembly instead of kernel_main.

The problem with this solution is that, if for some reason, you change the arguments, return value, or anything else (we don't know what affects name-mangling), your assembly code may break. Therefore, you have to be careful about this. On the other hand, this is not a good practice, as your going into non-standard stuff.

Note that name-mangling is not standardized, and varies from toolchain to toolchain. By depending on it, your sticking to the same compiler too.

Can't I do something standardized?

Yep. You could use a C function in C++ by declaring the function extern "C" like this

extern "C" void kernelMain(void);

This is the best solution in your case, as your kernel_main is already a C-style function with no parent class and namespace. Note that, the C function is written in C++ and still uses C++ features (internally).

Other solutions include using a macro indirection, where a C function calls the C++ function, if you really need to. Something like this -

///
/// Simple class containing a method to illustrate the concept of
/// indirection.
///
class SomeContainer
{
public:
     int execute(int y)
     {

     }
}

#define _SepArg_ , // Comma macro, to pass into args, comma not used directly

///
/// Indirection for methods having return values and arguments (other than
/// this). For methods returning void or having no arguments, make something
/// similar).
///
#define _Generate_Indirection_RetEArgs(ret, name, ThisType, thisArg, eargs) \
extern "C" ret name ( ThisType thisArg, eargs ) \
{                                     \
    return thisArg -> name ( eargs );         \
}                                     \

_Generate_Indirection_RetEArgs(int, execute, SomeContainer, x, int y);
Michael Petch
  • 46,082
  • 8
  • 107
  • 198
Shukant Pal
  • 706
  • 6
  • 19