From: Andrew Burgess <aburgess@redhat.com>
To: Simon Marchi <simon.marchi@efficios.com>, gdb-patches@sourceware.org
Cc: Simon Marchi <simon.marchi@polymtl.ca>
Subject: Re: [PATCH 2/2] gdb/macrotab: catch exception and rethrow around splay_tree_foreach
Date: Wed, 28 Oct 2026 17:37:00 +0000 [thread overview]
Message-ID: <87mrrx7mmb.fsf@redhat.com> (raw)
In-Reply-To: <20261026144606.1941722-2-simon.marchi@efficios.com>
Simon Marchi <simon.marchi@efficios.com> writes:
> From: Simon Marchi <simon.marchi@polymtl.ca>
>
> Bug 34876 talks about a crash that happens with "-O3 -flto", when
> completing and reaching the maximum number of completions.
>
> It can be reproduced by building GDB in this configuration:
>
> $ ./configure CFLAGS="-march=x86-64-v3 -flto -O3" CXXFLAGS="-march=x86-64-v3 -flto -O3" --disable-nls
>
> Then, using a dummy C program compiled with "gcc -g3", this crashes GDB:
>
> $ ./gdb -q -nx --data-directory data-directory ./a.out -ex list -ex "complete macro expand "
>
> The crash manifests as an invalid free, reported by glibc:
>
> free(): invalid pointer
>
> It is reported here:
>
> #0 0x00007ffff6a9a11c in ?? () from /usr/lib/libc.so.6
> #1 0x00007ffff6a3e5d0 in raise () from /usr/lib/libc.so.6
> #2 0x00007ffff6a25685 in abort () from /usr/lib/libc.so.6
> #3 0x00007ffff6a26704 in ?? () from /usr/lib/libc.so.6
> #4 0x00007ffff6aa47ec in ?? () from /usr/lib/libc.so.6
> #5 0x00007ffff6aa4804 in ?? () from /usr/lib/libc.so.6
> #6 0x00005555556e07ac in operator delete (p=<optimized out>) at /home/simark/src/binutils-gdb/gdbsupport/new-op.cc:108
> #7 operator delete (p=<optimized out>) at /home/simark/src/binutils-gdb/gdbsupport/new-op.cc:114
> #8 operator delete (p=<optimized out>) at /home/simark/src/binutils-gdb/gdbsupport/new-op.cc:120
> #9 std::__new_allocator<char>::deallocate (this=0x555555caa4f8 <completion_list_add_fields(completion_tracker&, symbol*, lookup_name_info const&, char const*, char const*)+136>, __p=<optimized out>, __n=<optimized out>) at /usr/include/c++/16/bits/new_allocator.h:183
> #10 std::allocator<char>::deallocate (this=0x555555caa4f8 <completion_list_add_fields(completion_tracker&, symbol*, lookup_name_info const&, char const*, char const*)+136>, __p=<optimized out>, __n=<optimized out>) at /usr/include/c++/16/bits/allocator.h:218
> #11 std::allocator_traits<std::allocator<char> >::deallocate (__a=..., __p=<optimized out>, __n=<optimized out>) at /usr/include/c++/16/bits/alloc_traits.h:689
> #12 std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_destroy (this=0x555555caa4f8 <completion_list_add_fields(completion_tracker&, symbol*, lookup_name_info const&, char const*, char const*)+136>, __size=<optimized out>) at /usr/include/c++/16/bits/basic_string.h:304
> #13 std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_dispose (this=0x555555caa4f8 <completion_list_add_fields(completion_tracker&, symbol*, lookup_name_info const&, char const*, char const*)+136>) at /usr/include/c++/16/bits/basic_string.h:298
> #14 std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string (this=0x555555caa4f8 <completion_list_add_fields(completion_tracker&, symbol*, lookup_name_info const&, char const*, char const*)+136>, this=<optimized out>) at /usr/include/c++/16/bits/basic_string.h:920
> #15 demangle_for_lookup_info::~demangle_for_lookup_info (this=0x555555caa4f8 <completion_list_add_fields(completion_tracker&, symbol*, lookup_name_info const&, char const*, char const*)+136>, this=<optimized out>) at /home/simark/src/binutils-gdb/gdb/symtab.h:183
> #16 std::_Optional_payload_base<demangle_for_lookup_info>::_M_destroy (this=0x555555caa4f8 <completion_list_add_fields(completion_tracker&, symbol*, lookup_name_info const&, char const*, char const*)+136>) at /usr/include/c++/16/optional:325
> #17 std::_Optional_payload_base<demangle_for_lookup_info>::_M_reset (this=0x555555caa4f8 <completion_list_add_fields(completion_tracker&, symbol*, lookup_name_info const&, char const*, char const*)+136>) at /usr/include/c++/16/optional:361
> #18 std::_Optional_payload<demangle_for_lookup_info, false, false, false>::~_Optional_payload (this=0x555555caa4f8 <completion_list_add_fields(completion_tracker&, symbol*, lookup_name_info const&, char const*, char const*)+136>, this=<optimized out>) at /usr/include/c++/16/optional:484
> #19 std::_Optional_base<demangle_for_lookup_info, false, false>::~_Optional_base (this=0x555555caa4f8 <completion_list_add_fields(completion_tracker&, symbol*, lookup_name_info const&, char const*, char const*)+136>, this=<optimized out>) at /usr/include/c++/16/optional:508
> #20 std::optional<demangle_for_lookup_info>::~optional (this=0x555555caa4f8 <completion_list_add_fields(completion_tracker&, symbol*, lookup_name_info const&, char const*, char const*)+136>, this=<optimized out>) at /usr/include/c++/16/optional:812
> #21 lookup_name_info::~lookup_name_info (this=0x555555caa440 <gdb::function_view<void (char const*, macro_definition const*, macro_source_file*, int)>::bind<default_collect_symbol_completion_matches_break_on(completion_tracker&, complete_symbol_mode, symbol_name_match_type, char const*, char const*, char const*, type_code)::{lambda(char const*, macro_definition const*, macro_source_file*, int)#1}>(default_collect_symbol_completion_matches_break_on(completion_tracker&, complete_symbol_mode, symbol_name_match_type, char const*, char const*, char const*, type_code)::{lambda(char const*, macro_definition const*, macro_source_file*, int)#1}&)::{lambda(gdb::fv_detail::erased_callable, char const*, macro_definition const*, macro_source_file*, int)#1}::_FUN(gdb::fv_detail::erased_callable, char const*, macro_definition const*, macro_source_file*, int)>, this=<optimized out>) at /home/simark/src/binutils-gdb/gdb/symtab.h:210
> #22 0x000055555569ca89 in default_collect_symbol_completion_matches_break_on (tracker=..., mode=<optimized out>, name_match_type=<optimized out>, text=<optimized out>, word=<optimized out>, break_on=<optimized out>, code=<optimized out>) at /home/simark/src/binutils-gdb/gdb/symtab.c:6111
> #23 0x0000555555826c22 in language_defn::collect_symbol_completion_matches (this=<optimized out>, tracker=..., mode=<optimized out>, name_match_type=<optimized out>, text=<optimized out>, word=<optimized out>, code=<optimized out>, this=<optimized out>, tracker=..., mode=<optimized out>, name_match_type=<optimized out>, text=<optimized out>, word=<optimized out>, code=<optimized out>) at /home/simark/src/binutils-gdb/gdb/language.h:477
> #24 collect_symbol_completion_matches (tracker=..., mode=complete_symbol_mode::EXPRESSION, name_match_type=symbol_name_match_type::EXPRESSION, text=<optimized out>, word=<optimized out>) at /home/simark/src/binutils-gdb/gdb/symtab.c:6122
> #25 symbol_completer (ignore=<optimized out>, tracker=..., text=<optimized out>, word=<optimized out>) at /home/simark/src/binutils-gdb/gdb/completer.c:1533
> #26 0x000055555583ef85 in complete_line_internal_normal_command (command=0x7fffffffd880 "macro expand ", tracker=..., word=0x7fffffffd88d "", cmd_args=<optimized out>, reason=handle_completions, c=0x5555563b02c0) at /home/simark/src/binutils-gdb/gdb/completer.c:1620
> #27 complete_line_internal_1 (tracker=..., text=text@entry=0x7fffffffe7e2 "", line_buffer=line_buffer@entry=0x7fffffffe7d5 "macro expand ", point=<optimized out>, reason=reason@entry=handle_completions) at /home/simark/src/binutils-gdb/gdb/completer.c:1843
> #28 0x000055555583fe81 in complete_line_internal (tracker=..., text=0x7fffffffe7e2 "", line_buffer=<optimized out>, point=<optimized out>, reason=handle_completions) at /home/simark/src/binutils-gdb/gdb/completer.c:1862
> #29 complete_line (tracker=..., text=0x7fffffffe7e2 "", line_buffer=<optimized out>, point=<optimized out>) at /home/simark/src/binutils-gdb/gdb/completer.c:2157
> #30 complete_line (tracker=..., text=0x7fffffffe7e2 "", line_buffer=<optimized out>, point=<optimized out>) at /home/simark/src/binutils-gdb/gdb/completer.c:2152
> #31 complete(char const*, char const**, int*) [clone .constprop.0] (line=0x7fffffffe7d5 "macro expand ", word=0x7fffffffdb88, quote_char=<optimized out>) at /home/simark/src/binutils-gdb/gdb/completer.c:2126
> #32 0x00005555557e16db in complete_command (arg=0x7fffffffe7d5 "macro expand ", from_tty=<optimized out>) at /home/simark/src/binutils-gdb/gdb/cli/cli-cmds.c:425
>
> I asked Claude to find the root cause, and it came up with the following
> explanation:
>
> 1. With -batch, or after 200 matches interactively,
> completion_tracker::add_completion throws
> MAX_COMPLETIONS_REACHED_ERROR.
>
> 2. That throw happens inside the add_macro_name callback, which is called
> from libiberty's splay_tree_foreach (C code) via
> foreach_macro_in_scope in gdb/macrotab.c.
>
> 3. libiberty is compiled as C without -fexceptions, so GCC treats
> splay_tree_foreach as nothrow. With LTO, GCC can see that across
> translation units, so it decides macro_for_each_in_scope can't throw
> either. It then compiles the call at symtab.c:6052 as non-throwing.
>
> 4. At runtime the exception still unwinds, because x86-64 has CFI
> everywhere. But it lands in a landing pad that doesn't belong to that
> call site. That pad runs ~lookup_name_info() with this = %rbx. At the
> throwing call, %rbx actually holds the function_view invoker address
> (0x555555b32780, a code address), so it destroys garbage and hands a
> bogus pointer to free().
>
> I can't tell for sure that it's correct, but it sounds very plausible to
> me (the address 0x555555b32780 is from another run on another computer, not
> related to the backtrace above).
>
> On x86-64, the psABI mandates that all programs and libraries have an async
> unwind table (.eh_frame), so the exception is able to go through the C
> frames unnoticed most of the time. But on ARM, for example, programs don't
> have unwind tables by default. I was able to reproduce the bug on my old
> Raspberry Pi running an outdated Debian:
>
> (gdb) show version
> GNU gdb (Raspbian 10.1-1.7) 10.1.90.20210103-git
> ...
> (gdb) list
> 1 int main()
> 2 {
> 3 }
> (gdb) complete macro expand
> terminate called after throwing an instance of 'gdb_exception_error'
> Aborted
>
> There, the exception causes std::terminate to be called as soon as it
> reaches the libiberty C frame.
>
> This patch updates the two callbacks passed to splay_tree_foreach in
> this file (foreach_macro and foreach_macro_in_scope) to catch any
> exception that would escape, stash it in macro_for_each_data::exception,
> and return 1 to stop the splay tree iteration. The caller of
> splay_tree_foreach then rethrows the exception if there is one.
>
> Since I'm re-indenting the code anyway, I took the opportunity to
> modernize it a bit.
>
> Improve gdb.base/macscp.exp to kind of cover this. The test won't catch
> the bug on a regular x86-64 build, but it would in an LTO build like in
> the bug report, or on ARM without unwind tables. It would also catch it
> if we just added "noexcept" to the callbacks.
>
> There is another latent instance of this bug in addrmap.c, where we call
> splay_tree_foreach and the callback can throw. And probably more when
> using other APIs. As future work, I propose that we go over all the
> callbacks that we pass to a C API to mark them "noexcept", so that these
> cases of exceptions crossing C frames consistently crash GDB on any
> configuration. And fix them if necessary to catch and rethrow
> exceptions like done in this patch.
>
> Change-Id: Ie7c4515a4dee1c93b04271f1d5057be23c96c039
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=34876
> ---
> gdb/macrotab.c | 73 ++++++++++++++++++++-----------
> gdb/testsuite/gdb.base/macscp.exp | 27 ++++++++++++
> 2 files changed, 75 insertions(+), 25 deletions(-)
>
> diff --git a/gdb/macrotab.c b/gdb/macrotab.c
> index 078eb5cb5aa7..a9b678b32e70 100644
> --- a/gdb/macrotab.c
> +++ b/gdb/macrotab.c
> @@ -945,22 +945,31 @@ struct macro_for_each_data
> gdb::function_view<macro_callback_fn> fn;
> struct macro_source_file *file;
> int line;
> + std::exception_ptr exception;
> };
>
> /* Helper function for macro_for_each. */
> static int
> -foreach_macro (splay_tree_node node, void *arg)
> +foreach_macro (splay_tree_node node, void *arg) noexcept
> {
> - struct macro_for_each_data *datum = (struct macro_for_each_data *) arg;
> - struct macro_key *key = (struct macro_key *) node->key;
> - struct macro_definition *def;
> + auto datum = static_cast<macro_for_each_data *> (arg);
>
> - std::string key_fullname = macro_source_fullname (key->start_file);
> - def = fixup_definition (key_fullname.c_str (), key->start_line,
> - (struct macro_definition *) node->value);
> + try
> + {
> + auto key = reinterpret_cast<macro_key *> (node->key);
> + auto value = reinterpret_cast<macro_definition *> (node->value);
> + std::string key_fullname = macro_source_fullname (key->start_file);
> + macro_definition *def = fixup_definition (key_fullname.c_str (),
> + key->start_line, value);
>
> - datum->fn (key->name, def, key->start_file, key->start_line);
> - return 0;
> + datum->fn (key->name, def, key->start_file, key->start_line);
> + return 0;
> + }
> + catch (...)
> + {
> + datum->exception = std::current_exception ();
> + return 1;
> + }
> }
>
> /* Call FN for every macro in TABLE. */
> @@ -974,28 +983,39 @@ macro_for_each (struct macro_table *table,
> datum.file = NULL;
Given you are modernising the code anyway, maybe NULL -> nullptr here?
But otherwise, this looks great. The approach and implementation make
sense to me.
Thanks for fixing this.
Approved-By: Andrew Burgess <aburgess@redhat.com>
Thanks,
Andrew
> datum.line = 0;
> splay_tree_foreach (table->definitions, foreach_macro, &datum);
> +
> + if (datum.exception != nullptr)
> + std::rethrow_exception (datum.exception);
> }
>
> static int
> -foreach_macro_in_scope (splay_tree_node node, void *info)
> +foreach_macro_in_scope (splay_tree_node node, void *info) noexcept
> {
> - struct macro_for_each_data *datum = (struct macro_for_each_data *) info;
> - struct macro_key *key = (struct macro_key *) node->key;
> - struct macro_definition *def;
> + auto datum = static_cast<macro_for_each_data *> (info);
>
> - std::string datum_fullname = macro_source_fullname (datum->file);
> - def = fixup_definition (datum_fullname.c_str (), datum->line,
> - (struct macro_definition *) node->value);
> + try
> + {
> + auto key = reinterpret_cast<macro_key *> (node->key);
> + auto value = reinterpret_cast<macro_definition *> (node->value);
> + std::string datum_fullname = macro_source_fullname (datum->file);
> + macro_definition *def = fixup_definition (datum_fullname.c_str (),
> + datum->line, value);
>
> - /* See if this macro is defined before the passed-in line, and
> - extends past that line. */
> - if (compare_locations (key->start_file, key->start_line,
> - datum->file, datum->line) < 0
> - && (!key->end_file
> - || compare_locations (key->end_file, key->end_line,
> - datum->file, datum->line) >= 0))
> - datum->fn (key->name, def, key->start_file, key->start_line);
> - return 0;
> + /* See if this macro is defined before the passed-in line, and
> + extends past that line. */
> + if (compare_locations (key->start_file, key->start_line,
> + datum->file, datum->line) < 0
> + && (key->end_file == nullptr
> + || compare_locations (key->end_file, key->end_line,
> + datum->file, datum->line) >= 0))
> + datum->fn (key->name, def, key->start_file, key->start_line);
> + return 0;
> + }
> + catch (...)
> + {
> + datum->exception = std::current_exception ();
> + return 1;
> + }
> }
>
> /* Call FN for every macro is visible in SCOPE. */
> @@ -1010,6 +1030,9 @@ macro_for_each_in_scope (struct macro_source_file *file, int line,
> datum.line = line;
> splay_tree_foreach (file->table->definitions,
> foreach_macro_in_scope, &datum);
> +
> + if (datum.exception != nullptr)
> + std::rethrow_exception (datum.exception);
> }
>
>
> diff --git a/gdb/testsuite/gdb.base/macscp.exp b/gdb/testsuite/gdb.base/macscp.exp
> index e40bfb633378..e0bcddb9312b 100644
> --- a/gdb/testsuite/gdb.base/macscp.exp
> +++ b/gdb/testsuite/gdb.base/macscp.exp
> @@ -14,6 +14,7 @@
> # You should have received a copy of the GNU General Public License
> # along with this program. If not, see <http://www.gnu.org/licenses/>.
>
> +load_lib completion-support.exp
>
> standard_testfile macscp1.c
> set objfile [standard_output_file ${testfile}.o]
> @@ -750,3 +751,29 @@ gdb_test "macro expand siginfo.si_addr" \
> gdb_test "print __FILE__" " = \".*macscp1.c\""
> gdb_test "print __LINE__" \
> " = [gdb_get_line_number {stopping point for line test}]"
> +
> +# Regression test for bug 34876.
> +
> +with_test_prefix "max-completions reached" {
> + with_set max-completions 2 {
> + # Macros from the program, visited by foreach_macro_in_scope. All
> + # the BEFORE_<func> macros are in scope at this point. The macros
> + # are visited in alphabetical order.
> + test_gdb_complete_multiple "macro expand " "BEFORE_" "MACSCP1_" {
> + "BEFORE_MACSCP1_1"
> + "BEFORE_MACSCP1_2"
> + } "" "" true "program macros"
> +
> + # User-defined macros, visited by foreach_macro.
> + gdb_test_no_output "macro define USER_MACRO_A 1"
> + gdb_test_no_output "macro define USER_MACRO_B 2"
> + gdb_test_no_output "macro define USER_MACRO_C 3"
> + test_gdb_complete_multiple "macro expand " "USER_MACRO_" "" {
> + "USER_MACRO_A"
> + "USER_MACRO_B"
> + } "" "" true "user macros"
> + gdb_test_no_output "macro undef USER_MACRO_A"
> + gdb_test_no_output "macro undef USER_MACRO_B"
> + gdb_test_no_output "macro undef USER_MACRO_C"
> + }
> +}
> --
> 2.56.0
next prev parent reply other threads:[~2026-10-28 17:37 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-26 14:45 [PATCH 1/2] gdb/testsuite: fix max-completions regex in test_gdb_complete_cmd_multiple Simon Marchi
2026-10-26 14:45 ` [PATCH 2/2] gdb/macrotab: catch exception and rethrow around splay_tree_foreach Simon Marchi
2026-10-28 17:37 ` Andrew Burgess [this message]
2026-10-28 19:24 ` Simon Marchi
2026-10-28 17:21 ` [PATCH 1/2] gdb/testsuite: fix max-completions regex in test_gdb_complete_cmd_multiple Andrew Burgess
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=87mrrx7mmb.fsf@redhat.com \
--to=aburgess@redhat.com \
--cc=gdb-patches@sourceware.org \
--cc=simon.marchi@efficios.com \
--cc=simon.marchi@polymtl.ca \
/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