Перейти к содержанию
    

Чтение внутренних регистров STM32

Единственное, для чего мне это понадобилось, это скинуть дамп регистров при возникновении Fault.

Пришлось написать вставку на ASM для копирования регистров в память (структуру). Остальная работа на C.

И где гарантия, что компилятор при очередной перекомпиляции, не разместит какие-то переменные в регистрах которые Вы хотели сохранить до вашей вставки?

Такое надо делать целиком в asm-функциях. Напишите ISR fault-ов полностью на asm, там всё элементарно.

 

Реально, по значениям LR, PC, R0-R4 в подавляющем большинстве случаев можно найти причину фэйла.

Не знаю не знаю... У меня сохраняются все регистры + дамп стека. И то часто не хватает.

 

У меня алгоритм такой:

Исключение -> запись контекста исключения в RAM -> перезапуск -> сохранение контекста на внешнем носителе или передача на отладочный сервер -> возобновление работы

У меня все fault-ы примерно так же обрабатываются, только кроме дампа регистров сохраняю ещё и дамп текущего стека.

Отличия:

Для release-сборки: всё так же, кроме передачи на отладочный сервер.

Для debug-сборки: вместо перезапуска - переинициализация железа в минимальный дефолт-конфиг trap-режима и зацикливание в цикле периодического вывода инфы о событии в лог (UART). Чтобы не пропускать такие ошибки, а исправлять сразу.

Этот-же механизм используется для обработки программных критических ошибок (через SVC).

 

На удаленном оборудовании пару раз выручало. Всех подробностей не вспомню, но потребовалось

Помогает очень часто в обнаружении редко проявляющихся ошибок. Когда тестируем сразу несколько десятков (а то и сотни) устройств в течение длительного времени (дни/недели непрерывной работы).

С JTAG-ом тут не посидишь, а вот устройства защёлкнувшиеся в trap-цикле сразу видны. А потом - по регистрам-стеку ищем причину.

И если такие ошибки проявились уже на объектах заказчика - тут другого выбора нет, очень помогает.

 

Без дампа стека вообще мало когда эта инфа помогает. Типичная ситуация: срабатывание исключения MPU защиты памяти при попытке обращения к недопустимой памяти изнутри memcpy().

От регистров почти никакого толку - в каком месте кода произошёл сбой? Зато по стеку можно легко проследить цепочку вызовов. Уже сколько так багов нашли.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

И где гарантия, что компилятор при очередной перекомпиляции, не разместит какие-то переменные в регистрах которые Вы хотели сохранить до вашей вставки?

Такое надо делать целиком в asm-функциях. Напишите ISR fault-ов полностью на asm, там всё элементарно.

С легкой руки (для IAR)

__stackless void FaultHandler(void * p_base, unsigned long EXC_RETURN)
{
 SaveContext(p_base, EXC_RETURN);
 EXPT.VERSION = APP_Get_FwVersion();
 gMarker = EXCEPTION_MARKER;
#ifdef NDEBUG
 SYS_Reset();
#else
 while (1);
#endif
}

__stackless __irq void HardFaultHandler(void)
{
if (SCB->HFSR & SCB_HFSR_DEBUGEVT_Pos)
  return;
 FaultHandler((void*)( (__get_LR() & BIT(2)) ? __get_PSP() : __get_MSP()),
             __get_LR());
}

__stackless __irq void MemoryFaultHandler(void)
{
 FaultHandler((void*)( (__get_LR() & BIT(2)) ? __get_PSP() : __get_MSP()),
             __get_LR());
}

__stackless __irq void BusFaultHandler(void)
{
 FaultHandler((void*)( (__get_LR() & BIT(2)) ? __get_PSP() : __get_MSP()),
             __get_LR());

}

__stackless __irq void UsageFaultHandler(void)
{
 FaultHandler((void*)( (__get_LR() & BIT(2)) ? __get_PSP() : __get_MSP()),
             __get_LR());

//  while(1);
}


