目前还没有一种标准的方法可以从汇编中调用C++函数。这是由于一个叫做名称修饰的特性所导致的。C++编译器工具链不会使用代码中精确书写的名称来发射符号。因此,你无法确定代表使用名称kernel_main
或kernelMain
编码的函数的符号的名称将是什么。
为什么需要名称修饰?
在C++中,您可以声明多个实体(类、函数、方法、命名空间等)具有相同的名称,但属于不同的父命名空间。如果两个具有相同局部名称(例如,在命名空间SymbolDomain
中class 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++函数,如果您真的需要的话。像这样 -
class SomeContainer
{
public:
int execute(int y)
{
}
}
#define _SepArg_ ,
#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);
void kernel_main()
应该变为extern "C" void kernel_main()
以避免 C++ 的名称重整。extern kernel_main
应该变为extern _kernel_main
,因为涉及到 C 代码生成。 - Sid Sextern "C"
)明确标记它们以进行导出。阅读此内容:https://dev59.com/P3NA5IYBdhLWcg3wPLP- 顺便说一句,我所说的不可能是指你不能以纯C++方式在二进制级别上与C++进行接口交互,因为没有这样的机制,您需要转向其他机制,这是完全可能的。 - luk32extern _kernel_main
会依赖于所使用的工具链。如果使用通用的 ELF g++ 编译器,则不需要前导下划线。如果工具链针对本机的 32 位 Windows 或 MacOS,则情况会有所不同。我建议在 OSDev 工作中使用通用的 ELF 交叉编译器之一,原因之一就是避免为特定平台生成本地代码的工具链的细微差别。通用 ELF 可执行文件不需要全局可访问符号的_
前缀。 - Michael Petch