gdb/gdb-rhbz2467251-computed-location-synthetic-pointers.patch
Guinevere Larsen e2a93eef3b Rebase GDB to Fedora's 17.2 release
Resolves: RHEL-126216
2026-07-15 11:38:36 -03:00

1230 lines
43 KiB
Diff
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

From FEDORA_PATCHES Mon Sep 17 00:00:00 2001
From: Andrew Burgess <aburgess@redhat.com>
Date: Fri, 8 May 2026 21:44:12 +0100
Subject: gdb-rhbz2467251-computed-location-synthetic-pointers.patch
;; Backport the following upstream commits in order to address RHBZ
;; 2467251: d980317c7f1, 958d06262a7, 7d1d7386561, 8915de0883c,
;; 8f65ab7b71f. These commits will all drop out when we rebase to GDB
;; 18.
*** Commit d980317c7f1: gdb: int to bool conversion in valprint.{c,}h
gdb: int to bool conversion in valprint.{c,h}
Some int to bool conversion in valprint.c and valprint.h. I also
moved the header comment on val_print_scalar_type_p into the header.
There should be no user visible changes after this commit.
Approved-By: Tom Tromey <tom@tromey.com>
Commit 958d06262a7: gdb: fix coerce_pieced_ref for multi-piece values
gdb: fix coerce_pieced_ref for multi-piece values
Bug PR gdb/30693 describes a case where the following assertion can be
triggered:
../../gdb/dwarf2/loc.c:2213: internal-error: value* coerce_pieced_ref(const value*): Assertion `closure->pieces.size () == 1' failed.
The problem is that coerce_pieced_ref makes the following claim:
/* gdb represents synthetic pointers as pieced values with a single
piece. */
gdb_assert (closure != NULL);
gdb_assert (closure->pieces.size () == 1);
But this is not really true. If an aggregate type contains a synthetic
pointer, then it is possible that the aggregate type will have a
computed location consisting of multiple pieces. When GDB prints the
fields of that aggregate type these fields are extracted by calling
value::primitive_field. Within value::primitive_field the location of
the field is set by calling value::set_component_location.
When the parent value that holds the field has a computed location, the
field value gains a reference to the parent value's closure, this can be
seen in copy_pieced_value_closure in dwarf2/expr.c.
What this means is that, if the aggregate value has a multi-piece
computed location, then the synthetic pointer field will also have a
reference to that same multi-piece computed location, even if there is
really only a single piece that describes the synthetic pointer itself.
Some parts of GDB are already aware of this. If we look at
check_pieced_synthetic_pointer which implements the
value::bits_synthetic_pointer function, you'll see that this function
searches through all of the pieces to find the piece that covers the
value we are looking for, it then checks if that piece is an implicit
pointer location. But back in coerce_pieced_ref, after calling
value::bits_synthetic_pointer, we still make the assertion that there
will be just a single piece.
Fix this by copying the search through all pieces logic into
coerce_pieced_ref (see note on efficiency below). We now search through
all the pieces looking for a piece that describes the location of the
synthetic pointer, and we then use that piece to form the pointer's
value.
There are some assertions in the new code, these align with how
check_pieced_synthetic_pointer operates.
In addition, there is an error for the case where multiple pieces are
used to describe the location of a synthetic pointer. This case is
technically allowed by check_pieced_synthetic_pointer, but supporting
this would require changes to indirect_synthetic_pointer, so I propose
leaving that until we see such a case in the wild.
On efficiency, you'll notice that check_pieced_synthetic_pointer
performs a search through all the location pieces, and
coerce_pieced_ref also has to search through the pieces. It would be
nice if this could be avoided in order to avoid multiple searches.
Currently though coerce_pieced_ref calls
value->bits_synthetic_pointer, which is an API that should be agnostic
to the underlying implementation, i.e. shouldn't need to know that the
implementation is computed, so passing pieces back would be harder.
Maybe coerce_pieced_ref could avoid the value::bits_synthetic_pointer
call, and instead call check_pieced_synthetic_pointer directly, or
some related helper function, and could get the pieces back that way.
But this breaks the cleanly structured API that we currently have.
For now I'm leaving things as they are. My assumption is that the
number of pieces used to represent a value is pretty low, so the
search is actually pretty cheap.
There's a new test that uses the DWARF assembler to create a
representative example of a multi-piece aggregate that contains a
synthetic pointer member variable. This test triggers the assertion
before this commit.
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=30693
Bug: https://bugzilla.redhat.com/show_bug.cgi?id=2467251
Approved-By: Tom Tromey <tom@tromey.com>
Commit 7d1d7386561: gdb: rename argument in valprint_check_validity
gdb: rename argument in valprint_check_validity
To understand the motivation for this commit you'll need to look ahead
two commits in this series for an upcoming bug fix.
Look at coerce_pieced_ref in dwarf2/expr.c, and this call:
if (value->bits_synthetic_pointer (value->embedded_offset (),
TARGET_CHAR_BIT * type->length ()))
{ ... }
This passes the result from value::embedded_offset as the first argument.
Though it's not clear from value.h, the number returned by
value::embedded_offset is a byte offset.
The value::bits_synthetic_pointer call can end up calling
check_pieced_synthetic_pointer, with the first argument to
value::bits_synthetic_pointer becoming the second argument to
check_pieced_synthetic_pointer. The second argument to
check_pieced_synthetic_pointer is called BIT_OFFSET, a value in bits.
Looking at how this argument is used confirms that it is expected to be
a value in bits, not bytes.
The fix should be easy, multiply the embedded offset by
TARGET_CHAR_BIT, but I think things are a little more complex than
this.
I started looking at how value::bits_synthetic_pointer is used, there
are only 7 uses, and they can be grouped as follows:
gdb/dwarf2/expr.c
gdb/valops.c
These call value::embedded_offset directly within the call to
value::bits_synthetic_pointer.
gdb/opencl-lang.c
This function is used as a wrapper that implements a
lval_funcs::check_synthetic_pointer callback, it adjusts the
arguments and then calls value::bits_synthetic_pointer. As such we'd
not expect to see embedded offset mentioned here as that would (we
assume) be handled by the caller.
gdb/valprint.c (in generic_val_print_ref)
This call passes an argument called 'embedded_offset', but the
function generic_val_print_ref is only used in one place, and the
embedded_offset is always passed as zero.
gdb/valprint.c (in valprint_check_validity)
This call also passes an argument called 'embedded_offset'. This
function is used in two places. In common_val_print the
embedded_offset is always passed as zero, but in
valprint_check_validity (in cp-valprint.c) the embedded_offset being
passed is the offset of a field within the value, not the value's
embedded_offset within some larger value.
gdb/cp-valprint.c
gdb/p-valprint.c
As with the call to valprint_check_validity in cp-valprint.c, these
two direct calls to value::bits_synthetic_pointer pass the offset of a
field within the value, rather than the value's embedded_offset.
What I see in the above is some confusion. In some places we are
passing the value::embedded_offset, while in other places we are
passing the offset of a field within the value itself.
If we consider the direct call to value::bits_synthetic_pointer in
gdb/cp-valprint.c, where a field offset is passed, then it should be
possible, that if we can create an object with a non-zero
embedded_offset (which isn't accounted for in this code path), then we
should see some bugs in GDB, and indeed, this is what I do see.
My plan for fixing this is to have the offset passed to
value::bits_synthetic_pointer always be a field offset within the value,
the value::embedded_offset will then be handled directly within the
various value::bits_synthetic_pointer implementations.
This commit is a small refactor in preparation for this fix.
I believe part of the confusion here is that we have functions that
take arguments called embedded_offset, when the value they should
accept is no longer the embedded offset.
So in this commit I propose renaming the embedded_offset argument to
field_byte_offset in valprint_check_validity. This is purely a
mechanical rename, there should be no user visible changes after this
commit.
Approved-By: Tom Tromey <tom@tromey.com>
Commit 8915de0883c: gdb: remove embedded_offset argument that is always 0
gdb: remove embedded_offset argument that is always 0
For the background and motivation for this patch you should read the
previous commit.
The goal of this commit started as similar to the previous one; rename
the embedded_offset argument to generic_val_print_ref. To do this I
started tracing back which values are passed into this function so I
could make sure the new argument name matched the usage.
But it turns out that the only value that is ever passed to this
function is zero.
I believe that, like the last commit, the embedded_offset is actually
representing the offset of a field within a value. However, in all of
the use cases, the "field" being accessed is the entire value, hence
why we always pass 0, we are asking about the whole value starting
from the very beginning.
Given this, I couldn't bring myself to rename the argument. Let's just
remove it. It turns out that there's a bunch of functions in
valprint.c that take an argument called embedded_offset, which are
always zero.
This commit removes the argument, and updates the code to assume zero.
There should be no user visible changes after this commit.
Approved-By: Tom Tromey <tom@tromey.com>
Commit 8f65ab7b71f: gdb: use value::embedded_offset in check_pieced_synthetic_pointer
gdb: use value::embedded_offset in check_pieced_synthetic_pointer
This commit builds on the previous two commits. You should go and
read them both for context.
Looking at the users of value::bits_synthetic_pointer, all callers but
two are now passing either the offset of a field within the value, or
hard-coded zero as they want to ask about the entire value.
The two exceptions are in coerce_pieced_ref (in dwarf2/expr.c) and in
value_addr (in valops.c) where we still pass value::embedded_offset.
The problem is that some of the other callers, where we currently pass
just a field offset, might also have a non-zero embedded offset,
specifically, the call in cp_print_value_fields is in this category.
In cp_print_value_fields we are printing fields from a value which is
potentially a base class contained within an instance of a derived
class, this will be represented by a non-zero embedded_offset, which
is currently not taken into account.
Additionally, the two callers that currently pass the
value::embedded_offset don't scale the embedded offset from bytes to
bits.
The failure to scale from bytes to bits is interesting, I originally
tried to create some tests that exposed this, but constantly failed.
It turns out, that in both these locations, in all code paths that I
could find, the value::embedded_offset will always be zero. For
example in coerce_pieced_ref, a reference cannot be a base class, and
so we never expect to see a non-zero embedded_offset in this case.
Similarly, in value_addr, the use of value::bits_synthetic_pointer is
within a block that only applies to reference types, which means the
embedded_offset will be zero.
Now within check_pieced_synthetic_pointer we already add the
value::offset into the bit_offset which is passed in. Remember, after
the previous two commits, the incoming bit_offset is (almost) always
the offset of a field within the value. The almost here is the two
cases mentioned above.
I propose that within check_pieced_synthetic_pointer we should take
into account both value::offset and value::embedded_offset. Then the
two cases that currently pass value::embedded_offset can be changed to
just pass zero.
With this done, and the previous two commits, we now have a consistent
model. value::bits_synthetic_pointer expects the (bit) offset of a
field within the value to check. In many cases this will be zero
meaning we want to check from the start of the value, but in some
cases it can be non-zero.
Then within value::bits_synthetic_pointer implementations, like
check_pieced_synthetic_pointer, we will take the value::offset and
value::embedded_offset into account, remembering to scale them from
bytes to bits.
I have also changed indirect_pieced_value to also take the
embedded_offset into account. I have done this for consistency rather
than necessity. I believe that the embedded_offset will always be
zero within indirect_pieced_value. The indirect_pieced_value function
is only called from value_ind (in valops.c), and only operates on
TYPE_CODE_PTR types (checked for in indirect_pieced_value). As a
TYPE_CODE_PTR cannot be the base class for a derived type, then we
don't expect to ever see a TYPE_CODE_PTR value with a non-zero
embedded_offset. But, having indirect_pieced_value take the embedded
offset into account is simple enough, and future proofs the code.
In both check_pieced_synthetic_pointer and indirect_pieced_value I
have changed uses of '8' to 'TARGET_CHAR_BIT', I was already touching
some of these lines, and I think TARGET_CHAR_BIT is clearer, but one
line in indirect_pieced_value was just updated to use TARGET_CHAR_BIT,
this is done for consistency.
The gdb.dwarf2/multi-piece-inherited-bitfield.exp test fails without
this patch, this exposes the case where the embedded_offset is
non-zero and we were previously failing to take this into account.
The gdb.dwarf2/multi-piece-primitive-field.exp test was something I
wrote while trying to exercise the coerce_pieced_ref code path some
more. It is an inheritance based version of the existing test, I was
wondering if this would result in a value with a non-zero
embedded_offset, but due to how the fields are extracted from the
aggregate prior to calling coerce_pieced_ref the embedded_offset is
always zero in this function.
Approved-By: Tom Tromey <tom@tromey.com>
diff --git a/gdb/dwarf2/expr.c b/gdb/dwarf2/expr.c
--- a/gdb/dwarf2/expr.c
+++ b/gdb/dwarf2/expr.c
@@ -492,7 +492,8 @@ check_pieced_synthetic_pointer (const value *value, LONGEST bit_offset,
piece_closure *c = (piece_closure *) value->computed_closure ();
int i;
- bit_offset += 8 * value->offset ();
+ bit_offset += (TARGET_CHAR_BIT
+ * (value->offset () + value->embedded_offset ()));
if (value->bitsize ())
bit_offset += value->bitpos ();
@@ -537,8 +538,9 @@ indirect_pieced_value (value *value)
if (type->code () != TYPE_CODE_PTR)
return NULL;
- int bit_length = 8 * type->length ();
- LONGEST bit_offset = 8 * value->offset ();
+ int bit_length = TARGET_CHAR_BIT * type->length ();
+ LONGEST bit_offset
+ = (TARGET_CHAR_BIT * (value->offset () + value->embedded_offset ()));
if (value->bitsize ())
bit_offset += value->bitpos ();
@@ -602,22 +604,62 @@ coerce_pieced_ref (const value *value)
{
struct type *type = check_typedef (value->type ());
- if (value->bits_synthetic_pointer (value->embedded_offset (),
- TARGET_CHAR_BIT * type->length ()))
+ if (value->bits_synthetic_pointer (0, TARGET_CHAR_BIT * type->length ()))
{
const piece_closure *closure
= (piece_closure *) value->computed_closure ();
frame_info_ptr frame
= get_selected_frame (_("No frame selected."));
- /* gdb represents synthetic pointers as pieced values with a single
- piece. */
- gdb_assert (closure != NULL);
- gdb_assert (closure->pieces.size () == 1);
+ gdb_assert (closure != nullptr);
+
+ /* The value::bits_synthetic_pointer will return true if multiple
+ pieces are used to cover VALUE, so long as each piece is
+ DWARF_VALUE_IMPLICIT_POINTER. I guess maybe this is possible, but
+ we've not seen such a case in the wild yet. For now then we look
+ in CLOSURE for a single DWARF_VALUE_IMPLICIT_POINTER piece that
+ covers VALUE. */
+ LONGEST bit_offset = TARGET_CHAR_BIT * (value->embedded_offset ()
+ + value->offset ());
+ if (value->bitsize ())
+ bit_offset += value->bitpos ();
+ int bit_length = TARGET_CHAR_BIT * type->length ();
+
+ const dwarf_expr_piece *piece = nullptr;
+ for (size_t i = 0; i < closure->pieces.size (); i++)
+ {
+ const dwarf_expr_piece *p = &closure->pieces[i];
+ size_t this_size_bits = p->size;
+
+ if (bit_offset >= this_size_bits)
+ {
+ bit_offset -= this_size_bits;
+ continue;
+ }
+
+ /* value::bits_synthetic_pointer does allow for multiple
+ pieces to describe the location of a single synthetic
+ pointer, or for a synthetic pointer to not start at the
+ exact start of a piece, however, we don't currently
+ support this case. */
+ if (bit_offset != 0 || bit_length != this_size_bits)
+ error (_("unsupported value-piece configuration "
+ "bit_offset = %s, bit_length = %d, this_size_bits = %s"),
+ plongest (bit_offset), bit_length,
+ pulongest (this_size_bits));
+
+ piece = p;
+ break;
+ }
+
+ /* If value::bits_synthetic_pointer returned true then we should have
+ found a suitable piece. */
+ gdb_assert (piece != nullptr);
+ gdb_assert (piece->location == DWARF_VALUE_IMPLICIT_POINTER);
return indirect_synthetic_pointer
- (closure->pieces[0].v.ptr.die_sect_off,
- closure->pieces[0].v.ptr.offset,
+ (piece->v.ptr.die_sect_off,
+ piece->v.ptr.offset,
closure->per_cu, closure->per_objfile, frame, type);
}
else
diff --git a/gdb/testsuite/gdb.dwarf2/multi-piece-inherited-bitfield.c b/gdb/testsuite/gdb.dwarf2/multi-piece-inherited-bitfield.c
new file mode 100644
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/multi-piece-inherited-bitfield.c
@@ -0,0 +1,25 @@
+/* Copyright (C) 2026 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ 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/>. */
+
+int target_var = 42;
+
+int
+main (void)
+{
+ asm ("main_label: .globl main_label");
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.dwarf2/multi-piece-inherited-bitfield.exp b/gdb/testsuite/gdb.dwarf2/multi-piece-inherited-bitfield.exp
new file mode 100644
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/multi-piece-inherited-bitfield.exp
@@ -0,0 +1,151 @@
+# 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/>.
+
+# Setup a derived struct that uses multiple inheritance and is described
+# by a multi-piece DWARF location. The second base class contains a
+# bitfield member. At one time, printing such a struct would incorrectly
+# display the bitfield as <synthetic pointer>.
+#
+# The cause was that check_pieced_synthetic_pointer (dwarf2/expr.c)
+# computes the bit offset as 8 * value->offset() but does not add
+# 8 * value->embedded_offset(). When a base subobject is extracted via
+# primitive_field, it gets a non-zero embedded_offset but offset remains
+# zero. check_pieced_synthetic_pointer then checks piece 0 instead of
+# the correct piece, and if piece 0 happens to be an implicit pointer,
+# it incorrectly reports the bitfield as a synthetic pointer.
+
+require allow_cplus_tests
+
+load_lib dwarf.exp
+
+# This test can only be run on targets which support DWARF-2 and use gas.
+require dwarf2_support
+
+standard_testfile .c .S
+
+set asm_file [standard_output_file ${srcfile2}]
+
+# First compile the C file only, so we can query some type sizes.
+if {[prepare_for_testing "failed to prepare" ${testfile} ${srcfile}]} {
+ return
+}
+
+Dwarf::assemble ${asm_file} {
+ cu {} {
+ DW_TAG_compile_unit {
+ {DW_AT_language @DW_LANG_C_plus_plus}
+ } {
+ declare_labels char_label int_label first_label second_label \
+ derived_label target_label
+ set int_size [get_sizeof "int" -1]
+
+ char_label: DW_TAG_base_type {
+ {DW_AT_byte_size 1 DW_FORM_udata}
+ {DW_AT_encoding @DW_ATE_unsigned_char}
+ {DW_AT_name "char"}
+ }
+
+ int_label: DW_TAG_base_type {
+ {DW_AT_byte_size ${int_size} DW_FORM_udata}
+ {DW_AT_encoding @DW_ATE_signed}
+ {DW_AT_name "int"}
+ }
+
+ first_label: DW_TAG_structure_type {
+ {DW_AT_name "First"}
+ {DW_AT_byte_size 1 DW_FORM_udata}
+ } {
+ DW_TAG_member {
+ {DW_AT_name "a"}
+ {DW_AT_type :${char_label}}
+ {DW_AT_data_member_location 0 DW_FORM_udata}
+ }
+ }
+
+ second_label: DW_TAG_structure_type {
+ {DW_AT_name "Second"}
+ {DW_AT_byte_size 1 DW_FORM_udata}
+ } {
+ DW_TAG_member {
+ {DW_AT_name "bf"}
+ {DW_AT_type :${int_label}}
+ {DW_AT_data_member_location 0 DW_FORM_udata}
+ {DW_AT_bit_size 8 DW_FORM_udata}
+ }
+ }
+
+ derived_label: DW_TAG_structure_type {
+ {DW_AT_name "Derived"}
+ {DW_AT_byte_size 2 DW_FORM_udata}
+ } {
+ DW_TAG_inheritance {
+ {DW_AT_type :${first_label}}
+ {DW_AT_data_member_location 0 DW_FORM_udata}
+ }
+
+ DW_TAG_inheritance {
+ {DW_AT_type :${second_label}}
+ {DW_AT_data_member_location 1 DW_FORM_udata}
+ }
+ }
+
+ target_label: DW_TAG_variable {
+ {DW_AT_name "target_var"}
+ {DW_AT_type :${int_label}}
+ {DW_AT_external 1 DW_FORM_flag}
+ {DW_AT_location {
+ DW_OP_addr [gdb_target_symbol "target_var"]
+ } SPECIAL_expr}
+ }
+
+ DW_TAG_subprogram {
+ {MACRO_AT_func { "main" }}
+ {DW_AT_type :${int_label}}
+ {DW_AT_external 1 DW_FORM_flag}
+ } {
+ DW_TAG_variable {
+ {DW_AT_name "d"}
+ {DW_AT_type :${derived_label}}
+ {DW_AT_location {
+ DW_OP_GNU_implicit_pointer $target_label 0
+ DW_OP_piece 1
+ DW_OP_const1u 42
+ DW_OP_stack_value
+ DW_OP_piece 1
+ } SPECIAL_expr}
+ }
+ }
+ }
+ }
+}
+
+# Now compile both C source and generated DWARF assembly.
+if {[prepare_for_testing "failed to prepare" ${testfile} \
+ [list ${asm_file} ${srcfile}] {}]} {
+ return
+}
+
+# Start the inferior so that 'd' is in scope.
+if {![runto_main]} {
+ return
+}
+
+# With the bug in check_pieced_synthetic_pointer, the bitfield bf in the
+# Second base subobject is incorrectly reported as <synthetic pointer>.
+# This happens because check_pieced_synthetic_pointer does not account for
+# embedded_offset, so it checks piece 0 (the implicit pointer covering
+# First) instead of piece 1 (the stack value covering Second).
+gdb_test "print d" " = {<First> = {a = <synthetic pointer>}, <Second> = {bf = 42}, <No data fields>}" \
+ "bitfield in base subobject is not synthetic pointer"
diff --git a/gdb/testsuite/gdb.dwarf2/multi-piece-primitive-field.c b/gdb/testsuite/gdb.dwarf2/multi-piece-primitive-field.c
new file mode 100644
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/multi-piece-primitive-field.c
@@ -0,0 +1,25 @@
+/* Copyright (C) 2026 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ 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/>. */
+
+int target_var = 42;
+
+int
+main (void)
+{
+ asm ("main_label: .globl main_label");
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.dwarf2/multi-piece-primitive-field.exp b/gdb/testsuite/gdb.dwarf2/multi-piece-primitive-field.exp
new file mode 100644
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/multi-piece-primitive-field.exp
@@ -0,0 +1,190 @@
+# 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/>.
+
+# Setup a struct variable that is described by a multi-piece DWARF location.
+# One of the struct's fields is a C++ reference type implemented via
+# DW_OP_GNU_implicit_pointer. At one time, attempting to print such a field
+# would trigger an assertion in GDB.
+#
+# The cause was that primitive_field extracts the reference field and
+# set_component_location copies the parent's entire piece_closure (just
+# incrementing its refcount). Then coerce_pieced_ref would assert
+# closure->pieces.size() == 1, which failed because the closure still had
+# all the parent's pieces.
+#
+# This test confirms that this issue has now been fixed.
+
+require allow_cplus_tests
+
+load_lib dwarf.exp
+
+# This test can only be run on targets which support DWARF-2 and use gas.
+require dwarf2_support
+
+standard_testfile .c .S
+
+set asm_file [standard_output_file ${srcfile2}]
+
+# First compile the C file only, so we can query some type sizes.
+if {[prepare_for_testing "failed to prepare" ${testfile} ${srcfile}]} {
+ return
+}
+
+Dwarf::assemble ${asm_file} {
+ cu {} {
+ DW_TAG_compile_unit {
+ {DW_AT_language @DW_LANG_C_plus_plus}
+ } {
+ declare_labels int_label struct_label ref_type_label target_label \
+ first_label second_label derived_label
+ set int_size [get_sizeof "int" -1]
+ set addr_size [get_sizeof "void *" -1]
+ set struct_size [expr {$int_size + $addr_size}]
+
+ int_label: DW_TAG_base_type {
+ {DW_AT_byte_size ${int_size} DW_FORM_udata}
+ {DW_AT_encoding @DW_ATE_signed}
+ {DW_AT_name "int"}
+ }
+
+ ref_type_label: DW_TAG_reference_type {
+ {DW_AT_byte_size ${addr_size} DW_FORM_udata}
+ {DW_AT_type :${int_label}}
+ }
+
+ struct_label: DW_TAG_structure_type {
+ {DW_AT_name "S"}
+ {DW_AT_byte_size ${struct_size} DW_FORM_udata}
+ } {
+ DW_TAG_member {
+ {DW_AT_name "x"}
+ {DW_AT_type :${int_label}}
+ {DW_AT_data_member_location 0 DW_FORM_udata}
+ }
+
+ DW_TAG_member {
+ {DW_AT_name "ref"}
+ {DW_AT_type :${ref_type_label}}
+ {DW_AT_data_member_location ${int_size} DW_FORM_udata}
+ }
+ }
+
+ first_label: DW_TAG_structure_type {
+ {DW_AT_name "first"}
+ {DW_AT_byte_size ${int_size} DW_FORM_udata}
+ } {
+ DW_TAG_member {
+ {DW_AT_name "x"}
+ {DW_AT_type :${int_label}}
+ {DW_AT_data_member_location 0 DW_FORM_udata}
+ }
+ }
+
+ second_label: DW_TAG_structure_type {
+ {DW_AT_name "second"}
+ {DW_AT_byte_size ${addr_size} DW_FORM_udata}
+ } {
+ DW_TAG_member {
+ {DW_AT_name "ref"}
+ {DW_AT_type :${ref_type_label}}
+ {DW_AT_data_member_location 0 DW_FORM_udata}
+ }
+ }
+
+ derived_label: DW_TAG_structure_type {
+ {DW_AT_name "derived"}
+ {DW_AT_byte_size ${struct_size} DW_FORM_udata}
+ } {
+ DW_TAG_inheritance {
+ {DW_AT_type :${first_label}}
+ {DW_AT_data_member_location 0 DW_FORM_udata}
+ }
+
+ DW_TAG_inheritance {
+ {DW_AT_type :${second_label}}
+ {DW_AT_data_member_location ${int_size} DW_FORM_udata}
+ }
+ }
+
+ target_label: DW_TAG_variable {
+ {DW_AT_name "target_var"}
+ {DW_AT_type :${int_label}}
+ {DW_AT_external 1 DW_FORM_flag}
+ {DW_AT_location {
+ DW_OP_addr [gdb_target_symbol "target_var"]
+ } SPECIAL_expr}
+ }
+
+ DW_TAG_subprogram {
+ {MACRO_AT_func { "main" }}
+ {DW_AT_type :${int_label}}
+ {DW_AT_external 1 DW_FORM_flag}
+ } {
+ DW_TAG_variable {
+ {DW_AT_name "s"}
+ {DW_AT_type :${struct_label}}
+ {DW_AT_location {
+ DW_OP_const4u 123
+ DW_OP_stack_value
+ DW_OP_piece $int_size
+ DW_OP_GNU_implicit_pointer $target_label 0
+ DW_OP_piece $addr_size
+ } SPECIAL_expr}
+ }
+
+ DW_TAG_variable {
+ {DW_AT_name "d"}
+ {DW_AT_type :${derived_label}}
+ {DW_AT_location {
+ DW_OP_const4u 456
+ DW_OP_stack_value
+ DW_OP_piece $int_size
+ DW_OP_GNU_implicit_pointer $target_label 0
+ DW_OP_piece $addr_size
+ } SPECIAL_expr}
+ }
+ }
+ }
+ }
+}
+
+# Now compile both C source and generated DWARF assembly.
+if {[prepare_for_testing "failed to prepare" ${testfile} \
+ [list ${asm_file} ${srcfile}] {}]} {
+ return
+}
+
+# Start the inferior so that 's' is in scope.
+if {![runto_main]} {
+ return
+}
+
+# Printing `s` used to crash with an assertion failure in coerce_pieced_ref
+# because the ref field inherited the parent struct's multi-piece closure.
+gdb_test "print s" " = {x = 123, ref = @$hex}" \
+ "print multi-piece struct with implicit pointer reference field"
+
+gdb_test "print s.ref" " = \\(int &\\) @$hex: 42" \
+ "print ref member of multi-piece struct"
+
+# Same test but with inheritance. The reference field is in the
+# second base class, so it reaches coerce_pieced_ref through a
+# different path to the above case.
+gdb_test "print d" \
+ " = {<first> = {x = 456}, <second> = {ref = @$hex}, <No data fields>}" \
+ "print inherited multi-piece struct with implicit pointer ref"
+
+gdb_test "print d.ref" " = \\(int &\\) @$hex: 42" \
+ "print ref member of inherited multi-piece struct"
diff --git a/gdb/valops.c b/gdb/valops.c
--- a/gdb/valops.c
+++ b/gdb/valops.c
@@ -1545,8 +1545,7 @@ value_addr (struct value *arg1)
if (TYPE_IS_REFERENCE (type))
{
- if (arg1->bits_synthetic_pointer (arg1->embedded_offset (),
- TARGET_CHAR_BIT * type->length ()))
+ if (arg1->bits_synthetic_pointer (0, TARGET_CHAR_BIT * type->length ()))
arg1 = coerce_ref (arg1);
else
{
diff --git a/gdb/valprint.c b/gdb/valprint.c
--- a/gdb/valprint.c
+++ b/gdb/valprint.c
@@ -92,7 +92,6 @@ static void set_output_radix_1 (int, unsigned);
static void val_print_type_code_flags (struct type *type,
struct value *original_value,
- int embedded_offset,
struct ui_file *stream);
/* Start print_max at this value. */
@@ -310,11 +309,9 @@ show_symbol_print (struct ui_file *file, int from_tty,
-/* A helper function for val_print. When printing in "summary" mode,
- we want to print scalar arguments, but not aggregate arguments.
- This function distinguishes between the two. */
+/* See valprint.h. */
-int
+bool
val_print_scalar_type_p (struct type *type)
{
type = check_typedef (type);
@@ -330,9 +327,9 @@ val_print_scalar_type_p (struct type *type)
case TYPE_CODE_UNION:
case TYPE_CODE_SET:
case TYPE_CODE_STRING:
- return 0;
+ return false;
default:
- return 1;
+ return true;
}
}
@@ -350,10 +347,10 @@ val_print_scalar_or_string_type_p (struct type *type,
/* See valprint.h. */
-int
+bool
valprint_check_validity (struct ui_file *stream,
struct type *type,
- LONGEST embedded_offset,
+ LONGEST field_byte_offset,
const struct value *val)
{
type = check_typedef (type);
@@ -361,31 +358,31 @@ valprint_check_validity (struct ui_file *stream,
if (type_not_associated (type))
{
val_print_not_associated (stream);
- return 0;
+ return false;
}
if (type_not_allocated (type))
{
val_print_not_allocated (stream);
- return 0;
+ return false;
}
if (type->code () != TYPE_CODE_UNION
&& type->code () != TYPE_CODE_STRUCT
&& type->code () != TYPE_CODE_ARRAY)
{
- if (val->bits_any_optimized_out (TARGET_CHAR_BIT * embedded_offset,
+ if (val->bits_any_optimized_out (TARGET_CHAR_BIT * field_byte_offset,
TARGET_CHAR_BIT * type->length ()))
{
val_print_optimized_out (val, stream);
- return 0;
+ return false;
}
- if (val->bits_synthetic_pointer (TARGET_CHAR_BIT * embedded_offset,
+ if (val->bits_synthetic_pointer (TARGET_CHAR_BIT * field_byte_offset,
TARGET_CHAR_BIT * type->length ()))
{
- const int is_ref = type->code () == TYPE_CODE_REF;
- int ref_is_addressable = 0;
+ const bool is_ref = type->code () == TYPE_CODE_REF;
+ bool ref_is_addressable = false;
if (is_ref)
{
@@ -403,14 +400,14 @@ valprint_check_validity (struct ui_file *stream,
return is_ref;
}
- if (!val->bytes_available (embedded_offset, type->length ()))
+ if (!val->bytes_available (field_byte_offset, type->length ()))
{
val_print_unavailable (stream);
- return 0;
+ return false;
}
}
- return 1;
+ return true;
}
void
@@ -595,14 +592,13 @@ generic_value_print_ptr (struct value *val, struct ui_file *stream,
static void
print_ref_address (struct type *type, const gdb_byte *address_buffer,
- int embedded_offset, struct ui_file *stream)
+ struct ui_file *stream)
{
struct gdbarch *gdbarch = type->arch ();
if (address_buffer != NULL)
{
- CORE_ADDR address
- = extract_typed_address (address_buffer + embedded_offset, type);
+ CORE_ADDR address = extract_typed_address (address_buffer, type);
gdb_printf (stream, "@");
gdb_puts (paddress (gdbarch, address), stream);
@@ -631,32 +627,27 @@ get_value_addr_contents (struct value *deref_val)
static void
generic_val_print_ref (struct type *type,
- int embedded_offset, struct ui_file *stream, int recurse,
+ struct ui_file *stream, int recurse,
struct value *original_value,
const struct value_print_options *options)
{
struct type *elttype = check_typedef (type->target_type ());
struct value *deref_val = NULL;
const bool value_is_synthetic
- = original_value->bits_synthetic_pointer (TARGET_CHAR_BIT * embedded_offset,
- TARGET_CHAR_BIT * type->length ());
- const int must_coerce_ref = ((options->addressprint && value_is_synthetic)
- || options->deref_ref);
- const int type_is_defined = elttype->code () != TYPE_CODE_UNDEF;
+ = original_value->bits_synthetic_pointer (0, (TARGET_CHAR_BIT
+ * type->length ()));
+ const bool must_coerce_ref = ((options->addressprint && value_is_synthetic)
+ || options->deref_ref);
+ const bool type_is_defined = elttype->code () != TYPE_CODE_UNDEF;
const gdb_byte *valaddr = original_value->contents_for_printing ().data ();
if (must_coerce_ref && type_is_defined)
{
deref_val = coerce_ref_if_computed (original_value);
- if (deref_val != NULL)
- {
- /* More complicated computed references are not supported. */
- gdb_assert (embedded_offset == 0);
- }
- else
+ if (deref_val == nullptr)
deref_val = value_at (type->target_type (),
- unpack_pointer (type, valaddr + embedded_offset));
+ unpack_pointer (type, valaddr));
}
/* Else, original_value isn't a synthetic reference or we don't have to print
the reference's contents.
@@ -676,7 +667,7 @@ generic_val_print_ref (struct type *type,
? get_value_addr_contents (deref_val)
: valaddr);
- print_ref_address (type, address, embedded_offset, stream);
+ print_ref_address (type, address, stream);
if (options->deref_ref)
gdb_puts (": ", stream);
@@ -718,7 +709,7 @@ generic_val_print_enum_1 (struct type *type, LONGEST val,
}
else if (type->is_flag_enum ())
{
- int first = 1;
+ bool first = true;
/* We have a "flag" enum, so we try to decompose it into pieces as
appropriate. The enum may have multiple enumerators representing
@@ -738,7 +729,7 @@ generic_val_print_enum_1 (struct type *type, LONGEST val,
if (first)
{
gdb_puts ("(", stream);
- first = 0;
+ first = false;
}
else
gdb_puts (" | ", stream);
@@ -780,20 +771,15 @@ generic_val_print_enum_1 (struct type *type, LONGEST val,
/* generic_val_print helper for TYPE_CODE_ENUM. */
static void
-generic_val_print_enum (struct type *type,
- int embedded_offset, struct ui_file *stream,
+generic_val_print_enum (struct type *type, struct ui_file *stream,
struct value *original_value,
const struct value_print_options *options)
{
- LONGEST val;
- struct gdbarch *gdbarch = type->arch ();
- int unit_size = gdbarch_addressable_memory_unit_size (gdbarch);
-
gdb_assert (!options->format);
const gdb_byte *valaddr = original_value->contents_for_printing ().data ();
- val = unpack_long (type, valaddr + embedded_offset * unit_size);
+ LONGEST val = unpack_long (type, valaddr);
generic_val_print_enum_1 (type, val, stream);
}
@@ -801,8 +787,7 @@ generic_val_print_enum (struct type *type,
/* generic_val_print helper for TYPE_CODE_FUNC and TYPE_CODE_METHOD. */
static void
-generic_val_print_func (struct type *type,
- int embedded_offset, CORE_ADDR address,
+generic_val_print_func (struct type *type, CORE_ADDR address,
struct ui_file *stream,
struct value *original_value,
const struct value_print_options *options)
@@ -1015,7 +1000,7 @@ generic_value_print (struct value *val, struct ui_file *stream, int recurse,
case TYPE_CODE_REF:
case TYPE_CODE_RVALUE_REF:
- generic_val_print_ref (type, 0, stream, recurse,
+ generic_val_print_ref (type, stream, recurse,
val, options);
break;
@@ -1023,14 +1008,14 @@ generic_value_print (struct value *val, struct ui_file *stream, int recurse,
if (options->format)
value_print_scalar_formatted (val, options, 0, stream);
else
- generic_val_print_enum (type, 0, stream, val, options);
+ generic_val_print_enum (type, stream, val, options);
break;
case TYPE_CODE_FLAGS:
if (options->format)
value_print_scalar_formatted (val, options, 0, stream);
else
- val_print_type_code_flags (type, val, 0, stream);
+ val_print_type_code_flags (type, val, stream);
break;
case TYPE_CODE_FUNC:
@@ -1038,7 +1023,7 @@ generic_value_print (struct value *val, struct ui_file *stream, int recurse,
if (options->format)
value_print_scalar_formatted (val, options, 0, stream);
else
- generic_val_print_func (type, 0, val->address (), stream,
+ generic_val_print_func (type, val->address (), stream,
val, options);
break;
@@ -1190,11 +1175,11 @@ val_print_check_max_depth (struct ui_file *stream, int recurse,
return false;
}
-/* Check whether the value VAL is printable. Return 1 if it is;
- return 0 and print an appropriate error message to STREAM according to
- OPTIONS if it is not. */
+/* Check whether the value VAL is printable. Return true if it is;
+ return false and print an appropriate error message to STREAM
+ according to OPTIONS if it is not. */
-static int
+static bool
value_check_printable (struct value *val, struct ui_file *stream,
const struct value_print_options *options)
{
@@ -1202,7 +1187,7 @@ value_check_printable (struct value *val, struct ui_file *stream,
{
fprintf_styled (stream, metadata_style.style (),
_("<address of value unknown>"));
- return 0;
+ return false;
}
if (val->entirely_optimized_out ())
@@ -1211,7 +1196,7 @@ value_check_printable (struct value *val, struct ui_file *stream,
gdb_printf (stream, "...");
else
val_print_optimized_out (val, stream);
- return 0;
+ return false;
}
if (val->entirely_unavailable ())
@@ -1220,7 +1205,7 @@ value_check_printable (struct value *val, struct ui_file *stream,
gdb_printf (stream, "...");
else
val_print_unavailable (stream);
- return 0;
+ return false;
}
if (val->type ()->code () == TYPE_CODE_INTERNAL_FUNCTION)
@@ -1228,16 +1213,16 @@ value_check_printable (struct value *val, struct ui_file *stream,
fprintf_styled (stream, metadata_style.style (),
_("<internal function %s>"),
value_internal_function_name (val));
- return 0;
+ return false;
}
if (type_not_allocated (val->type ()))
{
val_print_not_allocated (stream);
- return 0;
+ return false;
}
- return 1;
+ return true;
}
/* See valprint.h. */
@@ -1292,10 +1277,9 @@ debug_val (struct value *val)
static void
val_print_type_code_flags (struct type *type, struct value *original_value,
- int embedded_offset, struct ui_file *stream)
+ struct ui_file *stream)
{
- const gdb_byte *valaddr = (original_value->contents_for_printing ().data ()
- + embedded_offset);
+ const gdb_byte *valaddr = (original_value->contents_for_printing ().data ());
ULONGEST val = unpack_long (type, valaddr);
int field, nfields = type->num_fields ();
struct gdbarch *gdbarch = type->arch ();
@@ -3188,7 +3172,7 @@ test_print_flags (gdbarch *arch)
store_unsigned_integer (contents, 4, gdbarch_byte_order (arch), 0xaa);
string_file out;
- val_print_type_code_flags (flags_type, val, 0, &out);
+ val_print_type_code_flags (flags_type, val, &out);
SELF_CHECK (out.string () == "[ A=2 B=1 C=5 ]");
}
diff --git a/gdb/valprint.h b/gdb/valprint.h
--- a/gdb/valprint.h
+++ b/gdb/valprint.h
@@ -197,17 +197,15 @@ extern void print_function_pointer_address (const struct value_print_options *op
/* Helper function to check the validity of some bits of a value.
- If TYPE represents some aggregate type (e.g., a structure), return 1.
+ If TYPE represents some aggregate type (e.g., a structure), return true.
- Otherwise, any of the bytes starting at OFFSET and extending for
- TYPE->length () bytes are invalid, print a message to STREAM and
- return 0. The checking is done using FUNCS.
+ For non-aggregate TYPEs, if any of the bytes starting at FIELD_BYTE_OFFSET
+ and extending for TYPE->length () bytes are invalid, print a message to
+ STREAM and return false. Otherwise, return true. */
- Otherwise, return 1. */
-
-extern int valprint_check_validity (struct ui_file *stream, struct type *type,
- LONGEST embedded_offset,
- const struct value *val);
+extern bool valprint_check_validity (struct ui_file *stream, struct type *type,
+ LONGEST field_byte_offset,
+ const struct value *val);
extern void val_print_optimized_out (const struct value *val,
struct ui_file *stream);
@@ -272,7 +270,11 @@ extern void generic_printstr (struct ui_file *stream, struct type *type,
extern void output_command (const char *args, int from_tty);
-extern int val_print_scalar_type_p (struct type *type);
+/* When printing in "summary" mode we want to print scalar arguments
+ but not aggregate arguments. Return true if TYPE is a scalar type,
+ false if it is an aggregate. */
+
+extern bool val_print_scalar_type_p (struct type *type);
struct format_data
{