【问题标题】:GDB/DDD: Debug shared library with multi-process application C/C++GDB/DDD:使用多进程应用程序 C/C++ 调试共享库
【发布时间】:2014-09-12 16:13:40
【问题描述】:

我正在尝试调试服务器应用程序,但我在需要时遇到了一些困难。该应用程序分为两部分:

  • 一个服务器应用程序,它产生工作进程(不是线程)来处理传入的请求。服务器基本上会产生进程,这些进程将处理传入的请求,先到先得。
  • 服务器还以共享库的形式加载插件。共享库定义了服务器能够处理的大部分服务,因此大部分实际处理都在这里完成。

作为一个额外的乐趣,工作进程“重生”(即退出并产生一个新的工作进程),因此子进程的 PID 会定期更改。 -_-'

基本上我需要调试在共享库中调用的服务,但我不知道要提前附加到哪个进程,因为它们会临时获取请求。到目前为止,附加到主进程并设置断点似乎没有用。

有没有一种方法可以调试这个共享库代码而无需提前附加到进程?基本上我想调试调用相关函数的第一个进程。

目前我可能会尝试将工作进程的数量限制为 1 且不重生,但最好知道将来如何处理这样的场景,尤其是如果我想确保它在“发布”配置中仍然有效。

我正在尝试使用 DDD 和 GDB 进行调试的 Linux 平台上运行。

编辑:为了帮助说明我想要完成的工作,让我提供一个简短的概念证明。

#include <iostream>
#include <stdlib.h>
#include <unistd.h>

using namespace std;

int important_function( const int child_id )
{
    cout << "IMPORTANT(" << child_id << ")" << endl;
}

void child_task( const int child_id )
{
    const int delay = 10 - child_id;
    cout << "Child " << child_id << " started. Waiting " << delay << " seconds..." << endl;
    sleep(delay);
    important_function(child_id);
    exit(0);
}

int main( void )
{
    const int children = 10;
    for (int i = 0; i < 10; ++i)
    {
        pid_t pid = fork();
        if (pid < 0) cout << "Fork " << i << "failed." << endl;
        else if (pid == 0) child_task(i);
    }

    sleep(10);
    return 0;
}

这个程序将派生 10 个进程,这些进程将在调用 important_function 之前全部休眠 10 - id 秒,这是我想在第一个调用子进程中调试的函数(这里应该是我派生的最后一个)。

将 follow-fork-mode 设置为 child 将让我跟随第一个孩子分叉,这不是我想要的。我正在寻找第一个调用重要函数的孩子。

关闭 detach-on-fork 并没有帮助,因为它会暂停父进程,直到子进程 fork 退出,然后再继续 fork 其他进程(一次一个,在最后一个退出之后)。

在实际场景中,我能够附加到已经产生线程的已经运行的服务器应用程序上,并在第一个调用该函数的应用程序上停止,这一点也很重要。

我不确定这是否可行,因为我没有看到太多关于它的文档。基本上我想调试第一个应用程序来调用这行代码,不管它来自什么进程。 (虽然只有我的应用程序进程会调用代码,但似乎我的问题可能更普遍:附加到调用代码的第一个进程,无论其来源如何)。

【问题讨论】:

    标签: c++ c gdb multiprocessing


    【解决方案1】:

    您可以在 fork() 处设置断点,然后发出“继续”命令,直到主进程的下一步是生成要调试的子进程。此时,在要调试的函数处设置断点,然后向 gdb 发出“set follow-fork-mode child”命令。当您继续时,gdb 应该将您挂接到断点所在函数处的子进程。

    如果您发出命令“set detach-on-fork off”,gdb 将继续调试子进程。到达库中断点的进程应该在到达该断点时停止。问题是,当 detach-on-fork 关闭时,gdb 会停止所有在启动时分叉的子进程。我不知道如何告诉它在分叉后继续执行这些进程。

    我认为解决这个问题的方法是写一个gdb script 来切换到每个进程并发出一个继续命令。使用断点命中函数的进程应该停止。

    一位同事为让每个孩子继续学习的问题提供了另一种解决方案。您可以保留“detach-on-fork”,在每个子进程的入口点插入一条打印语句,打印出它的进程 ID,然后给它一个语句告诉它等待变量的更改,如下所示:

    {
        volatile int foo = 1;
        printf("execute \"gdb -p %u\" in a new terminal\n", (unsigned)getpid());
        printf("once GDB is loaded, give it the following commands:\n");
        printf("    set variable foo = 0\n");
        printf("    c\n");
        while (foo == 1) __asm__ __volatile__ ("":::"memory");
    }
    

    然后,启动 gdb,启动主进程,并将输出通过管道传输到文件。使用 bash 脚本,您可以读取子进程的进程 ID,启动多个 gdb 实例,将每个实例附加到不同的子进程之一,并通过清除变量“foo”来指示每个实例继续。

    【讨论】:

    • 这个问题是我不知道哪个孩子会接受和处理服务器请求。这不是以确定的方式完成的;相反,进程等待传入连接,然后尝试接受它,因此空闲进程中哪一个得到它取决于请求进来时谁有幸运行。
    • 好吧, detach-on-fork 关闭的问题在于 follow-fork-mode 子进程似乎会停止父进程,直到子进程停止。因此,作为一个简单的概念证明,我编写了一个应用程序,它分叉出 10 个子进程,每个子进程在唤醒并调用我想要调试的函数之前休眠 10-id 秒(因此最后一个分叉的线程(通常)会首先到达那里。启用分离后,我将仅在第一个生成的线程上进行调试。关闭它后,我将不得不依次调试所有线程,这也不是我所需要的。
    • 我建议关闭 follow-fork-mode,并关闭 detach-on-fork。这应该继续父进程并在所有子进程启动后立即停止。要让所有子进程继续运行,您可以切换到每个子进程并为每个子进程发出“继续”命令。目前我知道避免必须为每个进程手动执行此操作的唯一方法是编写一个 gdb 脚本来执行此操作,正如我在上面的回答中所建议的那样。
    • 为了使其有用,它不需要为了调试目的而更改代码。我希望能够停止正确的进程,而无需 a) 添加额外的调试代码(如提供的代码),b) 必须在每个进程上继续(即在附加时停止进程然后继续,在一定程度上改变应用程序的操作),或 c) 要求我必须使用 gdb 启动我的主应用程序(例如,调试已经运行的服务器)。我希望任何进程的共享库代码中都会出现更多的通用暂停,但这看起来不太可能。 ://
    • 虽然修改代码以打印出 PID 不起作用,但制作一个脚本来定期查看进程列表并附加 GDB 实例会起作用。但是我没有实现这一点。最适合我的调试方法是启动一个用于调试目的的工作线程。但从理论上讲,您的建议应该可以编排。
    猜你喜欢
    • 2011-06-29
    • 2011-09-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-11
    • 1970-01-01
    • 2023-04-04
    相关资源
    最近更新 更多