xref: /reactos/sdk/lib/rtl/amd64/unwind.c (revision 3e42f7b4)
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     _Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers,
307     _In_ ULONG64 ImageBase,
308     _In_ PRUNTIME_FUNCTION FunctionEntry)
309 {
310     CONTEXT LocalContext;
311     BYTE *InstrPtr;
312     DWORD Instr;
313     BYTE Reg, Mod;
314     ULONG64 EndAddress;
315 
316     /* Make a local copy of the context */
317     LocalContext = *Context;
318 
319     InstrPtr = (BYTE*)LocalContext.Rip;
320 
321     /* Check if first instruction of epilog is "add rsp, x" */
322     Instr = *(DWORD*)InstrPtr;
323     if ( (Instr & 0x00fffdff) == 0x00c48148 )
324     {
325         if ( (Instr & 0x0000ff00) == 0x8300 )
326         {
327             /* This is "add rsp, 0x??" */
328             LocalContext.Rsp += Instr >> 24;
329             InstrPtr += 4;
330         }
331         else
332         {
333             /* This is "add rsp, 0x???????? */
334             LocalContext.Rsp += *(DWORD*)(InstrPtr + 3);
335             InstrPtr += 7;
336         }
337     }
338     /* Check if first instruction of epilog is "lea rsp, ..." */
339     else if ( (Instr & 0x38fffe) == 0x208d48 )
340     {
341         /* Get the register */
342         Reg = ((Instr << 8) | (Instr >> 16)) & 0x7;
343 
344         LocalContext.Rsp = GetReg(&LocalContext, Reg);
345 
346         /* Get adressing mode */
347         Mod = (Instr >> 22) & 0x3;
348         if (Mod == 0)
349         {
350             /* No displacement */
351             InstrPtr += 3;
352         }
353         else if (Mod == 1)
354         {
355             /* 1 byte displacement */
356             LocalContext.Rsp += Instr >> 24;
357             InstrPtr += 4;
358         }
359         else if (Mod == 2)
360         {
361             /* 4 bytes displacement */
362             LocalContext.Rsp += *(DWORD*)(InstrPtr + 3);
363             InstrPtr += 7;
364         }
365     }
366 
367     /* Loop the following instructions before the ret */
368     EndAddress = FunctionEntry->EndAddress + ImageBase - 1;
369     while ((DWORD64)InstrPtr < EndAddress)
370     {
371         Instr = *(DWORD*)InstrPtr;
372 
373         /* Check for a simple pop */
374         if ( (Instr & 0xf8) == 0x58 )
375         {
376             /* Opcode pops a basic register from stack */
377             Reg = Instr & 0x7;
378             PopReg(&LocalContext, ContextPointers, Reg);
379             InstrPtr++;
380             continue;
381         }
382 
383         /* Check for REX + pop */
384         if ( (Instr & 0xf8fb) == 0x5841 )
385         {
386             /* Opcode is pop r8 .. r15 */
387             Reg = ((Instr >> 8) & 0x7) + 8;
388             PopReg(&LocalContext, ContextPointers, Reg);
389             InstrPtr += 2;
390             continue;
391         }
392 
393         /* Opcode not allowed for Epilog */
394         return FALSE;
395     }
396 
397     // check for popfq
398 
399     // also allow end with jmp imm, jmp [target], iretq
400 
401     /* Check if we are at the ret instruction */
402     if ((DWORD64)InstrPtr != EndAddress)
403     {
404         /* If we went past the end of the function, something is broken! */
405         ASSERT((DWORD64)InstrPtr <= EndAddress);
406         return FALSE;
407     }
408 
409     /* Make sure this is really a ret instruction */
410     if (*InstrPtr != 0xc3)
411     {
412         ASSERT(FALSE);
413         return FALSE;
414     }
415 
416     /* Unwind is finished, pop new Rip from Stack */
417     LocalContext.Rip = *(DWORD64*)LocalContext.Rsp;
418     LocalContext.Rsp += sizeof(DWORD64);
419 
420     *Context = LocalContext;
421     return TRUE;
422 }
423 
424 /*!
425 
426     \ref https://docs.microsoft.com/en-us/cpp/build/unwind-data-definitions-in-c
427 */
428 static
429 ULONG64
430 GetEstablisherFrame(
431     _In_ PCONTEXT Context,
432     _In_ PUNWIND_INFO UnwindInfo,
433     _In_ ULONG_PTR CodeOffset)
434 {
435     ULONG i;
436 
437     /* Check if we have a frame register */
438     if (UnwindInfo->FrameRegister == 0)
439     {
440         /* No frame register means we use Rsp */
441         return Context->Rsp;
442     }
443 
444     if ((CodeOffset >= UnwindInfo->SizeOfProlog) ||
445         ((UnwindInfo->Flags & UNW_FLAG_CHAININFO) != 0))
446     {
447         return GetReg(Context, UnwindInfo->FrameRegister) -
448                UnwindInfo->FrameOffset * 16;
449     }
450 
451     /* Loop all unwind ops */
452     for (i = 0;
453          i < UnwindInfo->CountOfCodes;
454          i += UnwindOpSlots(UnwindInfo->UnwindCode[i]))
455     {
456         /* Check for SET_FPREG */
457         if (UnwindInfo->UnwindCode[i].UnwindOp == UWOP_SET_FPREG)
458         {
459             return GetReg(Context, UnwindInfo->FrameRegister) -
460                    UnwindInfo->FrameOffset * 16;
461         }
462     }
463 
464     return Context->Rsp;
465 }
466 
467 PEXCEPTION_ROUTINE
468 NTAPI
469 RtlVirtualUnwind(
470     _In_ ULONG HandlerType,
471     _In_ ULONG64 ImageBase,
472     _In_ ULONG64 ControlPc,
473     _In_ PRUNTIME_FUNCTION FunctionEntry,
474     _Inout_ PCONTEXT Context,
475     _Outptr_ PVOID *HandlerData,
476     _Out_ PULONG64 EstablisherFrame,
477     _Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers)
478 {
479     PUNWIND_INFO UnwindInfo;
480     ULONG_PTR CodeOffset;
481     ULONG i, Offset;
482     UNWIND_CODE UnwindCode;
483     BYTE Reg;
484     PULONG LanguageHandler;
485 
486     /* Use relative virtual address */
487     ControlPc -= ImageBase;
488 
489     /* Sanity checks */
490     if ( (ControlPc < FunctionEntry->BeginAddress) ||
491          (ControlPc >= FunctionEntry->EndAddress) )
492     {
493         return NULL;
494     }
495 
496     /* Get a pointer to the unwind info */
497     UnwindInfo = RVA(ImageBase, FunctionEntry->UnwindData);
498 
499     /* The language specific handler data follows the unwind info */
500     LanguageHandler = ALIGN_UP_POINTER_BY(&UnwindInfo->UnwindCode[UnwindInfo->CountOfCodes], sizeof(ULONG));
501     *HandlerData = (LanguageHandler + 1);
502 
503     /* Calculate relative offset to function start */
504     CodeOffset = ControlPc - FunctionEntry->BeginAddress;
505 
506     *EstablisherFrame = GetEstablisherFrame(Context, UnwindInfo, CodeOffset);
507 
508     /* Check if we are in the function epilog and try to finish it */
509     if (CodeOffset > UnwindInfo->SizeOfProlog)
510     {
511         if (RtlpTryToUnwindEpilog(Context, ContextPointers, ImageBase, FunctionEntry))
512         {
513             /* There's no exception routine */
514             return NULL;
515         }
516     }
517 
518     /* Skip all Ops with an offset greater than the current Offset */
519     i = 0;
520     while ((i < UnwindInfo->CountOfCodes) &&
521            (UnwindInfo->UnwindCode[i].CodeOffset > CodeOffset))
522     {
523         i += UnwindOpSlots(UnwindInfo->UnwindCode[i]);
524     }
525 
526 RepeatChainedInfo:
527 
528     /* Process the remaining unwind ops */
529     while (i < UnwindInfo->CountOfCodes)
530     {
531         UnwindCode = UnwindInfo->UnwindCode[i];
532         switch (UnwindCode.UnwindOp)
533         {
534             case UWOP_PUSH_NONVOL:
535                 Reg = UnwindCode.OpInfo;
536                 PopReg(Context, ContextPointers, Reg);
537                 i++;
538                 break;
539 
540             case UWOP_ALLOC_LARGE:
541                 if (UnwindCode.OpInfo)
542                 {
543                     Offset = *(ULONG*)(&UnwindInfo->UnwindCode[i+1]);
544                     Context->Rsp += Offset;
545                     i += 3;
546                 }
547                 else
548                 {
549                     Offset = UnwindInfo->UnwindCode[i+1].FrameOffset;
550                     Context->Rsp += Offset * 8;
551                     i += 2;
552                 }
553                 break;
554 
555             case UWOP_ALLOC_SMALL:
556                 Context->Rsp += (UnwindCode.OpInfo + 1) * 8;
557                 i++;
558                 break;
559 
560             case UWOP_SET_FPREG:
561                 Reg = UnwindInfo->FrameRegister;
562                 Context->Rsp = GetReg(Context, Reg) - UnwindInfo->FrameOffset * 16;
563                 i++;
564                 break;
565 
566             case UWOP_SAVE_NONVOL:
567                 Reg = UnwindCode.OpInfo;
568                 Offset = *(USHORT*)(&UnwindInfo->UnwindCode[i + 1]);
569                 SetRegFromStackValue(Context, ContextPointers, Reg, (DWORD64*)Context->Rsp + Offset);
570                 i += 2;
571                 break;
572 
573             case UWOP_SAVE_NONVOL_FAR:
574                 Reg = UnwindCode.OpInfo;
575                 Offset = *(ULONG*)(&UnwindInfo->UnwindCode[i + 1]);
576                 SetRegFromStackValue(Context, ContextPointers, Reg, (DWORD64*)Context->Rsp + Offset);
577                 i += 3;
578                 break;
579 
580             case UWOP_EPILOG:
581                 i += 1;
582                 break;
583 
584             case UWOP_SPARE_CODE:
585                 ASSERT(FALSE);
586                 i += 2;
587                 break;
588 
589             case UWOP_SAVE_XMM128:
590                 Reg = UnwindCode.OpInfo;
591                 Offset = *(USHORT*)(&UnwindInfo->UnwindCode[i + 1]);
592                 SetXmmRegFromStackValue(Context, ContextPointers, Reg, (M128A*)(Context->Rsp + Offset));
593                 i += 2;
594                 break;
595 
596             case UWOP_SAVE_XMM128_FAR:
597                 Reg = UnwindCode.OpInfo;
598                 Offset = *(ULONG*)(&UnwindInfo->UnwindCode[i + 1]);
599                 SetXmmRegFromStackValue(Context, ContextPointers, Reg, (M128A*)(Context->Rsp + Offset));
600                 i += 3;
601                 break;
602 
603             case UWOP_PUSH_MACHFRAME:
604                 /* OpInfo is 1, when an error code was pushed, otherwise 0. */
605                 Context->Rsp += UnwindCode.OpInfo * sizeof(DWORD64);
606 
607                 /* Now pop the MACHINE_FRAME (Yes, "magic numbers", deal with it) */
608                 Context->Rip = *(PDWORD64)(Context->Rsp + 0x00);
609                 Context->SegCs = *(PDWORD64)(Context->Rsp + 0x08);
610                 Context->EFlags = *(PDWORD64)(Context->Rsp + 0x10);
611                 Context->SegSs = *(PDWORD64)(Context->Rsp + 0x20);
612                 Context->Rsp = *(PDWORD64)(Context->Rsp + 0x18);
613                 ASSERT((i + 1) == UnwindInfo->CountOfCodes);
614                 goto Exit;
615         }
616     }
617 
618     /* Check for chained info */
619     if (UnwindInfo->Flags & UNW_FLAG_CHAININFO)
620     {
621         /* See https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64?view=msvc-160#chained-unwind-info-structures */
622         FunctionEntry = (PRUNTIME_FUNCTION)&(UnwindInfo->UnwindCode[(UnwindInfo->CountOfCodes + 1) & ~1]);
623         UnwindInfo = RVA(ImageBase, FunctionEntry->UnwindData);
624         i = 0;
625         goto RepeatChainedInfo;
626     }
627 
628     /* Unwind is finished, pop new Rip from Stack */
629     if (Context->Rsp != 0)
630     {
631         Context->Rip = *(DWORD64*)Context->Rsp;
632         Context->Rsp += sizeof(DWORD64);
633     }
634 
635 Exit:
636 
637     /* Check if we have a handler and return it */
638     if (UnwindInfo->Flags & (UNW_FLAG_EHANDLER | UNW_FLAG_UHANDLER))
639     {
640         return RVA(ImageBase, *LanguageHandler);
641     }
642 
643     return NULL;
644 }
645 
646 /*!
647     \remark The implementation is based on the description in this blog: http://www.nynaeve.net/?p=106
648 
649         Differences to the desciption:
650         - Instead of using 2 pointers to the unwind context and previous context,
651           that are being swapped and the context copied, the unwind context is
652           kept in the local context and copied back into the context passed in
653           by the caller.
654 
655     \see http://www.nynaeve.net/?p=106
656 */
657 BOOLEAN
658 NTAPI
659 RtlpUnwindInternal(
660     _In_opt_ PVOID TargetFrame,
661     _In_opt_ PVOID TargetIp,
662     _In_ PEXCEPTION_RECORD ExceptionRecord,
663     _In_ PVOID ReturnValue,
664     _In_ PCONTEXT ContextRecord,
665     _In_opt_ struct _UNWIND_HISTORY_TABLE *HistoryTable,
666     _In_ ULONG HandlerType)
667 {
668     DISPATCHER_CONTEXT DispatcherContext;
669     PEXCEPTION_ROUTINE ExceptionRoutine;
670     EXCEPTION_DISPOSITION Disposition;
671     PRUNTIME_FUNCTION FunctionEntry;
672     ULONG_PTR StackLow, StackHigh;
673     ULONG64 ImageBase, EstablisherFrame;
674     CONTEXT UnwindContext;
675 
676     /* Get the current stack limits and registration frame */
677     RtlpGetStackLimits(&StackLow, &StackHigh);
678 
679     /* If we have a target frame, then this is our high limit */
680     if (TargetFrame != NULL)
681     {
682         StackHigh = (ULONG64)TargetFrame + 1;
683     }
684 
685     /* Copy the context */
686     UnwindContext = *ContextRecord;
687 
688     /* Set up the constant fields of the dispatcher context */
689     DispatcherContext.ContextRecord = ContextRecord;
690     DispatcherContext.HistoryTable = HistoryTable;
691     DispatcherContext.TargetIp = (ULONG64)TargetIp;
692 
693     /* Start looping */
694     while (TRUE)
695     {
696         /* Lookup the FunctionEntry for the current RIP */
697         FunctionEntry = RtlLookupFunctionEntry(UnwindContext.Rip, &ImageBase, NULL);
698         if (FunctionEntry == NULL)
699         {
700             /* No function entry, so this must be a leaf function. Pop the return address from the stack.
701                Note: this can happen after the first frame as the result of an exception */
702             UnwindContext.Rip = *(DWORD64*)UnwindContext.Rsp;
703             UnwindContext.Rsp += sizeof(DWORD64);
704 
705             /* Copy the context back for the next iteration */
706             *ContextRecord = UnwindContext;
707             continue;
708         }
709 
710         /* Save Rip before the virtual unwind */
711         DispatcherContext.ControlPc = UnwindContext.Rip;
712 
713         /* Do a virtual unwind to get the next frame */
714         ExceptionRoutine = RtlVirtualUnwind(HandlerType,
715                                             ImageBase,
716                                             UnwindContext.Rip,
717                                             FunctionEntry,
718                                             &UnwindContext,
719                                             &DispatcherContext.HandlerData,
720                                             &EstablisherFrame,
721                                             NULL);
722 
723         /* Check, if we are still within the stack boundaries */
724         if ((EstablisherFrame < StackLow) ||
725             (EstablisherFrame >= StackHigh) ||
726             (EstablisherFrame & 7))
727         {
728             /// TODO: Handle DPC stack
729 
730             /* If we are handling an exception, we are done here. */
731             if (HandlerType == UNW_FLAG_EHANDLER)
732             {
733                 ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID;
734                 return FALSE;
735             }
736 
737             __debugbreak();
738             RtlRaiseStatus(STATUS_BAD_STACK);
739         }
740 
741         /* Check if we have an exception routine */
742         if (ExceptionRoutine != NULL)
743         {
744             /* Check if this is the target frame */
745             if (EstablisherFrame == (ULONG64)TargetFrame)
746             {
747                 /* Set flag to inform the language handler */
748                 ExceptionRecord->ExceptionFlags |= EXCEPTION_TARGET_UNWIND;
749             }
750 
751             /* Log the exception if it's enabled */
752             RtlpCheckLogException(ExceptionRecord,
753                                   ContextRecord,
754                                   &DispatcherContext,
755                                   sizeof(DispatcherContext));
756 
757             /* Set up the variable fields of the dispatcher context */
758             DispatcherContext.ImageBase = ImageBase;
759             DispatcherContext.FunctionEntry = FunctionEntry;
760             DispatcherContext.LanguageHandler = ExceptionRoutine;
761             DispatcherContext.EstablisherFrame = EstablisherFrame;
762             DispatcherContext.ScopeIndex = 0;
763 
764             /* Store the return value in the unwind context */
765             UnwindContext.Rax = (ULONG64)ReturnValue;
766 
767              /* Loop all nested handlers */
768             do
769             {
770                 /// TODO: call RtlpExecuteHandlerForUnwind instead
771                 /* Call the language specific handler */
772                 Disposition = ExceptionRoutine(ExceptionRecord,
773                                                (PVOID)EstablisherFrame,
774                                                &UnwindContext,
775                                                &DispatcherContext);
776 
777                 /* Clear exception flags for the next iteration */
778                 ExceptionRecord->ExceptionFlags &= ~(EXCEPTION_TARGET_UNWIND |
779                                                      EXCEPTION_COLLIDED_UNWIND);
780 
781                 /* Check if we do exception handling */
782                 if (HandlerType == UNW_FLAG_EHANDLER)
783                 {
784                     if (Disposition == ExceptionContinueExecution)
785                     {
786                         /* Check if it was non-continuable */
787                         if (ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE)
788                         {
789                             __debugbreak();
790                             RtlRaiseStatus(EXCEPTION_NONCONTINUABLE_EXCEPTION);
791                         }
792 
793                         /* Execution continues */
794                         return TRUE;
795                     }
796                     else if (Disposition == ExceptionNestedException)
797                     {
798                         /// TODO
799                         __debugbreak();
800                     }
801                 }
802 
803                 if (Disposition == ExceptionCollidedUnwind)
804                 {
805                     /// TODO
806                     __debugbreak();
807                 }
808 
809                 /* This must be ExceptionContinueSearch now */
810                 if (Disposition != ExceptionContinueSearch)
811                 {
812                     __debugbreak();
813                     RtlRaiseStatus(STATUS_INVALID_DISPOSITION);
814                 }
815             } while (ExceptionRecord->ExceptionFlags & EXCEPTION_COLLIDED_UNWIND);
816         }
817 
818         /* Check, if we have left our stack (8.) */
819         if ((EstablisherFrame < StackLow) ||
820             (EstablisherFrame > StackHigh) ||
821             (EstablisherFrame & 7))
822         {
823             /// TODO: Check for DPC stack
824             __debugbreak();
825 
826             if (UnwindContext.Rip == ContextRecord->Rip)
827             {
828                 RtlRaiseStatus(STATUS_BAD_FUNCTION_TABLE);
829             }
830             else
831             {
832                 ZwRaiseException(ExceptionRecord, ContextRecord, FALSE);
833             }
834         }
835 
836         if (EstablisherFrame == (ULONG64)TargetFrame)
837         {
838             break;
839         }
840 
841         /* We have successfully unwound a frame. Copy the unwind context back. */
842         *ContextRecord = UnwindContext;
843     }
844 
845     if (ExceptionRecord->ExceptionCode != STATUS_UNWIND_CONSOLIDATE)
846     {
847         ContextRecord->Rip = (ULONG64)TargetIp;
848     }
849 
850     /* Set the return value */
851     ContextRecord->Rax = (ULONG64)ReturnValue;
852 
853     /* Restore the context */
854     RtlRestoreContext(ContextRecord, ExceptionRecord);
855 
856     /* Should never get here! */
857     ASSERT(FALSE);
858     return FALSE;
859 }
860 
861 VOID
862 NTAPI
863 RtlUnwindEx(
864     _In_opt_ PVOID TargetFrame,
865     _In_opt_ PVOID TargetIp,
866     _In_opt_ PEXCEPTION_RECORD ExceptionRecord,
867     _In_ PVOID ReturnValue,
868     _In_ PCONTEXT ContextRecord,
869     _In_opt_ struct _UNWIND_HISTORY_TABLE *HistoryTable)
870 {
871     EXCEPTION_RECORD LocalExceptionRecord;
872 
873     /* Capture the current context */
874     RtlCaptureContext(ContextRecord);
875 
876     /* Check if we have an exception record */
877     if (ExceptionRecord == NULL)
878     {
879         /* No exception record was passed, so set up a local one */
880         LocalExceptionRecord.ExceptionCode = STATUS_UNWIND;
881         LocalExceptionRecord.ExceptionAddress = (PVOID)ContextRecord->Rip;
882         LocalExceptionRecord.ExceptionRecord = NULL;
883         LocalExceptionRecord.NumberParameters = 0;
884         ExceptionRecord = &LocalExceptionRecord;
885     }
886 
887     /* Call the internal function */
888     RtlpUnwindInternal(TargetFrame,
889                        TargetIp,
890                        ExceptionRecord,
891                        ReturnValue,
892                        ContextRecord,
893                        HistoryTable,
894                        UNW_FLAG_UHANDLER);
895 }
896 
897 VOID
898 NTAPI
899 RtlUnwind(
900   IN PVOID TargetFrame,
901   IN PVOID TargetIp,
902   IN PEXCEPTION_RECORD ExceptionRecord,
903   IN PVOID ReturnValue)
904 {
905     UNIMPLEMENTED;
906     return;
907 }
908 
909 ULONG
910 NTAPI
911 RtlWalkFrameChain(OUT PVOID *Callers,
912                   IN ULONG Count,
913                   IN ULONG Flags)
914 {
915     CONTEXT Context;
916     ULONG64 ControlPc, ImageBase, EstablisherFrame;
917     ULONG64 StackLow, StackHigh;
918     PVOID HandlerData;
919     ULONG i, FramesToSkip;
920     PRUNTIME_FUNCTION FunctionEntry;
921 
922     DPRINT("Enter RtlWalkFrameChain\n");
923 
924     /* The upper bits in Flags define how many frames to skip */
925     FramesToSkip = Flags >> 8;
926 
927     /* Capture the current Context */
928     RtlCaptureContext(&Context);
929     ControlPc = Context.Rip;
930 
931     /* Get the stack limits */
932     RtlpGetStackLimits(&StackLow, &StackHigh);
933 
934     /* Check if we want the user-mode stack frame */
935     if (Flags & 1)
936     {
937     }
938 
939     _SEH2_TRY
940     {
941         /* Loop the frames */
942         for (i = 0; i < FramesToSkip + Count; i++)
943         {
944             /* Lookup the FunctionEntry for the current ControlPc */
945             FunctionEntry = RtlLookupFunctionEntry(ControlPc, &ImageBase, NULL);
946 
947             /* Is this a leaf function? */
948             if (!FunctionEntry)
949             {
950                 Context.Rip = *(DWORD64*)Context.Rsp;
951                 Context.Rsp += sizeof(DWORD64);
952                 DPRINT("leaf funtion, new Rip = %p, new Rsp = %p\n", (PVOID)Context.Rip, (PVOID)Context.Rsp);
953             }
954             else
955             {
956                 RtlVirtualUnwind(UNW_FLAG_NHANDLER,
957                                  ImageBase,
958                                  ControlPc,
959                                  FunctionEntry,
960                                  &Context,
961                                  &HandlerData,
962                                  &EstablisherFrame,
963                                  NULL);
964                 DPRINT("normal funtion, new Rip = %p, new Rsp = %p\n", (PVOID)Context.Rip, (PVOID)Context.Rsp);
965             }
966 
967             /* Check if we are in kernel mode */
968             if (RtlpGetMode() == KernelMode)
969             {
970                 /* Check if we left the kernel range */
971                 if (!(Flags & 1) && (Context.Rip < 0xFFFF800000000000ULL))
972                 {
973                     break;
974                 }
975             }
976             else
977             {
978                 /* Check if we left the user range */
979                 if ((Context.Rip < 0x10000) ||
980                     (Context.Rip > 0x000007FFFFFEFFFFULL))
981                 {
982                     break;
983                 }
984             }
985 
986             /* Check, if we have left our stack */
987             if ((Context.Rsp < StackLow) || (Context.Rsp > StackHigh))
988             {
989                 break;
990             }
991 
992             /* Continue with new Rip */
993             ControlPc = Context.Rip;
994 
995             /* Save value, if we are past the frames to skip */
996             if (i >= FramesToSkip)
997             {
998                 Callers[i - FramesToSkip] = (PVOID)ControlPc;
999             }
1000         }
1001     }
1002     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
1003     {
1004         DPRINT1("Exception while getting callers!\n");
1005         i = 0;
1006     }
1007     _SEH2_END;
1008 
1009     DPRINT("RtlWalkFrameChain returns %ld\n", i);
1010     return i;
1011 }
1012 
1013 /*! RtlGetCallersAddress
1014  * \ref http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Debug/RtlGetCallersAddress.html
1015  */
1016 #undef RtlGetCallersAddress
1017 VOID
1018 NTAPI
1019 RtlGetCallersAddress(
1020     OUT PVOID *CallersAddress,
1021     OUT PVOID *CallersCaller )
1022 {
1023     PVOID Callers[4];
1024     ULONG Number;
1025 
1026     /* Get callers:
1027      * RtlWalkFrameChain -> RtlGetCallersAddress -> x -> y */
1028     Number = RtlWalkFrameChain(Callers, 4, 0);
1029 
1030     *CallersAddress = (Number >= 3) ? Callers[2] : NULL;
1031     *CallersCaller = (Number == 4) ? Callers[3] : NULL;
1032 
1033     return;
1034 }
1035 
1036 static
1037 VOID
1038 RtlpCaptureNonVolatileContextPointers(
1039     _Out_ PKNONVOLATILE_CONTEXT_POINTERS NonvolatileContextPointers,
1040     _In_ ULONG64 TargetFrame)
1041 {
1042     CONTEXT Context;
1043     PRUNTIME_FUNCTION FunctionEntry;
1044     ULONG64 ImageBase;
1045     PVOID HandlerData;
1046     ULONG64 EstablisherFrame;
1047 
1048     /* Zero out the nonvolatile context pointers */
1049     RtlZeroMemory(NonvolatileContextPointers, sizeof(*NonvolatileContextPointers));
1050 
1051     /* Capture the current context */
1052     RtlCaptureContext(&Context);
1053 
1054     do
1055     {
1056         /* Look up the function entry */
1057         FunctionEntry = RtlLookupFunctionEntry(Context.Rip, &ImageBase, NULL);
1058         ASSERT(FunctionEntry != NULL);
1059 
1060         /* Do a virtual unwind to the caller and capture saved non-volatiles */
1061         RtlVirtualUnwind(UNW_FLAG_EHANDLER,
1062                          ImageBase,
1063                          Context.Rip,
1064                          FunctionEntry,
1065                          &Context,
1066                          &HandlerData,
1067                          &EstablisherFrame,
1068                          NonvolatileContextPointers);
1069 
1070         /* Make sure nothing fishy is going on. Currently this is for kernel mode only. */
1071         ASSERT(EstablisherFrame != 0);
1072         ASSERT((LONG64)Context.Rip < 0);
1073 
1074         /* Continue until we reached the target frame or user mode */
1075     } while (EstablisherFrame < TargetFrame);
1076 
1077     /* If the caller did the right thing, we should get exactly the target frame */
1078     ASSERT(EstablisherFrame == TargetFrame);
1079 }
1080 
1081 VOID
1082 RtlSetUnwindContext(
1083     _In_ PCONTEXT Context,
1084     _In_ DWORD64 TargetFrame)
1085 {
1086     KNONVOLATILE_CONTEXT_POINTERS ContextPointers;
1087 
1088     /* Capture pointers to the non-volatiles up to the target frame */
1089     RtlpCaptureNonVolatileContextPointers(&ContextPointers, TargetFrame);
1090 
1091     /* Copy the nonvolatiles to the captured locations */
1092     *ContextPointers.R12 = Context->R12;
1093     *ContextPointers.R13 = Context->R13;
1094     *ContextPointers.R14 = Context->R14;
1095     *ContextPointers.R15 = Context->R15;
1096     *ContextPointers.Xmm6 = Context->Xmm6;
1097     *ContextPointers.Xmm7 = Context->Xmm7;
1098     *ContextPointers.Xmm8 = Context->Xmm8;
1099     *ContextPointers.Xmm9 = Context->Xmm9;
1100     *ContextPointers.Xmm10 = Context->Xmm10;
1101     *ContextPointers.Xmm11 = Context->Xmm11;
1102     *ContextPointers.Xmm12 = Context->Xmm12;
1103     *ContextPointers.Xmm13 = Context->Xmm13;
1104     *ContextPointers.Xmm14 = Context->Xmm14;
1105     *ContextPointers.Xmm15 = Context->Xmm15;
1106 }
1107