使 BFD 库找到 class 成员函数的位置

Make BFD library find the location of a class member function

我正在使用函数 bfd_find_nearest_line 查找函数的源代码位置(来自带有调试符号的可执行文件 -- 使用 -g 编译)。自然地,其中一个参数是指向我要定位的函数的指针:

boolean
_bfd_elf_find_nearest_line (abfd, 
                section, 
                symbols, 
                offset, 
                filename_ptr, 
                functionname_ptr, // <- HERE!
                line_ptr)

https://sourceware.org/ml/binutils/2000-08/msg00248.html

经过相当多的(纯 C)样板后,我设法让它与普通函数一起工作(普通函数指针被强制转换为 *void)。

例如,这个有效:

int my_function(){return 5;}

int main(){
    _bfd_elf_find_nearest_line (...,
                (void*)(&my_function), 
                ...);
}

问题是 bfd_find_nearest_line 是否可以用来定位 class 成员函数的源代码。

struct A{
   int my_member_function(){return 5.;}
};

_bfd_elf_find_nearest_line (...,
                what_should_I_put_here??, 
                ...)

Class 成员函数(在这种情况下,如果类型为 int (A::*)())不是函数,特别是不能转换为任何函数指针,甚至不能转换为 void*。看这里:https://isocpp.org/wiki/faq/pointers-to-members#cant-cvt-memfnptr-to-voidptr

我完全理解这背后的逻辑,然而成员函数指针是唯一的句柄,我从中获得成员函数的信息,以便让 BFD 识别函数。我不希望这个指针调用一个函数。

我或多或少知道 C++ 是如何工作的,编译器会默默地生成一个等效的 free-C 函数,

__A_my_member_function(A* this){...}

但我不知道如何访问这个免费函数的地址,或者是否可能,以及 bfd 库是否能够找到原始 [=22] 的源位置=] 通过这个指针。 (至少目前我对虚函数不感兴趣。)

换句话说,

1) 我需要知道 bfd 是否能够定位成员函数,

2) 如果可以的话,我如何将类型 int (A::*)() 的成员函数指针映射到 bfd 可以接受的参数 (void*).


我通过其他方式(堆栈跟踪)知道指针存在,例如我可以得到在这种情况下调用了自由函数 _ZN1A18my_member_functionEv,但问题是我如何从 &(A::my_member_function).

好的,有好消息也有坏消息。

好消息:可能的。

坏消息:这并不简单。

您需要 c++filt 实用程序。

还有一些方法可以读取您的 executable 的符号 table,例如 readelf。如果您可以通过 bfd_* 调用枚举 [损坏的] 符号,您也许可以节省一步。

此外,这里有一个 大问题 :您需要在 文本字符串 中提供符号的 C++ 名称。因此,对于 &(A::my_member_function),您需要采用以下形式:"A::my_member_function()" 这应该不会太难,因为我认为您关心的数量有限。

您需要从 readelf -s <executable> 获取符号列表及其地址。准备解析此输出。您需要从字符串中解码十六进制地址以获取其二进制值。

这些将是经过处理的名称。对于每个符号,执行 c++filt -n mangled_name 并将输出(即管道)捕获到某些东西(例如 nice_name)中。它将返回给你 demangled 名称(即你想要的漂亮的 c++ 名称)。

现在,如果 nice_name 匹配 "A:my_member_function()",您现在有一个匹配项,您已经有了错位的名称,但更重要的是,符号的十六进制地址。将这个十六进制值[适当地转换]提供给你填充的 bfd functionname_ptr


注意: 上面的方法有效,但重复调用 c++filt

可能会很慢

一个更快的方法是捕获管道输出:

readelf -s <executable> | c++filt

这样做 [可能] 更容易,因为您只需要解析过滤后的输出并寻找匹配的好听的名字。

此外,如果您有多个您关心的符号,您可以在一次调用中获取所有地址。

好的,我找到办法了。首先,我发现 bfd 非常乐意从成员指针中检测成员函数调试信息,只要指针可以转换为 void*.

我使用的是 clang,它不允许我将成员函数指针转换为任何类型的指针或整数。 GCC 允许这样做但会发出警告。 甚至有一个标志允许指针指向名为 -Wno-pmf-conversions.

的成员转换

考虑到这些信息,我尽最大努力将成员函数指针转换为 void*,最后我使用联合来完成此操作。

struct A{
   int my_member_function(){return 5.;}
};

union void_caster_t{
    int (A::*p)(void) value;
    void* casted_value;
};
void_caster_t void_caster = {&A::my_member_function};

_bfd_elf_find_nearest_line (...,
                void_caster.casted_value, 
                ...)

终于bfd给我一个成员函数的调试信息。


我还没有弄清楚的是,如何获取指向构造函数和析构函数成员函数的指针。

例如

void_caster_t void_caster = {&A::~A};

给出编译器错误:"you can't take the address of the destructor"。

对于构造函数,我什至找不到正确的语法,因为它作为语法错误而失败。

void_caster_t void_caster = {&A::A};

同样,无法实现的所有逻辑都涉及无意义的回调,但这是不同的,因为我希望指针(或地址)获取调试信息,而不是回调。