Skip to content
This repository has been archived by the owner on Jan 24, 2022. It is now read-only.

Backtrace for HardFault #158

Closed
perlindgren opened this issue Dec 9, 2018 · 3 comments
Closed

Backtrace for HardFault #158

perlindgren opened this issue Dec 9, 2018 · 3 comments
Labels

Comments

@perlindgren
Copy link

I was experimenting with the latest release of cortex-m-rt (0.6.6).

Possibly related to #139.

The code (below) should hard fault, and it does and the handler is invoked.
a gdb bactrace works at the first breakpoint. (I used the "asm-inline" feature, to have the bkpts inlined).

(gdb) p/x *ef
$9 = cortex_m_rt::ExceptionFrame {r0: 0x2fffffff, r1: 0xf00000, r2: 0x20000000, r3: 0x20000008, r12: 0xdfffffff, lr: 0x80006c3, pc: 0x80002f8, xpsr: 0x61000000}

seems ok. (pc being the read_volatile on the faulty address)

However, when trying to make a backtrace from within testf or after testf (back in the HardFault handler), backtracing ends up in an infinite loop.

(gdb) bt
#0  HardFault (ef=<optimized out>) at examples/crash-semihosting.rs:121
#1  0x08001adc in HardFault (ef=0x2000ffe0) at examples/crash-semihosting.rs:120
#2  0x08001adc in HardFault (ef=0x2000ffe0) at examples/crash-semihosting.rs:120

I wonder if this is the expected behavior. If so, then the docs should perhaps indicate that if you want to make a backtrace, put an asm::bkptr at entry of the HardFault handler, and do the bactrace before you call any function, e.g.,

let _ = hprintln!("{:#?}", ef);

I did not use panic!(..) to print the ef, as I wanted the example as simple as possible (less indirections). Printing ef can of course be done from within gdb as well, and is often preferrable as bringing formatting and semi-hosting printing is somewhat bloating the code.

On another note, I tried printing `ef' over ITM, but that is not advisable, as (at least on the stlink/nucleo 64), the internal buffer of the debug interface will overflow causing the ITM channel to be frozen). After that ITM was inaccessible, requiring an openocd kill/restart, but that behaviour might be installation dependent.

I took a brief look at the generated asm (release mode), and it looks ok, it reserves som place on the stack for the HardFault handler, etc.

...
#[entry]
#[inline(never)]
fn main() -> ! {
    unsafe {
        // read an address outside of the RAM region; this causes a HardFault exception
        ptr::read_volatile(0x2FFF_FFFF as *const u32);
    }

    loop {}
}

#[inline(never)]
fn testf() {
    asm::bkpt();
}

#[exception]
fn HardFault(ef: &cortex_m_rt::ExceptionFrame) -> ! {
    // prints the exception frame using semihosting
    asm::bkpt();
    testf();
    asm::bkpt();
    let _ = hprintln!("{:#?}", ef);
    asm::bkpt();
    let _ = hprintln!("{:#?}", ef);
    asm::bkpt();
    loop {
        asm::bkpt();
    }
}
@perlindgren
Copy link
Author

Reading in on the related #139, perhaps the handler should not be ->!, but -> () and wrapped in an external handler being ->!. Then internally, we should have a correct stack handling (I hope, if LLVM is not outsmarting us by knowing that its called only in -> ! context and hence -> ! stack rules should apply).

In any case the consequence is that "after" printing the ef contents using semihosting, the backtrace is broken, which is worse than having no printing at all.

Any thoughts on how we should handle this problem?

Best
Per

@perlindgren
Copy link
Author

perlindgren commented Dec 9, 2018

Here is a possible workaround in the meantime (could be replacing the crash.rs from quickstart, so if somebody feels like making a temporary PR feel free).

Notice, this requires "asm-inline", as the breakpoint would else be __bkpt, which already triggers the problem with the backtrace. If stable is required you may instead put a software breakpoint on the HardFault handler and remove the asm::bkpt().

//! Debugging a crash (exception)
//!
//! Most crash conditions trigger a hard fault exception, whose handler is defined via
//! `exception!(HardFault, ..)`. The `HardFault` handler has access to the exception frame, a
//! snapshot of the CPU registers at the moment of the exception.
//!
//!
//! ``` text
//! (gdb) continue
//! Continuing.
//! 
//! Program received signal SIGTRAP, Trace/breakpoint trap.
//! HardFault (ef=0x2000ffe0) at examples/crash.rs:82
//! 122         asm::bkpt();
//! (gdb) p/x *ef
//! $16 = cortex_m_rt::ExceptionFrame {
//!     r0: 0x2fffffff, 
//!     r1: 0xf00000, 
//!     r2: 0x20000000, 
//!     r3: 0x0, 
//!     r12: 0x0, 
//!     lr: 0x8000477, 
//!     pc: 0x8000200, 
//!     xpsr: 0x61000000
//!     }     
//! ```
//! 
//! The program counter (pc) contains the address of the instruction that caused the exception. In GDB one can
//! disassemble the program around this address to observe the instruction that caused the
//! exception.
//! 
//! ```
//! (gdb) disassemble 0x8000200
//! Dump of assembler code for function main:
//!    0x080001fc <+0>:     mvn.w   r0, #3489660928 ; 0xd0000000
//!    0x08000200 <+4>:     ldr     r0, [r0, #0]
//!    0x08000202 <+6>:     movw    r0, #1228       ; 0x4cc
//!    0x08000206 <+10>:    movt    r0, #2048       ; 0x800
//!    0x0800020a <+14>:    bl      0x8000484 <core::panicking::panic>
//!    0x0800020e <+18>:    udf     #254    ; 0xfe
//! End of assembler dump.
//! ```
//!
//! `ldr r0, [r0, #0]` caused the exception. This instruction tried to load (read) a 32-bit word
//! from the address stored in the register `r0`. Looking again at the contents of `ExceptionFrame`
//! we see that the `r0` contained the address `0x2FFF_FFFF` when this instruction was executed.
//! 
//! We can further backtrace the calls leading up to the fault. 
//! ``` text
//! (gdb) bt
//! #0  HardFault (ef=0x2000ffe0) at examples/crash.rs:82
//! #1  <signal handler called>
//! #2  core::ptr::read_volatile (src=0x2fffffff) at /rustc/14997d56a550f4aa99fe737593cd2758227afc56/src/libcore/ptr.rs:885
//! #3  main () at examples/crash.rs:72
//! ```

#![no_main]
#![no_std]

extern crate panic_halt;
extern crate stm32f413;

use core::ptr;
use cortex_m::asm;
use cortex_m_rt::{entry, exception};

#[entry]
#[inline(never)]
fn main() -> ! {
    unsafe {
        // read an address outside of the RAM region; this causes a HardFault exception
        ptr::read_volatile(0x2FFF_FFFF as *const u32);
    }
    panic!();
}


#[exception]
fn HardFault(ef: &cortex_m_rt::ExceptionFrame) -> ! {
    // (gdb) p/x *ef
    // prints the exception frame.
    asm::bkpt();
    unsafe {
        ptr::read_volatile(ef);
    }

    panic!();
}

@jonas-schievink
Copy link
Contributor

This seems like a duplicate of #139, so closing in favor of that (though ultimately it's an upstream bug)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

2 participants