在GDB中调用lambda函数

17
//gdb-call-lambda.cpp 
#include <iostream>                                                             
                                                                                
void do_something(void) {                                                       
        std::cout << "blah blah" << std::endl;                                  
                                                                                
        auto lambda_func = [](void){                                            
                std::cout << "in lambda" << std::endl;                          
                return;                                                         
        };                                                                      
                                                                                
        lambda_func();                                                          
                                                                                
        std::cout << "..." << std::endl;                                        
                                                                                
        return;                                                                 
}                                                                               
                                                                                
int main(int argc, char **argv) {                                               
        do_something();                                                         
        return 0;                                                               
}

在这个示例程序中,如果你编译(g++ gdb-call-lambda.cpp --std=c++11 -g)并在gdb中运行它(gdb ./a.out),你可以让GDB调用任何“正常”的函数。例如:
(gdb) break main
Breakpoint 1 at 0x4008e7: file gdb-call-lambda.cpp, line 20.
(gdb) r
Starting program: /home/keithb/dev/mytest/gdb-call-lambda/a.out 

Breakpoint 1, main (argc=1, argv=0x7fffffffdfb8) at gdb-call-lambda.cpp:20
20      do_something();
(gdb) call do_something()
blah blah
in lambda
...

然而,如果您尝试调用lambda函数:

(gdb) break do_something
Breakpoint 2 at 0x400891: file gdb-call-lambda.cpp, line 5.
(gdb) c
Continuing.

Breakpoint 2, do_something () at gdb-call-lambda.cpp:5
5       std::cout << "blah blah" << std::endl;
(gdb) n
blah blah
12      lambda_func();
(gdb) n
in lambda
14      std::cout << "..." << std::endl;
(gdb) call lambda_func()
Invalid data type for function to be called

GDB 有些反应异常。因此我的问题是:如何在 GDB 中调用 lambda?与普通函数相比,询问 GDB 所期望的并没有什么有趣的内容:
(gdb) whatis lambda_func
type = __lambda0
(gdb) whatis do_something
type = void (void)

我查看了lambda_func是否拥有任何特殊成员,例如指向函数的指针,类似于std::function和/或std::bind:

(gdb) print lambda_func
$1 = {<No data fields>}

没有特殊成员?那么这可能只是一个大肚子的函数指针?
(gdb) call ((void (void)) lambda_func)()

Program received signal SIGSEGV, Segmentation fault.
0x00007fffffffdeaf in ?? ()
The program being debugged was signaled while in a function called from GDB.
GDB remains in the frame where the signal was received.
To change this behavior use "set unwindonsignal on".
Evaluation of the expression containing the function
(at 0x0x7fffffffdeaf) will be abandoned.
When the function is done executing, GDB will silently stop.

所以我甚至不确定传递任何参数或特别捕获类型的顺序。

我尝试了额外的call lambda_func.operator()()call lambda_func::operator()call lambda_func::operator()()call __lambda0call __lambda0()call __lambda0::operator()call __lambda0::operator()(),但都没有成功。

在Google上搜索发现有关在lambda中设置断点的内容,但没有关于如何从调试器中调用这些lambda的信息。

值得一提的是,这是在Ubuntu 14.04 64位系统上使用g++ 4.8.2-19ubuntu1和gdb7.7-0ubuntu3.1。


调用 lambda_func.operator() 会起作用吗? - John
它不起作用。我尝试了额外的 call lambda_func.operator()()call lambda_func::operator()call lambda_func::operator()()call __lambda0call __lambda0(),调用 __lambda0::operator()call __lambda0::operator()(),但都没有成功。 - inetknght
lldb 给出了不同的错误信息:error: call to a function '$_0::operator()() const' ('_ZNK3$_0clEv') that is not present in the target error: warning: function '<anonymous class>::operator()' has internal linkage but is not defined error: The expression could not be prepared to run in the target - tclamb
我不熟悉使用lldb。如果在lldb中也很困难,那么可能可以创建一个类似的问题或者编辑我的问题以包含lldb信息? - inetknght
3个回答

10

我原本期望 call __lambdaX::operator()() 能够运行,但实际上它并没有。我认为这可能与 GCC 的实现有关。当我需要在 GDB 中调用 lambda 函数时,这是我的解决方法。

简而言之,GDB 有一个 disassemble 命令,它会在 call 指令的行中给出 __lambda0::operator()() const 的调试信息。然后,将该地址转换为函数指针并调用它。

