0

I want to run GDB with a Python script that also drives the debug target's input, say through a pipe. I cannot seem to get the ordering of the target execution and pipe setup lined up to avoid blocking or an error. I've tried solutions with both blocking and non-blocking opens of the named pipes; a non-blocking example is shared below.

What doesn't help is that GDB doesn't seem to allow for background execution combined with input/output redirection, I try to circumvent that using starti and continue&.

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 ---")

Is there a way to make this work?

EDIT: I've also tried a pty solution:

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 ---")

Both of these work when working with gdb manually, neither seem to work when using the GDB Python API; ps indicates that the target is stopped by the debugger ('t' status).

4
  • Is your target executable flushing its output stream after each message? If not, it should be. Or it should set it up to be unbuffered or line buffered. Commented Apr 10 at 3:03
  • You can also use a pseudoterminal instead of a pipe, see openpty(3). Commented Apr 10 at 3:07
  • I've tried it with both line buffered and unbuffered targets for stdout, no luck. Commented Apr 10 at 11:28
  • @n.m.couldbeanAI I also tried the pty method, please see the edit Commented Apr 10 at 12:50

1 Answer 1

0

Perhaps this is helpful. This solution only works using gdb 16.2.

Other versions of gdb (Version 12.1) may have issues see GDB not stopping with "interrupt" command from python script for details on specific issues and changes made to GDB python support in 2023.

Example Output

gdb -q -x pty-to-inferior.py
pty names: portA portB
PTY THREAD USES portB VIA FD 12
Terminal for future runs of program being debugged is "portA".
State of pagination is off.
Controlling the inferior in non-stop mode is on.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Enter 5 integers:
Number 1: 
Number 2: 
Number 3: 
Number 4: 
Number 5: 

You entered:
1 2 3 4 5 

[Inferior 1 (process 96297) exited normally]

Compile gdb 16.2

mkdir build install
git clone https://sourceware.org/git/binutils-gdb.git
cd binutils-gdb
git checkout gdb-16.2-release
cd ../build
../binutils-gdb/configure --prefix=$(cd ../install && pwd)
make all install
PATH=$(cd ../install && pwd)/bin:$PATH
gdb --version
GNU gdb (GDB) 16.2

Target Being Debugged

This is a target program being debugged that accepts 5 numbers from stdin and outputs those numbers to stdout. Notice the use of fflush in the target to ensure the target isn't queuing data.

// gcc -ggdb main.c -o read5
#include <stdio.h>

int main()
{
    int numbers[5] = { -1, -2, -3, -4, -5 };

    // Read 5 integers
    printf("Enter 5 integers:\n");
    for (int i = 0; i < 5; i++) {
        printf("Number %d: ", i + 1);
        fflush(stdout);
        scanf("%d", &numbers[i]);
    }

    // Output the 5 values
    printf("\nYou entered:\n");
    fflush(stdout);
    for (int i = 0; i < 5; i++) {
        printf("%d ", numbers[i]);
        fflush(stdout);
    }
    printf("\n");
    fflush(stdout);
    return 0;
}

GDB to Inferior Connectivity Using socat pty

This socat command will create 2 pseudo ttys (PortA for the gdb inferior and PortB for the gdb python thread).

socat -d -d pty,raw,echo=0,link=$(pwd)/portA pty,raw,echo=0,link=$(pwd)/portB &

GDB Python Scripting

This is the file pty-to-inferior.py, the python thread will read stdout, print that to the gdb console, and respond by writing to stdin if being asked for a "Number".

  1. Use gdb version 16.2

  2. Create PortA and PortB using socat (see above)

  3. Invoke using gdb -q -x pty-to-inferior.py

    1. PortA is the pty used by the inferior being debugged

      1. gdb command tty PortA configures the inferrior to use the socat pty named PortA before running the command
    2. PortB is used by gdb python thread named pty

      1. Thread pty accepts inferior stdout as input (read)

      2. Thread pty provides inferior stdin as output (write)

      3. Thread pty sends output to gdb console (print)

# run like this: gdb -q -x pty-to-inferior.py
import os
import gdb
import time
import threading

class ptyThread(gdb.Thread):
    def __init__(self, name, pty_fd, event):
        super().__init__(name=name)
        self.pty_fd = pty_fd
        self.stop_event = event
    def run (self):
        number = 1
        while not self.stop_event.is_set():
            try:
                data = os.read(self.pty_fd, 1024)
                while data is not None:
                    stdout = data.decode("utf-8")
                    # echo stdout to gdb stdout too
                    print(stdout,flush=True)
                    if "Number" in stdout:
                        os.write(self.pty_fd, f"{number}\n".encode('ascii'))
                        number += 1
                    data = os.read(self.pty_fd, 1024)
            except BlockingIOError:
                continue
            time.sleep(0.5)

# socat -d -d pty,raw,echo=0,link=$(pwd)/portA pty,raw,echo=0,link=$(pwd)/portB &
portA_name = "portA"
portB_name = "portB"
print(f"pty names: {portA_name} {portB_name}")

pty_fd = os.open(portB_name, os.O_RDWR | os.O_NONBLOCK)
print( f"PTY THREAD USES {portB_name} VIA FD {pty_fd}", flush=True)
stop_event = threading.Event()
pty = ptyThread("PTY", pty_fd, stop_event)
pty.start()

gdb.execute("file read5")
gdb.execute("set mi-async on")
gdb.execute(f"tty {portA_name}")
gdb.execute("show inferior-tty")
gdb.execute("set pagination off")
gdb.execute("show pagination")
gdb.execute("set non-stop on")
gdb.execute("show non-stop")
gdb.execute("run")

stop_event.set()
pty.join()
os.close(pty_fd)

gdb.execute('quit')
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.