RTT без JLINK

от автора

Итак,продолжим про многоядерный бареметал.

“Обнаруженный в кустах рояль” под названием IAR i-Jet TRACE позволяет полноценную отладку SMP (Symmetric Multiprocessing) приложения, запущенного на нескольких ядрах многоядерного процессора, в данном случае это Allwinner T113.

Полноценная это значит: возможность старт-стоп отдельно каждого ядра, установка breakpoints для каждого ядра, просмотр регистров каждого ядра (окно регистров переключает значения при переключении on-focused ядра в окошке ядер IARa, см. скриншот ниже).

Эта SMP полноценность, мне кажется, важна для начального старта разработки на новом процессоре.

Попытки организовать такую отладку в “альтернативных наборах инструментов”, а именно, Segger Ozone + jLink, VS Code + cortex плагин + jLink, успеха не имели. Судя по всему, эти инструменты предлагают только вариант AMP (Asymmetric Multiprocessing).

Это когда через один отладочный девайс (jLink) к разным ядрам процессора подключаются разные сессии отладки, запускаемые из разных копий программ отладчика (Ozone, VSCode). При этом в эти копии отладчиков загружаются отдельные копии проекта, ссылающиеся на одни и те же исходники и выходные файлы. В случае SMP приложения это, ну, как-то “не то”…

Но, естественно, в случае i-Jet мы теряем удобный отладочный инструмент под названием RTT. Можно вернуться к использованию старого доброго ARM DCC (Debug Communication Channel). И я проверю как оно работает в случае многоядерности, но попозже.

А что же такое RTT? Это ведь просто кольцевой буфер и пара указателей голова-хвост в памяти целевого процессора. Вся фишка в том, что jLink самостоятельно периодически просматривает эту структуру и вычитывает сложенные туда сообщения, ориентируясь на значения указателей голова-хвост. И что же нам мешает читать эту структуру каким-то иным способом? Дальше речь пойдет про отладчик IAR CSPY, но, уверен, подобная функциональность есть и в других отладчиках. Поэтому данный рецеп может быть реализован и там.

Итак, IAR CSPY. В нем есть режим отображения переменных, называемый live view. Отлично, значит память периодически читать он умеет. Чешем репу дальше. Находим мануал EWARM_DebuggingGuide.ENU.pdf

А там внимательно читаем раздел Advanced Debugging -> C-SPY Macros. Ну вот же оно!

Быстренько набрасываем код для целевого процесора, заменяющий функции Segger RTT на свои, с кодоовым названием IAR_CSPY_RTT

iar_mac_rtt.h:

// Строка "RTT!" в формате Little-Endian#define IAR_RTT_SIGNATURE       0x21545452//должен быть 2^x !!!!#define IAR_RTT_BUF_SIZE        2048#define IAR_RTT_BUF_TOTAL       2void ijet_rtt_init(void);unsigned int ijet_rtt_writeString(unsigned int ch,const char *str);

iar_mac_rtt.c:

#include "iar_mac_rtt.h"#ifdef IAR_CSPY_RTT  typedef struct _IJET_RTT  {  volatile unsigned int signature; //Поле для проверки макросом  volatile unsigned int head[IAR_RTT_BUF_TOTAL];  volatile unsigned int tail[IAR_RTT_BUF_TOTAL];  char buffer[IAR_RTT_BUF_TOTAL][IAR_RTT_BUF_SIZE];  }IJET_RTT;  #pragma default_variable_attributes=@ "SEGGER_RTT_DATA"    volatile IJET_RTT ijet_rtt={.signature=0,.head=0,.tail=0};  #pragma default_variable_attributes=  void ijet_rtt_init(void)  {  ijet_rtt.signature=IAR_RTT_SIGNATURE; //Инициализируем сигнатуру  }  unsigned int ijet_rtt_writeString(unsigned int ch,const char *str)  {  unsigned int rc=0;  //Печатаем только если структура инициализирована  if(ch<IAR_RTT_BUF_TOTAL && ijet_rtt.signature==IAR_RTT_SIGNATURE)    {    while(*str)      {      unsigned int next_head=(ijet_rtt.head[ch]+1)%IAR_RTT_BUF_SIZE;      if(next_head==ijet_rtt.tail[ch])        {        break;        }      ijet_rtt.buffer[ch][ijet_rtt.head[ch]]=*str++;      ijet_rtt.head[ch]=next_head;      rc++;      }    }  return(rc);  }#endif

