breakpoint.c \
bt-utils.c \
btrace.c \
+ buffered-streams.c \
build-id.c \
buildsym.c \
c-lang.c \
bsd-uthread.h \
btrace.h \
bt-utils.h \
+ buffered-streams.h \
build-id.h \
buildsym.h \
c-exp.h \
--- /dev/null
+/* 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 ();
+}
--- /dev/null
+/* 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 */
#include "cli/cli-style.h"
#include "ui.h"
#include "cli/cli-cmds.h"
+#include "buffered-streams.h"
/* These are the CLI output functions */
#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;
#include "disasm.h"
#include "interps.h"
#include "finish-thread-state.h"
+#include "buffered-streams.h"
/* Prototypes for local functions */
#include "linespec.h"
#include "cli/cli-utils.h"
#include "objfiles.h"
+#include "buffered-streams.h"
#include "symfile.h"
#include "extension.h"
#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;
#include "gdbsupport/format.h"
#include "cli/cli-style.h"
#include "diagnostics.h"
+#include "buffered-streams.h"
#include <vector>
#include <memory>
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 ();
-}
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 */