Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Исключения

Исключения и прерывания — это аппаратный механизм, с помощью которого процессор обрабатывает асинхронные события и фатальные ошибки (например, выполнение недопустимой инструкции). Исключения подразумевают вытеснение и включают обработчики исключений — подпрограммы, выполняемые в ответ на сигнал, вызвавший событие.

Крейт cortex-m-rt предоставляет атрибут exception для объявления обработчиков исключений.

// Обработчик исключения для исключения SysTick (системный таймер)
#[exception]
fn SysTick() {
    // ..
}

Помимо атрибута exception, обработчики исключений выглядят как обычные функции, но есть одно важное различие: обработчики exception нельзя вызывать программно. Например, в приведенном выше примере вызов SysTick(); приведет к ошибке компиляции.

Такое поведение намеренное и необходимо для обеспечения следующей особенности: переменные static mut, объявленные внутри обработчиков exception, безопасны для использования.

#[exception]
fn SysTick() {
    static mut COUNT: u32 = 0;

    // `COUNT` преобразуется в тип `&mut u32` и безопасен для использования
    *COUNT += 1;
}

Как известно, использование переменных static mut в функции делает ее нереентерабельной. Вызов нереентерабельной функции, прямо или косвенно, из нескольких обработчиков исключений/прерываний или из main и одного или более обработчиков исключений/прерываний приводит к неопределенному поведению.

Безопасный Rust никогда не должен приводить к неопределенному поведению, поэтому нереентерабельные функции должны быть помечены как unsafe. Однако, как было сказано, обработчики exception могут безопасно использовать переменные static mut. Это возможно, потому что обработчики exception не могут быть вызваны программно, что исключает возможность реентерабельности. Эти обработчики вызываются самим аппаратным обеспечением...

ПРИМЕЧАНИЕ: Этот программный код не будет работать (т.е. не завершится сбоем) на QEMU, поскольку qemu-system-arm -machine lm3s6965evb не проверяет загрузку памяти и с радостью вернет 0 при чтении из недопустимой памяти.

#![no_main]
#![no_std]

use panic_halt as _;

use core::fmt::Write;
use core::ptr;

use cortex_m_rt::{entry, exception, ExceptionFrame};
use cortex_m_semihosting::hio;

#[entry]
fn main() -> ! {
    // Чтение из несуществующего адреса памяти
    unsafe {
        ptr::read_volatile(0x3FFF_0000 as *const u32);
    }

    loop {}
}

#[exception]
fn HardFault(ef: &ExceptionFrame) -> ! {
    if let Ok(mut hstdout) = hio::hstdout() {
        writeln!(hstdout, "{:#?}", ef).ok();
    }

    loop {}
}

Обработчик HardFault выводит значение ExceptionFrame. Если вы запустите этот код, вы увидите что-то вроде этого в консоли OpenOCD:

$ openocd
(..)
ExceptionFrame {
    r0: 0x3fff0000,
    r1: 0x00000003,
    r2: 0x080032e8,
    r3: 0x00000000,
    r12: 0x00000000,
    lr: 0x080016df,
    pc: 0x080016e2,
    xpsr: 0x61000000,
}

Значение pc — это значение программного счетчика на момент исключения, и оно указывает на инструкцию, вызвавшую исключение.

Если посмотреть дизассемблированный код программы:

$ cargo objdump --bin app --release -- -d --no-show-raw-insn --print-imm-hex
(..)
ResetTrampoline:
 8000942:       movw    r0, #0xfffe
 8000946:       movt    r0, #0x3fff
 800094a:       ldr     r0, [r0]
 800094c:       b       #-0x4 <ResetTrampoline+0xa>

Вы можете найти значение программного счетчика 0x0800094a в дизассемблированном коде. Вы увидите, что операция загрузки (ldr r0, [r0]) вызвала исключение. Поле r0 в ExceptionFrame покажет, что значение регистра r0 в этот момент было 0x3fff_fffe.