В настоящее время я разрабатываю прошивку для микроконтроллера Arm Cortex-M0+ и столкнулся с довольно интересной проблемой. Я не ищу никаких ответов, а скорее хотел бы поделиться проблемой с другими разработчиками, чтобы я мог (надеюсь) пролить свет на проблему, с которой я столкнулся. Я опишу это ниже:
У меня есть программа, которая динамически загружает (правильно скомпилированный и связанный) код с внешнего флэш-чипа, который должен выполняться прямо из ОЗУ MCU. Интересно то, что я могу идеально выполнить загруженный в RAM код при пошаговом запуске (через отладчик), но он всегда будет падать [формально HardFault] при свободном запуске. Я пытался отключить все прерывания, я дважды проверил инструкции, адреса памяти, выравнивание байтов, все, но я все еще не могу точно определить причину исключения.
Есть ли у кого-нибудь из вас намеки на то, что может произойти? Мне очень интересно узнать больше о вашем опыте! Спасибо,
Обновление 1 (30/05)
Свободный пробег в данном случае означает, что точка останова не устанавливается непосредственно перед переходом в ОЗУ. Всякий раз, когда я вхожу в ветку и выполняю инструкции в ОЗУ, она будет работать правильно и возвращаться. Везде, где точки останова нет (и, таким образом, MCU масштабирует ветку), наблюдается HardFault. Обратите внимание, что он будет аварийно завершать работу даже при загрузке с включенным отладчиком, но без установленной точки останова.
Обновление 2 (30/05)
Я использую Cypress S6E1C3 Series Arm Cortex M0+ FM0+ Микроконтроллер.
Обновление 3 (30/05)
Покопавшись и поиграв с кодом, я смог заставить его работать правильно! Однако он вызвал у меня больше вопросов, чем ответов. Читая официальную документацию ARM об инструкции BLX (BLX), я обнаружил, что LSBit адреса ветки определяет режим инструкций процессора (1 заставляет ее работать в режиме Thumb). Явно установив этот бит, У меня код работает всегда, даже в свободный режим. Дело в том, что код в ОЗУ не был скомпилирован в режиме Thumb, и нет причины очевидный, по которой пошаговое выполнение кода с помощью отладчика может привести к изменению режима инструкций... Есть идеи?
К.
Возможно, отладчик запускает сценарий запуска, который загружает различные регистры в MCU, чтобы подготовить MCU к запуску кода. При работе в свободном режиме вам необходимо убедиться, что загрузочный код загружает все эти регистры в одном порядке с одинаковыми значениями.
Я проверил техническое описание и руководство и не смог найти никаких упоминаний о спекулятивной предварительной выборке, несмотря на то, что это хорошая отправная точка... Я заметил, что в свободном режиме последний адрес, удерживаемый ПК перед HardFault, точно первый адрес в оперативной памяти, который MCU пытался выполнить.
Я очень сомневаюсь, что этот отлаживать загрузочный код с боковой загрузкой, упомянутый пользователем 3386109, является причиной ошибок. Я так говорю, потому что Я вижу сбой, даже когда я загружаюсь с отладчиком. Это работает только в том случае, если я установил точку останова прямо перед инструкцией ветвления и ссылки, которая заставляет MCU работать в пространстве ОЗУ., что мне кажется весьма подозрительным...
Обратите внимание, что я бы помог, если бы вы указали фактический MCU, а не просто сказали «ARM Cortex-M0+». У многих продавцов есть известные причуды, которые могут объяснить ваше поведение.
Хороший вопрос, Turbo J, сейчас я работаю с Cypress S6E1C3 Series Arm Cortex M0+ FM0+ Микроконтроллер.
Есть ли какой-нибудь механизм, который позволил бы узнать куда, что произошел HardFault? Это в самой инструкции ветвления (или выборке кода для инструкции после)? Или он работает некоторое время, прежде чем сбой?
Возможен ли аппаратный сбой? Вы проводили стресс-тестирование оперативной памяти с кодом, запускаемым из флэш-памяти? Последовательная выборка инструкций может нагружать тайминги памяти так, как не влияет на пошаговое выполнение?
Привет, Питер Кордес, я не уверен, где происходит сбой, но если бы мне пришлось угадывать, я бы сказал, что это происходит после выполнения ветки. Я говорю так, потому что ПК (R15) всегда будет указывать на адрес первой инструкции в ОЗУ. и я также вижу, что регистр ссылок (LR) всегда имеет правильный адрес возврата (во Flash). Сбой происходит всегда сразу, и это не похоже на то, что он работал какое-то время, а затем случайно зависал. Я не уверен, что причиной может быть аппаратный сбой, потому что я вижу ту же проблему при запуске этого кода на втором устройстве.
Питер Кордес, есть ли у вас какие-либо сведения о конвейере и времени выполнения инструкций в Arm?
У меня обновление...!
Чтобы уведомлять людей, когда вы отвечаете, используйте @username
.
Cortex-M Только поддерживает режим Thumb, а не режим ARM, поэтому указание вашему компилятору компилятору для Cortex-M0+ гарантирует, что он создаст код Thumb2.
Вот почему вам нужно установить младший бит вашего целевого адреса.
https://en.wikipedia.org/wiki/ARM_Cortex-M#Instruction_sets
Only Thumb-1 and Thumb-2 instruction sets are supported in Cortex-M architectures; the legacy 32-bit ARM instruction set isn't supported.
Если вы на самом деле посмотрите на байты кода в памяти, вы увидите, что это инструкции Thumb2.
Вопрос только в том, как ваш отладчик умудрился сделать это ошибкой нет. Возможно, в любом случае он должен специально обрабатывать BLX и не эмулировать отказ при переключении в режим ARM в поведении микроархитектур только для большого пальца. Или, может быть, просто обработка одноэтапного прерывания заканчивается правильным возвратом в режиме Thumb.
Я думаю, что «наследие» - это немного преувеличение для режима ARM в целом; Я думал, что высокопроизводительные чипы ARM (с большим кэшем инструкций) все еще могут выиграть от режима ARM, выполняя больше с меньшим количеством инструкций и более легким доступом к большему количеству регистров. Во всяком случае, это всего лишь формулировка Википедии.
Проблема была в адресе ветки (как правильно указал @PeterCordes). Переход к оперативной памяти выполнялся по следующему коду (слегка скорректированному под эту аудиторию):
// Prepare address and Function Pointer
uint32_t codeBufferAddress = reinterpret_cast<uint32_t>(&buffer) + offset;
void(*methodFromRAM)(void*) = reinterpret_cast<void(*)(void*)>(codeBufferAddress | 0x01);
// Branch to RAM
// thisPointer holds an object byte-stream...
(*methodFromRAM)(reinterpret_cast<void*>(thisPointer));
Обратите внимание, что в строке 3 codeBufferAddress
объединяется с 0x01
(codeBufferAddress | 0x01
), что гарантирует, что ветвь будет выбрана в Режим большого пальца. К тому моменту, когда я опубликовал этот вопрос, значение, хранящееся в codeBufferAddress
, было 0x20003E40
, что явно не устанавливает LSBit и, таким образом, заставит MCU работать в Режим ОХРАНЫ.
Я думаю, что компилятор не сможет вывести и настроить режим ветвления, потому что адрес динамически генерируемый (хотя я думаю, что это может быть немного умнее и заставить все ветки работать в Режим большого пальца, поскольку мой MCU может декодировать только Thumb).
В любом случае, при работе в пошаговом режиме я не мог наблюдать никаких изменений в целевом адресе, и пока могу только догадываться, что заставляет MCU работать в Режим большого пальца... но это только предположение. Есть идеи?
К.
Поддерживает ли ваш чип спекулятивную предварительную выборку? Я так не думаю, но если бы это было так, это могло бы быть Обходной путь предварительной выборки ARM там, где они пренебрегли тем, чтобы сделать ввод-вывод или другие области физического адресного пространства недоступными для предварительной выборки. (И поэтому спекулятивные загрузки или выборки кода из них приводили к жестким зависаниям.)