Description
virtual unwinders (*), for example based on gimli::{DebugFrame,UnwindSection}
, are not able to unwind through external assembly subroutines like __nop
because these subroutines lack CFI (Call Frame Information), which -- when present -- gets encoded in the .debug_frame
(**) section of the ELF.
$ # no `.debug_frame` in the output
$ arm-none-eabi-size -Ax bin/thumbv6m-none-eabi.a
.text 0x0 0x0
.data 0x0 0x0
.bss 0x0 0x0
(..)
.debug_line 0x160 0x0
.debug_info 0x22 0x0
.debug_abbrev 0x12 0x0
.debug_aranges 0xb0 0x0
.debug_str 0x2d 0x0
.debug_ranges 0xa8 0x0
.ARM.attributes 0x1c 0x0
(..)
For subroutines that do not touch the stack pointer or link register adding CFI with no register rules is sufficient to make virtual unwinding work. Adding that CFI looks like this:
.section .text.__nop
+ .cfi_sections .debug_frame
.global __nop
.thumb_func
+ .cfi_startproc
__nop:
bx lr
+ .cfi_endproc
.size __nop, . - __nop
And yeah bx lr
does not count as "touching LR" because it does not modify LR.
What needs to be done:
- the easy part: add this "no register rules" CFI prologue and epilogue to all assembly subroutines that do *not *modify the stack pointer (SP) or the linker register (LR) and then re-run
assemble.sh
. Some instructions that do modify SP / LR arepush
,pop
,stmia
,ldmia
,mov
/msr
where SP/LR is the destination (first argument) -- do not modify subroutines that contain those instructions (see "hard part" below)
To test this part run the following command on the archives (bin/*.a*
):
$ arm-none-eabi-readelf --debug-dump=frames thumbv7em-none-eabi.a
File: thumbv7em-none-eabi.a(asm.o)
Contents of the .debug_frame section:
00000000 0000000c ffffffff CIE
Version: 1
Augmentation: ""
Code alignment factor: 2
Data alignment factor: -4
Return address column: 14
DW_CFA_def_cfa: r13 ofs 0
00000010 0000000c 00000000 FDE cie=00000000 pc=00000000..00000004
00000020 0000000c 00000000 FDE cie=00000000 pc=00000000..00000004
00000030 0000000c 00000000 FDE cie=00000000 pc=00000000..00000004
00000040 0000000c 00000000 FDE cie=00000000 pc=00000000..00000004
00000050 0000000c 00000000 FDE cie=00000000 pc=00000000..00000004
You should see one "FDE" for each subroutine that now has CFI.
- the hard part: figure out how to write register rules for instructions that do modify SP / LR. Then add proper CFI to subroutines with weird ABIs like
cortex_m_rt::HardFaultTrampoline
(*) GDB doesn't seem to require this info (.debug_frame
) to print stack backtraces but GDB also contains a full blown disassembler so I guess it can figure out the missing info on its own
(**) AFAIK, for proper stack unwinding, where stack frames are popped and destructors are called as the stack is unwound, one needs .eh_frame
instead of .debug_frame
. But we are dealing with panic=abort
targets (which also lack the other unwinding ingredient: "landing pads" big question mark) here so we don't need the "full" version (.eh_frame
).
P.S. cortex-m
is not the only crate that's missing this info. cortex-m-rt
, cortex-m-semihosting
, etc. also need these changes.