如何使用Python subprocess.Popen()将SIGINT传递给子进程,使用shell=true

10

我目前正在尝试编写(Python 2.7.3)一个GDB包装器,它将允许我在脚本输入和与GDB进行交互式通信之间动态切换。

到目前为止,我使用了

self.process = subprocess.Popen(["gdb vuln"], stdin = subprocess.PIPE,  shell = True)

在我的脚本中启动gdb。(vuln 是我想要检查的二进制文件)

由于gdb的一个关键特性是在接收到SIGINT(STRG+C)时暂停附加进程的执行,允许用户检查寄存器和内存,因此我需要某种方法向其传递SIGINT信号。

两个命令都不能通过管道或重定向来发送信号:

self.process.send_signal(signal.SIGINT)

无或者不

os.kill(self.process.pid, signal.SIGINT)
或者
os.killpg(self.process.pid, signal.SIGINT)

为我工作。

当我使用这些函数之一时没有响应。我认为这个问题来自于使用shell=True。然而,此时我真的没有什么主意了。即使是我的老朋友Google这次也无法真正帮助我,所以也许你可以帮助我。提前感谢。 问候,迈克


1
由gdb创建的目标进程将位于新的pgrp中,因此向gdb或gdb的pgrp发送SIGINT将无效。我猜你可以使用Python的pty包并发送^C,但这听起来像是你可能想要使用gdb的异步模式,在该模式下,目标在gdb仍然接受命令的情况下运行,而gdb的interrupt命令将暂停目标。如果您稍微描述一下您的用例,我们可以提供建议。 - Mark Plotnick
谢谢你的回答,但对我来说,异步模式不是一个选择。基本上,我最终想要实现的是,有一个循环从控制台读取行,并将其写入self.process的标准输入,直到读取到某个特定的字符串(例如"quit")为止。 在这个循环中,如果用户按下STR+C(=SIGINT),程序应该捕获并传递给gdb,以达到与正常运行gdb相同的行为。 - Mike
4个回答

6
这是我所采用的方法:

这里是我所采用的方法:

import signal
import subprocess

try:
    p = subprocess.Popen(...)
    p.wait()
except KeyboardInterrupt:
    p.send_signal(signal.SIGINT)
    p.wait()

1

我深入研究了这个问题,并发现了一些有趣的事情。也许这些发现将来会帮助某些人。

当使用suprocess.Popen()调用gdb vuln时,它确实创建了三个进程,其中返回的pid是sh的进程号(5180)。

ps -a
 5180 pts/0    00:00:00 sh
 5181 pts/0    00:00:00 gdb
 5183 pts/0    00:00:00 vuln

因此,向该进程发送SIGINT实际上会将SIGINT发送给sh
此外,我继续寻找答案,并偶然发现了这篇文章https://bugzilla.kernel.org/show_bug.cgi?id=9039
简而言之,其中提到的是:
在正常使用gdb时按下STRG+C时,实际上会将SIGINT发送到被检查的程序(在本例中是vuln),然后ptrace将拦截它并将其传递给gdb。 这意味着,如果我使用self.process.send_signal(signal.SIGINT),则实际上永远不会以这种方式到达gdb。
临时解决方法:
我成功地通过以下方式解决了这个问题:subprocess.Popen("killall -s INT " + self.binary, shell = True)
这只是一个第一次的解决方法。当运行多个具有相同名称的应用程序时可能会造成严重的损坏。此外,如果未设置shell=True,它会以某种方式失败。 如果有更好的修复方法(例如如何获取由gdb启动的进程的pid),请告诉我。
谢谢,Mike 编辑: 感谢Mark指出查看进程的ppid。 我成功地通过以下方法缩小了发送SIGINT的进程:
out = subprocess.check_output(['ps', '-Aefj'])
for line in out.splitlines():
    if self.binary in line:
        l = line.split(" ")
        while "" in l:
            l.remove("")
        # Get sid and pgid of child process (/bin/sh)
        sid = os.getsid(self.process.pid)
        pgid  = os.getpgid(self.process.pid)
        #only true for target process
        if l[4] == str(sid) and l[3] != str(pgid):
            os.kill(pid, signal.SIGINT)

你引用了bugzilla.kernel.org上的内容。你正在运行Linux吗?你可以查看/proc目录下的status文件,以找到目标进程,并检查NamePpid条目,以找到具有所需应用程序名称和祖父进程==self.process.pid的进程。 - Mark Plotnick

0
一个修改自@sirex答案的方法,对我来说效果更好:
import signal
import subprocess

p = subprocess.Popen(...)
while True:
    try:
        p.wait()
        exit(0)
    except KeyboardInterrupt:
        p.send_signal(signal.SIGINT)

原始代码在第二个 ctrl-C 后会中止。


0

过去我做了类似以下这样的事情,如果我回忆起来没错的话,它对我来说似乎是有效的:

def detach_procesGroup():
    os.setpgrp()

subprocess.Popen(command,
                 stdout=subprocess.PIPE,
                 stderr=subprocess.PIPE,
                 preexec_fn=detach_processGroup)

谢谢您的快速回复。然而,它对我没有起作用 :( - Mike
效果相反:它防止从控制台传播 Ctrl+C 到子进程。对于由 gdb 启动的孙子进程没有影响(gdb 可能会创建另一个进程组)。 - jfs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接