diff --git a/gdb/amd64-windows-tdep.c b/gdb/amd64-windows-tdep.c index ee641868a94..376fc5bcdc7 100644 --- a/gdb/amd64-windows-tdep.c +++ b/gdb/amd64-windows-tdep.c @@ -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 ()) diff --git a/gdb/testsuite/gdb.cp/non-trivial-retval.cc b/gdb/testsuite/gdb.cp/non-trivial-retval.cc index e3188ea4534..c0f750f3de9 100644 --- a/gdb/testsuite/gdb.cp/non-trivial-retval.cc +++ b/gdb/testsuite/gdb.cp/non-trivial-retval.cc @@ -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 @@ -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; } diff --git a/gdb/testsuite/gdb.cp/non-trivial-retval.exp b/gdb/testsuite/gdb.cp/non-trivial-retval.exp index f5c32dca079..21df1711895 100644 --- a/gdb/testsuite/gdb.cp/non-trivial-retval.exp +++ b/gdb/testsuite/gdb.cp/non-trivial-retval.exp @@ -59,6 +59,8 @@ 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" @@ -66,6 +68,7 @@ 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}" \ @@ -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"