我有一个场景,我需要从Linux x64上的LLDB(但它可能是任何c++ API)调用一个函数,从一个用不同语言编写的应用程序。出于这个原因,我需要正确地理解调用约定以及参数是如何传递的。
我正在尝试呼叫SBDebugger::GetCommandInterpreter
,定义为:
lldb::SBCommandInterpreter GetCommandInterpreter();
完整的头文件可以在这里找到:https://github.com/llvm-mirror/lldb/blob/master/include/lldb/API/SBDebugger.h
我的假设是该方法将期望一个指向SBDebugger
的指针作为this
参数,在RDI寄存器中。然而,当以这种方式调用函数时,我得到一个分段错误。
查看函数的反汇编,可以看到:
push %r13
push %r12
mov %rsi,%r12
push %rbp
push %rbx
mov %rdi,%rbx
该函数同时读取RDI和RSI,尽管只期望this
参数。我看到的唯一解释是,功能预计this
值而不是一个引用。SBDebugger
的大小为16字节(一个shared_ptr
),调用约定声明单个16字节的参数将拆分为RDI和RSI寄存器,这与我所看到的相匹配。
然而,这对我来说没有意义,原因有很多:
- 如果
this
是按值传递的,如果该方法有任何副作用,它将如何工作?更改不会反映在调用者 上 - System V ABI声明:
If a C++ object has either a non-trivial copy constructor or a non-trivial destructor 11, it is passed by invisible reference
。SBDebugger
确实有自定义析构函数和复制构造函数:
SBDebugger();
SBDebugger(const lldb::SBDebugger &rhs);
SBDebugger(const lldb::DebuggerSP &debugger_sp);
~SBDebugger();
尽管如此,我尝试通过值传递SBDebugger
来调用该方法,它似乎有效,但是当我尝试使用返回的SBCommandInterpreter
时,我得到了一个分段错误,所以有可能该方法只返回垃圾。
关于这个方法调用有一些我不明白的地方,但是我还没能弄清楚是什么。我应该在哪些寄存器中传递什么值,为什么?
我明白了。我关注的是参数,但关键是返回值。引用System V调用约定:
结构体、类或联合对象只有在满足条件时才能从寄存器中的函数返回足够小而不太复杂。如果对象太复杂或者不适合然后调用者必须为对象提供存储空间并传递一个指向该空间的指针,作为函数的参数。
基本上,因为SBCommandInterpreter
被认为是一个复杂对象,所以GetCommandInterpreter
需要一个额外的隐藏参数,该参数是返回值写入的地址。所以"真实的"方法签名为:
lldb::SBCommandInterpreter* GetCommandInterpreter(SBCommandInterpreter& returnValue, SBDebugger* this);
所以this
参数像预期的那样通过引用传递,我只是缺少了一个额外的隐藏参数。