добавляем враппер для удобного переключения segger RTT <-> iar CSPY RTT

RTT_ex.h:

void RTT_Init(void);unsigned int RTT_WriteStringEx(unsigned char *str);

RTT_ex.c:

void RTT_Init(void);unsigned int RTT_WriteStringEx(unsigned char *str);RTT_ex.c:extern unsigned int getCPUID(void);#ifdef IAR_CSPY_RTT  #include "iar_mac_rtt.h"  #pragma message("Use iar C-SPY RTT")  //iar cspy mac RTT -----------------------------------  volatile unsigned int rtt_call_cnt0=0,rtt_call_cnt1=0;  void ijet_rtt_snap_fire0(void)  {  rtt_call_cnt0++;  }  void ijet_rtt_snap_fire1(void)  {  rtt_call_cnt1++;  }  void RTT_Init(void)  {  ijet_rtt_init();  }  unsigned int RTT_WriteStringEx(unsigned char *str)  {  unsigned int rc=0,bufN=getCPUID();  if(bufN<IAR_RTT_BUF_TOTAL)    {    rc=ijet_rtt_writeString(bufN,(const char *)str);    switch(bufN)      {      case 0:           ijet_rtt_snap_fire0();           break;      case 1:           ijet_rtt_snap_fire1();           break;      default:           break;      }    }  return(rc);  }#else  #include "SEGGER_RTT.h"  #pragma message("Use SEGGER RTT")  //segger RTT ------------------------------------------  void RTT_Init(void)  {  SEGGER_RTT_Init();  }  unsigned int RTT_WriteStringEx(unsigned char *str)  {  unsigned int rc=0,bufN=getCPUID();  if(bufN<SEGGER_RTT_MAX_NUM_UP_BUFFERS)    {    rc=SEGGER_RTT_WriteString(bufN,(const char *)str);    }  return(rc);  }#endif

В наших исходниках меняем SEGGER_RTT_WriteString(0,message);
на RTT_WriteStringEx(message);

В проект добавляем эти файлы и в настройки проекта добавляем переменную условной трансляции IAR_CSPY_RTT.
Все! Со стороны проекта целевого процессора больше ничего не надо.

Теперь начинается самая темная магия. Функция RTT_ex.c:ijet_rtt_snap_fire0() предназначена для установки breakpoint’a с условием. Когда процессор попадает на исполнение этой функции, CSPY запускает макрос, ассоциированный с этой точкой останова, но! так как возвращаемое макросом условие НЕ_РАВНО 1 — дебаггер тут же снова запускает исполнение программы!!!

Путем долгих мытарств и борьбы с синтаксисом макросов в течение многих дней, вот он, долгожданный результат. Вывод этого CSPY RTT можно осуществить в DebugLog IAR, но удобнее перенаправить куда-то еще.
К сожалению, IAR открывает файл лога в эксклюзивном режиме и в процессе отладочной сессии его посмотреть невозможно НИЧЕМ.

По счастью, вспомнилась оооочень старая тема с организацией передачи сообщений внутри компьютера при помощи NAMED_PIPES. Тогда надо было получать логи и управлять сборкой проекта мейкером mk196.exe в DOS-подсистеме OS/2 на специально выделенном старом древнем медленном РС, ибо на более быстрых этот мейкер зависал.
По named pipe тогда удалось передавать сообщения на другой (рабочий) компьютер с OS/2.

И вот это — СРАБОТАЛО! Встречаем — ДВА лога от 2х работающих ядер Cortex-A7. Как мы помним, в исходниках нашего проекта все ровно так же как было при использовании Segger RTT

Обращаем внимание на окно выбора ядер внизу-справа и кнопки старта-останова вверху-слева окна исх. текста

Обращаем внимание на окно выбора ядер внизу-справа и кнопки старта-останова вверху-слева окна исх. текста
консольные логи, регистры выбранного ядра

консольные логи, регистры выбранного ядра
установка breakpoint с условием

установка breakpoint с условием
подключение макроса

подключение макроса

ссылка на оригинал статьи https://habr.com/ru/articles/1062346/