Skip to content

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

Open
@japaric

Description

@japaric

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions