|
DWARF Standard
|
221105.1 |
Kyle Huey |
Add a mechanism for specifying subprogram return value locations |
Enhancement |
Open |
Caroline Tice |
Section 3.3.2, pg 78
DWARF allows a DW_TAG_subprogram/DW_TAG_inlined_subroutine (the latter
via DW_AT_abstract_origin) to note their return type with a DW_AT_type,
as detailed in Section 3.3.2. It does not, however, provide any
information about where the return type is in the program at the
subprogram boundary. Debuggers that support printing return values
at function exit (e.g. gdb) currently infer this from the platform ABI
(e.g. `amd64_return_value` in gdb/amd64-tdep.c). There is no requirement
that arbitrary functions actually follow the platform's standard ABI
though, and when this inference fails, it fails silently, presenting
the wrong value to users.
There are, in my opinion, two interesting cases:
1) Cases where the function follows some sort of ABI, just not the
platform's standard ABI.
The Rust compiler, for instance, doesn't always follow the standard
SYSV AMD64 ABI when compiling for that platform.
See https://github.com/rust-lang/rust/issues/85641 for one real world
example that silently breaks gdb.
In theory this case could be covered by adding a DW_CC_rust value for
the DW_AT_calling_convention attribute to the spec, and downstream
tools could be taught what that means and how to process it accordingly.
I think this is more complicated than having the compiler directly emit
the location information, and it wouldn't cover the second case.
2) Inline functions.
Inline functions don't necessarily follow any ABI. Depending on the
optimizations performed after inlining, they may not even have proper
bounds to determine what constitutes a single invocation of the function
(imagine an inline function whose instructions have been intermingled
by the optimizer with the instructions of its containing function).
But there are common cases where a inlined function does have a
meaningful and easy-to-determine return value.
Consider the simple C++ program
#include <iostream>
using namespace std;
inline bool greater_than(int x, int y) {
return x > y;
}
int main(int argc) {
if (greater_than(argc, 4)) {
cout << "I have more than 3 arguments\n";
} else {
cout << "I have 3 or fewer arguments\n";
}
return 0;
}
An optimizing compiler (e.g. gcc 12.2 with -O2) can convert the
greater_than function into a single comparison instruction inlined
into main. The corresponding bit in the flags register is clearly
not the ABI-specified location for the return value.
I propose to add language to the spec allowing DW_AT_location to be
present on DW_TAG_subprogram/DW_TAG_inlined_subroutine. When present,
it would contain a location expression specifying the location of the
function's return value. (In the two examples above, on x86-64, the
expressions "DW_OP_reg0 DW_OP_piece 4 DW_OP_reg1 DW_OP_piece 4" and
"DW_OP_regx 49 DW_OP_dup DW_OP_const1u 64 DW_OP_and DW_OP_lit6
DW_OP_shr DW_OP_lit0 DW_OP_eq DW_OP_swap DW_OP_dup DW_OP_const1u 128
DW_OP_and DW_OP_lit7 DW_OP_shr DW_OP_swap DW_OP_lit1 DW_OP_and
DW_OP_eq DW_OP_eq" respectively are capable of encoding the return
values). If it's not present, debuggers and other tools can fall back
to their current behavior.