38

I'm using GDB to debug some of my C applications. What I currently do is load the target application, set a breakpoint on line 30 and run it.

I would like to make GDB display the output of my own application in a new terminal window while I'm still able to control the breakpoint handling via the GDB terminal window, but I couldn't seem to find a proper switch. Is there any way making GDB display my program's output in its own window?

7 Answers 7

47

For people wondering how to use the GDB tty command here is a short description...

  • Open a new console window. We will redirect output from the program running under GDB here. This is our output window.
  • Run the tty command in the output window. This will show the name of the tty being used by the underlying console.

    $ tty
    /dev/pts/4

  • Open another console window and start GDB here. Let us call this the GDB window.

  • Now run the tty command in GDB using the tty file name obtained above and then start the debugging process.

    (gdb) tty /dev/pts/4
    (gdb) run

Now you should be able to see the program output separately in the output window.

Note: The GDB set new-console on command does not work on Linux! It is meant to be run on windows only. Use the tty method described above on Linux.

Sign up to request clarification or add additional context in comments.

4 Comments

This was the best way I found to get separate terminal output on Linux.
I tried it and get printf() output but I get the initial message warning: GDB: Failed to set controlling terminal: Operation not permitted. I googled around it looks like harmless warning message but was wondering if there was a fix for this. I'm running Ubuntu 20.04 on VirtualBox.
That would probably do for non-interactive programs, but for interactive ones you probably need a gdbserver
@MinhTran : I found a way to fix this error message. @x-yuri: GDB's tty does work for interactive commands, but the tty you specify must not have any child processes already attached to it. Both of you, see stackoverflow.com/a/71314902/1840698 .
18

You can use set new-console on to accomplish this as shown here.

6 Comments

Oh well thanks a lot this is exactly what I was looking for. I'll definitely keep your answer in mind when I start working with GDB again!
This answer saved my life just now. I dont know why I wasnt able to find this setting before. Thanks a bunch
@Herr_Doktor glad it helped :) gnu style documentation doesn't tend to lay itself out in a easy to find manner so it's understandable how you might have missed it.
Doesn't seem to be working for me: No symbol "new" in current context.
This seems to be for visualGDB, a commercial software that is not gnu gdb
|
13

Another way to do this would be to start your target program with gdbserver (assuming it is available to you). Then you can connect GDB started in a separate window to gdbserver.

GNU gdbserver documentation

From window A:

gdbserver :12345 myprog [args...]

From window B:

gdb myprog
GNU gdb 6.6
...
(gdb) target remote localhost:12345
Remote debugging using localhost:12345
0x009867c0 in ?? ()
(gdb) b main
Breakpoint 1 at 0x804834a: file myprog.c, line 40.
(gdb) c
Continuing.

Breakpoint 1, main (argc=1, argv=0xffff8904) at myprog.c:40
40          int i = 1;
(gdb) 

2 Comments

Looks like a great solution as well, many thanks for posting it. I've actually never worked with gdbserver before.
You might want to bind to "127.0.0.1:12345" instead.
7

The best way I know is to redirect the output of the program to a file, and then tail -f that file in another terminal. Redirection is done with run > filename, as documented in the GDB documentation.

1 Comment

Many thanks for your answer. Unfortunately it's not working for what I wanted to do, but I managed to find a dirty alternative solution for my purposes on my own by filtering the output and redirecting it into another application (My project is running on Windows and using the MiniGW bundle).
7

GDB's tty command does work, but it doesn't work well with interactive programs, like if you wanted to debug bash. Even for non-interactive programs, you get the following:

warning: GDB: Failed to set controlling terminal: Operation not permitted

I wrote a small program to fix both of these issues:

// Open a pty and let it idle, so that a process spawned in a different window
// can attach to it, start a new session, and set it as the controlling
// terminal. Useful for gdb debugging with gdb's `tty` command.
 
#include <inttypes.h>
typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64;
typedef  int8_t i8; typedef  int16_t i16; typedef  int32_t i32; typedef  int64_t i64;
#include <stdlib.h>
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>

#include <termios.h>
#include <pty.h>
#include <liburing.h>

#define BSIZE 4096

void raw_terminal(void)
{
    if (!isatty(0))
        return;
    struct termios t;
    tcgetattr(0, &t);
    t.c_lflag &= ~(ISIG | ICANON | ECHO);
    tcsetattr(0, TCSANOW, &t);
}