下面的示例可以更好地解释。

$ g++ -g -std=c++0x lambda.cpp 
$ ./a.out 
blah blah
in lambda
...

GDB:

$ gdb ./a.out 
GNU gdb (GDB) Fedora 7.7.1-13.fc20
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./a.out...done.
(gdb) b do_something()
Breakpoint 1 at 0x4008a3: file lambda.cpp, line 4.
(gdb) run
Starting program: /home/alper/cplusplus/a.out 

Breakpoint 1, do_something () at lambda.cpp:4
4           std::cout << "blah blah" << std::endl;                                  
Missing separate debuginfos, use: 
(gdb) n
blah blah
11          lambda_func();

拆开do_something函数

(gdb) disassemble do_something
Dump of assembler code for function do_something():
   0x40089b <+0>:   push   %rbp
   0x40089c <+1>:   mov    %rsp,%rbp
   0x40089f <+4>:   sub    $0x10,%rsp
=> 0x4008a3 <+8>:   mov    $0x4009fb,%esi
   0x4008a8 <+13>:  mov    $0x601060,%edi
   0x4008ad <+18>:  callq  0x400750 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
   0x4008b2 <+23>:  mov    $0x400770,%esi
   0x4008b7 <+28>:  mov    %rax,%rdi
   0x4008ba <+31>:  callq  0x400760 <_ZNSolsEPFRSoS_E@plt>
   0x4008bf <+36>:  lea    -0x1(%rbp),%rax
   0x4008c3 <+40>:  mov    %rax,%rdi
   0x4008c6 <+43>:  callq  0x400870 <__lambda0::operator()() const>
   0x4008cb <+48>:  mov    $0x400a05,%esi
   0x4008d0 <+53>:  mov    $0x601060,%edi
   0x4008d5 <+58>:  callq  0x400750 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
   0x4008da <+63>:  mov    $0x400770,%esi
   0x4008df <+68>:  mov    %rax,%rdi
   0x4008e2 <+71>:  callq  0x400760 <_ZNSolsEPFRSoS_E@plt>
   0x4008e7 <+76>:  nop
   0x4008e8 <+77>:  leaveq 
   0x4008e9 <+78>:  retq   

GDB输出了行号callq 0x400870 <__lambda0::operator()() const>,因此将0x400870转换为函数指针并调用它。

(gdb) call ((void (*)()) 0x400870)()
in lambda
(gdb) call ((void (*)()) 0x400870)()
in lambda
(gdb) call ((void (*)()) 0x400870)()
in lambda
注意:如果GCC内联了lambda函数,就没有需要调用的函数。例如,如果上面的示例使用优化开关-O3编译,则在GDB disassemble输出中将没有带有__lambda0::operator()() const的行。

虽然这并不是一个理想的解决方案,但它确实提供了调用void()函数的能力。通过一些尝试,我相信我也可以弄清如何调用具有其他签名的lambda表达式。谢谢! - inetknght
@inetknght,7年过去了,你有找到更好的解决方案吗? - John
不行,这个问题仍然存在。 - inetknght

1
在一个不那么合成的使用案例中,我成功地通过调用lambda来实现调用它。
call myLambda.operator()(param1,param2)

但在OP的测试案例中,GDB似乎认为该函数是内联的——尽管它并不是。我已经报告了这个问题bug 28137


“a less synthetic use case”是什么?你能详细解释一下吗? - John
@John,这只是指一个复杂程序的实际案例,很难简化为 [mcve]。 - Ruslan

0

注意:这些方法不能保证与C++14通用lambda一起使用。

情况1. 允许修改源代码

需要为每个单独的lambda修改源代码。

方法1.1. std::function

将lambda包装在std::function中,并显式实例化它。 (它确实会有额外的性能损失来动态分配内存,但这只是调试版本,不要紧)

如果lambda本身不接受任何本地数据类型作为输入或返回一个, 则可以显式实例化整个类。

否则,可以通过在程序中使用operator()来实例化它。

#include<iostream>
#include<functional>

template class std::function<int(int)>; // explicit instantiation

int main(){
   auto a=[](int x){
      std::cout<<x<<'\n';
      return x+1;
   };
   std::function a_function{a};
   a_function(1); // implicit instantiation
   __builtin_trap();
}

在gdb中调用a_function.operator()(1)

参考:

方法1.2:存储lambda的地址

程序:

#include<iostream>

