[NTOSKRNL] Handle some more KeFeatureFlags in amd64/cpu.c and set RtlpUse16ByteSLists
[reactos.git] / reactos / lib / rtl / amd64 / unwind.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS system libraries
4 * PURPOSE: Unwinding related functions
5 * PROGRAMMER: Timo Kreuzer (timo.kreuzer@reactos.org)
6 */
7
8 /* INCLUDES *****************************************************************/
9
10 #include <rtl.h>
11
12 #define NDEBUG
13 #include <debug.h>
14
15 #define UNWIND_HISTORY_TABLE_NONE 0
16 #define UNWIND_HISTORY_TABLE_GLOBAL 1
17 #define UNWIND_HISTORY_TABLE_LOCAL 2
18
19 #define UWOP_PUSH_NONVOL 0
20 #define UWOP_ALLOC_LARGE 1
21 #define UWOP_ALLOC_SMALL 2
22 #define UWOP_SET_FPREG 3
23 #define UWOP_SAVE_NONVOL 4
24 #define UWOP_SAVE_NONVOL_FAR 5
25 #define UWOP_SAVE_XMM 6
26 #define UWOP_SAVE_XMM_FAR 7
27 #define UWOP_SAVE_XMM128 8
28 #define UWOP_SAVE_XMM128_FAR 9
29 #define UWOP_PUSH_MACHFRAME 10
30
31 #define UNW_FLAG_NHANDLER 0
32 #define UNW_FLAG_EHANDLER 1
33 #define UNW_FLAG_UHANDLER 2
34 #define UNW_FLAG_CHAININFO 4
35
36 typedef unsigned char UBYTE;
37
38 typedef union _UNWIND_CODE
39 {
40 struct
41 {
42 UBYTE CodeOffset;
43 UBYTE UnwindOp:4;
44 UBYTE OpInfo:4;
45 };
46 USHORT FrameOffset;
47 } UNWIND_CODE, *PUNWIND_CODE;
48
49 typedef struct _UNWIND_INFO
50 {
51 UBYTE Version:3;
52 UBYTE Flags:5;
53 UBYTE SizeOfProlog;
54 UBYTE CountOfCodes;
55 UBYTE FrameRegister:4;
56 UBYTE FrameOffset:4;
57 UNWIND_CODE UnwindCode[1];
58 /* union {
59 OPTIONAL ULONG ExceptionHandler;
60 OPTIONAL ULONG FunctionEntry;
61 };
62 OPTIONAL ULONG ExceptionData[];
63 */
64 } UNWIND_INFO, *PUNWIND_INFO;
65
66 /* FUNCTIONS *****************************************************************/
67
68 /*! RtlLookupFunctionTable
69 * \brief Locates the table of RUNTIME_FUNCTION entries for a code address.
70 * \param ControlPc
71 * Address of the code, for which the table should be searched.
72 * \param ImageBase
73 * Pointer to a DWORD64 that receives the base address of the
74 * corresponding executable image.
75 * \param Length
76 * Pointer to an ULONG that receives the number of table entries
77 * present in the table.
78 */
79 PRUNTIME_FUNCTION
80 NTAPI
81 RtlLookupFunctionTable(
82 IN DWORD64 ControlPc,
83 OUT PDWORD64 ImageBase,
84 OUT PULONG Length)
85 {
86 PVOID Table;
87 ULONG Size;
88
89 /* Find corresponding file header from code address */
90 if (!RtlPcToFileHeader((PVOID)ControlPc, (PVOID*)ImageBase))
91 {
92 /* Nothing found */
93 return NULL;
94 }
95
96 /* Locate the exception directory */
97 Table = RtlImageDirectoryEntryToData((PVOID)*ImageBase,
98 TRUE,
99 IMAGE_DIRECTORY_ENTRY_EXCEPTION,
100 &Size);
101
102 /* Return the number of entries */
103 *Length = Size / sizeof(RUNTIME_FUNCTION);
104
105 /* Return the address of the table */
106 return Table;
107 }
108
109 /*! RtlLookupFunctionEntry
110 * \brief Locates the RUNTIME_FUNCTION entry corresponding to a code address.
111 * \ref http://msdn.microsoft.com/en-us/library/ms680597(VS.85).aspx
112 * \todo Implement HistoryTable
113 */
114 PRUNTIME_FUNCTION
115 NTAPI
116 RtlLookupFunctionEntry(
117 IN DWORD64 ControlPc,
118 OUT PDWORD64 ImageBase,
119 OUT PUNWIND_HISTORY_TABLE HistoryTable)
120 {
121 PRUNTIME_FUNCTION FunctionTable, FunctionEntry;
122 ULONG TableLength;
123 ULONG IndexLo, IndexHi, IndexMid;
124
125 /* Find the corresponding table */
126 FunctionTable = RtlLookupFunctionTable(ControlPc, ImageBase, &TableLength);
127
128 /* Fail, if no table is found */
129 if (!FunctionTable)
130 {
131 return NULL;
132 }
133
134 /* Use relative virtual address */
135 ControlPc -= *ImageBase;
136
137 /* Do a binary search */
138 IndexLo = 0;
139 IndexHi = TableLength;
140 while (IndexHi > IndexLo)
141 {
142 IndexMid = (IndexLo + IndexHi) / 2;
143 FunctionEntry = &FunctionTable[IndexMid];
144
145 if (ControlPc < FunctionEntry->BeginAddress)
146 {
147 /* Continue search in lower half */
148 IndexHi = IndexMid;
149 }
150 else if (ControlPc >= FunctionEntry->EndAddress)
151 {
152 /* Continue search in upper half */
153 IndexLo = IndexMid + 1;
154 }
155 else
156 {
157 /* ControlPc is within limits, return entry */
158 return FunctionEntry;
159 }
160 }
161
162 /* Nothing found, return NULL */
163 return NULL;
164 }
165
166 BOOLEAN
167 NTAPI
168 RtlAddFunctionTable(
169 IN PRUNTIME_FUNCTION FunctionTable,
170 IN DWORD EntryCount,
171 IN DWORD64 BaseAddress)
172 {
173 UNIMPLEMENTED;
174 return FALSE;
175 }
176
177 BOOLEAN
178 NTAPI
179 RtlDeleteFunctionTable(
180 IN PRUNTIME_FUNCTION FunctionTable)
181 {
182 UNIMPLEMENTED;
183 return FALSE;
184 }
185
186 BOOLEAN
187 NTAPI
188 RtlInstallFunctionTableCallback(
189 IN DWORD64 TableIdentifier,
190 IN DWORD64 BaseAddress,
191 IN DWORD Length,
192 IN PGET_RUNTIME_FUNCTION_CALLBACK Callback,
193 IN PVOID Context,
194 IN PCWSTR OutOfProcessCallbackDll)
195 {
196 UNIMPLEMENTED;
197 return FALSE;
198 }
199
200 void
201 FORCEINLINE
202 SetReg(PCONTEXT Context, BYTE Reg, DWORD64 Value)
203 {
204 ((DWORD64*)(&Context->Rax))[Reg] = Value;
205 }
206
207 DWORD64
208 FORCEINLINE
209 GetReg(PCONTEXT Context, BYTE Reg)
210 {
211 return ((DWORD64*)(&Context->Rax))[Reg];
212 }
213
214 void
215 FORCEINLINE
216 PopReg(PCONTEXT Context, BYTE Reg)
217 {
218 DWORD64 Value = *(DWORD64*)Context->Rsp;
219 Context->Rsp += 8;
220 SetReg(Context, Reg, Value);
221 }
222
223 /*! RtlpTryToUnwindEpilog
224 * \brief Helper function that tries to unwind epilog instructions.
225 * \return TRUE if we have been in an epilog and it could be unwound.
226 * FALSE if the instructions were not allowed for an epilog.
227 * \ref
228 * http://msdn.microsoft.com/en-us/library/8ydc79k6(VS.80).aspx
229 * http://msdn.microsoft.com/en-us/library/tawsa7cb.aspx
230 * \todo
231 * - Test and compare with Windows behaviour
232 */
233 BOOLEAN
234 static
235 __inline
236 RtlpTryToUnwindEpilog(
237 PCONTEXT Context,
238 ULONG64 ImageBase,
239 PRUNTIME_FUNCTION FunctionEntry)
240 {
241 CONTEXT LocalContext;
242 BYTE *InstrPtr;
243 DWORD Instr;
244 BYTE Reg, Mod;
245 ULONG64 EndAddress;
246
247 /* Make a local copy of the context */
248 LocalContext = *Context;
249
250 InstrPtr = (BYTE*)LocalContext.Rip;
251
252 /* Check if first instruction of epilog is "add rsp, x" */
253 Instr = *(DWORD*)InstrPtr;
254 if ( (Instr & 0x00fffdff) == 0x00c48148 )
255 {
256 if ( (Instr & 0x0000ff00) == 0x8300 )
257 {
258 /* This is "add rsp, 0x??" */
259 LocalContext.Rsp += Instr >> 24;
260 InstrPtr += 4;
261 }
262 else
263 {
264 /* This is "add rsp, 0x???????? */
265 LocalContext.Rsp += *(DWORD*)(InstrPtr + 3);
266 InstrPtr += 7;
267 }
268 }
269 /* Check if first instruction of epilog is "lea rsp, ..." */
270 else if ( (Instr & 0x38fffe) == 0x208d48 )
271 {
272 /* Get the register */
273 Reg = ((Instr << 8) | (Instr >> 16)) & 0x7;
274
275 LocalContext.Rsp = GetReg(&LocalContext, Reg);
276
277 /* Get adressing mode */
278 Mod = (Instr >> 22) & 0x3;
279 if (Mod == 0)
280 {
281 /* No displacement */
282 InstrPtr += 3;
283 }
284 else if (Mod == 1)
285 {
286 /* 1 byte displacement */
287 LocalContext.Rsp += Instr >> 24;
288 InstrPtr += 4;
289 }
290 else if (Mod == 2)
291 {
292 /* 4 bytes displacement */
293 LocalContext.Rsp += *(DWORD*)(InstrPtr + 3);
294 InstrPtr += 7;
295 }
296 }
297
298 /* Loop the following instructions */
299 EndAddress = FunctionEntry->EndAddress + ImageBase;
300 while((DWORD64)InstrPtr < EndAddress)
301 {
302 Instr = *(DWORD*)InstrPtr;
303
304 /* Check for a simple pop */
305 if ( (Instr & 0xf8) == 0x58 )
306 {
307 /* Opcode pops a basic register from stack */
308 Reg = Instr & 0x7;
309 PopReg(&LocalContext, Reg);
310 InstrPtr++;
311 continue;
312 }
313
314 /* Check for REX + pop */
315 if ( (Instr & 0xf8fb) == 0x5841 )
316 {
317 /* Opcode is pop r8 .. r15 */
318 Reg = (Instr & 0x7) + 8;
319 PopReg(&LocalContext, Reg);
320 InstrPtr += 2;
321 continue;
322 }
323
324 /* Check for retn / retf */
325 if ( (Instr & 0xf7) == 0xc3 )
326 {
327 /* We are finished */
328 break;
329 }
330
331 /* Opcode not allowed for Epilog */
332 return FALSE;
333 }
334
335 /* Unwind is finished, pop new Rip from Stack */
336 LocalContext.Rip = *(DWORD64*)LocalContext.Rsp;
337 LocalContext.Rsp += sizeof(DWORD64);
338
339 *Context = LocalContext;
340 return TRUE;
341 }
342
343
344 PEXCEPTION_ROUTINE
345 NTAPI
346 RtlVirtualUnwind(
347 IN ULONG HandlerType,
348 IN ULONG64 ImageBase,
349 IN ULONG64 ControlPc,
350 IN PRUNTIME_FUNCTION FunctionEntry,
351 IN OUT PCONTEXT Context,
352 OUT PVOID *HandlerData,
353 OUT PULONG64 EstablisherFrame,
354 IN OUT PKNONVOLATILE_CONTEXT_POINTERS ContextPointers)
355 {
356 PUNWIND_INFO UnwindInfo;
357 ULONG_PTR CodeOffset;
358 ULONG i;
359 UNWIND_CODE UnwindCode;
360 BYTE Reg;
361
362 /* Use relative virtual address */
363 ControlPc -= ImageBase;
364
365 /* Sanity checks */
366 if ( (ControlPc < FunctionEntry->BeginAddress) ||
367 (ControlPc >= FunctionEntry->EndAddress) )
368 {
369 return NULL;
370 }
371
372 /* Get a pointer to the unwind info */
373 UnwindInfo = RVA(ImageBase, FunctionEntry->UnwindData);
374
375 /* Calculate relative offset to function start */
376 CodeOffset = ControlPc - FunctionEntry->BeginAddress;
377
378 /* Check if we are in the function epilog and try to finish it */
379 if (CodeOffset > UnwindInfo->SizeOfProlog)
380 {
381 if (RtlpTryToUnwindEpilog(Context, ImageBase, FunctionEntry))
382 {
383 /* There's no exception routine */
384 return NULL;
385 }
386 }
387
388 /* Skip all Ops with an offset greater than the current Offset */
389 i = 0;
390 while (i < UnwindInfo->CountOfCodes &&
391 CodeOffset < UnwindInfo->UnwindCode[i].CodeOffset)
392 {
393 UnwindCode = UnwindInfo->UnwindCode[i];
394 switch (UnwindCode.UnwindOp)
395 {
396 case UWOP_SAVE_NONVOL:
397 case UWOP_SAVE_XMM:
398 case UWOP_SAVE_XMM128:
399 i += 2;
400 break;
401
402 case UWOP_SAVE_NONVOL_FAR:
403 case UWOP_SAVE_XMM_FAR:
404 case UWOP_SAVE_XMM128_FAR:
405 i += 3;
406 break;
407
408 case UWOP_ALLOC_LARGE:
409 i += UnwindCode.OpInfo ? 3 : 2;
410 break;
411
412 default:
413 i++;
414 }
415 }
416
417 /* Process the left Ops */
418 while (i < UnwindInfo->CountOfCodes)
419 {
420 UnwindCode = UnwindInfo->UnwindCode[i];
421 switch (UnwindCode.UnwindOp)
422 {
423 case UWOP_PUSH_NONVOL:
424 Reg = UnwindCode.OpInfo;
425 SetReg(Context, Reg, *(DWORD64*)Context->Rsp);
426 Context->Rsp += sizeof(DWORD64);
427 i++;
428 break;
429
430 case UWOP_ALLOC_LARGE:
431 if (UnwindCode.OpInfo)
432 {
433 ULONG Offset = *(ULONG*)(&UnwindInfo->UnwindCode[i+1]);
434 Context->Rsp += Offset;
435 i += 3;
436 }
437 else
438 {
439 USHORT Offset = UnwindInfo->UnwindCode[i+1].FrameOffset;
440 Context->Rsp += Offset * 8;
441 i += 2;
442 }
443 break;
444
445 case UWOP_ALLOC_SMALL:
446 Context->Rsp += (UnwindCode.OpInfo + 1) * 8;
447 i++;
448 break;
449
450 case UWOP_SET_FPREG:
451 i++;
452 break;
453
454 case UWOP_SAVE_NONVOL:
455 i += 2;
456 break;
457
458 case UWOP_SAVE_NONVOL_FAR:
459 i += 3;
460 break;
461
462 case UWOP_SAVE_XMM:
463 i += 2;
464 break;
465
466 case UWOP_SAVE_XMM_FAR:
467 i += 3;
468 break;
469
470 case UWOP_SAVE_XMM128:
471 i += 2;
472 break;
473
474 case UWOP_SAVE_XMM128_FAR:
475 i += 3;
476 break;
477
478 case UWOP_PUSH_MACHFRAME:
479 i += 1;
480 break;
481 }
482 }
483
484 /* Unwind is finished, pop new Rip from Stack */
485 Context->Rip = *(DWORD64*)Context->Rsp;
486 Context->Rsp += sizeof(DWORD64);
487
488 return 0;
489 }
490
491 VOID
492 NTAPI
493 RtlUnwindEx(
494 IN ULONG64 TargetFrame,
495 IN ULONG64 TargetIp,
496 IN PEXCEPTION_RECORD ExceptionRecord,
497 IN PVOID ReturnValue,
498 OUT PCONTEXT OriginalContext,
499 IN PUNWIND_HISTORY_TABLE HistoryTable)
500 {
501 UNIMPLEMENTED;
502 return;
503 }
504
505 VOID
506 NTAPI
507 RtlUnwind(
508 IN PVOID TargetFrame,
509 IN PVOID TargetIp,
510 IN PEXCEPTION_RECORD ExceptionRecord,
511 IN PVOID ReturnValue)
512 {
513 UNIMPLEMENTED;
514 return;
515 }
516
517 ULONG
518 NTAPI
519 RtlWalkFrameChain(OUT PVOID *Callers,
520 IN ULONG Count,
521 IN ULONG Flags)
522 {
523 CONTEXT Context;
524 ULONG64 ControlPc, ImageBase, EstablisherFrame;
525 ULONG64 StackLow, StackHigh;
526 PVOID HandlerData;
527 ULONG i;
528 PRUNTIME_FUNCTION FunctionEntry;
529
530 DPRINT("Enter RtlWalkFrameChain\n");
531
532 /* Capture the current Context */
533 RtlCaptureContext(&Context);
534 ControlPc = Context.Rip;
535
536 /* Get the stack limits */
537 RtlpGetStackLimits(&StackLow, &StackHigh);
538
539 /* Check if we want the user-mode stack frame */
540 if (Flags == 1)
541 {
542 }
543
544 /* Loop the frames */
545 for (i = 0; i < Count; i++)
546 {
547 /* Lookup the FunctionEntry for the current ControlPc */
548 FunctionEntry = RtlLookupFunctionEntry(ControlPc, &ImageBase, NULL);
549
550 /* Is this a leaf function? */
551 if (!FunctionEntry)
552 {
553 Context.Rip = *(DWORD64*)Context.Rsp;
554 Context.Rsp += sizeof(DWORD64);
555 DPRINT("leaf funtion, new Rip = %p, new Rsp = %p\n", (PVOID)Context.Rip, (PVOID)Context.Rsp);
556 }
557 else
558 {
559 RtlVirtualUnwind(0,
560 ImageBase,
561 ControlPc,
562 FunctionEntry,
563 &Context,
564 &HandlerData,
565 &EstablisherFrame,
566 NULL);
567 DPRINT("normal funtion, new Rip = %p, new Rsp = %p\n", (PVOID)Context.Rip, (PVOID)Context.Rsp);
568 }
569
570 /* Check if new Rip is valid */
571 if (!Context.Rip)
572 {
573 break;
574 }
575
576 /* Check, if we have left our stack */
577 if ((Context.Rsp < StackLow) || (Context.Rsp > StackHigh))
578 {
579 break;
580 }
581
582 /* Save this frame and continue with new Rip */
583 ControlPc = Context.Rip;
584 Callers[i] = (PVOID)ControlPc;
585 }
586
587 DPRINT("RtlWalkFrameChain returns %ld\n", i);
588 return i;
589 }
590
591 /*! RtlGetCallersAddress
592 * \ref http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Debug/RtlGetCallersAddress.html
593 */
594 #undef RtlGetCallersAddress
595 VOID
596 NTAPI
597 RtlGetCallersAddress(
598 OUT PVOID *CallersAddress,
599 OUT PVOID *CallersCaller )
600 {
601 PVOID Callers[4];
602 ULONG Number;
603
604 /* Get callers:
605 * RtlWalkFrameChain -> RtlGetCallersAddress -> x -> y */
606 Number = RtlWalkFrameChain(Callers, 4, 0);
607
608 if (CallersAddress)
609 {
610 *CallersAddress = (Number >= 3) ? Callers[2] : NULL;
611 }
612 if (CallersCaller)
613 {
614 *CallersCaller = (Number == 4) ? Callers[3] : NULL;
615 }
616
617 return;
618 }
619
620 // FIXME: move to different file
621 VOID
622 NTAPI
623 RtlRaiseException(IN PEXCEPTION_RECORD ExceptionRecord)
624 {
625 CONTEXT Context;
626 NTSTATUS Status = STATUS_INVALID_DISPOSITION;
627 ULONG64 ImageBase;
628 PRUNTIME_FUNCTION FunctionEntry;
629 PVOID HandlerData;
630 ULONG64 EstablisherFrame;
631
632 /* Capture the context */
633 RtlCaptureContext(&Context);
634
635 /* Get the function entry for this function */
636 FunctionEntry = RtlLookupFunctionEntry(Context.Rip,
637 &ImageBase,
638 NULL);
639
640 /* Check if we found it */
641 if (FunctionEntry)
642 {
643 /* Unwind to the caller of this function */
644 RtlVirtualUnwind(UNW_FLAG_NHANDLER,
645 ImageBase,
646 Context.Rip,
647 FunctionEntry,
648 &Context,
649 &HandlerData,
650 &EstablisherFrame,
651 NULL);
652
653 /* Save the exception address */
654 ExceptionRecord->ExceptionAddress = (PVOID)Context.Rip;
655
656 /* Write the context flag */
657 Context.ContextFlags = CONTEXT_FULL;
658
659 /* Check if user mode debugger is active */
660 if (RtlpCheckForActiveDebugger())
661 {
662 /* Raise an exception immediately */
663 Status = ZwRaiseException(ExceptionRecord, &Context, TRUE);
664 }
665 else
666 {
667 /* Dispatch the exception and check if we should continue */
668 if (!RtlDispatchException(ExceptionRecord, &Context))
669 {
670 /* Raise the exception */
671 Status = ZwRaiseException(ExceptionRecord, &Context, FALSE);
672 }
673 else
674 {
675 /* Continue, go back to previous context */
676 Status = ZwContinue(&Context, FALSE);
677 }
678 }
679 }
680
681 /* If we returned, raise a status */
682 RtlRaiseStatus(Status);
683 }
684