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