2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS kernel
4 * FILE: ntoskrnl/ke/i386/exp.c
5 * PURPOSE: Handling exceptions
7 * PROGRAMMERS: David Welch (welch@cwcom.net)
8 * Skywing (skywing@valhallalegends.com)
11 /* INCLUDES *****************************************************************/
16 #include <internal/debug.h>
18 /* GLOBALS *****************************************************************/
20 #define FLAG_IF (1<<9)
23 #define STR(x) _STR(x)
26 # define ARRAY_SIZE(x) (sizeof (x) / sizeof (x[0]))
29 extern void KiSystemService(void);
30 extern void interrupt_handler2d(void);
32 extern VOID
KiTrap0(VOID
);
33 extern VOID
KiTrap1(VOID
);
34 extern VOID
KiTrap2(VOID
);
35 extern VOID
KiTrap3(VOID
);
36 extern VOID
KiTrap4(VOID
);
37 extern VOID
KiTrap5(VOID
);
38 extern VOID
KiTrap6(VOID
);
39 extern VOID
KiTrap7(VOID
);
40 extern VOID
KiTrap8(VOID
);
41 extern VOID
KiTrap9(VOID
);
42 extern VOID
KiTrap10(VOID
);
43 extern VOID
KiTrap11(VOID
);
44 extern VOID
KiTrap12(VOID
);
45 extern VOID
KiTrap13(VOID
);
46 extern VOID
KiTrap14(VOID
);
47 extern VOID
KiTrap15(VOID
);
48 extern VOID
KiTrap16(VOID
);
49 extern VOID
KiTrap17(VOID
);
50 extern VOID
KiTrap18(VOID
);
51 extern VOID
KiTrap19(VOID
);
52 extern VOID
KiTrapUnknown(VOID
);
54 extern ULONG init_stack
;
55 extern ULONG init_stack_top
;
57 extern BOOLEAN Ke386NoExecute
;
59 static char *ExceptionTypeStrings
[] =
66 "BOUND range exceeded",
68 "No Math Coprocessor",
72 "Segment Not Present",
73 "Stack Segment Fault",
83 NTSTATUS ExceptionToNtStatus
[] =
85 STATUS_INTEGER_DIVIDE_BY_ZERO
,
87 STATUS_ACCESS_VIOLATION
,
89 STATUS_INTEGER_OVERFLOW
,
90 STATUS_ARRAY_BOUNDS_EXCEEDED
,
91 STATUS_ILLEGAL_INSTRUCTION
,
92 STATUS_FLOAT_INVALID_OPERATION
,
93 STATUS_ACCESS_VIOLATION
,
94 STATUS_ACCESS_VIOLATION
,
95 STATUS_ACCESS_VIOLATION
,
96 STATUS_ACCESS_VIOLATION
,
97 STATUS_STACK_OVERFLOW
,
98 STATUS_ACCESS_VIOLATION
,
99 STATUS_ACCESS_VIOLATION
,
100 STATUS_ACCESS_VIOLATION
, /* RESERVED */
101 STATUS_FLOAT_INVALID_OPERATION
, /* Should not be used, the FPU can give more specific info */
102 STATUS_DATATYPE_MISALIGNMENT
,
103 STATUS_ACCESS_VIOLATION
,
104 STATUS_FLOAT_MULTIPLE_TRAPS
,
107 /* FUNCTIONS ****************************************************************/
109 #if defined(DBG) || defined(KDBG)
111 KeRosPrintAddress(PVOID address
)
113 return KdbSymPrintAddress(address
);
117 KeRosPrintAddress(PVOID address
)
119 PLIST_ENTRY current_entry
;
120 MODULE_TEXT_SECTION
* current
;
121 extern LIST_ENTRY ModuleTextListHead
;
122 ULONG_PTR RelativeAddress
;
124 current_entry
= ModuleTextListHead
.Flink
;
126 while (current_entry
!= &ModuleTextListHead
&&
127 current_entry
!= NULL
)
130 CONTAINING_RECORD(current_entry
, MODULE_TEXT_SECTION
, ListEntry
);
132 if (address
>= (PVOID
)current
->Base
&&
133 address
< (PVOID
)(current
->Base
+ current
->Length
))
135 RelativeAddress
= (ULONG_PTR
) address
- current
->Base
;
136 DbgPrint("<%ws: %x>", current
->Name
, RelativeAddress
);
139 current_entry
= current_entry
->Flink
;
146 KiKernelTrapHandler(PKTRAP_FRAME Tf
, ULONG ExceptionNr
, PVOID Cr2
)
150 Er
.ExceptionFlags
= 0;
151 Er
.ExceptionRecord
= NULL
;
152 Er
.ExceptionAddress
= (PVOID
)Tf
->Eip
;
154 if (ExceptionNr
== 14)
156 Er
.ExceptionCode
= STATUS_ACCESS_VIOLATION
;
157 Er
.NumberParameters
= 2;
158 Er
.ExceptionInformation
[0] = Tf
->ErrorCode
& 0x1;
159 Er
.ExceptionInformation
[1] = (ULONG
)Cr2
;
163 if (ExceptionNr
< ARRAY_SIZE(ExceptionToNtStatus
))
165 Er
.ExceptionCode
= ExceptionToNtStatus
[ExceptionNr
];
169 Er
.ExceptionCode
= STATUS_ACCESS_VIOLATION
;
171 Er
.NumberParameters
= 0;
174 /* FIXME: Which exceptions are noncontinuable? */
175 Er
.ExceptionFlags
= 0;
177 KiDispatchException(&Er
, 0, Tf
, KernelMode
, TRUE
);
183 KiDoubleFaultHandler(VOID
)
189 ULONG ExceptionNr
= 8;
195 static PVOID StackTrace
[MM_STACK_SIZE
/ sizeof(PVOID
)];
196 static ULONG StackRepeatCount
[MM_STACK_SIZE
/ sizeof(PVOID
)];
197 static ULONG StackRepeatLength
[MM_STACK_SIZE
/ sizeof(PVOID
)];
202 OldTss
= KeGetCurrentKPCR()->TSS
;
207 if (PsGetCurrentThread() != NULL
&&
208 PsGetCurrentThread()->ThreadsProcess
!= NULL
)
211 PsGetCurrentThread()->ThreadsProcess
->Pcb
.DirectoryTableBase
.QuadPart
;
219 * Check for stack underflow
221 if (PsGetCurrentThread() != NULL
&&
222 Esp0
< (ULONG
)PsGetCurrentThread()->Tcb
.StackLimit
)
224 DbgPrint("Stack underflow (tf->esp %x Limit %x)\n",
225 Esp0
, (ULONG
)PsGetCurrentThread()->Tcb
.StackLimit
);
230 * Print out the CPU registers
232 if (ExceptionNr
< ARRAY_SIZE(ExceptionTypeStrings
))
234 DbgPrint("%s Exception: %d(%x)\n", ExceptionTypeStrings
[ExceptionNr
],
239 DbgPrint("Exception: %d(%x)\n", ExceptionNr
, 0);
241 DbgPrint("CS:EIP %x:%x ", OldTss
->Cs
, OldTss
->Eip
);
242 KeRosPrintAddress((PVOID
)OldTss
->Eip
);
244 DbgPrint("cr2 %x cr3 %x ", cr2
, OldCr3
);
245 DbgPrint("Proc: %x ",PsGetCurrentProcess());
246 if (PsGetCurrentProcess() != NULL
)
248 DbgPrint("Pid: %x <", PsGetCurrentProcess()->UniqueProcessId
);
249 DbgPrint("%.8s> ", PsGetCurrentProcess()->ImageFileName
);
251 if (PsGetCurrentThread() != NULL
)
253 DbgPrint("Thrd: %x Tid: %x",
254 PsGetCurrentThread(),
255 PsGetCurrentThread()->Cid
.UniqueThread
);
258 DbgPrint("DS %x ES %x FS %x GS %x\n", OldTss
->Ds
, OldTss
->Es
,
259 OldTss
->Fs
, OldTss
->Gs
);
260 DbgPrint("EAX: %.8x EBX: %.8x ECX: %.8x\n", OldTss
->Eax
, OldTss
->Ebx
,
262 DbgPrint("EDX: %.8x EBP: %.8x ESI: %.8x\n ESP: %.8x", OldTss
->Edx
,
263 OldTss
->Ebp
, OldTss
->Esi
, Esp0
);
264 DbgPrint("EDI: %.8x EFLAGS: %.8x ", OldTss
->Edi
, OldTss
->Eflags
);
265 if (OldTss
->Cs
== KERNEL_CS
)
267 DbgPrint("kESP %.8x ", Esp0
);
268 if (PsGetCurrentThread() != NULL
)
270 DbgPrint("kernel stack base %x\n",
271 PsGetCurrentThread()->Tcb
.StackLimit
);
277 DbgPrint("User ESP %.8x\n", OldTss
->Esp
);
279 if ((OldTss
->Cs
& 0xffff) == KERNEL_CS
)
281 if (PsGetCurrentThread() != NULL
)
283 StackLimit
= (ULONG
)PsGetCurrentThread()->Tcb
.StackBase
;
284 StackBase
= (ULONG
)PsGetCurrentThread()->Tcb
.StackLimit
;
288 StackLimit
= (ULONG
)init_stack_top
;
289 StackBase
= (ULONG
)init_stack
;
293 Change to an #if 0 to reduce the amount of information printed on
294 a recursive stack trace.
297 DbgPrint("Frames: ");
298 Frame
= (PULONG
)OldTss
->Ebp
;
299 while (Frame
!= NULL
&& (ULONG
)Frame
>= StackBase
)
301 KeRosPrintAddress((PVOID
)Frame
[1]);
302 Frame
= (PULONG
)Frame
[0];
306 DbgPrint("Frames: ");
308 Frame
= (PULONG
)OldTss
->Ebp
;
309 while (Frame
!= NULL
&& (ULONG
)Frame
>= StackBase
)
311 StackTrace
[i
] = (PVOID
)Frame
[1];
312 Frame
= (PULONG
)Frame
[0];
318 while (i
< TraceLength
)
320 StackRepeatCount
[i
] = 0;
323 while ((j
- i
) <= (TraceLength
- j
) && FoundRepeat
== FALSE
)
325 if (memcmp(&StackTrace
[i
], &StackTrace
[j
],
326 (j
- i
) * sizeof(PVOID
)) == 0)
328 StackRepeatCount
[i
] = 2;
329 StackRepeatLength
[i
] = j
- i
;
337 if (FoundRepeat
== FALSE
)
342 j
= j
+ StackRepeatLength
[i
];
343 while ((TraceLength
- j
) >= StackRepeatLength
[i
] &&
346 if (memcmp(&StackTrace
[i
], &StackTrace
[j
],
347 StackRepeatLength
[i
] * sizeof(PVOID
)) == 0)
349 StackRepeatCount
[i
]++;
350 j
= j
+ StackRepeatLength
[i
];
361 while (i
< TraceLength
)
363 if (StackRepeatCount
[i
] == 0)
365 KeRosPrintAddress(StackTrace
[i
]);
371 if (StackRepeatLength
[i
] == 0)
375 for (j
= 0; j
< StackRepeatLength
[i
]; j
++)
377 KeRosPrintAddress(StackTrace
[i
+ j
]);
379 DbgPrint("}*%d", StackRepeatCount
[i
]);
380 i
= i
+ StackRepeatLength
[i
] * StackRepeatCount
[i
];
392 KiDumpTrapFrame(PKTRAP_FRAME Tf
, ULONG Parameter1
, ULONG Parameter2
)
397 ULONG ExceptionNr
= (ULONG
)Tf
->DebugArgMark
;
398 ULONG cr2
= (ULONG
)Tf
->DebugPointer
;
403 * Print out the CPU registers
405 if (ExceptionNr
< ARRAY_SIZE(ExceptionTypeStrings
))
407 DbgPrint("%s Exception: %d(%x)\n", ExceptionTypeStrings
[ExceptionNr
],
408 ExceptionNr
, Tf
->ErrorCode
&0xffff);
412 DbgPrint("Exception: %d(%x)\n", ExceptionNr
, Tf
->ErrorCode
&0xffff);
414 DbgPrint("Processor: %d CS:EIP %x:%x ", KeGetCurrentProcessorNumber(),
415 Tf
->Cs
&0xffff, Tf
->Eip
);
416 KeRosPrintAddress((PVOID
)Tf
->Eip
);
418 Ke386GetPageTableDirectory(cr3_
);
419 DbgPrint("cr2 %x cr3 %x ", cr2
, cr3_
);
420 DbgPrint("Proc: %x ",PsGetCurrentProcess());
421 if (PsGetCurrentProcess() != NULL
)
423 DbgPrint("Pid: %x <", PsGetCurrentProcess()->UniqueProcessId
);
424 DbgPrint("%.8s> ", PsGetCurrentProcess()->ImageFileName
);
426 if (PsGetCurrentThread() != NULL
)
428 DbgPrint("Thrd: %x Tid: %x",
429 PsGetCurrentThread(),
430 PsGetCurrentThread()->Cid
.UniqueThread
);
433 DbgPrint("DS %x ES %x FS %x GS %x\n", Tf
->Ds
&0xffff, Tf
->Es
&0xffff,
434 Tf
->Fs
&0xffff, Tf
->Gs
&0xfff);
435 DbgPrint("EAX: %.8x EBX: %.8x ECX: %.8x\n", Tf
->Eax
, Tf
->Ebx
, Tf
->Ecx
);
436 DbgPrint("EDX: %.8x EBP: %.8x ESI: %.8x ESP: %.8x\n", Tf
->Edx
,
437 Tf
->Ebp
, Tf
->Esi
, Esp0
);
438 DbgPrint("EDI: %.8x EFLAGS: %.8x ", Tf
->Edi
, Tf
->Eflags
);
439 if ((Tf
->Cs
&0xffff) == KERNEL_CS
)
441 DbgPrint("kESP %.8x ", Esp0
);
442 if (PsGetCurrentThread() != NULL
)
444 DbgPrint("kernel stack base %x\n",
445 PsGetCurrentThread()->Tcb
.StackLimit
);
450 if (PsGetCurrentThread() != NULL
)
452 StackLimit
= (ULONG
)PsGetCurrentThread()->Tcb
.StackBase
;
456 StackLimit
= (ULONG
)init_stack_top
;
460 * Dump the stack frames
462 KeDumpStackFrames((PULONG
)Tf
->Ebp
);
466 KiTrapHandler(PKTRAP_FRAME Tf
, ULONG ExceptionNr
)
468 * FUNCTION: Called by the lowlevel execption handlers to print an amusing
469 * message and halt the computer
471 * Complete CPU context
478 /* Store the exception number in an unused field in the trap frame. */
479 Tf
->DebugArgMark
= (PVOID
)ExceptionNr
;
481 /* Use the address of the trap frame as approximation to the ring0 esp */
482 Esp0
= (ULONG
)&Tf
->Eip
;
486 Tf
->DebugPointer
= (PVOID
)cr2
;
488 if (ExceptionNr
== 14 && Tf
->Eflags
& FLAG_IF
)
490 Ke386EnableInterrupts();
494 * If this was a V86 mode exception then handle it specially
496 if (Tf
->Eflags
& (1 << 17))
498 return(KeV86Exception(ExceptionNr
, Tf
, cr2
));
502 * Check for stack underflow, this may be obsolete
504 if (PsGetCurrentThread() != NULL
&&
505 Esp0
< (ULONG
)PsGetCurrentThread()->Tcb
.StackLimit
)
507 DbgPrint("Stack underflow (tf->esp %x Limit %x)\n",
508 Esp0
, (ULONG
)PsGetCurrentThread()->Tcb
.StackLimit
);
513 * Maybe handle the page fault and return
515 if (ExceptionNr
== 14)
517 if (Ke386NoExecute
&& Tf
->ErrorCode
& 0x10 && cr2
>= KERNEL_BASE
)
519 KEBUGCHECKWITHTF(ATTEMPTED_EXECUTE_OF_NOEXECUTE_MEMORY
, 0, 0, 0, 0, Tf
);
521 Status
= MmPageFault(Tf
->Cs
&0xffff,
526 if (NT_SUCCESS(Status
))
533 * Check for a breakpoint that was only for the attention of the debugger.
535 if (ExceptionNr
== 3 && Tf
->Eip
== ((ULONG
)DbgBreakPointNoBugCheck
) + 1)
538 EIP is already adjusted by the processor to point to the instruction
539 after the breakpoint.
545 * Try to handle device-not-present, math-fault and xmm-fault exceptions.
547 if (ExceptionNr
== 7 || ExceptionNr
== 16 || ExceptionNr
== 19)
549 Status
= KiHandleFpuFault(Tf
, ExceptionNr
);
550 if (NT_SUCCESS(Status
))
557 * Handle user exceptions differently
559 if ((Tf
->Cs
& 0xFFFF) == USER_CS
)
561 return(KiUserTrapHandler(Tf
, ExceptionNr
, (PVOID
)cr2
));
565 return(KiKernelTrapHandler(Tf
, ExceptionNr
, (PVOID
)cr2
));
570 KeDumpStackFrames(PULONG Frame
)
572 PULONG StackBase
, StackEnd
;
573 MEMORY_BASIC_INFORMATION mbi
;
574 ULONG ResultLength
= sizeof(mbi
);
577 DbgPrint("Frames:\n");
580 Status
= MiQueryVirtualMemory (
583 MemoryBasicInformation
,
587 if ( !NT_SUCCESS(Status
) )
589 DPRINT1("Can't dump stack frames: NtQueryVirtualMemory() failed: %x\n", Status
);
594 StackEnd
= mbi
.BaseAddress
+ mbi
.RegionSize
;
596 while ( Frame
>= StackBase
&& Frame
< StackEnd
)
598 ULONG Addr
= Frame
[1];
599 if (!KeRosPrintAddress((PVOID
)Addr
))
600 DbgPrint("<%X>", Addr
);
601 if ( Addr
== 0 || Addr
== 0xDEADBEEF )
604 Frame
= (PULONG
)Frame
[0];
616 KeRosDumpStackFrames ( PULONG Frame
, ULONG FrameCount
)
619 PULONG StackBase
, StackEnd
;
620 MEMORY_BASIC_INFORMATION mbi
;
621 ULONG ResultLength
= sizeof(mbi
);
624 DbgPrint("Frames: ");
630 __asm__("mov %%ebp, %%ebx" : "=b" (Frame
) : );
631 #elif defined(_MSC_VER)
632 __asm mov
[Frame
], ebp
634 //Frame = (PULONG)Frame[0]; // step out of KeRosDumpStackFrames
637 Status
= MiQueryVirtualMemory (
640 MemoryBasicInformation
,
644 if ( !NT_SUCCESS(Status
) )
646 DPRINT1("Can't dump stack frames: NtQueryVirtualMemory() failed: %x\n", Status
);
651 StackEnd
= mbi
.BaseAddress
+ mbi
.RegionSize
;
653 while ( Frame
>= StackBase
&& Frame
< StackEnd
&& i
++ < FrameCount
)
655 ULONG Addr
= Frame
[1];
656 if (!KeRosPrintAddress((PVOID
)Addr
))
657 DbgPrint("<%X>", Addr
);
658 if ( Addr
== 0 || Addr
== 0xDEADBEEF )
661 Frame
= (PULONG
)Frame
[0];
673 KeRosGetStackFrames ( PULONG Frames
, ULONG FrameCount
)
676 PULONG StackBase
, StackEnd
, Frame
;
677 MEMORY_BASIC_INFORMATION mbi
;
678 ULONG ResultLength
= sizeof(mbi
);
684 __asm__("mov %%ebp, %%ebx" : "=b" (Frame
) : );
685 #elif defined(_MSC_VER)
686 __asm mov
[Frame
], ebp
689 Status
= MiQueryVirtualMemory (
692 MemoryBasicInformation
,
696 if ( !NT_SUCCESS(Status
) )
698 DPRINT1("Can't get stack frames: NtQueryVirtualMemory() failed: %x\n", Status
);
703 StackEnd
= mbi
.BaseAddress
+ mbi
.RegionSize
;
705 while ( Count
< FrameCount
&& Frame
>= StackBase
&& Frame
< StackEnd
)
707 Frames
[Count
++] = Frame
[1];
709 Frame
= (PULONG
)Frame
[0];
720 set_system_call_gate(unsigned int sel
, unsigned int func
)
722 DPRINT("sel %x %d\n",sel
,sel
);
723 KiIdt
[sel
].a
= (((int)func
)&0xffff) +
725 KiIdt
[sel
].b
= 0xef00 + (((int)func
)&0xffff0000);
726 DPRINT("idt[sel].b %x\n",KiIdt
[sel
].b
);
729 static void set_interrupt_gate(unsigned int sel
, unsigned int func
)
731 DPRINT("set_interrupt_gate(sel %d, func %x)\n",sel
,func
);
732 KiIdt
[sel
].a
= (((int)func
)&0xffff) +
734 KiIdt
[sel
].b
= 0x8e00 + (((int)func
)&0xffff0000);
737 static void set_trap_gate(unsigned int sel
, unsigned int func
, unsigned int dpl
)
739 DPRINT("set_trap_gate(sel %d, func %x, dpl %d)\n",sel
, func
, dpl
);
741 KiIdt
[sel
].a
= (((int)func
)&0xffff) +
743 KiIdt
[sel
].b
= 0x8f00 + (dpl
<< 13) + (((int)func
)&0xffff0000);
747 set_task_gate(unsigned int sel
, unsigned task_sel
)
749 KiIdt
[sel
].a
= task_sel
<< 16;
750 KiIdt
[sel
].b
= 0x8500;
754 KeInitExceptions(VOID
)
756 * FUNCTION: Initalize CPU exception handling
761 DPRINT("KeInitExceptions()\n");
764 * Set up the other gates
766 set_trap_gate(0, (ULONG
)KiTrap0
, 0);
767 set_trap_gate(1, (ULONG
)KiTrap1
, 0);
768 set_trap_gate(2, (ULONG
)KiTrap2
, 0);
769 set_trap_gate(3, (ULONG
)KiTrap3
, 3);
770 set_trap_gate(4, (ULONG
)KiTrap4
, 0);
771 set_trap_gate(5, (ULONG
)KiTrap5
, 0);
772 set_trap_gate(6, (ULONG
)KiTrap6
, 0);
773 set_trap_gate(7, (ULONG
)KiTrap7
, 0);
774 set_task_gate(8, TRAP_TSS_SELECTOR
);
775 set_trap_gate(9, (ULONG
)KiTrap9
, 0);
776 set_trap_gate(10, (ULONG
)KiTrap10
, 0);
777 set_trap_gate(11, (ULONG
)KiTrap11
, 0);
778 set_trap_gate(12, (ULONG
)KiTrap12
, 0);
779 set_trap_gate(13, (ULONG
)KiTrap13
, 0);
780 set_interrupt_gate(14, (ULONG
)KiTrap14
);
781 set_trap_gate(15, (ULONG
)KiTrap15
, 0);
782 set_trap_gate(16, (ULONG
)KiTrap16
, 0);
783 set_trap_gate(17, (ULONG
)KiTrap17
, 0);
784 set_trap_gate(18, (ULONG
)KiTrap18
, 0);
785 set_trap_gate(19, (ULONG
)KiTrap19
, 0);
787 for (i
= 20; i
< 256; i
++)
789 set_trap_gate(i
,(int)KiTrapUnknown
, 0);
792 set_system_call_gate(0x2d,(int)interrupt_handler2d
);
793 set_system_call_gate(0x2e,(int)KiSystemService
);
800 KeRaiseUserException(IN NTSTATUS ExceptionCode
)
803 PKTHREAD Thread
= KeGetCurrentThread();
806 Thread
->Teb
->ExceptionCode
= ExceptionCode
;
808 return(ExceptionCode
);
811 OldEip
= Thread
->TrapFrame
->Eip
;
812 Thread
->TrapFrame
->Eip
= (ULONG_PTR
)LdrpGetSystemDllRaiseExceptionDispatcher();
813 return((NTSTATUS
)OldEip
);
822 IN PEXCEPTION_RECORD ExceptionRecord
,
824 IN BOOLEAN SearchFrames
)
826 PKTHREAD Thread
= KeGetCurrentThread();
827 PKTRAP_FRAME TrapFrame
= Thread
->TrapFrame
;
828 PKTRAP_FRAME PrevTrapFrame
= (PKTRAP_FRAME
)TrapFrame
->Edx
;
830 KeGetCurrentKPCR()->Tib
.ExceptionList
= TrapFrame
->ExceptionList
;
832 KiDispatchException(ExceptionRecord
,
838 /* Restore the user context */
839 Thread
->TrapFrame
= PrevTrapFrame
;
840 __asm__("mov %%ebx, %%esp;\n" "jmp _KiServiceExit": : "b" (TrapFrame
));
842 /* We never get here */
843 return(STATUS_SUCCESS
);