Отладка микроконтроллеров ARM Cortex-M по UART

В данной статье я расскажу вам как можно использовать регистры отладки и breakpoint'ов в микроконтроллерах, построенных на ядрах ARM Cortex-M

Введение


Знали ли вы что ядра ARM Cortex-M в микроконтроллерах могут отлаживать сами себя?
Оказывается могут.

Читая Technical Reference Manual на ядро Cortex M3 обнаружил что у него есть прерывание DebugMon. Далее проанализировал все регистры с ним связанные. В итоге выяснил что МК может попадать в это прерывание при условии равенства регистра PC и одного из регистров FP_COMP.
Это означает что мы можем устанавливать точки останова в отлаживаемой прошивке. Так-же можно принудительно вызывать прерывание DebugMon установив бит MON_PEND регистра DEMCR в 1.

Проверка теории


Так как данные регистры присутствуют во всём семействе ядер ARM Cortex-M, возьмем первую попавшуюся отладочную плату. У меня это оказалось stm32f723e-disco. Для того чтобы не тратить время на написание инициализационного кода периферии, используем CubeMX
Из периферии нам понадобится лишь UART6, соединенный с ST-Link'ом и светодиод на плате:

Настройки в CubeMX
image

Генерируем проект и сразу открываем его IDE.

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

Определения регистров
#define DHCSR (*(uint32_t*)0xE000EDF0)
#define DCRSR (*(uint32_t*)0xE000EDF4)
#define DCRDR (*(uint32_t*)0xE000EDF8)
#define DEMCR (*(uint32_t*)0xE000EDFC)

#define NUMOFBKPTS 8

typedef struct {
  uint32_t FP_CTRL;
  uint32_t FP_REMAP;
  uint32_t FP_COMP[NUMOFBKPTS];
} fp_t;

#define FP ((fp_t*)0xE0002000)


Для проверки что наш проект в принципе работает, пишем код, мигающий светодиодом:

Мигаем светодиодом
  while (1)
  {
    for (int i=0;i<1000000;i++)
      asm("nop");
    
    HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_7);
  }


Светодиод успешно заморгал, значит проект успешно компилируется и запускается.

Добавляем в этот цикл строчку:

asm("BKPT #0");

Эта ассемблерная вставка является программной точкой останова. Теперь при отладке через ST-Link программа всегда будет останавливаться на этой строчке. Выходим из Debug в IDE и светодиод начинает моргать как и раньше.

Но что нам это дало?

А то что мы можем отлавливать точку останова прерыванием DebugMon.

Пишем его обработчик:

Обработчик DebugMon
void DebugMon_Handler(void)
{
  while (1)
  {
    if ((USART6->ISR & USART_ISR_TXE) != 0U)
    {
      USART6->TDR = '!';
    }
  }
}


Компилируем, прошиваем и ничего не изменилось. Чтобы разрешить работу прерывания DebugMon, нужно поднять бит MON_EN в регистре DEMCR.

Теперь при отладке через ST-Link программа будет останавливаться как и раньше на этой строчке. Как только мы выйдем из режима отладки, в терминале начнут появляться восклицательные знаки:

Вывод терминала
image

Далее проверяем тоже самое с регистром FP_COMP.

Находим адрес любой инструкции в цикле функции main средствами IDE и активируем точку останова:

FP->FP_COMP[0] = 0x080017CC | 1; // адрес середины бесконечного цикла = 0x080017CC

При отключенном аппаратном отладчике, микроконтроллер тоже попадает в прерывание DebugMon.

Как это можно использовать


С помощью вышеприведенных регистров и прерывания DebugMon возможно реализовать отладку микроконтроллера без SWD/JTAG отладчика. Так-же становится возможным отлаживать прошивку устройства, с которым есть связь, но доступ к выводам SWD затруднен.

Продолжение следует…
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 17

    +1
    Очень любопытно, особенно asm(«BKPT #0»); возьму на вооружение. Ваш код UART контрастирует с кодом UART из соседней статьи.
      0
      Так тот код студенты писали.
        0

        То преподавателей писал

          +2
          Вам обязательно быть токсичными засранцами?
            0
            Нет, но правда она такая.
              0
              Правда о том, что ты докопался до забора, автор той статьи тебя не понял и ты теперь ходишь и рассказываешь даже в соседних темах какой он неразумный? На твой вопрос, к слову, дальше дали ответ, а встречный вопрос ты проигнорировал. Ту статью я считаю достаточно качественной, а твоё поведение (и ещё пары товарищей из комментов) — неадекватным и деструктивным.
      +3

      Идея интересная, буду ждать продолжения!
      Пара ремарок:


      Spoiler header

      Если вы пользуетесь HAL'ом, то вы автоматически пользуетесь и библиотекой CMSIS, которая дает следующие возможности:


      • вместо ассемблерной вставки можно использовать псевдоним __BKPT; аналогичные псевдонимы есть для большинства ассемблерных команд
      • адреса отладочных регистров можно не определять самостоятельно, к ним есть доступ через "структуру" CoreDebug — CoreDebug->DEMCR, например
        0
        Про псевдонимы был в курсе, просто не использую их,
        а вот про определение CoreDebug не знал. Спасибо
          0

          А почему псевдонимы не используете?

            0
            Ассемблерная вставка — первое что пришло в голову, про макросы не всегда помню
              0

              Понятно, спасибо.

        0

        Жду продолжения. Хорошо бы рассмотреть, что вообще можно делать в прерывании DebugMon, а то бесконечная печать восклицательных знаков пока не сильно отличается от такой же печати в коде самой прошивки.
        Как я понимаю, в прерывании можно реализовать свой протокол для общения с самопальным отладчиком на компьютере, включая просмотр содержимого памяти и регистров?

          0
          Чтение памяти и регистров можно реализовать даже без DebugMon.
          Он нужен для возможности установки breakpointов
          0
          Находим адрес любой инструкции в цикле функции main средствами IDE и активируем точку останова:
          FP->FP_COMP[0] = 0x080017CC | 1; // адрес середины бесконечного цикла = 0x080017CC


          И так при каждой компиляции программы узнавать через отладку адреса всех нужных точек остановки? Не проще ли breakpoint поставить? Ну или можно как-то «автоматизировать» выдергивание адресов нужных инструкций?

          P.S. DebugMon на самом деле идеально не для инструкций использовать, а для переменных, чьи адреса всегда известны. Жду продолжения.
            +1
            Так это я для проверки и демонстрации работоспособности сделал
            В следующей статье не будет ручного выдергивания адресов, только автоматическое
            0

            Ожидал статью про то, как прикрутить gdb агент...

              0
              Надеюсь, что в продолжении как раз будет полноценная библиотека-API. Чтобы можно было через openocd работать полноценно.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое