Skip to content

Commit c541772

Browse files
committed
Improve vCont multi-threading support
Updates MRI core to version 1.1-20200506-0. Added Platform_RtosSetThreadState() API to allow GDB to freeze threads other than the one currently being single stepped via the "set scheduler-locking step" command. The MRI core will only use this new function if Platform_RtosIsSetThreadStateSupported() return a non-zero value. Otherwise single stepping will silently ignore an attempt to single step any thread other than the one which caused the current halt and whether other threads are resumed while single stepping is determined by the port implementor (usually they would be resumed as that is the default behaviour expected by GDB). I moved alot of the thread tracking functionality of ThreadDebug into the newly created ThreadList structure. It tracks the thread ids of the active threads, their original priorities before being lowered to osPriorityIdle to halt them during debugging, and their frozen/thawed/ single-stepping state. It ping pongs between g_pCurrThreadList and g_pPrevThreadList so that it can reference the previously known priority levels as the current priorities might still be those set by the debugger if GDB requested that they be left frozen while single stepping another thread. I moved the call to restoreRtxHandlers() down into the DebugMon handler before it attempts to awaken mriMain() rather than doing it in mriMain() just after it awakens. This fixed an infinite loop that could occur during certain single stepping operations: * Single step debug event doesn't occur because that particular thread isn't scheduled by the RTOS. * CTRL+C is pressed by the user to force a break. * Communication channel see the CTRL+C and pends a DebugMon interrupt. * DebugMon interrupt attempts to wake up mriMain() by signalling the MRI_THREAD_DEBUG_EVENT_FLAG flag. This will cause RTX to PendSV. * When DebugMon handler returns, PendSV handler will execute in an attempt to switch the current context over to mriMain thread. This handler is hooked though for single stepping by ThreadDebug which means that it will pend a DebugMon interrupt again and so enters the infinite loop. With my new fix, the PendSv hook is removed before DebugMon returns so that the PendSv handler can run without pending yet another interrupt into DebugMon. The mriThreadSingleStepThreadId global is now set by Platform_RtosSetThreadState() when the MRI core sets the state of a thread to MRI_PLATFORM_THREAD_SINGLE_STEPPING. This allows GDB to single step a thread other than the one which originally caused the halt. I updated Platform_RtosGetExtraThreadInfo() and Platform_RtosGetThreadContext() in ThreadDebug to return NULL if the specified thread-id isn't in the current g_pCurrThreadList. This fixes a memory fault that was being generated by ThreadDebug when GDB would first connect. When GDB first connects its sends a Hg0 command which resulted in a call to Platform_RtosGetThreadContext() with a thread-id of 0. The previous code would start trying to build up a register context based off of this NULL pointer which was a bad thing. I updated mriDebugException() to replay the calls to Platform_RtosSetThreadState() before it makes an early exit for situations like PC still being in the specified range during a ranged single step operation. This allows ThreadDebug to make sure that its g_pCurrThreadList object is always updated to the correct state before it allows execution of the debuggee to continue. I modified the behaviour of ranged single step to only skip the first hardcoded breakpoint in the range whereas before it would skip all breakpoints that were found within the range. Now returns an error if GDB specifies more than one single step or more than one continue action in a single vCont command. This assumption makes the implementation of the vCont command much simpler and matches the behaviour that I have seen from GDB so far.
1 parent 72cc7d4 commit c541772

File tree

9 files changed

+399
-114
lines changed

9 files changed

+399
-114
lines changed

libraries/KernelDebug/src/KernelDebug.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,3 +603,12 @@ int Platform_RtosIsThreadActive(uint32_t threadId)
603603

604604
return 0;
605605
}
606+
607+
int Platform_RtosIsSetThreadStateSupported(void)
608+
{
609+
return 0;
610+
}
611+
612+
void Platform_RtosSetThreadState(uint32_t threadId, PlatformThreadState state)
613+
{
614+
}

libraries/MRI/src/core/cmd_continue.c

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,17 @@
2121
#include <core/cmd_continue.h>
2222

2323

