1 /*
2  * Percepio DFM v2.1.0
3  * Copyright 2023 Percepio AB
4  * www.percepio.com
5  *
6  * SPDX-License-Identifier: Apache-2.0
7  *
8  * DFM Crash Catcher integration
9  */
10 
11 #include <CrashCatcher.h>
12 #include <string.h>
13 #include <dfm.h>
14 #include <dfmCrashCatcher.h>
15 #include <CrashCatcherPriv.h>
16 #include <dfmKernelPort.h>
17 
18 #if ((DFM_CFG_ENABLED) >= 1)
19 
20 #if ((DFM_CFG_CRASH_ADD_TRACE) >= 1)
21 #include <trcRecorder.h>
22 static void prvAddTracePayload(void);
23 #endif
24 
25 /* See https://developer.arm.com/documentation/dui0552/a/cortex-m3-peripherals/system-control-block/configurable-fault-status-register*/
26 #define ARM_CORTEX_M_CFSR_REGISTER *(uint32_t*)0xE000ED28
27 
28 static DfmAlertHandle_t xAlertHandle = 0;
29 
30 dfmTrapInfo_t dfmTrapInfo = {-1, NULL, NULL, -1};
31 
32 #if ((DFM_CFG_CRASH_ADD_TRACE) >= 1)
33 static TraceStringHandle_t TzUserEventChannel = NULL;
34 #endif
35 
36 // TODO: Better to use a random number here, so it is harder to spoof?
37 void *__stack_chk_guard = (void *)0xdeadbeef;
38 
39 static uint8_t* ucBufferPos;
40 static uint8_t ucDataBuffer[CRASH_DUMP_BUFFER_SIZE] __attribute__ ((aligned (8)));
41 
42 static void dumpHalfWords(const uint16_t* pMemory, size_t elementCount);
43 static void dumpWords(const uint32_t* pMemory, size_t elementCount);
44 
45 uint32_t stackPointer = 0;
46 
47 /* Used for snprintf calls */
48 char cDfmPrintBuffer[128];
49 
prvGetFileNameFromPath(char * szPath)50 static char* prvGetFileNameFromPath(char* szPath)
51 {
52 	return strrchr(szPath, '/')+1; /* +1 to skip the last '/' character */
53 }
54 
prvCalculateChecksum(char * ptr,size_t maxlen)55 static uint32_t prvCalculateChecksum(char *ptr, size_t maxlen)
56 {
57 	uint32_t chksum = 0;
58 	size_t i = 0;
59 
60 	if (ptr == NULL)
61 	{
62 		return 0;
63 	}
64 
65 	while ((ptr[i] != (char)0) && (i < maxlen))
66 	{
67 		chksum += (uint32_t)ptr[i];
68 		i++;
69 	}
70 
71 	return chksum;
72 }
73 
CrashCatcher_GetMemoryRegions(void)74 const CrashCatcherMemoryRegion* CrashCatcher_GetMemoryRegions(void)
75 {
76 	static CrashCatcherMemoryRegion regions[] = {
77 			{0xFFFFFFFF, 0xFFFFFFFF, CRASH_CATCHER_BYTE},
78 			{CRASH_MEM_REGION1_START, CRASH_MEM_REGION1_START + CRASH_MEM_REGION1_SIZE, CRASH_CATCHER_BYTE},
79 			{CRASH_MEM_REGION2_START, CRASH_MEM_REGION2_START + CRASH_MEM_REGION2_SIZE, CRASH_CATCHER_BYTE},
80 			{CRASH_MEM_REGION3_START, CRASH_MEM_REGION3_START + CRASH_MEM_REGION3_SIZE, CRASH_CATCHER_BYTE}
81 	};
82 
83 	/* Region 0 is reserved, always relative to the current stack pointer */
84 	regions[0].startAddress = stackPointer;
85 	regions[0].endAddress = stackPointer + CRASH_STACK_CAPTURE_SIZE;
86 
87 	return regions;
88 }
89 
CrashCatcher_DumpStart(const CrashCatcherInfo * pInfo)90 void CrashCatcher_DumpStart(const CrashCatcherInfo* pInfo)
91 {
92 	int alerttype;
93 	char* szFileName = (void*)0;
94 	char* szCurrentTaskName = (void*)0;
95 
96 	stackPointer = pInfo->sp;
97 
98 #if ((DFM_CFG_CRASH_ADD_TRACE) >= 1)
99 	if (TzUserEventChannel == 0)
100 	{
101 		xTraceStringRegister("ALERT", &TzUserEventChannel);
102 	}
103 #endif
104 
105 	ucBufferPos = &ucDataBuffer[0];
106 
107 	DFM_DEBUG_PRINT("\nDFM Alert\n");
108 
109 	if (dfmTrapInfo.alertType >= 0)
110 	{
111 		/* On the DFM_TRAP macro.
112 		 * This sets dfmTrapInfo and then generates an NMI exception to trigger this error handler.
113 		 * dfmTrapInfo.message = "Assert failed" or similar.
114 		 * dfmTrapInfo.file = __FILE__ (full path, extract the filename from this!)
115 		 * dfmTrapInfo.line = __LINE__ (integer)
116 		 * */
117 		szFileName = prvGetFileNameFromPath(dfmTrapInfo.file);
118 		snprintf(cDfmPrintBuffer, sizeof(cDfmPrintBuffer), "%s at %s:%u", dfmTrapInfo.message, szFileName, dfmTrapInfo.line);
119 
120 		DFM_DEBUG_PRINT("  DFM_TRAP(): ");
121 		DFM_DEBUG_PRINT(cDfmPrintBuffer);
122 		DFM_DEBUG_PRINT("\n");
123 
124 		alerttype = dfmTrapInfo.alertType;
125 	}
126 	else
127 	{
128 		DFM_DEBUG_PRINT("  DFM: Hard fault\n");
129 
130 		snprintf(cDfmPrintBuffer, sizeof(cDfmPrintBuffer), "Hard fault exception (CFSR reg: 0x%08X)", (unsigned int)ARM_CORTEX_M_CFSR_REGISTER);
131 
132 		alerttype = DFM_TYPE_HARDFAULT;
133 	}
134 
135 	if (xDfmAlertBegin(alerttype, cDfmPrintBuffer, &xAlertHandle) == DFM_SUCCESS)
136 	{
137 		(void)xDfmKernelPortGetCurrentTaskName(&szCurrentTaskName);
138 
139 		xDfmAlertAddSymptom(xAlertHandle, DFM_SYMPTOM_CURRENT_TASK, prvCalculateChecksum(szCurrentTaskName, 32));
140 		xDfmAlertAddSymptom(xAlertHandle, DFM_SYMPTOM_STACKPTR, pInfo->sp);
141 
142 		if (dfmTrapInfo.alertType >= 0)
143 		{
144 			/* On DFM_TRAP */
145 			xDfmAlertAddSymptom(xAlertHandle, DFM_SYMPTOM_FILE, prvCalculateChecksum(szFileName, 32));
146 			xDfmAlertAddSymptom(xAlertHandle, DFM_SYMPTOM_LINE, dfmTrapInfo.line);
147 		}
148 		else
149 		{
150 			/* On hard faults */
151 			xDfmAlertAddSymptom(xAlertHandle, DFM_SYMPTOM_ARM_SCB_FCSR, ARM_CORTEX_M_CFSR_REGISTER);
152 
153 			/******************************************************************
154 			 * TODO: Add MMAR and BFAR regs here as symptoms to get the address
155 			 * of the problematic instruction. Would be a good symptom.
156 			 *****************************************************************/
157 		}
158 
159 #if ((DFM_CFG_CRASH_ADD_TRACE) >= 1)
160 		xTracePrint(TzUserEventChannel, cDfmPrintBuffer);
161 		prvAddTracePayload();
162 #endif
163 		DFM_DEBUG_PRINT("  DFM: Storing the alert.\n");
164 	}
165 	else
166 	{
167 		DFM_DEBUG_PRINT("  DFM: Not yet initialized. Alert ignored.\n"); // Always log this!
168 	}
169 	DFM_DEBUG_PRINT("\n");
170 
171 	dfmTrapInfo.alertType = -1;
172 	dfmTrapInfo.message = NULL;
173 }
174 
175 #if ((DFM_CFG_CRASH_ADD_TRACE) >= 1)
prvAddTracePayload(void)176 static void prvAddTracePayload(void)
177 {
178 	char* szName;
179 	void* pvBuffer = (void*)0;
180 	uint32_t ulBufferSize = 0;
181 	if (TRC_CFG_RECORDER_MODE == TRC_RECORDER_MODE_STREAMING)
182 	{
183 		szName = "dfm_trace.psfs";
184 	}
185 	else if (TRC_CFG_RECORDER_MODE == TRC_RECORDER_MODE_SNAPSHOT)
186 	{
187 		szName = "dfm_trace.trc";
188 	}
189 
190 	if (xTraceIsRecorderEnabled() == 1)
191 	{
192 		xTraceDisable();
193 	}
194 	xTraceGetEventBuffer(&pvBuffer, &ulBufferSize);
195 	xDfmAlertAddPayload(xAlertHandle, pvBuffer, ulBufferSize, szName);
196 }
197 #endif
198 
CrashCatcher_DumpMemory(const void * pvMemory,CrashCatcherElementSizes elementSize,size_t elementCount)199 void CrashCatcher_DumpMemory(const void* pvMemory, CrashCatcherElementSizes elementSize, size_t elementCount)
200 {
201 	int32_t current_usage = (uint32_t)ucBufferPos - (uint32_t)ucDataBuffer;
202 
203 	/* This function is called when CrashCatcher detects an internal stack overflow (it has a separate stack) */
204 	if (g_crashCatcherStack[0] != CRASH_CATCHER_STACK_SENTINEL)
205 	{
206 		/* Always try to print this error. But it might actually not print since the memory has been corrupted. */
207 		DFM_ERROR_PRINT("DFM: ERROR, stack overflow in CrashCatcher, see comment in dfmCrashCatcher.c\n");
208 
209 		/**********************************************************************************************************
210 
211 		If you get here, there has been a stack overflow on the CrashCatcher stack.
212 		This is separate from the main stack and defined in CrashCatcher.c (g_crashCatcherStack).
213 
214 		This error might happen because of diagnostic prints and other function calls while saving the alert.
215 		You may increase the stack size in CrashCatcherPriv.h or turn off the logging (DFM_CFG_USE_DEBUG_LOGGING).
216 
217 		***********************************************************************************************************/
218 
219 		vDfmDisableInterrupts();
220 		for (;;); // Stop here...
221 	}
222 
223 	if (elementCount == 0)
224 	{
225 		/* May happen if CRASH_MEM_REGION<X>_SIZE is set to 0 by mistake (e.g. if using 0 instead of 0xFFFFFFFF for CRASH_MEM_REGION<X>_START on unused slots. */
226 		DFM_ERROR_PRINT("DFM: Warning, memory region size is zero!\n");
227 		return;
228 	}
229 
230 	switch (elementSize)
231 	{
232 
233 		case CRASH_CATCHER_BYTE:
234 
235 			if ( current_usage + elementCount >= CRASH_DUMP_BUFFER_SIZE)
236 			{
237 				DFM_ERROR_PRINT("\nDFM: Error, ucDataBuffer not large enough!\n\n");
238 				return;
239 			}
240 
241 			memcpy((void*)ucBufferPos, pvMemory, elementCount);
242 			ucBufferPos += elementCount;
243 			break;
244 
245 		case CRASH_CATCHER_HALFWORD:
246 
247 			if ( current_usage + elementCount*2 >= CRASH_DUMP_BUFFER_SIZE)
248 			{
249 				DFM_ERROR_PRINT("\nDFM: Error, ucDataBuffer not large enough!\n\n");
250 				return;
251 			}
252 			dumpHalfWords(pvMemory, elementCount);
253 
254 			break;
255 
256 		case CRASH_CATCHER_WORD:
257 
258 			if ( current_usage + elementCount*4 >= CRASH_DUMP_BUFFER_SIZE)
259 			{
260 				DFM_ERROR_PRINT("\nDFM: Error, ucDataBuffer not large enough!\n\n");
261 				return;
262 			}
263 
264 			dumpWords(pvMemory, elementCount);
265 
266 			break;
267 
268 		default:
269 			DFM_ERROR_PRINT("\nDFM: Error, unhandled case!\n\n");
270 			break;
271 	}
272 }
273 
dumpHalfWords(const uint16_t * pMemory,size_t elementCount)274 static void dumpHalfWords(const uint16_t* pMemory, size_t elementCount)
275 {
276 	size_t i;
277 	for (i = 0 ; i < elementCount ; i++)
278 	{
279 		uint16_t val = *pMemory++;
280 		memcpy((void*)ucBufferPos, &val, sizeof(val));
281 		ucBufferPos += sizeof(val);
282 	}
283 }
284 
dumpWords(const uint32_t * pMemory,size_t elementCount)285 static void dumpWords(const uint32_t* pMemory, size_t elementCount)
286 {
287 	size_t i;
288 	for (i = 0 ; i < elementCount ; i++)
289 	{
290 		uint32_t val = *pMemory++;
291 		memcpy((void*)ucBufferPos, &val, sizeof(val));
292 		ucBufferPos += sizeof(val);
293 	}
294 }
295 
CrashCatcher_DumpEnd(void)296 CrashCatcherReturnCodes CrashCatcher_DumpEnd(void)
297 {
298 	if (xAlertHandle != 0)
299 	{
300 		uint32_t size = (uint32_t)ucBufferPos - (uint32_t)ucDataBuffer;
301 		if (xDfmAlertAddPayload(xAlertHandle, ucDataBuffer, size, CRASH_DUMP_NAME) == DFM_SUCCESS)
302 		{
303 			DFM_DEBUG_PRINT("  DFM: Crash dump stored.\n");
304 		}
305 		else
306 		{
307 			DFM_ERROR_PRINT("  DFM: Failed storing crash dump.\n");
308 		}
309 
310 #ifdef DFM_CLOUD_PORT_ALWAYS_ATTEMPT_TRANSFER
311 		/* The cloud port has indicated it is always OK to attempt to transfer */
312 		if (xDfmAlertEnd(xAlertHandle) == DFM_SUCCESS)
313 #else
314 		/* Cloud port transfer cannot be trusted, so we only attempt to store it */
315 		if (xDfmAlertEndOffline(xAlertHandle) == DFM_SUCCESS)
316 #endif
317 		{
318 			DFM_DEBUG_PRINT("  DFM: Alert stored OK.\n");
319 		}
320 		else
321 		{
322 			DFM_ERROR_PRINT("  DFM: Failed storing alert.\n");
323 		}
324 
325 	}
326 
327 	CRASH_FINALIZE();
328 	return CRASH_CATCHER_EXIT;
329 }
330 
331 
332 /* Called by gcc stack-checking code when using the gcc option -fstack-protector-strong */
__stack_chk_fail(void)333 void __stack_chk_fail(void)
334 {
335 	// If this happens, the stack has been corrupted by the previous function in the call stack.
336 	// Note that the exact location of the stack corruption is not known, since detected when exiting the function.
337 	DFM_TRAP(DFM_TYPE_STACK_CHK_FAILED, "Stack corruption detected");
338 }
339 
340 #endif
341