/* * This file is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . * * Copyright (C) 2021 Siddharth Bharat Purohit, CubePilot Pty Ltd */ #include #include #include "hal.h" #include "string.h" #include "watchdog.h" #include "stm32_util.h" #include "flash.h" CRASH_CATCHER_TEST_WRITEABLE CrashCatcherReturnCodes g_crashCatcherDumpEndReturn = CRASH_CATCHER_TRY_AGAIN; static CrashCatcherInfo g_info; static bool do_flash_crash_dump = true; static void* dump_start_address; static void* dump_end_address; static void CrashCatcher_DumpStartFlash(const CrashCatcherInfo* pInfo); static CrashCatcherReturnCodes CrashCatcher_DumpEndFlash(void); static void CrashCatcher_DumpMemoryFlash(const void* pvMemory, CrashCatcherElementSizes elementSize, size_t elementCount); #if defined(HAL_CRASH_SERIAL_PORT) static bool uart_initialised = false; static bool do_serial_crash_dump = false; #ifndef HAL_CRASH_SERIAL_PORT_BAUD #define HAL_CRASH_SERIAL_PORT_BAUD 921600 #endif // HAL_CRASH_SERIAL_PORT_BAUD #if !defined(USART_ISR_RXNE) #define USART_ISR_RXNE USART_ISR_RXNE_RXFNE #endif static void printString(const char* pString); static void waitForUserInput(void); static void dumpBytes(const uint8_t* pMemory, size_t elementCount); static void dumpByteAsHex(uint8_t byte); static void dumpHexDigit(uint8_t nibble); static void dumpHalfwords(const uint16_t* pMemory, size_t elementCount); static void dumpWords(const uint32_t* pMemory, size_t elementCount); static void CrashCatcher_DumpStartHex(const CrashCatcherInfo* pInfo); static CrashCatcherReturnCodes CrashCatcher_DumpEndHex(void); static void CrashCatcher_DumpMemoryHex(const void* pvMemory, CrashCatcherElementSizes elementSize, size_t elementCount); #endif // HAL_CRASH_SERIAL_PORT extern uint32_t __crash_log_base__, __crash_log_end__; uint32_t stm32_crash_dump_size(void) { uint32_t* page_addr = (uint32_t*)&__crash_log_base__; uint32_t page_size = stm32_crash_dump_max_size(); return page_addr[(page_size / sizeof(uint32_t)) - 1]; } uint32_t stm32_crash_dump_max_size(void) { return (uint32_t)&__crash_log_end__ - (uint32_t)&__crash_log_base__; } uint32_t stm32_crash_dump_addr(void) { return (uint32_t)&__crash_log_base__; } bool stm32_crash_dump_region_erased(void) { for (uint32_t i = 0; i < stm32_crash_dump_max_size(); i += 4) { if (((uint32_t*)stm32_crash_dump_addr())[i / 4] != 0xFFFFFFFF) { return false; } } return true; } #define ARRAY_SIZE(X) (sizeof(X)/sizeof(X[0])) extern uint32_t __ram0_start__, __ram0_end__, __heap_base__, __heap_end__, __bss_base__, __bss_end__; #define REMAINDER_MEM_REGION_SIZE (15000) // remainder memory for crashcatcher internal regions static uint32_t dump_size = 0; static uint8_t dump_buffer[32]; // we need to maintain a dump buffer of 32bytes for H7 static uint8_t buf_off = 0; const CrashCatcherMemoryRegion* CrashCatcher_GetMemoryRegions(void); const CrashCatcherMemoryRegion* CrashCatcher_GetMemoryRegions(void) { // do a full dump if on serial static CrashCatcherMemoryRegion regions[60] = { {(uint32_t)&__ram0_start__, (uint32_t)&__ram0_end__, CRASH_CATCHER_BYTE}, {(uint32_t)&ch, (uint32_t)&ch + sizeof(ch), CRASH_CATCHER_BYTE}}; uint32_t total_dump_size = dump_size + buf_off + REMAINDER_MEM_REGION_SIZE; // loop through chibios threads and add their stack info uint8_t curr_region = 2; for (thread_t *tp = chRegFirstThread(); tp && (curr_region < (ARRAY_SIZE(regions) - 1)); tp = chRegNextThread(tp)) { uint32_t total_stack; if (tp->wabase == (void*)&__main_thread_stack_base__) { // main thread has its stack separated from the thread context total_stack = (uint32_t)((const uint8_t *)&__main_thread_stack_end__ - (const uint8_t *)&__main_thread_stack_base__); } else { // all other threads have their thread context pointer // above the stack top total_stack = (uint32_t)(tp) - (uint32_t)(tp->wabase); } // log names if in RAM if (tp->name != NULL && is_address_in_memory((void*)tp->name)) { regions[curr_region].elementSize = CRASH_CATCHER_BYTE; regions[curr_region].startAddress = (uint32_t)(tp->name); regions[curr_region++].endAddress = (uint32_t)(tp->name) + 13; } // log thread info regions[curr_region].elementSize = CRASH_CATCHER_BYTE; regions[curr_region].startAddress = (uint32_t)(tp); regions[curr_region++].endAddress = (uint32_t)(tp) + sizeof(thread_t); // log thread stacks regions[curr_region].elementSize = CRASH_CATCHER_BYTE; regions[curr_region].startAddress = (uint32_t)(tp->wabase); regions[curr_region++].endAddress = (uint32_t)(tp->wabase) + total_stack; total_dump_size += total_stack; if ((total_dump_size) >= stm32_crash_dump_max_size()) { // we can't log anymore than this goto finalise; } } // log statically alocated memory int32_t bss_size = ((uint32_t)&__bss_end__) - ((uint32_t)&__bss_base__); int32_t available_space = stm32_crash_dump_max_size() - total_dump_size; if (available_space < 0) { // we can't log anymore than this goto finalise; } if (bss_size > available_space) { // dump however much we can regions[curr_region].elementSize = CRASH_CATCHER_BYTE; regions[curr_region].startAddress = (uint32_t)&__bss_base__; regions[curr_region++].endAddress = (uint32_t)&__bss_base__ + available_space; total_dump_size += available_space; } else { // dump the entire bss regions[curr_region].elementSize = CRASH_CATCHER_BYTE; regions[curr_region].startAddress = (uint32_t)&__bss_base__; regions[curr_region++].endAddress = (uint32_t)&__bss_end__; total_dump_size += bss_size; } // dump the Heap as well as much as we can int32_t heap_size = ((uint32_t)&__heap_end__) - ((uint32_t)&__heap_base__); available_space = stm32_crash_dump_max_size() - total_dump_size; if (available_space < 0) { // we can't log anymore than this goto finalise; } if (heap_size > available_space) { // dump however much we can regions[curr_region].elementSize = CRASH_CATCHER_BYTE; regions[curr_region].startAddress = (uint32_t)&__heap_base__; regions[curr_region++].endAddress = (uint32_t)&__heap_base__ + available_space; total_dump_size += available_space; } else { // dump the entire heap regions[curr_region].elementSize = CRASH_CATCHER_BYTE; regions[curr_region].startAddress = (uint32_t)&__heap_base__; regions[curr_region++].endAddress = (uint32_t)&__heap_end__; total_dump_size += heap_size; } finalise: // ensure that last is filled with 0xFFFFFFFF if (curr_region < ARRAY_SIZE(regions)) { regions[curr_region].elementSize = CRASH_CATCHER_BYTE; regions[curr_region].startAddress = 0xFFFFFFFF; regions[curr_region].endAddress = 0xFFFFFFFF; } else { regions[ARRAY_SIZE(regions) - 1].elementSize = CRASH_CATCHER_BYTE; regions[ARRAY_SIZE(regions) - 1].startAddress = 0xFFFFFFFF; regions[ARRAY_SIZE(regions) - 1].endAddress = 0xFFFFFFFF; } if (do_flash_crash_dump) { // smaller dump if on flash regions[0].startAddress = (uint32_t)dump_start_address; regions[0].endAddress = (uint32_t)dump_end_address; } return regions; } void CrashCatcher_DumpMemory(const void* pvMemory, CrashCatcherElementSizes elementSize, size_t elementCount) { (void)pvMemory; (void)elementSize; (void)elementCount; #if defined(HAL_CRASH_SERIAL_PORT) if (do_serial_crash_dump) { CrashCatcher_DumpMemoryHex(pvMemory, elementSize, elementCount); } #endif if (do_flash_crash_dump) { CrashCatcher_DumpMemoryFlash(pvMemory, elementSize, elementCount); } } void CrashCatcher_DumpStart(const CrashCatcherInfo* pInfo) { // Record the fault info for watchdog struct port_extctx* ctx = (struct port_extctx*)pInfo->sp; FaultType faultType = (FaultType)__get_IPSR(); save_fault_watchdog(__LINE__, faultType, pInfo->sp, ctx->lr_thd); #if defined(HAL_CRASH_SERIAL_PORT) if (do_serial_crash_dump) { CrashCatcher_DumpStartHex(pInfo); } #endif if (do_flash_crash_dump) { CrashCatcher_DumpStartFlash(pInfo); } } CrashCatcherReturnCodes CrashCatcher_DumpEnd(void) { #if defined(HAL_CRASH_SERIAL_PORT) if (do_serial_crash_dump) { return CrashCatcher_DumpEndHex(); } #endif if (do_flash_crash_dump) { return CrashCatcher_DumpEndFlash(); } do_flash_crash_dump = false; #if defined(HAL_CRASH_SERIAL_PORT) do_serial_crash_dump = true; #endif return CRASH_CATCHER_TRY_AGAIN; } // -------------- FlashDump Code -------------------- static void CrashCatcher_DumpStartFlash(const CrashCatcherInfo* pInfo) { // initialise for dumping void *sp = (void*)pInfo->sp; if (sp == NULL || !is_address_in_memory(sp)) { do_flash_crash_dump = false; return; } // let's set the memory range to be dumped if (get_addr_mem_region_start_addr(sp) + HAL_MAX_STACK_FRAME_SIZE > sp) { // only go until the start of the region dump_start_address = get_addr_mem_region_start_addr(sp); } else { // go back as far as we need to dump_start_address = sp - HAL_MAX_STACK_FRAME_SIZE; } if (get_addr_mem_region_end_addr(sp) < sp + HAL_PROCESS_STACK_SIZE) { // only go until the end of the region dump_end_address = get_addr_mem_region_end_addr(sp); } else { // go ahead as far as we need to dump_end_address = sp + HAL_PROCESS_STACK_SIZE; } dump_size = 0; buf_off = 0; // we expect crash dump flash page to already be empty if (!stm32_crash_dump_region_erased()) { // stuff is already there, maybe last dump // so just do nothing do_flash_crash_dump = false; return; } stm32_watchdog_pat(); // unlock flash page for write stm32_flash_keep_unlocked(true); } // only flushes if we have a full buffer static void flush_dump_buffer(void) { if (buf_off == sizeof(dump_buffer)) { uint32_t page_start = (uint32_t)stm32_crash_dump_addr(); // write dump buffer to flash stm32_flash_write(page_start + dump_size, dump_buffer, sizeof(dump_buffer)); dump_size += sizeof(dump_buffer); buf_off = 0; memset(dump_buffer, 0, sizeof(dump_buffer)); stm32_watchdog_pat(); } } // Does the requested Dump to Flash static void CrashCatcher_DumpMemoryFlash(const void* pvMemory, CrashCatcherElementSizes elementSize, size_t elementCount) { const uint8_t* pv = (const uint8_t*)pvMemory; size_t cnt = 0; while (cnt < elementCount) { if (dump_size + buf_off + sizeof(dump_size) >= stm32_crash_dump_max_size()) { // when this happens, buf_off will be sizeof(buffer)-sizeof(dump_size) // 0xFF will be used to detect that we were in the middle of taking a dump memset(&dump_buffer[sizeof(dump_buffer)-sizeof(dump_size)], 0xFF, sizeof(dump_size)); buf_off = sizeof(dump_buffer); return; } flush_dump_buffer(); switch (elementSize) { case CRASH_CATCHER_BYTE: dump_buffer[buf_off++] = pv[cnt++]; break; case CRASH_CATCHER_HALFWORD: // we need to split the half word into two parts dump_buffer[buf_off++] = (uint16_t)*pv & 0xFF; flush_dump_buffer(); dump_buffer[buf_off++] = ((uint16_t)*pv >> 8) & 0xFF; cnt++; break; case CRASH_CATCHER_WORD: // we need to split the word and then write for (size_t i = 0; i < sizeof(uint32_t); i++) { dump_buffer[buf_off++] = ((uint32_t)*pv >> (8*i)) & 0xFF; flush_dump_buffer(); } cnt++; break; } } } static CrashCatcherReturnCodes CrashCatcher_DumpEndFlash(void) { // flush the buffer if (dump_size + buf_off + sizeof(dump_size) >= stm32_crash_dump_max_size()) { // when this happens, buf_off will be sizeof(buffer)-sizeof(dump_size) // 0xFF will be used to detect that we were in the middle of taking a dump memset(&dump_buffer[sizeof(dump_buffer)-sizeof(dump_size)], 0xFF, sizeof(dump_size)); buf_off = sizeof(dump_buffer); } if (buf_off > 0) { if (dump_size + sizeof(dump_buffer) >= stm32_crash_dump_max_size() && buf_off < sizeof(dump_buffer)) { // we have a partially full buffer towards the end, so we need to write the buffer // and then write the size of the buffer memcpy(&dump_buffer[sizeof(dump_buffer)-sizeof(dump_size)], &dump_size, sizeof(dump_size)); buf_off = 32; } stm32_flash_write(stm32_crash_dump_addr() + dump_size, dump_buffer, 32); dump_size += buf_off; buf_off = 0; memset(dump_buffer, 0, sizeof(dump_buffer)); stm32_watchdog_pat(); } // write size to buffer if not already written if (dump_size < stm32_crash_dump_max_size()) { memcpy(&dump_buffer[sizeof(dump_buffer)-sizeof(dump_size)], &dump_size, sizeof(dump_size)); stm32_flash_write(stm32_crash_dump_addr() + stm32_crash_dump_max_size() - sizeof(dump_buffer), dump_buffer, sizeof(dump_buffer)); stm32_watchdog_pat(); } stm32_flash_keep_unlocked(false); // How big of a dump did we take, record that at the end of flash sector if (g_crashCatcherDumpEndReturn == CRASH_CATCHER_TRY_AGAIN && g_info.isBKPT) return CRASH_CATCHER_EXIT; else return g_crashCatcherDumpEndReturn; } // -------------- HexDump Code -------------------- /* Copyright (C) 2018 Adam Green (https://github.com/adamgreen) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #if defined(HAL_CRASH_SERIAL_PORT) static void CrashCatcher_DumpStartHex(const CrashCatcherInfo* pInfo) { g_info = *pInfo; printString("\r\n\r\n"); if (pInfo->isBKPT) printString("BREAKPOINT"); else printString("CRASH"); printString(" ENCOUNTERED\r\n" "Enable logging and then press any key to start dump.\r\n"); waitForUserInput(); printString("\r\n"); } static void printString(const char* pString) { while (*pString) CrashCatcher_putc(*pString++); } static void waitForUserInput(void) { CrashCatcher_getc(); } static void CrashCatcher_DumpMemoryHex(const void* pvMemory, CrashCatcherElementSizes elementSize, size_t elementCount) { switch (elementSize) { case CRASH_CATCHER_BYTE: dumpBytes(pvMemory, elementCount); break; case CRASH_CATCHER_HALFWORD: dumpHalfwords(pvMemory, elementCount); break; case CRASH_CATCHER_WORD: dumpWords(pvMemory, elementCount); break; } printString("\r\n"); } static void dumpBytes(const uint8_t* pMemory, size_t elementCount) { size_t i; for (i = 0 ; i < elementCount ; i++) { /* Only dump 16 bytes to a single line before introducing a line break. */ if (i != 0 && (i & 0xF) == 0) printString("\r\n"); dumpByteAsHex(*pMemory++); } } static void dumpByteAsHex(uint8_t byte) { dumpHexDigit(byte >> 4); dumpHexDigit(byte & 0xF); } static void dumpHexDigit(uint8_t nibble) { static const char hexToASCII[] = "0123456789ABCDEF"; // assert( nibble < 16 ); CrashCatcher_putc(hexToASCII[nibble]); } static void dumpHalfwords(const uint16_t* pMemory, size_t elementCount) { size_t i; for (i = 0 ; i < elementCount ; i++) { uint16_t val = *pMemory++; /* Only dump 8 halfwords to a single line before introducing a line break. */ if (i != 0 && (i & 0x7) == 0) printString("\r\n"); dumpBytes((uint8_t*)&val, sizeof(val)); } } static void dumpWords(const uint32_t* pMemory, size_t elementCount) { size_t i; for (i = 0 ; i < elementCount ; i++) { uint32_t val = *pMemory++; /* Only dump 4 words to a single line before introducing a line break. */ if (i != 0 && (i & 0x3) == 0) printString("\r\n"); dumpBytes((uint8_t*)&val, sizeof(val)); } } static CrashCatcherReturnCodes CrashCatcher_DumpEndHex(void) { printString("\r\nEnd of dump\r\n"); if (g_crashCatcherDumpEndReturn == CRASH_CATCHER_TRY_AGAIN && g_info.isBKPT) return CRASH_CATCHER_EXIT; else return g_crashCatcherDumpEndReturn; } /* initialise serial ports */ static void init_uarts(void) { USART_TypeDef *u = HAL_CRASH_SERIAL_PORT; IRQ_DISABLE_HAL_CRASH_SERIAL_PORT(); RCC_RESET_HAL_CRASH_SERIAL_PORT(); /* Baud rate setting.*/ uint32_t fck; #if defined(STM32F7) || defined(STM32H7) || defined(STM32F3) || defined(STM32G4) || defined(STM32L4) fck = (uint32_t)(((HAL_CRASH_SERIAL_PORT_CLOCK + ((HAL_CRASH_SERIAL_PORT_BAUD)/2)) / HAL_CRASH_SERIAL_PORT_BAUD)); #else #if STM32_HAS_USART6 if ((u == USART1) || (u == USART6)) #else if (u == USART1) #endif fck = (STM32_PCLK2+((HAL_CRASH_SERIAL_PORT_BAUD)/2)) / HAL_CRASH_SERIAL_PORT_BAUD; else fck = (STM32_PCLK1+((HAL_CRASH_SERIAL_PORT_BAUD)/2)) / HAL_CRASH_SERIAL_PORT_BAUD; #endif //defined(STM32F7) || defined(STM32H7) || defined(STM32F3) || defined(STM32G4) || defined(STM32L4) u->BRR = fck; #if defined(STM32F7) || defined(STM32H7) || defined(STM32F3) || defined(STM32G4) || defined(STM32L4) /* Resetting eventual pending status flags.*/ u->ICR = 0xFFFFFFFFU; #else u->SR = 0; (void)u->SR; /* SR reset step 1.*/ (void)u->DR; /* SR reset step 2.*/ #endif u->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; uart_initialised = true; } int CrashCatcher_getc(void); int CrashCatcher_getc(void) { if (!uart_initialised) { init_uarts(); } USART_TypeDef *u = HAL_CRASH_SERIAL_PORT; // wait for a follwing string, only then do we start dumping static const char* wait_for_string = "dump_crash_log"; uint8_t curr_off = 0; while (true) { #if defined(STM32F7) || defined(STM32H7) || defined(STM32F3) || defined(STM32G4) || defined(STM32L4) while (!(USART_ISR_RXNE & u->ISR)) {} uint8_t c = u->RDR; #else while (!(USART_SR_RXNE & u->SR)) {} uint8_t c = u->DR; #endif if (c == wait_for_string[curr_off]) { curr_off++; if (curr_off == strlen(wait_for_string)) { return 0; } } else { curr_off = 0; } } return -1; } void CrashCatcher_putc(int c); void CrashCatcher_putc(int c) { if (!uart_initialised) { init_uarts(); } USART_TypeDef *u = HAL_CRASH_SERIAL_PORT; #if defined(STM32F7) || defined(STM32H7) || defined(STM32F3) || defined(STM32G4) || defined(STM32L4) u->TDR = c & 0xFF; #else u->DR = c & 0xFF; #endif #if defined(STM32F7) || defined(STM32H7) || defined(STM32F3) || defined(STM32G4) || defined(STM32L4) while (!(USART_ISR_TC & u->ISR)) { #else while (!(USART_SR_TC & u->SR)) { #endif // keep alive while dump is happening stm32_watchdog_pat(); } } #endif // #if defined(HAL_CRASH_SERIAL_PORT)