24-
static uint32_t skipHardcodedBreakpoint(void);
2524
static int shouldSkipHardcodedBreakpoint(void);
2625
static int isCurrentInstructionHardcodedBreakpoint(void);
2726
uint32_t ContinueExecution(int setPC, uint32_t newPC)
2827
{
29-
uint32_t returnValue = skipHardcodedBreakpoint();
28+
uint32_t returnValue = SkipHardcodedBreakpoint();
3029
if (setPC)
3130
Platform_SetProgramCounter(newPC);
3231
return (returnValue | HANDLER_RETURN_RESUME_PROGRAM | HANDLER_RETURN_RETURN_IMMEDIATELY);
3332
}
3433

35-
static uint32_t skipHardcodedBreakpoint(void)
34+
uint32_t SkipHardcodedBreakpoint(void)
3635
{
3736
if (shouldSkipHardcodedBreakpoint())
3837
{
@@ -125,7 +124,7 @@ uint32_t HandleContinueWithSignalCommand(void)
125124
*/
126125
uint32_t HandleDetachCommand(void)
127126
{
128-
skipHardcodedBreakpoint();
127+
SkipHardcodedBreakpoint();
129128
PrepareStringResponse("OK");
130129
return HANDLER_RETURN_RESUME_PROGRAM;
131130
}

libraries/MRI/src/core/cmd_continue.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@
2020

2121
/* Real name of functions are in mri namespace. */
2222
uint32_t mriCmd_ContinueExecution(int setPC, uint32_t newPC);
23+
uint32_t mriCmd_SkipHardcodedBreakpoint(void);
2324
uint32_t mriCmd_HandleContinueCommand(void);
2425
uint32_t mriCmd_HandleContinueWithSignalCommand(void);
2526
uint32_t mriCmd_HandleDetachCommand(void);
2627

2728
/* Macroes which allow code to drop the mri namespace prefix. */
2829
#define ContinueExecution mriCmd_ContinueExecution
30+
#define SkipHardcodedBreakpoint mriCmd_SkipHardcodedBreakpoint
2931
#define HandleContinueCommand mriCmd_HandleContinueCommand
3032
#define HandleContinueWithSignalCommand mriCmd_HandleContinueWithSignalCommand
3133
#define HandleDetachCommand mriCmd_HandleDetachCommand

libraries/MRI/src/core/cmd_vcont.c

Lines changed: 138 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <core/mri.h>
2424
#include <core/platforms.h>
2525
#include <core/try_catch.h>
26+
#include <string.h>
2627

2728

2829
typedef enum
@@ -54,6 +55,12 @@ typedef struct
5455
} Action;
5556

5657

58+
/* Store last continue/step actions here so that they can be used to replay Platform_RtosSetThreadState() calls
59+
during ranged single steps. */
60+
static Action g_lastContinueAction;
61+
static Action g_lastStepAction;
62+
63+
5764
static uint32_t handleVContQueryCommand(void);
5865
static uint32_t handleVContCommand(void);
5966
static Action parseAction(Buffer* pBuffer);
@@ -64,8 +71,11 @@ static Action parseSingleStepWithSignalAction(Buffer* pBuffer);
6471
static Action parseRangedSingleStepAction(Buffer* pBuffer);
6572
static AddressRange parseAddressRange(Buffer* pBuffer);
6673
static ThreadId parseOptionalThreadId(Buffer* pBuffer);
74+
static int doesThreadIdMatchHaltedThreadId(const Action* pAction);
75+
static uint32_t handleSingleStepAndContinueCommands(const Action* pContinueAction, const Action* pStepAction);
6776
static uint32_t handleAction(const Action* pAction);
68-
static uint32_t handleRangedSingleStep(const Action* pAction);
77+
static uint32_t handleSingleStepAndContinueCommandsWithSetThreadState(const Action* pContinueAction, const Action* pStepAction);
78+
static uint32_t handleActionWithSetThreadState(const Action* pAction);
6979
static uint32_t justAdvancedPastBreakpoint(uint32_t continueReturn);
7080
/* Handle the extended 'v' commands used by gdb.
7181
@@ -116,9 +126,8 @@ static uint32_t handleVContQueryCommand(void)
116126
*/
117127
static uint32_t handleVContCommand(void)
118128
{
119-
uint32_t returnValue = 0;
120-
int useDefault = 1;
121-
Action defaultAction = { {0, THREAD_ID_NONE}, {0, 0}, ACTION_NONE };
129+
Action continueAction = { {0, THREAD_ID_NONE}, {0, 0}, ACTION_NONE };
130+
Action stepAction = { {0, THREAD_ID_NONE}, {0, 0}, ACTION_NONE };
122131
Buffer* pBuffer = GetBuffer();
123132
Action action;
124133

@@ -134,14 +143,29 @@ static uint32_t handleVContCommand(void)
134143
return 0;
135144
}
136145

137-
if (action.threadId.type == THREAD_ID_NONE)
146+
switch (action.type)
138147
{
139-
defaultAction = action;
140-
}
141-
else
142-
{
143-
returnValue = handleAction(&action);
144-
useDefault = 0;
148+
case ACTION_RANGED_SINGLE_STEP:
149+
case ACTION_SINGLE_STEP:
150+
if (stepAction.type != ACTION_NONE)
151+
{
152+
/* Should only have 1 single step action in vCont command. */
153+
PrepareStringResponse(MRI_ERROR_INVALID_ARGUMENT);
154+
return 0;
155+
}
156+
stepAction = action;
157+
break;
158+
case ACTION_CONTINUE:
159+
if (action.threadId.type == THREAD_ID_SPECIFIC || continueAction.type != ACTION_NONE)
160+
{
161+
/* Continue action shouldn't specify thread id & there should only be 1 of them in vCont command. */
162+
PrepareStringResponse(MRI_ERROR_INVALID_ARGUMENT);
163+
return 0;
164+
}
165+
continueAction = action;
166+
break;
167+
default:
168+
break;
145169
}
146170
}
147171
if (Buffer_BytesLeft(pBuffer) > 0)
@@ -150,9 +174,11 @@ static uint32_t handleVContCommand(void)
150174
PrepareStringResponse(MRI_ERROR_INVALID_ARGUMENT);
151175
return 0;
152176
}
153-
if (useDefault)
154-
returnValue = handleAction(&defaultAction);
155-
return returnValue;
177+
178+
if (Platform_RtosIsSetThreadStateSupported())
179+
return handleSingleStepAndContinueCommandsWithSetThreadState(&continueAction, &stepAction);
180+
else
181+
return handleSingleStepAndContinueCommands(&continueAction, &stepAction);
156182
}
157183

