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

Поиск Reset Handler и трассировка вызова main

Когда Pico 2 перезагружается, процессор начинает выполнение с Reset Handler. Чтобы понять, как запускается наша программа, найдём Reset Handler, дизассемблируем его и пройдём по цепочке вызовов до нашей настоящей функции main на Rust.

Когда Pico 2 запускается, процессор не переходит сразу к нашей функции main на Rust. Вместо этого он проходит короткую цепочку функций, которую предоставляет среда выполнения Cortex-M.

В этом разделе мы:

  1. Найдём адрес, с которого чип начинает выполнение после сброса
  2. Посмотрим, какую функцию вызывает Reset Handler
  3. Пройдём по цепочке до нашей настоящей функции main на Rust

Чтение записи вектора сброса

Процессор Cortex-M начинает выполнение с чтения таблицы в начале флеш-памяти, которая называется таблицей векторов.

Первые две записи:

  • Слово 0 (смещение 0x00): начальное значение указателя стека
  • Слово 1 (смещение 0x04): адрес обработчика сброса

На Pico 2 флеш-память начинается с адреса 0x10000000, поэтому:

  • Начальное значение указателя стека хранится по адресу 0x10000000
  • Адрес обработчика сброса находится по адресу 0x10000004

Что такое Reset Handler?

Reset Handler — это первая функция, которая выполняется при включении или сбросе процессора. Она выполняет инициализацию и в конечном итоге вызывает нашу функцию main.

Прочитаем её в GDB:

(gdb) x/wx 0x10000004

Пример вывода — реальные адреса в ваших сборках могут отличаться, используйте свои, следуя этой главе:

0x10000004 <__RESET_VECTOR>:    0x1000010d

Это значение — адрес, на который процессор переходит после сброса. Младший бит (так называемый «бит Thumb») всегда равен 1, поэтому фактический адрес — 0x1000010c. Но можно использовать любое из двух значений (0x1000010d или 0x1000010c), GDB с обоими справится.

Также можно использовать программу readelf, чтобы найти адрес точки входа:

arm-none-eabi-readelf -h ./target/thumbv8m.main-none-eabihf/debug/pico-debug

Дизассемблирование Reset Handler

Теперь попросим GDB показать инструкции по этому адресу:

(gdb) disas 0x1000010d

# или

(gdb) disas 0x1000010c

Вы увидите ассемблерные инструкции обработчика сброса. Найдите инструкцию bl (Branch with Link), которая вызывает другую функцию:

...
0x10000140 <+52>:    isb     sy
0x10000144 <+56>:    bl      0x1000031c <main>
0x10000148 <+60>:    udf     #0

Reset Handler вызывает функцию по адресу 0x1000031c, которую GDB показывает как main, — но это ещё не наша функция main на Rust.

Что это за «main»?

main по адресу 0x1000031c — не функция main нашей программы. Это небольшая обёртка, созданная крейтом cortex-m-rt. Такую обёртку часто называют трамплином (trampoline), потому что позже она передаёт управление настоящей точке входа.

Её демангленное имя обычно такое:

#![allow(unused)]
fn main() {
// NOTE: здесь префикс pico_debug — это имя нашего проекта
pico_debug::__cortex_m_rt_main_trampoline
}

Давайте дизассемблируем её.

Дизассемблирование трамплина

(gdb) disas 0x1000031c

Вывод:

Dump of assembler code for function main:
   0x1000031c <+0>:     push    {r7, lr}
   0x1000031e <+2>:     mov     r7, sp
   0x10000320 <+4>:     bl      0x10000164 <_ZN10pico_debug18__cortex_m_rt_main17he0b4d19700c84ad2E>
End of assembler dump.

Это очень маленький кусок кода. Всё, что он делает, — вызывает настоящую точку входа на Rust, которая называется:

#![allow(unused)]
fn main() {
pico_debug::__cortex_m_rt_main
}

Включение демангленных имён

Имена функций Rust по умолчанию искажены (mangled) и выглядят нечитаемо.

Включим деманглинг (demangling):

set print asm-demangle on

Теперь попробуйте:

(gdb) disas 0x1000031c
# или
(gdb) disas pico_debug::__cortex_m_rt_main_trampoline

Теперь должны отображаться читаемые имена Rust.

Dump of assembler code for function pico_debug::__cortex_m_rt_main_trampoline:
   0x1000031c <+0>:     push    {r7, lr}
   0x1000031e <+2>:     mov     r7, sp
   0x10000320 <+4>:     bl      0x10000164 <pico_debug::__cortex_m_rt_main>
End of assembler dump.

Дизассемблирование настоящей функции main на Rust

Теперь посмотрим на нашу функцию main:

disas pico_debug::__cortex_m_rt_main

Вы увидите логику программы: сначала код начальной настройки, затем цикл, который переключает вывод светодиода.

#![allow(unused)]
fn main() {
...

0x100002dc <+376>:   bl      0x100079a4 <rp235x_hal::timer::Timer<rp235x_hal::timer::CopyableTimer0>::new_timer0>
0x100002e0 <+380>:   bl      0x10000b30 <rp235x_hal::gpio::Pin<rp235x_hal::gpio::pin::bank0::Gpio25, rp235x_hal::gpio::func::FunctionNull, rp235x_hal::gpio::pull::PullDown>::into_push_pull_output<rp235x_hal::gpio::pin::bank0::Gpio25, rp235x_hal::gpio::func::FunctionNull, rp235x_hal::gpio::pull::PullDown>>
...
0x100002f8 <+404>:   bl      0x10000c48 <rp235x_hal::gpio::eh1::{impl#1}::set_high<rp235x_hal::gpio::pin::bank0::Gpio25, rp235x_hal::gpio::pull::PullDown>>
...
0x10000306 <+418>:   bl      0x100006b8 <rp235x_hal::timer::{impl#7}::delay_ms<rp235x_hal::timer::CopyableTimer0>>
...
0x1000030c <+424>:   bl      0x10000c38 <rp235x_hal::gpio::eh1::{impl#1}::set_low<rp235x_hal::gpio::pin::bank0::Gpio25, rp235x_hal::gpio::pull::PullDown>>
...
}