Ich verwende GDB, um einige meiner C-Anwendungen zu debuggen. Was ich derzeit tue, ist die Zielanwendung zu laden, einen Haltepunkt in Zeile 30 zu setzen und sie auszuführen.
Ich möchte, dass GDB die Ausgabe meiner eigenen Anwendung in einem neuen Terminalfenster anzeigt, während ich die Haltepunktbehandlung immer noch über das GDB-Terminalfenster steuern kann, aber ich konnte anscheinend keinen richtigen Schalter finden. Gibt es eine Möglichkeit, GDB dazu zu bringen, die Ausgabe meines Programms in einem eigenen Fenster anzuzeigen?
Für Leute, die sich fragen, wie man den GDB-Befehl tty benutzt, ist hier eine kurze Beschreibung …
- Öffnen Sie ein neues Konsolenfenster. Wir werden die Ausgabe des unter GDB laufenden Programms hier umleiten. Dies ist unser Ausgabefenster.
-
Führen Sie die aus tty
Befehl im Ausgabefenster. Dies zeigt den Namen des tty an, der von der zugrunde liegenden Konsole verwendet wird.
$ tty
/dev/pts/4
-
Öffnen Sie ein weiteres Konsolenfenster und starten Sie GDB hier. Nennen wir dies das GDB-Fenster.
-
Führen Sie nun den tty-Befehl in GDB mit dem oben erhaltenen tty-Dateinamen aus und starten Sie dann den Debugging-Prozess.
(gdb) tty /dev/pts/4
(gdb) run
Jetzt sollten Sie die Programmausgabe separat im Ausgabefenster sehen können.
Hinweis: Die GDB set new-console on
Befehl funktioniert nicht unter Linux! Es soll nur unter Windows ausgeführt werden. Verwenden Sie die oben beschriebene tty-Methode unter Linux.
Sie können verwenden set new-console on
um dies wie gezeigt zu erreichen hier.
Eine andere Möglichkeit wäre, Ihr Zielprogramm mit gdbserver zu starten (vorausgesetzt, es steht Ihnen zur Verfügung). Dann können Sie GDB, das in einem separaten Fenster gestartet wurde, mit gdbserver verbinden.
GNU gdbserver-Dokumentation
Aus Fenster A:
gdbserver :12345 myprog [args...]
Aus Fenster 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)
Der beste Weg, den ich kenne, ist, die Ausgabe des Programms in eine Datei umzuleiten und dann tail -f
diese Datei in einem anderen Terminal. Umleitung ist erledigt run > filename
wie dokumentiert in der GDB-Dokumentation.
Benutz einfach tty
Befehl. Wenn Sie möchten, dass die Ausgabe Ihres Programms nach /dev/pts/5 umgeleitet wird, geben Sie Folgendes ein:
tty /dev/pts/5
GDBs tty
Der Befehl funktioniert, aber er funktioniert nicht gut mit interaktiven Programmen, z. B. wenn Sie Bash debuggen möchten. Auch für nicht interaktive Programme erhalten Sie Folgendes:
warning: GDB: Failed to set controlling terminal: Operation not permitted
Ich habe ein kleines Programm geschrieben, um diese beiden Probleme zu beheben:
// 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;
}
Angenommen, Sie speichern das Programm unter idleterm.c
. Um es zu kompilieren:
> gcc -o idleterm idleterm.c -luring
Um es zu verwenden, starten Sie ein Terminalfenster und führen Sie es in diesem Fenster aus idleterm
. Es wird der Name des tty gedruckt, an das angehängt werden soll:
> ./idleterm
pid = 3405922 tty = /dev/pts/0
█
Kopieren Sie diesen tty-Pfad und fügen Sie ihn in einem zweiten Fenster in eine gdb-Sitzung ein:
> gdb bash
Reading symbols from bash...
(No debugging symbols found in bash)
(gdb) tty /dev/pts/0
(gdb) r
Starting program: /usr/bin/bash
…
Im ersten Fenster erscheint eine Bash-Eingabeaufforderung. Alle Interaktionen mit bash, die ein spezielles TTY-Verhalten erfordern, funktionieren normal, einschließlich ^C
, ^Z
etc.
idleterm
geht vorbei alle Tastatureingabe bis zum zu debuggenden untergeordneten Prozess, einschließlich ^C und ^Z. Um also Idleterm zu stoppen, müssen Sie es von einem separaten Fenster aus beenden. Deshalb idleterm
gibt seine pid aus. Kopieren Sie die PID und fügen Sie sie dann in den Kill-Befehl ein:
> kill 3405922
Wenn es nur eine Instanz von gibt idleterm
auf dem System läuft, kann man natürlich einfach verwenden killall
.
Mit lldb auf dem Mac führt Folgendes das Programm in einem neuen Terminalfenster aus, während der Debugger vom ursprünglichen Fenster aus steuert:
$ lldb <prog>
(lldb) b main # if you want to set a breakpoint
(lldb) process launch --tty -- <args>
Dadurch wird das Programm als Prozess in ausgeführt spezifizierten tty (Terminalfenster):
$ 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>