158184
static Action parseAction(Buffer* pBuffer)
@@ -284,57 +310,134 @@ static ThreadId parseOptionalThreadId(Buffer* pBuffer)
284310
return threadId;
285311
}
286312

313+
static int doesThreadIdMatchHaltedThreadId(const Action* pAction)
314+
{
315+
return pAction->threadId.type == THREAD_ID_SPECIFIC && pAction->threadId.id == Platform_RtosGetHaltedThreadId();
316+
}
317+
318+
static uint32_t handleSingleStepAndContinueCommands(const Action* pContinueAction, const Action* pStepAction)
319+
{
320+
uint32_t returnValue = 0;
321+
322+
if (pStepAction->type != ACTION_NONE)
323+
returnValue |= handleAction(pStepAction);
324+
else
325+
returnValue |= handleAction(pContinueAction);
326+
327+
return returnValue;
328+
}
329+
287330
static uint32_t handleAction(const Action* pAction)
288331
{
289332
const int noSetPC = 0;
290333
const uint32_t newPC = 0;
334+
uint32_t returnValue = 0;
291335

292-
if (pAction->threadId.type == THREAD_ID_SPECIFIC && pAction->threadId.id != Platform_RtosGetHaltedThreadId())
293-
{
294-
/* The only specific thread ID that can be handled is the halting thread's ID. */
295-
PrepareStringResponse(MRI_ERROR_INVALID_ARGUMENT);
296-
return 0;
297-
}
298-
336+
/* No matter what thread ID is specified, it will be treated as though it was the halting thread-id since that is
337+
all a stub running without Platform_RtosSetThreadState() can handle anyway. */
299338
switch (pAction->type)
300339
{
301340
case ACTION_CONTINUE:
302341
return ContinueExecution(noSetPC, newPC);
303342
case ACTION_SINGLE_STEP:
304343
return HandleSingleStepCommand();
305344
case ACTION_RANGED_SINGLE_STEP:
306-
return handleRangedSingleStep(pAction);
345+
returnValue = HandleSingleStepCommand();
346+
if (returnValue)
347+
SetSingleSteppingRange(&pAction->range);
348+
return returnValue;
307349
default:
308350
PrepareStringResponse(MRI_ERROR_INVALID_ARGUMENT);
309351
return 0;
310352
}
311353
}
312354