// Refers to the state of a Joint /while it's waiting in io_uring_enter/.
enum State {
    READ,
    WRITE
};
// Joins two fds together, like splice, but not a syscall and works on any two
// fds.
struct Joint {
    u8 buf[BSIZE];
    i32 ifd;
    i32 ofd;
    enum State state;
    u32 nread;
};
void roll_joint(struct Joint *j, struct io_uring *ur, i32 ifd, i32 ofd)
{
    j->ifd = ifd;
    j->ofd = ofd;
    j->state = READ;
    struct io_uring_sqe *sqe = io_uring_get_sqe(ur);
    io_uring_prep_read(sqe, j->ifd, j->buf, BSIZE, 0);
    io_uring_sqe_set_data(sqe, j);
    io_uring_submit(ur);
}
i32 main(i32 argc, char **argv)
{
    raw_terminal();
    struct io_uring ur;
    assert(io_uring_queue_init(256, &ur, 0) == 0);

    i32 ptm, pts;
    assert(openpty(&ptm, &pts, NULL, NULL, NULL) == 0);
    dprintf(2, "pid = %u   tty = %s\n", getpid(), ttyname(pts));

    struct Joint jkbd;
    roll_joint(&jkbd, &ur, 0, ptm);
    struct Joint jscreen;
    roll_joint(&jscreen, &ur, ptm, 1);

    for (;;) {
        struct io_uring_cqe *cqe;
        for (;;) {
            // Actions like suspend to RAM can interrupt the io_uring_enter
            // syscall. If we get interrupted, try again. For all other errors,
            // bail. Also, wait_cqe negates the error for no reason. It never
            // returns positive numbers. Very silly.
            u32 res = -io_uring_wait_cqe(&ur, &cqe);
            if (res == 0)
                break;
            else if (res != EINTR) {
                dprintf(2, "io_uring_enter returns errno %d\n", res);
                exit(res);
            }
        }
        struct Joint *j = io_uring_cqe_get_data(cqe);
        if (j->state == READ) {
            // Exiting READ state. Finish with the read...
            j->nread = cqe->res;
            assert(j->nread > 0);

            // Now, start the write.
            j->state = WRITE;
            struct io_uring_sqe *sqe = io_uring_get_sqe(&ur);
            io_uring_prep_write(sqe, j->ofd, j->buf, j->nread, 0);
            io_uring_sqe_set_data(sqe, j);
            io_uring_submit(&ur);
        }
        else if (j->state == WRITE) {
            // Exiting WRITE state. Finish with the write...
            i64 nwritten = cqe->res;
            assert(nwritten == j->nread);

            // Now, start the read.
            j->state = READ;
            struct io_uring_sqe *sqe = io_uring_get_sqe(&ur);
            io_uring_prep_read(sqe, j->ifd, j->buf, BSIZE, 0);
            io_uring_sqe_set_data(sqe, j);
            io_uring_submit(&ur);
        }
        io_uring_cqe_seen(&ur, cqe);
    }

    io_uring_queue_exit(&ur);
    return 0;
}

Suppose you save the program to idleterm.c. To compile it:

> gcc -o idleterm idleterm.c -luring

To use it, start one terminal window, and on that window, run idleterm. It will print the name of the tty to attach to:

> ./idleterm
pid = 3405922   tty = /dev/pts/0
█

Copy that tty path and paste it into a gdb session in a second window:

> gdb bash
Reading symbols from bash...
(No debugging symbols found in bash)
(gdb) tty /dev/pts/0
(gdb) r
Starting program: /usr/bin/bash
…

A bash prompt will appear in the first window. All of the interactions with bash requiring special TTY behaviour will work normally, including ^C, ^Z, etc.

idleterm passes all keyboard input through to the child process being debugged, including ^C and ^Z. So in order to stop idleterm, you'll have to kill it from a separate window. This is why idleterm prints its pid. Copy the pid, then paste it into the kill command:

> kill 3405922

If there's only one instance of idleterm running on the system, you can of course just use killall.

8 Comments

My Linux distro is too old for liburing so I wrote a python gist that does the same thing.
Nice1, @BenC. I was wondering whether it could be done with python. It is normally my go-to for simple programs like this, but I know that python does magical things with signals, stdin, stdout, and stderr, and I didn't want to deal with that class of possible problems.
Yeah, I didn't have to use liburing. I could have used pthreads, poll, select, or epoll, really. I used liburing mostly for fun and for personal edification. It was my first liburing program. Liburing is awesome!! :D
I just discovered socat, which will do the same thing with socat PTY,link=/path/to/link STDIO,cfmakeraw (where you can choose the path /path/to/link and use that in gdb's tty command).
In my ubuntu machine, the correct compile command is gcc -o idleterm idleterm.c -luring -lutil also remember to sudo apt install liburing-dev
|
3

Just use tty command. If you want output of your program redirected to /dev/pts/5 type:

tty /dev/pts/5

5 Comments

Is that a new option for the 'tty' command? On my Fedora 19 system the 'tty' command doesn't accept that parameter.
@KdawgUD: Are you sure? The command works for me. Can you show error message?
In docs here there is an example.
Here is the error: "tty: extra operand ‘/dev/pts/4’" using Fedora 19
@KdawgUD: there is tty utility that print the file name of the terminal, you can use it in standart shell prompt and it does not accept arguments. To redirect program output in GDB you should type it in GDB CLI prompt.
1

With lldb on Mac the following runs the program in w new terminal window while the debugger controls from the original window:

$ lldb   <prog>
(lldb) b main   # if you want to set a breakpoint
(lldb) process launch --tty -- <args>

This runs the program as a process in specified tty (terminal window):

$ tty   # (in other window, get tty name)
/dev/ttys002

$ lldb  
(lldb) b main   # if you want to set a breakpoint
(lldb) process launch --tty=/dev/ttys002 -- <args>

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.