void SaveContext(void * p_base, unsigned long EXC_RETURN)
{
 EXPT.Registers = *(struct exception_saved_context * )p_base;
 EXPT.SP        = (DWORD)p_base + sizeof(struct exception_saved_context);

 EXPT.Status.dwRaw = SCB->CFSR;
 EXPT.Exception = (int_source_t)(SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk);
 EXPT.EXC_RETURN = ( EXPT.Status.BFSR.BFARVALID) ? SCB->BFAR : EXC_RETURN;
 SCB->CFSR = 0;

 EXPT.HeapFree          = OS_MEMORY_AVAILABLE();
 EXPT.ShedulerState     = OS_GET_SHEDULER_STATE();
 EXPT.Context.StackFree = OS_GET_TASK_STACK_WATERMARK(OS_GET_CURRENT_TASK_HANDLE());
 strncpy(EXPT.Context.Name, (char*)OS_GET_CURRENT_TASK_NAME() , sizeof(EXPT.Context.Name));
 EXPT.Context.Name[sizeof(EXPT.Context.Name)-1] = '\0';

 

Для CORTEX ядра все получается и без ASM функций.

А вот для ARM делал вставку на ASM с обработчиками исключений и функцией сохранения регистров.

 

В большинстве случаев эта информация помогает на столе.

В основном в исключения валимся при переполнении стека. Поэтому место возникновения исключения, при наличии RTOS, мало что скажет. Главное состояние планировщика, текущая задача(последняя в случае переключения контекста) и свободное место в стеке задачи.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

__stackless void FaultHandler(void * p_base, unsigned long EXC_RETURN)

{

SaveContext(p_base, EXC_RETURN);

...

Для CORTEX ядра все получается и без ASM функций.

Ещё раз: Где гарантия, что компилятор не вставит перед Вашей SaveContext() например создание стекового фрейма с занесением указателя на него в любой из R4-R11?

Типа:

PUSH {R7, LR}

ADD R7, SP, #N

...

или ещё чего, чего ему вздумается.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Ещё раз: Где гарантия, что компилятор не вставит перед Вашей SaveContext() например создание стекового фрейма с занесением указателя на него в любой из R4-R11?

Как где? __stackless же. У gcc есть аналогичная штука - naked.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Как где? __stackless же. У gcc есть аналогичная штука - naked.

А это обязательное или только рекомендуемое как inline? А функции, которые из FaultHandler() вызываются тоже все __stackless?

И стек - это для примера. Что угодно, например - в какой-либо регистр может заранее положить указатель на секцию данных, либо какие-то другие данные.

Использование регистров внутри си-функции - по усмотрению компилятора, любые домыслы по их содержимому - потенциальные грабли.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

jcxz, если делать что-то бездумно - то возможны любые грабли.

А если подумать - компилятор пытается лишь сделать то, что от него просят. И не больше.

Головой думать надо, прежде что-то делать.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

И не больше.

Ну, ну. Я как-то на Си осмелился пописать под AVR. На меге8 килобайты флеш закончились настолько быстро, что захотелось узнать куда.

Листинг показал, что компилятор сохраняет в стек все регистры при вызове функций, даже неиспользуемые.

Отдельным приключением было сделать программный UART на Си - по тактам реализация очень неустойчива - на том краю планеты бабочка

махнет рукой (добавим, например, переменную в проект), а у нас пару лишних тактов в цикле задержки.

Что касается регистров, тактов и байтов - компилятор очень непредсказуемая штука.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Листинг показал, что компилятор сохраняет в стек все регистры при вызове функций, даже неиспользуемые.

Компилятор IAR так поступает при выключенной оптимизации (или невысоком уровне).

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

adnega, речь немного не про такой случай.

Использование стека еще как компиляторозависимо. От того, как он будет оптимизировать код, и какую оптимизацию ему разрешено использовать.

Уверен, и Вашему случаю нашлось бы объяснение в мануалах.

 

Здесь о том, что __stackless говорит компилятору, что стека НЕТ! То есть только регистры. И ничего он в них выделять не будет, пока не попросишь.

 

Да, и определитесь, что у вас закончилось, стек или флеш.... не вяжется как-то.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Компилятор IAR так поступает при выключенной оптимизации (или невысоком уровне).

Тут советовали головой думать, но моя не может придумать - "зачем"?

Зачем неиспользуемые регистры складывать в стек??

 

Хотелось, чтобы основные "просьбы" к компилятору были в исходнике, а не различных ключах.

Именно поэтому есть задачки, не решаемые на С гарантированно, рассчитывать на здравый смысл компилятора я бы не стал.

 

Да, и определитесь, что у вас закончилось, стек или флеш.... не вяжется как-то.

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

 11e:    1f 92           push    r1
120:    0f 92           push    r0
122:    0f b6           in    r0, 0x3f; 63
124:    0f 92           push    r0
126:    11 24           eor    r1, r1
128:    2f 93           push    r18
12a:    3f 93           push    r19
12c:    4f 93           push    r20
12e:    5f 93           push    r21
130:    6f 93           push    r22
132:    7f 93           push    r23
134:    8f 93           push    r24
136:    9f 93           push    r25
138:    af 93           push    r26
13a:    bf 93           push    r27
13c:    ef 93           push    r30
13e:    ff 93           push    r31

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

Я описывал ситуацию, когда банальная установка переменной оборачивалась push-ами и pop-ами для всех регистров.

В моем случае резко закончилась флеш, хотя и к глубине стека требования резко возрастают.

Перед использованием С этот же проект был реализован на asm. При 10% asm-функционала на С закончился флеш.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Зачем неиспользуемые регистры складывать в стек??

Затем что оптимизации НЕТ. Делаем так, чтобы 110% все работало и отладка была удобной.

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

Программа не работала? Так какие претензии к компилятору?

 

 

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

Так да. Закончится и то, и другое.

 

На AVR почти не писал. А в ARM,как тут уже приводили, - одна инструкция хооть для всех регистров.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

jcxz, если делать что-то бездумно - то возможны любые грабли.

А если подумать - компилятор пытается лишь сделать то, что от него просят. И не больше.

Головой думать надо, прежде что-то делать.

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

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

Если думать именно головой, то как раз не надо делать никаких гаданий как именно будет построен код внутри си-функции.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

Но, вероятность получить "пасхальное яйцо" от компилятора примерно равна вероятности получить неработоспособный код со вполне работоспособных исходников.

Выбор всегда есть:

- хочешь сложное кодирование но 110% результат - пиши в машинных кодах,

- хочешь более легкое программирование и 100% результат - попытайся думать так, как думает компилятор.

 

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

К слову, ASM файлы тоже в большинстве случаев не переносимы.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

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

Гость
К сожалению, ваш контент содержит запрещённые слова. Пожалуйста, отредактируйте контент, чтобы удалить выделенные ниже слова.
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...