313-
static uint32_t handleRangedSingleStep(const Action* pAction)
355+
static uint32_t handleSingleStepAndContinueCommandsWithSetThreadState(const Action* pContinueAction, const Action* pStepAction)
314356
{
315-
const uint32_t noSetPC = 0;
316-
uint32_t returnValue = 0;
317-
uint32_t currPC;
357+
uint32_t returnValue = 0;
358+
uint32_t skippedHardcodedBreakpoint = 0;
359+
360+
if (pContinueAction->type != ACTION_NONE || doesThreadIdMatchHaltedThreadId(pStepAction))
361+
skippedHardcodedBreakpoint = SkipHardcodedBreakpoint();
362+
363+
if (pContinueAction->type != ACTION_NONE)
364+
returnValue |= handleActionWithSetThreadState(pContinueAction);
365+
366+
if (pStepAction->type != ACTION_NONE)
367+
{
368+
if (pStepAction->threadId.type != THREAD_ID_SPECIFIC)
369+
{
370+
PrepareStringResponse(MRI_ERROR_INVALID_ARGUMENT);
371+
return 0;
372+
}
373+
returnValue |= handleActionWithSetThreadState(pStepAction);
374+
}
318375

319-
do
376+
if (pContinueAction->type == ACTION_NONE && pStepAction->type == ACTION_NONE)
320377
{
321-
returnValue = ContinueExecution(noSetPC, 0);
322-
currPC = Platform_GetProgramCounter();
323-
} while (justAdvancedPastBreakpoint(returnValue) && currPC >= pAction->range.start && currPC < pAction->range.end);
378+
PrepareStringResponse(MRI_ERROR_INVALID_ARGUMENT);
379+
return 0;
380+
}
381+
382+
g_lastContinueAction = *pContinueAction;
383+
g_lastStepAction = *pStepAction;
324384

325-
if (justAdvancedPastBreakpoint(returnValue))
385+
if (doesThreadIdMatchHaltedThreadId(pStepAction) && justAdvancedPastBreakpoint(skippedHardcodedBreakpoint))
326386
{
327-
/* Treat the advance through the range over hardcoded breakpoints as the single step and don't
328-
resume execution but send T stop response instead. */
387+
/* Treat the advance over hardcoded breakpoints as the single step and don't resume execution but send T
388+
stop response instead. */
329389
return Send_T_StopResponse();
330390
}
331391

332-
Platform_EnableSingleStep();
333-
SetSingleSteppingRange(&pAction->range);
334392
return returnValue;
335393
}
336394

