我有一个Python脚本,它启动几个外部程序作为子进程并使用管道与它们通信。该系统运行在Linux上。我想调试其中一个子进程,一个特定的可执行程序。如何将 gdb 附加到它?我尝试了多种方法,但没有任何效果。
set follow-fork-mode child
在 gdb 下运行 python。不起作用,因为这不是唯一的孩子。第一个孩子被调试,而不是我想要的。set detach-on-fork off
在 gdb 下运行 python。不可行,因为这不是唯一的孩子。我需要手动告诉 gdb 继续调试父进程,直到启动感兴趣的程序。理论上可行,但是手工操作太多容易出错。如果所有这些手动工作都可以自动化,这是一个可行的方法。我怎样才能做到这一点?
我怎样才能做到这一点?
您可以使用
gdb -p $pid
附加进程,而无需从 GDB 启动它。您可能需要禁用 YAMA 才能正常工作。
如果您的进程在有机会附加之前崩溃了,此答案显示了调试此类进程的一种方法。
更新:
不幸的是,插入代码并重建程序会改变行为。
在这种情况下,
mv $exe $exe.real
,并在其位置放置一个包装器可执行文件。包装纸应该是这样的:
#include <unistd.h>
int main(int argc, char *argv[])
{
char argv0[PATH_MAX];
volatile int spin = 1;
strcpy(argv0, argv[0]);
strcat(argv0, ".real");
while (spin) sleep(1);
execvp(argv0, argv); // Should not return
abort(); // Unreachable
}
将GDB附加到包装器,设置
spin = 0
并继续调试$exe.real
。
为了解决这个问题,我在traceexec 0.4.0中添加了调试器启动器功能:https://github.com/kxxt/tracexec/releases/tag/v0.4.0
基本上,此解决方案会在 execve{,at} 系统调用的系统调用退出停止后停止进程,并将 gdb 附加到它们。
假设我们有以下两个简单的程序 a 和 b:
fn main() {
println!("Hello from A!");
}
use std::io::stdin;
use std::io::Read;
fn main() {
println!("Hello from B!");
let mut input = String::new();
stdin().read_to_string(&mut input).unwrap();
println!("Stdin: {}", input);
}
以及执行它们的脚本。 (我这里使用的是shell脚本,但是这里使用python/perl脚本也没关系。)
./a --complex-arguments-here | ./b --other --complex --arguments --here
在安装了display和konsole的Linux系统上,以下命令使用shell脚本启动tracexec tui:
tracexec tui -t -b sysexit:in-filename:/a -b sysexit:in-filename:/b --default-external-command "konsole -e gdb -ex cont -ex cont -p {{PID}}" -- ./shell-script
然后在 TUI 中,gdb 可以附加到命中管理器中的 a 和 b。
如果上面的文字不清楚,可以在这里观看视频:https://github.com/kxxt/tracexec/assets/18085551/72c755a5-0f2f-4bf9-beb9-98c8d6b5e5fd
完整代码可在 https://github.com/kxxt/tracexec/tree/main/demonstration/gdb-launcher