int main(){
   auto a=[](int x){
      std::cout<<x<<'\n';
      return x+1;
   };
   auto a_operator_call=&decltype(a)::operator();
   __builtin_trap();
}

用如下方式调用:(需要适当的编译选项,以确保a_operator_call的值不会被优化掉)

(gdb) print (a.*a_operator_call)(1)
1
$1 = 2

参考:

情况二:不允许修改源代码时

情况二点一:全局 lambda

#include<iostream>

auto a=[](int x){
   std::cout<<x<<'\n';
   return x+1;
};

int main(){
   __builtin_trap();
}

你需要禁用xmethod(至少在我的当前gdb版本中。我认为这是一个bug),并使用语法a.operator()(...)进行调用:

(gdb) print a
$1 = {<No data fields>}

(gdb) print a(1)
Invalid data type for function to be called.

(gdb) print a.operator()(1)
Python Exception <class 'TypeError'> expected string or bytes-like object:
Error while looking for matching xmethod workers defined in Python.

(gdb) disable xmethod

(gdb) print a.operator()(1)
1
$2 = 2

参考:

案例2.2. 本地lambda函数

注意:对于这种方法,可能需要使用-fkeep-inline-functions编译。

考虑一个简单的程序

#include<iostream>

int main(){
   auto a=[](int x){
      std::cout<<x<<'\n';
      return x+1;
   };
   __builtin_trap();
}

看看在可执行文件中生成了哪些符号:

(gdb) shell nm --demangle ./a.out |grep lambda
000000000001fe4c t main::{lambda(int)#1}::operator()(int) const
000000000001ff5c t main::{lambda(int)#1}::operator int (*)(int)() const
000000000001ff40 t main::{lambda(int)#1}::_FUN(int)

(gdb) print 'main::{lambda(int)#1}::operator()(int) const'
$1 = {int (const struct {...} * const, int)} 0x555555573e4c <operator()(int) const>

(gdb) print 'main::{lambda(int)#1}::operator()(int) const' (a, 1)
No symbol "(null)" in current context.

(gdb) set $c='main::{lambda(int)#1}::operator()(int) const'

(gdb) print (a.*$c)(1)
Non-pointer-to-member value used in pointer-to-member construct

(gdb) print $c(&a, 1)
1
$2 = 2

您可以看到,该符号具有函数指针类型,而不是成员函数指针类型,因此必须使用正常语法调用它(尽管如上面的方法1.2所示,gdb确实支持函数到成员的解引用)。

可以通过Python程序等方式自动解析带有lambda的符号的名称,并使用maintenance print type命令获取其类型。在调用lambda时,可以使用maintenance print type命令获取lambda对象的实际类型(ptype单独使用不足以确定类型,因为某些不同的类型具有相同的字符串表示形式,在gdb中被视为相等,例如下面的示例)。

  • 然后将这些类型进行匹配,以确定正确的符号。

尝试失败

  • 使用gdb:

    (gdb) print a
    $1 = {<没有数据字段>}
    
    (gdb) ptype a
    type = 结构体 {
    }
    
    (gdb) ptype $c
    type = int (*)(const 结构体 {...} * const, int)
    
  • 使用Python API:

    不幸的是,由于某种原因,它认为具有相同定义的不同类型是相等的。

    例如,在此程序中:

    #include<iostream>
    
    int main(){
        auto a=[](int x){ std::cout<<x<<'\n'; return x+1; };
        auto b=[](int x){ std::cout<<x<<'\n'; return x+2; };
        struct{ int x; } c;
        struct{ int x; } d;
        struct{ int y; } e;
        __builtin_trap();
    }
    

    在嵌入在gdb中的IPython交互式shell中:

    In [1]: gdb.parse_and_eval("a").type == gdb.parse_and_eval("b").type
    Out[38]: True
    
    In [39]: gdb.parse_and_eval("c").type == gdb.parse_and_eval("d").type
    Out[39]: True
    
    In [40]: gdb.parse_and_eval("c").type == gdb.parse_and_eval("e").type
    Out[40]: False
    

可以尝试的事情(虽然在这种情况下不是必要的)

  • -fno-inline-functions
  • -fkeep-inline-functions
  • -gdwarf-5
  • -g3
  • __attribute__((used))
  • -fno-eliminate-unused-debug-types
  • -fkeep-static-functions
  • -fkeep-static-consts

参考:


我基于此制作了一个Python包(gdb-call-lambda)。 - user202729

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接