- NDK 0.98, now with versionned headers. Too many changes to list, see the TinyKRNL...
[reactos.git] / reactos / ntoskrnl / ke / i386 / exp.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS Kernel
4 * FILE: ntoskrnl/ke/i386/exp.c
5 * PURPOSE: Exception Support Code
6 * PROGRAMMERS: Alex Ionescu (alex@relsoft.net)
7 * Gregor Anich
8 * David Welch (welch@cwcom.net)
9 * Skywing (skywing@valhallalegends.com)
10 */
11
12 /*
13 * FIXMES:
14 * - Clean up file (remove all stack functions and use RtlWalkFrameChain/RtlCaptureStackBacktrace)
15 * - Sanitize some context fields.
16 * - Add PSEH handler when an exception occurs in an exception (KiCopyExceptionRecord).
17 * - Forward exceptions to user-mode debugger.
18 */
19
20 /* INCLUDES *****************************************************************/
21
22 #include <ntoskrnl.h>
23
24 #define NDEBUG
25 #include <internal/debug.h>
26
27 #if defined (ALLOC_PRAGMA)
28 #pragma alloc_text(INIT, KeInitExceptions)
29 #endif
30
31 VOID
32 NTAPI
33 Ki386AdjustEsp0(
34 IN PKTRAP_FRAME TrapFrame
35 );
36
37 extern KIDTENTRY KiIdt[];
38
39 /* GLOBALS *****************************************************************/
40
41 #define FLAG_IF (1<<9)
42
43 #define _STR(x) #x
44 #define STR(x) _STR(x)
45
46 #ifndef ARRAY_SIZE
47 # define ARRAY_SIZE(x) (sizeof (x) / sizeof (x[0]))
48 #endif
49
50 extern ULONG init_stack;
51 extern ULONG init_stack_top;
52
53 extern BOOLEAN Ke386NoExecute;
54
55 static char *ExceptionTypeStrings[] =
56 {
57 "Divide Error",
58 "Debug Trap",
59 "NMI",
60 "Breakpoint",
61 "Overflow",
62 "BOUND range exceeded",
63 "Invalid Opcode",
64 "No Math Coprocessor",
65 "Double Fault",
66 "Unknown(9)",
67 "Invalid TSS",
68 "Segment Not Present",
69 "Stack Segment Fault",
70 "General Protection",
71 "Page Fault",
72 "Reserved(15)",
73 "Math Fault",
74 "Alignment Check",
75 "Machine Check",
76 "SIMD Fault"
77 };
78
79 NTSTATUS ExceptionToNtStatus[] =
80 {
81 STATUS_INTEGER_DIVIDE_BY_ZERO,
82 STATUS_SINGLE_STEP,
83 STATUS_ACCESS_VIOLATION,
84 STATUS_BREAKPOINT,
85 STATUS_INTEGER_OVERFLOW,
86 STATUS_ARRAY_BOUNDS_EXCEEDED,
87 STATUS_ILLEGAL_INSTRUCTION,
88 STATUS_FLOAT_INVALID_OPERATION,
89 STATUS_ACCESS_VIOLATION,
90 STATUS_ACCESS_VIOLATION,
91 STATUS_ACCESS_VIOLATION,
92 STATUS_ACCESS_VIOLATION,
93 STATUS_STACK_OVERFLOW,
94 STATUS_ACCESS_VIOLATION,
95 STATUS_ACCESS_VIOLATION,
96 STATUS_ACCESS_VIOLATION, /* RESERVED */
97 STATUS_FLOAT_INVALID_OPERATION, /* Should not be used, the FPU can give more specific info */
98 STATUS_DATATYPE_MISALIGNMENT,
99 STATUS_ACCESS_VIOLATION,
100 STATUS_FLOAT_MULTIPLE_TRAPS,
101 };
102
103 /* FUNCTIONS ****************************************************************/
104
105 BOOLEAN STDCALL
106 KiRosPrintAddress(PVOID address)
107 {
108 PLIST_ENTRY current_entry;
109 PLDR_DATA_TABLE_ENTRY current;
110 extern LIST_ENTRY ModuleListHead;
111 ULONG_PTR RelativeAddress;
112 ULONG i = 0;
113
114 do
115 {
116 current_entry = ModuleListHead.Flink;
117
118 while (current_entry != &ModuleListHead)
119 {
120 current =
121 CONTAINING_RECORD(current_entry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
122
123 if (address >= (PVOID)current->DllBase &&
124 address < (PVOID)((ULONG_PTR)current->DllBase + current->SizeOfImage))
125 {
126 RelativeAddress = (ULONG_PTR) address - (ULONG_PTR) current->DllBase;
127 DbgPrint("<%wZ: %x>", &current->FullDllName, RelativeAddress);
128 return(TRUE);
129 }
130 current_entry = current_entry->Flink;
131 }
132
133 address = (PVOID)((ULONG_PTR)address & ~(ULONG_PTR)MmSystemRangeStart);
134 } while(++i <= 1);
135
136 return(FALSE);
137 }
138
139 ULONG
140 KiKernelTrapHandler(PKTRAP_FRAME Tf, ULONG ExceptionNr, PVOID Cr2)
141 {
142 EXCEPTION_RECORD Er;
143
144 Er.ExceptionFlags = 0;
145 Er.ExceptionRecord = NULL;
146 Er.ExceptionAddress = (PVOID)Tf->Eip;
147
148 if (ExceptionNr == 14)
149 {
150 Er.ExceptionCode = STATUS_ACCESS_VIOLATION;
151 Er.NumberParameters = 2;
152 Er.ExceptionInformation[0] = Tf->ErrCode & 0x1;
153 Er.ExceptionInformation[1] = (ULONG)Cr2;
154 }
155 else
156 {
157 if (ExceptionNr < ARRAY_SIZE(ExceptionToNtStatus))
158 {
159 Er.ExceptionCode = ExceptionToNtStatus[ExceptionNr];
160 }
161 else
162 {
163 Er.ExceptionCode = STATUS_ACCESS_VIOLATION;
164 }
165 Er.NumberParameters = 0;
166 }
167
168 /* FIXME: Which exceptions are noncontinuable? */
169 Er.ExceptionFlags = 0;
170
171 KiDispatchException(&Er, NULL, Tf, KernelMode, TRUE);
172
173 return(0);
174 }
175
176 VOID
177 KiDoubleFaultHandler(VOID)
178 {
179 #if 0
180 unsigned int cr2;
181 ULONG StackLimit;
182 ULONG StackBase;
183 ULONG Esp0;
184 ULONG ExceptionNr = 8;
185 KTSS* OldTss;
186 PULONG Frame;
187 ULONG OldCr3;
188 #if 0
189 ULONG i, j;
190 static PVOID StackTrace[MM_STACK_SIZE / sizeof(PVOID)];
191 static ULONG StackRepeatCount[MM_STACK_SIZE / sizeof(PVOID)];
192 static ULONG StackRepeatLength[MM_STACK_SIZE / sizeof(PVOID)];
193 ULONG TraceLength;
194 BOOLEAN FoundRepeat;
195 #endif
196
197 OldTss = KeGetCurrentKPCR()->TSS;
198 Esp0 = OldTss->Esp0;
199
200 /* Get CR2 */
201 cr2 = Ke386GetCr2();
202 if (PsGetCurrentThread() != NULL &&
203 PsGetCurrentThread()->ThreadsProcess != NULL)
204 {
205 OldCr3 = (ULONG)
206 PsGetCurrentThread()->ThreadsProcess->Pcb.DirectoryTableBase.QuadPart;
207 }
208 else
209 {
210 OldCr3 = 0xBEADF0AL;
211 }
212
213 /*
214 * Check for stack underflow
215 */
216 if (PsGetCurrentThread() != NULL &&
217 Esp0 < (ULONG)PsGetCurrentThread()->Tcb.StackLimit)
218 {
219 DbgPrint("Stack underflow (tf->esp %x Limit %x)\n",
220 Esp0, (ULONG)PsGetCurrentThread()->Tcb.StackLimit);
221 ExceptionNr = 12;
222 }
223
224 /*
225 * Print out the CPU registers
226 */
227 if (ExceptionNr < ARRAY_SIZE(ExceptionTypeStrings))
228 {
229 DbgPrint("%s Exception: %d(%x)\n", ExceptionTypeStrings[ExceptionNr],
230 ExceptionNr, 0);
231 }
232 else
233 {
234 DbgPrint("Exception: %d(%x)\n", ExceptionNr, 0);
235 }
236 DbgPrint("CS:EIP %x:%x ", OldTss->Cs, OldTss->Eip);
237 KeRosPrintAddress((PVOID)OldTss->Eip);
238 DbgPrint("\n");
239 DbgPrint("cr2 %x cr3 %x ", cr2, OldCr3);
240 DbgPrint("Proc: %x ",PsGetCurrentProcess());
241 if (PsGetCurrentProcess() != NULL)
242 {
243 DbgPrint("Pid: %x <", PsGetCurrentProcess()->UniqueProcessId);
244 DbgPrint("%.16s> ", PsGetCurrentProcess()->ImageFileName);
245 }
246 if (PsGetCurrentThread() != NULL)
247 {
248 DbgPrint("Thrd: %x Tid: %x",
249 PsGetCurrentThread(),
250 PsGetCurrentThread()->Cid.UniqueThread);
251 }
252 DbgPrint("\n");
253 DbgPrint("DS %x ES %x FS %x GS %x\n", OldTss->Ds, OldTss->Es,
254 OldTss->Fs, OldTss->Gs);
255 DbgPrint("EAX: %.8x EBX: %.8x ECX: %.8x\n", OldTss->Eax, OldTss->Ebx,
256 OldTss->Ecx);
257 DbgPrint("EDX: %.8x EBP: %.8x ESI: %.8x\nESP: %.8x ", OldTss->Edx,
258 OldTss->Ebp, OldTss->Esi, Esp0);
259 DbgPrint("EDI: %.8x EFLAGS: %.8x ", OldTss->Edi, OldTss->Eflags);
260 if (OldTss->Cs == KGDT_R0_CODE)
261 {
262 DbgPrint("kESP %.8x ", Esp0);
263 if (PsGetCurrentThread() != NULL)
264 {
265 DbgPrint("kernel stack base %x\n",
266 PsGetCurrentThread()->Tcb.StackLimit);
267
268 }
269 }
270 else
271 {
272 DbgPrint("User ESP %.8x\n", OldTss->Esp);
273 }
274 if ((OldTss->Cs & 0xffff) == KGDT_R0_CODE)
275 {
276 if (PsGetCurrentThread() != NULL)
277 {
278 StackLimit = (ULONG)PsGetCurrentThread()->Tcb.StackBase;
279 StackBase = (ULONG)PsGetCurrentThread()->Tcb.StackLimit;
280 }
281 else
282 {
283 StackLimit = (ULONG)init_stack_top;
284 StackBase = (ULONG)init_stack;
285 }
286
287 /*
288 Change to an #if 0 to reduce the amount of information printed on
289 a recursive stack trace.
290 */
291 #if 1
292 DbgPrint("Frames: ");
293 Frame = (PULONG)OldTss->Ebp;
294 while (Frame != NULL && (ULONG)Frame >= StackBase)
295 {
296 KeRosPrintAddress((PVOID)Frame[1]);
297 Frame = (PULONG)Frame[0];
298 DbgPrint("\n");
299 }
300 #else
301 DbgPrint("Frames: ");
302 i = 0;
303 Frame = (PULONG)OldTss->Ebp;
304 while (Frame != NULL && (ULONG)Frame >= StackBase)
305 {
306 StackTrace[i] = (PVOID)Frame[1];
307 Frame = (PULONG)Frame[0];
308 i++;
309 }
310 TraceLength = i;
311
312 i = 0;
313 while (i < TraceLength)
314 {
315 StackRepeatCount[i] = 0;
316 j = i + 1;
317 FoundRepeat = FALSE;
318 while ((j - i) <= (TraceLength - j) && FoundRepeat == FALSE)
319 {
320 if (memcmp(&StackTrace[i], &StackTrace[j],
321 (j - i) * sizeof(PVOID)) == 0)
322 {
323 StackRepeatCount[i] = 2;
324 StackRepeatLength[i] = j - i;
325 FoundRepeat = TRUE;
326 }
327 else
328 {
329 j++;
330 }
331 }
332 if (FoundRepeat == FALSE)
333 {
334 i++;
335 continue;
336 }
337 j = j + StackRepeatLength[i];
338 while ((TraceLength - j) >= StackRepeatLength[i] &&
339 FoundRepeat == TRUE)
340 {
341 if (memcmp(&StackTrace[i], &StackTrace[j],
342 StackRepeatLength[i] * sizeof(PVOID)) == 0)
343 {
344 StackRepeatCount[i]++;
345 j = j + StackRepeatLength[i];
346 }
347 else
348 {
349 FoundRepeat = FALSE;
350 }
351 }
352 i = j;
353 }
354
355 i = 0;
356 while (i < TraceLength)
357 {
358 if (StackRepeatCount[i] == 0)
359 {
360 KeRosPrintAddress(StackTrace[i]);
361 i++;
362 }
363 else
364 {
365 DbgPrint("{");
366 if (StackRepeatLength[i] == 0)
367 {
368 for(;;);
369 }
370 for (j = 0; j < StackRepeatLength[i]; j++)
371 {
372 KeRosPrintAddress(StackTrace[i + j]);
373 }
374 DbgPrint("}*%d", StackRepeatCount[i]);
375 i = i + StackRepeatLength[i] * StackRepeatCount[i];
376 }
377 }
378 #endif
379 }
380 #endif
381 DbgPrint("\n");
382 for(;;);
383 }
384
385 VOID
386 NTAPI
387 KiDumpTrapFrame(PKTRAP_FRAME Tf, ULONG Parameter1, ULONG Parameter2)
388 {
389 ULONG cr3_;
390 ULONG StackLimit;
391 ULONG Esp0;
392 ULONG ExceptionNr = (ULONG)Tf->DbgArgMark;
393 ULONG cr2 = (ULONG)Tf->DbgArgPointer;
394
395 Esp0 = (ULONG)Tf;
396
397 /*
398 * Print out the CPU registers
399 */
400 if (ExceptionNr < ARRAY_SIZE(ExceptionTypeStrings))
401 {
402 DbgPrint("%s Exception: %d(%x)\n", ExceptionTypeStrings[ExceptionNr],
403 ExceptionNr, Tf->ErrCode&0xffff);
404 }
405 else
406 {
407 DbgPrint("Exception: %d(%x)\n", ExceptionNr, Tf->ErrCode&0xffff);
408 }
409 DbgPrint("Processor: %d CS:EIP %x:%x ", KeGetCurrentProcessorNumber(),
410 Tf->SegCs&0xffff, Tf->Eip);
411 KeRosPrintAddress((PVOID)Tf->Eip);
412 DbgPrint("\n");
413 Ke386GetPageTableDirectory(cr3_);
414 DbgPrint("cr2 %x cr3 %x ", cr2, cr3_);
415 DbgPrint("Proc: %x ",PsGetCurrentProcess());
416 if (PsGetCurrentProcess() != NULL)
417 {
418 DbgPrint("Pid: %x <", PsGetCurrentProcess()->UniqueProcessId);
419 DbgPrint("%.16s> ", PsGetCurrentProcess()->ImageFileName);
420 }
421 if (PsGetCurrentThread() != NULL)
422 {
423 DbgPrint("Thrd: %x Tid: %x",
424 PsGetCurrentThread(),
425 PsGetCurrentThread()->Cid.UniqueThread);
426 }
427 DbgPrint("\n");
428 DbgPrint("DS %x ES %x FS %x GS %x\n", Tf->SegDs&0xffff, Tf->SegEs&0xffff,
429 Tf->SegFs&0xffff, Tf->SegGs&0xfff);
430 DbgPrint("EAX: %.8x EBX: %.8x ECX: %.8x\n", Tf->Eax, Tf->Ebx, Tf->Ecx);
431 DbgPrint("EDX: %.8x EBP: %.8x ESI: %.8x ESP: %.8x\n", Tf->Edx,
432 Tf->Ebp, Tf->Esi, Esp0);
433 DbgPrint("EDI: %.8x EFLAGS: %.8x ", Tf->Edi, Tf->EFlags);
434 if ((Tf->SegCs&0xffff) == KGDT_R0_CODE)
435 {
436 DbgPrint("kESP %.8x ", Esp0);
437 if (PsGetCurrentThread() != NULL)
438 {
439 DbgPrint("kernel stack base %x\n",
440 PsGetCurrentThread()->Tcb.StackLimit);
441
442 }
443 }
444
445 if (PsGetCurrentThread() != NULL)
446 {
447 StackLimit = (ULONG)PsGetCurrentThread()->Tcb.StackBase;
448 }
449 else
450 {
451 StackLimit = (ULONG)init_stack_top;
452 }
453
454 /*
455 * Dump the stack frames
456 */
457 KeDumpStackFrames((PULONG)Tf->Ebp);
458 }
459
460 ULONG
461 KiTrapHandler(PKTRAP_FRAME Tf, ULONG ExceptionNr)
462 /*
463 * FUNCTION: Called by the lowlevel execption handlers to print an amusing
464 * message and halt the computer
465 * ARGUMENTS:
466 * Complete CPU context
467 */
468 {
469 ULONG_PTR cr2;
470 NTSTATUS Status;
471 ULONG Esp0;
472
473 ASSERT(ExceptionNr != 14);
474
475 /* Use the address of the trap frame as approximation to the ring0 esp */
476 Esp0 = (ULONG)&Tf->Eip;
477
478 /* Get CR2 */
479 cr2 = Ke386GetCr2();
480 Tf->DbgArgPointer = cr2;
481
482 /*
483 * If this was a V86 mode exception then handle it specially
484 */
485 if (Tf->EFlags & (1 << 17))
486 {
487 DPRINT("Tf->Eflags, %x, Tf->Eip %x, ExceptionNr: %d\n", Tf->EFlags, Tf->Eip, ExceptionNr);
488 return(KeV86Exception(ExceptionNr, Tf, cr2));
489 }
490
491 /*
492 * Check for stack underflow, this may be obsolete
493 */
494 if (PsGetCurrentThread() != NULL &&
495 Esp0 < (ULONG)PsGetCurrentThread()->Tcb.StackLimit)
496 {
497 DPRINT1("Stack underflow (tf->esp %x Limit %x Eip %x)\n",
498 Esp0, (ULONG)PsGetCurrentThread()->Tcb.StackLimit, Tf->Eip);
499 ExceptionNr = 12;
500 }
501
502 if (ExceptionNr == 15)
503 {
504 /*
505 * FIXME:
506 * This exception should never occur. The P6 has a bug, which does sometimes deliver
507 * the apic spurious interrupt as exception 15. On an athlon64, I get one exception
508 * in the early boot phase in apic mode (using the smp build). I've looked to the linux
509 * sources. Linux does ignore this exception.
510 *
511 */
512 DPRINT1("Ignoring P6 Local APIC Spurious Interrupt Bug...\n");
513 return(0);
514 }
515
516 /*
517 * Check for a breakpoint that was only for the attention of the debugger.
518 */
519 if (ExceptionNr == 3 && Tf->Eip == ((ULONG)DbgBreakPointNoBugCheck) + 1)
520 {
521 /*
522 EIP is already adjusted by the processor to point to the instruction
523 after the breakpoint.
524 */
525 return(0);
526 }
527
528 /*
529 * Try to handle device-not-present, math-fault and xmm-fault exceptions.
530 */
531 if (ExceptionNr == 7 || ExceptionNr == 16 || ExceptionNr == 19)
532 {
533 Status = KiHandleFpuFault(Tf, ExceptionNr);
534 if (NT_SUCCESS(Status))
535 {
536 return(0);
537 }
538 }
539
540 /*
541 * Handle user exceptions differently
542 */
543 if ((Tf->SegCs & 0xFFFF) == (KGDT_R3_CODE | RPL_MASK))
544 {
545 return(KiUserTrapHandler(Tf, ExceptionNr, (PVOID)cr2));
546 }
547 else
548 {
549 return(KiKernelTrapHandler(Tf, ExceptionNr, (PVOID)cr2));
550 }
551 }
552
553 ULONG
554 NTAPI
555 KiEspFromTrapFrame(IN PKTRAP_FRAME TrapFrame)
556 {
557 /* Check if this is user-mode or V86 */
558 if ((TrapFrame->SegCs & MODE_MASK) || (TrapFrame->EFlags & X86_EFLAGS_VM))
559 {
560 /* Return it directly */
561 return TrapFrame->HardwareEsp;
562 }
563 else
564 {
565 /* Edited frame */
566 if (!(TrapFrame->SegCs & FRAME_EDITED))
567 {
568 /* Return edited value */
569 return TrapFrame->TempEsp;
570 }
571 else
572 {
573 /* Virgin frame, calculate */
574 return (ULONG)&TrapFrame->HardwareEsp;
575 }
576 }
577 }
578
579 VOID
580 NTAPI
581 KiEspToTrapFrame(IN PKTRAP_FRAME TrapFrame,
582 IN ULONG Esp)
583 {
584 ULONG Previous = KiEspFromTrapFrame(TrapFrame);
585
586 /* Check if this is user-mode or V86 */
587 if ((TrapFrame->SegCs & MODE_MASK) || (TrapFrame->EFlags & X86_EFLAGS_VM))
588 {
589 /* Write it directly */
590 TrapFrame->HardwareEsp = Esp;
591 }
592 else
593 {
594 /* Don't allow ESP to be lowered, this is illegal */
595 if (Esp < Previous)
596 {
597 KeBugCheck(SET_OF_INVALID_CONTEXT);
598 }
599
600 /* Create an edit frame, check if it was alrady */
601 if (!(TrapFrame->SegCs & FRAME_EDITED))
602 {
603 /* Update the value */
604 TrapFrame->TempEsp = Esp;
605 }
606 else
607 {
608 /* Check if ESP changed */
609 if (Previous != Esp)
610 {
611 /* Save CS */
612 TrapFrame->TempSegCs = TrapFrame->SegCs;
613 TrapFrame->SegCs &= ~FRAME_EDITED;
614
615 /* Save ESP */
616 TrapFrame->TempEsp = Esp;
617 }
618 }
619 }
620 }
621
622 ULONG
623 NTAPI
624 KiSsFromTrapFrame(IN PKTRAP_FRAME TrapFrame)
625 {
626 /* If this was V86 Mode */
627 if (TrapFrame->EFlags & X86_EFLAGS_VM)
628 {
629 /* Just return it */
630 return TrapFrame->HardwareSegSs;
631 }
632 else if (TrapFrame->SegCs & MODE_MASK)
633 {
634 /* Usermode, return the User SS */
635 return TrapFrame->HardwareSegSs | RPL_MASK;
636 }
637 else
638 {
639 /* Kernel mode */
640 return KGDT_R0_DATA;
641 }
642 }
643
644 VOID
645 NTAPI
646 KiSsToTrapFrame(IN PKTRAP_FRAME TrapFrame,
647 IN ULONG Ss)
648 {
649 /* Remove the high-bits */
650 Ss &= 0xFFFF;
651
652 /* If this was V86 Mode */
653 if (TrapFrame->EFlags & X86_EFLAGS_VM)
654 {
655 /* Just write it */
656 TrapFrame->HardwareSegSs = Ss;
657 }
658 else if (TrapFrame->SegCs & MODE_MASK)
659 {
660 /* Usermode, save the User SS */
661 TrapFrame->HardwareSegSs = Ss | RPL_MASK;
662 }
663 }
664
665 VOID
666 NTAPI
667 KeContextToTrapFrame(IN PCONTEXT Context,
668 IN OUT PKEXCEPTION_FRAME ExceptionFrame,
669 IN OUT PKTRAP_FRAME TrapFrame,
670 IN ULONG ContextFlags,
671 IN KPROCESSOR_MODE PreviousMode)
672 {
673 PFX_SAVE_AREA FxSaveArea;
674 //ULONG i; Future Use
675 BOOLEAN V86Switch = FALSE;
676
677 /* Start with the basic Registers */
678 if ((ContextFlags & CONTEXT_CONTROL) == CONTEXT_CONTROL)
679 {
680 /* Check if we went through a V86 switch */
681 if ((Context->EFlags & X86_EFLAGS_VM) !=
682 (TrapFrame->EFlags & X86_EFLAGS_VM))
683 {
684 /* We did, remember this for later */
685 V86Switch = TRUE;
686 }
687
688 /* Copy EFLAGS. FIXME: Needs to be sanitized */
689 TrapFrame->EFlags = Context->EFlags;
690
691 /* Copy EBP and EIP */
692 TrapFrame->Ebp = Context->Ebp;
693 TrapFrame->Eip = Context->Eip;
694
695 /* Check if we were in V86 Mode */
696 if (TrapFrame->EFlags & X86_EFLAGS_VM)
697 {
698 /* Simply copy the CS value */
699 TrapFrame->SegCs = Context->SegCs;
700 }
701 else
702 {
703 /* We weren't in V86, so sanitize the CS (FIXME!) */
704 TrapFrame->SegCs = Context->SegCs;
705
706 /* Don't let it under 8, that's invalid */
707 if ((PreviousMode != KernelMode) && (TrapFrame->SegCs < 8))
708 {
709 /* Force it to User CS */
710 TrapFrame->SegCs = (KGDT_R3_CODE | RPL_MASK);
711 }
712 }
713
714 /* Handle SS Specially for validation */
715 KiSsToTrapFrame(TrapFrame, Context->SegSs);
716
717 /* Write ESP back; take into account Edited Trap Frames */
718 KiEspToTrapFrame(TrapFrame, Context->Esp);
719
720 /* Handle our V86 Bias if we went through a switch */
721 if (V86Switch) Ki386AdjustEsp0(TrapFrame);
722 }
723
724 /* Process the Integer Registers */
725 if ((ContextFlags & CONTEXT_INTEGER) == CONTEXT_INTEGER)
726 {
727 TrapFrame->Eax = Context->Eax;
728 TrapFrame->Ebx = Context->Ebx;
729 TrapFrame->Ecx = Context->Ecx;
730 TrapFrame->Edx = Context->Edx;
731 TrapFrame->Esi = Context->Esi;
732 TrapFrame->Edi = Context->Edi;
733 }
734
735 /* Process the Context Segments */
736 if ((ContextFlags & CONTEXT_SEGMENTS) == CONTEXT_SEGMENTS)
737 {
738 /* Check if we were in V86 Mode */
739 if (TrapFrame->EFlags & X86_EFLAGS_VM)
740 {
741 /* Copy the V86 Segments directlry */
742 TrapFrame->V86Ds = Context->SegDs;
743 TrapFrame->V86Es = Context->SegEs;
744 TrapFrame->V86Fs = Context->SegFs;
745 TrapFrame->V86Gs = Context->SegGs;
746 }
747 else if (!(TrapFrame->SegCs & MODE_MASK))
748 {
749 /* For kernel mode, write the standard values */
750 TrapFrame->SegDs = KGDT_R3_DATA | RPL_MASK;
751 TrapFrame->SegEs = KGDT_R3_DATA | RPL_MASK;
752 TrapFrame->SegFs = Context->SegFs;
753 TrapFrame->SegGs = 0;
754 }
755 else
756 {
757 /* For user mode, return the values directlry */
758 TrapFrame->SegDs = Context->SegDs;
759 TrapFrame->SegEs = Context->SegEs;
760 TrapFrame->SegFs = Context->SegFs;
761
762 /* Handle GS specially */
763 if (TrapFrame->SegCs == (KGDT_R3_CODE | RPL_MASK))
764 {
765 /* Don't use it, if user */
766 TrapFrame->SegGs = 0;
767 }
768 else
769 {
770 /* Copy it if kernel */
771 TrapFrame->SegGs = Context->SegGs;
772 }
773 }
774 }
775
776 /* Handle the extended registers */
777 if (((ContextFlags & CONTEXT_EXTENDED_REGISTERS) ==
778 CONTEXT_EXTENDED_REGISTERS) &&
779 ((TrapFrame->SegCs & MODE_MASK) == UserMode))
780 {
781 /* Get the FX Area */
782 FxSaveArea = (PFX_SAVE_AREA)(TrapFrame + 1);
783
784 /* Check if NPX is present */
785 if (KeI386NpxPresent)
786 {
787 /* Future use */
788 }
789 }
790
791 /* Handle the floating point state */
792 if (((ContextFlags & CONTEXT_FLOATING_POINT) ==
793 CONTEXT_FLOATING_POINT) &&
794 ((TrapFrame->SegCs & MODE_MASK) == UserMode))
795 {
796 /* Get the FX Area */
797 FxSaveArea = (PFX_SAVE_AREA)(TrapFrame + 1);
798
799 /* Check if NPX is present */
800 if (KeI386NpxPresent)
801 {
802 /* Future use */
803 }
804 else
805 {
806 /* Future use */
807 }
808 }
809
810 /* Handle the Debug Registers */
811 if ((ContextFlags & CONTEXT_DEBUG_REGISTERS) == CONTEXT_DEBUG_REGISTERS)
812 {
813 /* FIXME: All these should be sanitized */
814 TrapFrame->Dr0 = Context->Dr0;
815 TrapFrame->Dr1 = Context->Dr1;
816 TrapFrame->Dr2 = Context->Dr2;
817 TrapFrame->Dr3 = Context->Dr3;
818 TrapFrame->Dr6 = Context->Dr6;
819 TrapFrame->Dr7 = Context->Dr7;
820
821 /* Check if usermode */
822 if (PreviousMode != KernelMode)
823 {
824 /* Set the Debug Flag */
825 KeGetCurrentThread()->DispatcherHeader.DebugActive =
826 (Context->Dr7 & DR7_ACTIVE);
827 }
828 }
829
830 /* Handle FPU and Extended Registers */
831 KiContextToFxSaveArea((PFX_SAVE_AREA)(TrapFrame + 1), Context);
832 }
833
834 VOID
835 NTAPI
836 KeTrapFrameToContext(IN PKTRAP_FRAME TrapFrame,
837 IN PKEXCEPTION_FRAME ExceptionFrame,
838 IN OUT PCONTEXT Context)
839 {
840 PFX_SAVE_AREA FxSaveArea = NULL;
841
842 /* Start with the Control flags */
843 if ((Context->ContextFlags & CONTEXT_CONTROL) == CONTEXT_CONTROL)
844 {
845 /* EBP, EIP and EFLAGS */
846 Context->Ebp = TrapFrame->Ebp;
847 Context->Eip = TrapFrame->Eip;
848 Context->EFlags = TrapFrame->EFlags;
849
850 /* Return the correct CS */
851 if (!(TrapFrame->SegCs & FRAME_EDITED) &&
852 !(TrapFrame->EFlags & X86_EFLAGS_VM))
853 {
854 /* Get it from the Temp location */
855 Context->SegCs = TrapFrame->TempSegCs & 0xFFFF;
856 }
857 else
858 {
859 /* Return it directly */
860 Context->SegCs = TrapFrame->SegCs & 0xFFFF;
861 }
862
863 /* Get the Ss and ESP */
864 Context->SegSs = KiSsFromTrapFrame(TrapFrame);
865 Context->Esp = KiEspFromTrapFrame(TrapFrame);
866 }
867
868 /* Handle the Segments */
869 if ((Context->ContextFlags & CONTEXT_SEGMENTS) == CONTEXT_SEGMENTS)
870 {
871 /* Do V86 Mode first */
872 if (TrapFrame->EFlags & X86_EFLAGS_VM)
873 {
874 /* Return from the V86 location */
875 Context->SegGs = TrapFrame->V86Gs & 0xFFFF;
876 Context->SegFs = TrapFrame->V86Fs & 0xFFFF;
877 Context->SegEs = TrapFrame->V86Es & 0xFFFF;
878 Context->SegDs = TrapFrame->V86Ds & 0xFFFF;
879 }
880 else
881 {
882 /* Check if this was a Kernel Trap */
883 if (TrapFrame->SegCs == KGDT_R0_CODE)
884 {
885 /* Set valid selectors */
886 TrapFrame->SegGs = 0;
887 TrapFrame->SegFs = KGDT_R0_PCR;
888 TrapFrame->SegEs = KGDT_R3_DATA | RPL_MASK;
889 TrapFrame->SegDs = KGDT_R3_DATA | RPL_MASK;
890 }
891
892 /* Return the segments */
893 Context->SegGs = TrapFrame->SegGs & 0xFFFF;
894 Context->SegFs = TrapFrame->SegFs & 0xFFFF;
895 Context->SegEs = TrapFrame->SegEs & 0xFFFF;
896 Context->SegDs = TrapFrame->SegDs & 0xFFFF;
897 }
898 }
899
900 /* Handle the simple registers */
901 if ((Context->ContextFlags & CONTEXT_INTEGER) == CONTEXT_INTEGER)
902 {
903 /* Return them directly */
904 Context->Eax = TrapFrame->Eax;
905 Context->Ebx = TrapFrame->Ebx;
906 Context->Ecx = TrapFrame->Ecx;
907 Context->Edx = TrapFrame->Edx;
908 Context->Esi = TrapFrame->Esi;
909 Context->Edi = TrapFrame->Edi;
910 }
911
912 /* Handle extended registers */
913 if (((Context->ContextFlags & CONTEXT_EXTENDED_REGISTERS) ==
914 CONTEXT_EXTENDED_REGISTERS) &&
915 ((TrapFrame->SegCs & MODE_MASK) == UserMode))
916 {
917 /* Get the FX Save Area */
918 FxSaveArea = (PFX_SAVE_AREA)(TrapFrame + 1);
919
920 /* Make sure NPX is present */
921 if (KeI386NpxPresent)
922 {
923 /* Future use */
924 }
925
926 /* Old code */
927 FxSaveArea = KiGetFpuState(KeGetCurrentThread());
928 if (FxSaveArea != NULL)
929 {
930 memcpy(Context->ExtendedRegisters, &FxSaveArea->U.FxArea,
931 min(sizeof (Context->ExtendedRegisters), sizeof (FxSaveArea->U.FxArea)) );
932 }
933 else
934 {
935 Context->ContextFlags &= (~CONTEXT_EXTENDED_REGISTERS) | CONTEXT_i386;
936 }
937 }
938
939 /* Handle Floating Point */
940 if (((Context->ContextFlags & CONTEXT_FLOATING_POINT) ==
941 CONTEXT_FLOATING_POINT) &&
942 ((TrapFrame->SegCs & MODE_MASK) == UserMode))
943 {
944 /* Get the FX Save Area */
945 FxSaveArea = (PFX_SAVE_AREA)(TrapFrame + 1);
946
947 /* Make sure we have an NPX */
948 if (KeI386NpxPresent)
949 {
950 /* Future use */
951 }
952 else
953 {
954 /* Future Use */
955 }
956
957 /* Old code */
958 FxSaveArea = KiGetFpuState(KeGetCurrentThread());
959 if (FxSaveArea != NULL)
960 {
961 KiFxSaveAreaToFloatingSaveArea(&Context->FloatSave, FxSaveArea);
962 }
963 else
964 {
965 Context->ContextFlags &= (~CONTEXT_FLOATING_POINT) | CONTEXT_i386;
966 }
967 }
968
969 /* Handle debug registers */
970 if ((Context->ContextFlags & CONTEXT_DEBUG_REGISTERS) ==
971 CONTEXT_DEBUG_REGISTERS)
972 {
973 /* Copy the debug registers */
974 Context->Dr0 = TrapFrame->Dr0;
975 Context->Dr1 = TrapFrame->Dr1;
976 Context->Dr2 = TrapFrame->Dr2;
977 Context->Dr3 = TrapFrame->Dr3;
978 Context->Dr6 = TrapFrame->Dr6;
979
980 /* For user-mode, only set DR7 if a debugger is active */
981 if (((TrapFrame->SegCs & MODE_MASK) ||
982 (TrapFrame->EFlags & EFLAGS_V86_MASK)) &&
983 (KeGetCurrentThread()->DispatcherHeader.DebugActive))
984 {
985 /* Copy it over */
986 Context->Dr7 = TrapFrame->Dr7;
987 }
988 else
989 {
990 /* Clear it */
991 Context->Dr7 = 0;
992 }
993 }
994 }
995
996 VOID
997 NTAPI
998 KeDumpStackFrames(PULONG Frame)
999 {
1000 PULONG StackBase, StackEnd;
1001 MEMORY_BASIC_INFORMATION mbi;
1002 ULONG ResultLength = sizeof(mbi);
1003 NTSTATUS Status;
1004
1005 DbgPrint("Frames:\n");
1006 _SEH_TRY
1007 {
1008 Status = MiQueryVirtualMemory (
1009 (HANDLE)-1,
1010 Frame,
1011 MemoryBasicInformation,
1012 &mbi,
1013 sizeof(mbi),
1014 &ResultLength );
1015 if ( !NT_SUCCESS(Status) )
1016 {
1017 DPRINT1("Can't dump stack frames: MiQueryVirtualMemory() failed: %x\n", Status );
1018 return;
1019 }
1020
1021 StackBase = Frame;
1022 StackEnd = (PULONG)((ULONG_PTR)mbi.BaseAddress + mbi.RegionSize);
1023
1024 while ( Frame >= StackBase && Frame < StackEnd )
1025 {
1026 ULONG Addr = Frame[1];
1027 if (!KeRosPrintAddress((PVOID)Addr))
1028 DbgPrint("<%X>", Addr);
1029 if ( Addr == 0 || Addr == 0xDEADBEEF )
1030 break;
1031 StackBase = Frame;
1032 Frame = (PULONG)Frame[0];
1033 DbgPrint("\n");
1034 }
1035 }
1036 _SEH_HANDLE
1037 {
1038 }
1039 _SEH_END;
1040 DbgPrint("\n");
1041 }
1042
1043 VOID STDCALL
1044 KeRosDumpStackFrames ( PULONG Frame, ULONG FrameCount )
1045 {
1046 ULONG i=0;
1047 PULONG StackBase, StackEnd;
1048 MEMORY_BASIC_INFORMATION mbi;
1049 ULONG ResultLength = sizeof(mbi);
1050 NTSTATUS Status;
1051
1052 DbgPrint("Frames: ");
1053 _SEH_TRY
1054 {
1055 if ( !Frame )
1056 {
1057 #if defined __GNUC__
1058 __asm__("mov %%ebp, %0" : "=r" (Frame) : );
1059 #elif defined(_MSC_VER)
1060 __asm mov [Frame], ebp
1061 #endif
1062 //Frame = (PULONG)Frame[0]; // step out of KeRosDumpStackFrames
1063 }
1064
1065 Status = MiQueryVirtualMemory (
1066 (HANDLE)-1,
1067 Frame,
1068 MemoryBasicInformation,
1069 &mbi,
1070 sizeof(mbi),
1071 &ResultLength );
1072 if ( !NT_SUCCESS(Status) )
1073 {
1074 DPRINT1("Can't dump stack frames: MiQueryVirtualMemory() failed: %x\n", Status );
1075 return;
1076 }
1077
1078 StackBase = Frame;
1079 StackEnd = (PULONG)((ULONG_PTR)mbi.BaseAddress + mbi.RegionSize);
1080
1081 while ( Frame >= StackBase && Frame < StackEnd && i++ < FrameCount )
1082 {
1083 ULONG Addr = Frame[1];
1084 if (!KeRosPrintAddress((PVOID)Addr))
1085 DbgPrint("<%X>", Addr);
1086 if ( Addr == 0 || Addr == 0xDEADBEEF )
1087 break;
1088 StackBase = Frame;
1089 Frame = (PULONG)Frame[0];
1090 DbgPrint(" ");
1091 }
1092 }
1093 _SEH_HANDLE
1094 {
1095 }
1096 _SEH_END;
1097 DbgPrint("\n");
1098 }
1099
1100 ULONG STDCALL
1101 KeRosGetStackFrames ( PULONG Frames, ULONG FrameCount )
1102 {
1103 ULONG Count = 0;
1104 PULONG StackBase, StackEnd, Frame;
1105 MEMORY_BASIC_INFORMATION mbi;
1106 ULONG ResultLength = sizeof(mbi);
1107 NTSTATUS Status;
1108
1109 _SEH_TRY
1110 {
1111 #if defined __GNUC__
1112 __asm__("mov %%ebp, %0" : "=r" (Frame) : );
1113 #elif defined(_MSC_VER)
1114 __asm mov [Frame], ebp
1115 #endif
1116
1117 Status = MiQueryVirtualMemory (
1118 (HANDLE)-1,
1119 Frame,
1120 MemoryBasicInformation,
1121 &mbi,
1122 sizeof(mbi),
1123 &ResultLength );
1124 if ( !NT_SUCCESS(Status) )
1125 {
1126 DPRINT1("Can't get stack frames: MiQueryVirtualMemory() failed: %x\n", Status );
1127 return 0;
1128 }
1129
1130 StackBase = Frame;
1131 StackEnd = (PULONG)((ULONG_PTR)mbi.BaseAddress + mbi.RegionSize);
1132
1133 while ( Count < FrameCount && Frame >= StackBase && Frame < StackEnd )
1134 {
1135 Frames[Count++] = Frame[1];
1136 StackBase = Frame;
1137 Frame = (PULONG)Frame[0];
1138 }
1139 }
1140 _SEH_HANDLE
1141 {
1142 }
1143 _SEH_END;
1144 return Count;
1145 }
1146
1147 VOID
1148 INIT_FUNCTION
1149 NTAPI
1150 KeInitExceptions(VOID)
1151 {
1152 ULONG i;
1153 USHORT FlippedSelector;
1154
1155 /* Loop the IDT */
1156 for (i = 0; i <= MAXIMUM_IDTVECTOR; i ++)
1157 {
1158 /* Save the current Selector */
1159 FlippedSelector = KiIdt[i].Selector;
1160
1161 /* Flip Selector and Extended Offset */
1162 KiIdt[i].Selector = KiIdt[i].ExtendedOffset;
1163 KiIdt[i].ExtendedOffset = FlippedSelector;
1164 }
1165 }
1166
1167 VOID
1168 NTAPI
1169 KiDispatchException(PEXCEPTION_RECORD ExceptionRecord,
1170 PKEXCEPTION_FRAME ExceptionFrame,
1171 PKTRAP_FRAME TrapFrame,
1172 KPROCESSOR_MODE PreviousMode,
1173 BOOLEAN FirstChance)
1174 {
1175 CONTEXT Context;
1176 KD_CONTINUE_TYPE Action;
1177 ULONG_PTR Stack, NewStack;
1178 ULONG Size;
1179 BOOLEAN UserDispatch = FALSE;
1180 DPRINT("KiDispatchException() called\n");
1181
1182 /* Increase number of Exception Dispatches */
1183 KeGetCurrentPrcb()->KeExceptionDispatchCount++;
1184
1185 /* Set the context flags */
1186 Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
1187
1188 /* Check if User Mode */
1189 if (PreviousMode == UserMode)
1190 {
1191 /* Add the FPU Flag */
1192 Context.ContextFlags |= CONTEXT_FLOATING_POINT;
1193 if (KeI386FxsrPresent) Context.ContextFlags |= CONTEXT_EXTENDED_REGISTERS;
1194 }
1195
1196 /* Get a Context */
1197 KeTrapFrameToContext(TrapFrame, ExceptionFrame, &Context);
1198
1199 /* Handle kernel-mode first, it's simpler */
1200 if (PreviousMode == KernelMode)
1201 {
1202 /* Check if this is a first-chance exception */
1203 if (FirstChance == TRUE)
1204 {
1205 /* Break into the debugger for the first time */
1206 Action = KdpEnterDebuggerException(ExceptionRecord,
1207 PreviousMode,
1208 &Context,
1209 TrapFrame,
1210 TRUE,
1211 TRUE);
1212
1213 /* If the debugger said continue, then continue */
1214 if (Action == kdContinue) goto Handled;
1215
1216 /* If the Debugger couldn't handle it, dispatch the exception */
1217 if (RtlDispatchException(ExceptionRecord, &Context))
1218 {
1219 /* It was handled by an exception handler, continue */
1220 goto Handled;
1221 }
1222 }
1223
1224 /* This is a second-chance exception, only for the debugger */
1225 Action = KdpEnterDebuggerException(ExceptionRecord,
1226 PreviousMode,
1227 &Context,
1228 TrapFrame,
1229 FALSE,
1230 FALSE);
1231
1232 /* If the debugger said continue, then continue */
1233 if (Action == kdContinue) goto Handled;
1234
1235 /* Third strike; you're out */
1236 KEBUGCHECKWITHTF(KMODE_EXCEPTION_NOT_HANDLED,
1237 ExceptionRecord->ExceptionCode,
1238 (ULONG_PTR)ExceptionRecord->ExceptionAddress,
1239 ExceptionRecord->ExceptionInformation[0],
1240 ExceptionRecord->ExceptionInformation[1],
1241 TrapFrame);
1242 }
1243 else
1244 {
1245 /* User mode exception, was it first-chance? */
1246 if (FirstChance)
1247 {
1248 /* Enter Debugger if available */
1249 Action = KdpEnterDebuggerException(ExceptionRecord,
1250 PreviousMode,
1251 &Context,
1252 TrapFrame,
1253 TRUE,
1254 TRUE);
1255
1256 /* Exit if we're continuing */
1257 if (Action == kdContinue) goto Handled;
1258
1259 /* FIXME: Forward exception to user mode debugger */
1260
1261 /* Set up the user-stack */
1262 _SEH_TRY
1263 {
1264 /* Align context size and get stack pointer */
1265 Size = (sizeof(CONTEXT) + 3) & ~3;
1266 Stack = (Context.Esp & ~3) - Size;
1267 DPRINT("Stack: %lx\n", Stack);
1268
1269 /* Probe stack and copy Context */
1270 ProbeForWrite((PVOID)Stack, Size, sizeof(ULONG));
1271 RtlCopyMemory((PVOID)Stack, &Context, sizeof(CONTEXT));
1272
1273 /* Align exception record size and get stack pointer */
1274 Size = (sizeof(EXCEPTION_RECORD) -
1275 (EXCEPTION_MAXIMUM_PARAMETERS - ExceptionRecord->NumberParameters) *
1276 sizeof(ULONG) + 3) & ~3;
1277 NewStack = Stack - Size;
1278 DPRINT("NewStack: %lx\n", NewStack);
1279
1280 /* Probe stack and copy exception record. Don't forget to add the two params */
1281 ProbeForWrite((PVOID)(NewStack - 2 * sizeof(ULONG_PTR)),
1282 Size + 2 * sizeof(ULONG_PTR),
1283 sizeof(ULONG));
1284 RtlCopyMemory((PVOID)NewStack, ExceptionRecord, Size);
1285
1286 /* Now write the two params for the user-mode dispatcher */
1287 *(PULONG_PTR)(NewStack - 1 * sizeof(ULONG_PTR)) = Stack;
1288 *(PULONG_PTR)(NewStack - 2 * sizeof(ULONG_PTR)) = NewStack;
1289
1290 /* Set new Stack Pointer */
1291 KiEspToTrapFrame(TrapFrame, NewStack - 2 * sizeof(ULONG_PTR));
1292
1293 /* Set EIP to the User-mode Dispathcer */
1294 TrapFrame->Eip = (ULONG)KeUserExceptionDispatcher;
1295 UserDispatch = TRUE;
1296 _SEH_LEAVE;
1297 }
1298 _SEH_HANDLE
1299 {
1300 /* Do second-chance */
1301 }
1302 _SEH_END;
1303 }
1304
1305 /* If we dispatch to user, return now */
1306 if (UserDispatch) return;
1307
1308 /* FIXME: Forward the exception to the debugger for 2nd chance */
1309
1310 /* 3rd strike, kill the thread */
1311 DPRINT1("Unhandled UserMode exception, terminating thread\n");
1312 ZwTerminateThread(NtCurrentThread(), ExceptionRecord->ExceptionCode);
1313 KEBUGCHECKWITHTF(KMODE_EXCEPTION_NOT_HANDLED,
1314 ExceptionRecord->ExceptionCode,
1315 (ULONG_PTR)ExceptionRecord->ExceptionAddress,
1316 ExceptionRecord->ExceptionInformation[0],
1317 ExceptionRecord->ExceptionInformation[1],
1318 TrapFrame);
1319 }
1320
1321 Handled:
1322 /* Convert the context back into Trap/Exception Frames */
1323 KeContextToTrapFrame(&Context,
1324 NULL,
1325 TrapFrame,
1326 Context.ContextFlags,
1327 PreviousMode);
1328 return;
1329 }
1330
1331 /*
1332 * @implemented
1333 */
1334 NTSTATUS
1335 NTAPI
1336 KeRaiseUserException(IN NTSTATUS ExceptionCode)
1337 {
1338 ULONG OldEip;
1339 PKTHREAD Thread = KeGetCurrentThread();
1340
1341 /* Make sure we can access the TEB */
1342 _SEH_TRY
1343 {
1344 Thread->Teb->ExceptionCode = ExceptionCode;
1345 }
1346 _SEH_HANDLE
1347 {
1348 return(ExceptionCode);
1349 }
1350 _SEH_END;
1351
1352 /* Get the old EIP */
1353 OldEip = Thread->TrapFrame->Eip;
1354
1355 /* Change it to the user-mode dispatcher */
1356 Thread->TrapFrame->Eip = (ULONG_PTR)KeRaiseUserExceptionDispatcher;
1357
1358 /* Return the old EIP */
1359 return((NTSTATUS)OldEip);
1360 }
1361