]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
[gdb/rust] Fix handling of unsigned discriminant
authorTom de Vries <tdevries@suse.de>
Tue, 11 Nov 2025 21:34:24 +0000 (22:34 +0100)
committerTom de Vries <tdevries@suse.de>
Tue, 11 Nov 2025 21:34:24 +0000 (22:34 +0100)
On i686-linux, with test-case gdb.rust/simple.exp, we get:
...
(gdb) print str_none^M
$71 = core::option::Option<alloc::string::String>::Some(alloc::string::String {vec: alloc::vec::Vec<u8, alloc::alloc::Global> {buf: alloc::raw_vec::RawVec<u8, alloc::alloc::Global> {inner: alloc::raw_vec::RawVecInner<alloc::alloc::Global> {ptr: core::ptr::unique::Unique<u8> {pointer: core::ptr::non_null::NonNull<u8> {pointer: 0xbfffe6e8}, _marker: core::marker::PhantomData<u8>}, cap: core::num::niche_types::UsizeNoHighBit (2147483648), alloc: alloc::alloc::Global}, _marker: core::marker::PhantomData<u8>}, len: 4321411}})^M
(gdb) FAIL: $exp: print str_none
...
while this is expected:
...
(gdb) print str_none^M
$71 = core::option::Option<alloc::string::String>::None^M
(gdb) PASS: $exp: print str_none
...

Printing the variable in C mode:
...
$ gdb -q -batch outputs/gdb.rust/simple/simple \
    -ex "b 161" \
    -ex run \
    -ex "set language c" \
    -ex "p /x str_none"
  ...
$1 = {0x80000000, Some = {__0 = {vec = {buf = {inner = {ptr = {pointer = {pointer = 0xbfffedd8}, _marker = {<No data fields>}}, cap = {__0 = 0x80000000}, alloc = {<No data fields>}}, _marker = {<No data fields>}}, len = 0x41f083}}}}
...
shows us that the discriminant value is 0x80000000, which matches the "None"
variant:
...
 <3><1427>: Abbrev Number: 16 (DW_TAG_structure_type)
    <1428>   DW_AT_name        : Option<alloc::string::String>
    <142c>   DW_AT_byte_size   : 12
    <142d>   DW_AT_accessibility: 1     (public)
    <142e>   DW_AT_alignment   : 4
 <4><142f>: Abbrev Number: 47 (DW_TAG_variant_part)
    <1430>   DW_AT_discr       : <0x1434>
 <5><1434>: Abbrev Number: 48 (DW_TAG_member)
    <1435>   DW_AT_type        : <0x2cba>
    <1439>   DW_AT_alignment   : 4
    <143a>   DW_AT_data_member_location: 0
    <143b>   DW_AT_artificial  : 1
 <5><143b>: Abbrev Number: 52 (DW_TAG_variant)
    <143c>   DW_AT_discr_value : 0x80000000
 <6><1440>: Abbrev Number: 4 (DW_TAG_member)
    <1441>   DW_AT_name        : None
    <1445>   DW_AT_type        : <0x145a>
    <1449>   DW_AT_alignment   : 4
    <144a>   DW_AT_data_member_location: 0
 <6><144b>: Abbrev Number: 0
 <5><144c>: Abbrev Number: 51 (DW_TAG_variant)
 <6><144d>: Abbrev Number: 4 (DW_TAG_member)
    <144e>   DW_AT_name        : Some
    <1452>   DW_AT_type        : <0x146c>
    <1456>   DW_AT_alignment   : 4
    <1457>   DW_AT_data_member_location: 0
 <6><1458>: Abbrev Number: 0
 <5><1459>: Abbrev Number: 0
...
but the dynamic type resolves to the "Some" variant instead.

This is caused by signedness confusion.

The DW_AT_discr_value 0x80000000 is encoded as an LEB128 number, and the
signedness is determined by the "tag type for the variant part", which in this
case is unsigned:
...
 <1><2cba>: Abbrev Number: 6 (DW_TAG_base_type)
    <2cbb>   DW_AT_name        : u32
    <2cbf>   DW_AT_encoding    : 7      (unsigned)
    <2cc0>   DW_AT_byte_size   : 4
...

However, the value gets interpreted as signed instead (value printed in
resolve_dynamic_struct):
...
(gdb) p /x variant_prop.m_data.variant_parts.m_array.variants.m_array[0].discriminants.m_array[0]
$3 = {low = 0xffffffff80000000, high = 0xffffffff80000000}
...
and then compared against an unsigned 0x80000000 in variant::matches().

Fix this in create_one_variant_part, by passing the required signedness as a
parameter to create_one_variant.

Tested on i686-linux and x86_64-linux.

Approved-By: Tom Tromey <tom@tromey.com>
PR rust/33620
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33620

gdb/dwarf2/read.c

index df6e784025dced93edbd542d0fc876636183a205..29f100b7638248f60afb30b5e7ab42f085088fd7 100644 (file)
@@ -10439,14 +10439,16 @@ static const gdb::array_view<variant_part> create_variant_parts
    the variant to fill in.  OBSTACK is where any needed allocations
    will be done.  OFFSET_MAP holds the mapping from section offsets to
    fields for the type.  FI describes the fields of the type we're
-   processing.  FIELD is the variant field we're converting.  */
+   processing.  FIELD is the variant field we're converting.  IS_UNSIGNED
+   contains the signedness of the discriminant.  */
 
 static void
 create_one_variant (variant &result, struct obstack *obstack,
                    const offset_map_type &offset_map,
-                   struct field_info *fi, const variant_field &field)
+                   struct field_info *fi, const variant_field &field,
+                   bool is_unsigned)
 {
-  result.discriminants = convert_variant_range (obstack, field, false);
+  result.discriminants = convert_variant_range (obstack, field, is_unsigned);
   result.first_field = field.first_field + fi->baseclasses.size ();
   result.last_field = field.last_field + fi->baseclasses.size ();
   result.parts = create_variant_parts (obstack, offset_map, fi,
@@ -10485,7 +10487,7 @@ create_one_variant_part (variant_part &result,
   variant *output = new (obstack) variant[n];
   for (size_t i = 0; i < n; ++i)
     create_one_variant (output[i], obstack, offset_map, fi,
-                       builder.variants[i]);
+                       builder.variants[i], result.is_unsigned);
 
   result.variants = gdb::array_view<variant> (output, n);
 }