395+
static uint32_t handleActionWithSetThreadState(const Action* pAction)
396+
{
397+
uint32_t returnValue = 0;
398+
399+
switch (pAction->type)
400+
{
401+
case ACTION_CONTINUE:
402+
Platform_RtosSetThreadState(MRI_PLATFORM_ALL_THREADS, MRI_PLATFORM_THREAD_THAWED);
403+
returnValue = HANDLER_RETURN_RESUME_PROGRAM | HANDLER_RETURN_RETURN_IMMEDIATELY;
404+
break;
405+
case ACTION_SINGLE_STEP:
406+
Platform_RtosSetThreadState(pAction->threadId.id, MRI_PLATFORM_THREAD_SINGLE_STEPPING);
407+
Platform_EnableSingleStep();
408+
returnValue = HANDLER_RETURN_RESUME_PROGRAM | HANDLER_RETURN_RETURN_IMMEDIATELY;
409+
break;
410+
case ACTION_RANGED_SINGLE_STEP:
411+
Platform_RtosSetThreadState(pAction->threadId.id, MRI_PLATFORM_THREAD_SINGLE_STEPPING);
412+
Platform_EnableSingleStep();
413+
SetSingleSteppingRange(&pAction->range);
414+
returnValue = HANDLER_RETURN_RESUME_PROGRAM | HANDLER_RETURN_RETURN_IMMEDIATELY;
415+
break;
416+
default:
417+
break;
418+
}
419+
return returnValue;
420+
}
421+
337422
static uint32_t justAdvancedPastBreakpoint(uint32_t continueReturn)
338423
{
339424
return continueReturn & HANDLER_RETURN_SKIPPED_OVER_BREAK;
340425
}
426+
427+
428+
void ReplaySetThreadStateCalls(void)
429+
{
430+
if (!Platform_RtosIsSetThreadStateSupported())
431+
return;
432+
if (g_lastContinueAction.type != ACTION_NONE)
433+
Platform_RtosSetThreadState(MRI_PLATFORM_ALL_THREADS, MRI_PLATFORM_THREAD_THAWED);
434+
if (g_lastStepAction.type != ACTION_NONE)
435+
Platform_RtosSetThreadState(g_lastStepAction.threadId.id, MRI_PLATFORM_THREAD_SINGLE_STEPPING);
436+
}
437+
438+
439+
void ForgetSetThreadStateCalls(void)
440+
{
441+
memset(&g_lastContinueAction, 0, sizeof(g_lastContinueAction));
442+
memset(&g_lastStepAction, 0, sizeof(g_lastStepAction));
443+
}

libraries/MRI/src/core/cmd_vcont.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,12 @@
2020

2121
/* Real name of functions are in mri namespace. */
2222
uint32_t mriCmd_HandleVContCommands(void);
23+
void mriCmd_ReplaySetThreadStateCalls(void);
24+
void mriCmd_ForgetSetThreadStateCalls(void);
2325

2426
/* Macroes which allow code to drop the mri namespace prefix. */
25-
#define HandleVContCommands mriCmd_HandleVContCommands
27+
#define HandleVContCommands mriCmd_HandleVContCommands
28+
#define ReplaySetThreadStateCalls mriCmd_ReplaySetThreadStateCalls
29+
#define ForgetSetThreadStateCalls mriCmd_ForgetSetThreadStateCalls
2630

2731
#endif /* CMD_VCONT_H_ */

libraries/MRI/src/core/mri.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,10 @@ void mriDebugException(MriContext* pContext)
194194
{
195195
resumeExecution = pTempBreakpointCallback(pvTempBreakpointContext);
196196
if (resumeExecution)
197+
{
198+
ReplaySetThreadStateCalls();
197199
return;
200+
}
198201
}
199202
}
200203

@@ -206,6 +209,7 @@ void mriDebugException(MriContext* pContext)
206209
{
207210
Platform_DisableSingleStep();
208211
Platform_EnableSingleStep();
212+
ReplaySetThreadStateCalls();
209213
return;
210214
}
211215
}
@@ -220,6 +224,7 @@ void mriDebugException(MriContext* pContext)
220224
Semihost_HandleSemihostRequest() &&
221225
!justSingleStepped )
222226
{
227+
ReplaySetThreadStateCalls();
223228
prepareForDebuggerExit();
224229
return;
225230
}
@@ -228,6 +233,7 @@ void mriDebugException(MriContext* pContext)
228233
Platform_DisplayFaultCauseToGdbConsole();
229234
Send_T_StopResponse();
230235

236+
ForgetSetThreadStateCalls();
231237
GdbCommandHandlingLoop();
232238

233239
prepareForDebuggerExit();

0 commit comments

Comments
 (0)