* [PATCH v3 01/21] Remove unnecessary override of write_async_safe
2026-01-30 13:17 [PATCH v3 00/21] Rework gdb logging and output redirection Tom Tromey
@ 2026-01-30 13:17 ` Tom Tromey
2026-01-30 13:17 ` [PATCH v3 02/21] Move stdtarg to ui Tom Tromey
` (19 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Tom Tromey @ 2026-01-30 13:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey, Andrew Burgess
pager_file overrides write_async_safe but does so in the same way as
its superclass. So, remove the unnecessary override.
Approved-By: Andrew Burgess <aburgess@redhat.com>
---
| 5 -----
1 file changed, 5 deletions(-)
--git a/gdb/pager.h b/gdb/pager.h
index a23e1bcc2de..5d64ffcf16c 100644
--- a/gdb/pager.h
+++ b/gdb/pager.h
@@ -44,11 +44,6 @@ class pager_file : public wrapped_file
void puts (const char *str) override;
- void write_async_safe (const char *buf, long length_buf) override
- {
- m_stream->write_async_safe (buf, length_buf);
- }
-
void emit_style_escape (const ui_file_style &style) override;
void flush () override;
--
2.49.0
^ permalink raw reply [flat|nested] 23+ messages in thread* [PATCH v3 02/21] Move stdtarg to ui
2026-01-30 13:17 [PATCH v3 00/21] Rework gdb logging and output redirection Tom Tromey
2026-01-30 13:17 ` [PATCH v3 01/21] Remove unnecessary override of write_async_safe Tom Tromey
@ 2026-01-30 13:17 ` Tom Tromey
2026-01-30 13:17 ` [PATCH v3 03/21] Turn wrapped_file into a template Tom Tromey
` (18 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Tom Tromey @ 2026-01-30 13:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
Currently, most of the output streams are handled by the UI -- with
the sole exception of gdb_stdtarg. This doesn't make sense to me, and
it is is useful in the longer term to make it per-UI so that the
logging approach can be uniform across all streams. So, this moves it
to the UI, following the implementation approach of all other output
streams.
---
gdb/main.c | 2 --
gdb/top.c | 6 ++++++
gdb/ui.c | 3 ++-
gdb/ui.h | 2 ++
gdb/utils.h | 5 +++--
5 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/gdb/main.c b/gdb/main.c
index 296f59b0b57..ac83b953cf2 100644
--- a/gdb/main.c
+++ b/gdb/main.c
@@ -81,7 +81,6 @@ std::string python_libdir;
/* Target IO streams. */
struct ui_file *gdb_stdtargin;
-struct ui_file *gdb_stdtarg;
/* True if --batch or --batch-silent was seen. */
int batch_flag = 0;
@@ -695,7 +694,6 @@ captured_main_1 (struct captured_main_args *context)
gdb_internal_backtrace_init_str ();
current_ui = main_ui;
- gdb_stdtarg = gdb_stderr;
gdb_stdtargin = gdb_stdin;
/* Put a CLI based uiout in place early. If the early initialization
diff --git a/gdb/top.c b/gdb/top.c
index f1d0baeb3f4..e7d1ded96e4 100644
--- a/gdb/top.c
+++ b/gdb/top.c
@@ -115,6 +115,12 @@ current_ui_gdb_stdlog_ptr ()
return ¤t_ui->m_gdb_stdlog;
}
+struct ui_file **
+current_ui_gdb_stdtarg_ptr ()
+{
+ return ¤t_ui->m_gdb_stdtarg;
+}
+
struct ui_out **
current_ui_current_uiout_ptr ()
{
diff --git a/gdb/ui.c b/gdb/ui.c
index 3e1ef9f8a34..6d29026117e 100644
--- a/gdb/ui.c
+++ b/gdb/ui.c
@@ -51,7 +51,8 @@ ui::ui (FILE *instream_, FILE *outstream_, FILE *errstream_)
m_gdb_stdout (new pager_file (new stdio_file (outstream))),
m_gdb_stdin (new stdio_file (instream)),
m_gdb_stderr (new stderr_file (errstream)),
- m_gdb_stdlog (new timestamped_file (m_gdb_stderr))
+ m_gdb_stdlog (new timestamped_file (m_gdb_stderr)),
+ m_gdb_stdtarg (m_gdb_stderr)
{
unbuffer_stream (instream_);
diff --git a/gdb/ui.h b/gdb/ui.h
index 10159b1c38e..ba98d2500af 100644
--- a/gdb/ui.h
+++ b/gdb/ui.h
@@ -155,6 +155,8 @@ struct ui
/* Log/debug/trace messages that should bypass normal stdout/stderr
filtering. */
struct ui_file *m_gdb_stdlog;
+ /* Target output. */
+ struct ui_file *m_gdb_stdtarg;
/* The current ui_out. */
struct ui_out *m_current_uiout = nullptr;
diff --git a/gdb/utils.h b/gdb/utils.h
index 5f6b54d2953..4f11cbf379f 100644
--- a/gdb/utils.h
+++ b/gdb/utils.h
@@ -169,6 +169,7 @@ extern struct ui_file **current_ui_gdb_stdout_ptr (void);
extern struct ui_file **current_ui_gdb_stdin_ptr (void);
extern struct ui_file **current_ui_gdb_stderr_ptr (void);
extern struct ui_file **current_ui_gdb_stdlog_ptr (void);
+extern struct ui_file **current_ui_gdb_stdtarg_ptr ();
/* Flush STREAM. */
extern void gdb_flush (struct ui_file *stream);
@@ -185,11 +186,11 @@ extern void gdb_flush (struct ui_file *stream);
/* Log/debug/trace messages that bypasses the pager, if one is in
use. */
#define gdb_stdlog (*current_ui_gdb_stdlog_ptr ())
+/* Target output. */
+#define gdb_stdtarg (*current_ui_gdb_stdtarg_ptr ())
/* Truly global ui_file streams. These are all defined in main.c. */
-/* Target output that should bypass the pager, if one is in use. */
-extern struct ui_file *gdb_stdtarg;
extern struct ui_file *gdb_stdtargin;
/* Set the screen dimensions to WIDTH and HEIGHT. */
--
2.49.0
^ permalink raw reply [flat|nested] 23+ messages in thread* [PATCH v3 03/21] Turn wrapped_file into a template
2026-01-30 13:17 [PATCH v3 00/21] Rework gdb logging and output redirection Tom Tromey
2026-01-30 13:17 ` [PATCH v3 01/21] Remove unnecessary override of write_async_safe Tom Tromey
2026-01-30 13:17 ` [PATCH v3 02/21] Move stdtarg to ui Tom Tromey
@ 2026-01-30 13:17 ` Tom Tromey
2026-01-30 13:17 ` [PATCH v3 04/21] Small rewrite of get_unbuffered Tom Tromey
` (17 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Tom Tromey @ 2026-01-30 13:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
This turns wrapped_file into a template, specialized by the type of
pointer it holds. This makes it cleaner to have it own the stream it
points to.
---
| 11 +++--------
gdb/tui/tui-io.c | 2 +-
gdb/ui-file.h | 18 +++++++++---------
gdb/ui.c | 2 +-
gdb/utils.c | 2 +-
5 files changed, 15 insertions(+), 20 deletions(-)
--git a/gdb/pager.h b/gdb/pager.h
index 5d64ffcf16c..697134be805 100644
--- a/gdb/pager.h
+++ b/gdb/pager.h
@@ -23,21 +23,16 @@
/* A ui_file that implements output paging and unfiltered output. */
-class pager_file : public wrapped_file
+class pager_file : public wrapped_file<ui_file_up>
{
public:
/* Create a new pager_file. The new object takes ownership of
STREAM. */
- explicit pager_file (ui_file *stream)
- : wrapped_file (stream)
+ explicit pager_file (ui_file_up stream)
+ : wrapped_file (std::move (stream))
{
}
- ~pager_file ()
- {
- delete m_stream;
- }
-
DISABLE_COPY_AND_ASSIGN (pager_file);
void write (const char *buf, long length_buf) override;
diff --git a/gdb/tui/tui-io.c b/gdb/tui/tui-io.c
index bf212da92d0..7abe4fa3e81 100644
--- a/gdb/tui/tui-io.c
+++ b/gdb/tui/tui-io.c
@@ -933,7 +933,7 @@ tui_initialize_io (void)
#endif
/* Create tui output streams. */
- tui_stdout = new pager_file (new tui_file (stdout, true));
+ tui_stdout = new pager_file (std::make_unique<tui_file> (stdout, true));
tui_stderr = new tui_file (stderr, false);
tui_stdlog = new timestamped_file (tui_stderr);
tui_out = new cli_ui_out (tui_stdout, 0);
diff --git a/gdb/ui-file.h b/gdb/ui-file.h
index 22badec4abe..231c32deafc 100644
--- a/gdb/ui-file.h
+++ b/gdb/ui-file.h
@@ -416,8 +416,11 @@ class no_terminal_escape_file : public escape_buffering_file
void do_write (const char *buf, long len) override;
};
-/* A base class for ui_file types that wrap another ui_file. */
+/* A base class for ui_file types that wrap another ui_file. The
+ precise underlying ui_file type is a template parameter, so that
+ either owning or non-owning wrappers can be made. */
+template<typename T>
class wrapped_file : public ui_file
{
public:
@@ -451,22 +454,19 @@ class wrapped_file : public ui_file
protected:
- /* Note that this class does not assume ownership of the stream.
- However, a subclass may choose to, by adding a 'delete' to its
- destructor. */
- explicit wrapped_file (ui_file *stream)
- : m_stream (stream)
+ explicit wrapped_file (T stream)
+ : m_stream (std::move (stream))
{
}
/* The underlying stream. */
- ui_file *m_stream;
+ T m_stream;
};
/* A ui_file that optionally puts a timestamp at the start of each
line of output. */
-class timestamped_file : public wrapped_file
+class timestamped_file : public wrapped_file<ui_file *>
{
public:
explicit timestamped_file (ui_file *stream)
@@ -490,7 +490,7 @@ class timestamped_file : public wrapped_file
Note that this only really handles ASCII output correctly. */
-class tab_expansion_file : public wrapped_file
+class tab_expansion_file : public wrapped_file<ui_file *>
{
public:
diff --git a/gdb/ui.c b/gdb/ui.c
index 6d29026117e..647e63d1bde 100644
--- a/gdb/ui.c
+++ b/gdb/ui.c
@@ -48,7 +48,7 @@ ui::ui (FILE *instream_, FILE *outstream_, FILE *errstream_)
errstream (errstream_),
input_fd (fileno (instream)),
m_input_interactive_p (ISATTY (instream)),
- m_gdb_stdout (new pager_file (new stdio_file (outstream))),
+ m_gdb_stdout (new pager_file (std::make_unique<stdio_file> (outstream))),
m_gdb_stdin (new stdio_file (instream)),
m_gdb_stderr (new stderr_file (errstream)),
m_gdb_stdlog (new timestamped_file (m_gdb_stderr)),
diff --git a/gdb/utils.c b/gdb/utils.c
index ceea8317dc5..ef2d0fa0c41 100644
--- a/gdb/utils.c
+++ b/gdb/utils.c
@@ -1838,7 +1838,7 @@ static void
test_pager ()
{
string_file *strfile = new string_file ();
- pager_file pager (strfile);
+ pager_file pager { ui_file_up (strfile) };
/* Make sure the pager is disabled. */
scoped_restore save_enabled
--
2.49.0
^ permalink raw reply [flat|nested] 23+ messages in thread* [PATCH v3 04/21] Small rewrite of get_unbuffered
2026-01-30 13:17 [PATCH v3 00/21] Rework gdb logging and output redirection Tom Tromey
` (2 preceding siblings ...)
2026-01-30 13:17 ` [PATCH v3 03/21] Turn wrapped_file into a template Tom Tromey
@ 2026-01-30 13:17 ` Tom Tromey
2026-01-30 13:17 ` [PATCH v3 05/21] Move buffered stream to new files Tom Tromey
` (16 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Tom Tromey @ 2026-01-30 13:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey, Andrew Burgess
This rewrites get_unbuffered to use iteration rather than recursion.
I also noticed a typo in the documentation comment, so that's fixed as
well.
Approved-By: Andrew Burgess <aburgess@redhat.com>
---
gdb/ui-out.c | 11 +++++++----
gdb/ui-out.h | 2 +-
2 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/gdb/ui-out.c b/gdb/ui-out.c
index 87874e22530..00c2055f492 100644
--- a/gdb/ui-out.c
+++ b/gdb/ui-out.c
@@ -907,12 +907,15 @@ buffer_group::flush_here (ui_file *stream)
ui_file *
get_unbuffered (ui_file *stream)
{
- buffering_file *buf = dynamic_cast<buffering_file *> (stream);
+ while (true)
+ {
+ buffering_file *buf = dynamic_cast<buffering_file *> (stream);
- if (buf == nullptr)
- return stream;
+ if (buf == nullptr)
+ return stream;
- return get_unbuffered (buf->stream ());
+ stream = buf->stream ();
+ }
}
buffered_streams::buffered_streams (buffer_group *group, ui_out *uiout)
diff --git a/gdb/ui-out.h b/gdb/ui-out.h
index 9d63b4c2940..ee5e68fa233 100644
--- a/gdb/ui-out.h
+++ b/gdb/ui-out.h
@@ -529,7 +529,7 @@ struct buffer_group
std::unique_ptr<buffered_streams> m_buffered_streams;
};
-/* If FILE is a buffering_file, return it's underlying stream. */
+/* If FILE is a buffering_file, return its underlying stream. */
extern ui_file *get_unbuffered (ui_file *file);
--
2.49.0
^ permalink raw reply [flat|nested] 23+ messages in thread* [PATCH v3 05/21] Move buffered stream to new files
2026-01-30 13:17 [PATCH v3 00/21] Rework gdb logging and output redirection Tom Tromey
` (3 preceding siblings ...)
2026-01-30 13:17 ` [PATCH v3 04/21] Small rewrite of get_unbuffered Tom Tromey
@ 2026-01-30 13:17 ` Tom Tromey
2026-01-30 13:17 ` [PATCH v3 06/21] Remove TYPE_FN_FIELD_STUB and associated code Tom Tromey
` (15 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Tom Tromey @ 2026-01-30 13:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey, Andrew Burgess
The buffered stream code is currently in ui-out.h and ui-out.c, which
seems weird because it seems more like a low-level ui-file idea (for
the most part, it does also affect some ui_out).
This patch moves the declarations to a new header file,
buffered-streams.h and the implementation to buffered-streams.c. This
seems cleaner to me.
Approved-By: Andrew Burgess <aburgess@redhat.com>
---
gdb/Makefile.in | 2 +
gdb/buffered-streams.c | 155 +++++++++++++++++++++++++++++++++++
gdb/buffered-streams.h | 205 +++++++++++++++++++++++++++++++++++++++++++++++
gdb/cli-out.c | 1 +
gdb/debuginfod-support.c | 1 +
gdb/infrun.c | 1 +
gdb/stack.c | 1 +
gdb/thread.c | 1 +
gdb/ui-out.c | 137 +------------------------------
gdb/ui-out.h | 180 -----------------------------------------
10 files changed, 368 insertions(+), 316 deletions(-)
diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index fc76925344f..9aa46f8b1fa 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -1071,6 +1071,7 @@ COMMON_SFILES = \
breakpoint.c \
bt-utils.c \
btrace.c \
+ buffered-streams.c \
build-id.c \
buildsym.c \
c-lang.c \
@@ -1346,6 +1347,7 @@ HFILES_NO_SRCDIR = \
bsd-uthread.h \
btrace.h \
bt-utils.h \
+ buffered-streams.h \
build-id.h \
buildsym.h \
c-exp.h \
diff --git a/gdb/buffered-streams.c b/gdb/buffered-streams.c
new file mode 100644
index 00000000000..a9d3fcf4f5d
--- /dev/null
+++ b/gdb/buffered-streams.c
@@ -0,0 +1,155 @@
+/* Copyright (C) 2025, 2026 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "buffered-streams.h"
+#include "ui-out.h"
+
+/* See buffered-streams.h. */
+
+void
+buffer_group::output_unit::flush () const
+{
+ if (!m_msg.empty ())
+ m_stream->puts (m_msg.c_str ());
+
+ if (m_wrap_hint >= 0)
+ m_stream->wrap_here (m_wrap_hint);
+
+ if (m_flush)
+ m_stream->flush ();
+}
+
+/* See buffered-streams.h. */
+
+void
+buffer_group::write (const char *buf, long length_buf, ui_file *stream)
+{
+ /* Record each line separately. */
+ for (size_t prev = 0, cur = 0; cur < length_buf; ++cur)
+ if (buf[cur] == '\n' || cur == length_buf - 1)
+ {
+ std::string msg (buf + prev, cur - prev + 1);
+
+ if (m_buffered_output.size () > 0
+ && m_buffered_output.back ().m_wrap_hint == -1
+ && m_buffered_output.back ().m_stream == stream
+ && m_buffered_output.back ().m_msg.size () > 0
+ && m_buffered_output.back ().m_msg.back () != '\n')
+ m_buffered_output.back ().m_msg.append (msg);
+ else
+ m_buffered_output.emplace_back (msg).m_stream = stream;
+ prev = cur + 1;
+ }
+}
+
+/* See buffered-streams.h. */
+
+void
+buffer_group::wrap_here (int indent, ui_file *stream)
+{
+ m_buffered_output.emplace_back ("", indent).m_stream = stream;
+}
+
+/* See buffered-streams.h. */
+
+void
+buffer_group::flush_here (ui_file *stream)
+{
+ m_buffered_output.emplace_back ("", -1, true).m_stream = stream;
+}
+
+/* See buffered-streams.h. */
+
+ui_file *
+get_unbuffered (ui_file *stream)
+{
+ while (true)
+ {
+ buffering_file *buf = dynamic_cast<buffering_file *> (stream);
+
+ if (buf == nullptr)
+ return stream;
+
+ stream = buf->stream ();
+ }
+}
+
+buffered_streams::buffered_streams (buffer_group *group, ui_out *uiout)
+ : m_buffered_stdout (group, gdb_stdout),
+ m_buffered_stderr (group, gdb_stderr),
+ m_buffered_stdlog (group, gdb_stdlog),
+ m_buffered_stdtarg (group, gdb_stdtarg),
+ m_uiout (uiout)
+{
+ gdb_stdout = &m_buffered_stdout;
+ gdb_stderr = &m_buffered_stderr;
+ gdb_stdlog = &m_buffered_stdlog;
+ gdb_stdtarg = &m_buffered_stdtarg;
+
+ ui_file *stream = current_uiout->current_stream ();
+ if (stream != nullptr)
+ {
+ m_buffered_current_uiout.emplace (group, stream);
+ current_uiout->redirect (&(*m_buffered_current_uiout));
+ }
+
+ stream = m_uiout->current_stream ();
+ if (stream != nullptr && current_uiout != m_uiout)
+ {
+ m_buffered_uiout.emplace (group, stream);
+ m_uiout->redirect (&(*m_buffered_uiout));
+ }
+
+ m_buffers_in_place = true;
+}
+
+/* See buffered-streams.h. */
+
+void
+buffered_streams::remove_buffers ()
+{
+ if (!m_buffers_in_place)
+ return;
+
+ m_buffers_in_place = false;
+
+ gdb_stdout = m_buffered_stdout.stream ();
+ gdb_stderr = m_buffered_stderr.stream ();
+ gdb_stdlog = m_buffered_stdlog.stream ();
+ gdb_stdtarg = m_buffered_stdtarg.stream ();
+
+ if (m_buffered_current_uiout.has_value ())
+ current_uiout->redirect (nullptr);
+
+ if (m_buffered_uiout.has_value ())
+ m_uiout->redirect (nullptr);
+}
+
+buffer_group::buffer_group (ui_out *uiout)
+ : m_buffered_streams (new buffered_streams (this, uiout))
+{ /* Nothing. */ }
+
+/* See buffered-streams.h. */
+
+void
+buffer_group::flush () const
+{
+ m_buffered_streams->remove_buffers ();
+
+ for (const output_unit &ou : m_buffered_output)
+ ou.flush ();
+}
diff --git a/gdb/buffered-streams.h b/gdb/buffered-streams.h
new file mode 100644
index 00000000000..0da2d8a8f43
--- /dev/null
+++ b/gdb/buffered-streams.h
@@ -0,0 +1,205 @@
+/* Copyright (C) 2025, 2026 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef GDB_BUFFERED_STREAMS_H
+#define GDB_BUFFERED_STREAMS_H
+
+#include <optional>
+#include "ui-file.h"
+
+struct buffered_streams;
+class ui_out;
+
+/* Organizes writes to a collection of buffered output streams
+ so that when flushed, output is written to all streams in
+ chronological order. */
+
+struct buffer_group
+{
+ buffer_group (ui_out *uiout);
+
+ /* Flush all buffered writes to the underlying output streams. */
+ void flush () const;
+
+ /* Record contents of BUF and associate it with STREAM. */
+ void write (const char *buf, long length_buf, ui_file *stream);
+
+ /* Record a wrap_here and associate it with STREAM. */
+ void wrap_here (int indent, ui_file *stream);
+
+ /* Record a call to flush and associate it with STREAM. */
+ void flush_here (ui_file *stream);
+
+private:
+
+ struct output_unit
+ {
+ output_unit (std::string msg, int wrap_hint = -1, bool flush = false)
+ : m_msg (msg), m_wrap_hint (wrap_hint), m_flush (flush)
+ {}
+
+ /* Write contents of this output_unit to the underlying stream. */
+ void flush () const;
+
+ /* Underlying stream for which this output unit will be written to. */
+ ui_file *m_stream;
+
+ /* String to be written to underlying buffer. */
+ std::string m_msg;
+
+ /* Argument to wrap_here. -1 indicates no wrap. Used to call wrap_here
+ during buffer flush. */
+ int m_wrap_hint;
+
+ /* Indicate that the underlying output stream's flush should be called. */
+ bool m_flush;
+ };
+
+ /* Output_units to be written to buffered output streams. */
+ std::vector<output_unit> m_buffered_output;
+
+ /* Buffered output streams. */
+ std::unique_ptr<buffered_streams> m_buffered_streams;
+};
+
+/* If FILE is a buffering_file, return its underlying stream. */
+
+extern ui_file *get_unbuffered (ui_file *file);
+
+/* Buffer output to gdb_stdout and gdb_stderr for the duration of FUNC. */
+
+template<typename F, typename... Arg>
+void
+do_with_buffered_output (F func, ui_out *uiout, Arg... args)
+{
+ buffer_group g (uiout);
+
+ try
+ {
+ func (uiout, std::forward<Arg> (args)...);
+ }
+ catch (gdb_exception &ex)
+ {
+ /* Ideally flush would be called in the destructor of buffer_group,
+ however flushing might cause an exception to be thrown. Catch it
+ and ensure the first exception propagates. */
+ try
+ {
+ g.flush ();
+ }
+ catch (const gdb_exception &)
+ {
+ }
+
+ throw_exception (std::move (ex));
+ }
+
+ /* Try was successful. Let any further exceptions propagate. */
+ g.flush ();
+}
+
+/* Accumulate writes to an underlying ui_file. Output to the
+ underlying file is deferred until required. */
+
+struct buffering_file : public ui_file
+{
+ buffering_file (buffer_group *group, ui_file *stream)
+ : m_group (group),
+ m_stream (stream)
+ { /* Nothing. */ }
+
+ /* Return the underlying output stream. */
+ ui_file *stream () const
+ {
+ return m_stream;
+ }
+
+ /* Record the contents of BUF. */
+ void write (const char *buf, long length_buf) override
+ {
+ m_group->write (buf, length_buf, m_stream);
+ }
+
+ /* Record a wrap_here call with argument INDENT. */
+ void wrap_here (int indent) override
+ {
+ m_group->wrap_here (indent, m_stream);
+ }
+
+ /* Return true if the underlying stream is a tty. */
+ bool isatty () override
+ {
+ return m_stream->isatty ();
+ }
+
+ /* Return true if ANSI escapes can be used on the underlying stream. */
+ bool can_emit_style_escape () override
+ {
+ return m_stream->can_emit_style_escape ();
+ }
+
+ /* Flush the underlying output stream. */
+ void flush () override
+ {
+ return m_group->flush_here (m_stream);
+ }
+
+private:
+
+ /* Coordinates buffering across multiple buffering_files. */
+ buffer_group *m_group;
+
+ /* The underlying output stream. */
+ ui_file *m_stream;
+};
+
+/* Attaches and detaches buffers for each of the gdb_std* streams. */
+
+struct buffered_streams
+{
+ buffered_streams (buffer_group *group, ui_out *uiout);
+
+ ~buffered_streams ()
+ {
+ this->remove_buffers ();
+ }
+
+ /* Remove buffering_files from all underlying streams. */
+ void remove_buffers ();
+
+private:
+
+ /* True if buffers are still attached to each underlying output stream. */
+ bool m_buffers_in_place;
+
+ /* Buffers for each gdb_std* output stream. */
+ buffering_file m_buffered_stdout;
+ buffering_file m_buffered_stderr;
+ buffering_file m_buffered_stdlog;
+ buffering_file m_buffered_stdtarg;
+
+ /* Buffer for current_uiout's output stream. */
+ std::optional<buffering_file> m_buffered_current_uiout;
+
+ /* Additional ui_out being buffered. */
+ ui_out *m_uiout;
+
+ /* Buffer for m_uiout's output stream. */
+ std::optional<buffering_file> m_buffered_uiout;
+};
+
+#endif /* GDB_BUFFERED_STREAMS_H */
diff --git a/gdb/cli-out.c b/gdb/cli-out.c
index 0f4dbc7768d..b1ea9560fcf 100644
--- a/gdb/cli-out.c
+++ b/gdb/cli-out.c
@@ -27,6 +27,7 @@
#include "cli/cli-style.h"
#include "ui.h"
#include "cli/cli-cmds.h"
+#include "buffered-streams.h"
/* These are the CLI output functions */
diff --git a/gdb/debuginfod-support.c b/gdb/debuginfod-support.c
index 16012f826ce..3da3edc432d 100644
--- a/gdb/debuginfod-support.c
+++ b/gdb/debuginfod-support.c
@@ -26,6 +26,7 @@
#include "cli/cli-style.h"
#include "cli-out.h"
#include "target.h"
+#include "buffered-streams.h"
/* Set/show debuginfod commands. */
static cmd_list_element *set_debuginfod_prefix_list;
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 6bcd8ec5cc0..422b4728477 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -76,6 +76,7 @@
#include "disasm.h"
#include "interps.h"
#include "finish-thread-state.h"
+#include "buffered-streams.h"
/* Prototypes for local functions */
diff --git a/gdb/stack.c b/gdb/stack.c
index 732d525083e..919cbf73642 100644
--- a/gdb/stack.c
+++ b/gdb/stack.c
@@ -50,6 +50,7 @@
#include "linespec.h"
#include "cli/cli-utils.h"
#include "objfiles.h"
+#include "buffered-streams.h"
#include "symfile.h"
#include "extension.h"
diff --git a/gdb/thread.c b/gdb/thread.c
index 0788bea235a..96e3bb7b50f 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -50,6 +50,7 @@
#include "stack.h"
#include "interps.h"
#include "record-full.h"
+#include "buffered-streams.h"
/* Print notices when new threads are attached and detached. */
static bool print_thread_events = true;
diff --git a/gdb/ui-out.c b/gdb/ui-out.c
index 00c2055f492..8a41d9897fa 100644
--- a/gdb/ui-out.c
+++ b/gdb/ui-out.c
@@ -26,6 +26,7 @@
#include "gdbsupport/format.h"
#include "cli/cli-style.h"
#include "diagnostics.h"
+#include "buffered-streams.h"
#include <vector>
#include <memory>
@@ -847,139 +848,3 @@ ui_out::ui_out (ui_out_flags flags)
ui_out::~ui_out ()
{
}
-
-/* See ui-out.h. */
-
-void
-buffer_group::output_unit::flush () const
-{
- if (!m_msg.empty ())
- m_stream->puts (m_msg.c_str ());
-
- if (m_wrap_hint >= 0)
- m_stream->wrap_here (m_wrap_hint);
-
- if (m_flush)
- m_stream->flush ();
-}
-
-/* See ui-out.h. */
-
-void
-buffer_group::write (const char *buf, long length_buf, ui_file *stream)
-{
- /* Record each line separately. */
- for (size_t prev = 0, cur = 0; cur < length_buf; ++cur)
- if (buf[cur] == '\n' || cur == length_buf - 1)
- {
- std::string msg (buf + prev, cur - prev + 1);
-
- if (m_buffered_output.size () > 0
- && m_buffered_output.back ().m_wrap_hint == -1
- && m_buffered_output.back ().m_stream == stream
- && m_buffered_output.back ().m_msg.size () > 0
- && m_buffered_output.back ().m_msg.back () != '\n')
- m_buffered_output.back ().m_msg.append (msg);
- else
- m_buffered_output.emplace_back (msg).m_stream = stream;
- prev = cur + 1;
- }
-}
-
-/* See ui-out.h. */
-
-void
-buffer_group::wrap_here (int indent, ui_file *stream)
-{
- m_buffered_output.emplace_back ("", indent).m_stream = stream;
-}
-
-/* See ui-out.h. */
-
-void
-buffer_group::flush_here (ui_file *stream)
-{
- m_buffered_output.emplace_back ("", -1, true).m_stream = stream;
-}
-
-/* See ui-out.h. */
-
-ui_file *
-get_unbuffered (ui_file *stream)
-{
- while (true)
- {
- buffering_file *buf = dynamic_cast<buffering_file *> (stream);
-
- if (buf == nullptr)
- return stream;
-
- stream = buf->stream ();
- }
-}
-
-buffered_streams::buffered_streams (buffer_group *group, ui_out *uiout)
- : m_buffered_stdout (group, gdb_stdout),
- m_buffered_stderr (group, gdb_stderr),
- m_buffered_stdlog (group, gdb_stdlog),
- m_buffered_stdtarg (group, gdb_stdtarg),
- m_uiout (uiout)
-{
- gdb_stdout = &m_buffered_stdout;
- gdb_stderr = &m_buffered_stderr;
- gdb_stdlog = &m_buffered_stdlog;
- gdb_stdtarg = &m_buffered_stdtarg;
-
- ui_file *stream = current_uiout->current_stream ();
- if (stream != nullptr)
- {
- m_buffered_current_uiout.emplace (group, stream);
- current_uiout->redirect (&(*m_buffered_current_uiout));
- }
-
- stream = m_uiout->current_stream ();
- if (stream != nullptr && current_uiout != m_uiout)
- {
- m_buffered_uiout.emplace (group, stream);
- m_uiout->redirect (&(*m_buffered_uiout));
- }
-
- m_buffers_in_place = true;
-}
-
-/* See ui-out.h. */
-
-void
-buffered_streams::remove_buffers ()
-{
- if (!m_buffers_in_place)
- return;
-
- m_buffers_in_place = false;
-
- gdb_stdout = m_buffered_stdout.stream ();
- gdb_stderr = m_buffered_stderr.stream ();
- gdb_stdlog = m_buffered_stdlog.stream ();
- gdb_stdtarg = m_buffered_stdtarg.stream ();
-
- if (m_buffered_current_uiout.has_value ())
- current_uiout->redirect (nullptr);
-
- if (m_buffered_uiout.has_value ())
- m_uiout->redirect (nullptr);
-}
-
-buffer_group::buffer_group (ui_out *uiout)
- : m_buffered_streams (new buffered_streams (this, uiout))
-{ /* Nothing. */ }
-
-/* See ui-out.h. */
-
-void
-buffer_group::flush () const
-{
- m_buffered_streams->remove_buffers ();
-
- for (const output_unit &ou : m_buffered_output)
- ou.flush ();
-}
diff --git a/gdb/ui-out.h b/gdb/ui-out.h
index ee5e68fa233..69d9910443e 100644
--- a/gdb/ui-out.h
+++ b/gdb/ui-out.h
@@ -475,184 +475,4 @@ class ui_out_redirect_pop
struct ui_out *m_uiout;
};
-struct buffered_streams;
-
-/* Organizes writes to a collection of buffered output streams
- so that when flushed, output is written to all streams in
- chronological order. */
-
-struct buffer_group
-{
- buffer_group (ui_out *uiout);
-
- /* Flush all buffered writes to the underlying output streams. */
- void flush () const;
-
- /* Record contents of BUF and associate it with STREAM. */
- void write (const char *buf, long length_buf, ui_file *stream);
-
- /* Record a wrap_here and associate it with STREAM. */
- void wrap_here (int indent, ui_file *stream);
-
- /* Record a call to flush and associate it with STREAM. */
- void flush_here (ui_file *stream);
-
-private:
-
- struct output_unit
- {
- output_unit (std::string msg, int wrap_hint = -1, bool flush = false)
- : m_msg (msg), m_wrap_hint (wrap_hint), m_flush (flush)
- {}
-
- /* Write contents of this output_unit to the underlying stream. */
- void flush () const;
-
- /* Underlying stream for which this output unit will be written to. */
- ui_file *m_stream;
-
- /* String to be written to underlying buffer. */
- std::string m_msg;
-
- /* Argument to wrap_here. -1 indicates no wrap. Used to call wrap_here
- during buffer flush. */
- int m_wrap_hint;
-
- /* Indicate that the underlying output stream's flush should be called. */
- bool m_flush;
- };
-
- /* Output_units to be written to buffered output streams. */
- std::vector<output_unit> m_buffered_output;
-
- /* Buffered output streams. */
- std::unique_ptr<buffered_streams> m_buffered_streams;
-};
-
-/* If FILE is a buffering_file, return its underlying stream. */
-
-extern ui_file *get_unbuffered (ui_file *file);
-
-/* Buffer output to gdb_stdout and gdb_stderr for the duration of FUNC. */
-
-template<typename F, typename... Arg>
-void
-do_with_buffered_output (F func, ui_out *uiout, Arg... args)
-{
- buffer_group g (uiout);
-
- try
- {
- func (uiout, std::forward<Arg> (args)...);
- }
- catch (gdb_exception &ex)
- {
- /* Ideally flush would be called in the destructor of buffer_group,
- however flushing might cause an exception to be thrown. Catch it
- and ensure the first exception propagates. */
- try
- {
- g.flush ();
- }
- catch (const gdb_exception &)
- {
- }
-
- throw_exception (std::move (ex));
- }
-
- /* Try was successful. Let any further exceptions propagate. */
- g.flush ();
-}
-
-/* Accumulate writes to an underlying ui_file. Output to the
- underlying file is deferred until required. */
-
-struct buffering_file : public ui_file
-{
- buffering_file (buffer_group *group, ui_file *stream)
- : m_group (group),
- m_stream (stream)
- { /* Nothing. */ }
-
- /* Return the underlying output stream. */
- ui_file *stream () const
- {
- return m_stream;
- }
-
- /* Record the contents of BUF. */
- void write (const char *buf, long length_buf) override
- {
- m_group->write (buf, length_buf, m_stream);
- }
-
- /* Record a wrap_here call with argument INDENT. */
- void wrap_here (int indent) override
- {
- m_group->wrap_here (indent, m_stream);
- }
-
- /* Return true if the underlying stream is a tty. */
- bool isatty () override
- {
- return m_stream->isatty ();
- }
-
- /* Return true if ANSI escapes can be used on the underlying stream. */
- bool can_emit_style_escape () override
- {
- return m_stream->can_emit_style_escape ();
- }
-
- /* Flush the underlying output stream. */
- void flush () override
- {
- return m_group->flush_here (m_stream);
- }
-
-private:
-
- /* Coordinates buffering across multiple buffering_files. */
- buffer_group *m_group;
-
- /* The underlying output stream. */
- ui_file *m_stream;
-};
-
-/* Attaches and detaches buffers for each of the gdb_std* streams. */
-
-struct buffered_streams
-{
- buffered_streams (buffer_group *group, ui_out *uiout);
-
- ~buffered_streams ()
- {
- this->remove_buffers ();
- }
-
- /* Remove buffering_files from all underlying streams. */
- void remove_buffers ();
-
-private:
-
- /* True if buffers are still attached to each underlying output stream. */
- bool m_buffers_in_place;
-
- /* Buffers for each gdb_std* output stream. */
- buffering_file m_buffered_stdout;
- buffering_file m_buffered_stderr;
- buffering_file m_buffered_stdlog;
- buffering_file m_buffered_stdtarg;
-
- /* Buffer for current_uiout's output stream. */
- std::optional<buffering_file> m_buffered_current_uiout;
-
- /* Additional ui_out being buffered. */
- ui_out *m_uiout;
-
- /* Buffer for m_uiout's output stream. */
- std::optional<buffering_file> m_buffered_uiout;
-};
-
#endif /* GDB_UI_OUT_H */
--
2.49.0
^ permalink raw reply [flat|nested] 23+ messages in thread* [PATCH v3 06/21] Remove TYPE_FN_FIELD_STUB and associated code
2026-01-30 13:17 [PATCH v3 00/21] Rework gdb logging and output redirection Tom Tromey
` (4 preceding siblings ...)
2026-01-30 13:17 ` [PATCH v3 05/21] Move buffered stream to new files Tom Tromey
@ 2026-01-30 13:17 ` Tom Tromey
2026-01-30 13:17 ` [PATCH v3 07/21] Change how stdin is handled in the UI Tom Tromey
` (14 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Tom Tromey @ 2026-01-30 13:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey, Andrew Burgess
This patch started by removing the error suppression code from
safe_parse_type, since I didn't think this was warranted.
However, looking at this a bit deeper showed that this is only called
from check_stub_method_group, which checks TYPE_FN_FIELD_STUB -- but
since the removal of the stabs code, nothing ever sets that flag, and
so a bunch of dead code can now be removed.
Approved-By: Andrew Burgess <aburgess@redhat.com>
---
gdb/c-typeprint.c | 35 ++++--------
gdb/gdbtypes.c | 167 ------------------------------------------------------
gdb/gdbtypes.h | 20 +------
gdb/gnu-v3-abi.c | 1 -
gdb/linespec.c | 2 -
gdb/valops.c | 6 --
6 files changed, 13 insertions(+), 218 deletions(-)
diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
index 683517a2176..b1689e192b5 100644
--- a/gdb/c-typeprint.c
+++ b/gdb/c-typeprint.c
@@ -1106,7 +1106,6 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
for (j = 0; j < len2; j++)
{
const char *mangled_name;
- gdb::unique_xmalloc_ptr<char> mangled_name_holder;
const char *physname = TYPE_FN_FIELD_PHYSNAME (f, j);
int is_full_physname_constructor =
TYPE_FN_FIELD_CONSTRUCTOR (f, j)
@@ -1151,14 +1150,7 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
gdb_puts (" ", stream);
}
- if (TYPE_FN_FIELD_STUB (f, j))
- {
- /* Build something we can demangle. */
- mangled_name_holder.reset (gdb_mangle_name (type, i, j));
- mangled_name = mangled_name_holder.get ();
- }
- else
- mangled_name = TYPE_FN_FIELD_PHYSNAME (f, j);
+ mangled_name = TYPE_FN_FIELD_PHYSNAME (f, j);
gdb::unique_xmalloc_ptr<char> demangled_name
= gdb_demangle (mangled_name,
@@ -1170,22 +1162,15 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
arguments, the demangling will fail.
Let's try to reconstruct the function
signature from the symbol information. */
- if (!TYPE_FN_FIELD_STUB (f, j))
- {
- int staticp = TYPE_FN_FIELD_STATIC_P (f, j);
- struct type *mtype = TYPE_FN_FIELD_TYPE (f, j);
-
- cp_type_print_method_args (mtype,
- "",
- method_name,
- staticp,
- stream, language,
- &local_flags);
- }
- else
- fprintf_styled (stream, metadata_style.style (),
- _("<badly mangled name '%s'>"),
- mangled_name);
+ int staticp = TYPE_FN_FIELD_STATIC_P (f, j);
+ struct type *mtype = TYPE_FN_FIELD_TYPE (f, j);
+
+ cp_type_print_method_args (mtype,
+ "",
+ method_name,
+ staticp,
+ stream, language,
+ &local_flags);
}
else
{
diff --git a/gdb/gdbtypes.c b/gdb/gdbtypes.c
index 75404d00e1a..a08336b5669 100644
--- a/gdb/gdbtypes.c
+++ b/gdb/gdbtypes.c
@@ -3141,171 +3141,6 @@ check_typedef (struct type *type)
return type;
}
-/* Parse a type expression in the string [P..P+LENGTH). If an error
- occurs, silently return a void type. */
-
-static struct type *
-safe_parse_type (struct gdbarch *gdbarch, const char *p, int length)
-{
- struct type *type = NULL; /* Initialize to keep gcc happy. */
-
- /* Suppress error messages. */
- scoped_restore saved_gdb_stderr = make_scoped_restore (&gdb_stderr,
- &null_stream);
-
- /* Call parse_and_eval_type() without fear of longjmp()s. */
- try
- {
- type = parse_and_eval_type (p, length);
- }
- catch (const gdb_exception_error &except)
- {
- type = builtin_type (gdbarch)->builtin_void;
- }
-
- return type;
-}
-
-/* Ugly hack to convert method stubs into method types.
-
- He ain't kiddin'. This demangles the name of the method into a
- string including argument types, parses out each argument type,
- generates a string casting a zero to that type, evaluates the
- string, and stuffs the resulting type into an argtype vector!!!
- Then it knows the type of the whole function (including argument
- types for overloading), which info used to be in the stab's but was
- removed to hack back the space required for them. */
-
-static void
-check_stub_method (struct type *type, int method_id, int signature_id)
-{
- struct gdbarch *gdbarch = type->arch ();
- struct fn_field *f;
- char *mangled_name = gdb_mangle_name (type, method_id, signature_id);
- gdb::unique_xmalloc_ptr<char> demangled_name
- = gdb_demangle (mangled_name, DMGL_PARAMS | DMGL_ANSI);
- char *argtypetext, *p;
- int depth = 0, argcount = 1;
- struct field *argtypes;
- struct type *mtype;
-
- /* Make sure we got back a function string that we can use. */
- if (demangled_name)
- p = strchr (demangled_name.get (), '(');
- else
- p = NULL;
-
- if (demangled_name == NULL || p == NULL)
- error (_("Internal: Cannot demangle mangled name `%s'."),
- mangled_name);
-
- /* Now, read in the parameters that define this type. */
- p += 1;
- argtypetext = p;
- while (*p)
- {
- if (*p == '(' || *p == '<')
- {
- depth += 1;
- }
- else if (*p == ')' || *p == '>')
- {
- depth -= 1;
- }
- else if (*p == ',' && depth == 0)
- {
- argcount += 1;
- }
-
- p += 1;
- }
-
- /* If we read one argument and it was ``void'', don't count it. */
- if (startswith (argtypetext, "(void)"))
- argcount -= 1;
-
- /* We need one extra slot, for the THIS pointer. */
-
- argtypes = (struct field *)
- TYPE_ZALLOC (type, (argcount + 1) * sizeof (struct field));
- p = argtypetext;
-
- /* Add THIS pointer for non-static methods. */
- f = TYPE_FN_FIELDLIST1 (type, method_id);
- if (TYPE_FN_FIELD_STATIC_P (f, signature_id))
- argcount = 0;
- else
- {
- argtypes[0].set_type (lookup_pointer_type (type));
- argcount = 1;
- }
-
- if (*p != ')') /* () means no args, skip while. */
- {
- depth = 0;
- while (*p)
- {
- if (depth <= 0 && (*p == ',' || *p == ')'))
- {
- /* Avoid parsing of ellipsis, they will be handled below.
- Also avoid ``void'' as above. */
- if (strncmp (argtypetext, "...", p - argtypetext) != 0
- && strncmp (argtypetext, "void", p - argtypetext) != 0)
- {
- argtypes[argcount].set_type
- (safe_parse_type (gdbarch, argtypetext, p - argtypetext));
- argcount += 1;
- }
- argtypetext = p + 1;
- }
-
- if (*p == '(' || *p == '<')
- {
- depth += 1;
- }
- else if (*p == ')' || *p == '>')
- {
- depth -= 1;
- }
-
- p += 1;
- }
- }
-
- TYPE_FN_FIELD_PHYSNAME (f, signature_id) = mangled_name;
-
- /* Now update the old "stub" type into a real type. */
- mtype = TYPE_FN_FIELD_TYPE (f, signature_id);
- /* MTYPE may currently be a function (TYPE_CODE_FUNC).
- We want a method (TYPE_CODE_METHOD). */
- smash_to_method_type (mtype, type, mtype->target_type (),
- gdb::make_array_view (argtypes, argcount),
- p[-2] == '.');
- mtype->set_is_stub (false);
- TYPE_FN_FIELD_STUB (f, signature_id) = 0;
-}
-
-/* This is the external interface to check_stub_method, above. This
- function unstubs all of the signatures for TYPE's METHOD_ID method
- name. After calling this function TYPE_FN_FIELD_STUB will be
- cleared for each signature and TYPE_FN_FIELDLIST_NAME will be
- correct.
-
- This function unfortunately can not die until stabs do. */
-
-void
-check_stub_method_group (struct type *type, int method_id)
-{
- int len = TYPE_FN_FIELDLIST_LENGTH (type, method_id);
- struct fn_field *f = TYPE_FN_FIELDLIST1 (type, method_id);
-
- for (int j = 0; j < len; j++)
- {
- if (TYPE_FN_FIELD_STUB (f, j))
- check_stub_method (type, method_id, j);
- }
-}
-
/* Ensure it is in .rodata (if available) by working around GCC PR 44690. */
const struct cplus_struct_type cplus_struct_default = { };
@@ -4988,8 +4823,6 @@ dump_fn_fieldlists (struct type *type, int spaces)
TYPE_FN_FIELD_PRIVATE (f, overload_idx));
gdb_printf ("%*sis_protected %d\n", spaces + 8, "",
TYPE_FN_FIELD_PROTECTED (f, overload_idx));
- gdb_printf ("%*sis_stub %d\n", spaces + 8, "",
- TYPE_FN_FIELD_STUB (f, overload_idx));
gdb_printf ("%*sdefaulted %d\n", spaces + 8, "",
TYPE_FN_FIELD_DEFAULTED (f, overload_idx));
gdb_printf ("%*sis_deleted %d\n", spaces + 8, "",
diff --git a/gdb/gdbtypes.h b/gdb/gdbtypes.h
index 8ba314eb663..986999f1191 100644
--- a/gdb/gdbtypes.h
+++ b/gdb/gdbtypes.h
@@ -1624,15 +1624,9 @@ struct fn_fieldlist
struct fn_field
{
- /* * If is_stub is clear, this is the mangled name which we can look
- up to find the address of the method (FIXME: it would be cleaner
- to have a pointer to the struct symbol here instead).
-
- If is_stub is set, this is the portion of the mangled name which
- specifies the arguments. For example, "ii", if there are two int
- arguments, or "" if there are no arguments. See gdb_mangle_name
- for the conversion from this format to the one used if is_stub is
- clear. */
+ /* * This is the mangled name which we can look up to find the
+ address of the method (FIXME: it would be cleaner to have a
+ pointer to the struct symbol here instead). */
const char *physname;
@@ -1655,11 +1649,6 @@ struct fn_field
unsigned int is_volatile:1;
unsigned int is_artificial:1;
- /* * A stub method only has some fields valid (but they are enough
- to reconstruct the rest of the fields). */
-
- unsigned int is_stub:1;
-
/* * True if this function is a constructor, false otherwise. */
unsigned int is_constructor : 1;
@@ -2005,7 +1994,6 @@ extern void set_type_vptr_basetype (struct type *, struct type *);
#define TYPE_FN_FIELD_PROTECTED(thisfn, n) \
((thisfn)[n].accessibility == accessibility::PROTECTED)
#define TYPE_FN_FIELD_ARTIFICIAL(thisfn, n) ((thisfn)[n].is_artificial)
-#define TYPE_FN_FIELD_STUB(thisfn, n) ((thisfn)[n].is_stub)
#define TYPE_FN_FIELD_CONSTRUCTOR(thisfn, n) ((thisfn)[n].is_constructor)
#define TYPE_FN_FIELD_FCONTEXT(thisfn, n) ((thisfn)[n].fcontext)
#define TYPE_FN_FIELD_VOFFSET(thisfn, n) ((thisfn)[n].voffset-2)
@@ -2693,8 +2681,6 @@ extern void apply_bit_offset_to_field (struct field &field,
extern struct type *check_typedef (struct type *);
-extern void check_stub_method_group (struct type *, int);
-
extern char *gdb_mangle_name (struct type *, int, int);
/* Lookup a typedef or primitive type named NAME, visible in lexical block
diff --git a/gdb/gnu-v3-abi.c b/gdb/gnu-v3-abi.c
index 4a4ed899a06..247a866be95 100644
--- a/gdb/gnu-v3-abi.c
+++ b/gdb/gnu-v3-abi.c
@@ -544,7 +544,6 @@ gnuv3_find_method_in (struct type *domain, CORE_ADDR voffset,
f = TYPE_FN_FIELDLIST1 (domain, i);
len2 = TYPE_FN_FIELDLIST_LENGTH (domain, i);
- check_stub_method_group (domain, i);
for (j = 0; j < len2; j++)
if (TYPE_FN_FIELD_VOFFSET (f, j) == voffset)
return TYPE_FN_FIELD_PHYSNAME (f, j);
diff --git a/gdb/linespec.c b/gdb/linespec.c
index edd5f917f3a..4376a769e49 100644
--- a/gdb/linespec.c
+++ b/gdb/linespec.c
@@ -1252,8 +1252,6 @@ find_methods (struct type *t, enum language t_lang, const char *name,
const char *phys_name;
f = TYPE_FN_FIELDLIST1 (t, method_counter);
- if (TYPE_FN_FIELD_STUB (f, field_counter))
- continue;
phys_name = TYPE_FN_FIELD_PHYSNAME (f, field_counter);
result_names->push_back (phys_name);
}
diff --git a/gdb/valops.c b/gdb/valops.c
index 568f8f84a9e..c89e5c4adc8 100644
--- a/gdb/valops.c
+++ b/gdb/valops.c
@@ -2209,7 +2209,6 @@ search_struct_method (const char *name, struct value **arg1p,
struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
name_matched = 1;
- check_stub_method_group (type, i);
if (j > 0 && !args.has_value ())
error (_("cannot resolve overloaded method "
"`%s': no arguments supplied"), name);
@@ -2510,9 +2509,6 @@ find_method_list (struct value **argp, const char *method,
*basetype = type;
*boffset = offset;
- /* Resolve any stub methods. */
- check_stub_method_group (type, i);
-
break;
}
}
@@ -3649,8 +3645,6 @@ value_struct_elt_for_reference (struct type *domain, int offset,
int len = TYPE_FN_FIELDLIST_LENGTH (t, i);
struct fn_field *f = TYPE_FN_FIELDLIST1 (t, i);
- check_stub_method_group (t, i);
-
if (intype)
{
for (j = 0; j < len; ++j)
--
2.49.0
^ permalink raw reply [flat|nested] 23+ messages in thread* [PATCH v3 07/21] Change how stdin is handled in the UI
2026-01-30 13:17 [PATCH v3 00/21] Rework gdb logging and output redirection Tom Tromey
` (5 preceding siblings ...)
2026-01-30 13:17 ` [PATCH v3 06/21] Remove TYPE_FN_FIELD_STUB and associated code Tom Tromey
@ 2026-01-30 13:17 ` Tom Tromey
2026-01-30 13:17 ` [PATCH v3 08/21] Remove gdb_stdtargin Tom Tromey
` (13 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Tom Tromey @ 2026-01-30 13:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
gdb_stdin is never overridden, so it doesn't need to be an lvalue.
This patch changes how it is implemented.
Future patches will change the other streams here, but I thought since
this one is truly different from the others, it should be handled
separately.
---
gdb/top.c | 6 +++---
gdb/utils.h | 6 +++---
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/gdb/top.c b/gdb/top.c
index e7d1ded96e4..5819dfa7897 100644
--- a/gdb/top.c
+++ b/gdb/top.c
@@ -97,10 +97,10 @@ current_ui_gdb_stdout_ptr ()
return ¤t_ui->m_gdb_stdout;
}
-struct ui_file **
-current_ui_gdb_stdin_ptr ()
+struct ui_file *
+current_ui_gdb_stdin ()
{
- return ¤t_ui->m_gdb_stdin;
+ return current_ui->m_gdb_stdin;
}
struct ui_file **
diff --git a/gdb/utils.h b/gdb/utils.h
index 4f11cbf379f..28824ad2e1a 100644
--- a/gdb/utils.h
+++ b/gdb/utils.h
@@ -166,7 +166,7 @@ extern bool pagination_enabled;
extern bool debug_timestamp;
extern struct ui_file **current_ui_gdb_stdout_ptr (void);
-extern struct ui_file **current_ui_gdb_stdin_ptr (void);
+extern struct ui_file *current_ui_gdb_stdin ();
extern struct ui_file **current_ui_gdb_stderr_ptr (void);
extern struct ui_file **current_ui_gdb_stdlog_ptr (void);
extern struct ui_file **current_ui_gdb_stdtarg_ptr ();
@@ -178,8 +178,8 @@ extern void gdb_flush (struct ui_file *stream);
/* Normal results */
#define gdb_stdout (*current_ui_gdb_stdout_ptr ())
-/* Input stream */
-#define gdb_stdin (*current_ui_gdb_stdin_ptr ())
+/* Input stream. */
+#define gdb_stdin (current_ui_gdb_stdin ())
/* Serious error notifications. This bypasses the pager, if one is in
use. */
#define gdb_stderr (*current_ui_gdb_stderr_ptr ())
--
2.49.0
^ permalink raw reply [flat|nested] 23+ messages in thread* [PATCH v3 08/21] Remove gdb_stdtargin
2026-01-30 13:17 [PATCH v3 00/21] Rework gdb logging and output redirection Tom Tromey
` (6 preceding siblings ...)
2026-01-30 13:17 ` [PATCH v3 07/21] Change how stdin is handled in the UI Tom Tromey
@ 2026-01-30 13:17 ` Tom Tromey
2026-01-30 13:17 ` [PATCH v3 09/21] Improve fputs_highlighted by using ui_file::write Tom Tromey
` (12 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Tom Tromey @ 2026-01-30 13:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
gdb_stdtargin is only used in a single place; and, strangely, does not
follow the current UI. This patch removes the global and changes the
one user to use gdb_stdin instead.
---
gdb/main.c | 5 -----
gdb/remote-fileio.c | 2 +-
gdb/utils.h | 4 ----
3 files changed, 1 insertion(+), 10 deletions(-)
diff --git a/gdb/main.c b/gdb/main.c
index ac83b953cf2..c9bd79a4f8b 100644
--- a/gdb/main.c
+++ b/gdb/main.c
@@ -79,9 +79,6 @@ static int gdb_datadir_provided = 0;
the possibly relocated path to python's lib directory. */
std::string python_libdir;
-/* Target IO streams. */
-struct ui_file *gdb_stdtargin;
-
/* True if --batch or --batch-silent was seen. */
int batch_flag = 0;
@@ -694,8 +691,6 @@ captured_main_1 (struct captured_main_args *context)
gdb_internal_backtrace_init_str ();
current_ui = main_ui;
- gdb_stdtargin = gdb_stdin;
-
/* Put a CLI based uiout in place early. If the early initialization
files trigger any I/O then it isn't hard to reach parts of GDB that
assume current_uiout is not nullptr. Maybe we should just install the
diff --git a/gdb/remote-fileio.c b/gdb/remote-fileio.c
index 61dfb2552aa..459f249d37b 100644
--- a/gdb/remote-fileio.c
+++ b/gdb/remote-fileio.c
@@ -542,7 +542,7 @@ remote_fileio_func_read (remote_target *remote, char *buf)
limit this read to something smaller than that - by a
safe margin, in case the limit depends on system
resources or version. */
- ret = gdb_stdtargin->read ((char *) buffer, 16383);
+ ret = gdb_stdin->read ((char *) buffer, 16383);
if (ret > 0 && (size_t)ret > length)
{
remaining_buf = (char *) xmalloc (ret - length);
diff --git a/gdb/utils.h b/gdb/utils.h
index 28824ad2e1a..4d888b80274 100644
--- a/gdb/utils.h
+++ b/gdb/utils.h
@@ -189,10 +189,6 @@ extern void gdb_flush (struct ui_file *stream);
/* Target output. */
#define gdb_stdtarg (*current_ui_gdb_stdtarg_ptr ())
-/* Truly global ui_file streams. These are all defined in main.c. */
-
-extern struct ui_file *gdb_stdtargin;
-
/* Set the screen dimensions to WIDTH and HEIGHT. */
extern void set_screen_width_and_height (int width, int height);
--
2.49.0
^ permalink raw reply [flat|nested] 23+ messages in thread* [PATCH v3 09/21] Improve fputs_highlighted by using ui_file::write
2026-01-30 13:17 [PATCH v3 00/21] Rework gdb logging and output redirection Tom Tromey
` (7 preceding siblings ...)
2026-01-30 13:17 ` [PATCH v3 08/21] Remove gdb_stdtargin Tom Tromey
@ 2026-01-30 13:17 ` Tom Tromey
2026-01-30 13:17 ` [PATCH v3 10/21] Add stream to buffer_group::output_unit constructor Tom Tromey
` (11 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Tom Tromey @ 2026-01-30 13:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey, Andrew Burgess
I noticed that fputs_highlighted writes a single character at a time.
It's more idiomatic to use ui_file::write.
Approved-By: Andrew Burgess <aburgess@redhat.com>
---
gdb/utils.c | 18 ++++++++----------
1 file changed, 8 insertions(+), 10 deletions(-)
diff --git a/gdb/utils.c b/gdb/utils.c
index ef2d0fa0c41..f4c9e584628 100644
--- a/gdb/utils.c
+++ b/gdb/utils.c
@@ -1921,22 +1921,20 @@ fputs_highlighted (const char *str, const compiled_regex &highlight,
size_t n_highlight = pmatch.rm_eo - pmatch.rm_so;
/* Output the part before pmatch with current style. */
- while (pmatch.rm_so > 0)
+ if (pmatch.rm_so > 0)
{
- gdb_putc (*str, stream);
- pmatch.rm_so--;
- str++;
+ stream->write (str, pmatch.rm_so);
+ str += pmatch.rm_so;
}
/* Output pmatch with the highlight style. */
- stream->emit_style_escape (highlight_style.style ());
- while (n_highlight > 0)
+ if (n_highlight > 0)
{
- gdb_putc (*str, stream);
- n_highlight--;
- str++;
+ stream->emit_style_escape (highlight_style.style ());
+ stream->write (str, n_highlight);
+ str += n_highlight;
+ stream->emit_style_escape (ui_file_style ());
}
- stream->emit_style_escape (ui_file_style ());
}
/* Output the trailing part of STR not matching HIGHLIGHT. */
--
2.49.0
^ permalink raw reply [flat|nested] 23+ messages in thread* [PATCH v3 10/21] Add stream to buffer_group::output_unit constructor
2026-01-30 13:17 [PATCH v3 00/21] Rework gdb logging and output redirection Tom Tromey
` (8 preceding siblings ...)
2026-01-30 13:17 ` [PATCH v3 09/21] Improve fputs_highlighted by using ui_file::write Tom Tromey
@ 2026-01-30 13:17 ` Tom Tromey
2026-01-30 13:17 ` [PATCH v3 11/21] Restore ui_file::can_page Tom Tromey
` (10 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Tom Tromey @ 2026-01-30 13:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey, Andrew Burgess
I noticed that when creating a new output_unit, all the calls looked
like:
m_buffered_output.emplace_back ("", indent).m_stream = stream;
It seems better to simply pass the stream to the constructor.
Approved-By: Andrew Burgess <aburgess@redhat.com>
---
gdb/buffered-streams.c | 6 +++---
gdb/buffered-streams.h | 6 ++++--
2 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/gdb/buffered-streams.c b/gdb/buffered-streams.c
index a9d3fcf4f5d..71122c7f237 100644
--- a/gdb/buffered-streams.c
+++ b/gdb/buffered-streams.c
@@ -51,7 +51,7 @@ buffer_group::write (const char *buf, long length_buf, ui_file *stream)
&& m_buffered_output.back ().m_msg.back () != '\n')
m_buffered_output.back ().m_msg.append (msg);
else
- m_buffered_output.emplace_back (msg).m_stream = stream;
+ m_buffered_output.emplace_back (stream, msg);
prev = cur + 1;
}
}
@@ -61,7 +61,7 @@ buffer_group::write (const char *buf, long length_buf, ui_file *stream)
void
buffer_group::wrap_here (int indent, ui_file *stream)
{
- m_buffered_output.emplace_back ("", indent).m_stream = stream;
+ m_buffered_output.emplace_back (stream, "", indent);
}
/* See buffered-streams.h. */
@@ -69,7 +69,7 @@ buffer_group::wrap_here (int indent, ui_file *stream)
void
buffer_group::flush_here (ui_file *stream)
{
- m_buffered_output.emplace_back ("", -1, true).m_stream = stream;
+ m_buffered_output.emplace_back (stream, "", -1, true);
}
/* See buffered-streams.h. */
diff --git a/gdb/buffered-streams.h b/gdb/buffered-streams.h
index 0da2d8a8f43..9da45b08b6b 100644
--- a/gdb/buffered-streams.h
+++ b/gdb/buffered-streams.h
@@ -48,8 +48,10 @@ struct buffer_group
struct output_unit
{
- output_unit (std::string msg, int wrap_hint = -1, bool flush = false)
- : m_msg (msg), m_wrap_hint (wrap_hint), m_flush (flush)
+ output_unit (ui_file *stream, std::string msg, int wrap_hint = -1,
+ bool flush = false)
+ : m_stream (stream), m_msg (msg), m_wrap_hint (wrap_hint),
+ m_flush (flush)
{}
/* Write contents of this output_unit to the underlying stream. */
--
2.49.0
^ permalink raw reply [flat|nested] 23+ messages in thread* [PATCH v3 11/21] Restore ui_file::can_page
2026-01-30 13:17 [PATCH v3 00/21] Rework gdb logging and output redirection Tom Tromey
` (9 preceding siblings ...)
2026-01-30 13:17 ` [PATCH v3 10/21] Add stream to buffer_group::output_unit constructor Tom Tromey
@ 2026-01-30 13:17 ` Tom Tromey
2026-01-30 13:17 ` [PATCH v3 12/21] Rewrite cli-style.c:do_show Tom Tromey
` (9 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Tom Tromey @ 2026-01-30 13:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
A while back, I removed the ui_file::can_page method. It wasn't
needed for a while, due to how output redirections were done.
With the new approach in this series, it's convenient to have this
method again, because the pipeline downstream from the pager won't
change when logging -- the logging filter will be enabled or disabled
without reconfiguring the pipeline. This method lets the pager adapt
correctly.
---
gdb/ui-file.h | 18 ++++++++++++++++++
gdb/utils.c | 6 ++++--
2 files changed, 22 insertions(+), 2 deletions(-)
diff --git a/gdb/ui-file.h b/gdb/ui-file.h
index 231c32deafc..4aaf4d0e54e 100644
--- a/gdb/ui-file.h
+++ b/gdb/ui-file.h
@@ -101,6 +101,14 @@ class ui_file
virtual int fd () const
{ return -1; }
+ /* Return true if this object supports paging, false otherwise. */
+ virtual bool can_page () const
+ {
+ /* Almost no file supports paging, which is why this is the
+ default. */
+ return false;
+ }
+
/* Indicate that if the next sequence of characters overflows the
line, a newline should be inserted here rather than when it hits
the end. If INDENT is non-zero, it is a number of spaces to be
@@ -270,6 +278,11 @@ class stdio_file : public ui_file
int fd () const override
{ return m_fd; }
+ bool can_page () const override
+ {
+ return m_file == stdout;
+ }
+
private:
/* Sets the internal stream to FILE, and saves the FILE's file
descriptor in M_FD. */
@@ -452,6 +465,11 @@ class wrapped_file : public ui_file
void write_async_safe (const char *buf, long length_buf) override
{ return m_stream->write_async_safe (buf, length_buf); }
+ bool can_page () const override
+ {
+ return m_stream->can_page ();
+ }
+
protected:
explicit wrapped_file (T stream)
diff --git a/gdb/utils.c b/gdb/utils.c
index f4c9e584628..03a1d9146f2 100644
--- a/gdb/utils.c
+++ b/gdb/utils.c
@@ -1640,7 +1640,8 @@ pager_file::check_for_overfull_line (const unsigned int lines_allowed)
this loop, so we must continue to check it here. */
if (pagination_enabled
&& !pagination_disabled_for_command
- && lines_printed >= lines_allowed)
+ && lines_printed >= lines_allowed
+ && m_stream->can_page ())
{
prompt_for_continue ();
did_paginate = true;
@@ -1713,7 +1714,8 @@ pager_file::puts (const char *linebuffer)
it here. */
if (pagination_enabled
&& !pagination_disabled_for_command
- && lines_printed >= lines_allowed)
+ && lines_printed >= lines_allowed
+ && m_stream->can_page ())
prompt_for_continue ();
while (*linebuffer != '\0' && *linebuffer != '\n')
--
2.49.0
^ permalink raw reply [flat|nested] 23+ messages in thread* [PATCH v3 12/21] Rewrite cli-style.c:do_show
2026-01-30 13:17 [PATCH v3 00/21] Rework gdb logging and output redirection Tom Tromey
` (10 preceding siblings ...)
2026-01-30 13:17 ` [PATCH v3 11/21] Restore ui_file::can_page Tom Tromey
@ 2026-01-30 13:17 ` Tom Tromey
2026-01-30 13:17 ` [PATCH v3 13/21] Remove m_applied_style from ui_file Tom Tromey
` (8 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Tom Tromey @ 2026-01-30 13:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey, Andrew Burgess
IMO it looks a little nicer if cli-style.c:do_show uses a single
printf to display its output. This is also slightly more
i18-friendly.
I noticed this while debugging a case where multiple redundant ANSI
escapes were sent. In conjunction with a subsequent patch, this
approach happens to fix that problem. FWIW I don't consider such
redundancies to be bugs, but it was convenient to change this one.
Approved-By: Andrew Burgess <aburgess@redhat.com>
---
gdb/cli/cli-style.c | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/gdb/cli/cli-style.c b/gdb/cli/cli-style.c
index 411d8d91347..ac4fb27e725 100644
--- a/gdb/cli/cli-style.c
+++ b/gdb/cli/cli-style.c
@@ -262,9 +262,8 @@ do_show (const char *what, struct ui_file *file,
const char *value)
{
cli_style_option *cso = (cli_style_option *) cmd->context ();
- gdb_puts (_("The "), file);
- fprintf_styled (file, cso->style (), _("\"%s\" style"), cso->name ());
- gdb_printf (file, _(" %s is: %s\n"), what, value);
+ gdb_printf (file, _("The %p[\"%s\" style%p] %s is: %s\n"),
+ cso->style ().ptr (), cso->name (), nullptr, what, value);
}
/* See cli-style.h. */
--
2.49.0
^ permalink raw reply [flat|nested] 23+ messages in thread* [PATCH v3 13/21] Remove m_applied_style from ui_file
2026-01-30 13:17 [PATCH v3 00/21] Rework gdb logging and output redirection Tom Tromey
` (11 preceding siblings ...)
2026-01-30 13:17 ` [PATCH v3 12/21] Rewrite cli-style.c:do_show Tom Tromey
@ 2026-01-30 13:17 ` Tom Tromey
2026-01-30 13:17 ` [PATCH v3 14/21] Add a new logging_file implementation Tom Tromey
` (7 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Tom Tromey @ 2026-01-30 13:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey, Andrew Burgess
While working on this series, I found a number of odd styling issues
recurred. For instance, the issue where the pager would lose track
and style subsequent output incorrectly reappeared.
It turned out that different ui_file objects in the output pipeline
would get confused about their current style. And, looking deeper at
this, I realized that mainly it is the pager that really needs to
track the current style at all. All the other file implementations
can be purely reactive (except the buffered stream code, as Andrew
pointed out).
This patch moves m_applied_style from ui_file and into pager_file.
This necessitated making ui_file::vprintf virtual, so that the base
class could pass in the "plain" style as the starting point, whereas
the pager could use the applied style. (I did not investigate whether
this was truly necessary, and I somewhat suspect it might not be.)
This straightforward approach caused some regressions, mostly
involving extra ANSI escapes being emitted. I fixed most of these by
arranging for ui_out::call_do_message to track styles a little more
thoroughly.
Co-Authored-By: Andrew Burgess <aburgess@redhat.com>
---
gdb/buffered-streams.h | 12 +++++
gdb/cli-out.c | 20 ++++++--
gdb/cli-out.h | 7 ++-
gdb/mi/mi-out.c | 3 +-
gdb/mi/mi-out.h | 5 +-
| 6 +++
gdb/python/py-uiout.h | 5 +-
gdb/testsuite/gdb.python/py-styled-execute.exp | 3 +-
gdb/ui-file.c | 9 ++--
gdb/ui-file.h | 7 +--
gdb/ui-out.c | 63 +++++++++++++++++---------
gdb/ui-out.h | 22 +++++++--
gdb/utils.c | 7 +++
13 files changed, 118 insertions(+), 51 deletions(-)
diff --git a/gdb/buffered-streams.h b/gdb/buffered-streams.h
index 9da45b08b6b..62598e70ca0 100644
--- a/gdb/buffered-streams.h
+++ b/gdb/buffered-streams.h
@@ -154,6 +154,15 @@ struct buffering_file : public ui_file
return m_stream->can_emit_style_escape ();
}
+ void emit_style_escape (const ui_file_style &style) override
+ {
+ if (can_emit_style_escape () && style != m_applied_style)
+ {
+ m_applied_style = style;
+ ui_file::emit_style_escape (style);
+ }
+ }
+
/* Flush the underlying output stream. */
void flush () override
{
@@ -167,6 +176,9 @@ struct buffering_file : public ui_file
/* The underlying output stream. */
ui_file *m_stream;
+
+ /* The currently applied style. */
+ ui_file_style m_applied_style;
};
/* Attaches and detaches buffers for each of the gdb_std* streams. */
diff --git a/gdb/cli-out.c b/gdb/cli-out.c
index b1ea9560fcf..d8ac13ab827 100644
--- a/gdb/cli-out.c
+++ b/gdb/cli-out.c
@@ -221,20 +221,24 @@ cli_ui_out::do_text (const char *string)
}
void
-cli_ui_out::do_message (const ui_file_style &style,
+cli_ui_out::do_message (ui_file_style ¤t_style,
+ const ui_file_style &style,
const char *format, va_list args)
{
if (m_suppress_output)
return;
std::string str = string_vprintf (format, args);
- if (!str.empty ())
+ if (str.empty ())
+ return;
+
+ ui_file *stream = m_streams.back ();
+ if (current_style != style)
{
- ui_file *stream = m_streams.back ();
stream->emit_style_escape (style);
- stream->puts (str.c_str ());
- stream->emit_style_escape (ui_file_style ());
+ current_style = style;
}
+ stream->puts (str.c_str ());
}
void
@@ -489,6 +493,12 @@ cli_ui_out::can_emit_style_escape () const
return m_streams.back ()->can_emit_style_escape ();
}
+void
+cli_ui_out::emit_style_escape (const ui_file_style &style)
+{
+ m_streams.back ()->emit_style_escape (style);
+}
+
/* CLI interface to display tab-completion matches. */
/* CLI version of displayer.crlf. */
diff --git a/gdb/cli-out.h b/gdb/cli-out.h
index b34601f0f46..74307074a7c 100644
--- a/gdb/cli-out.h
+++ b/gdb/cli-out.h
@@ -35,6 +35,8 @@ class cli_ui_out : public ui_out
bool can_emit_style_escape () const override;
+ void emit_style_escape (const ui_file_style &style) override;
+
ui_file *current_stream () const override
{ return m_streams.back (); }
@@ -69,9 +71,10 @@ class cli_ui_out : public ui_out
override ATTRIBUTE_PRINTF (7, 0);
virtual void do_spaces (int numspaces) override;
virtual void do_text (const char *string) override;
- virtual void do_message (const ui_file_style &style,
+ virtual void do_message (ui_file_style ¤t_style,
+ const ui_file_style &style,
const char *format, va_list args) override
- ATTRIBUTE_PRINTF (3,0);
+ ATTRIBUTE_PRINTF (4, 0);
virtual void do_wrap_hint (int indent) override;
virtual void do_flush () override;
virtual void do_redirect (struct ui_file *outstream) override;
diff --git a/gdb/mi/mi-out.c b/gdb/mi/mi-out.c
index aac00ae7f76..41b0ee8cdbd 100644
--- a/gdb/mi/mi-out.c
+++ b/gdb/mi/mi-out.c
@@ -167,7 +167,8 @@ mi_ui_out::do_text (const char *string)
}
void
-mi_ui_out::do_message (const ui_file_style &style,
+mi_ui_out::do_message (ui_file_style ¤t_style,
+ const ui_file_style &style,
const char *format, va_list args)
{
}
diff --git a/gdb/mi/mi-out.h b/gdb/mi/mi-out.h
index ee70659abd3..d1d26718ac4 100644
--- a/gdb/mi/mi-out.h
+++ b/gdb/mi/mi-out.h
@@ -78,9 +78,10 @@ class mi_ui_out : public ui_out
override ATTRIBUTE_PRINTF (7,0);
virtual void do_spaces (int numspaces) override;
virtual void do_text (const char *string) override;
- virtual void do_message (const ui_file_style &style,
+ virtual void do_message (ui_file_style ¤t_style,
+ const ui_file_style &style,
const char *format, va_list args) override
- ATTRIBUTE_PRINTF (3,0);
+ ATTRIBUTE_PRINTF (4, 0);
virtual void do_wrap_hint (int indent) override;
virtual void do_flush () override;
virtual void do_redirect (struct ui_file *outstream) override;
--git a/gdb/pager.h b/gdb/pager.h
index 697134be805..114024d5e5f 100644
--- a/gdb/pager.h
+++ b/gdb/pager.h
@@ -51,6 +51,9 @@ class pager_file : public wrapped_file<ui_file_up>
m_stream->puts_unfiltered (str);
}
+ void vprintf (const char *fmt, va_list args) override
+ ATTRIBUTE_PRINTF (2, 0);
+
private:
void prompt_for_continue ();
@@ -92,6 +95,9 @@ class pager_file : public wrapped_file<ui_file_up>
wrapping is not in effect. */
int m_wrap_column = 0;
+ /* The currently applied style. */
+ ui_file_style m_applied_style;
+
/* The style applied at the time that wrap_here was called. */
ui_file_style m_wrap_style;
diff --git a/gdb/python/py-uiout.h b/gdb/python/py-uiout.h
index 159f1b22e46..21c068e9077 100644
--- a/gdb/python/py-uiout.h
+++ b/gdb/python/py-uiout.h
@@ -109,9 +109,10 @@ class py_ui_out : public ui_out
void do_text (const char *string) override
{ }
- void do_message (const ui_file_style &style,
+ void do_message (ui_file_style ¤t_style,
+ const ui_file_style &style,
const char *format, va_list args)
- override ATTRIBUTE_PRINTF (3,0)
+ override ATTRIBUTE_PRINTF (4, 0)
{ }
void do_wrap_hint (int indent) override
diff --git a/gdb/testsuite/gdb.python/py-styled-execute.exp b/gdb/testsuite/gdb.python/py-styled-execute.exp
index afaaf928573..efc65d3be50 100644
--- a/gdb/testsuite/gdb.python/py-styled-execute.exp
+++ b/gdb/testsuite/gdb.python/py-styled-execute.exp
@@ -60,8 +60,7 @@ proc test_gdb_execute_styling {} {
# Two possible outputs, BASIC_RE, the unstyled output text, or
# STYLED_RE, the same things, but with styling applied.
set text "\"version\" style"
- set styled_text \
- [style "\"" version][style "version" version][style "\" style" version]
+ set styled_text [style $text version]
set basic_re "The $text foreground color is: \[^\r\n\]+"
set styled_re "The $styled_text foreground color is: \[^\r\n\]+"
diff --git a/gdb/ui-file.c b/gdb/ui-file.c
index c5931deaf20..d655e7edd9e 100644
--- a/gdb/ui-file.c
+++ b/gdb/ui-file.c
@@ -70,7 +70,7 @@ void
ui_file::vprintf (const char *format, va_list args)
{
ui_out_flags flags = disallow_ui_out_field;
- cli_ui_out (this, flags).vmessage (m_applied_style, format, args);
+ cli_ui_out (this, flags).vmessage ({}, format, args);
}
/* See ui-file.h. */
@@ -78,11 +78,8 @@ ui_file::vprintf (const char *format, va_list args)
void
ui_file::emit_style_escape (const ui_file_style &style)
{
- if (can_emit_style_escape () && style != m_applied_style)
- {
- m_applied_style = style;
- this->puts (style.to_ansi ().c_str ());
- }
+ if (can_emit_style_escape ())
+ this->puts (style.to_ansi ().c_str ());
}
/* See ui-file.h. */
diff --git a/gdb/ui-file.h b/gdb/ui-file.h
index 4aaf4d0e54e..84529de3618 100644
--- a/gdb/ui-file.h
+++ b/gdb/ui-file.h
@@ -55,7 +55,7 @@ class ui_file
void putc (int c);
- void vprintf (const char *, va_list) ATTRIBUTE_PRINTF (2, 0);
+ virtual void vprintf (const char *, va_list) ATTRIBUTE_PRINTF (2, 0);
/* Methods below are both public, and overridable by ui_file
subclasses. */
@@ -139,11 +139,6 @@ class ui_file
this->puts (str);
}
-protected:
-
- /* The currently applied style. */
- ui_file_style m_applied_style;
-
private:
/* Helper function for putstr and putstrn. Print the character C on
diff --git a/gdb/ui-out.c b/gdb/ui-out.c
index 8a41d9897fa..ac792b43905 100644
--- a/gdb/ui-out.c
+++ b/gdb/ui-out.c
@@ -559,7 +559,8 @@ ui_out::field_fmt (const char *fldname, const ui_file_style &style,
}
void
-ui_out::call_do_message (const ui_file_style &style, const char *format,
+ui_out::call_do_message (ui_file_style ¤t_style,
+ const ui_file_style &style, const char *format,
...)
{
va_list args;
@@ -571,7 +572,7 @@ ui_out::call_do_message (const ui_file_style &style, const char *format,
to put a "format" attribute on call_do_message. */
DIAGNOSTIC_PUSH
DIAGNOSTIC_IGNORE_FORMAT_NONLITERAL
- do_message (style, format, args);
+ do_message (current_style, style, format, args);
DIAGNOSTIC_POP
va_end (args);
@@ -583,6 +584,7 @@ ui_out::vmessage (const ui_file_style &in_style, const char *format,
{
format_pieces fpieces (&format, true);
+ ui_file_style current_style = in_style;
ui_file_style style = in_style;
for (auto &&piece : fpieces)
@@ -608,13 +610,15 @@ ui_out::vmessage (const ui_file_style &in_style, const char *format,
switch (piece.n_int_args)
{
case 0:
- call_do_message (style, current_substring, str);
+ call_do_message (current_style, style, current_substring,
+ str);
break;
case 1:
- call_do_message (style, current_substring, intvals[0], str);
+ call_do_message (current_style, style, current_substring,
+ intvals[0], str);
break;
case 2:
- call_do_message (style, current_substring,
+ call_do_message (current_style, style, current_substring,
intvals[0], intvals[1], str);
break;
}
@@ -627,7 +631,8 @@ ui_out::vmessage (const ui_file_style &in_style, const char *format,
gdb_assert_not_reached ("wide_char_arg not supported in vmessage");
break;
case long_long_arg:
- call_do_message (style, current_substring, va_arg (args, long long));
+ call_do_message (current_style, style, current_substring,
+ va_arg (args, long long));
break;
case int_arg:
{
@@ -635,13 +640,15 @@ ui_out::vmessage (const ui_file_style &in_style, const char *format,
switch (piece.n_int_args)
{
case 0:
- call_do_message (style, current_substring, val);
+ call_do_message (current_style, style, current_substring,
+ val);
break;
case 1:
- call_do_message (style, current_substring, intvals[0], val);
+ call_do_message (current_style, style, current_substring,
+ intvals[0], val);
break;
case 2:
- call_do_message (style, current_substring,
+ call_do_message (current_style, style, current_substring,
intvals[0], intvals[1], val);
break;
}
@@ -653,13 +660,15 @@ ui_out::vmessage (const ui_file_style &in_style, const char *format,
switch (piece.n_int_args)
{
case 0:
- call_do_message (style, current_substring, val);
+ call_do_message (current_style, style, current_substring,
+ val);
break;
case 1:
- call_do_message (style, current_substring, intvals[0], val);
+ call_do_message (current_style, style, current_substring,
+ intvals[0], val);
break;
case 2:
- call_do_message (style, current_substring,
+ call_do_message (current_style, style, current_substring,
intvals[0], intvals[1], val);
break;
}
@@ -671,13 +680,15 @@ ui_out::vmessage (const ui_file_style &in_style, const char *format,
switch (piece.n_int_args)
{
case 0:
- call_do_message (style, current_substring, val);
+ call_do_message (current_style, style, current_substring,
+ val);
break;
case 1:
- call_do_message (style, current_substring, intvals[0], val);
+ call_do_message (current_style, style, current_substring,
+ intvals[0], val);
break;
case 2:
- call_do_message (style, current_substring,
+ call_do_message (current_style, style, current_substring,
intvals[0], intvals[1], val);
break;
}
@@ -689,20 +700,23 @@ ui_out::vmessage (const ui_file_style &in_style, const char *format,
switch (piece.n_int_args)
{
case 0:
- call_do_message (style, current_substring, val);
+ call_do_message (current_style, style, current_substring,
+ val);
break;
case 1:
- call_do_message (style, current_substring, intvals[0], val);
+ call_do_message (current_style, style, current_substring,
+ intvals[0], val);
break;
case 2:
- call_do_message (style, current_substring,
+ call_do_message (current_style, style, current_substring,
intvals[0], intvals[1], val);
break;
}
}
break;
case double_arg:
- call_do_message (style, current_substring, va_arg (args, double));
+ call_do_message (current_style, style, current_substring,
+ va_arg (args, double));
break;
case long_double_arg:
gdb_assert_not_reached ("long_double_arg not supported in vmessage");
@@ -743,7 +757,7 @@ ui_out::vmessage (const ui_file_style &in_style, const char *format,
case 's':
{
styled_string_s *ss = va_arg (args, styled_string_s *);
- call_do_message (ss->style, "%s", ss->str);
+ call_do_message (current_style, ss->style, "%s", ss->str);
}
break;
case '[':
@@ -758,7 +772,8 @@ ui_out::vmessage (const ui_file_style &in_style, const char *format,
}
break;
default:
- call_do_message (style, current_substring, va_arg (args, void *));
+ call_do_message (current_style, style, current_substring,
+ va_arg (args, void *));
break;
}
break;
@@ -770,12 +785,16 @@ ui_out::vmessage (const ui_file_style &in_style, const char *format,
because some platforms have modified GCC to include
-Wformat-security by default, which will warn here if
there is no argument. */
- call_do_message (style, current_substring, 0);
+ call_do_message (current_style, style, current_substring, 0);
break;
default:
internal_error (_("failed internal consistency check"));
}
}
+
+ ui_file_style plain;
+ if (can_emit_style_escape () && current_style != plain)
+ emit_style_escape (plain);
}
void
diff --git a/gdb/ui-out.h b/gdb/ui-out.h
index 69d9910443e..28af8d521e7 100644
--- a/gdb/ui-out.h
+++ b/gdb/ui-out.h
@@ -282,6 +282,11 @@ class ui_out
escapes. */
virtual bool can_emit_style_escape () const = 0;
+ /* Emit a style escape, if possible. */
+ virtual void emit_style_escape (const ui_file_style &style)
+ {
+ }
+
/* Return the ui_file currently used for output. */
virtual ui_file *current_stream () const = 0;
@@ -361,9 +366,17 @@ class ui_out
ATTRIBUTE_PRINTF (7, 0) = 0;
virtual void do_spaces (int numspaces) = 0;
virtual void do_text (const char *string) = 0;
- virtual void do_message (const ui_file_style &style,
+
+ /* A helper for vprintf and call_do_message. Formats a string and
+ then prints it using STYLE. This should take care to only change
+ the style when necessary (i.e., don't bother if the formatted
+ string is empty, or if the desired style is the same as
+ CURRENT_STYLE). Updates current_style if the style was
+ changed. */
+ virtual void do_message (ui_file_style ¤t_style,
+ const ui_file_style &style,
const char *format, va_list args)
- ATTRIBUTE_PRINTF (3,0) = 0;
+ ATTRIBUTE_PRINTF (4, 0) = 0;
virtual void do_wrap_hint (int indent) = 0;
virtual void do_flush () = 0;
virtual void do_redirect (struct ui_file *outstream) = 0;
@@ -380,7 +393,10 @@ class ui_out
{ return false; }
private:
- void call_do_message (const ui_file_style &style, const char *format,
+ /* A helper for vmessage that wraps a call to do_message. This will
+ update CURRENT_STYLE when needed. */
+ void call_do_message (ui_file_style ¤t_style,
+ const ui_file_style &style, const char *format,
...);
ui_out_flags m_flags;
diff --git a/gdb/utils.c b/gdb/utils.c
index 03a1d9146f2..bec749afc40 100644
--- a/gdb/utils.c
+++ b/gdb/utils.c
@@ -1673,6 +1673,13 @@ pager_file::check_for_overfull_line (const unsigned int lines_allowed)
}
}
+void
+pager_file::vprintf (const char *format, va_list args)
+{
+ ui_out_flags flags = disallow_ui_out_field;
+ cli_ui_out (this, flags).vmessage (m_applied_style, format, args);
+}
+
void
pager_file::puts (const char *linebuffer)
{
--
2.49.0
^ permalink raw reply [flat|nested] 23+ messages in thread* [PATCH v3 14/21] Add a new logging_file implementation
2026-01-30 13:17 [PATCH v3 00/21] Rework gdb logging and output redirection Tom Tromey
` (12 preceding siblings ...)
2026-01-30 13:17 ` [PATCH v3 13/21] Remove m_applied_style from ui_file Tom Tromey
@ 2026-01-30 13:17 ` Tom Tromey
2026-01-30 13:17 ` [PATCH v3 15/21] Rewrite output redirection and logging Tom Tromey
` (6 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Tom Tromey @ 2026-01-30 13:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
This adds a new logging_file subclass of ui_file. This new subclass
handles the details of logging, by consulting the relevant globals.
I think a dependency on globals is warranted here, because the logging
settings themselves are global.
The idea of this approach is that rather than modifying the output
pipeline in response to logging commands, a logging_file will simply
always be in the pipeline, and will then react to the appropriate
settings. ("Appropriate" because there are tests that the logger
doesn't immediately react to changes, so it captures settings at the
moment logging starts.)
The new code isn't actually used yet -- nothing in this patch
constructs a logging_file. It's separate for easier review.
---
gdb/cli/cli-logging.c | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++
gdb/logging-file.h | 82 ++++++++++++++++++++++++++++++++++
2 files changed, 201 insertions(+)
diff --git a/gdb/cli/cli-logging.c b/gdb/cli/cli-logging.c
index c00c6bf0488..6f2a6287700 100644
--- a/gdb/cli/cli-logging.c
+++ b/gdb/cli/cli-logging.c
@@ -20,6 +20,7 @@
#include "cli/cli-cmds.h"
#include "ui-out.h"
#include "interps.h"
+#include "logging-file.h"
#include "cli/cli-style.h"
#include "cli/cli-decode.h"
@@ -61,6 +62,9 @@ show_logging_overwrite (struct ui_file *file, int from_tty,
gdb_printf (file, _("off: Logging appends to the log file.\n"));
}
+/* The current log file, or nullptr if none. */
+static ui_file_up log_file;
+
/* Value as configured by the user. */
static bool logging_redirect;
static bool debug_redirect;
@@ -96,6 +100,119 @@ show_logging_debug_redirect (struct ui_file *file, int from_tty,
_("off: Debug output will go to both the screen and the log file.\n"));
}
+/* Values as used by the logging_file implementation. These are
+ separate and only set when logging is enabled, because historically
+ gdb required you to disable and re-enable logging to change these
+ settings. */
+
+static bool logging_redirect_for_file;
+static bool debug_redirect_for_file;
+
+/* See logging-file.h. */
+
+template<typename T>
+bool
+logging_file<T>::ordinary_output () const
+{
+ if (log_file == nullptr)
+ return true;
+ if (logging_redirect_for_file)
+ return false;
+ if (debug_redirect_for_file)
+ return !m_for_stdlog;
+ return true;
+}
+
+/* See logging-file.h. */
+
+template<typename T>
+void
+logging_file<T>::flush ()
+{
+ if (log_file != nullptr)
+ log_file->flush ();
+ /* Always flushing seems fine. */
+ m_out->flush ();
+}
+
+/* See logging-file.h. */
+
+template<typename T>
+bool
+logging_file<T>::can_page () const
+{
+ /* If all output is redirected, do not page. */
+ if (!ordinary_output ())
+ return false;
+ /* In other cases, paging happens if the underlying stream can
+ page. */
+ return m_out->can_page ();
+}
+
+/* See logging-file.h. */
+
+template<typename T>
+void
+logging_file<T>::write (const char *buf, long length_buf)
+{
+ if (log_file != nullptr)
+ log_file->write (buf, length_buf);
+ if (ordinary_output ())
+ m_out->write (buf, length_buf);
+}
+
+/* See logging-file.h. */
+
+template<typename T>
+void
+logging_file<T>::write_async_safe (const char *buf, long length_buf)
+{
+ if (log_file != nullptr)
+ log_file->write_async_safe (buf, length_buf);
+ if (ordinary_output ())
+ m_out->write_async_safe (buf, length_buf);
+}
+
+/* See logging-file.h. */
+
+template<typename T>
+void
+logging_file<T>::puts (const char *linebuffer)
+{
+ if (log_file != nullptr)
+ log_file->puts (linebuffer);
+ if (ordinary_output ())
+ m_out->puts (linebuffer);
+}
+
+/* See logging-file.h. */
+
+template<typename T>
+void
+logging_file<T>::emit_style_escape (const ui_file_style &style)
+{
+ if (log_file != nullptr)
+ log_file->emit_style_escape (style);
+ if (ordinary_output ())
+ m_out->emit_style_escape (style);
+}
+
+/* See logging-file.h. */
+
+template<typename T>
+void
+logging_file<T>::puts_unfiltered (const char *str)
+{
+ if (log_file != nullptr)
+ log_file->puts_unfiltered (str);
+ if (ordinary_output ())
+ m_out->puts_unfiltered (str);
+}
+
+/* The available instantiations of logging_file. */
+template class logging_file<ui_file *>;
+template class logging_file<ui_file_up>;
+
/* If we've pushed output files, close them and pop them. */
static void
pop_output_files (void)
@@ -141,6 +258,8 @@ handle_redirections (int from_tty)
}
saved_filename = logging_filename;
+ logging_redirect_for_file = logging_redirect;
+ debug_redirect_for_file = debug_redirect;
/* Let the interpreter do anything it needs. */
current_interp_set_logging (std::move (log), logging_redirect,
diff --git a/gdb/logging-file.h b/gdb/logging-file.h
new file mode 100644
index 00000000000..5dfe1118494
--- /dev/null
+++ b/gdb/logging-file.h
@@ -0,0 +1,82 @@
+/* Copyright (C) 2025, 2026 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef GDB_LOGGING_FILE_H
+#define GDB_LOGGING_FILE_H
+
+#include "ui-file.h"
+
+/* A ui_file implementation that optionally writes its output to a
+ second logging stream. Whether logging is actually done depends on
+ the user's logging settings. The precise underlying ui_file type
+ is a template parameter, so that either owning or non-owning
+ instances can be made. */
+
+template<typename T>
+class logging_file : public ui_file
+{
+public:
+ /* This wraps another stream. Whether or not output actually goes
+ to that stream depends on the redirection settings. FOR_STDLOG
+ should only be set for a stream intended by use as gdb_stdlog;
+ this is used to implement the "debug redirect" feature. */
+ logging_file (T out, bool for_stdlog = false)
+ : m_out (std::move (out)),
+ m_for_stdlog (for_stdlog)
+ {
+ }
+
+ void write (const char *buf, long length_buf) override;
+ void write_async_safe (const char *buf, long length_buf) override;
+ void puts (const char *) override;
+ void flush () override;
+ bool can_page () const override;
+ void emit_style_escape (const ui_file_style &style) override;
+ void puts_unfiltered (const char *str) override;
+
+ bool isatty () override
+ {
+ /* Defer to the wrapped file. */
+ return m_out->isatty ();
+ }
+
+ bool term_out () override
+ {
+ /* Defer to the wrapped file. */
+ return m_out->term_out ();
+ }
+
+ bool can_emit_style_escape () override
+ {
+ /* Defer to the wrapped file. */
+ return m_out->can_emit_style_escape ();
+ }
+
+private:
+ /* A helper function that returns true if output should go to
+ M_OUT. */
+ bool ordinary_output () const;
+
+ /* The underlying file. */
+ T m_out;
+
+ /* True if this stream is used for gdb_stdlog. This is used to
+ implement the debug redirect feature. */
+ bool m_for_stdlog;
+};
+
+#endif /* GDB_LOGGING_FILE_H */
--
2.49.0
^ permalink raw reply [flat|nested] 23+ messages in thread* [PATCH v3 15/21] Rewrite output redirection and logging
2026-01-30 13:17 [PATCH v3 00/21] Rework gdb logging and output redirection Tom Tromey
` (13 preceding siblings ...)
2026-01-30 13:17 ` [PATCH v3 14/21] Add a new logging_file implementation Tom Tromey
@ 2026-01-30 13:17 ` Tom Tromey
2026-01-30 13:17 ` [PATCH v3 16/21] Remove tee_file Tom Tromey
` (5 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Tom Tromey @ 2026-01-30 13:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
This patch changes how gdb output redirection is done.
Currently, output is done via the UI. gdb_stdout, for example, is a
define the expands to an lvalue referencing a field in the current UI.
When redirecting, this field may temporarily be reset; and when
logging is enabled or disabled, this is also done.
This has lead to bugs where the combination of redirection and logging
results in use-after-free. Crashes are readily observable; see the
new test cases.
This patch upends this. Now, gdb_stdout is simply an rvalue, and
refers to the current interpreter. The interpreter provides ui_files
that do whatever rewriting is needed (mostly for MI); then output is
forward to the current UI via an indirection (see the new
ui::passthrough_file).
The ui provides paging, logging, timestamps, and the final stream that
writes to an actual file descriptor.
Redirection is handled at the ui layer. Rather than changing the
output pipeline, new ui_files are simply swapped in by rewriting
pointers, hopefully with a scoped_restore.
Redirecting at the ui layer means that interpreter rewriting is still
applied when capturing output. This fixes one of the reported bugs.
Not changing the pipeline means that the problems with the combination
of redirect and logging simply vanish. Logging just changes a flag
and doesn't involve object destruction. This does mean, though, that
spots capturing output are responsible for also having a logging_file
in the mix, should logging be desired. (IMO, it generally is, and
that's what I've implemented here.)
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=17697
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=28620
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=28798
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=28948
---
gdb/buffered-streams.c | 24 ++++----
gdb/cli/cli-interp.c | 70 +----------------------
gdb/cli/cli-interp.h | 22 --------
gdb/cli/cli-logging.c | 27 +--------
gdb/fork-child.c | 4 +-
gdb/guile/scm-ports.c | 14 +++--
gdb/interps.c | 25 +++-----
gdb/interps.h | 66 ++++++++++++++--------
gdb/main.c | 3 +-
gdb/mi/mi-console.c | 10 ----
gdb/mi/mi-console.h | 3 -
gdb/mi/mi-interp.c | 64 ++-------------------
gdb/mi/mi-interp.h | 18 +-----
gdb/python/py-dap.c | 10 +---
gdb/testsuite/gdb.base/early-logging.exp | 35 ++++++++++++
gdb/testsuite/gdb.python/python.exp | 16 ++++++
gdb/top.c | 97 ++++++++++++++++++++++++++------
gdb/tui/tui-interp.c | 15 -----
gdb/tui/tui-io.c | 40 +++++++------
gdb/ui.c | 18 ++++--
gdb/ui.h | 70 ++++++++++++++++++-----
gdb/utils.h | 57 +++++++++++++++----
22 files changed, 358 insertions(+), 350 deletions(-)
diff --git a/gdb/buffered-streams.c b/gdb/buffered-streams.c
index 71122c7f237..16a202ce1bb 100644
--- a/gdb/buffered-streams.c
+++ b/gdb/buffered-streams.c
@@ -89,16 +89,16 @@ get_unbuffered (ui_file *stream)
}
buffered_streams::buffered_streams (buffer_group *group, ui_out *uiout)
- : m_buffered_stdout (group, gdb_stdout),
- m_buffered_stderr (group, gdb_stderr),
- m_buffered_stdlog (group, gdb_stdlog),
- m_buffered_stdtarg (group, gdb_stdtarg),
+ : m_buffered_stdout (group, *redirectable_stdout ()),
+ m_buffered_stderr (group, *redirectable_stderr ()),
+ m_buffered_stdlog (group, *redirectable_stdlog ()),
+ m_buffered_stdtarg (group, *redirectable_stdtarg ()),
m_uiout (uiout)
{
- gdb_stdout = &m_buffered_stdout;
- gdb_stderr = &m_buffered_stderr;
- gdb_stdlog = &m_buffered_stdlog;
- gdb_stdtarg = &m_buffered_stdtarg;
+ *redirectable_stdout () = &m_buffered_stdout;
+ *redirectable_stderr () = &m_buffered_stderr;
+ *redirectable_stdlog () = &m_buffered_stdlog;
+ *redirectable_stdtarg () = &m_buffered_stdtarg;
ui_file *stream = current_uiout->current_stream ();
if (stream != nullptr)
@@ -127,10 +127,10 @@ buffered_streams::remove_buffers ()
m_buffers_in_place = false;
- gdb_stdout = m_buffered_stdout.stream ();
- gdb_stderr = m_buffered_stderr.stream ();
- gdb_stdlog = m_buffered_stdlog.stream ();
- gdb_stdtarg = m_buffered_stdtarg.stream ();
+ *redirectable_stdout () = m_buffered_stdout.stream ();
+ *redirectable_stderr () = m_buffered_stderr.stream ();
+ *redirectable_stdlog () = m_buffered_stdlog.stream ();
+ *redirectable_stdtarg () = m_buffered_stdtarg.stream ();
if (m_buffered_current_uiout.has_value ())
current_uiout->redirect (nullptr);
diff --git a/gdb/cli/cli-interp.c b/gdb/cli/cli-interp.c
index 91e9b164830..55892d2ce28 100644
--- a/gdb/cli/cli-interp.c
+++ b/gdb/cli/cli-interp.c
@@ -59,7 +59,7 @@ class cli_interp final : public cli_interp_base
cli_interp::cli_interp (const char *name)
: cli_interp_base (name),
- m_cli_uiout (new cli_ui_out (gdb_stdout))
+ m_cli_uiout (new cli_ui_out (m_stdout.get ()))
{
}
@@ -183,27 +183,10 @@ void
cli_interp::resume ()
{
struct ui *ui = current_ui;
- struct ui_file *stream;
-
- /*sync_execution = 1; */
-
- /* gdb_setup_readline will change gdb_stdout. If the CLI was
- previously writing to gdb_stdout, then set it to the new
- gdb_stdout afterwards. */
-
- stream = m_cli_uiout->set_stream (gdb_stdout);
- if (stream != gdb_stdout)
- {
- m_cli_uiout->set_stream (stream);
- stream = NULL;
- }
gdb_setup_readline (1);
ui->input_handler = command_line_handler;
-
- if (stream != NULL)
- m_cli_uiout->set_stream (gdb_stdout);
}
void
@@ -252,57 +235,6 @@ cli_interp::interp_ui_out ()
return m_cli_uiout.get ();
}
-/* See cli-interp.h. */
-
-void
-cli_interp_base::set_logging (ui_file_up logfile, bool logging_redirect,
- bool debug_redirect)
-{
- if (logfile != nullptr)
- {
- gdb_assert (m_saved_output == nullptr);
- m_saved_output = std::make_unique<saved_output_files> ();
- m_saved_output->out = gdb_stdout;
- m_saved_output->err = gdb_stderr;
- m_saved_output->log = gdb_stdlog;
- m_saved_output->targ = gdb_stdtarg;
-
- ui_file *logfile_p = logfile.get ();
- m_saved_output->logfile_holder = std::move (logfile);
-
- /* The new stdout and stderr only depend on whether logging
- redirection is being done. */
- ui_file *new_stdout = logfile_p;
- ui_file *new_stderr = logfile_p;
- if (!logging_redirect)
- {
- m_saved_output->stdout_holder.reset
- (new tee_file (gdb_stdout, logfile_p));
- new_stdout = m_saved_output->stdout_holder.get ();
- m_saved_output->stderr_holder.reset
- (new tee_file (gdb_stderr, logfile_p));
- new_stderr = m_saved_output->stderr_holder.get ();
- }
-
- m_saved_output->stdlog_holder.reset
- (new timestamped_file (debug_redirect ? logfile_p : new_stderr));
-
- gdb_stdout = new_stdout;
- gdb_stdlog = m_saved_output->stdlog_holder.get ();
- gdb_stderr = new_stderr;
- gdb_stdtarg = new_stderr;
- }
- else
- {
- gdb_stdout = m_saved_output->out;
- gdb_stderr = m_saved_output->err;
- gdb_stdlog = m_saved_output->log;
- gdb_stdtarg = m_saved_output->targ;
-
- m_saved_output.reset (nullptr);
- }
-}
-
/* Factory for CLI interpreters. */
static struct interp *
diff --git a/gdb/cli/cli-interp.h b/gdb/cli/cli-interp.h
index a766e38873d..c8428acfa10 100644
--- a/gdb/cli/cli-interp.h
+++ b/gdb/cli/cli-interp.h
@@ -28,8 +28,6 @@ class cli_interp_base : public interp
explicit cli_interp_base (const char *name);
virtual ~cli_interp_base () = 0;
- void set_logging (ui_file_up logfile, bool logging_redirect,
- bool debug_redirect) override;
void pre_command_loop () override;
bool supports_command_editing () override;
@@ -41,26 +39,6 @@ class cli_interp_base : public interp
void on_sync_execution_done () override;
void on_command_error () override;
void on_user_selected_context_changed (user_selected_what selection) override;
-
-private:
- struct saved_output_files
- {
- /* Saved gdb_stdout, gdb_stderr, etc. */
- ui_file *out;
- ui_file *err;
- ui_file *log;
- ui_file *targ;
- /* When redirecting, some or all of these may be non-null
- depending on the logging mode. */
- ui_file_up stdout_holder;
- ui_file_up stderr_holder;
- ui_file_up stdlog_holder;
- ui_file_up logfile_holder;
- };
-
- /* These hold the pushed copies of the gdb output files. If NULL
- then nothing has yet been pushed. */
- std::unique_ptr<saved_output_files> m_saved_output;
};
/* Returns true if the current stop should be printed to
diff --git a/gdb/cli/cli-logging.c b/gdb/cli/cli-logging.c
index 6f2a6287700..c046e9131bc 100644
--- a/gdb/cli/cli-logging.c
+++ b/gdb/cli/cli-logging.c
@@ -213,17 +213,6 @@ logging_file<T>::puts_unfiltered (const char *str)
template class logging_file<ui_file *>;
template class logging_file<ui_file_up>;
-/* If we've pushed output files, close them and pop them. */
-static void
-pop_output_files (void)
-{
- current_interp_set_logging (NULL, false, false);
-
- /* Stay consistent with handle_redirections. */
- if (!current_uiout->is_mi_like_p ())
- current_uiout->redirect (NULL);
-}
-
/* This is a helper for the `set logging' command. */
static void
handle_redirections (int from_tty)
@@ -260,19 +249,7 @@ handle_redirections (int from_tty)
saved_filename = logging_filename;
logging_redirect_for_file = logging_redirect;
debug_redirect_for_file = debug_redirect;
-
- /* Let the interpreter do anything it needs. */
- current_interp_set_logging (std::move (log), logging_redirect,
- debug_redirect);
-
- /* Redirect the current ui-out object's output to the log. Use
- gdb_stdout, not log, since the interpreter may have created a tee
- that wraps the log. Don't do the redirect for MI, it confuses
- MI's ui-out scheme. Note that we may get here with MI as current
- interpreter, but with the current ui_out as a CLI ui_out, with
- '-interpreter-exec console "set logging on"'. */
- if (!current_uiout->is_mi_like_p ())
- current_uiout->redirect (gdb_stdout);
+ log_file = std::move (log);
}
static void
@@ -292,7 +269,7 @@ set_logging_off (const char *args, int from_tty)
if (saved_filename.empty ())
return;
- pop_output_files ();
+ log_file.reset ();
if (from_tty)
gdb_printf ("Done logging to %s.\n",
saved_filename.c_str ());
diff --git a/gdb/fork-child.c b/gdb/fork-child.c
index 318ca46e8cb..bd75a9593aa 100644
--- a/gdb/fork-child.c
+++ b/gdb/fork-child.c
@@ -47,8 +47,8 @@ get_exec_wrapper ()
void
gdb_flush_out_err ()
{
- gdb_flush (main_ui->m_gdb_stdout);
- gdb_flush (main_ui->m_gdb_stderr);
+ gdb_flush (main_ui->m_ui_stdout);
+ gdb_flush (main_ui->m_ui_stderr);
}
/* The ui structure that will be saved on 'prefork_hook' and
diff --git a/gdb/guile/scm-ports.c b/gdb/guile/scm-ports.c
index 8841970cf1f..78be66ce2a2 100644
--- a/gdb/guile/scm-ports.c
+++ b/gdb/guile/scm-ports.c
@@ -25,6 +25,7 @@
#include "ui.h"
#include "target.h"
#include "guile-internal.h"
+#include "logging-file.h"
#include <optional>
#ifdef HAVE_POLL
@@ -323,20 +324,23 @@ ioscm_with_output_to_port_worker (SCM port, SCM thunk, enum oport oport,
scoped_restore restore_async = make_scoped_restore (¤t_ui->async, 0);
- ui_file_up port_file (new ioscm_file_port (port));
+ ui_file_up log_file
+ = (std::make_unique<logging_file<ui_file_up>>
+ (std::make_unique<ioscm_file_port> (port)));
scoped_restore save_file = make_scoped_restore (oport == GDB_STDERR
- ? &gdb_stderr : &gdb_stdout);
+ ? redirectable_stderr ()
+ : redirectable_stdout ());
{
std::optional<ui_out_redirect_pop> redirect_popper;
if (oport == GDB_STDERR)
- gdb_stderr = port_file.get ();
+ *redirectable_stderr () = log_file.get ();
else
{
- redirect_popper.emplace (current_uiout, port_file.get ());
+ redirect_popper.emplace (current_uiout, log_file.get ());
- gdb_stdout = port_file.get ();
+ *redirectable_stderr () = log_file.get ();
}
result = gdbscm_safe_call_0 (thunk, NULL);
diff --git a/gdb/interps.c b/gdb/interps.c
index 903c52e7cf7..23663cb5ef5 100644
--- a/gdb/interps.c
+++ b/gdb/interps.c
@@ -45,9 +45,16 @@
static struct interp *interp_lookup_existing (struct ui *ui,
const char *name);
-interp::interp (const char *name)
+interp::interp (const char *name, bool make_outputs)
: m_name (name)
{
+ if (make_outputs)
+ {
+ m_stdout = std::make_unique<ui::ui_stdout_file> ();
+ m_stderr = std::make_unique<ui::ui_stderr_file> ();
+ m_stdlog = std::make_unique<ui::ui_stdlog_file> ();
+ m_stdtarg = std::make_unique<ui::ui_stdtarg_file> ();
+ }
}
interp::~interp () = default;
@@ -197,15 +204,6 @@ set_top_level_interpreter (const char *name, bool for_new_ui)
interp_set (interp, true);
}
-void
-current_interp_set_logging (ui_file_up logfile, bool logging_redirect,
- bool debug_redirect)
-{
- struct interp *interp = current_ui->current_interpreter;
-
- interp->set_logging (std::move (logfile), logging_redirect, debug_redirect);
-}
-
/* Temporarily overrides the current interpreter. */
struct interp *
scoped_restore_interp::set_interp (const char *name)
@@ -286,13 +284,6 @@ interpreter_exec_cmd (const char *args, int from_tty)
unsigned int nrules;
unsigned int i;
- /* Interpreters may clobber stdout/stderr (e.g. in mi_interp::resume at time
- of writing), preserve their state here. */
- scoped_restore save_stdout = make_scoped_restore (&gdb_stdout);
- scoped_restore save_stderr = make_scoped_restore (&gdb_stderr);
- scoped_restore save_stdlog = make_scoped_restore (&gdb_stdlog);
- scoped_restore save_stdtarg = make_scoped_restore (&gdb_stdtarg);
-
if (args == NULL)
error_no_arg (_("interpreter-exec command"));
diff --git a/gdb/interps.h b/gdb/interps.h
index 2178e6a454e..d92d6b977cf 100644
--- a/gdb/interps.h
+++ b/gdb/interps.h
@@ -51,7 +51,10 @@ extern void interp_exec (struct interp *interp, const char *command);
class interp : public intrusive_list_node<interp>
{
public:
- explicit interp (const char *name);
+ /* Construct a new interpreter with the given name. If MAKE_OUTPUTS
+ is true, also initialize the standard output streams to the
+ instances of the appropriate ui::passthrough_file type. */
+ explicit interp (const char *name, bool make_outputs = true);
virtual ~interp () = 0;
void init (bool top_level)
@@ -74,12 +77,6 @@ class interp : public intrusive_list_node<interp>
formatter. */
virtual ui_out *interp_ui_out () = 0;
- /* Provides a hook for interpreters to do any additional
- setup/cleanup that they might need when logging is enabled or
- disabled. */
- virtual void set_logging (ui_file_up logfile, bool logging_redirect,
- bool debug_redirect) = 0;
-
/* Called before starting an event loop, to give the interpreter a
chance to e.g., print a prompt. */
virtual void pre_command_loop ()
@@ -205,6 +202,46 @@ class interp : public intrusive_list_node<interp>
virtual void on_memory_changed (inferior *inf, CORE_ADDR addr, ssize_t len,
const bfd_byte *data) {}
+ /* Accessors that return the various standard output files. */
+ ui_file *get_stdout ()
+ {
+ return m_stdout.get ();
+ }
+
+ ui_file *get_stderr ()
+ {
+ return m_stderr.get ();
+ }
+
+ ui_file *get_stdlog ()
+ {
+ return m_stdlog.get ();
+ }
+
+ ui_file *get_stdtarg ()
+ {
+ return m_stdtarg.get ();
+ }
+
+protected:
+
+ /* The standard output streams.
+
+ Each interpreter can manage these streams as it likes. The only
+ general rule is that the final ui_file in each pipeline should
+ be a ui::ui_*_file of the appropriate type.
+
+ The overall approach here is that output starts with the
+ interpreter -- that is, "globals" like gdb_stdout just route to
+ these fields in the current interpreter.
+
+ After any processing by the interpreter, output is then sent to
+ the UI's channels. The UI handles paging, logging, etc. */
+ ui_file_up m_stdout;
+ ui_file_up m_stderr;
+ ui_file_up m_stdlog;
+ ui_file_up m_stdtarg;
+
private:
/* Called to perform any needed initialization. */
virtual void do_init (bool top_level)
@@ -258,21 +295,6 @@ class scoped_restore_interp
extern int current_interp_named_p (const char *name);
-/* Call this function to give the current interpreter an opportunity
- to do any special handling of streams when logging is enabled or
- disabled. LOGFILE is the stream for the log file when logging is
- starting and is NULL when logging is ending. LOGGING_REDIRECT is
- the value of the "set logging redirect" setting. If true, the
- interpreter should configure the output streams to send output only
- to the logfile. If false, the interpreter should configure the
- output streams to send output to both the current output stream
- (i.e., the terminal) and the log file. DEBUG_REDIRECT is same as
- LOGGING_REDIRECT, but for the value of "set logging debugredirect"
- instead. */
-extern void current_interp_set_logging (ui_file_up logfile,
- bool logging_redirect,
- bool debug_redirect);
-
/* Returns the top-level interpreter. */
extern struct interp *top_level_interpreter (void);
diff --git a/gdb/main.c b/gdb/main.c
index c9bd79a4f8b..a4e6cddef70 100644
--- a/gdb/main.c
+++ b/gdb/main.c
@@ -60,6 +60,7 @@
#include "cli-out.h"
#include "bt-utils.h"
#include "terminal.h"
+#include "logging-file.h"
/* The selected interpreter. */
std::string interpreter_p;
@@ -962,7 +963,7 @@ captured_main_1 (struct captured_main_args *context)
break;
case 'B':
batch_flag = batch_silent = 1;
- gdb_stdout = new null_file ();
+ current_ui->m_ui_stdout = new null_file ();
break;
case 'D':
if (optarg[0] == '\0')
diff --git a/gdb/mi/mi-console.c b/gdb/mi/mi-console.c
index 444894b75f9..1a10bfa21fb 100644
--- a/gdb/mi/mi-console.c
+++ b/gdb/mi/mi-console.c
@@ -93,13 +93,3 @@ mi_console_file::flush ()
m_buffer.clear ();
}
-
-/* Change the underlying stream of the console directly; this is
- useful as a minimum-impact way to reflect external changes like
- logging enable/disable. */
-
-void
-mi_console_file::set_raw (ui_file *raw)
-{
- m_raw = raw;
-}
diff --git a/gdb/mi/mi-console.h b/gdb/mi/mi-console.h
index ac1cefe6885..8dea558fb9b 100644
--- a/gdb/mi/mi-console.h
+++ b/gdb/mi/mi-console.h
@@ -30,9 +30,6 @@ class mi_console_file : public ui_file
string PREFIX and quoting it with QUOTE. */
mi_console_file (ui_file *raw, const char *prefix, char quote);
- /* MI-specific API. */
- void set_raw (ui_file *raw);
-
/* ui_file-specific methods. */
void flush () override;
diff --git a/gdb/mi/mi-interp.c b/gdb/mi/mi-interp.c
index a7cfb66f4af..40c9fcb350c 100644
--- a/gdb/mi/mi-interp.c
+++ b/gdb/mi/mi-interp.c
@@ -85,18 +85,18 @@ mi_interp::do_init (bool top_level)
/* Store the current output channel, so that we can create a console
channel that encapsulates and prefixes all gdb_output-type bits
coming from the rest of the debugger. */
- mi->raw_stdout = gdb_stdout;
+ mi->raw_stdout = new ui::ui_stdout_file ();
/* Create MI console channels, each with a different prefix so they
can be distinguished. */
- mi->out = new mi_console_file (mi->raw_stdout, "~", '"');
- mi->err = new mi_console_file (mi->raw_stdout, "&", '"');
- mi->log = mi->err;
- mi->targ = new mi_console_file (mi->raw_stdout, "@", '"');
+ m_stdout = std::make_unique<mi_console_file> (mi->raw_stdout, "~", '"');
+ m_stderr = std::make_unique<mi_console_file> (mi->raw_stdout, "&", '"');
+ m_stdlog = std::make_unique<mi_console_file> (mi->raw_stdout, "&", '"');
+ m_stdtarg = std::make_unique<mi_console_file> (mi->raw_stdout, "@", '"');
mi->event_channel = new mi_console_file (mi->raw_stdout, "=", 0);
mi->mi_uiout = mi_out_new (name ()).release ();
gdb_assert (mi->mi_uiout != nullptr);
- mi->cli_uiout = new cli_ui_out (mi->out);
+ mi->cli_uiout = new cli_ui_out (m_stdout.get ());
if (top_level)
{
@@ -116,23 +116,13 @@ mi_interp::do_init (bool top_level)
void
mi_interp::resume ()
{
- struct mi_interp *mi = this;
struct ui *ui = current_ui;
- /* As per hack note in mi_interpreter_init, swap in the output
- channels... */
gdb_setup_readline (0);
ui->call_readline = gdb_readline_no_editing_callback;
ui->input_handler = mi_execute_command_input_handler;
- gdb_stdout = mi->out;
- /* Route error and log output through the MI. */
- gdb_stderr = mi->err;
- gdb_stdlog = mi->log;
- /* Route target output through the MI. */
- gdb_stdtarg = mi->targ;
-
deprecated_show_load_progress = mi_load_progress;
}
@@ -885,48 +875,6 @@ mi_interp::interp_ui_out ()
return this->mi_uiout;
}
-/* Do MI-specific logging actions; save raw_stdout, and change all
- the consoles to use the supplied ui-file(s). */
-
-void
-mi_interp::set_logging (ui_file_up logfile, bool logging_redirect,
- bool debug_redirect)
-{
- struct mi_interp *mi = this;
-
- if (logfile != NULL)
- {
- mi->saved_raw_stdout = mi->raw_stdout;
-
- ui_file *logfile_p = logfile.get ();
- mi->logfile_holder = std::move (logfile);
-
- /* If something is not being redirected, then a tee containing both the
- logfile and stdout. */
- ui_file *tee = nullptr;
- if (!logging_redirect || !debug_redirect)
- {
- tee = new tee_file (mi->raw_stdout, logfile_p);
- mi->stdout_holder.reset (tee);
- }
-
- mi->raw_stdout = logging_redirect ? logfile_p : tee;
- }
- else
- {
- mi->logfile_holder.reset ();
- mi->stdout_holder.reset ();
- mi->raw_stdout = mi->saved_raw_stdout;
- mi->saved_raw_stdout = nullptr;
- }
-
- mi->out->set_raw (mi->raw_stdout);
- mi->err->set_raw (mi->raw_stdout);
- mi->log->set_raw (mi->raw_stdout);
- mi->targ->set_raw (mi->raw_stdout);
- mi->event_channel->set_raw (mi->raw_stdout);
-}
-
/* Factory for MI interpreters. */
static struct interp *
diff --git a/gdb/mi/mi-interp.h b/gdb/mi/mi-interp.h
index 39b39779de7..2ffd3a151fb 100644
--- a/gdb/mi/mi-interp.h
+++ b/gdb/mi/mi-interp.h
@@ -30,7 +30,7 @@ class mi_interp final : public interp
{
public:
mi_interp (const char *name)
- : interp (name)
+ : interp (name, false)
{}
void do_init (bool top_level) override;
@@ -38,8 +38,6 @@ class mi_interp final : public interp
void suspend () override;
void exec (const char *command_str) override;
ui_out *interp_ui_out () override;
- void set_logging (ui_file_up logfile, bool logging_redirect,
- bool debug_redirect) override;
void pre_command_loop () override;
void on_signal_received (gdb_signal sig) override;
@@ -74,22 +72,10 @@ class mi_interp final : public interp
void on_memory_changed (inferior *inf, CORE_ADDR addr, ssize_t len,
const bfd_byte *data) override;
- /* MI's output channels */
- mi_console_file *out;
- mi_console_file *err;
- mi_console_file *log;
- mi_console_file *targ;
mi_console_file *event_channel;
/* Raw console output. */
- struct ui_file *raw_stdout;
-
- /* Save the original value of raw_stdout here when logging, and the
- file which we need to delete, so we can restore correctly when
- done. */
- struct ui_file *saved_raw_stdout;
- ui_file_up logfile_holder;
- ui_file_up stdout_holder;
+ ui_file *raw_stdout;
/* MI's builder. */
struct ui_out *mi_uiout;
diff --git a/gdb/python/py-dap.c b/gdb/python/py-dap.c
index 47d90e51938..07cd47bdc0e 100644
--- a/gdb/python/py-dap.c
+++ b/gdb/python/py-dap.c
@@ -27,9 +27,9 @@ class dap_interp final : public interp
public:
explicit dap_interp (const char *name)
- : interp (name),
- m_ui_out (new cli_ui_out (gdb_stdout))
+ : interp (name)
{
+ m_ui_out = std::make_unique<cli_ui_out> (m_stdout.get ());
}
~dap_interp () override = default;
@@ -49,12 +49,6 @@ class dap_interp final : public interp
/* Just ignore it. */
}
- void set_logging (ui_file_up logfile, bool logging_redirect,
- bool debug_redirect) override
- {
- /* Just ignore it. */
- }
-
ui_out *interp_ui_out () override
{
return m_ui_out.get ();
diff --git a/gdb/testsuite/gdb.base/early-logging.exp b/gdb/testsuite/gdb.base/early-logging.exp
new file mode 100644
index 00000000000..2a9eef499d6
--- /dev/null
+++ b/gdb/testsuite/gdb.base/early-logging.exp
@@ -0,0 +1,35 @@
+# Copyright 2021-2025 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Test GDB's early init file mechanism.
+
+require {!is_remote host}
+
+save_vars { INTERNAL_GDBFLAGS } {
+ set logfile [standard_output_file gdb.txt]
+ append INTERNAL_GDBFLAGS " " \
+ [join [list \
+ -eiex \
+ "\"set logging file $logfile\"" \
+ -eiex \
+ "\"set logging enabled on\""]]
+
+ # The bug was that gdb would crash during startup if logging was
+ # enabled early.
+ clean_restart
+
+ # Just check that it's still alive.
+ gdb_test "print 23" " = 23" "it lives"
+}
diff --git a/gdb/testsuite/gdb.python/python.exp b/gdb/testsuite/gdb.python/python.exp
index ca3a4d23753..f05f3ac1419 100644
--- a/gdb/testsuite/gdb.python/python.exp
+++ b/gdb/testsuite/gdb.python/python.exp
@@ -120,6 +120,13 @@ gdb_test "python gdb.execute('echo 2\\necho 3\\\\n\\n')" "23" \
"multi-line execute"
gdb_test " " "23" "gdb.execute does not affect repeat history"
+gdb_test_no_output \
+ "python x = gdb.execute(\"interpreter-exec mi '-data-evaluate-expression 23'\", to_string=True)" \
+ "execute MI command from python"
+gdb_test "python print(x)" \
+ "\\^done,value=\"23\"\r\n" \
+ "output of MI command"
+
# Test post_event.
gdb_test_multiline "post event insertion" \
"python" "" \
@@ -562,3 +569,12 @@ foreach type {Instruction LazyString Membuf Record RecordFunctionSegment \
gdb_test "python print(type(gdb.$type))" "<class 'type'>" \
"gdb.$type is registered"
}
+
+set logfile [host_standard_output_file gdb.txt]
+gdb_test_no_output "set logging file $logfile" \
+ "set logging file [file tail $logfile]"
+gdb_test_no_output "python gdb.execute('set logging enabled on')" \
+ "enable logging via python"
+gdb_test "set logging enabled off" \
+ "Done logging to .*gdb.txt." \
+ "disable logging"
diff --git a/gdb/top.c b/gdb/top.c
index 5819dfa7897..c3dc9abe60d 100644
--- a/gdb/top.c
+++ b/gdb/top.c
@@ -57,6 +57,7 @@
#include "gdbsupport/pathstuff.h"
#include "cli/cli-style.h"
#include "pager.h"
+#include "logging-file.h"
/* readline include files. */
#include "readline/readline.h"
@@ -91,34 +92,96 @@ extern void initialize_all_files (void);
#define DEFAULT_PROMPT "(gdb) "
#endif
-struct ui_file **
-current_ui_gdb_stdout_ptr ()
+/* See utils.h. */
+
+struct ui_file *
+current_gdb_stdout ()
{
- return ¤t_ui->m_gdb_stdout;
+ /* In early startup, there's no interpreter, but we'd still like to
+ be able to print. */
+ interp *curr = current_interpreter ();
+ if (curr == nullptr)
+ return current_ui->m_ui_stdout;
+ return curr->get_stdout ();
}
+/* See utils.h. */
+
struct ui_file *
-current_ui_gdb_stdin ()
+current_gdb_stdin ()
{
return current_ui->m_gdb_stdin;
}
+/* See utils.h. */
+
+struct ui_file *
+current_gdb_stderr ()
+{
+ /* In early startup, there's no interpreter, but we'd still like to
+ be able to print. */
+ interp *curr = current_interpreter ();
+ if (curr == nullptr)
+ return current_ui->m_ui_stderr;
+ return curr->get_stderr ();
+}
+
+/* See utils.h. */
+
+struct ui_file *
+current_gdb_stdlog ()
+{
+ /* In early startup, there's no interpreter, but we'd still like to
+ be able to print. */
+ interp *curr = current_interpreter ();
+ if (curr == nullptr)
+ return current_ui->m_ui_stdlog;
+ return curr->get_stdlog ();
+}
+
+/* See utils.h. */
+
+struct ui_file *
+current_gdb_stdtarg ()
+{
+ /* In early startup, there's no interpreter, but we'd still like to
+ be able to print. */
+ interp *curr = current_interpreter ();
+ if (curr == nullptr)
+ return current_ui->m_ui_stdtarg;
+ return curr->get_stdtarg ();
+}
+
+/* See utils.h. */
+
struct ui_file **
-current_ui_gdb_stderr_ptr ()
+redirectable_stdout ()
{
- return ¤t_ui->m_gdb_stderr;
+ return ¤t_ui->m_ui_stdout;
}
+/* See utils.h. */
+
struct ui_file **
-current_ui_gdb_stdlog_ptr ()
+redirectable_stderr ()
{
- return ¤t_ui->m_gdb_stdlog;
+ return ¤t_ui->m_ui_stderr;
}
+/* See utils.h. */
+
+struct ui_file **
+redirectable_stdlog ()
+{
+ return ¤t_ui->m_ui_stdlog;
+}
+
+/* See utils.h. */
+
struct ui_file **
-current_ui_gdb_stdtarg_ptr ()
+redirectable_stdtarg ()
{
- return ¤t_ui->m_gdb_stdtarg;
+ return ¤t_ui->m_ui_stdtarg;
}
struct ui_out **
@@ -603,22 +666,22 @@ execute_command (const char *p, int from_tty)
static void
execute_fn_to_ui_file (struct ui_file *file, std::function<void(void)> fn)
{
- /* GDB_STDOUT should be better already restored during these
- restoration callbacks. */
set_batch_flag_and_restore_page_info save_page_info;
scoped_restore save_async = make_scoped_restore (¤t_ui->async, 0);
- ui_out_redirect_pop redirect_popper (current_uiout, file);
+ logging_file<ui_file *> logger (file);
+ ui_out_redirect_pop redirect_popper (current_uiout, &logger);
scoped_restore save_stdout
- = make_scoped_restore (&gdb_stdout, file);
+ = make_scoped_restore (redirectable_stdout (), &logger);
scoped_restore save_stderr
- = make_scoped_restore (&gdb_stderr, file);
+ = make_scoped_restore (redirectable_stderr (), &logger);
+ logging_file<ui_file *> log_logger (file, true);
scoped_restore save_stdlog
- = make_scoped_restore (&gdb_stdlog, file);
+ = make_scoped_restore (redirectable_stdlog (), &log_logger);
scoped_restore save_stdtarg
- = make_scoped_restore (&gdb_stdtarg, file);
+ = make_scoped_restore (redirectable_stdtarg (), &logger);
fn ();
}
diff --git a/gdb/tui/tui-interp.c b/gdb/tui/tui-interp.c
index 1316c8bedd6..e171ae6263e 100644
--- a/gdb/tui/tui-interp.c
+++ b/gdb/tui/tui-interp.c
@@ -105,26 +105,11 @@ void
tui_interp::resume ()
{
struct ui *ui = current_ui;
- struct ui_file *stream;
-
- /* gdb_setup_readline will change gdb_stdout. If the TUI was
- previously writing to gdb_stdout, then set it to the new
- gdb_stdout afterwards. */
-
- stream = tui_old_uiout->set_stream (gdb_stdout);
- if (stream != gdb_stdout)
- {
- tui_old_uiout->set_stream (stream);
- stream = NULL;
- }
gdb_setup_readline (1);
ui->input_handler = tui_command_line_handler;
- if (stream != NULL)
- tui_old_uiout->set_stream (gdb_stdout);
-
if (tui_start_enabled)
tui_enable ();
}
diff --git a/gdb/tui/tui-io.c b/gdb/tui/tui-io.c
index 7abe4fa3e81..41704fc44d1 100644
--- a/gdb/tui/tui-io.c
+++ b/gdb/tui/tui-io.c
@@ -45,6 +45,7 @@
#include "gdbsupport/unordered_map.h"
#include "pager.h"
#include "gdbsupport/gdb-checked-static-cast.h"
+#include "logging-file.h"
/* This redefines CTRL if it is not already defined, so it must come
after terminal state related include files like <term.h> and
@@ -73,7 +74,7 @@ key_is_start_sequence (int ch)
mode.
In curses mode, the gdb outputs are made in a curses command
- window. For this, the gdb_stdout and gdb_stderr are redirected to
+ window. For this, the redirectable stdout and stderr are set to
the specific ui_file implemented by TUI. The output is handled by
tui_puts(). The input is also controlled by curses with
tui_getc(). The readline library uses this function to get its
@@ -861,16 +862,16 @@ tui_setup_io (int mode)
rl_already_prompted = 0;
/* Keep track of previous gdb output. */
- tui_old_stdout = gdb_stdout;
- tui_old_stderr = gdb_stderr;
- tui_old_stdlog = gdb_stdlog;
+ tui_old_stdout = *redirectable_stdout ();
+ tui_old_stderr = *redirectable_stderr ();
+ tui_old_stdlog = *redirectable_stdlog ();
tui_old_uiout = gdb::checked_static_cast<cli_ui_out *> (current_uiout);
/* Reconfigure gdb output. */
- gdb_stdout = tui_stdout;
- gdb_stderr = tui_stderr;
- gdb_stdlog = tui_stdlog;
- gdb_stdtarg = gdb_stderr;
+ *redirectable_stdout () = tui_stdout;
+ *redirectable_stderr () = tui_stderr;
+ *redirectable_stdlog () = tui_stdlog;
+ *redirectable_stdtarg () = tui_stderr;
current_uiout = tui_out;
/* Save tty for SIGCONT. */
@@ -879,10 +880,10 @@ tui_setup_io (int mode)
else
{
/* Restore gdb output. */
- gdb_stdout = tui_old_stdout;
- gdb_stderr = tui_old_stderr;
- gdb_stdlog = tui_old_stdlog;
- gdb_stdtarg = gdb_stderr;
+ *redirectable_stdout () = tui_old_stdout;
+ *redirectable_stderr () = tui_old_stderr;
+ *redirectable_stdlog () = tui_old_stdlog;
+ *redirectable_stdtarg () = tui_old_stderr;
current_uiout = tui_old_uiout;
/* Restore readline. */
@@ -933,13 +934,18 @@ tui_initialize_io (void)
#endif
/* Create tui output streams. */
- tui_stdout = new pager_file (std::make_unique<tui_file> (stdout, true));
- tui_stderr = new tui_file (stderr, false);
- tui_stdlog = new timestamped_file (tui_stderr);
+ tui_stdout = new pager_file (std::make_unique<logging_file<ui_file_up>>
+ (std::make_unique<tui_file> (stdout, true)));
+ ui_file *err_out = new tui_file (stderr, false);
+ /* Let tui_stderr own ERR_OUT. */
+ tui_stderr = new logging_file<ui_file_up> (ui_file_up (err_out));
+ tui_stdlog = (new timestamped_file
+ (new logging_file<ui_file *> (err_out, true)));
tui_out = new cli_ui_out (tui_stdout, 0);
- /* Create the default UI. */
- tui_old_uiout = new cli_ui_out (gdb_stdout);
+ /* Using redirectable_stdout here is a hack. This should probably
+ be done when constructing the interpreter instead. */
+ tui_old_uiout = new cli_ui_out (*redirectable_stdout ());
#ifdef TUI_USE_PIPE_FOR_READLINE
/* Temporary solution for readline writing to stdout: redirect
diff --git a/gdb/ui.c b/gdb/ui.c
index 647e63d1bde..9d71135bc36 100644
--- a/gdb/ui.c
+++ b/gdb/ui.c
@@ -27,6 +27,7 @@
#include "pager.h"
#include "main.h"
#include "top.h"
+#include "logging-file.h"
/* See top.h. */
@@ -48,11 +49,16 @@ ui::ui (FILE *instream_, FILE *outstream_, FILE *errstream_)
errstream (errstream_),
input_fd (fileno (instream)),
m_input_interactive_p (ISATTY (instream)),
- m_gdb_stdout (new pager_file (std::make_unique<stdio_file> (outstream))),
+ m_ui_stdout (new pager_file
+ (std::make_unique<logging_file<ui_file_up>>
+ (std::make_unique<stdio_file> (outstream)))),
m_gdb_stdin (new stdio_file (instream)),
- m_gdb_stderr (new stderr_file (errstream)),
- m_gdb_stdlog (new timestamped_file (m_gdb_stderr)),
- m_gdb_stdtarg (m_gdb_stderr)
+ m_ui_stderr (new logging_file<ui_file_up>
+ (std::make_unique<stderr_file> (errstream))),
+ m_ui_stdlog (new timestamped_file
+ (new logging_file<ui_file_up>
+ (std::make_unique<stderr_file> (errstream), true))),
+ m_ui_stdtarg (m_ui_stderr)
{
unbuffer_stream (instream_);
@@ -86,8 +92,8 @@ ui::~ui ()
ui_list = next;
delete m_gdb_stdin;
- delete m_gdb_stdout;
- delete m_gdb_stderr;
+ delete m_ui_stdout;
+ delete m_ui_stderr;
}
diff --git a/gdb/ui.h b/gdb/ui.h
index ba98d2500af..c2b1bca2121 100644
--- a/gdb/ui.h
+++ b/gdb/ui.h
@@ -22,8 +22,10 @@
#include "gdbsupport/intrusive_list.h"
#include "gdbsupport/next-iterator.h"
#include "gdbsupport/scoped_restore.h"
+#include "ui-file.h"
struct interp;
+struct ui;
/* Prompt state. */
@@ -43,6 +45,17 @@ enum prompt_state
PROMPTED,
};
+/* The main UI. This is the UI that is bound to stdin/stdout/stderr.
+ It always exists and is created automatically when GDB starts
+ up. */
+extern struct ui *main_ui;
+
+/* The current UI. */
+extern struct ui *current_ui;
+
+/* The list of all UIs. */
+extern struct ui *ui_list;
+
/* All about a user interface instance. Each user interface has its
own I/O files/streams, readline state, its own top level
interpreter (for the main UI, this is the interpreter specified
@@ -141,22 +154,60 @@ struct ui
execution. */
bool keep_prompt_blocked = false;
+ /* A "smart pointer" that references a particular member of the
+ current UI. */
+ template<ui_file *ui::* F>
+ struct ui_file_ptr
+ {
+ ui_file *operator-> () const
+ {
+ return current_ui->*F;
+ }
+ };
+
+ /* A ui_file that simply forwards. */
+ template<ui_file *ui::* F>
+ class passthrough_file : public wrapped_file<ui_file_ptr<F>>
+ {
+ public:
+ passthrough_file () : wrapped_file<ui_file_ptr<F>> ({})
+ {
+ }
+
+ void write (const char *buf, long len) override
+ {
+ this->m_stream->write (buf, len);
+ }
+ };
+
/* The fields below that start with "m_" are "private". They're
meant to be accessed through wrapper macros that make them look
like globals. */
/* The ui_file streams. */
/* Normal results */
- struct ui_file *m_gdb_stdout;
+ struct ui_file *m_ui_stdout;
/* Input stream */
struct ui_file *m_gdb_stdin;
/* Serious error notifications */
- struct ui_file *m_gdb_stderr;
+ struct ui_file *m_ui_stderr;
/* Log/debug/trace messages that should bypass normal stdout/stderr
filtering. */
- struct ui_file *m_gdb_stdlog;
+ struct ui_file *m_ui_stdlog;
/* Target output. */
- struct ui_file *m_gdb_stdtarg;
+ struct ui_file *m_ui_stdtarg;
+
+ /* Types for directing output to the various UI-managed streams. An
+ indirection solves all problems. Here the problem being solved
+ is that an interpreter could be active with any UI; and that
+ redirections (like capturing command output to a string) is done
+ by manipulating the pointers in the current UI. To this end, we
+ export some ui_file types that can be used to implement the
+ various kinds of redirections. */
+ using ui_stdout_file = passthrough_file<&ui::m_ui_stdout>;
+ using ui_stderr_file = passthrough_file<&ui::m_ui_stderr>;
+ using ui_stdlog_file = passthrough_file<&ui::m_ui_stdlog>;
+ using ui_stdtarg_file = passthrough_file<&ui::m_ui_stdtarg>;
/* The current ui_out. */
struct ui_out *m_current_uiout = nullptr;
@@ -171,17 +222,6 @@ struct ui
bool input_interactive_p () const;
};
-/* The main UI. This is the UI that is bound to stdin/stdout/stderr.
- It always exists and is created automatically when GDB starts
- up. */
-extern struct ui *main_ui;
-
-/* The current UI. */
-extern struct ui *current_ui;
-
-/* The list of all UIs. */
-extern struct ui *ui_list;
-
/* State for SWITCH_THRU_ALL_UIS. */
class switch_thru_all_uis
{
diff --git a/gdb/utils.h b/gdb/utils.h
index 4d888b80274..7cab021e7a9 100644
--- a/gdb/utils.h
+++ b/gdb/utils.h
@@ -165,11 +165,48 @@ extern bool pagination_enabled;
/* A flag indicating whether to timestamp debugging messages. */
extern bool debug_timestamp;
-extern struct ui_file **current_ui_gdb_stdout_ptr (void);
-extern struct ui_file *current_ui_gdb_stdin ();
-extern struct ui_file **current_ui_gdb_stderr_ptr (void);
-extern struct ui_file **current_ui_gdb_stdlog_ptr (void);
-extern struct ui_file **current_ui_gdb_stdtarg_ptr ();
+/* Return the current ui_file for a given output stream.
+
+ Output in gdb is somewhat complicated. Responsibility for it is
+ split between the interpreter and the UI, and has to allow for
+ output manipulation (like MI quoting), paging, logging, timestamps,
+ etc.
+
+ The outermost ui_file comes from the current interpreter. (That is
+ what these functions return.)
+
+ The interpreter supplies anything that it needs. E.g., for the
+ CLI, this is nothing, but for MI, each stream applies its own
+ quoting rule to the output.
+
+ The interpreter's pipeline ends with a ui::passthrough_file,
+ causing output to then proceed via the UI's pipeline.
+
+ The UI provides whatever is needed by that UI. For instance, the
+ pager is handled here. Logging is also handled here, and the
+ stdlog file also has a timestamp filter on it.
+
+ The UI is also where redirection happens. That is, when
+ temporarily redirecting output to a different stream (like a
+ string_file), the redirectable_* streams are used -- these just
+ return pointers to fields in the current UI. This way,
+ interpreter-specific changes are still applied before output.
+
+ When redirecting, if logging is still desired (normally it is),
+ then it is the redirector's responsibility to put a logging file
+ into the pipeline. */
+
+extern struct ui_file *current_gdb_stdout ();
+extern struct ui_file *current_gdb_stderr ();
+extern struct ui_file *current_gdb_stdlog ();
+extern struct ui_file *current_gdb_stdtarg ();
+
+extern struct ui_file **redirectable_stdout ();
+extern struct ui_file **redirectable_stderr ();
+extern struct ui_file **redirectable_stdlog ();
+extern struct ui_file **redirectable_stdtarg ();
+
+extern struct ui_file *current_gdb_stdin ();
/* Flush STREAM. */
extern void gdb_flush (struct ui_file *stream);
@@ -177,17 +214,17 @@ extern void gdb_flush (struct ui_file *stream);
/* The current top level's ui_file streams. */
/* Normal results */
-#define gdb_stdout (*current_ui_gdb_stdout_ptr ())
+#define gdb_stdout (current_gdb_stdout ())
/* Input stream. */
-#define gdb_stdin (current_ui_gdb_stdin ())
+#define gdb_stdin (current_gdb_stdin ())
/* Serious error notifications. This bypasses the pager, if one is in
use. */
-#define gdb_stderr (*current_ui_gdb_stderr_ptr ())
+#define gdb_stderr (current_gdb_stderr ())
/* Log/debug/trace messages that bypasses the pager, if one is in
use. */
-#define gdb_stdlog (*current_ui_gdb_stdlog_ptr ())
+#define gdb_stdlog (current_gdb_stdlog ())
/* Target output. */
-#define gdb_stdtarg (*current_ui_gdb_stdtarg_ptr ())
+#define gdb_stdtarg (current_gdb_stdtarg ())
/* Set the screen dimensions to WIDTH and HEIGHT. */
--
2.49.0
^ permalink raw reply [flat|nested] 23+ messages in thread* [PATCH v3 16/21] Remove tee_file
2026-01-30 13:17 [PATCH v3 00/21] Rework gdb logging and output redirection Tom Tromey
` (14 preceding siblings ...)
2026-01-30 13:17 ` [PATCH v3 15/21] Rewrite output redirection and logging Tom Tromey
@ 2026-01-30 13:17 ` Tom Tromey
2026-01-30 13:17 ` [PATCH v3 17/21] Warn if log file changed while logging Tom Tromey
` (4 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Tom Tromey @ 2026-01-30 13:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey, Andrew Burgess
tee_file is no longer used and can be deleted.
Approved-By: Andrew Burgess <aburgess@redhat.com>
---
gdb/ui-file.c | 60 -----------------------------------------------------------
gdb/ui-file.h | 37 ------------------------------------
2 files changed, 97 deletions(-)
diff --git a/gdb/ui-file.c b/gdb/ui-file.c
index d655e7edd9e..2bd692274f7 100644
--- a/gdb/ui-file.c
+++ b/gdb/ui-file.c
@@ -342,66 +342,6 @@ stderr_file::stderr_file (FILE *stream)
\f
-tee_file::tee_file (ui_file *one, ui_file *two)
- : m_one (one),
- m_two (two)
-{}
-
-tee_file::~tee_file ()
-{
-}
-
-void
-tee_file::flush ()
-{
- m_one->flush ();
- m_two->flush ();
-}
-
-void
-tee_file::write (const char *buf, long length_buf)
-{
- m_one->write (buf, length_buf);
- m_two->write (buf, length_buf);
-}
-
-void
-tee_file::write_async_safe (const char *buf, long length_buf)
-{
- m_one->write_async_safe (buf, length_buf);
- m_two->write_async_safe (buf, length_buf);
-}
-
-void
-tee_file::puts (const char *linebuffer)
-{
- m_one->puts (linebuffer);
- m_two->puts (linebuffer);
-}
-
-bool
-tee_file::isatty ()
-{
- return m_one->isatty ();
-}
-
-/* See ui-file.h. */
-
-bool
-tee_file::term_out ()
-{
- return m_one->term_out ();
-}
-
-/* See ui-file.h. */
-
-bool
-tee_file::can_emit_style_escape ()
-{
- return (m_one->term_out ()
- && term_cli_styling ());
-}
-
/* See ui-file.h. */
void
diff --git a/gdb/ui-file.h b/gdb/ui-file.h
index 84529de3618..94897ff0b77 100644
--- a/gdb/ui-file.h
+++ b/gdb/ui-file.h
@@ -333,43 +333,6 @@ class stderr_file : public stdio_file
void puts (const char *linebuffer) override;
};
-/* A ui_file implementation that maps onto two ui-file objects. */
-
-class tee_file : public ui_file
-{
-public:
- /* Create a file which writes to both ONE and TWO. Ownership of
- both files is up to the user. */
- tee_file (ui_file *one, ui_file *two);
- ~tee_file () override;
-
- void write (const char *buf, long length_buf) override;
- void write_async_safe (const char *buf, long length_buf) override;
- void puts (const char *) override;
-
- bool isatty () override;
- bool term_out () override;
- bool can_emit_style_escape () override;
- void flush () override;
-
- void emit_style_escape (const ui_file_style &style) override
- {
- m_one->emit_style_escape (style);
- m_two->emit_style_escape (style);
- }
-
- void puts_unfiltered (const char *str) override
- {
- m_one->puts_unfiltered (str);
- m_two->puts_unfiltered (str);
- }
-
-private:
- /* The two underlying ui_files. */
- ui_file *m_one;
- ui_file *m_two;
-};
-
/* A ui_file implementation that buffers terminal escape sequences.
Note that this does not buffer in general -- it only buffers when
an incomplete but potentially recognizable escape sequence is
--
2.49.0
^ permalink raw reply [flat|nested] 23+ messages in thread* [PATCH v3 17/21] Warn if log file changed while logging
2026-01-30 13:17 [PATCH v3 00/21] Rework gdb logging and output redirection Tom Tromey
` (15 preceding siblings ...)
2026-01-30 13:17 ` [PATCH v3 16/21] Remove tee_file Tom Tromey
@ 2026-01-30 13:17 ` Tom Tromey
2026-01-30 13:17 ` [PATCH v3 18/21] Fix leaks with timestamped_file Tom Tromey
` (3 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Tom Tromey @ 2026-01-30 13:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey, Andrew Burgess
PR gdb/33531 points out that while some "set logging" commands will
warn if you attempt to change settings when logging is already active,
"set logging file" does not. This patch corrects this oversight.
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33531
Approved-By: Andrew Burgess <aburgess@redhat.com>
---
gdb/cli/cli-logging.c | 26 +++++++++++++++++---------
gdb/testsuite/gdb.base/ui-redirect.exp | 3 +++
2 files changed, 20 insertions(+), 9 deletions(-)
diff --git a/gdb/cli/cli-logging.c b/gdb/cli/cli-logging.c
index c046e9131bc..8d22a62fa02 100644
--- a/gdb/cli/cli-logging.c
+++ b/gdb/cli/cli-logging.c
@@ -26,7 +26,23 @@
static std::string saved_filename;
+static void
+maybe_warn_already_logging ()
+{
+ if (!saved_filename.empty ())
+ warning (_("Currently logging to %s. Turn the logging off and on to "
+ "make the new setting effective."), saved_filename.c_str ());
+}
+
static std::string logging_filename = "gdb.txt";
+
+static void
+set_logging_filename (const char *args,
+ int from_tty, struct cmd_list_element *c)
+{
+ maybe_warn_already_logging ();
+}
+
static void
show_logging_filename (struct ui_file *file, int from_tty,
struct cmd_list_element *c, const char *value)
@@ -37,14 +53,6 @@ show_logging_filename (struct ui_file *file, int from_tty,
static bool logging_overwrite;
-static void
-maybe_warn_already_logging ()
-{
- if (!saved_filename.empty ())
- warning (_("Currently logging to %s. Turn the logging off and on to "
- "make the new setting effective."), saved_filename.c_str ());
-}
-
static void
set_logging_overwrite (const char *args,
int from_tty, struct cmd_list_element *c)
@@ -344,7 +352,7 @@ If debug redirect is on, debug will go only to the log file."),
Set the current logfile."), _("\
Show the current logfile."), _("\
The logfile is used when directing GDB's output."),
- NULL,
+ set_logging_filename,
show_logging_filename,
&set_logging_cmdlist, &show_logging_cmdlist);
diff --git a/gdb/testsuite/gdb.base/ui-redirect.exp b/gdb/testsuite/gdb.base/ui-redirect.exp
index a0382893980..b128391115f 100644
--- a/gdb/testsuite/gdb.base/ui-redirect.exp
+++ b/gdb/testsuite/gdb.base/ui-redirect.exp
@@ -100,6 +100,9 @@ with_test_prefix "redirect while already logging" {
"Copying output to /dev/null.*Copying debug output to /dev/null\\."
gdb_test "set logging redirect on" \
".*warning: Currently logging .*Turn the logging off and on to make the new setting effective.*"
+ gdb_test "set logging file /dev/null" \
+ ".*warning: Currently logging .*Turn the logging off and on to make the new setting effective.*" \
+ "warn when changing log filename"
gdb_test "save breakpoints $cmds_file" "Saved to file '$cmds_file'\\." \
"save breakpoints cmds.txt"
cmp_file_string "$cmds_file" "$cmds" "cmds.txt"
--
2.49.0
^ permalink raw reply [flat|nested] 23+ messages in thread* [PATCH v3 18/21] Fix leaks with timestamped_file
2026-01-30 13:17 [PATCH v3 00/21] Rework gdb logging and output redirection Tom Tromey
` (16 preceding siblings ...)
2026-01-30 13:17 ` [PATCH v3 17/21] Warn if log file changed while logging Tom Tromey
@ 2026-01-30 13:17 ` Tom Tromey
2026-01-30 13:17 ` [PATCH v3 19/21] Use std::make_unique with ui_files Tom Tromey
` (2 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Tom Tromey @ 2026-01-30 13:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
This changes timestamped_file to own the stream it wraps. This is
done simply because it was appropriate for all the code using this
class.
---
gdb/tui/tui-io.c | 2 +-
gdb/ui-file.h | 6 +++---
gdb/ui.c | 2 +-
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/gdb/tui/tui-io.c b/gdb/tui/tui-io.c
index 41704fc44d1..f673fbf36f6 100644
--- a/gdb/tui/tui-io.c
+++ b/gdb/tui/tui-io.c
@@ -940,7 +940,7 @@ tui_initialize_io (void)
/* Let tui_stderr own ERR_OUT. */
tui_stderr = new logging_file<ui_file_up> (ui_file_up (err_out));
tui_stdlog = (new timestamped_file
- (new logging_file<ui_file *> (err_out, true)));
+ (std::make_unique<logging_file<ui_file *>> (err_out, true)));
tui_out = new cli_ui_out (tui_stdout, 0);
/* Using redirectable_stdout here is a hack. This should probably
diff --git a/gdb/ui-file.h b/gdb/ui-file.h
index 94897ff0b77..d5771635468 100644
--- a/gdb/ui-file.h
+++ b/gdb/ui-file.h
@@ -442,11 +442,11 @@ class wrapped_file : public ui_file
/* A ui_file that optionally puts a timestamp at the start of each
line of output. */
-class timestamped_file : public wrapped_file<ui_file *>
+class timestamped_file : public wrapped_file<ui_file_up>
{
public:
- explicit timestamped_file (ui_file *stream)
- : wrapped_file (stream)
+ explicit timestamped_file (ui_file_up stream)
+ : wrapped_file (std::move (stream))
{
}
diff --git a/gdb/ui.c b/gdb/ui.c
index 9d71135bc36..c1fc92e4ee6 100644
--- a/gdb/ui.c
+++ b/gdb/ui.c
@@ -56,7 +56,7 @@ ui::ui (FILE *instream_, FILE *outstream_, FILE *errstream_)
m_ui_stderr (new logging_file<ui_file_up>
(std::make_unique<stderr_file> (errstream))),
m_ui_stdlog (new timestamped_file
- (new logging_file<ui_file_up>
+ (std::make_unique<logging_file<ui_file_up>>
(std::make_unique<stderr_file> (errstream), true))),
m_ui_stdtarg (m_ui_stderr)
{
--
2.49.0
^ permalink raw reply [flat|nested] 23+ messages in thread* [PATCH v3 19/21] Use std::make_unique with ui_files
2026-01-30 13:17 [PATCH v3 00/21] Rework gdb logging and output redirection Tom Tromey
` (17 preceding siblings ...)
2026-01-30 13:17 ` [PATCH v3 18/21] Fix leaks with timestamped_file Tom Tromey
@ 2026-01-30 13:17 ` Tom Tromey
2026-01-30 13:17 ` [PATCH v3 20/21] Style filenames in cli-logging.c Tom Tromey
2026-01-30 13:17 ` [PATCH v3 21/21] Update gdb.execute documentation Tom Tromey
20 siblings, 0 replies; 23+ messages in thread
From: Tom Tromey @ 2026-01-30 13:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
This changes a couple of spots to use std::make_unique rather than
'new'. This is a bit more idiomatic. I've only touched code
involving ui_file here, there are plenty more changes like this that
could be made, but I considered those unrelated to this series.
---
gdb/cli/cli-logging.c | 2 +-
gdb/serial.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/gdb/cli/cli-logging.c b/gdb/cli/cli-logging.c
index 8d22a62fa02..836c24445bc 100644
--- a/gdb/cli/cli-logging.c
+++ b/gdb/cli/cli-logging.c
@@ -232,7 +232,7 @@ handle_redirections (int from_tty)
return;
}
- stdio_file_up log (new no_terminal_escape_file ());
+ stdio_file_up log = std::make_unique<no_terminal_escape_file> ();
if (!log->open (logging_filename.c_str (), logging_overwrite ? "w" : "a"))
perror_with_name (_("set logging"));
diff --git a/gdb/serial.c b/gdb/serial.c
index c27e3792715..f034c1c94eb 100644
--- a/gdb/serial.c
+++ b/gdb/serial.c
@@ -238,7 +238,7 @@ serial_open_ops_1 (const struct serial_ops *ops, const char *open_name)
if (!serial_logfile.empty ())
{
- stdio_file_up file (new stdio_file ());
+ stdio_file_up file = std::make_unique<stdio_file> ();
if (!file->open (serial_logfile.c_str (), "w"))
perror_with_name (serial_logfile.c_str ());
--
2.49.0
^ permalink raw reply [flat|nested] 23+ messages in thread* [PATCH v3 20/21] Style filenames in cli-logging.c
2026-01-30 13:17 [PATCH v3 00/21] Rework gdb logging and output redirection Tom Tromey
` (18 preceding siblings ...)
2026-01-30 13:17 ` [PATCH v3 19/21] Use std::make_unique with ui_files Tom Tromey
@ 2026-01-30 13:17 ` Tom Tromey
2026-01-30 13:17 ` [PATCH v3 21/21] Update gdb.execute documentation Tom Tromey
20 siblings, 0 replies; 23+ messages in thread
From: Tom Tromey @ 2026-01-30 13:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
Andrew pointed out that some code in cli-logging.c should use the
filename style. This patch fixes these spots.
---
gdb/cli/cli-logging.c | 37 +++++++++++++++++++++++--------------
gdb/testsuite/gdb.base/style.exp | 20 ++++++++++++++++++++
2 files changed, 43 insertions(+), 14 deletions(-)
diff --git a/gdb/cli/cli-logging.c b/gdb/cli/cli-logging.c
index 836c24445bc..38c16fbf18f 100644
--- a/gdb/cli/cli-logging.c
+++ b/gdb/cli/cli-logging.c
@@ -30,8 +30,10 @@ static void
maybe_warn_already_logging ()
{
if (!saved_filename.empty ())
- warning (_("Currently logging to %s. Turn the logging off and on to "
- "make the new setting effective."), saved_filename.c_str ());
+ warning (_("Currently logging to %ps. Turn the logging off and on to "
+ "make the new setting effective."),
+ styled_string (file_name_style.style (),
+ saved_filename.c_str ()));
}
static std::string logging_filename = "gdb.txt";
@@ -227,8 +229,9 @@ handle_redirections (int from_tty)
{
if (!saved_filename.empty ())
{
- gdb_printf ("Already logging to %s.\n",
- saved_filename.c_str ());
+ gdb_printf ("Already logging to %ps.\n",
+ styled_string (file_name_style.style (),
+ saved_filename.c_str ()));
return;
}
@@ -240,18 +243,22 @@ handle_redirections (int from_tty)
if (from_tty)
{
if (!logging_redirect)
- gdb_printf ("Copying output to %s.\n",
- logging_filename.c_str ());
+ gdb_printf ("Copying output to %ps.\n",
+ styled_string (file_name_style.style (),
+ logging_filename.c_str ()));
else
- gdb_printf ("Redirecting output to %s.\n",
- logging_filename.c_str ());
+ gdb_printf ("Redirecting output to %ps.\n",
+ styled_string (file_name_style.style (),
+ logging_filename.c_str ()));
if (!debug_redirect)
- gdb_printf ("Copying debug output to %s.\n",
- logging_filename.c_str ());
+ gdb_printf ("Copying debug output to %ps.\n",
+ styled_string (file_name_style.style (),
+ logging_filename.c_str ()));
else
- gdb_printf ("Redirecting debug output to %s.\n",
- logging_filename.c_str ());
+ gdb_printf ("Redirecting debug output to %ps.\n",
+ styled_string (file_name_style.style (),
+ logging_filename.c_str ()));
}
saved_filename = logging_filename;
@@ -279,8 +286,10 @@ set_logging_off (const char *args, int from_tty)
log_file.reset ();
if (from_tty)
- gdb_printf ("Done logging to %s.\n",
- saved_filename.c_str ());
+ gdb_printf ("Done logging to %ps.\n",
+ styled_string (file_name_style.style (),
+ saved_filename.c_str ()));
+
saved_filename.clear ();
}
diff --git a/gdb/testsuite/gdb.base/style.exp b/gdb/testsuite/gdb.base/style.exp
index 23d50f4cfad..5c07541b18b 100644
--- a/gdb/testsuite/gdb.base/style.exp
+++ b/gdb/testsuite/gdb.base/style.exp
@@ -1047,6 +1047,25 @@ proc test_finish_styling {} {
gdb_test "finish" "Value returned is [style \\\$$::decimal variable] = 0"
}
+# Test some logging styling.
+proc test_logging_styling {} {
+ with_ansi_styling_terminal {
+ clean_restart
+ }
+
+ set filename [standard_output_file logging.txt]
+ gdb_test_no_output "set logging file $filename" \
+ "set logging file"
+ gdb_test "set logging enabled on" \
+ [multi_line \
+ "Copying output to [style $filename file]\\." \
+ "Copying debug output to [style $filename file]\\."] \
+ "logging filenames are styled"
+ gdb_test "set logging enabled off" \
+ "Done logging to [style $filename file]\\." \
+ "filename styled when logging disabled"
+}
+
# Check to see if the Python styling of disassembler output is
# expected or not, this styling requires Python support in GDB, and
# the Python pygments module to be available.
@@ -1093,3 +1112,4 @@ test_pagination_cmd_after_quit_styling
test_pagination_prompt_styling
test_pagination_continue_styling
test_finish_styling
+test_logging_styling
--
2.49.0
^ permalink raw reply [flat|nested] 23+ messages in thread* [PATCH v3 21/21] Update gdb.execute documentation
2026-01-30 13:17 [PATCH v3 00/21] Rework gdb logging and output redirection Tom Tromey
` (19 preceding siblings ...)
2026-01-30 13:17 ` [PATCH v3 20/21] Style filenames in cli-logging.c Tom Tromey
@ 2026-01-30 13:17 ` Tom Tromey
2026-01-30 13:35 ` Eli Zaretskii
20 siblings, 1 reply; 23+ messages in thread
From: Tom Tromey @ 2026-01-30 13:17 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
This updates the gdb.execute documentation to describe how logging and
perhaps other settings affect what can be captured.
---
gdb/doc/python.texi | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 1f88ea7e9ad..74357620b7a 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -305,6 +305,13 @@ return value is @code{None}. If @var{to_string} is @code{True}, the
@value{GDBN} virtual terminal will be temporarily set to unlimited width
and height, and its pagination will be disabled; @pxref{Screen Size}.
+Note that, even when @var{to_string} is @code{True}, other settings
+that affect output will still apply. For example, some logging
+settings (@pxref{Logging Output}) may still cause the output to be
+redirected. If this matters, you can temporarily disable settings by
+prefixing the given command with the appropriate @code{with}
+invocations (@pxref{Command Settings}).
+
When @var{styling} is @code{True}, the output, whether sent to
standard output, or to a string, will have styling applied, if
@value{GDBN}'s standard output supports styling, and @kbd{show style
--
2.49.0
^ permalink raw reply [flat|nested] 23+ messages in thread