xref: /reactos/sdk/lib/rtl/amd64/unwind.c (revision 3d516e71)
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 https://learn.microsoft.com/en-us/windows/win32/api/winnt/nf-winnt-rtllookupfunctionentry
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 static __inline
653 BOOL
654 RtlpIsStackPointerValid(
655     _In_ ULONG64 StackPointer,
656     _In_ ULONG64 LowLimit,
657     _In_ ULONG64 HighLimit)
658 {
659     return (StackPointer >= LowLimit) &&
660            (StackPointer < HighLimit) &&
661            ((StackPointer & 7) == 0);
662 }
663 
664 /*!
665     \remark The implementation is based on the description in this blog: http://www.nynaeve.net/?p=106
666 
667         Differences to the desciption:
668         - Instead of using 2 pointers to the unwind context and previous context,
669           that are being swapped and the context copied, the unwind context is
670           kept in the local context and copied back into the context passed in
671           by the caller.
672 
673     \see http://www.nynaeve.net/?p=106
674 */
675 BOOLEAN
676 NTAPI
677 RtlpUnwindInternal(
678     _In_opt_ PVOID TargetFrame,
679     _In_opt_ PVOID TargetIp,
680     _In_ PEXCEPTION_RECORD ExceptionRecord,
681     _In_ PVOID ReturnValue,
682     _In_ PCONTEXT ContextRecord,
683     _In_opt_ struct _UNWIND_HISTORY_TABLE *HistoryTable,
684     _In_ ULONG HandlerType)
685 {
686     DISPATCHER_CONTEXT DispatcherContext;
687     PEXCEPTION_ROUTINE ExceptionRoutine;
688     EXCEPTION_DISPOSITION Disposition;
689     PRUNTIME_FUNCTION FunctionEntry;
690     ULONG_PTR StackLow, StackHigh;
691     ULONG64 ImageBase, EstablisherFrame;
692     CONTEXT UnwindContext;
693 
694     /* Get the current stack limits */
695     RtlpGetStackLimits(&StackLow, &StackHigh);
696 
697     /* If we have a target frame, then this is our high limit */
698     if (TargetFrame != NULL)
699     {
700         StackHigh = (ULONG64)TargetFrame + 1;
701     }
702 
703     /* Copy the context */
704     UnwindContext = *ContextRecord;
705 
706     /* Set up the constant fields of the dispatcher context */
707     DispatcherContext.ContextRecord = &UnwindContext;
708     DispatcherContext.HistoryTable = HistoryTable;
709     DispatcherContext.TargetIp = (ULONG64)TargetIp;
710 
711     /* Start looping */
712     while (TRUE)
713     {
714         if (!RtlpIsStackPointerValid(UnwindContext.Rsp, StackLow, StackHigh))
715         {
716             return FALSE;
717         }
718 
719         /* Lookup the FunctionEntry for the current RIP */
720         FunctionEntry = RtlLookupFunctionEntry(UnwindContext.Rip, &ImageBase, NULL);
721         if (FunctionEntry == NULL)
722         {
723             /* No function entry, so this must be a leaf function. Pop the return address from the stack.
724                Note: this can happen after the first frame as the result of an exception */
725             UnwindContext.Rip = *(DWORD64*)UnwindContext.Rsp;
726             UnwindContext.Rsp += sizeof(DWORD64);
727 
728             if (HandlerType == UNW_FLAG_UHANDLER)
729             {
730                 /* Copy the context back for the next iteration */
731                 *ContextRecord = UnwindContext;
732             }
733             continue;
734         }
735 
736         /* Save Rip before the virtual unwind */
737         DispatcherContext.ControlPc = UnwindContext.Rip;
738 
739         /* Do a virtual unwind to get the next frame */
740         ExceptionRoutine = RtlVirtualUnwind(HandlerType,
741                                             ImageBase,
742                                             UnwindContext.Rip,
743                                             FunctionEntry,
744                                             &UnwindContext,
745                                             &DispatcherContext.HandlerData,
746                                             &EstablisherFrame,
747                                             NULL);
748 
749         /* Check, if we are still within the stack boundaries */
750         if ((EstablisherFrame < StackLow) ||
751             (EstablisherFrame >= StackHigh) ||
752             (EstablisherFrame & 7))
753         {
754             /// TODO: Handle DPC stack
755 
756             /* If we are handling an exception, we are done here. */
757             if (HandlerType == UNW_FLAG_EHANDLER)
758             {
759                 ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID;
760                 return FALSE;
761             }
762 
763             __debugbreak();
764             RtlRaiseStatus(STATUS_BAD_STACK);
765         }
766 
767         /* Check if we have an exception routine */
768         if (ExceptionRoutine != NULL)
769         {
770             /* Check if this is the target frame */
771             if (EstablisherFrame == (ULONG64)TargetFrame)
772             {
773                 /* Set flag to inform the language handler */
774                 ExceptionRecord->ExceptionFlags |= EXCEPTION_TARGET_UNWIND;
775             }
776 
777             /* Log the exception if it's enabled */
778             RtlpCheckLogException(ExceptionRecord,
779                                   &UnwindContext,
780                                   &DispatcherContext,
781                                   sizeof(DispatcherContext));
782 
783             /* Set up the variable fields of the dispatcher context */
784             DispatcherContext.ImageBase = ImageBase;
785             DispatcherContext.FunctionEntry = FunctionEntry;
786             DispatcherContext.LanguageHandler = ExceptionRoutine;
787             DispatcherContext.EstablisherFrame = EstablisherFrame;
788             DispatcherContext.ScopeIndex = 0;
789 
790             /* Store the return value in the unwind context */
791             UnwindContext.Rax = (ULONG64)ReturnValue;
792 
793              /* Loop all nested handlers */
794             do
795             {
796                 /// TODO: call RtlpExecuteHandlerForUnwind instead
797                 /* Call the language specific handler */
798                 Disposition = ExceptionRoutine(ExceptionRecord,
799                                                (PVOID)EstablisherFrame,
800                                                ContextRecord,
801                                                &DispatcherContext);
802 
803                 /* Clear exception flags for the next iteration */
804                 ExceptionRecord->ExceptionFlags &= ~(EXCEPTION_TARGET_UNWIND |
805                                                      EXCEPTION_COLLIDED_UNWIND);
806 
807                 /* Check if we do exception handling */
808                 if (HandlerType == UNW_FLAG_EHANDLER)
809                 {
810                     if (Disposition == ExceptionContinueExecution)
811                     {
812                         /* Check if it was non-continuable */
813                         if (ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE)
814                         {
815                             __debugbreak();
816                             RtlRaiseStatus(EXCEPTION_NONCONTINUABLE_EXCEPTION);
817                         }
818 
819                         /* Execution continues */
820                         return TRUE;
821                     }
822                     else if (Disposition == ExceptionNestedException)
823                     {
824                         /// TODO
825                         __debugbreak();
826                     }
827                 }
828 
829                 if (Disposition == ExceptionCollidedUnwind)
830                 {
831                     /// TODO
832                     __debugbreak();
833                 }
834 
835                 /* This must be ExceptionContinueSearch now */
836                 if (Disposition != ExceptionContinueSearch)
837                 {
838                     __debugbreak();
839                     RtlRaiseStatus(STATUS_INVALID_DISPOSITION);
840                 }
841             } while (ExceptionRecord->ExceptionFlags & EXCEPTION_COLLIDED_UNWIND);
842         }
843 
844         /* Check, if we have left our stack (8.) */
845         if ((EstablisherFrame < StackLow) ||
846             (EstablisherFrame > StackHigh) ||
847             (EstablisherFrame & 7))
848         {
849             /// TODO: Check for DPC stack
850             __debugbreak();
851 
852             if (UnwindContext.Rip == ContextRecord->Rip)
853             {
854                 RtlRaiseStatus(STATUS_BAD_FUNCTION_TABLE);
855             }
856             else
857             {
858                 ZwRaiseException(ExceptionRecord, ContextRecord, FALSE);
859             }
860         }
861 
862         if (EstablisherFrame == (ULONG64)TargetFrame)
863         {
864             break;
865         }
866 
867         if (HandlerType == UNW_FLAG_UHANDLER)
868         {
869             /* We have successfully unwound a frame. Copy the unwind context back. */
870             *ContextRecord = UnwindContext;
871         }
872     }
873 
874     if (ExceptionRecord->ExceptionCode != STATUS_UNWIND_CONSOLIDATE)
875     {
876         ContextRecord->Rip = (ULONG64)TargetIp;
877     }
878 
879     /* Set the return value */
880     ContextRecord->Rax = (ULONG64)ReturnValue;
881 
882     /* Restore the context */
883     RtlRestoreContext(ContextRecord, ExceptionRecord);
884 
885     /* Should never get here! */
886     ASSERT(FALSE);
887     return FALSE;
888 }
889 
890 VOID
891 NTAPI
892 RtlUnwindEx(
893     _In_opt_ PVOID TargetFrame,
894     _In_opt_ PVOID TargetIp,
895     _In_opt_ PEXCEPTION_RECORD ExceptionRecord,
896     _In_ PVOID ReturnValue,
897     _In_ PCONTEXT ContextRecord,
898     _In_opt_ struct _UNWIND_HISTORY_TABLE *HistoryTable)
899 {
900     EXCEPTION_RECORD LocalExceptionRecord;
901 
902     /* Capture the current context */
903     RtlCaptureContext(ContextRecord);
904 
905     /* Check if we have an exception record */
906     if (ExceptionRecord == NULL)
907     {
908         /* No exception record was passed, so set up a local one */
909         LocalExceptionRecord.ExceptionCode = STATUS_UNWIND;
910         LocalExceptionRecord.ExceptionAddress = (PVOID)ContextRecord->Rip;
911         LocalExceptionRecord.ExceptionRecord = NULL;
912         LocalExceptionRecord.NumberParameters = 0;
913         ExceptionRecord = &LocalExceptionRecord;
914     }
915 
916     /* Set unwind flags */
917     ExceptionRecord->ExceptionFlags = EXCEPTION_UNWINDING;
918     if (TargetFrame == NULL)
919     {
920         ExceptionRecord->ExceptionFlags |= EXCEPTION_EXIT_UNWIND;
921     }
922 
923     /* Call the internal function */
924     RtlpUnwindInternal(TargetFrame,
925                        TargetIp,
926                        ExceptionRecord,
927                        ReturnValue,
928                        ContextRecord,
929                        HistoryTable,
930                        UNW_FLAG_UHANDLER);
931 }
932 
933 VOID
934 NTAPI
935 RtlUnwind(
936     _In_opt_ PVOID TargetFrame,
937     _In_opt_ PVOID TargetIp,
938     _In_opt_ PEXCEPTION_RECORD ExceptionRecord,
939     _In_ PVOID ReturnValue)
940 {
941     CONTEXT Context;
942 
943     RtlUnwindEx(TargetFrame,
944                 TargetIp,
945                 ExceptionRecord,
946                 ReturnValue,
947                 &Context,
948                 NULL);
949 }
950 
951 ULONG
952 NTAPI
953 RtlWalkFrameChain(OUT PVOID *Callers,
954                   IN ULONG Count,
955                   IN ULONG Flags)
956 {
957     CONTEXT Context;
958     ULONG64 ControlPc, ImageBase, EstablisherFrame;
959     ULONG64 StackLow, StackHigh;
960     PVOID HandlerData;
961     ULONG i, FramesToSkip;
962     PRUNTIME_FUNCTION FunctionEntry;
963     MODE CurrentMode = RtlpGetMode();
964 
965     DPRINT("Enter RtlWalkFrameChain\n");
966 
967     /* The upper bits in Flags define how many frames to skip */
968     FramesToSkip = Flags >> 8;
969 
970     /* Capture the current Context */
971     RtlCaptureContext(&Context);
972     ControlPc = Context.Rip;
973 
974     /* Get the stack limits */
975     RtlpGetStackLimits(&StackLow, &StackHigh);
976 
977     _SEH2_TRY
978     {
979         /* Loop the frames */
980         for (i = 0; i < FramesToSkip + Count; i++)
981         {
982             /* Lookup the FunctionEntry for the current ControlPc */
983             FunctionEntry = RtlLookupFunctionEntry(ControlPc, &ImageBase, NULL);
984 
985             /* Is this a leaf function? */
986             if (!FunctionEntry)
987             {
988                 Context.Rip = *(DWORD64*)Context.Rsp;
989                 Context.Rsp += sizeof(DWORD64);
990                 DPRINT("leaf funtion, new Rip = %p, new Rsp = %p\n", (PVOID)Context.Rip, (PVOID)Context.Rsp);
991             }
992             else
993             {
994                 RtlVirtualUnwind(UNW_FLAG_NHANDLER,
995                                  ImageBase,
996                                  ControlPc,
997                                  FunctionEntry,
998                                  &Context,
999                                  &HandlerData,
1000                                  &EstablisherFrame,
1001                                  NULL);
1002                 DPRINT("normal funtion, new Rip = %p, new Rsp = %p\n", (PVOID)Context.Rip, (PVOID)Context.Rsp);
1003             }
1004 
1005             /* Check if we are in kernel mode */
1006             if (CurrentMode == KernelMode)
1007             {
1008                 /* Check if we left the kernel range */
1009                 if (Context.Rip < 0xFFFF800000000000ULL)
1010                 {
1011                     /* Bail out, unless user mode was requested */
1012                     if ((Flags & 1) == 0)
1013                     {
1014                         break;
1015                     }
1016 
1017                     /* We are in user mode now, get UM stack bounds */
1018                     CurrentMode = UserMode;
1019                     StackLow = (ULONG64)NtCurrentTeb()->NtTib.StackLimit;
1020                     StackHigh = (ULONG64)NtCurrentTeb()->NtTib.StackBase;
1021                 }
1022             }
1023 
1024             /* Check (again) if we are in user mode now */
1025             if (CurrentMode == UserMode)
1026             {
1027                 /* Check if we left the user range */
1028                 if ((Context.Rip < 0x10000) ||
1029                     (Context.Rip > 0x000007FFFFFEFFFFULL))
1030                 {
1031                     break;
1032                 }
1033             }
1034 
1035             /* Check, if we have left our stack */
1036             if ((Context.Rsp <= StackLow) || (Context.Rsp >= StackHigh))
1037             {
1038                 break;
1039             }
1040 
1041             /* Continue with new Rip */
1042             ControlPc = Context.Rip;
1043 
1044             /* Save value, if we are past the frames to skip */
1045             if (i >= FramesToSkip)
1046             {
1047                 Callers[i - FramesToSkip] = (PVOID)ControlPc;
1048             }
1049         }
1050     }
1051     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
1052     {
1053         DPRINT1("Exception while getting callers!\n");
1054         i = 0;
1055     }
1056     _SEH2_END;
1057 
1058     DPRINT("RtlWalkFrameChain returns %ld\n", i);
1059     return i;
1060 }
1061 
1062 /*! RtlGetCallersAddress
1063  * \ref http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Debug/RtlGetCallersAddress.html
1064  */
1065 #undef RtlGetCallersAddress
1066 VOID
1067 NTAPI
1068 RtlGetCallersAddress(
1069     OUT PVOID *CallersAddress,
1070     OUT PVOID *CallersCaller )
1071 {
1072     PVOID Callers[4];
1073     ULONG Number;
1074 
1075     /* Get callers:
1076      * RtlWalkFrameChain -> RtlGetCallersAddress -> x -> y */
1077     Number = RtlWalkFrameChain(Callers, 4, 0);
1078 
1079     *CallersAddress = (Number >= 3) ? Callers[2] : NULL;
1080     *CallersCaller = (Number == 4) ? Callers[3] : NULL;
1081 
1082     return;
1083 }
1084 
1085 static
1086 VOID
1087 RtlpCaptureNonVolatileContextPointers(
1088     _Out_ PKNONVOLATILE_CONTEXT_POINTERS NonvolatileContextPointers,
1089     _In_ ULONG64 TargetFrame)
1090 {
1091     CONTEXT Context;
1092     PRUNTIME_FUNCTION FunctionEntry;
1093     ULONG64 ImageBase;
1094     PVOID HandlerData;
1095     ULONG64 EstablisherFrame;
1096 
1097     /* Zero out the nonvolatile context pointers */
1098     RtlZeroMemory(NonvolatileContextPointers, sizeof(*NonvolatileContextPointers));
1099 
1100     /* Capture the current context */
1101     RtlCaptureContext(&Context);
1102 
1103     do
1104     {
1105         /* Make sure nothing fishy is going on. Currently this is for kernel mode only. */
1106         ASSERT((LONG64)Context.Rip < 0);
1107         ASSERT((LONG64)Context.Rsp < 0);
1108 
1109         /* Look up the function entry */
1110         FunctionEntry = RtlLookupFunctionEntry(Context.Rip, &ImageBase, NULL);
1111         if (FunctionEntry != NULL)
1112         {
1113             /* Do a virtual unwind to the caller and capture saved non-volatiles */
1114             RtlVirtualUnwind(UNW_FLAG_EHANDLER,
1115                              ImageBase,
1116                              Context.Rip,
1117                              FunctionEntry,
1118                              &Context,
1119                              &HandlerData,
1120                              &EstablisherFrame,
1121                              NonvolatileContextPointers);
1122 
1123             ASSERT(EstablisherFrame != 0);
1124         }
1125         else
1126         {
1127             Context.Rip = *(PULONG64)Context.Rsp;
1128             Context.Rsp += 8;
1129         }
1130 
1131         /* Continue until we reach user mode */
1132     } while ((LONG64)Context.Rip < 0);
1133 
1134     /* If the caller did the right thing, we should get past the target frame */
1135     ASSERT(EstablisherFrame >= TargetFrame);
1136 }
1137 
1138 VOID
1139 RtlSetUnwindContext(
1140     _In_ PCONTEXT Context,
1141     _In_ DWORD64 TargetFrame)
1142 {
1143     KNONVOLATILE_CONTEXT_POINTERS ContextPointers;
1144 
1145     /* Capture pointers to the non-volatiles up to the target frame */
1146     RtlpCaptureNonVolatileContextPointers(&ContextPointers, TargetFrame);
1147 
1148     /* Copy the nonvolatiles to the captured locations */
1149     *ContextPointers.R12 = Context->R12;
1150     *ContextPointers.R13 = Context->R13;
1151     *ContextPointers.R14 = Context->R14;
1152     *ContextPointers.R15 = Context->R15;
1153     *ContextPointers.Xmm6 = Context->Xmm6;
1154     *ContextPointers.Xmm7 = Context->Xmm7;
1155     *ContextPointers.Xmm8 = Context->Xmm8;
1156     *ContextPointers.Xmm9 = Context->Xmm9;
1157     *ContextPointers.Xmm10 = Context->Xmm10;
1158     *ContextPointers.Xmm11 = Context->Xmm11;
1159     *ContextPointers.Xmm12 = Context->Xmm12;
1160     *ContextPointers.Xmm13 = Context->Xmm13;
1161     *ContextPointers.Xmm14 = Context->Xmm14;
1162     *ContextPointers.Xmm15 = Context->Xmm15;
1163 }
1164 
1165 VOID
1166 RtlpRestoreContextInternal(
1167     _In_ PCONTEXT ContextRecord);
1168 
1169 VOID
1170 RtlRestoreContext(
1171     _In_ PCONTEXT ContextRecord,
1172     _In_ PEXCEPTION_RECORD ExceptionRecord)
1173 {
1174     if (ExceptionRecord != NULL)
1175     {
1176         if ((ExceptionRecord->ExceptionCode == STATUS_UNWIND_CONSOLIDATE) &&
1177             (ExceptionRecord->NumberParameters >= 1))
1178         {
1179             PVOID (*Consolidate)(EXCEPTION_RECORD*) = (PVOID)ExceptionRecord->ExceptionInformation[0];
1180             // FIXME: This should be called through an asm wrapper to allow handling recursive unwinding
1181             ContextRecord->Rip = (ULONG64)Consolidate(ExceptionRecord);
1182         }
1183     }
1184 
1185     RtlpRestoreContextInternal(ContextRecord);
1186 }
1187