NASM调用外部C++函数

4

我正在尝试从NASM调用外部的C++函数。在通过谷歌搜索后,我并没有找到任何相关的解决方案。
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

跑这两个文件进行编译后,我得到了这个错误:kernel.asm(.text+0xe): undefined reference to kernel_main' 这里出了什么问题?谢谢。

2
void kernel_main() 应该变为 extern "C" void kernel_main() 以避免 C++ 的名称重整。extern kernel_main 应该变为 extern _kernel_main,因为涉及到 C 代码生成。 - Sid S
2
据我所知,从根本上讲这是不可能的。也就是说,C++没有标准化的ABI,名称(符号)是混淆的。您需要以某种标准方式(例如通过extern "C")明确标记它们以进行导出。阅读此内容:https://dev59.com/P3NA5IYBdhLWcg3wPLP- 顺便说一句,我所说的不可能是指你不能以纯C++方式在二进制级别上与C++进行接口交互,因为没有这样的机制,您需要转向其他机制,这是完全可能的。 - luk32
我所知道的没有一种标准化语言有一个标准的ABI——当然C语言不包括在内。https://dev59.com/hW855IYBdhLWcg3wMBLV - user2100815
extern _kernel_main 会依赖于所使用的工具链。如果使用通用的 ELF g++ 编译器,则不需要前导下划线。如果工具链针对本机的 32 位 Windows 或 MacOS,则情况会有所不同。我建议在 OSDev 工作中使用通用的 ELF 交叉编译器之一,原因之一就是避免为特定平台生成本地代码的工具链的细微差别。通用 ELF 可执行文件不需要全局可访问符号的 _ 前缀。 - Michael Petch
1个回答

6

目前还没有一种标准的方法可以从汇编中调用C++函数。这是由于一个叫做名称修饰的特性所导致的。C++编译器工具链不会使用代码中精确书写的名称来发射符号。因此,你无法确定代表使用名称kernel_mainkernelMain编码的函数的符号的名称将是什么。

为什么需要名称修饰?

在C++中,您可以声明多个实体(类、函数、方法、命名空间等)具有相同的名称,但属于不同的父命名空间。如果两个具有相同局部名称(例如,在命名空间SymbolDomainclass SomeContainer的局部名称为SomeContainer,但全局名称为SymbolDomain::SomeContainer,至少在这个答案中是这样说的),则会导致符号冲突。

方法重载也会导致冲突,因此,每个参数的类型也会以某种形式被发射出来。为了应对这种情况,C++工具链将在ELF二进制对象中对实际名称进行一些名称修饰。

那么,我不能在汇编中使用C++名称修饰的名称吗?

可以,这是一种解决方案。您可以使用readelf -s fileName命令查看kernel_main的目标文件。您将不得不搜索具有与kernel_main相似之处的符号。一旦您认为找到了它,就可以使用echo _ZnSymbolName | c++filt确认它,该命令应输出kernel_main

然后您可以在汇编中使用这个名称,而不是kernel_main

这种解决方案的问题在于,如果由于某种原因,您更改了参数、返回值或其他任何内容(我们不知道什么会影响名称修饰),则您的汇编代码可能会出错。因此,您必须小心处理。另一方面,这不是一个好的做法,因为你正在使用非标准的东西。

请注意,名称修饰并没有标准化,因工具链而异。依赖它,就意味着你要使用同一个编译器。

我不能做一些标准化的事情吗?

可以。您可以通过像这样声明函数extern "C"来在C++中使用C函数:

extern "C" void kernelMain(void);

这对您来说是最好的解决方案,因为您的kernel_main已经是一个没有父类和命名空间的C样式函数。需要注意的是,此C函数是在C++中编写并仍然使用C++特性(内部)。

其他解决方案包括使用宏间接调用C++函数,如果您真的需要的话。像这样 -

///
/// 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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接