【问题标题】:Add functions in gdb at runtime在运行时在 gdb 中添加函数
【发布时间】:2010-04-09 02:14:44
【问题描述】:

我正在尝试在 gdb 中调试一些基于 STL 的 C++ 代码。 代码有类似

int myfunc()
{
   std::map<int,int> m;
   ...
}

现在在 gdb 中,在 myfunc 中使用“print m”给出了一些非常难看的东西。 我所看到的推荐是编译类似

void printmap( std::map<int,int> m )
{
   for( std::map<int,int>::iterator it = ... )
   {
      printf("%d : %d", it->first, it->second );
   }
}

然后在gdb中做

(gdb) call printmap( m )

这似乎是处理问题的好方法...但是我可以将 printmap 放入一个单独的目标文件(甚至是动态库)中,然后在运行时将其加载到 gdb 中,而不是将其编译到我的二进制文件中 - 作为重新编译每次我想查看另一个 STL 变量时,二进制文件并不好玩.. 而为打印例程编译和加载单个 .o 文件可能是可以接受的。


更新:

在 Nikolais 的建议下,我正在查看 dlopen/dlsym。

所以我还没有完成这项工作,但感觉离我越来越近了。

在 printit.cpp 中

#include <stdio.h>

extern "C" void printit()
{
  printf("OMG Fuzzies");
}

使用

编译成 .so
g++ -Wall -g -fPIC -c printit.cpp
g++ -shared -Wl,-undefined,dynamic_lookup -o printit.so printit.o

启动我的测试应用程序并使用 dlopen ( 2 = RTLD_NOW ) 加载 .so,然后尝试使用 dlsym 获取调试功能的符号。

(gdb) break main
(gdb) run
(gdb) print (void*) dlopen("printit.so", 2 )
$1 = (void *) 0x100270
(gdb) print (void*) dlsym( 0x100270, "_printit" )
$2 = (void *) 0x0

如此接近,但由于某种原因我无法得到那个符号......(如果我把 dlopen/dlsym 在我的可执行文件中调用)我猜我要么编译了错误的库,要么错误地使用了 dlsym。

如果我能得到符号,我假设我可以使用类似的方法调用函数

(gdb) print (( void(*)() )(0x....))()

我正在 OS X 10.4 上编译它,这可能会导致我的一些 .so 问题...任何指针将不胜感激。


了解如何让所有这些工作。已在下面发布为解决方案。

【问题讨论】:

标签: c++ gdb


【解决方案1】:

所以我的解决方案是使用dlopen 在运行时加载包含我的调试例程的共享对象。事实证明,当您正确设置所有编译标志时,它比我想象的还要简单。

在 OS X 上,这意味着您可以像这样编译您的应用程序和调试对象:

all : application.x debug_helper.so

application.x : application.cpp
    g++ -g application.cpp -o application.x -fPIC

debug_helper.so : debug_helper.o
    g++ -dynamiclib -o debug_helper.so debug_helper.o

debug_helper.o : debug_helper.cpp
    g++ -Wall -g -fPIC -c debug_helper.cpp

应用程序上的-fPIC 至关重要,-dynamiclib 也是如此(而不是尝试使用 linux 的 -shared 标志)

debug_helper.cpp 的示例可能如下所示

#include <map>
#include <stdio.h>

extern "C" 
void printMap( const std::map<int,int> &m )
{
  printf("Map of size %d\n", int(m.size()) );
  for( std::map<int,int>::const_iterator it = m.begin(); it!=m.end(); ++it )
  {
    printf("%d : %d \n", it->first, it->second );
  }
  fflush(stdout);
}

不知道为什么我选择使用 stdio 而不是 iostream 的东西......我想你可以使用任何一个。 (只是不要忘记刷新流...)

现在我的应用程序文件如下所示:

#include <map>

int main()
{
  std::map<int,int> m;
  m[1]=2;
  m[2]=5;
  m[3]=10;
  m[4]=17;
}

这是一个调试会话示例(删除了一些输出)

启动应用程序并在一个有趣的点中断

(gdb) break main
(gdb) run
Reading symbols for shared libraries +++. done   
Breakpoint 1, main () at test.cpp:5
5     std::map<int,int> m;

在调试助手库中加​​载

(gdb) print (void*) dlopen("./debug_helper.so",2)
Reading symbols for shared libraries . done
$1 = (void *) 0x100270
(gdb) n
6     m[1]=2;

GDB 很聪明,可以为我们捕获所有新符号,因此我们不需要使用dlsym 等。 我们可以直接调用函数。

(gdb) call printMap(m)
Map of size 0
(gdb) n
(gdb) n
(gdb) n
9     m[4]=17;
(gdb) call printMap(m)
Map of size 3
1 : 2 
2 : 5 
3 : 10 

让我们向 printMap 添加更多信息。 首先,卸载库。

(gdb) print (int) dlclose($1)
$2 = 0

编辑源以添加条目总和。重新编译然后加载 将新库重新放入 gdb(无需重新启动可执行文件或 gdb)

(gdb) print (void*) dlopen("./debug_helper.so",2)
Reading symbols for shared libraries . done
$3 = (void *) 0x100270

使用修改后的函数

(gdb) call printMap(m)
Map of size 3
1 : 2 
2 : 5 
3 : 10 
SUM = 17

我认为这可以满足我的所有需求。

【讨论】:

  • 请注意,如果 dlopen 结果是 NULL 指针,则可能需要将路径提供为 ./debug_helper.so(来自 dlopen 手册页:如果文件名包含斜杠(“/ "),然后将其解释为(相对或绝对)路径名。否则,[...])
  • @user202729 很好,我将更改路径以使用“./”。
【解决方案2】:

据我所知,您所要求的并不是直接可能的。不过有一个接近的选择(谁说要多一层间接性?:)

使用您的所有打印机例程构建一个单独的动态库,然后将延迟加载打印包装器添加到您的程序中。我的意思是这样的:

/// this is in your program, lazy print wrapper
void print_map( const std::map<int,int>& m ) // NOTE THE CONST REFERENCE
{
    static bool loaded = false;
    static void* print_lib = 0;
    static void (*print_func_ptr)( const std::map<int,int>& ) = 0;

    if ( !loaded )
    {
        // dlopen dynamic lib, check for errors, assign to print_lib
        // dlsym the library function by name, assign to print_func_ptr
        loaded = true;
    }

    print_func_ptr( m );
}

然后您可以在 gdb 会话中调用 print_map 并且库将自动加载。 请注意,上面的代码通过 const 引用 接受地图。您在问题中提出的函数会生成其参数的副本。

还可以查看here,了解一些使 gdb 为 STL 容器产生更好输出的方法。

【讨论】:

  • 是的,我并不关心调用是否复制了参数。但是这个解决方案对我来说仍然不切实际,因为如果我想查看另一个 STL 类,我需要重新编译我的二进制文件并重新启动 gdb。但是,您可能会使用 dlopen。我会进一步调查。
  • 哦,您现在想要一个解决方案,甚至不用离开 gdb 控制台?现在的孩子们...... :)
【解决方案3】:

我建议看看这里:http://sourceware.org/gdb/wiki/STLSupport

有几种不同的方式来显示 STL 容器(我们已经为您完成了相关工作)。任何选项都需要您在配置后重新启动 gdb,但以后使用会很好。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-07-07
    • 1970-01-01
    • 2017-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-05
    • 1970-01-01
    相关资源
    最近更新 更多