]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
Rewrite output redirection and logging
authorTom Tromey <tom@tromey.com>
Sat, 6 Dec 2025 21:12:37 +0000 (15:12 -0600)
committerTom Tromey <tom@tromey.com>
Mon, 9 Feb 2026 15:15:44 +0000 (08:15 -0700)
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.

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
Approved-By: Andrew Burgess <aburgess@redhat.com>
22 files changed:
gdb/buffered-streams.c
gdb/cli/cli-interp.c
gdb/cli/cli-interp.h
gdb/cli/cli-logging.c
gdb/fork-child.c
gdb/guile/scm-ports.c
gdb/interps.c
gdb/interps.h
gdb/main.c
gdb/mi/mi-console.c
gdb/mi/mi-console.h
gdb/mi/mi-interp.c
gdb/mi/mi-interp.h
gdb/python/py-dap.c
gdb/testsuite/gdb.base/early-logging.exp [new file with mode: 0644]
gdb/testsuite/gdb.python/python.exp
gdb/top.c
gdb/tui/tui-interp.c
gdb/tui/tui-io.c
gdb/ui.c
gdb/ui.h
gdb/utils.h

index 71122c7f23702404cba726678309c257abe69d95..16a202ce1bb2087a31cbe5b3367d1527c68b141a 100644 (file)
@@ -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);
index 91e9b16483083a86bc7e0b0d99c02bbd321e7e93..55892d2ce2874c37317181274eb755b21394d1f3 100644 (file)
@@ -59,7 +59,7 @@ private:
 
 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 *
index a766e38873d80505b85199ad4be136731115720f..c8428acfa10569a3db7ebbad6cac29ba9dfd3f1f 100644 (file)
@@ -28,8 +28,6 @@ public:
   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 @@ public:
   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
index 6f2a628770088750906a577b72951d1956a50a9f..c046e9131bcc6213d7a7146a764c61586d871c47 100644 (file)
@@ -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 ());
index 318ca46e8cbb2be27dc477c331fe2816bd6f9a1c..bd75a9593aa504aeb23ed458a0a2a57a823a682a 100644 (file)
@@ -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
index 8841970cf1fb08a0fc951d64f90777ebb59cad2e..e8ded385f16ef10b73f3ce31000c37752ad9bddb 100644 (file)
@@ -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,21 @@ ioscm_with_output_to_port_worker (SCM port, SCM thunk, enum oport oport,
 
   scoped_restore restore_async = make_scoped_restore (&current_ui->async, 0);
 
-  ui_file_up port_file (new ioscm_file_port (port));
+  ui_file_up port_file = 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 () = port_file.get ();
     else
       {
        redirect_popper.emplace (current_uiout, port_file.get ());
 
-       gdb_stdout = port_file.get ();
+       *redirectable_stderr () = port_file.get ();
       }
 
     result = gdbscm_safe_call_0 (thunk, NULL);
index 903c52e7cf7b96ed8dc1b311c127895c804ea72c..23663cb5ef53d41f90ee69541901c10ad6eb5643 100644 (file)
 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"));
 
index 2178e6a454e88851ceda0c49ce172522e844db23..d92d6b977cf69d41527a859554cc571e1830a921 100644 (file)
@@ -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 @@ public:
      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 @@ public:
   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 @@ private:
 
 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);
 
index c9bd79a4f8b0b4106c60422e4a576e7136fed82a..a4e6cddef70295d8626e00c41e89ec895d46a12f 100644 (file)
@@ -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')
index 444894b75f98e62e2011a971e43757cfe5a51195..1a10bfa21fb960b0de9b2f49bb8fe35065443557 100644 (file)
@@ -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;
-}
index ac1cefe68854bbcb8db11f664038ed270d81b88f..8dea558fb9b4504abc15fcb6d254aa3b66d4920d 100644 (file)
@@ -30,9 +30,6 @@ public:
      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;
index a7cfb66f4af2b6b0af5a4256f774e8f92d175294..40c9fcb350c5ceebac6de3a7297f29bd02b6b04e 100644 (file)
@@ -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 *
index 39b39779de73994285f821da8d4f0ec932b1ea4a..2ffd3a151fbef3d27e4f388aedc48c998ce9cf7b 100644 (file)
@@ -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 @@ public:
   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 @@ public:
   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;
