Как передать SIGINT дочернему процессу с помощью Python subprocess.Popen(), используя shell = true

В настоящее время я пытаюсь написать (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 не мог мне помочь на этот раз, так что, может быть, вы можете мне помочь. Заранее спасибо.

Ура, Майк

4 ответа

Вот что сработало для меня:

import signal
import subprocess

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

Я посмотрел глубже в проблему и нашел некоторые интересные вещи. Возможно, эти выводы помогут кому-то в будущем.

При звонке gdb vuln используя suprocess.Popen() он фактически создает три процесса, в которых возвращается 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

Короче говоря, здесь упоминается следующее:

При регулярном использовании STRG+C при использовании GDB SIGINT фактически отправляется в исследуемую программу (в данном случае vuln), тогда ptrace перехватит его и передаст в gdb. Что это значит, что если я использую self.process.send_signal(signal.SIGINT) фактически он никогда не достигнет GDB.

Временное решение:

Мне удалось обойти эту проблему, просто позвонив subprocess.popen() следующее:

subprocess.Popen("killall -s INT " + self.binary, shell = True)

Это не более чем первый обходной путь. Когда запущено несколько приложений с одинаковым именем, это может привести к серьезным повреждениям. Кроме того, это как-то не получается, если shell=True не установлен. Если у кого-то есть лучшее решение (например, как получить pid процесса starttet с помощью gdb), пожалуйста, дайте мне знать.

Ура, Майк

РЕДАКТИРОВАТЬ:

Спасибо Марку за указание посмотреть на 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)

В прошлом я делал что-то вроде следующего, и, если вспомнить, мне показалось, что это сработало:

def detach_procesGroup():
    os.setpgrp()

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

Модификация ответа @sirex, которая мне подошла лучше:

      import signal
import subprocess

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

Исходный код прервется после второгоctrl-C.

Другие вопросы по тегам