From: Tom de Vries <tdevries@suse.de>
To: gdb-patches@sourceware.org
Subject: [PATCH 1/3] [gdb] Fix backtrace in log file (libbacktrace case)
Date: Fri, 23 Oct 2026 09:38:11 +0200 [thread overview]
Message-ID: <20261023073813.2224939-2-tdevries@suse.de> (raw)
In-Reply-To: <20261023073813.2224939-1-tdevries@suse.de>
Consider test-case gdb.base/logging-internal-error.exp (added by this patch).
It enables logging to a file log.txt, and then does "maint internal-error".
In gdb.log we find the expected output:
...
(gdb) maint internal-error
gdb/maint.c:82: internal-error:
A problem internal to GDB has been detected,
further debugging may prove unreliable.
----- Backtrace -----
0x659d42 gdb_internal_backtrace_1
gdb/bt-utils.c:122
...
0x419554 main
gdb/gdb.c:38
---------------------
This is a bug, please report it. For instructions, see:
<https://www.gnu.org/software/gdb/bugs/>.
Command aborted.
(gdb)
...
But in log.txt, the backtrace is missing:
...
gdb/maint.c:82: internal-error:
A problem internal to GDB has been detected,
further debugging may prove unreliable.
This is a bug, please report it. For instructions, see:
<https://www.gnu.org/software/gdb/bugs/>.
Command aborted.
...
The backtrace is printed using sig_write:
...
void
sig_write (const char *msg)
{
if (gdb_stderr == nullptr || gdb_stderr->fd () == -1)
std::ignore = ::write (2, msg, strlen (msg));
else
gdb_stderr->write_async_safe (msg, strlen (msg));
}
...
Because logging_file::fd () == -1, plain write is used instead of
write_async_safe. Consequently, the message is printed, but not
recorded in the log file.
Presumably the fd () == -1 check in sig_write is there to avoid running into
the default implementation of write_async_safe:
...
virtual void write_async_safe (const char *buf, long length_buf)
{ gdb_assert_not_reached ("write_async_safe"); }
...
when gdb_stderr is temporarily set to a string_file.
Fix this by:
- making the default write_async_safe call do something more useful than
asserting: a write to fd (),
- making write_async_safe return true if the write was handled, and
- using that return value in sig_write to decide whether to call a fallback.
---
gdb/bt-utils.c | 9 ++-
gdb/cli/cli-logging.c | 8 +-
gdb/logging-file.h | 2 +-
gdb/mi/mi-console.c | 7 +-
gdb/mi/mi-console.h | 2 +-
.../gdb.base/logging-internal-error.exp | 75 +++++++++++++++++++
gdb/ui-file.c | 17 +----
gdb/ui-file.h | 20 +++--
8 files changed, 108 insertions(+), 32 deletions(-)
create mode 100644 gdb/testsuite/gdb.base/logging-internal-error.exp
diff --git a/gdb/bt-utils.c b/gdb/bt-utils.c
index 21a291c3df3..fcb912c8021 100644
--- a/gdb/bt-utils.c
+++ b/gdb/bt-utils.c
@@ -45,10 +45,11 @@ gdb_internal_backtrace_set_cmd (const char *args, int from_tty,
void
sig_write (const char *msg)
{
- if (gdb_stderr == nullptr || gdb_stderr->fd () == -1)
- std::ignore = ::write (2, msg, strlen (msg));
- else
- gdb_stderr->write_async_safe (msg, strlen (msg));
+ size_t len = strlen (msg);
+
+ if (gdb_stderr == nullptr
+ || !gdb_stderr->write_async_safe (msg, len))
+ std::ignore = ::write (2, msg, len);
}
#ifdef GDB_PRINT_INTERNAL_BACKTRACE
diff --git a/gdb/cli/cli-logging.c b/gdb/cli/cli-logging.c
index c9482d59199..d8212de4abb 100644
--- a/gdb/cli/cli-logging.c
+++ b/gdb/cli/cli-logging.c
@@ -174,13 +174,15 @@ logging_file<T>::write (const char *buf, long length_buf)
/* See logging-file.h. */
template<typename T>
-void
+bool
logging_file<T>::write_async_safe (const char *buf, long length_buf)
{
+ bool handled = false;
if (log_file != nullptr)
- log_file->write_async_safe (buf, length_buf);
+ handled = log_file->write_async_safe (buf, length_buf);
if (ordinary_output ())
- m_out->write_async_safe (buf, length_buf);
+ handled = m_out->write_async_safe (buf, length_buf);
+ return handled;
}
/* See logging-file.h. */
diff --git a/gdb/logging-file.h b/gdb/logging-file.h
index 5dfe1118494..f8b9f101150 100644
--- a/gdb/logging-file.h
+++ b/gdb/logging-file.h
@@ -41,7 +41,7 @@ class logging_file : public ui_file
}
void write (const char *buf, long length_buf) override;
- void write_async_safe (const char *buf, long length_buf) override;
+ bool write_async_safe (const char *buf, long length_buf) override;
void puts (const char *) override;
void flush () override;
bool can_page () const override;
diff --git a/gdb/mi/mi-console.c b/gdb/mi/mi-console.c
index 1a10bfa21fb..a5f69691277 100644
--- a/gdb/mi/mi-console.c
+++ b/gdb/mi/mi-console.c
@@ -47,10 +47,12 @@ mi_console_file::write (const char *buf, long length_buf)
this->flush ();
}
-void
+bool
mi_console_file::write_async_safe (const char *buf, long length_buf)
{
- m_raw->write_async_safe (m_prefix, strlen (m_prefix));
+ if (!m_raw->write_async_safe (m_prefix, strlen (m_prefix)))
+ return false;
+
if (m_quote)
{
m_raw->write_async_safe (&m_quote, 1);
@@ -62,6 +64,7 @@ mi_console_file::write_async_safe (const char *buf, long length_buf)
char nl = '\n';
m_raw->write_async_safe (&nl, 1);
+ return true;
}
void
diff --git a/gdb/mi/mi-console.h b/gdb/mi/mi-console.h
index 8dea558fb9b..3e5618495ee 100644
--- a/gdb/mi/mi-console.h
+++ b/gdb/mi/mi-console.h
@@ -36,7 +36,7 @@ class mi_console_file : public ui_file
void write (const char *buf, long length_buf) override;
- void write_async_safe (const char *buf, long length_buf) override;
+ bool write_async_safe (const char *buf, long length_buf) override;
private:
/* The wrapped raw output stream. */
diff --git a/gdb/testsuite/gdb.base/logging-internal-error.exp b/gdb/testsuite/gdb.base/logging-internal-error.exp
new file mode 100644
index 00000000000..b121ca42612
--- /dev/null
+++ b/gdb/testsuite/gdb.base/logging-internal-error.exp
@@ -0,0 +1,75 @@
+# Copyright 2026 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/>.
+
+# Check that the backtrace generated by "maint internal-error", if any, also
+# ends up in the log file.
+
+require {!isremote host}
+
+# Do not run if gdb debug is enabled as it will interfere with logging.
+require !gdb_debug_enabled
+
+clean_restart
+
+# Don't quit, don't produce a core file.
+gdb_test_no_output "maint set internal-error corefile no"
+gdb_test_no_output "maint set internal-error quit no"
+
+set log_name [standard_output_file log.txt]
+gdb_test_no_output "set logging file $log_name" \
+ "set logging filename"
+gdb_test_no_output "set logging overwrite on"
+gdb_test "set logging enabled on" "Copying output to .*"
+
+set seen_bt 0
+set re_bt [string_to_regexp "----- Backtrace -----"]
+
+set cmd "maint internal-error"
+send_gdb "$cmd\n"
+set re \
+ [multi_line \
+ [string_to_regexp "Command aborted."] \
+ "$gdb_prompt $"]
+gdb_expect {
+ -re $re_bt {
+ set seen_bt 1
+ exp_continue
+ }
+ -re $re {
+ pass $cmd
+ }
+ -re "$gdb_prompt $" {
+ fail $cmd
+ }
+ timeout {
+ fail "$cmd (timeout)"
+ }
+ eof {
+ fail "$cmd (eof)"
+ }
+}
+
+gdb_test "set logging enabled off" "Done logging to .*"
+
+set fd [open $log_name]
+set data [read -nonewline $fd]
+close $fd
+
+set test "Logged backtrace"
+if {$seen_bt} {
+ gdb_assert {[regexp $re_bt $data]} $test
+} else {
+ unsupported $test
+}
diff --git a/gdb/ui-file.c b/gdb/ui-file.c
index 006bdc923e4..85de5440f0c 100644
--- a/gdb/ui-file.c
+++ b/gdb/ui-file.c
@@ -157,10 +157,11 @@ null_file::puts (const char *)
/* Discard the request. */
}
-void
+bool
null_file::write_async_safe (const char *buf, long sizeof_buf)
{
- /* Discard the request. */
+ /* Discard the request. We consider it handled though. */
+ return true;
}
\f
@@ -271,18 +272,6 @@ stdio_file::write (const char *buf, long length_buf)
}
}
-void
-stdio_file::write_async_safe (const char *buf, long length_buf)
-{
- /* This is written the way it is to avoid a warning from gcc about not using the
- result of write (since it can be declared with attribute warn_unused_result).
- Alas casting to void doesn't work for this. */
- if (::write (m_fd, buf, length_buf))
- {
- /* Nothing. */
- }
-}
-
void
stdio_file::puts (const char *linebuffer)
{
diff --git a/gdb/ui-file.h b/gdb/ui-file.h
index f5c23b9b6d9..23cbd2c7e78 100644
--- a/gdb/ui-file.h
+++ b/gdb/ui-file.h
@@ -20,6 +20,7 @@
#define GDB_UI_FILE_H
#include <string>
+#include <tuple>
#include "ui-style.h"
/* The abstract ui_file base class. */
@@ -67,9 +68,16 @@ class ui_file
first. Implementations are also free to ignore some or all of
the request. puts_async is not provided as the async versions
are rarely used, no point in having both for a rarely used
- interface. */
- virtual void write_async_safe (const char *buf, long length_buf)
- { gdb_assert_not_reached ("write_async_safe"); }
+ interface. This default implementation writes to fd (). Return true if
+ the write was handled. */
+ virtual bool write_async_safe (const char *buf, long length_buf)
+ {
+ int fd = this->fd ();
+ if (fd == -1)
+ return false;
+ std::ignore = ::write (fd, buf, length_buf);
+ return true;
+ }
/* Some ui_files override this to provide a efficient implementation
that avoids a strlen. */
@@ -163,7 +171,7 @@ class null_file : public ui_file
{
public:
void write (const char *buf, long length_buf) override;
- void write_async_safe (const char *buf, long sizeof_buf) override;
+ bool write_async_safe (const char *buf, long sizeof_buf) override;
void puts (const char *str) override;
};
@@ -267,8 +275,6 @@ class stdio_file : public ui_file
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;
long read (char *buf, long length_buf) override;
@@ -436,7 +442,7 @@ class wrapped_file : public ui_file
void write (const char *buf, long length_buf) override
{ return m_stream->write (buf, length_buf); }
- void write_async_safe (const char *buf, long length_buf) override
+ bool write_async_safe (const char *buf, long length_buf) override
{ return m_stream->write_async_safe (buf, length_buf); }
bool can_page () const override
--
2.51.0
next prev parent reply other threads:[~2026-10-23 7:39 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-23 7:38 [PATCH 0/3] [gdb] Fix backtrace in log file Tom de Vries
2026-10-23 7:38 ` Tom de Vries [this message]
2026-10-23 7:38 ` [PATCH 2/3] [gdb] Don't flush in handle_fatal_signal Tom de Vries
2026-10-23 7:38 ` [PATCH 3/3] [gdb] Fix backtrace in log file (execinfo case) Tom de Vries
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261023073813.2224939-2-tdevries@suse.de \
--to=tdevries@suse.de \
--cc=gdb-patches@sourceware.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox