clang/llvm has a feature to take identical sequences of instructions
from multiple functions, put them in a separate utility function, and
call that utility function, reducing overall codesize. Primarly useful
in memory constrained environments.
The CFI unwind instructions for these OUTLINED_FUNCTIONs in
eh_frame/debug_frame is entirely incorrect; clang does not emit any
directives for them.
Furthermore, the callers sometimes call the OUTLINED_FUNCTION in a
non-ABI call manner. For instance, if the last few instructions of a
function (including the epilogue) are put in an OUTLINED_FUNCTION, a
function will simply tail-call/jump to the outlined function.
If a function prologue is separated into an OUTLINED_FUNCTION, a non ABI
call to preserve the return-address-register may be used, e.g. on RISCV
a `jal t0, OUTLINED_FUNCTION_nn` may be used, putting the return address
in the t0 temporary register; lldb's instruction analysis while in the
OUTLINED_FUNCTION will not understand that this non-ABI calling method
was used.
This PR overrides any normal unwind information when we find a stack
frame in an OUTLINED_FUNCTION and replaces it with the architectural
default unwind plan, hopefully this is a target where stack frames start
with a saved fp+pc up the stack, and there is an arch default unwind
plan that can get past this point while trusting no sources of unwind
information. We may end up skipping a stack frame in the process (the
caller of the OUTLINED_FUNCTION), but it's the best we can do.
There is a further problem not handled here, where the prologue of a
function is outlined. None of the unwind information for the original
function will be correct - not CFI directives in eh_frame/debug_frame no
lldb's instruction emulation. We need to also treat this case as "use
the arch default unwind plan, it's our only hope" but I need to come up
with a way to detect this scenario still. We don't have a simple
function name check here; we'll need to detect the call to the
OUTLINED_FUNCTION at the start of the function and handle it somehow.
This test case includes a main.c and outlined-function.s which simulates
what the three different OUTLINED_FUNCTION combinations look like in
AArch64 assembly (prologue, mid-function, epilogue), but I've never
succeeded at getting assembly to compile cross-platform, so I also
created a corefile and JSON binary (arm64-outlined-epilogue-core.yaml,
binary.json) and run yaml2macho-core to create a Mach-O corefile to run
the test case against. This corefile is simply me capturing some memory
and register contents while debugging the main.c and outlined-function.s
at the "epilogue OUTLINED" and we hit the OUTLINED_FUNCTION on the stack
walk.
rdar://179530769
---------
Co-authored-by: Jonas Devlieghere <jonas@devlieghere.com>