Proposal
Problem statement
Currently in rust, there's limited ways to get information about the caller:
#[track_caller] can give you a Location, but this is incompatible with FFI,
- Using a crate like
backtrace can generate a full stack trace, but this is expensive when you
- With
-Cforce-frame-pointers on, you can read the return address using inline assembly, but on some architectures, this depends on llvm et. al preserving the frame address register (and on others, the link/return register), so this is inherently fragile and relying on non-guaranteed behaviour
Obtaining the return address can be a useful step to a light-to-midweight caller information lookup.
Motivating examples or use cases
When implementing constraint erroring (as part of libc Annex K), I wanted the ability to get the caller address so that the constraint handler can print a diagnostic message that provides that information to the user for debugging purposes (informing the user, hopefully an application developer, of the precise location of the constriant error). The address would be printed directly and also looked up using the equivalent of a function like dladdr(3).
Solution sketch
// in core::arch
#[inline(always)]
pub fn return_address() -> *mut ();
The core::arch::return_address() function returns a pointer with an address that corresponds to the caller of the function that called the return_address() function, or a null pointer if it cannot be determined. The pointer has no provenance, as if created by core::ptr::without_provenance. It cannot be used to read memory (other than ZSTs)
The value returned by the function depends highly on the architecture and compiler (including any options set). In particular, it is allowed to be wrong (particularily if inlining is involved), or even contain a nonsense value. The result of this macro must not be relied upon for soundness or correctness, only for debugging purposes.
Formally, this function returns a pointer with a non-deterministic address and no provenance.
This is equivalent to the gcc __builtin_return_address(0) intrinsic.
Alternatives
The function can be provided as a macro or a directly exposed intrinsic. I do not recommend the latter, as core::mem::transmute is the only other actual intrinsic that is exported in a (properly) public interface.
A macro could make better sense to demonstrate the "necessity of inlining".
The function could instead have type usize to reflect that it returns an address, not necessarily a dereferenceable pointer. However, given that the llvm and gcc implmenetations of the underlying intrinsic return pointers, it's likely better to emit raw pointers. This is also consistent with function signatures like dladdr (which, alone with addr2line would be the primary routine).
Finally, this can be replaced with a standard library, or userspace backtrace library. However, there are two notable kinds of backtracing, which have downsides, and if you only need the immediate caller, render a full backtrace unsuitable:
- Frame walking using the frame address to walk stack frames. This requires the caller of the backtrace , and every level back from that that needs to be captured be compiled with frame pointers on, which is not the default in optimized builds. This is faster than unwinding but is still slow.
- Unwind-based Backtracing, which uses unwind tables and debug info to perform a more elaborate search. This does not have the frame-pointer requirement above, but it much more complex and thus slower. It also requires hauling arround a bunch of excess code for reading ELF Data Structures (sometimes duplicated from the actual unwinder).
In either case, both are slower (to fully compute) than obtaining the immediate return address of the currrent function (which can also be done correctly regardless of compilation settings, as the compiler knows where the return address lives.
Writing this precise routine in user rust is not possible: It would require both specific knowledge of the platform ABI and architecture, as well as guarantees that do not hold with respect to inline assembly that would actually read the return address.
Links and related work
What happens now?
This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.
Possible responses
The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):
- We think this problem seems worth solving, and the standard library might be the right place to solve it.
- We think that this probably doesn't belong in the standard library.
Second, if there's a concrete solution:
- We think this specific solution looks roughly right, approved, you or someone else should implement this. (Further review will still happen on the subsequent implementation PR.)
- We're not sure this is the right solution, and the alternatives or other materials don't give us enough information to be sure about that. Here are some questions we have that aren't answered, or rough ideas about alternatives we'd want to see discussed.
Proposal
Problem statement
Currently in rust, there's limited ways to get information about the caller:
#[track_caller]can give you aLocation, but this is incompatible with FFI,backtracecan generate a full stack trace, but this is expensive when you-Cforce-frame-pointerson, you can read the return address using inline assembly, but on some architectures, this depends on llvm et. al preserving the frame address register (and on others, the link/return register), so this is inherently fragile and relying on non-guaranteed behaviourObtaining the return address can be a useful step to a light-to-midweight caller information lookup.
Motivating examples or use cases
When implementing constraint erroring (as part of libc Annex K), I wanted the ability to get the caller address so that the constraint handler can print a diagnostic message that provides that information to the user for debugging purposes (informing the user, hopefully an application developer, of the precise location of the constriant error). The address would be printed directly and also looked up using the equivalent of a function like
dladdr(3).Solution sketch
Alternatives
The function can be provided as a macro or a directly exposed intrinsic. I do not recommend the latter, as
core::mem::transmuteis the only other actual intrinsic that is exported in a (properly) public interface.A macro could make better sense to demonstrate the "necessity of inlining".
The function could instead have type
usizeto reflect that it returns an address, not necessarily a dereferenceable pointer. However, given that the llvm and gcc implmenetations of the underlying intrinsic return pointers, it's likely better to emit raw pointers. This is also consistent with function signatures likedladdr(which, alone withaddr2linewould be the primary routine).Finally, this can be replaced with a standard library, or userspace backtrace library. However, there are two notable kinds of backtracing, which have downsides, and if you only need the immediate caller, render a full backtrace unsuitable:
In either case, both are slower (to fully compute) than obtaining the immediate return address of the currrent function (which can also be done correctly regardless of compilation settings, as the compiler knows where the return address lives.
Writing this precise routine in user rust is not possible: It would require both specific knowledge of the platform ABI and architecture, as well as guarantees that do not hold with respect to inline assembly that would actually read the return address.
Links and related work
What happens now?
This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.
Possible responses
The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):
Second, if there's a concrete solution: