e620d344e6994ac5b170c176bd6498a0f07d12a0
[mirror_edk2.git] / EmbeddedPkg / GdbStub / Arm / Processor.c
1 /** @file
2 Processor specific parts of the GDB stub
3
4 Copyright (c) 2008-2009, Apple Inc. All rights reserved.
5
6 All rights reserved. This program and the accompanying materials
7 are licensed and made available under the terms and conditions of the BSD License
8 which accompanies this distribution. The full text of the license may be found at
9 http://opensource.org/licenses/bsd-license.php
10
11 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
12 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
13
14 **/
15 /** @file
16
17 Copyright (c) 2008, Apple, Inc
18 All rights reserved. This program and the accompanying materials
19 are licensed and made available under the terms and conditions of the BSD License
20 which accompanies this distribution. The full text of the license may be found at
21 http://opensource.org/licenses/bsd-license.php
22
23 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
24 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
25
26 **/
27
28 #include <GdbStubInternal.h>
29 #include <Library/CacheMaintenanceLib.h>
30 #include <Library/PrintLib.h>
31
32 //
33 // Array of exception types that need to be hooked by the debugger
34 // (efi, gdb) //efi number
35 //
36 EFI_EXCEPTION_TYPE_ENTRY gExceptionType[] = {
37 { EXCEPT_ARM_SOFTWARE_INTERRUPT, GDB_SIGTRAP }
38 // { EXCEPT_ARM_UNDEFINED_INSTRUCTION, GDB_SIGTRAP },
39 // { EXCEPT_ARM_PREFETCH_ABORT, GDB_SIGTRAP },
40 // { EXCEPT_ARM_DATA_ABORT, GDB_SIGEMT },
41 // { EXCEPT_ARM_RESERVED, GDB_SIGILL }
42 };
43
44 // Shut up some annoying RVCT warnings
45 #ifdef __CC_ARM
46 #pragma diag_suppress 1296
47 #endif
48
49 UINTN gRegisterOffsets[] = {
50 OFFSET_OF(EFI_SYSTEM_CONTEXT_ARM, R0),
51 OFFSET_OF(EFI_SYSTEM_CONTEXT_ARM, R1),
52 OFFSET_OF(EFI_SYSTEM_CONTEXT_ARM, R2),
53 OFFSET_OF(EFI_SYSTEM_CONTEXT_ARM, R3),
54 OFFSET_OF(EFI_SYSTEM_CONTEXT_ARM, R4),
55 OFFSET_OF(EFI_SYSTEM_CONTEXT_ARM, R5),
56 OFFSET_OF(EFI_SYSTEM_CONTEXT_ARM, R6),
57 OFFSET_OF(EFI_SYSTEM_CONTEXT_ARM, R7),
58 OFFSET_OF(EFI_SYSTEM_CONTEXT_ARM, R8),
59 OFFSET_OF(EFI_SYSTEM_CONTEXT_ARM, R9),
60 OFFSET_OF(EFI_SYSTEM_CONTEXT_ARM, R10),
61 OFFSET_OF(EFI_SYSTEM_CONTEXT_ARM, R11),
62 OFFSET_OF(EFI_SYSTEM_CONTEXT_ARM, R12),
63 OFFSET_OF(EFI_SYSTEM_CONTEXT_ARM, SP),
64 OFFSET_OF(EFI_SYSTEM_CONTEXT_ARM, LR),
65 OFFSET_OF(EFI_SYSTEM_CONTEXT_ARM, PC),
66 0x00000F01, // f0
67 0x00000F02,
68 0x00000F03,
69 0x00000F11, // f1
70 0x00000F12,
71 0x00000F13,
72 0x00000F21, // f2
73 0x00000F22,
74 0x00000F23,
75 0x00000F31, // f3
76 0x00000F32,
77 0x00000F33,
78 0x00000F41, // f4
79 0x00000F42,
80 0x00000F43,
81 0x00000F51, // f5
82 0x00000F52,
83 0x00000F53,
84 0x00000F61, // f6
85 0x00000F62,
86 0x00000F63,
87 0x00000F71, // f7
88 0x00000F72,
89 0x00000F73,
90 0x00000FFF, // fps
91 0x00000FFF,
92 0x00000FFF,
93 OFFSET_OF(EFI_SYSTEM_CONTEXT_ARM, CPSR)
94 };
95
96 // restore warnings for RVCT
97 #ifdef __CC_ARM
98 #pragma diag_default 1296
99 #endif
100
101 /**
102 Return the number of entries in the gExceptionType[]
103
104 @retval UINTN, the number of entries in the gExceptionType[] array.
105 **/
106 UINTN
107 MaxEfiException (
108 VOID
109 )
110 {
111 return sizeof (gExceptionType)/sizeof (EFI_EXCEPTION_TYPE_ENTRY);
112 }
113
114
115 /**
116 Return the number of entries in the gRegisters[]
117
118 @retval UINTN, the number of entries (registers) in the gRegisters[] array.
119 **/
120 UINTN
121 MaxRegisterCount (
122 VOID
123 )
124 {
125 return sizeof (gRegisterOffsets)/sizeof (UINTN);
126 }
127
128
129 /**
130 Check to see if the ISA is supported.
131 ISA = Instruction Set Architecture
132
133 @retval TRUE if Isa is supported
134
135 **/
136 BOOLEAN
137 CheckIsa (
138 IN EFI_INSTRUCTION_SET_ARCHITECTURE Isa
139 )
140 {
141 if (Isa == IsaArm) {
142 return TRUE;
143 } else {
144 return FALSE;
145 }
146 }
147
148
149 /**
150 This takes in the register number and the System Context, and returns a pointer to the RegNumber-th register in gdb ordering
151 It is, by default, set to find the register pointer of the ARM member
152 @param SystemContext Register content at time of the exception
153 @param RegNumber The register to which we want to find a pointer
154 @retval the pointer to the RegNumber-th pointer
155 **/
156 UINTN *
157 FindPointerToRegister(
158 IN EFI_SYSTEM_CONTEXT SystemContext,
159 IN UINTN RegNumber
160 )
161 {
162 UINT8 *TempPtr;
163 ASSERT(gRegisterOffsets[RegNumber] < 0xF00);
164 TempPtr = ((UINT8 *)SystemContext.SystemContextArm) + gRegisterOffsets[RegNumber];
165 return (UINT32 *)TempPtr;
166 }
167
168
169 /**
170 Adds the RegNumber-th register's value to the output buffer, starting at the given OutBufPtr
171 @param SystemContext Register content at time of the exception
172 @param RegNumber the number of the register that we want to read
173 @param OutBufPtr pointer to the output buffer's end. the new data will be added from this point on.
174 @retval the pointer to the next character of the output buffer that is available to be written on.
175 **/
176 CHAR8 *
177 BasicReadRegister (
178 IN EFI_SYSTEM_CONTEXT SystemContext,
179 IN UINTN RegNumber,
180 IN CHAR8 *OutBufPtr
181 )
182 {
183 UINTN RegSize;
184 CHAR8 Char;
185
186 if (gRegisterOffsets[RegNumber] > 0xF00) {
187 AsciiSPrint(OutBufPtr, 9, "00000000");
188 OutBufPtr += 8;
189 return OutBufPtr;
190 }
191
192 RegSize = 0;
193 while (RegSize < 32) {
194 Char = mHexToStr[(UINT8)((*FindPointerToRegister(SystemContext, RegNumber) >> (RegSize+4)) & 0xf)];
195 if ((Char >= 'A') && (Char <= 'F')) {
196 Char = Char - 'A' + 'a';
197 }
198 *OutBufPtr++ = Char;
199
200 Char = mHexToStr[(UINT8)((*FindPointerToRegister(SystemContext, RegNumber) >> RegSize) & 0xf)];
201 if ((Char >= 'A') && (Char <= 'F')) {
202 Char = Char - 'A' + 'a';
203 }
204 *OutBufPtr++ = Char;
205
206 RegSize = RegSize + 8;
207 }
208 return OutBufPtr;
209 }
210
211
212 /** ‘p n’
213 Reads the n-th register's value into an output buffer and sends it as a packet
214 @param SystemContext Register content at time of the exception
215 @param InBuffer Pointer to the input buffer received from gdb server
216 **/
217 VOID
218 ReadNthRegister (
219 IN EFI_SYSTEM_CONTEXT SystemContext,
220 IN CHAR8 *InBuffer
221 )
222 {
223 UINTN RegNumber;
224 CHAR8 OutBuffer[9]; // 1 reg=8 hex chars, and the end '\0' (escape seq)
225 CHAR8 *OutBufPtr; // pointer to the output buffer
226
227 RegNumber = AsciiStrHexToUintn (&InBuffer[1]);
228
229 if (RegNumber >= MaxRegisterCount()) {
230 SendError (GDB_EINVALIDREGNUM);
231 return;
232 }
233
234 OutBufPtr = OutBuffer;
235 OutBufPtr = BasicReadRegister (SystemContext, RegNumber, OutBufPtr);
236
237 *OutBufPtr = '\0'; // the end of the buffer
238 SendPacket(OutBuffer);
239 }
240
241
242 /** ‘g’
243 Reads the general registers into an output buffer and sends it as a packet
244 @param SystemContext Register content at time of the exception
245 **/
246 VOID
247 EFIAPI
248 ReadGeneralRegisters (
249 IN EFI_SYSTEM_CONTEXT SystemContext
250 )
251 {
252 UINTN Index;
253 CHAR8 *OutBuffer;
254 CHAR8 *OutBufPtr;
255 UINTN RegisterCount = MaxRegisterCount();
256
257 // It is not safe to allocate pool here....
258 OutBuffer = AllocatePool((RegisterCount * 8) + 1); // 8 bytes per register in string format plus a null to terminate
259 OutBufPtr = OutBuffer;
260 for (Index = 0; Index < RegisterCount; Index++) {
261 OutBufPtr = BasicReadRegister (SystemContext, Index, OutBufPtr);
262 }
263
264 *OutBufPtr = '\0';
265 SendPacket(OutBuffer);
266 FreePool(OutBuffer);
267 }
268
269
270 /**
271 Adds the RegNumber-th register's value to the output buffer, starting at the given OutBufPtr
272 @param SystemContext Register content at time of the exception
273 @param RegNumber the number of the register that we want to write
274 @param InBufPtr pointer to the output buffer. the new data will be extracted from the input buffer from this point on.
275 @retval the pointer to the next character of the input buffer that can be used
276 **/
277 CHAR8
278 *BasicWriteRegister (
279 IN EFI_SYSTEM_CONTEXT SystemContext,
280 IN UINTN RegNumber,
281 IN CHAR8 *InBufPtr
282 )
283 {
284 UINTN RegSize;
285 UINTN TempValue; // the value transferred from a hex char
286 UINT32 NewValue; // the new value of the RegNumber-th Register
287
288 if (gRegisterOffsets[RegNumber] > 0xF00) {
289 return InBufPtr + 8;
290 }
291
292 NewValue = 0;
293 RegSize = 0;
294 while (RegSize < 32) {
295 TempValue = HexCharToInt(*InBufPtr++);
296
297 if ((INTN)TempValue < 0) {
298 SendError (GDB_EBADMEMDATA);
299 return NULL;
300 }
301
302 NewValue += (TempValue << (RegSize+4));
303 TempValue = HexCharToInt(*InBufPtr++);
304
305 if ((INTN)TempValue < 0) {
306 SendError (GDB_EBADMEMDATA);
307 return NULL;
308 }
309
310 NewValue += (TempValue << RegSize);
311 RegSize = RegSize + 8;
312 }
313 *(FindPointerToRegister(SystemContext, RegNumber)) = NewValue;
314 return InBufPtr;
315 }
316
317
318 /** ‘P n...=r...’
319 Writes the new value of n-th register received into the input buffer to the n-th register
320 @param SystemContext Register content at time of the exception
321 @param InBuffer Ponter to the input buffer received from gdb server
322 **/
323 VOID
324 WriteNthRegister (
325 IN EFI_SYSTEM_CONTEXT SystemContext,
326 IN CHAR8 *InBuffer
327 )
328 {
329 UINTN RegNumber;
330 CHAR8 RegNumBuffer[MAX_REG_NUM_BUF_SIZE]; // put the 'n..' part of the message into this array
331 CHAR8 *RegNumBufPtr;
332 CHAR8 *InBufPtr; // pointer to the input buffer
333
334 // find the register number to write
335 InBufPtr = &InBuffer[1];
336 RegNumBufPtr = RegNumBuffer;
337 while (*InBufPtr != '=') {
338 *RegNumBufPtr++ = *InBufPtr++;
339 }
340 *RegNumBufPtr = '\0';
341 RegNumber = AsciiStrHexToUintn (RegNumBuffer);
342
343 // check if this is a valid Register Number
344 if (RegNumber >= MaxRegisterCount()) {
345 SendError (GDB_EINVALIDREGNUM);
346 return;
347 }
348 InBufPtr++; // skips the '=' character
349 BasicWriteRegister (SystemContext, RegNumber, InBufPtr);
350 SendSuccess();
351 }
352
353
354 /** ‘G XX...’
355 Writes the new values received into the input buffer to the general registers
356 @param SystemContext Register content at time of the exception
357 @param InBuffer Pointer to the input buffer received from gdb server
358 **/
359
360 VOID
361 EFIAPI
362 WriteGeneralRegisters (
363 IN EFI_SYSTEM_CONTEXT SystemContext,
364 IN CHAR8 *InBuffer
365 )
366 {
367 UINTN i;
368 CHAR8 *InBufPtr; /// pointer to the input buffer
369 UINTN MinLength;
370 UINTN RegisterCount = MaxRegisterCount();
371
372 MinLength = (RegisterCount * 8) + 1; // 'G' plus the registers in ASCII format
373
374 if (AsciiStrLen(InBuffer) < MinLength) {
375 //Bad message. Message is not the right length
376 SendError (GDB_EBADBUFSIZE);
377 return;
378 }
379
380 InBufPtr = &InBuffer[1];
381
382 // Read the new values for the registers from the input buffer to an array, NewValueArray.
383 // The values in the array are in the gdb ordering
384 for(i = 0; i < RegisterCount; i++) {
385 InBufPtr = BasicWriteRegister (SystemContext, i, InBufPtr);
386 }
387
388 SendSuccess ();
389 }
390
391 // What about Thumb?
392 // Use SWI 0xdbdbdb as the debug instruction
393 #define GDB_ARM_BKPT 0xefdbdbdb
394
395 BOOLEAN mSingleStepActive = FALSE;
396 UINT32 mSingleStepPC;
397 UINT32 mSingleStepData;
398 UINTN mSingleStepDataSize;
399
400 typedef struct {
401 LIST_ENTRY Link;
402 UINT64 Signature;
403 UINT32 Address;
404 UINT32 Instruction;
405 } ARM_SOFTWARE_BREAKPOINT;
406
407 #define ARM_SOFTWARE_BREAKPOINT_SIGNATURE SIGNATURE_64('A', 'R', 'M', 'B', 'R', 'K', 'P', 'T')
408 #define ARM_SOFTWARE_BREAKPOINT_FROM_LINK(a) CR(a, ARM_SOFTWARE_BREAKPOINT, Link, ARM_SOFTWARE_BREAKPOINT_SIGNATURE)
409
410 LIST_ENTRY BreakpointList;
411
412 /**
413 Insert Single Step in the SystemContext
414
415 @param SystemContext Register content at time of the exception
416 **/
417 VOID
418 AddSingleStep (
419 IN EFI_SYSTEM_CONTEXT SystemContext
420 )
421 {
422 if (mSingleStepActive) {
423 // Currently don't support nesting
424 return;
425 }
426 mSingleStepActive = TRUE;
427
428 mSingleStepPC = SystemContext.SystemContextArm->PC;
429
430 mSingleStepDataSize = sizeof (UINT32);
431 mSingleStepData = (*(UINT32 *)mSingleStepPC);
432 *(UINT32 *)mSingleStepPC = GDB_ARM_BKPT;
433 if (*(UINT32 *)mSingleStepPC != GDB_ARM_BKPT) {
434 // For some reason our breakpoint did not take
435 mSingleStepActive = FALSE;
436 }
437
438 InvalidateInstructionCacheRange((VOID *)mSingleStepPC, mSingleStepDataSize);
439 //DEBUG((EFI_D_ERROR, "AddSingleStep at 0x%08x (was: 0x%08x is:0x%08x)\n", SystemContext.SystemContextArm->PC, mSingleStepData, *(UINT32 *)mSingleStepPC));
440 }
441
442
443 /**
444 Remove Single Step in the SystemContext
445
446 @param SystemContext Register content at time of the exception
447 **/
448 VOID
449 RemoveSingleStep (
450 IN EFI_SYSTEM_CONTEXT SystemContext
451 )
452 {
453 if (!mSingleStepActive) {
454 return;
455 }
456
457 if (mSingleStepDataSize == sizeof (UINT16)) {
458 *(UINT16 *)mSingleStepPC = (UINT16)mSingleStepData;
459 } else {
460 //DEBUG((EFI_D_ERROR, "RemoveSingleStep at 0x%08x (was: 0x%08x is:0x%08x)\n", SystemContext.SystemContextArm->PC, *(UINT32 *)mSingleStepPC, mSingleStepData));
461 *(UINT32 *)mSingleStepPC = mSingleStepData;
462 }
463 InvalidateInstructionCacheRange((VOID *)mSingleStepPC, mSingleStepDataSize);
464 mSingleStepActive = FALSE;
465 }
466
467
468
469 /** ‘c [addr ]’
470 Continue. addr is Address to resume. If addr is omitted, resume at current
471 Address.
472
473 @param SystemContext Register content at time of the exception
474 **/
475 VOID
476 EFIAPI
477 ContinueAtAddress (
478 IN EFI_SYSTEM_CONTEXT SystemContext,
479 IN CHAR8 *PacketData
480 )
481 {
482 if (PacketData[1] != '\0') {
483 SystemContext.SystemContextArm->PC = AsciiStrHexToUintn(&PacketData[1]);
484 }
485 }
486
487
488 /** ‘s [addr ]’
489 Single step. addr is the Address at which to resume. If addr is omitted, resume
490 at same Address.
491
492 @param SystemContext Register content at time of the exception
493 **/
494 VOID
495 EFIAPI
496 SingleStep (
497 IN EFI_SYSTEM_CONTEXT SystemContext,
498 IN CHAR8 *PacketData
499 )
500 {
501 SendNotSupported();
502 }
503
504 UINTN
505 GetBreakpointDataAddress (
506 IN EFI_SYSTEM_CONTEXT SystemContext,
507 IN UINTN BreakpointNumber
508 )
509 {
510 return 0;
511 }
512
513 UINTN
514 GetBreakpointDetected (
515 IN EFI_SYSTEM_CONTEXT SystemContext
516 )
517 {
518 return 0;
519 }
520
521 BREAK_TYPE
522 GetBreakpointType (
523 IN EFI_SYSTEM_CONTEXT SystemContext,
524 IN UINTN BreakpointNumber
525 )
526 {
527 return NotSupported;
528 }
529
530 ARM_SOFTWARE_BREAKPOINT *
531 SearchBreakpointList (
532 IN UINT32 Address
533 )
534 {
535 LIST_ENTRY *Current;
536 ARM_SOFTWARE_BREAKPOINT *Breakpoint;
537
538 Current = GetFirstNode(&BreakpointList);
539 while (!IsNull(&BreakpointList, Current)) {
540 Breakpoint = ARM_SOFTWARE_BREAKPOINT_FROM_LINK(Current);
541
542 if (Address == Breakpoint->Address) {
543 return Breakpoint;
544 }
545
546 Current = GetNextNode(&BreakpointList, Current);
547 }
548
549 return NULL;
550 }
551
552 VOID
553 SetBreakpoint (
554 IN UINT32 Address
555 )
556 {
557 ARM_SOFTWARE_BREAKPOINT *Breakpoint;
558
559 Breakpoint = SearchBreakpointList(Address);
560
561 if (Breakpoint != NULL) {
562 return;
563 }
564
565 // create and fill breakpoint structure
566 Breakpoint = AllocatePool(sizeof(ARM_SOFTWARE_BREAKPOINT));
567
568 Breakpoint->Signature = ARM_SOFTWARE_BREAKPOINT_SIGNATURE;
569 Breakpoint->Address = Address;
570 Breakpoint->Instruction = *(UINT32 *)Address;
571
572 // Add it to the list
573 InsertTailList(&BreakpointList, &Breakpoint->Link);
574
575 // Insert the software breakpoint
576 *(UINT32 *)Address = GDB_ARM_BKPT;
577 InvalidateInstructionCacheRange((VOID *)Address, 4);
578
579 //DEBUG((EFI_D_ERROR, "SetBreakpoint at 0x%08x (was: 0x%08x is:0x%08x)\n", Address, Breakpoint->Instruction, *(UINT32 *)Address));
580 }
581
582 VOID
583 ClearBreakpoint (
584 IN UINT32 Address
585 )
586 {
587 ARM_SOFTWARE_BREAKPOINT *Breakpoint;
588
589 Breakpoint = SearchBreakpointList(Address);
590
591 if (Breakpoint == NULL) {
592 return;
593 }
594
595 // Add it to the list
596 RemoveEntryList(&Breakpoint->Link);
597
598 // Restore the original instruction
599 *(UINT32 *)Address = Breakpoint->Instruction;
600 InvalidateInstructionCacheRange((VOID *)Address, 4);
601
602 //DEBUG((EFI_D_ERROR, "ClearBreakpoint at 0x%08x (was: 0x%08x is:0x%08x)\n", Address, GDB_ARM_BKPT, *(UINT32 *)Address));
603
604 FreePool(Breakpoint);
605 }
606
607 VOID
608 EFIAPI
609 InsertBreakPoint (
610 IN EFI_SYSTEM_CONTEXT SystemContext,
611 IN CHAR8 *PacketData
612 )
613 {
614 UINTN Type;
615 UINTN Address;
616 UINTN Length;
617 UINTN ErrorCode;
618
619 ErrorCode = ParseBreakpointPacket(PacketData, &Type, &Address, &Length);
620 if (ErrorCode > 0) {
621 SendError ((UINT8)ErrorCode);
622 return;
623 }
624
625 switch (Type) {
626 case 0: //Software breakpoint
627 break;
628
629 default :
630 DEBUG((EFI_D_ERROR, "Insert breakpoint default: %x\n", Type));
631 SendError (GDB_EINVALIDBRKPOINTTYPE);
632 return;
633 }
634
635 SetBreakpoint(Address);
636
637 SendSuccess ();
638 }
639
640 VOID
641 EFIAPI
642 RemoveBreakPoint (
643 IN EFI_SYSTEM_CONTEXT SystemContext,
644 IN CHAR8 *PacketData
645 )
646 {
647 UINTN Type;
648 UINTN Address;
649 UINTN Length;
650 UINTN ErrorCode;
651
652 //Parse breakpoint packet data
653 ErrorCode = ParseBreakpointPacket (PacketData, &Type, &Address, &Length);
654 if (ErrorCode > 0) {
655 SendError ((UINT8)ErrorCode);
656 return;
657 }
658
659 switch (Type) {
660 case 0: //Software breakpoint
661 break;
662
663 default:
664 SendError (GDB_EINVALIDBRKPOINTTYPE);
665 return;
666 }
667
668 ClearBreakpoint(Address);
669
670 SendSuccess ();
671 }
672
673 VOID
674 InitializeProcessor (
675 VOID
676 )
677 {
678 // Initialize breakpoint list
679 InitializeListHead(&BreakpointList);
680 }
681
682 BOOLEAN
683 ValidateAddress (
684 IN VOID *Address
685 )
686 {
687 if ((UINT32)Address < 0x80000000) {
688 return FALSE;
689 } else {
690 return TRUE;
691 }
692 }
693
694 BOOLEAN
695 ValidateException (
696 IN EFI_EXCEPTION_TYPE ExceptionType,
697 IN OUT EFI_SYSTEM_CONTEXT SystemContext
698 )
699 {
700 UINT32 ExceptionAddress;
701 UINT32 Instruction;
702
703 // Is it a debugger SWI?
704 ExceptionAddress = SystemContext.SystemContextArm->PC -= 4;
705 Instruction = *(UINT32 *)ExceptionAddress;
706 if (Instruction != GDB_ARM_BKPT) {
707 return FALSE;
708 }
709
710 // Special for SWI-based exception handling. SWI sets up the context
711 // to return to the instruction following the SWI instruction - NOT what we want
712 // for a debugger!
713 SystemContext.SystemContextArm->PC = ExceptionAddress;
714
715 return TRUE;
716 }
717