当使用多线程python扩展来调试多线程程序

时间:2015-11-02 16:55:37

标签: multithreading gdb gdb-python

我正在尝试开发一个GDB python扩展,它定义了一个启动新线程的命令,用户可以在其中检查任意类型的变量。我的python扩展的骨架是这样的:

import gdb
import threading

def plot_thread():
    import time
    while True:
        print('Placeholder for a window event loop.')
        time.sleep(1)
        pass
    pass

class PlotterCommand(gdb.Command):
    def __init__(self):
        super(PlotterCommand, self).__init__("plot",
                                            gdb.COMMAND_DATA,
                                            gdb.COMPLETE_SYMBOL)
        self.dont_repeat()
        pass

    def invoke(self, arg, from_tty):
        plot_thread_instance=threading.Thread(target=plot_thread)
        plot_thread_instance.daemon=True
        plot_thread_instance.start()
        pass

    pass

PlotterCommand()

可以看出,我在这里定义了一个 plot 命令。当我尝试调试以下程序时,如果我:

,GDB将挂起
  1. 在procedure()线程内的任何位置放置一个断点(比如第9行,在while循环中)。
  2. gdb命中断点后运行命令 plot
  3. 之后运行继续
  4. #include <iostream>
    #include <thread>
    
    using namespace std;
    
    void procedure() {
        cout << "before loop"<<endl;
        while(1) {
            cout << "loop iteration"<<endl;
        }
    }
    
    int main() {
        thread t(procedure);
        t.join();
        return 0;
    }
    

    最奇怪的是,如果我更改此代码以在不启动线程的情况下调用 procedure(),GDB永远不会挂起(并且占位符消息仍然按照我的预期打印)。

    到目前为止,我已尝试使用GDB版本7.5.1和7.10运行此过程,但我总是遇到相同的行为。

    我做错了什么? GDB不支持守护程序线程吗?这似乎与section 23.2.2.1 of the documentation建议的不一致:GDB可能不是线程安全的,但我不认为它应该在启动这样一个愚蠢的守护程序线程后挂起。

1 个答案:

答案 0 :(得分:2)

来自this blog post

  

GDB使用此函数(sigsuspend,GDB挂起的函数)等待来自应用程序执行的新事件:当调试对象出现问题时(请参阅调试器如何工作),内核将通知通过发送SIGCHLD信号的GDB。收到后,GDB会醒来并检查发生了什么。

     

但是,信号传递给GDB进程,但不一定传递给它的主线程。实践中,它经常发送到第二个线程,它不关心它(这是默认行为),并且继续其生命,好像什么也没发生。

解决方案是配置线程信号处理行为,以便只有GDB主线程通过这些信号得到通知:

import gdb
import threading
import pysigset, signal # Import these packages!

def plot_thread():
    import time
    while True:
        print('Placeholder for a window event loop.')
        time.sleep(1)
        pass
    pass

class PlotterCommand(gdb.Command):
    def __init__(self):
        super(PlotterCommand, self).__init__("plot",
                                            gdb.COMMAND_DATA,
                                            gdb.COMPLETE_SYMBOL)
        self.dont_repeat()
        pass

    def invoke(self, arg, from_tty):
        with pysigset.suspended_signals(signal.SIGCHLD): # Disable signals here!
            plot_thread_instance=threading.Thread(target=plot_thread)
            plot_thread_instance.daemon=True
            plot_thread_instance.start()
            pass
        pass

    pass

PlotterCommand()

pysigset 包是必需的,可以从pip安装(sudo pip install pysigset)。