如何让GDB/Python脚本通过管道控制调试目标的输入输出
问题:GDB Python脚本中通过管道驱动目标输入的阻塞问题
我希望在GDB中运行Python脚本,同时通过管道驱动调试目标的输入,但始终无法协调好目标执行与管道设置的顺序,频繁出现阻塞或错误。我尝试过命名管道的阻塞与非阻塞打开方案,以及PTY方案——手动操作GDB时这两种方案都能正常工作,但通过GDB Python API运行时全部失效,ps显示调试目标处于被调试器暂停的状态('t'状态)。
尝试1:命名管道(非阻塞)方案
import os # create input/output pipes in_pipe = "in" out_pipe = "out" try: os.unlink(in_pipe) except FileNotFoundError: pass try: os.unlink(out_pipe) except FileNotFoundError: pass os.mkfifo(in_pipe) os.system(f"sleep infinity > {in_pipe} &") print("in pipe:", in_pipe) os.mkfifo(out_pipe) os.system(f"sleep infinity > {out_pipe} &") print("out pipe:", out_pipe) # open in/out pipes to prevent starti command from blocking # open a tmp read-only FD for the input pipe to prevent it's write-only open from blocking out_fd = os.open(out_pipe, os.O_RDONLY | os.O_NONBLOCK) tmp_fd = os.open(in_pipe, os.O_RDONLY | os.O_NONBLOCK) # prevent blocking in_fd = os.open(in_pipe, os.O_WRONLY | os.O_NONBLOCK) gdb.execute(f"set target-async on") gdb.execute(f"starti < {in_pipe} > {out_pipe}") gdb.execute(f"c&") print(os.read(out_fd, 8)) # causes BlockingIOError for some reason, even if I ensure enough time for the target to print print("--- DONE ---")
尝试2:PTY方案
import os import pty master, slave = pty.openpty() tty_name = os.ttyname(slave) gdb.execute(f"set inferior-tty {tty_name}") gdb.execute(f"set target-async on") gdb.execute(f"r&") print("1") #os.write(master, b"1\n") print(os.read(master, 8)) # blocks??? print("2") print("--- DONE ---")
可行解决办法
核心问题分析
GDB异步模式下,c&或r&仅让GDB后台执行,但Python主线程若直接进行IO操作,会与GDB事件循环冲突,导致目标进程被暂停。此外,非阻塞IO需配合GDB事件回调处理,不能直接同步调用os.read。
方案1:基于GDB事件回调的命名管道处理
修改命名管道代码,利用GDB的ContinueEvent回调,在目标进程运行后通过select监听管道事件,避免阻塞:
import os import select # 创建并初始化管道 in_pipe = "in" out_pipe = "out" for pipe in [in_pipe, out_pipe]: try: os.unlink(pipe) except FileNotFoundError: pass os.mkfifo(pipe) # 用cat保持管道打开,避免目标进程阻塞 os.system(f"cat > {pipe} &") # 打开管道并设置非阻塞 out_fd = os.open(out_pipe, os.O_RDONLY | os.O_NONBLOCK) in_fd = os.open(in_pipe, os.O_WRONLY | os.O_NONBLOCK) # 启用GDB异步模式 gdb.execute("set target-async on") # 定义回调函数:目标进程继续运行后处理输出 def handle_target_output(event): if isinstance(event, gdb.ContinueEvent): # 使用select等待管道有数据,超时5秒 rlist, _, _ = select.select([out_fd], [], [], 5) if rlist: try: data = os.read(out_fd, 1024) if data: print("目标输出:", data.decode()) except BlockingIOError: pass # 注册事件回调 gdb.events.cont.connect(handle_target_output) # 启动目标并继续执行 gdb.execute(f"starti < {in_pipe} > {out_pipe}") gdb.execute("c")
方案2:PTY方案的修正(结合GDB事件循环)
PTY方案的问题是直接调用os.read会阻塞Python线程,导致GDB无法处理目标事件。需通过gdb.post_event将IO操作放入GDB事件循环处理:
import os import pty import select master, slave = pty.openpty() tty_name = os.ttyname(slave) # 配置GDB gdb.execute(f"set inferior-tty {tty_name}") gdb.execute("set target-async on") gdb.execute("set pagination off") # 定义IO处理函数,通过GDB事件循环调度 def process_pty_io(): # 监听PTY主设备,超时2秒 rlist, _, _ = select.select([master], [], [], 2) if rlist: try: data = os.read(master, 1024) print("目标输出:", data.decode()) except BlockingIOError: pass # 调度下一次IO检查 gdb.post_event(process_pty_io) # 目标启动后开始处理IO def on_target_start(event): gdb.post_event(process_pty_io) gdb.events.cont.connect(on_target_start) # 启动目标进程 gdb.execute("r&") # 让GDB进入事件循环 gdb.execute("c")
关键注意事项
- 必须保证GDB事件循环正常运行,禁止Python主线程阻塞在IO操作上,否则目标进程会被暂停。
- 用
gdb.post_event将IO操作放入GDB事件队列,由GDB主线程处理,避免线程冲突。 - 命名管道需用后台进程(如
cat)保持打开,防止目标进程打开管道时阻塞。
内容的提问来源于stack exchange,提问作者r4dr3fr4d
相关产品推荐
相关产品推荐

