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