Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions gdb/amd64-windows-tdep.c
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,77 @@ amd64_windows_return_value (struct gdbarch *gdbarch, struct value *function,
int len = type->length ();
int regnum = -1;

/* On Windows, a struct or union must be returned via a hidden sret
pointer (passed in RCX, returned in RAX) rather than in registers
if any of the following hold:
(a) has a user-defined (non-trivial) copy constructor
(b) has a user-defined (non-trivial) destructor
(c) has any private or protected non-static data members

Conditions (a) and (b) are enforced by both GCC/MinGW and the MSVC
ABI. Condition (c) is an additional MSVC-ABI requirement that
GCC/MinGW does not enforce.

See https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention. */
if (type->code () == TYPE_CODE_STRUCT
|| type->code () == TYPE_CODE_UNION)
{
struct language_pass_by_ref_info info = language_pass_by_reference (type);

/* Conditions (a) and (b): non-trivial copy ctor or destructor. */
bool needs_sret = (!info.trivially_copy_constructible
|| !info.trivially_destructible);

/* Condition (c): private or protected non-static data members.
Only the MSVC ABI requires sret in this case; GCC/MinGW ignores
member access specifiers for return-value classification. Detect
MSVC-compiled code by the '?' prefix on the mangled symbol name
(MSVC mangles as '?foo@@...'; GNU/Itanium mangles as '_Z...').

Note: once upstream's GDB_OSABI_WINDOWS_MSVC (Pedro Alves,
gdb-patches 2026-07) is available AND the sniffer can reliably
distinguish the two Windows ABIs from the binary, this heuristic
could be replaced by gdbarch_osabi (gdbarch) == GDB_OSABI_WINDOWS_MSVC.
Until then the mangled-name check is the only per-binary detector.

See https://sourceware.org/pipermail/gdb-patches/2026-July/228640.html. */
if (!needs_sret && function != nullptr)
{
bound_minimal_symbol func_msym
= lookup_minimal_symbol_by_pc (function->address ());
if (func_msym.minsym != nullptr)
{
const char *lname = func_msym.minsym->linkage_name ();
if (lname != nullptr && lname[0] == '?' && HAVE_CPLUS_STRUCT (type))
{
int n_bases = TYPE_N_BASECLASSES (type);
for (int i = n_bases; i < type->num_fields (); i++)
{
if (type->field (i).is_static ())
continue;
if (type->field (i).is_private ()
|| type->field (i).is_protected ())
{
needs_sret = true;
break;
}
}
}
}
}

if (needs_sret)
{
if (read_value != nullptr)
{
ULONGEST addr;
regcache_raw_read_unsigned (regcache, AMD64_RAX_REGNUM, &addr);
*read_value = value_at_non_lval (type, addr);
}
return RETURN_VALUE_ABI_RETURNS_ADDRESS;
}
}

/* See if our value is returned through a register. If it is, then
store the associated register number in REGNUM. */
switch (type->code ())
Expand Down
22 changes: 22 additions & 0 deletions gdb/testsuite/gdb.cp/non-trivial-retval.cc
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,27 @@ f5 (int i1, int i2)
return f;
}

/* G has only private nonstatic data members. The C++ standard considers
it trivially copyable (no user-defined copy constructor or destructor),
but the Windows x64 ABI requires all nonstatic data members to be public
for a type to qualify for register return. Types with any private or
protected nonstatic data members must be returned via a hidden pointer
(sret) instead. This tests that GDB correctly handles such a return. */
class G
{
int g;
public:
G () : g (0) {}
G (int val) : g (val) {}
int get () const { return g; }
};

G
f6 (int i1, int i2)
{
return G (i1 + i2);
}

/* We place a breakpoint on the call to this function. */

void
Expand All @@ -183,6 +204,7 @@ main (void)
C c = f3 (i1, i2);
E e = f4 (i1, i2);
F f = f5 (i1, i2);
G g = f6 (i1, i2);

return 0;
}
7 changes: 7 additions & 0 deletions gdb/testsuite/gdb.cp/non-trivial-retval.exp
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,16 @@ gdb_test "p f4 ($i1, $i2)" ".* = {.* e = 123}" \
"p f4 (i1, i2)"
gdb_test "p f5 ($i1, $i2)" ".* = {f = 123}" \
"p f5 (i1, i2)"
gdb_test "p f6 ($i1, $i2)" ".* = {g = 123}" \
"p f6 (i1, i2)"

gdb_breakpoint "f1"
gdb_breakpoint "f2"
gdb_breakpoint "f22"
gdb_breakpoint "f3"
gdb_breakpoint "f4"
gdb_breakpoint "f5"
gdb_breakpoint "f6"

gdb_continue_to_breakpoint "Break in f1"
gdb_test "finish" " = {a = 123}" \
Expand All @@ -90,3 +93,7 @@ gdb_test "finish" " = {.* e = 123}" \
gdb_continue_to_breakpoint "Break in f5"
gdb_test "finish" " = {f = 123}" \
"finish from f5"

gdb_continue_to_breakpoint "Break in f6"
gdb_test "finish" " = {g = 123}" \
"finish from f6"
Loading