10

I am currently trying to write (Python 2.7.3) kind of a wrapper for GDB, which will allow me to dynamically switch from scripted input to interactive communication with GDB.

So far I use

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

to start gdb within my script. (vuln is the binary I want to examine)

Since a key feature of gdb is to pause the execution of the attached process and allow the user to inspect registers and memory on receiving SIGINT (STRG+C) I do need some way to pass a SIGINT signal to it.

Neither

self.process.send_signal(signal.SIGINT)

nor

os.kill(self.process.pid, signal.SIGINT)

or

os.killpg(self.process.pid, signal.SIGINT)

work for me.

When I use one of these functions there is no response. I suppose this problem arises from the use of shell=True. However, at this point I am really out of ideas. Even my old friend Google couldn't really help me out this time, so maybe you can help me. Thank's in advance.

Cheers, Mike

Mike
  • 111
  • 1
  • 1
  • 6
  • 1
    A target process created by gdb will be in a new pgrp, so sending SIGINT to gdb or to gdb's pgrp won't work. You could use python's pty package and send `^C` to it, I guess, but it sounds like you may want to use gdb's async mode, in which the target runs while gdb still accepts commands, and the gdb `interrupt` command will pause the target. If you describe your use case a bit more, we can offer suggestions. – Mark Plotnick Jun 18 '15 at 21:19
  • Thanks for your answer, however running in async mode is not an option for me. Basically what I finally want to achieve is, to have a loop which reads lines from console and writes it to the stdin of self.process until a certain string (e.g. "quit") is read. While in this loop the program should catch STR+C (=SIGINT) if pressed by the user and pass it to gdb, in order to achieve the same behaviour as if gdb was run regularly. – Mike Jun 19 '15 at 13:53

4 Answers4

6

Here is what worked for me:

import signal
import subprocess

try:
    p = subprocess.Popen(...)
    p.wait()
except KeyboardInterrupt:
    p.send_signal(signal.SIGINT)
    p.wait()
sirex
  • 4,593
  • 2
  • 32
  • 21
1

I looked deeper into the problem and found some interesting things. Maybe these findings will help someone in the future.

When calling gdb vuln using suprocess.Popen() it does in fact create three processes, where the pid returned is the one of 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

Consequently sending a SIGINT to the process will in fact send SIGINT to sh.

Besides, I continued looking for an answer and stumbled upon this post https://bugzilla.kernel.org/show_bug.cgi?id=9039

To keep it short, what is mentioned there is the following:

When pressing STRG+C while using gdb regularly SIGINT is in fact sent to the examined program (in this case vuln), then ptrace will intercept it and pass it to gdb. What this means is, that if I use self.process.send_signal(signal.SIGINT) it will in fact never reach gdb this way.

Temporary Workaround:

I managed to work around this problem by simply calling subprocess.popen() as follows:

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

This is nothing more than a first workaround. When multiple applications with the same name are running might do some serious damage. Besides, it somehow fails, if shell=True is not set. If someone has a better fix (e.g. how to get the pid of the process startet by gdb), please let me know.

Cheers, Mike

EDIT:

Thanks to Mark for pointing out to look at the ppid of the process. I managed to narrow down the process's to which SIGINT is sent using the following approach:

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)
Mike
  • 111
  • 1
  • 1
  • 6
  • You quoted from bugzilla.kernel.org. Are you running Linux? You can look at the `status` files under `/proc` to find the target process, examining the `Name` and `Ppid` entries to find a process with the desired app name and grandparent == self.process.pid. – Mark Plotnick Jun 19 '15 at 19:50
0

I have done something like the following in the past and if I recollect it seemed to work for me :

def detach_procesGroup():
    os.setpgrp()

subprocess.Popen(command,
                 stdout=subprocess.PIPE,
                 stderr=subprocess.PIPE,
                 preexec_fn=detach_processGroup)
Ram K
  • 1,746
  • 2
  • 14
  • 23
  • Thanks for your quick answer. However it didn't work for me :( – Mike Jun 19 '15 at 13:57
  • the effect is the opposite: it prevents propagating `Ctrl+C` from console to the child process. And it has no effect on the grandchild process started by `gdb` (`gdb` may create yet another process group). – jfs Jun 21 '15 at 11:29
0

A modification of @sirex answer, which worked better for me:

import signal
import subprocess

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

The original code would abort after the 2nd ctrl-C.

MilesF
  • 142
  • 1
  • 8