Итак,продолжим про многоядерный бареметал.
“Обнаруженный в кустах рояль” под названием 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
ссылка на оригинал статьи https://habr.com/ru/articles/1062346/