xref: /reactos/sdk/lib/rtl/amd64/unwind.c (revision 190b3da9)
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 #if 0 // These are deprecated / not for x64
26 #define UWOP_SAVE_XMM 6
27 #define UWOP_SAVE_XMM_FAR 7
28 #else
29 #define UWOP_EPILOG 6
30 #define UWOP_SPARE_CODE 7
31 #endif
32 #define UWOP_SAVE_XMM128 8
33 #define UWOP_SAVE_XMM128_FAR 9
34 #define UWOP_PUSH_MACHFRAME 10
35 
36 
37 typedef unsigned char UBYTE;
38 
39 typedef union _UNWIND_CODE
40 {
41     struct
42     {
43         UBYTE CodeOffset;
44         UBYTE UnwindOp:4;
45         UBYTE OpInfo:4;
46     };
47     USHORT FrameOffset;
48 } UNWIND_CODE, *PUNWIND_CODE;
49 
50 typedef struct _UNWIND_INFO
51 {
52     UBYTE Version:3;
53     UBYTE Flags:5;
54     UBYTE SizeOfProlog;
55     UBYTE CountOfCodes;
56     UBYTE FrameRegister:4;
57     UBYTE FrameOffset:4;
58     UNWIND_CODE UnwindCode[1];
59 /*    union {
60         OPTIONAL ULONG ExceptionHandler;
61         OPTIONAL ULONG FunctionEntry;
62     };
63     OPTIONAL ULONG ExceptionData[];
64 */
65 } UNWIND_INFO, *PUNWIND_INFO;
66 
67 /* FUNCTIONS *****************************************************************/
68 
69 /*! RtlLookupFunctionTable
70  * \brief Locates the table of RUNTIME_FUNCTION entries for a code address.
71  * \param ControlPc
72  *            Address of the code, for which the table should be searched.
73  * \param ImageBase
74  *            Pointer to a DWORD64 that receives the base address of the
75  *            corresponding executable image.
76  * \param Length
77  *            Pointer to an ULONG that receives the number of table entries
78  *            present in the table.
79  */
80 PRUNTIME_FUNCTION
81 NTAPI
82 RtlLookupFunctionTable(
83     IN DWORD64 ControlPc,
84     OUT PDWORD64 ImageBase,
85     OUT PULONG Length)
86 {
87     PVOID Table;
88     ULONG Size;
89 
90     /* Find corresponding file header from code address */
91     if (!RtlPcToFileHeader((PVOID)ControlPc, (PVOID*)ImageBase))
92     {
93         /* Nothing found */
94         return NULL;
95     }
96 
97     /* Locate the exception directory */
98     Table = RtlImageDirectoryEntryToData((PVOID)*ImageBase,
99                                          TRUE,
100                                          IMAGE_DIRECTORY_ENTRY_EXCEPTION,
101                                          &Size);
102 
103     /* Return the number of entries */
104     *Length = Size / sizeof(RUNTIME_FUNCTION);
105 
106     /* Return the address of the table */
107     return Table;
108 }
109 
110 PRUNTIME_FUNCTION
111 NTAPI
112 RtlpLookupDynamicFunctionEntry(
113     _In_ DWORD64 ControlPc,
114     _Out_ PDWORD64 ImageBase,
115     _In_ PUNWIND_HISTORY_TABLE HistoryTable);
116 
117 /*! RtlLookupFunctionEntry
118  * \brief Locates the RUNTIME_FUNCTION entry corresponding to a code address.
119  * \ref http://msdn.microsoft.com/en-us/library/ms680597(VS.85).aspx
120  * \todo Implement HistoryTable
121  */
122 PRUNTIME_FUNCTION
123 NTAPI
124 RtlLookupFunctionEntry(
125     IN DWORD64 ControlPc,
126     OUT PDWORD64 ImageBase,
127     OUT PUNWIND_HISTORY_TABLE HistoryTable)
128 {
129     PRUNTIME_FUNCTION FunctionTable, FunctionEntry;
130     ULONG TableLength;
131     ULONG IndexLo, IndexHi, IndexMid;
132 
133     /* Find the corresponding table */
134     FunctionTable = RtlLookupFunctionTable(ControlPc, ImageBase, &TableLength);
135 
136     /* If no table is found, try dynamic function tables */
137     if (!FunctionTable)
138     {
139         return RtlpLookupDynamicFunctionEntry(ControlPc, ImageBase, HistoryTable);
140     }
141 
142     /* Use relative virtual address */
143     ControlPc -= *ImageBase;
144 
145     /* Do a binary search */
146     IndexLo = 0;
147     IndexHi = TableLength;
148     while (IndexHi > IndexLo)
149     {
150         IndexMid = (IndexLo + IndexHi) / 2;
151         FunctionEntry = &FunctionTable[IndexMid];
152 
153         if (ControlPc < FunctionEntry->BeginAddress)
154         {
155             /* Continue search in lower half */
156             IndexHi = IndexMid;
157         }
158         else if (ControlPc >= FunctionEntry->EndAddress)
159         {
160             /* Continue search in upper half */
161             IndexLo = IndexMid + 1;
162         }
163         else
164         {
165             /* ControlPc is within limits, return entry */
166             return FunctionEntry;
167         }
168     }
169 
170     /* Nothing found, return NULL */
171     return NULL;
172 }
173 
174 static
175 __inline
176 ULONG
177 UnwindOpSlots(
178     _In_ UNWIND_CODE UnwindCode)
179 {
180     static const UCHAR UnwindOpExtraSlotTable[] =
181     {
182         0, // UWOP_PUSH_NONVOL
183         1, // UWOP_ALLOC_LARGE (or 3, special cased in lookup code)
184         0, // UWOP_ALLOC_SMALL
185         0, // UWOP_SET_FPREG
186         1, // UWOP_SAVE_NONVOL
187         2, // UWOP_SAVE_NONVOL_FAR
188         1, // UWOP_EPILOG // previously UWOP_SAVE_XMM
189         2, // UWOP_SPARE_CODE // previously UWOP_SAVE_XMM_FAR
190         1, // UWOP_SAVE_XMM128
191         2, // UWOP_SAVE_XMM128_FAR
192         0, // UWOP_PUSH_MACHFRAME
193         2, // UWOP_SET_FPREG_LARGE
194     };
195 
196     if ((UnwindCode.UnwindOp == UWOP_ALLOC_LARGE) &&
197         (UnwindCode.OpInfo != 0))
198     {
199         return 3;
200     }
201     else
202     {
203         return UnwindOpExtraSlotTable[UnwindCode.UnwindOp] + 1;
204     }
205 }
206 
207 static
208 __inline
209 void
210 SetReg(
211     _Inout_ PCONTEXT Context,
212     _In_ BYTE Reg,
213     _In_ DWORD64 Value)
214 {
215     ((DWORD64*)(&Context->Rax))[Reg] = Value;
216 }
217 
218 static
219 __inline
220 void
221 SetRegFromStackValue(
222     _Inout_ PCONTEXT Context,
223     _Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers,
224     _In_ BYTE Reg,
225     _In_ PDWORD64 ValuePointer)
226 {
227     SetReg(Context, Reg, *ValuePointer);
228     if (ContextPointers != NULL)
229     {
230         ContextPointers->IntegerContext[Reg] = ValuePointer;
231     }
232 }
233 
234 static
235 __inline
236 DWORD64
237 GetReg(
238     _In_ PCONTEXT Context,
239     _In_ BYTE Reg)
240 {
241     return ((DWORD64*)(&Context->Rax))[Reg];
242 }
243 
244 static
245 __inline
246 void
247 PopReg(
248     _Inout_ PCONTEXT Context,
249     _Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers,
250     _In_ BYTE Reg)
251 {
252     SetRegFromStackValue(Context, ContextPointers, Reg, (PDWORD64)Context->Rsp);
253     Context->Rsp += sizeof(DWORD64);
254 }
255 
256 static
257 __inline
258 void
259 SetXmmReg(
260     _Inout_ PCONTEXT Context,
261     _In_ BYTE Reg,
262     _In_ M128A Value)
263 {
264     ((M128A*)(&Context->Xmm0))[Reg] = Value;
265 }
266 
267 static
268 __inline
269 void
270 SetXmmRegFromStackValue(
271     _Out_ PCONTEXT Context,
272     _Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers,
273     _In_ BYTE Reg,
274     _In_ M128A *ValuePointer)
275 {
276     SetXmmReg(Context, Reg, *ValuePointer);
277     if (ContextPointers != NULL)
278     {
279         ContextPointers->FloatingContext[Reg] = ValuePointer;
280     }
281 }
282 
283 static
284 __inline
285 M128A
286 GetXmmReg(PCONTEXT Context, BYTE Reg)
287 {
288     return ((M128A*)(&Context->Xmm0))[Reg];
289 }
290 
291 /*! RtlpTryToUnwindEpilog
292  * \brief Helper function that tries to unwind epilog instructions.
293  * \return TRUE if we have been in an epilog and it could be unwound.
294  *         FALSE if the instructions were not allowed for an epilog.
295  * \ref
296  *  https://docs.microsoft.com/en-us/cpp/build/unwind-procedure
297  *  https://docs.microsoft.com/en-us/cpp/build/prolog-and-epilog
298  * \todo
299  *  - Test and compare with Windows behaviour
300  */
301 static
302 __inline
303 BOOLEAN
304 RtlpTryToUnwindEpilog(
305     _Inout_ PCONTEXT Context,
306     _In_ ULONG64 ControlPc,
307     _Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers,
308     _In_ ULONG64 ImageBase,
309     _In_ PRUNTIME_FUNCTION FunctionEntry)
310 {
311     CONTEXT LocalContext;
312     BYTE *InstrPtr;
313     DWORD Instr;
314     BYTE Reg, Mod;
315     ULONG64 EndAddress;
316 
317     /* Make a local copy of the context */
318     LocalContext = *Context;
319 
320     InstrPtr = (BYTE*)ControlPc;
321 
322     /* Check if first instruction of epilog is "add rsp, x" */
323     Instr = *(DWORD*)InstrPtr;
324     if ( (Instr & 0x00fffdff) == 0x00c48148 )
325     {
326         if ( (Instr & 0x0000ff00) == 0x8300 )
327         {
328             /* This is "add rsp, 0x??" */
329             LocalContext.Rsp += Instr >> 24;
330             InstrPtr += 4;
331         }
332         else
333         {
334             /* This is "add rsp, 0x???????? */
335             LocalContext.Rsp += *(DWORD*)(InstrPtr + 3);
336             InstrPtr += 7;
337         }
338     }
339     /* Check if first instruction of epilog is "lea rsp, ..." */
340     else if ( (Instr & 0x38fffe) == 0x208d48 )
341     {
342         /* Get the register */
343         Reg = (Instr >> 16) & 0x7;
344 
345         /* REX.R */
346         Reg += (Instr & 1) * 8;
347 
348         LocalContext.Rsp = GetReg(&LocalContext, Reg);
349 
350         /* Get addressing mode */
351         Mod = (Instr >> 22) & 0x3;
352         if (Mod == 0)
353         {
354             /* No displacement */
355             InstrPtr += 3;
356         }
357         else if (Mod == 1)
358         {
359             /* 1 byte displacement */
360             LocalContext.Rsp += (LONG)(CHAR)(Instr >> 24);
361             InstrPtr += 4;
362         }
363         else if (Mod == 2)
364         {
365             /* 4 bytes displacement */
366             LocalContext.Rsp += *(LONG*)(InstrPtr + 3);
367             InstrPtr += 7;
368         }
369     }
370 
371     /* Loop the following instructions before the ret */
372     EndAddress = FunctionEntry->EndAddress + ImageBase - 1;
373     while ((DWORD64)InstrPtr < EndAddress)
374     {
375         Instr = *(DWORD*)InstrPtr;
376 
377         /* Check for a simple pop */
378         if ( (Instr & 0xf8) == 0x58 )
379         {
380             /* Opcode pops a basic register from stack */
381             Reg = Instr & 0x7;
382             PopReg(&LocalContext, ContextPointers, Reg);
383             InstrPtr++;
384             continue;
385         }
386 
387         /* Check for REX + pop */
388         if ( (Instr & 0xf8fb) == 0x5841 )
389         {
390             /* Opcode is pop r8 .. r15 */
391             Reg = ((Instr >> 8) & 0x7) + 8;
392             PopReg(&LocalContext, ContextPointers, Reg);
393             InstrPtr += 2;
394             continue;
395         }
396 
397         /* Opcode not allowed for Epilog */
398         return FALSE;
399     }
400 
401     // check for popfq
402 
403     // also allow end with jmp imm, jmp [target], iretq
404 
405     /* Check if we are at the ret instruction */
406     if ((DWORD64)InstrPtr != EndAddress)
407     {
408         /* If we went past the end of the function, something is broken! */
409         ASSERT((DWORD64)InstrPtr <= EndAddress);
410         return FALSE;
411     }
412 
413     /* Make sure this is really a ret instruction */
414     if (*InstrPtr != 0xc3)
415     {
416         return FALSE;
417     }
418 
419     /* Unwind is finished, pop new Rip from Stack */
420     LocalContext.Rip = *(DWORD64*)LocalContext.Rsp;
421     LocalContext.Rsp += sizeof(DWORD64);
422 
423     *Context = LocalContext;
424     return TRUE;
425 }
426 
427 /*!
428 
429     \ref https://docs.microsoft.com/en-us/cpp/build/unwind-data-definitions-in-c
430 */
431 static
432 ULONG64
433 GetEstablisherFrame(
434     _In_ PCONTEXT Context,
435     _In_ PUNWIND_INFO UnwindInfo,
436     _In_ ULONG_PTR CodeOffset)
437 {
438     ULONG i;
439 
440     /* Check if we have a frame register */
441     if (UnwindInfo->FrameRegister == 0)
442     {
443         /* No frame register means we use Rsp */
444         return Context->Rsp;
445     }
446 
447     if ((CodeOffset >= UnwindInfo->SizeOfProlog) ||
448         ((UnwindInfo->Flags & UNW_FLAG_CHAININFO) != 0))
449     {
450         return GetReg(Context, UnwindInfo->FrameRegister) -
451                UnwindInfo->FrameOffset * 16;
452     }
453 
454     /* Loop all unwind ops */
455     for (i = 0;
456          i < UnwindInfo->CountOfCodes;
457          i += UnwindOpSlots(UnwindInfo->UnwindCode[i]))
458     {
459         /* Skip codes past our code offset */
460         if (UnwindInfo->UnwindCode[i].CodeOffset > CodeOffset)
461         {
462             continue;
463         }
464 
465         /* Check for SET_FPREG */
466         if (UnwindInfo->UnwindCode[i].UnwindOp == UWOP_SET_FPREG)
467         {
468             return GetReg(Context, UnwindInfo->FrameRegister) -
469                        UnwindInfo->FrameOffset * 16;
470         }
471     }
472 
473     return Context->Rsp;
474 }
475 
476 PEXCEPTION_ROUTINE
477 NTAPI
478 RtlVirtualUnwind(
479     _In_ ULONG HandlerType,
480     _In_ ULONG64 ImageBase,
481     _In_ ULONG64 ControlPc,
482     _In_ PRUNTIME_FUNCTION FunctionEntry,
483     _Inout_ PCONTEXT Context,
484     _Outptr_ PVOID *HandlerData,
485     _Out_ PULONG64 EstablisherFrame,
486     _Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers)
487 {
488     PUNWIND_INFO UnwindInfo;
489     ULONG_PTR ControlRva, CodeOffset;
490     ULONG i, Offset;
491     UNWIND_CODE UnwindCode;
492     BYTE Reg;
493     PULONG LanguageHandler;
494 
495     /* Get relative virtual address */
496     ControlRva = ControlPc - ImageBase;
497 
498     /* Sanity checks */
499     if ( (ControlRva < FunctionEntry->BeginAddress) ||
500          (ControlRva >= FunctionEntry->EndAddress) )
501     {
502         return NULL;
503     }
504 
505     /* Get a pointer to the unwind info */
506     UnwindInfo = RVA(ImageBase, FunctionEntry->UnwindData);
507 
508     /* The language specific handler data follows the unwind info */
509     LanguageHandler = ALIGN_UP_POINTER_BY(&UnwindInfo->UnwindCode[UnwindInfo->CountOfCodes], sizeof(ULONG));
510 
511     /* Calculate relative offset to function start */
512     CodeOffset = ControlRva - FunctionEntry->BeginAddress;
513 
514     *EstablisherFrame = GetEstablisherFrame(Context, UnwindInfo, CodeOffset);
515 
516     /* Check if we are in the function epilog and try to finish it */
517     if ((CodeOffset > UnwindInfo->SizeOfProlog) && (UnwindInfo->CountOfCodes > 0))
518     {
519         if (RtlpTryToUnwindEpilog(Context, ControlPc, ContextPointers, ImageBase, FunctionEntry))
520         {
521             /* There's no exception routine */
522             return NULL;
523         }
524     }
525 
526     /* Skip all Ops with an offset greater than the current Offset */
527     i = 0;
528     while ((i < UnwindInfo->CountOfCodes) &&
529            (UnwindInfo->UnwindCode[i].CodeOffset > CodeOffset))
530     {
531         i += UnwindOpSlots(UnwindInfo->UnwindCode[i]);
532     }
533 
534 RepeatChainedInfo:
535 
536     /* Process the remaining unwind ops */
537     while (i < UnwindInfo->CountOfCodes)
538     {
539         UnwindCode = UnwindInfo->UnwindCode[i];
540         switch (UnwindCode.UnwindOp)
541         {
542             case UWOP_PUSH_NONVOL:
543                 Reg = UnwindCode.OpInfo;
544                 PopReg(Context, ContextPointers, Reg);
545                 i++;
546                 break;
547 
548             case UWOP_ALLOC_LARGE:
549                 if (UnwindCode.OpInfo)
550                 {
551                     Offset = *(ULONG*)(&UnwindInfo->UnwindCode[i+1]);
552                     Context->Rsp += Offset;
553                     i += 3;
554                 }
555                 else
556                 {
557                     Offset = UnwindInfo->UnwindCode[i+1].FrameOffset;
558                     Context->Rsp += Offset * 8;
559                     i += 2;
560                 }
561                 break;
562 
563             case UWOP_ALLOC_SMALL:
564                 Context->Rsp += (UnwindCode.OpInfo + 1) * 8;
565                 i++;
566                 break;
567 
568             case UWOP_SET_FPREG:
569                 Reg = UnwindInfo->FrameRegister;
570                 Context->Rsp = GetReg(Context, Reg) - UnwindInfo->FrameOffset * 16;
571                 i++;
572                 break;
573 
574             case UWOP_SAVE_NONVOL:
575                 Reg = UnwindCode.OpInfo;
576                 Offset = UnwindInfo->UnwindCode[i + 1].FrameOffset;
577                 SetRegFromStackValue(Context, ContextPointers, Reg, (DWORD64*)Context->Rsp + Offset);
578                 i += 2;
579                 break;
580 
581             case UWOP_SAVE_NONVOL_FAR:
582                 Reg = UnwindCode.OpInfo;
583                 Offset = *(ULONG*)(&UnwindInfo->UnwindCode[i + 1]);
584                 SetRegFromStackValue(Context, ContextPointers, Reg, (DWORD64*)Context->Rsp + Offset);
585                 i += 3;
586                 break;
587 
588             case UWOP_EPILOG:
589                 i += 1;
590                 break;
591 
592             case UWOP_SPARE_CODE:
593                 ASSERT(FALSE);
594                 i += 2;
595                 break;
596 
597             case UWOP_SAVE_XMM128:
598                 Reg = UnwindCode.OpInfo;
599                 Offset = UnwindInfo->UnwindCode[i + 1].FrameOffset;
600                 SetXmmRegFromStackValue(Context, ContextPointers, Reg, (M128A*)Context->Rsp + Offset);
601                 i += 2;
602                 break;
603 
604             case UWOP_SAVE_XMM128_FAR:
605                 Reg = UnwindCode.OpInfo;
606                 Offset = *(ULONG*)(&UnwindInfo->UnwindCode[i + 1]);
607                 SetXmmRegFromStackValue(Context, ContextPointers, Reg, (M128A*)Context->Rsp + Offset);
608                 i += 3;
609                 break;
610 
611             case UWOP_PUSH_MACHFRAME:
612                 /* OpInfo is 1, when an error code was pushed, otherwise 0. */
613                 Context->Rsp += UnwindCode.OpInfo * sizeof(DWORD64);
614 
615                 /* Now pop the MACHINE_FRAME (RIP/RSP only. And yes, "magic numbers", deal with it) */
616                 Context->Rip = *(PDWORD64)(Context->Rsp + 0x00);
617                 Context->Rsp = *(PDWORD64)(Context->Rsp + 0x18);
618                 ASSERT((i + 1) == UnwindInfo->CountOfCodes);
619                 goto Exit;
620         }
621     }
622 
623     /* Check for chained info */
624     if (UnwindInfo->Flags & UNW_FLAG_CHAININFO)
625     {
626         /* See https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64?view=msvc-160#chained-unwind-info-structures */
627         FunctionEntry = (PRUNTIME_FUNCTION)&(UnwindInfo->UnwindCode[(UnwindInfo->CountOfCodes + 1) & ~1]);
628         UnwindInfo = RVA(ImageBase, FunctionEntry->UnwindData);
629         i = 0;
630         goto RepeatChainedInfo;
631     }
632 
633     /* Unwind is finished, pop new Rip from Stack */
634     if (Context->Rsp != 0)
635     {
636         Context->Rip = *(DWORD64*)Context->Rsp;
637         Context->Rsp += sizeof(DWORD64);
638     }
639 
640 Exit:
641 
642     /* Check if we have a handler and return it */
643     if (UnwindInfo->Flags & (HandlerType & (UNW_FLAG_EHANDLER | UNW_FLAG_UHANDLER)))
644     {
645         *HandlerData = (LanguageHandler + 1);
646         return RVA(ImageBase, *LanguageHandler);
647     }
648 
649     return NULL;
650 }
651 
652 /*!
653     \remark The implementation is based on the description in this blog: http://www.nynaeve.net/?p=106
654 
655         Differences to the desciption:
656         - Instead of using 2 pointers to the unwind context and previous context,
657           that are being swapped and the context copied, the unwind context is
658           kept in the local context and copied back into the context passed in
659           by the caller.
660 
661     \see http://www.nynaeve.net/?p=106
662 */
663 BOOLEAN
664 NTAPI
665 RtlpUnwindInternal(
666     _In_opt_ PVOID TargetFrame,
667     _In_opt_ PVOID TargetIp,
668     _In_ PEXCEPTION_RECORD ExceptionRecord,
669     _In_ PVOID ReturnValue,
670     _In_ PCONTEXT ContextRecord,
671     _In_opt_ struct _UNWIND_HISTORY_TABLE *HistoryTable,
672     _In_ ULONG HandlerType)
673 {
674     DISPATCHER_CONTEXT DispatcherContext;
675     PEXCEPTION_ROUTINE ExceptionRoutine;
676     EXCEPTION_DISPOSITION Disposition;
677     PRUNTIME_FUNCTION FunctionEntry;
678     ULONG_PTR StackLow, StackHigh;
679     ULONG64 ImageBase, EstablisherFrame;
680     CONTEXT UnwindContext;
681 
682     /* Get the current stack limits and registration frame */
683     RtlpGetStackLimits(&StackLow, &StackHigh);
684 
685     /* If we have a target frame, then this is our high limit */
686     if (TargetFrame != NULL)
687     {
688         StackHigh = (ULONG64)TargetFrame + 1;
689     }
690 
691     /* Copy the context */
692     UnwindContext = *ContextRecord;
693 
694     /* Set up the constant fields of the dispatcher context */
695     DispatcherContext.ContextRecord = &UnwindContext;
696     DispatcherContext.HistoryTable = HistoryTable;
697     DispatcherContext.TargetIp = (ULONG64)TargetIp;
698 
699     /* Start looping */
700     while (TRUE)
701     {
702         /* Lookup the FunctionEntry for the current RIP */
703         FunctionEntry = RtlLookupFunctionEntry(UnwindContext.Rip, &ImageBase, NULL);
704         if (FunctionEntry == NULL)
705         {
706             /* No function entry, so this must be a leaf function. Pop the return address from the stack.
707                Note: this can happen after the first frame as the result of an exception */
708             UnwindContext.Rip = *(DWORD64*)UnwindContext.Rsp;
709             UnwindContext.Rsp += sizeof(DWORD64);
710 
711             /* Copy the context back for the next iteration */
712             *ContextRecord = UnwindContext;
713             continue;
714         }
715 
716         /* Save Rip before the virtual unwind */
717         DispatcherContext.ControlPc = UnwindContext.Rip;
718 
719         /* Do a virtual unwind to get the next frame */
720         ExceptionRoutine = RtlVirtualUnwind(HandlerType,
721                                             ImageBase,
722                                             UnwindContext.Rip,
723                                             FunctionEntry,
724                                             &UnwindContext,
725                                             &DispatcherContext.HandlerData,
726                                             &EstablisherFrame,
727                                             NULL);
728 
729         /* Check, if we are still within the stack boundaries */
730         if ((EstablisherFrame < StackLow) ||
731             (EstablisherFrame >= StackHigh) ||
732             (EstablisherFrame & 7))
733         {
734             /// TODO: Handle DPC stack
735 
736             /* If we are handling an exception, we are done here. */
737             if (HandlerType == UNW_FLAG_EHANDLER)
738             {
739                 ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID;
740                 return FALSE;
741             }
742 
743             __debugbreak();
744             RtlRaiseStatus(STATUS_BAD_STACK);
745         }
746 
747         /* Check if we have an exception routine */
748         if (ExceptionRoutine != NULL)
749         {
750             /* Check if this is the target frame */
751             if (EstablisherFrame == (ULONG64)TargetFrame)
752             {
753                 /* Set flag to inform the language handler */
754                 ExceptionRecord->ExceptionFlags |= EXCEPTION_TARGET_UNWIND;
755             }
756 
757             /* Log the exception if it's enabled */
758             RtlpCheckLogException(ExceptionRecord,
759                                   ContextRecord,
760                                   &DispatcherContext,
761                                   sizeof(DispatcherContext));
762 
763             /* Set up the variable fields of the dispatcher context */
764             DispatcherContext.ImageBase = ImageBase;
765             DispatcherContext.FunctionEntry = FunctionEntry;
766             DispatcherContext.LanguageHandler = ExceptionRoutine;
767             DispatcherContext.EstablisherFrame = EstablisherFrame;
768             DispatcherContext.ScopeIndex = 0;
769 
770             /* Store the return value in the unwind context */
771             UnwindContext.Rax = (ULONG64)ReturnValue;
772 
773              /* Loop all nested handlers */
774             do
775             {
776                 /// TODO: call RtlpExecuteHandlerForUnwind instead
777                 /* Call the language specific handler */
778                 Disposition = ExceptionRoutine(ExceptionRecord,
779                                                (PVOID)EstablisherFrame,
780                                                ContextRecord,
781                                                &DispatcherContext);
782 
783                 /* Clear exception flags for the next iteration */
784                 ExceptionRecord->ExceptionFlags &= ~(EXCEPTION_TARGET_UNWIND |
785                                                      EXCEPTION_COLLIDED_UNWIND);
786 
787                 /* Check if we do exception handling */
788                 if (HandlerType == UNW_FLAG_EHANDLER)
789                 {
790                     if (Disposition == ExceptionContinueExecution)
791                     {
792                         /* Check if it was non-continuable */
793                         if (ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE)
794                         {
795                             __debugbreak();
796                             RtlRaiseStatus(EXCEPTION_NONCONTINUABLE_EXCEPTION);
797                         }
798 
799                         /* Execution continues */
800                         return TRUE;
801                     }
802                     else if (Disposition == ExceptionNestedException)
803                     {
804                         /// TODO
805                         __debugbreak();
806                     }
807                 }
808 
809                 if (Disposition == ExceptionCollidedUnwind)
810                 {
811                     /// TODO
812                     __debugbreak();
813                 }
814 
815                 /* This must be ExceptionContinueSearch now */
816                 if (Disposition != ExceptionContinueSearch)
817                 {
818                     __debugbreak();
819                     RtlRaiseStatus(STATUS_INVALID_DISPOSITION);
820                 }
821             } while (ExceptionRecord->ExceptionFlags & EXCEPTION_COLLIDED_UNWIND);
822         }
823 
824         /* Check, if we have left our stack (8.) */
825         if ((EstablisherFrame < StackLow) ||
826             (EstablisherFrame > StackHigh) ||
827             (EstablisherFrame & 7))
828         {
829             /// TODO: Check for DPC stack
830             __debugbreak();
831 
832             if (UnwindContext.Rip == ContextRecord->Rip)
833             {
834                 RtlRaiseStatus(STATUS_BAD_FUNCTION_TABLE);
835             }
836             else
837             {
838                 ZwRaiseException(ExceptionRecord, ContextRecord, FALSE);
839             }
840         }
841 
842         if (EstablisherFrame == (ULONG64)TargetFrame)
843         {
844             break;
845         }
846 
847         /* We have successfully unwound a frame. Copy the unwind context back. */
848         *ContextRecord = UnwindContext;
849     }
850 
851     if (ExceptionRecord->ExceptionCode != STATUS_UNWIND_CONSOLIDATE)
852     {
853         ContextRecord->Rip = (ULONG64)TargetIp;
854     }
855 
856     /* Set the return value */
857     ContextRecord->Rax = (ULONG64)ReturnValue;
858 
859     /* Restore the context */
860     RtlRestoreContext(ContextRecord, ExceptionRecord);
861 
862     /* Should never get here! */
863     ASSERT(FALSE);
864     return FALSE;
865 }
866 
867 VOID
868 NTAPI
869 RtlUnwindEx(
870     _In_opt_ PVOID TargetFrame,
871     _In_opt_ PVOID TargetIp,
872     _In_opt_ PEXCEPTION_RECORD ExceptionRecord,
873     _In_ PVOID ReturnValue,
874     _In_ PCONTEXT ContextRecord,
875     _In_opt_ struct _UNWIND_HISTORY_TABLE *HistoryTable)
876 {
877     EXCEPTION_RECORD LocalExceptionRecord;
878 
879     /* Capture the current context */
880     RtlCaptureContext(ContextRecord);
881 
882     /* Check if we have an exception record */
883     if (ExceptionRecord == NULL)
884     {
885         /* No exception record was passed, so set up a local one */
886         LocalExceptionRecord.ExceptionCode = STATUS_UNWIND;
887         LocalExceptionRecord.ExceptionAddress = (PVOID)ContextRecord->Rip;
888         LocalExceptionRecord.ExceptionRecord = NULL;
889         LocalExceptionRecord.NumberParameters = 0;
890         ExceptionRecord = &LocalExceptionRecord;
891     }
892 
893     /* Set unwind flags */
894     ExceptionRecord->ExceptionFlags = EXCEPTION_UNWINDING;
895     if (TargetFrame == NULL)
896     {
897         ExceptionRecord->ExceptionFlags |= EXCEPTION_EXIT_UNWIND;
898     }
899 
900     /* Call the internal function */
901     RtlpUnwindInternal(TargetFrame,
902                        TargetIp,
903                        ExceptionRecord,
904                        ReturnValue,
905                        ContextRecord,
906                        HistoryTable,
907                        UNW_FLAG_UHANDLER);
908 }
909 
910 VOID
911 NTAPI
912 RtlUnwind(
913     _In_opt_ PVOID TargetFrame,
914     _In_opt_ PVOID TargetIp,
915     _In_opt_ PEXCEPTION_RECORD ExceptionRecord,
916     _In_ PVOID ReturnValue)
917 {
918     CONTEXT Context;
919 
920     RtlUnwindEx(TargetFrame,
921                 TargetIp,
922                 ExceptionRecord,
923                 ReturnValue,
924                 &Context,
925                 NULL);
926 }
927 
928 ULONG
929 NTAPI
930 RtlWalkFrameChain(OUT PVOID *Callers,
931                   IN ULONG Count,
932                   IN ULONG Flags)
933 {
934     CONTEXT Context;
935     ULONG64 ControlPc, ImageBase, EstablisherFrame;
936     ULONG64 StackLow, StackHigh;
937     PVOID HandlerData;
938     ULONG i, FramesToSkip;
939     PRUNTIME_FUNCTION FunctionEntry;
940 
941     DPRINT("Enter RtlWalkFrameChain\n");
942 
943     /* The upper bits in Flags define how many frames to skip */
944     FramesToSkip = Flags >> 8;
945 
946     /* Capture the current Context */
947     RtlCaptureContext(&Context);
948     ControlPc = Context.Rip;
949 
950     /* Get the stack limits */
951     RtlpGetStackLimits(&StackLow, &StackHigh);
952 
953     /* Check if we want the user-mode stack frame */
954     if (Flags & 1)
955     {
956     }
957 
958     _SEH2_TRY
959     {
960         /* Loop the frames */
961         for (i = 0; i < FramesToSkip + Count; i++)
962         {
963             /* Lookup the FunctionEntry for the current ControlPc */
964             FunctionEntry = RtlLookupFunctionEntry(ControlPc, &ImageBase, NULL);
965 
966             /* Is this a leaf function? */
967             if (!FunctionEntry)
968             {
969                 Context.Rip = *(DWORD64*)Context.Rsp;
970                 Context.Rsp += sizeof(DWORD64);
971                 DPRINT("leaf funtion, new Rip = %p, new Rsp = %p\n", (PVOID)Context.Rip, (PVOID)Context.Rsp);
972             }
973             else
974             {
975                 RtlVirtualUnwind(UNW_FLAG_NHANDLER,
976                                  ImageBase,
977                                  ControlPc,
978                                  FunctionEntry,
979                                  &Context,
980                                  &HandlerData,
981                                  &EstablisherFrame,
982                                  NULL);
983                 DPRINT("normal funtion, new Rip = %p, new Rsp = %p\n", (PVOID)Context.Rip, (PVOID)Context.Rsp);
984             }
985 
986             /* Check if we are in kernel mode */
987             if (RtlpGetMode() == KernelMode)
988             {
989                 /* Check if we left the kernel range */
990                 if (!(Flags & 1) && (Context.Rip < 0xFFFF800000000000ULL))
991                 {
992                     break;
993                 }
994             }
995             else
996             {
997                 /* Check if we left the user range */
998                 if ((Context.Rip < 0x10000) ||
999                     (Context.Rip > 0x000007FFFFFEFFFFULL))
1000                 {
1001                     break;
1002                 }
1003             }
1004 
1005             /* Check, if we have left our stack */
1006             if ((Context.Rsp <= StackLow) || (Context.Rsp >= StackHigh))
1007             {
1008                 break;
1009             }
1010 
1011             /* Continue with new Rip */
1012             ControlPc = Context.Rip;
1013 
1014             /* Save value, if we are past the frames to skip */
1015             if (i >= FramesToSkip)
1016             {
1017                 Callers[i - FramesToSkip] = (PVOID)ControlPc;
1018             }
1019         }
1020     }
1021     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
1022     {
1023         DPRINT1("Exception while getting callers!\n");
1024         i = 0;
1025     }
1026     _SEH2_END;
1027 
1028     DPRINT("RtlWalkFrameChain returns %ld\n", i);
1029     return i;
1030 }
1031 
1032 /*! RtlGetCallersAddress
1033  * \ref http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Debug/RtlGetCallersAddress.html
1034  */
1035 #undef RtlGetCallersAddress
1036 VOID
1037 NTAPI
1038 RtlGetCallersAddress(
1039     OUT PVOID *CallersAddress,
1040     OUT PVOID *CallersCaller )
1041 {
1042     PVOID Callers[4];
1043     ULONG Number;
1044 
1045     /* Get callers:
1046      * RtlWalkFrameChain -> RtlGetCallersAddress -> x -> y */
1047     Number = RtlWalkFrameChain(Callers, 4, 0);
1048 
1049     *CallersAddress = (Number >= 3) ? Callers[2] : NULL;
1050     *CallersCaller = (Number == 4) ? Callers[3] : NULL;
1051 
1052     return;
1053 }
1054 
1055 static
1056 VOID
1057 RtlpCaptureNonVolatileContextPointers(
1058     _Out_ PKNONVOLATILE_CONTEXT_POINTERS NonvolatileContextPointers,
1059     _In_ ULONG64 TargetFrame)
1060 {
1061     CONTEXT Context;
1062     PRUNTIME_FUNCTION FunctionEntry;
1063     ULONG64 ImageBase;
1064     PVOID HandlerData;
1065     ULONG64 EstablisherFrame;
1066 
1067     /* Zero out the nonvolatile context pointers */
1068     RtlZeroMemory(NonvolatileContextPointers, sizeof(*NonvolatileContextPointers));
1069 
1070     /* Capture the current context */
1071     RtlCaptureContext(&Context);
1072 
1073     do
1074     {
1075         /* Make sure nothing fishy is going on. Currently this is for kernel mode only. */
1076         ASSERT((LONG64)Context.Rip < 0);
1077         ASSERT((LONG64)Context.Rsp < 0);
1078 
1079         /* Look up the function entry */
1080         FunctionEntry = RtlLookupFunctionEntry(Context.Rip, &ImageBase, NULL);
1081         if (FunctionEntry != NULL)
1082         {
1083             /* Do a virtual unwind to the caller and capture saved non-volatiles */
1084             RtlVirtualUnwind(UNW_FLAG_EHANDLER,
1085                              ImageBase,
1086                              Context.Rip,
1087                              FunctionEntry,
1088                              &Context,
1089                              &HandlerData,
1090                              &EstablisherFrame,
1091                              NonvolatileContextPointers);
1092 
1093             ASSERT(EstablisherFrame != 0);
1094         }
1095         else
1096         {
1097             Context.Rip = *(PULONG64)Context.Rsp;
1098             Context.Rsp += 8;
1099         }
1100 
1101         /* Continue until we reach user mode */
1102     } while ((LONG64)Context.Rip < 0);
1103 
1104     /* If the caller did the right thing, we should get past the target frame */
1105     ASSERT(EstablisherFrame >= TargetFrame);
1106 }
1107 
1108 VOID
1109 RtlSetUnwindContext(
1110     _In_ PCONTEXT Context,
1111     _In_ DWORD64 TargetFrame)
1112 {
1113     KNONVOLATILE_CONTEXT_POINTERS ContextPointers;
1114 
1115     /* Capture pointers to the non-volatiles up to the target frame */
1116     RtlpCaptureNonVolatileContextPointers(&ContextPointers, TargetFrame);
1117 
1118     /* Copy the nonvolatiles to the captured locations */
1119     *ContextPointers.R12 = Context->R12;
1120     *ContextPointers.R13 = Context->R13;
1121     *ContextPointers.R14 = Context->R14;
1122     *ContextPointers.R15 = Context->R15;
1123     *ContextPointers.Xmm6 = Context->Xmm6;
1124     *ContextPointers.Xmm7 = Context->Xmm7;
1125     *ContextPointers.Xmm8 = Context->Xmm8;
1126     *ContextPointers.Xmm9 = Context->Xmm9;
1127     *ContextPointers.Xmm10 = Context->Xmm10;
1128     *ContextPointers.Xmm11 = Context->Xmm11;
1129     *ContextPointers.Xmm12 = Context->Xmm12;
1130     *ContextPointers.Xmm13 = Context->Xmm13;
1131     *ContextPointers.Xmm14 = Context->Xmm14;
1132     *ContextPointers.Xmm15 = Context->Xmm15;
1133 }
1134 
1135 VOID
1136 RtlpRestoreContextInternal(
1137     _In_ PCONTEXT ContextRecord);
1138 
1139 VOID
1140 RtlRestoreContext(
1141     _In_ PCONTEXT ContextRecord,
1142     _In_ PEXCEPTION_RECORD ExceptionRecord)
1143 {
1144     if (ExceptionRecord != NULL)
1145     {
1146         if ((ExceptionRecord->ExceptionCode == STATUS_UNWIND_CONSOLIDATE) &&
1147             (ExceptionRecord->NumberParameters >= 1))
1148         {
1149             PVOID (*Consolidate)(EXCEPTION_RECORD*) = (PVOID)ExceptionRecord->ExceptionInformation[0];
1150             // FIXME: This should be called through an asm wrapper to allow handling recursive unwinding
1151             ContextRecord->Rip = (ULONG64)Consolidate(ExceptionRecord);
1152         }
1153     }
1154 
1155     RtlpRestoreContextInternal(ContextRecord);
1156 }
1157