Fixing gdb crash issue when loading an elf file compiled with rust 1.87.0

Dominic Clifton
2025-10-05T15:07:47Z

[-- Attachment #1.1: Type: text/plain, Size: 5130 bytes --]

Problem:

When starting a debugger session to an STM32H743ZI development board with
the Embassy stm32h7 blinky example GDB crashes with this error:

terminate called after throwing an instance of 'std::logic_error'

what(): basic_string: construction from null is not valid

TL;DR: missing null-pointer checking for the name and directory in the dwarf
debug info, patches attached.

Background:

I do desktop and embedded development, for many years, in Rust and C/C++.
My C/C++ is rusty though, so this patch should be checked by maintainers
familiar with the code for handling dwarf files.

I tried older released versions of arm-gnu-toolchain with GDB, as follows:

arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi = working, but too
old, wasn't compiled with python

arm-gnu-toolchain-14.2.rel1-mingw-w64-i686-arm-none-eabi = not working, same
issue

arm-gnu-toolchain-14.3.rel1-mingw-w64-x86_64-arm-none-eabi = not working,
same issue

$ /C/Program\ Files/JetBrains/CLion\ 2025.2.3/bin/gdb/win/x64/bin/gdb.exe
-version (GNU gdb (GDB; JetBrains IDE bundle; build 216) 15.2) = not
working, same issue

Locally built "GNU gdb (GDB) 16.3" = not working, same issue.

So down the rabbit hole I went.

After enabling RustRover 2025.2 debug logging and finding the MI2
communication sequence I found this in the log:

2025-10-04 09:19:22,164 [48150271] FINE - #c.j.c.e.debugger -
32676>31-target-select remote :1337

2025-10-04 09:19:22,379 [48150486] FINE - #c.j.c.e.debugger -
32676<terminate called after throwing an instance of 'std::logic_error'

2025-10-04 09:19:22,379 [48150486] FINE - #c.j.c.e.debugger - 32676< what():
basic_string: construction from null is not valid

I re-created this on the command line, avoiding the IDE entirely, built the
the latest released version of gdb (16.2) from a release tarball, on mysys2
ucrt64, and ran it though a local gdb instance:

$ echo "-target-select remote :1337" > commands.mi2

$ gdb /ucrt64/bin/arm-none-eabi-gdb

(gdb) set args
D:\\Users\\Hydra\\Documents\\dev\\projects\\embassy\\examples\\stm32h7\\targ
et\\thumbv7em-none-eabihf\\release\\blinky --interpreter=mi2 < commands.mi2

(gdb) catch throw

(gdb) run

This caused the same error again, I dumped the backtrace.

(gdb) bt

.

#9 0x00007ff7a66e0e28 in std::__cxx11::basic_string<char,
std::char_traits<char>, std::allocator<char>
>::basic_string<std::allocator<char> > (__a=..., this=0x7feb80,
__s=<optimized out>)

at C:/msys64/ucrt64/include/c++/14.2.0/bits/basic_string.h:651

#10 read_file_scope (die=0x5d42d40, cu=0x5b148b0) at dwarf2/read.c:7536

#11 process_die (die=0x5d42d40, cu=cu@entry=0x5b148b0) at dwarf2/read.c:6497

#12 0x00007ff7a66e4680 in process_full_comp_unit
(pretend_language=<optimized out>, cu=0x5b148b0) at dwarf2/read.c:6260

#13 process_queue (per_objfile=<optimized out>) at dwarf2/read.c:5552

#14 dw2_do_instantiate_symtab (per_cu=0x5a69e20, per_objfile=<optimized
out>, skip_partial=<optimized out>) at dwarf2/read.c:1779

.

After looking at the code and trying a few things, with the aid of chatgpt
since I'm unfamiliar with the codebase and gdb internals, it seems some
additional null-pointer checking is required when handing some debugging
information for the `asm.S` file used in the build process for elf files
generated by cargo/rust for the Embassy stm32 examples.

I made two patches, one to find_file_and_directory and one to
read_file_scope.

After applying the first patch, the problem still occurred, and after
further digging a second patch was made, when it was applied the debugger
no-longer crashes and I'm able to step-debug the embedded rust program in
RustRover 2025.2

For reference, you can generate the same elf file I was using as follows:

git clone https://github.com/embassy-rs/embassy.git

cd embassy

git checkout e4e06ab5b136a21c98d42491ef1e31b6dc25e08e

cd examples/stm32h7

cargo build --bin blinky -release

the obj-dump tool can be used to dump the dwarf debugging information.

arm-none-eabi-objdump.exe -h ./target/thumbv7em-none-eabihf/release/blinky
-W > dwarf.txt

I then checked out the latest version of gdb from the gdb git repo and build
it, again on windows 11/msys2/ucrt64.

I then ported the changes to the latest version and built again, re-tested,
all ok, and exported two patches from git, attached.

0001-find_file_and_directory-return-empty-strings-instead.patch

0002-read_file_scope-avoid-eventual-crash-when-the-file-o.patch