index 47d90e51938813a5e476cfad01ce432cbb8976c0..07cd47bdc0e69b0a893a571f592f333a8690c453 100644 (file)
@@ -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 @@ public:
     /* 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 (file)
index 0000000..2a9eef4
--- /dev/null
@@ -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"
+}
index ca3a4d2375320b9c1fdb1bf8c5a4acaac861b669..07c51e7ac3fa00a8c077bfa1060976e7b1089185 100644 (file)
@@ -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,22 @@ 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 "set logging redirect on" \
+    "enable logging redirect via python"
+gdb_test_no_output "python gdb.execute('set logging enabled on')" \
+    "enable logging via python"
+
+# Even with logging redirect on, this should capture the output.
+gdb_test_no_output "python x = gdb.execute('print 23', to_string=True)" \
+    "print from python with logging redirect"
+
+gdb_test "set logging enabled off" \
+    "Done logging to .*gdb.txt." \
+    "disable logging"
+
+gdb_test "python print(x)" " = 23.*" \
+    "python still captured output"
index 5819dfa789759e1291aa4b8112bf689302ebcf5c..d9966c6ec693983b8d0d947d3fa72a8c7ff81ccb 100644 (file)
--- 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 &current_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 &current_ui->m_gdb_stderr;
+  return &current_ui->m_ui_stdout;
 }
 
+/* See utils.h.  */
+
 struct ui_file **
-current_ui_gdb_stdlog_ptr ()
+redirectable_stderr ()
 {
-  return &current_ui->m_gdb_stdlog;
+  return &current_ui->m_ui_stderr;
 }
 
+/* See utils.h.  */
+
+struct ui_file **
+redirectable_stdlog ()
+{
+  return &current_ui->m_ui_stdlog;
+}
+
+/* See utils.h.  */
+
 struct ui_file **
-current_ui_gdb_stdtarg_ptr ()
+redirectable_stdtarg ()
 {
-  return &current_ui->m_gdb_stdtarg;
+  return &current_ui->m_ui_stdtarg;
 }
 
 struct ui_out **
@@ -603,8 +666,6 @@ 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 (&current_ui->async, 0);
@@ -612,13 +673,13 @@ execute_fn_to_ui_file (struct ui_file *file, std::function<void(void)> fn)
   ui_out_redirect_pop redirect_popper (current_uiout, file);
 
   scoped_restore save_stdout
-    = make_scoped_restore (&gdb_stdout, file);
+    = make_scoped_restore (redirectable_stdout (), file);
   scoped_restore save_stderr
-    = make_scoped_restore (&gdb_stderr, file);
+    = make_scoped_restore (redirectable_stderr (), file);
   scoped_restore save_stdlog
-    = make_scoped_restore (&gdb_stdlog, file);
+    = make_scoped_restore (redirectable_stdlog (), file);
   scoped_restore save_stdtarg
-    = make_scoped_restore (&gdb_stdtarg, file);
+    = make_scoped_restore (redirectable_stdtarg (), file);
 
   fn ();
 }
index 1316c8bedd69891a37867488bd7d67505bd626ca..e171ae6263ece55094315f4765999781839b1f77 100644 (file)
@@ -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 ();
 }
index 7abe4fa3e81632103e1f51a06ef065d180692ded..41704fc44d13771030e21ae64cd73a785fff625d 100644 (file)
@@ -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
index 647e63d1bdef951f2d854455ce0ab047a382ccd6..9d71135bc3683dc22697305a157be03826076105 100644 (file)
--- 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;
 }
 
 
index ba98d2500afd9e41604d50bcbd62b8002c559469..c2b1bca2121f9eb0a22243261cddbe91c71c03ba 100644 (file)
--- a/gdb/ui.h
+++ b/gdb/ui.h
 #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
 {
index 4d888b802747d72a3bbb4ec61ae70b2a86c01e8c..7cab021e7a91526a866633863cbb17663d3e406c 100644 (file)
@@ -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.  */