我试图从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'
这有什么不对?感谢。
答案 0 :(得分:3)
截至目前,还没有从程序集调用C ++函数的标准化方法。这是由于名为 name-mangaling 的功能。 C ++编译器工具链不会使用在代码中写入的名称发出符号。因此,您不知道代表名称为kernel_main
或kernelMain
的函数的符号的名称是什么。
为什么需要进行名称管理?
您可以在C ++中声明具有相同名称但在不同父命名空间下的多个实体(类,函数,方法,命名空间等)。如果名称为本地名称的两个实体(例如,名称空间class SomeContainer
中的本地名称SymbolDomain
为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函数调用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);