我目前正在尝试使用许多不同的模块调试一个非常大的应用程序,其中一些模块是用C编写的,另一些模块是用Python语言编写的。它同时使用多线程和CUDA。它运行在Linux下的现代英特尔处理器上。
目前,我有一个测试用例,它在循环中运行大约一个小时,然后出现Assert错误的分段错误。查看堆栈跟踪,它显示我正在使用sig
的有效值调用g_signal_disconnect(obj, sig)
,但g_signal_disconnect
看到的是sig
的无意义的值。在为呼叫设置的寄存器和实际呼叫之间,似乎发生了一些事情,改变了保存sig
值的%rsi
寄存器。也就是说,调用方的堆栈帧在局部变量和寄存器中显示sig
的正确值,但被调用方看到的是一个很大的随机数。我猜是其他任务正在运行或外部中断发生并导致了问题,但这完全是猜测。
这个错误是一致的,因为它总是这个特定的调用被粉碎,但它只在数千(数十万?)中随机发生一次这一呼叫的执行情况。无论我是在本地运行、在gdb下运行还是在valgrind下运行,似乎都无关紧要。它仍然在发生。
因为它是一个正在更改的寄存器,所以我不能让GDB在它上设置一个观察点来查看是什么在更改它。GDB也不能在多线程环境中反向运行代码。
因为它是一个CUDA应用程序,所以我不能使用rr-DEBUGER来记录导致该问题的确切指令流。
虽然我可以在valgrind下运行该程序并获得一些结果,但它只在我使用它时告诉我sig值未定义,而不是当某些东西使其未定义时告诉我。Valgrind也没有显示任何可能是罪魁祸首的内存或多任务处理错误。
现在,我确实有权完全访问发生错误的模块的源代码,所以我可以检测它,这是有意义的,或者重新编译它,只要那些编译选项与它运行的Linux堆栈的其余部分兼容,所以可能有一些我可以做的事情,但我不知道是什么。
只要找到一些方法来知道在寄存器粉碎窗口期间发生了哪些任务运行和/或中断,就可以很大程度上缩小范围,但我也不知道如何获得这些信息。
有没有人知道什么工具、小贴士、技巧或诸如此类的东西,可以让我当场抓到那个破坏登记器的人?一旦我知道什么是例行公事,就应该有可能修复它。
1条答案
按热度按时间w41d8nur1#
好的,谢谢大家的帮助。为了解决我提出的实际问题,这类事情目前最好由一个可以记录和重放多线程指令流的调试器来解决。RR-Debugger做到了这一点,并且是开源的,但不支持CUDA。Undo UDB是商业性的,部分支持CUDA。目前,在类似的情况下,它是您最好的选择(尽管在我的情况下,它的CUDA支持不足)。这两个都是GDB录音设施的附加组件。
现在,对于最终被发现并修复的实际错误,事实证明它不是注册腐败,而只是看起来像它。事实证明,这是一个数据竞赛问题。我为犯了这个特别的错误而感到尴尬,但事实就是如此。代码的大致释义如下:
事实证明,大约每200,000次调用中就有一次,该信号将在调用to g_Signal_Connect和它的信号id存储在data->sig之间被触发。这将导致从回调中的data->sig中提取的值是随机垃圾,g_ignal_disconnect会(正确地)抱怨这一点。
然而,由于回调位于与Signal_Setup例程不同的线程中,Signal_Setup将在几毫秒后完成,并完成对struct Signal_Data的填充,因此它将是正确的。结果是,当我在调试器中查看堆栈帧时,数据结构具有有效数据,但从该结构读取的寄存器是垃圾。因此,我假设在一个狭窄的窗口中出现寄存器损坏。
直到我为每个信号设置和每个信号回调添加了带时间戳的日志记录,并在设置之前看到一个回调,就在崩溃之前,我才发现真正的错误。