It's also possible that there's an issue with the tools used to create the
.elf file in the first place which may also need investigating and reporting
to the appropriate team (I think llvm in this case). The rust tools
(cargo/rustc/llvm linker/etc) are are already in the wild, I was using
stable rust 1.87.0.

It would be fantastic to have these fixes, or a better one based on these
patches, applied to GDB so that other developers don't have to go though a
day's worth of troubleshooting and compiling of and patching gdb to be able
to debug their embedded apps.

Kind regards,

Dominic Clifton

[-- Attachment #1.2: Type: text/html, Size: 9431 bytes --]

[-- Attachment #2: 0001-find_file_and_directory-return-empty-strings-instead.patch --]
[-- Type: application/octet-stream, Size: 1310 bytes --]

From f1b1d70a13a500936e15fa32025f0624efcb5cfb Mon Sep 17 00:00:00 2001
From: Dominic Clifton <me@dominicclifton.name>
Date: Sun, 5 Oct 2025 12:31:47 +0200
Subject: [PATCH 1/2] find_file_and_directory - return empty strings instead of
null pointers.

---
gdb/dwarf2/read.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index 955893c5f0c..525357f7d2e 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -5942,10 +5942,18 @@ find_file_and_directory (struct die_info *die, struct dwarf2_cu *cu)
if (cu->per_cu->fnd != nullptr)
return *cu->per_cu->fnd;

+ // Ensure that nullptrs become empty strings
+ const char *name = dwarf2_string_attr(die, DW_AT_name, cu);
+ const char *comp_dir = dwarf2_string_attr(die, DW_AT_comp_dir, cu);
+
+ if (!name)
+ name = "";
+ if (!comp_dir)
+ comp_dir = "";
+
/* Find the filename. Do not use dwarf2_name here, since the filename
is not a source language identifier. */
- file_and_directory res (dwarf2_string_attr (die, DW_AT_name, cu),
- dwarf2_string_attr (die, DW_AT_comp_dir, cu));
+ file_and_directory res(name, comp_dir);

if (res.get_comp_dir () == nullptr
&& cu->producer_is_gcc_lt_4_3 ()
--
2.48.1

[-- Attachment #3: 0002-read_file_scope-avoid-eventual-crash-when-the-file-o.patch --]
[-- Type: application/octet-stream, Size: 2523 bytes --]

From d3eb6fb04c17a1a3b22a13e2b5bb2fa023b00b7d Mon Sep 17 00:00:00 2001
From: Dominic Clifton <me@dominicclifton.name>
Date: Sun, 5 Oct 2025 12:40:28 +0200
Subject: [PATCH 2/2] read_file_scope - avoid eventual crash when the file or
directory name is null.

* without this, launching the debugger results in the following error:

```
terminate called after throwing an instance of 'std::logic_error' what(): basic_string: construction from null is not valid
```
---
gdb/dwarf2/read.c | 17 +++++++++++------
1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index 525357f7d2e..287ce7b518c 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -6091,28 +6091,33 @@ read_file_scope (struct die_info *die, struct dwarf2_cu *cu)

file_and_directory &fnd = find_file_and_directory (die, cu);

+ // ensure fnd.get_name() and fnd.get_comp_dir() are never null, which they sometimes are
+ const char *fname = fnd.get_name() ? fnd.get_name() : "";
+ const char *compdir = fnd.get_comp_dir() ? fnd.get_comp_dir() : "";
+
/* GAS supports generating dwarf-5 info starting version 2.35. Versions
2.35-2.37 generate an incorrect CU name attribute: it's relative,
implicitly prefixing it with the compilation dir. Work around this by
prefixing it with the source dir instead. */
- if (cu->header.version == 5 && !IS_ABSOLUTE_PATH (fnd.get_name ())
+ if (cu->header.version == 5 && !IS_ABSOLUTE_PATH (fname)
&& cu->producer_is_gas_lt_2_38 ())
{
attr = dwarf2_attr (die, DW_AT_stmt_list, cu);
if (attr != nullptr && attr->form_is_unsigned ())
{
sect_offset line_offset = (sect_offset) attr->as_unsigned ();
- line_header_up lh = dwarf_decode_line_header (line_offset, cu,
- fnd.get_comp_dir ());
+ line_header_up lh = dwarf_decode_line_header (line_offset, cu, compdir);
if (lh->version == 5 && lh->include_dir_at (1) != nullptr)
{
- std::string dir = lh->include_dir_at (1);
- fnd.set_comp_dir (std::move (dir));
+ const char* dir_ptr = lh->include_dir_at (1);
+ std::string dir = dir_ptr ? dir_ptr : compdir;
+
+ fnd.set_comp_dir (std::move (dir));
}
}
}

- cu->start_compunit_symtab (fnd.get_name (), fnd.intern_comp_dir (objfile),
+ cu->start_compunit_symtab (fname, fnd.intern_comp_dir (objfile),
lowpc);

gdb_assert (per_objfile->sym_cu == nullptr);
--
2.48.1