rust-embedded / rust-embedded/cortex-m

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

Open
#215 5 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

help wanted
Dominant language
Rust
Stars
1k
Forks
202
Avg merge
6d 2h
Merged PRs (30d)
2

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 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.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

The issue points to external assembly subroutines and assemble.sh; start by locating those assembly sources and rerun assemble.sh. Inspect bin/.a with arm-none-eabi-readelf --debug-dump=frames, confirming an FDE for each eligible routine. Done means CFI is added to routines that do not modify SP/LR, with register rules also handled for hard cases such as cortex_m_rt::HardFaultTrampoline.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
embedded-iot
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.