gdb获取C++类成员函数地址

省流:info line xx(成员函数所在行号)即可查看函数入口地址

一. 获取类成员函数地址

在.cpp文件中声明并定义如下一个类

// memfunc_test.cpp
class Com_class {
public:
    int mem1;
    void func_c()         {cout << "call func_common " << this->mem1 << endl;}
};

void func_c() 这个普通成员函数存放在何处,如何获取其地址?

依照直觉,是否如同成员变量一样,成员函数也是在创建(new)类实例对象时一同创建在呢?为了验证,首先创建一个类的实例对象:

int main() {
    Com_class *cc = new Com_class{};
    cc->mem1 = 1;

    cc->func_c();

    delete cc;
}

使用gdb查看对象的内存结构:

(gdb) p *cc
$1 = {
  mem1 = 1
}

可以看到,对象只保存了成员变量,并没有保存成员函数,原因是为节省内存空间,所有类对象使用一个成员函数即可,而成员变量则独自保存在栈区或者堆区。用sizeof查看,也确实只有一个int类型的大小:

(gdb) p sizeof(*cc)
$7 = 4

那么,成员函数保存至何处,是在代码区(.text)吗?
如果是在代码区,那我们应该可以通过在gdb中使用如下指令查看函数入口地址:

(gdb) info line 8
Line 8 of "memfunc_test.cpp" starts at address 0x5555555552c2 \
<Com_class::func_c()> and ends at 0x5555555552d2 <Com_class::func_c()+16>.

第八行为文件中成员函数所在行,根据所示信息,似乎确实是成员函数地址。为了验证,我们从该地址直接调用函数,检查能否打印出正确结果:

!这个操作或许有风险

(gdb) print ( (void (*)()) 0x5555555552c2 )()
call func_common 0
$2 = void

确实是打印出了结果,但打印成员变量mem1的值却与预期的不同,我们先前已经将其赋值为1,但打印结果却是0。确是为何?

其实上面已经提到,所有对象公用一个成员函数,那么应当能够区分是哪个对象调用该函数。
因此,该函数并非是空参数的,而是需要传入对象的指针(this),我们修改为以下方式再试:

(gdb) print ( (void (*)(Com_class *)) 0x5555555552c2 )(cc)
call func_common 1
$3 = void

可以看到,这次打印的值符合预期了。可以确定这个地址确实是成员函数的地址,那么我们再查看此地址所在段:

(gdb) maintenance info sections
...
 [14]     0x5555555550b0->0x555555555120 at 0x000010b0: .plt.sec ALLOC LOAD READONLY CODE HAS_CONTENTS
 [15]     0x555555555120->0x555555555385 at 0x00001120: .text ALLOC LOAD READONLY CODE HAS_CONTENTS
 [16]     0x555555555388->0x555555555395 at 0x00001388: .fini ALLOC LOAD READONLY CODE HAS_CONTENTS
...

很明显,该地址处在.text段的地址范围内,即成员函数存放在代码区


虽然问题已经得到解决,不过以上实验也发现了一些有意思的行为,即函数明明需要传入参数,但当我们在gdb中并未传递参数的时候,函数也能顺利执行(虽然结果不对),按照以下方式也顺利运行了:

(gdb) print ( (void (*)()) 0x5555555552c2 )(cc)
call func_common 1
$9 = void

可以看到,我们并没有将地址强制类型转换为含参数的函数指针类型,直接传递参数也可正确运行。

关于函数如何传递参数的问题,我们需要从汇编的角度才可以解答,根据x86 calling convention,this指针的值是通过rdi寄存器传递的,为了验证,我们直接将rdi寄存器的值设置为对象的地址,并且不向函数传递任何参数:

(gdb) print ( (void (*)()) 0x5555555552c2 )()
call func_common 0
$1 = void
(gdb) print cc
$2 = (Com_class *) 0x55555556aeb0
(gdb) set var $rdi=0x55555556aeb0
(gdb) print ( (void (*)()) 0x5555555552c2 )()
call func_common 1
$3 = void

可以看到,即使不传递参数,直接调用函数也可以打印正确的值。有关函数调用的相关原理,将会专门在另一篇博文进行介绍。

————结束————

posted @   Luke老黑  阅读(758)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示