Redirect File Descriptor of Running Process

This note explains how to redirect stdin (or any other file descriptor) of a pre-existing process using the GNU debugger (gdb) and a FIFO. It was tested on FreeBSD 11.

An example of use would be saving the contents of remote vi sessions after they are detached due to a dropped connection.

First, make a FIFO:

$ mkfifo /tmp/vififo

Assuming there is a pre-existing vi session with PID 91266, connect with gdb, close file descriptor 0 and reopen it as a connection to the FIFO with the call close and call open commands.

$ gdb -p 91266
<snip>
Attaching to process 91266
<snip>
(gdb) call close (0)
$1 = 0
(gdb) call open ("/tmp/vififo", 0600)

At this point gdb will appear to hang. Leave it and open a new terminal. Use echo to send characters to the process through the FIFO.

Special characters may be escaped by pressing Ctrl-V followed by the character. For example, to send an Escape, press Ctrl-V followed by Escape which results in an Escape code, or ^[.

Continuing the example, tell vi to save the current buffer to a file.

$ echo "^[:w /tmp/vi_recover.txt" > /tmp/vififo

After this command the gdb session should start responding again, returning to a (gdb) prompt. Exit gdb.

$2 = 0
(gdb) quit
A debugging session is active.

    Inferior 1 [process 91266] will be detached.

Quit anyway? (y or n) Y
Detaching from program: /hh/bin/vi, process 91266
[Inferior 1 (process 91266) detached]

The characters have now been received by vi and a file should be waiting at /tmp/vi_recover.txt.

Expose Process STDIO on TCP Port

This note explains how to launch a process and connect its stdin/stdout/stderr directly to a TCP port. It was tested on FreeBSD 12.

As an example, we will launch a bash process running on a Debian 10 machine and connect it to TCP port 4242. Then, from a FreeBSD machine we will connect and utilize the bash session remotely.

First, on the Debian machine, we use socat, a program which establishes two bidirectional byte streams and transfers data between them. In our case, we tell socat to build a PTY at /tmp/pty_test and connect it to TCP port 4242.

socat PTY,link=/tmp/pty_test,raw TCP-LISTEN:4242,reuseaddr,fork

Note that the PTY argument to socat must precede the TCP-LISTEN argument. Despite the bidirectional nature, the endpoint definition order is not interchangeable.

Now we use setsid to start a new bash process and attach it to the PTY at /tmp/pty_test. Note that we collapse stdout and stderr into a single combined stream.

setsid sh -c 'exec bash --login <> /tmp/pty_test >&0 2>&1'

Replace bash --login with whatever other command you desire to execute.

Now, from the FreeBSD machine, connect the remote TCP port to your local STDIO with socat. Assuming the Debian machine running bash is at 192.168.1.107, execute the following command.

socat STDIO,raw TCP:192.168.1.107:4242

At this point you have a fully interative bash session running on the Debian machine, controlled from the FreeBSD machine.