cortex-m icon indicating copy to clipboard operation
cortex-m copied to clipboard

add virtual unwind info (.debug_frame / CFI) to external assembly

Open japaric opened this issue 5 years ago • 5 comments

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 are push, 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.

japaric avatar May 05 '20 21:05 japaric

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

Having interacted with that code I would strongly suggest you not rely on it but rather use the CFI directives. There's nothing more unpleasant than debugging your debugger when it becomes confused by an especially strange stack frame and either gets lost or crashes. (Though I guess the problem might be less acute on ARMv7 than on OR1K.)

whitequark avatar May 05 '20 21:05 whitequark

Other crates with external assembly:

  • [ ] https://github.com/rust-embedded/msp430-rt/blob/master/asm.s
  • [ ] https://github.com/rust-embedded/cortex-m-rt/blob/master/asm.s
  • [ ] https://github.com/rust-embedded/cortex-m-semihosting/blob/master/asm.s
  • [ ] https://github.com/rust-embedded/riscv/blob/master/asm.S

Tiwalun avatar May 06 '20 15:05 Tiwalun

@japaric Regarding the psp_w and msp_w functions:

I'm not sure if it is possible to properly unwind after overwriting the stack pointer, I don't see a way how the debugger could recover the previous call frame address, it's not stored anywhere.

Tiwalun avatar May 11 '20 20:05 Tiwalun

The functions that overwrite the stack pointer should probably use .cfi_undefined directives after the instruction

jonas-schievink avatar Aug 25 '20 18:08 jonas-schievink

This interacts with https://github.com/rust-embedded/cortex-m/pull/259 – I've removed all the directives there, since rustc should generate them automatically for all Rust functions. But I don't know if that can see through inline assembly and emit unwind info for individual instructions in there.

jonas-schievink avatar Aug 25 '20 23:08 jonas-schievink