xref: /reactos/sdk/lib/rtl/amd64/unwind.c (revision 5eab2ddb)
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
RtlLookupFunctionTable(IN DWORD64 ControlPc,OUT PDWORD64 ImageBase,OUT PULONG Length)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
RtlLookupFunctionEntry(IN DWORD64 ControlPc,OUT PDWORD64 ImageBase,OUT PUNWIND_HISTORY_TABLE HistoryTable)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
UnwindOpSlots(_In_ UNWIND_CODE UnwindCode)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
SetReg(_Inout_ PCONTEXT Context,_In_ BYTE Reg,_In_ DWORD64 Value)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
SetRegFromStackValue(_Inout_ PCONTEXT Context,_Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers,_In_ BYTE Reg,_In_ PDWORD64 ValuePointer)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
GetReg(_In_ PCONTEXT Context,_In_ BYTE Reg)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
PopReg(_Inout_ PCONTEXT Context,_Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers,_In_ BYTE Reg)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
SetXmmReg(_Inout_ PCONTEXT Context,_In_ BYTE Reg,_In_ M128A Value)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
SetXmmRegFromStackValue(_Out_ PCONTEXT Context,_Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers,_In_ BYTE Reg,_In_ M128A * ValuePointer)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
GetXmmReg(PCONTEXT Context,BYTE Reg)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
RtlpTryToUnwindEpilog(_Inout_ PCONTEXT Context,_In_ ULONG64 ControlPc,_Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers,_In_ ULONG64 ImageBase,_In_ PRUNTIME_FUNCTION FunctionEntry)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
GetEstablisherFrame(_In_ PCONTEXT Context,_In_ PUNWIND_INFO UnwindInfo,_In_ ULONG_PTR CodeOffset)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
RtlVirtualUnwind(_In_ ULONG HandlerType,_In_ ULONG64 ImageBase,_In_ ULONG64 ControlPc,_In_ PRUNTIME_FUNCTION FunctionEntry,_Inout_ PCONTEXT Context,_Outptr_ PVOID * HandlerData,_Out_ PULONG64 EstablisherFrame,_Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers)478 RtlVirtualUnwind(
479     _In_ ULONG HandlerType,
480     _In_ ULONG64 ImageBase,
481     _In_ ULONG64 ControlPc,
482     _In_ PRUNTIME_FUNCTION FunctionEntry,
483     _Inout_ PCONTEXT Context,
484     _Outptr_ PVOID *HandlerData,
485     _Out_ PULONG64 EstablisherFrame,
486     _Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers)
487 {
488     PUNWIND_INFO UnwindInfo;
489     ULONG_PTR ControlRva, CodeOffset;
490     ULONG i, Offset;
491     UNWIND_CODE UnwindCode;
492     BYTE Reg;
493     PULONG LanguageHandler;
494 
495     /* Get relative virtual address */
496     ControlRva = ControlPc - ImageBase;
497 
498     /* Sanity checks */
499     if ( (ControlRva < FunctionEntry->BeginAddress) ||
500          (ControlRva >= FunctionEntry->EndAddress) )
501     {
502         return NULL;
503     }
504 
505     /* Get a pointer to the unwind info */
506     UnwindInfo = RVA(ImageBase, FunctionEntry->UnwindData);
507 
508     /* The language specific handler data follows the unwind info */
509     LanguageHandler = ALIGN_UP_POINTER_BY(&UnwindInfo->UnwindCode[UnwindInfo->CountOfCodes], sizeof(ULONG));
510 
511     /* Calculate relative offset to function start */
512     CodeOffset = ControlRva - FunctionEntry->BeginAddress;
513 
514     *EstablisherFrame = GetEstablisherFrame(Context, UnwindInfo, CodeOffset);
515 
516     /* Check if we are in the function epilog and try to finish it */
517     if ((CodeOffset > UnwindInfo->SizeOfProlog) && (UnwindInfo->CountOfCodes > 0))
518     {
519         if (RtlpTryToUnwindEpilog(Context, ControlPc, ContextPointers, ImageBase, FunctionEntry))
520         {
521             /* There's no exception routine */
522             return NULL;
523         }
524     }
525 
526     /* Skip all Ops with an offset greater than the current Offset */
527     i = 0;
528     while ((i < UnwindInfo->CountOfCodes) &&
529            (UnwindInfo->UnwindCode[i].CodeOffset > CodeOffset))
530     {
531         i += UnwindOpSlots(UnwindInfo->UnwindCode[i]);
532     }
533 
534 RepeatChainedInfo:
535 
536     /* Process the remaining unwind ops */
537     while (i < UnwindInfo->CountOfCodes)
538     {
539         UnwindCode = UnwindInfo->UnwindCode[i];
540         switch (UnwindCode.UnwindOp)
541         {
542             case UWOP_PUSH_NONVOL:
543                 Reg = UnwindCode.OpInfo;
544                 PopReg(Context, ContextPointers, Reg);
545                 i++;
546                 break;
547 
548             case UWOP_ALLOC_LARGE:
549                 if (UnwindCode.OpInfo)
550                 {
551                     Offset = *(ULONG*)(&UnwindInfo->UnwindCode[i+1]);
552                     Context->Rsp += Offset;
553                     i += 3;
554                 }
555                 else
556                 {
557                     Offset = UnwindInfo->UnwindCode[i+1].FrameOffset;
558                     Context->Rsp += Offset * 8;
559                     i += 2;
560                 }
561                 break;
562 
563             case UWOP_ALLOC_SMALL:
564                 Context->Rsp += (UnwindCode.OpInfo + 1) * 8;
565                 i++;
566                 break;
567 
568             case UWOP_SET_FPREG:
569                 Reg = UnwindInfo->FrameRegister;
570                 Context->Rsp = GetReg(Context, Reg) - UnwindInfo->FrameOffset * 16;
571                 i++;
572                 break;
573 
574             case UWOP_SAVE_NONVOL:
575                 Reg = UnwindCode.OpInfo;
576                 Offset = UnwindInfo->UnwindCode[i + 1].FrameOffset;
577                 SetRegFromStackValue(Context, ContextPointers, Reg, (DWORD64*)Context->Rsp + Offset);
578                 i += 2;
579                 break;
580 
581             case UWOP_SAVE_NONVOL_FAR:
582                 Reg = UnwindCode.OpInfo;
583                 Offset = *(ULONG*)(&UnwindInfo->UnwindCode[i + 1]);
584                 SetRegFromStackValue(Context, ContextPointers, Reg, (DWORD64*)Context->Rsp + Offset);
585                 i += 3;
586                 break;
587 
588             case UWOP_EPILOG:
589                 i += 1;
590                 break;
591 
592             case UWOP_SPARE_CODE:
593                 ASSERT(FALSE);
594                 i += 2;
595                 break;
596 
597             case UWOP_SAVE_XMM128:
598                 Reg = UnwindCode.OpInfo;
599                 Offset = UnwindInfo->UnwindCode[i + 1].FrameOffset;
600                 SetXmmRegFromStackValue(Context, ContextPointers, Reg, (M128A*)Context->Rsp + Offset);
601                 i += 2;
602                 break;
603 
604             case UWOP_SAVE_XMM128_FAR:
605                 Reg = UnwindCode.OpInfo;
606                 Offset = *(ULONG*)(&UnwindInfo->UnwindCode[i + 1]);
607                 SetXmmRegFromStackValue(Context, ContextPointers, Reg, (M128A*)Context->Rsp + Offset);
608                 i += 3;
609                 break;
610 
611             case UWOP_PUSH_MACHFRAME:
612                 /* OpInfo is 1, when an error code was pushed, otherwise 0. */
613                 Context->Rsp += UnwindCode.OpInfo * sizeof(DWORD64);
614 
615                 /* Now pop the MACHINE_FRAME (RIP/RSP only. And yes, "magic numbers", deal with it) */
616                 Context->Rip = *(PDWORD64)(Context->Rsp + 0x00);
617                 Context->Rsp = *(PDWORD64)(Context->Rsp + 0x18);
618                 ASSERT((i + 1) == UnwindInfo->CountOfCodes);
619                 goto Exit;
620         }
621     }
622 
623     /* Check for chained info */
624     if (UnwindInfo->Flags & UNW_FLAG_CHAININFO)
625     {
626         /* See https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64?view=msvc-160#chained-unwind-info-structures */
627         FunctionEntry = (PRUNTIME_FUNCTION)&(UnwindInfo->UnwindCode[(UnwindInfo->CountOfCodes + 1) & ~1]);
628         UnwindInfo = RVA(ImageBase, FunctionEntry->UnwindData);
629         i = 0;
630         goto RepeatChainedInfo;
631     }
632 
633     /* Unwind is finished, pop new Rip from Stack */
634     if (Context->Rsp != 0)
635     {
636         Context->Rip = *(DWORD64*)Context->Rsp;
637         Context->Rsp += sizeof(DWORD64);
638     }
639 
640 Exit:
641 
642     /* Check if we have a handler and return it */
643     if (UnwindInfo->Flags & (HandlerType & (UNW_FLAG_EHANDLER | UNW_FLAG_UHANDLER)))
644     {
645         *HandlerData = (LanguageHandler + 1);
646         return RVA(ImageBase, *LanguageHandler);
647     }
648 
649     return NULL;
650 }
651 
652 /*!
653     \remark The implementation is based on the description in this blog: http://www.nynaeve.net/?p=106
654 
655         Differences to the desciption:
656         - Instead of using 2 pointers to the unwind context and previous context,
657           that are being swapped and the context copied, the unwind context is
658           kept in the local context and copied back into the context passed in
659           by the caller.
660 
661     \see http://www.nynaeve.net/?p=106
662 */
663 BOOLEAN
664 NTAPI
RtlpUnwindInternal(_In_opt_ PVOID TargetFrame,_In_opt_ PVOID TargetIp,_In_ PEXCEPTION_RECORD ExceptionRecord,_In_ PVOID ReturnValue,_In_ PCONTEXT ContextRecord,_In_opt_ struct _UNWIND_HISTORY_TABLE * HistoryTable,_In_ ULONG HandlerType)665 RtlpUnwindInternal(
666     _In_opt_ PVOID TargetFrame,
667     _In_opt_ PVOID TargetIp,
668     _In_ PEXCEPTION_RECORD ExceptionRecord,
669     _In_ PVOID ReturnValue,
670     _In_ PCONTEXT ContextRecord,
671     _In_opt_ struct _UNWIND_HISTORY_TABLE *HistoryTable,
672     _In_ ULONG HandlerType)
673 {
674     DISPATCHER_CONTEXT DispatcherContext;
675     PEXCEPTION_ROUTINE ExceptionRoutine;
676     EXCEPTION_DISPOSITION Disposition;
677     PRUNTIME_FUNCTION FunctionEntry;
678     ULONG_PTR StackLow, StackHigh;
679     ULONG64 ImageBase, EstablisherFrame;
680     CONTEXT UnwindContext;
681 
682     /* Get the current stack limits */
683     RtlpGetStackLimits(&StackLow, &StackHigh);
684 
685     /* If we have a target frame, then this is our high limit */
686     if (TargetFrame != NULL)
687     {
688         StackHigh = (ULONG64)TargetFrame + 1;
689     }
690 
691     /* Copy the context */
692     UnwindContext = *ContextRecord;
693 
694     /* Set up the constant fields of the dispatcher context */
695     DispatcherContext.ContextRecord = &UnwindContext;
696     DispatcherContext.HistoryTable = HistoryTable;
697     DispatcherContext.TargetIp = (ULONG64)TargetIp;
698 
699     /* Start looping */
700     while (TRUE)
701     {
702         /* Lookup the FunctionEntry for the current RIP */
703         FunctionEntry = RtlLookupFunctionEntry(UnwindContext.Rip, &ImageBase, NULL);
704         if (FunctionEntry == NULL)
705         {
706             /* No function entry, so this must be a leaf function. Pop the return address from the stack.
707                Note: this can happen after the first frame as the result of an exception */
708             UnwindContext.Rip = *(DWORD64*)UnwindContext.Rsp;
709             UnwindContext.Rsp += sizeof(DWORD64);
710 
711             if (HandlerType == UNW_FLAG_UHANDLER)
712             {
713                 /* Copy the context back for the next iteration */
714                 *ContextRecord = UnwindContext;
715             }
716             continue;
717         }
718 
719         /* Save Rip before the virtual unwind */
720         DispatcherContext.ControlPc = UnwindContext.Rip;
721 
722         /* Do a virtual unwind to get the next frame */
723         ExceptionRoutine = RtlVirtualUnwind(HandlerType,
724                                             ImageBase,
725                                             UnwindContext.Rip,
726                                             FunctionEntry,
727                                             &UnwindContext,
728                                             &DispatcherContext.HandlerData,
729                                             &EstablisherFrame,
730                                             NULL);
731 
732         /* Check, if we are still within the stack boundaries */
733         if ((EstablisherFrame < StackLow) ||
734             (EstablisherFrame >= StackHigh) ||
735             (EstablisherFrame & 7))
736         {
737             /// TODO: Handle DPC stack
738 
739             /* If we are handling an exception, we are done here. */
740             if (HandlerType == UNW_FLAG_EHANDLER)
741             {
742                 ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID;
743                 return FALSE;
744             }
745 
746             __debugbreak();
747             RtlRaiseStatus(STATUS_BAD_STACK);
748         }
749 
750         /* Check if we have an exception routine */
751         if (ExceptionRoutine != NULL)
752         {
753             /* Check if this is the target frame */
754             if (EstablisherFrame == (ULONG64)TargetFrame)
755             {
756                 /* Set flag to inform the language handler */
757                 ExceptionRecord->ExceptionFlags |= EXCEPTION_TARGET_UNWIND;
758             }
759 
760             /* Log the exception if it's enabled */
761             RtlpCheckLogException(ExceptionRecord,
762                                   &UnwindContext,
763                                   &DispatcherContext,
764                                   sizeof(DispatcherContext));
765 
766             /* Set up the variable fields of the dispatcher context */
767             DispatcherContext.ImageBase = ImageBase;
768             DispatcherContext.FunctionEntry = FunctionEntry;
769             DispatcherContext.LanguageHandler = ExceptionRoutine;
770             DispatcherContext.EstablisherFrame = EstablisherFrame;
771             DispatcherContext.ScopeIndex = 0;
772 
773             /* Store the return value in the unwind context */
774             UnwindContext.Rax = (ULONG64)ReturnValue;
775 
776              /* Loop all nested handlers */
777             do
778             {
779                 /// TODO: call RtlpExecuteHandlerForUnwind instead
780                 /* Call the language specific handler */
781                 Disposition = ExceptionRoutine(ExceptionRecord,
782                                                (PVOID)EstablisherFrame,
783                                                ContextRecord,
784                                                &DispatcherContext);
785 
786                 /* Clear exception flags for the next iteration */
787                 ExceptionRecord->ExceptionFlags &= ~(EXCEPTION_TARGET_UNWIND |
788                                                      EXCEPTION_COLLIDED_UNWIND);
789 
790                 /* Check if we do exception handling */
791                 if (HandlerType == UNW_FLAG_EHANDLER)
792                 {
793                     if (Disposition == ExceptionContinueExecution)
794                     {
795                         /* Check if it was non-continuable */
796                         if (ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE)
797                         {
798                             __debugbreak();
799                             RtlRaiseStatus(EXCEPTION_NONCONTINUABLE_EXCEPTION);
800                         }
801 
802                         /* Execution continues */
803                         return TRUE;
804                     }
805                     else if (Disposition == ExceptionNestedException)
806                     {
807                         /// TODO
808                         __debugbreak();
809                     }
810                 }
811 
812                 if (Disposition == ExceptionCollidedUnwind)
813                 {
814                     /// TODO
815                     __debugbreak();
816                 }
817 
818                 /* This must be ExceptionContinueSearch now */
819                 if (Disposition != ExceptionContinueSearch)
820                 {
821                     __debugbreak();
822                     RtlRaiseStatus(STATUS_INVALID_DISPOSITION);
823                 }
824             } while (ExceptionRecord->ExceptionFlags & EXCEPTION_COLLIDED_UNWIND);
825         }
826 
827         /* Check, if we have left our stack (8.) */
828         if ((EstablisherFrame < StackLow) ||
829             (EstablisherFrame > StackHigh) ||
830             (EstablisherFrame & 7))
831         {
832             /// TODO: Check for DPC stack
833             __debugbreak();
834 
835             if (UnwindContext.Rip == ContextRecord->Rip)
836             {
837                 RtlRaiseStatus(STATUS_BAD_FUNCTION_TABLE);
838             }
839             else
840             {
841                 ZwRaiseException(ExceptionRecord, ContextRecord, FALSE);
842             }
843         }
844 
845         if (EstablisherFrame == (ULONG64)TargetFrame)
846         {
847             break;
848         }
849 
850         if (HandlerType == UNW_FLAG_UHANDLER)
851         {
852             /* We have successfully unwound a frame. Copy the unwind context back. */
853             *ContextRecord = UnwindContext;
854         }
855     }
856 
857     if (ExceptionRecord->ExceptionCode != STATUS_UNWIND_CONSOLIDATE)
858     {
859         ContextRecord->Rip = (ULONG64)TargetIp;
860     }
861 
862     /* Set the return value */
863     ContextRecord->Rax = (ULONG64)ReturnValue;
864 
865     /* Restore the context */
866     RtlRestoreContext(ContextRecord, ExceptionRecord);
867 
868     /* Should never get here! */
869     ASSERT(FALSE);
870     return FALSE;
871 }
872 
873 VOID
874 NTAPI
RtlUnwindEx(_In_opt_ PVOID TargetFrame,_In_opt_ PVOID TargetIp,_In_opt_ PEXCEPTION_RECORD ExceptionRecord,_In_ PVOID ReturnValue,_In_ PCONTEXT ContextRecord,_In_opt_ struct _UNWIND_HISTORY_TABLE * HistoryTable)875 RtlUnwindEx(
876     _In_opt_ PVOID TargetFrame,
877     _In_opt_ PVOID TargetIp,
878     _In_opt_ PEXCEPTION_RECORD ExceptionRecord,
879     _In_ PVOID ReturnValue,
880     _In_ PCONTEXT ContextRecord,
881     _In_opt_ struct _UNWIND_HISTORY_TABLE *HistoryTable)
882 {
883     EXCEPTION_RECORD LocalExceptionRecord;
884 
885     /* Capture the current context */
886     RtlCaptureContext(ContextRecord);
887 
888     /* Check if we have an exception record */
889     if (ExceptionRecord == NULL)
890     {
891         /* No exception record was passed, so set up a local one */
892         LocalExceptionRecord.ExceptionCode = STATUS_UNWIND;
893         LocalExceptionRecord.ExceptionAddress = (PVOID)ContextRecord->Rip;
894         LocalExceptionRecord.ExceptionRecord = NULL;
895         LocalExceptionRecord.NumberParameters = 0;
896         ExceptionRecord = &LocalExceptionRecord;
897     }
898 
899     /* Set unwind flags */
900     ExceptionRecord->ExceptionFlags = EXCEPTION_UNWINDING;
901     if (TargetFrame == NULL)
902     {
903         ExceptionRecord->ExceptionFlags |= EXCEPTION_EXIT_UNWIND;
904     }
905 
906     /* Call the internal function */
907     RtlpUnwindInternal(TargetFrame,
908                        TargetIp,
909                        ExceptionRecord,
910                        ReturnValue,
911                        ContextRecord,
912                        HistoryTable,
913                        UNW_FLAG_UHANDLER);
914 }
915 
916 VOID
917 NTAPI
RtlUnwind(_In_opt_ PVOID TargetFrame,_In_opt_ PVOID TargetIp,_In_opt_ PEXCEPTION_RECORD ExceptionRecord,_In_ PVOID ReturnValue)918 RtlUnwind(
919     _In_opt_ PVOID TargetFrame,
920     _In_opt_ PVOID TargetIp,
921     _In_opt_ PEXCEPTION_RECORD ExceptionRecord,
922     _In_ PVOID ReturnValue)
923 {
924     CONTEXT Context;
925 
926     RtlUnwindEx(TargetFrame,
927                 TargetIp,
928                 ExceptionRecord,
929                 ReturnValue,
930                 &Context,
931                 NULL);
932 }
933 
934 ULONG
935 NTAPI
RtlWalkFrameChain(OUT PVOID * Callers,IN ULONG Count,IN ULONG Flags)936 RtlWalkFrameChain(OUT PVOID *Callers,
937                   IN ULONG Count,
938                   IN ULONG Flags)
939 {
940     CONTEXT Context;
941     ULONG64 ControlPc, ImageBase, EstablisherFrame;
942     ULONG64 StackLow, StackHigh;
943     PVOID HandlerData;
944     ULONG i, FramesToSkip;
945     PRUNTIME_FUNCTION FunctionEntry;
946 
947     DPRINT("Enter RtlWalkFrameChain\n");
948 
949     /* The upper bits in Flags define how many frames to skip */
950     FramesToSkip = Flags >> 8;
951 
952     /* Capture the current Context */
953     RtlCaptureContext(&Context);
954     ControlPc = Context.Rip;
955 
956     /* Get the stack limits */
957     RtlpGetStackLimits(&StackLow, &StackHigh);
958 
959     /* Check if we want the user-mode stack frame */
960     if (Flags & 1)
961     {
962     }
963 
964     _SEH2_TRY
965     {
966         /* Loop the frames */
967         for (i = 0; i < FramesToSkip + Count; i++)
968         {
969             /* Lookup the FunctionEntry for the current ControlPc */
970             FunctionEntry = RtlLookupFunctionEntry(ControlPc, &ImageBase, NULL);
971 
972             /* Is this a leaf function? */
973             if (!FunctionEntry)
974             {
975                 Context.Rip = *(DWORD64*)Context.Rsp;
976                 Context.Rsp += sizeof(DWORD64);
977                 DPRINT("leaf funtion, new Rip = %p, new Rsp = %p\n", (PVOID)Context.Rip, (PVOID)Context.Rsp);
978             }
979             else
980             {
981                 RtlVirtualUnwind(UNW_FLAG_NHANDLER,
982                                  ImageBase,
983                                  ControlPc,
984                                  FunctionEntry,
985                                  &Context,
986                                  &HandlerData,
987                                  &EstablisherFrame,
988                                  NULL);
989                 DPRINT("normal funtion, new Rip = %p, new Rsp = %p\n", (PVOID)Context.Rip, (PVOID)Context.Rsp);
990             }
991 
992             /* Check if we are in kernel mode */
993             if (RtlpGetMode() == KernelMode)
994             {
995                 /* Check if we left the kernel range */
996                 if (!(Flags & 1) && (Context.Rip < 0xFFFF800000000000ULL))
997                 {
998                     break;
999                 }
1000             }
1001             else
1002             {
1003                 /* Check if we left the user range */
1004                 if ((Context.Rip < 0x10000) ||
1005                     (Context.Rip > 0x000007FFFFFEFFFFULL))
1006                 {
1007                     break;
1008                 }
1009             }
1010 
1011             /* Check, if we have left our stack */
1012             if ((Context.Rsp <= StackLow) || (Context.Rsp >= StackHigh))
1013             {
1014                 break;
1015             }
1016 
1017             /* Continue with new Rip */
1018             ControlPc = Context.Rip;
1019 
1020             /* Save value, if we are past the frames to skip */
1021             if (i >= FramesToSkip)
1022             {
1023                 Callers[i - FramesToSkip] = (PVOID)ControlPc;
1024             }
1025         }
1026     }
1027     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
1028     {
1029         DPRINT1("Exception while getting callers!\n");
1030         i = 0;
1031     }
1032     _SEH2_END;
1033 
1034     DPRINT("RtlWalkFrameChain returns %ld\n", i);
1035     return i;
1036 }
1037 
1038 /*! RtlGetCallersAddress
1039  * \ref http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Debug/RtlGetCallersAddress.html
1040  */
1041 #undef RtlGetCallersAddress
1042 VOID
1043 NTAPI
RtlGetCallersAddress(OUT PVOID * CallersAddress,OUT PVOID * CallersCaller)1044 RtlGetCallersAddress(
1045     OUT PVOID *CallersAddress,
1046     OUT PVOID *CallersCaller )
1047 {
1048     PVOID Callers[4];
1049     ULONG Number;
1050 
1051     /* Get callers:
1052      * RtlWalkFrameChain -> RtlGetCallersAddress -> x -> y */
1053     Number = RtlWalkFrameChain(Callers, 4, 0);
1054 
1055     *CallersAddress = (Number >= 3) ? Callers[2] : NULL;
1056     *CallersCaller = (Number == 4) ? Callers[3] : NULL;
1057 
1058     return;
1059 }
1060 
1061 static
1062 VOID
RtlpCaptureNonVolatileContextPointers(_Out_ PKNONVOLATILE_CONTEXT_POINTERS NonvolatileContextPointers,_In_ ULONG64 TargetFrame)1063 RtlpCaptureNonVolatileContextPointers(
1064     _Out_ PKNONVOLATILE_CONTEXT_POINTERS NonvolatileContextPointers,
1065     _In_ ULONG64 TargetFrame)
1066 {
1067     CONTEXT Context;
1068     PRUNTIME_FUNCTION FunctionEntry;
1069     ULONG64 ImageBase;
1070     PVOID HandlerData;
1071     ULONG64 EstablisherFrame;
1072 
1073     /* Zero out the nonvolatile context pointers */
1074     RtlZeroMemory(NonvolatileContextPointers, sizeof(*NonvolatileContextPointers));
1075 
1076     /* Capture the current context */
1077     RtlCaptureContext(&Context);
1078 
1079     do
1080     {
1081         /* Make sure nothing fishy is going on. Currently this is for kernel mode only. */
1082         ASSERT((LONG64)Context.Rip < 0);
1083         ASSERT((LONG64)Context.Rsp < 0);
1084 
1085         /* Look up the function entry */
1086         FunctionEntry = RtlLookupFunctionEntry(Context.Rip, &ImageBase, NULL);
1087         if (FunctionEntry != NULL)
1088         {
1089             /* Do a virtual unwind to the caller and capture saved non-volatiles */
1090             RtlVirtualUnwind(UNW_FLAG_EHANDLER,
1091                              ImageBase,
1092                              Context.Rip,
1093                              FunctionEntry,
1094                              &Context,
1095                              &HandlerData,
1096                              &EstablisherFrame,
1097                              NonvolatileContextPointers);
1098 
1099             ASSERT(EstablisherFrame != 0);
1100         }
1101         else
1102         {
1103             Context.Rip = *(PULONG64)Context.Rsp;
1104             Context.Rsp += 8;
1105         }
1106 
1107         /* Continue until we reach user mode */
1108     } while ((LONG64)Context.Rip < 0);
1109 
1110     /* If the caller did the right thing, we should get past the target frame */
1111     ASSERT(EstablisherFrame >= TargetFrame);
1112 }
1113 
1114 VOID
RtlSetUnwindContext(_In_ PCONTEXT Context,_In_ DWORD64 TargetFrame)1115 RtlSetUnwindContext(
1116     _In_ PCONTEXT Context,
1117     _In_ DWORD64 TargetFrame)
1118 {
1119     KNONVOLATILE_CONTEXT_POINTERS ContextPointers;
1120 
1121     /* Capture pointers to the non-volatiles up to the target frame */
1122     RtlpCaptureNonVolatileContextPointers(&ContextPointers, TargetFrame);
1123 
1124     /* Copy the nonvolatiles to the captured locations */
1125     *ContextPointers.R12 = Context->R12;
1126     *ContextPointers.R13 = Context->R13;
1127     *ContextPointers.R14 = Context->R14;
1128     *ContextPointers.R15 = Context->R15;
1129     *ContextPointers.Xmm6 = Context->Xmm6;
1130     *ContextPointers.Xmm7 = Context->Xmm7;
1131     *ContextPointers.Xmm8 = Context->Xmm8;
1132     *ContextPointers.Xmm9 = Context->Xmm9;
1133     *ContextPointers.Xmm10 = Context->Xmm10;
1134     *ContextPointers.Xmm11 = Context->Xmm11;
1135     *ContextPointers.Xmm12 = Context->Xmm12;
1136     *ContextPointers.Xmm13 = Context->Xmm13;
1137     *ContextPointers.Xmm14 = Context->Xmm14;
1138     *ContextPointers.Xmm15 = Context->Xmm15;
1139 }
1140 
1141 VOID
1142 RtlpRestoreContextInternal(
1143     _In_ PCONTEXT ContextRecord);
1144 
1145 VOID
RtlRestoreContext(_In_ PCONTEXT ContextRecord,_In_ PEXCEPTION_RECORD ExceptionRecord)1146 RtlRestoreContext(
1147     _In_ PCONTEXT ContextRecord,
1148     _In_ PEXCEPTION_RECORD ExceptionRecord)
1149 {
1150     if (ExceptionRecord != NULL)
1151     {
1152         if ((ExceptionRecord->ExceptionCode == STATUS_UNWIND_CONSOLIDATE) &&
1153             (ExceptionRecord->NumberParameters >= 1))
1154         {
1155             PVOID (*Consolidate)(EXCEPTION_RECORD*) = (PVOID)ExceptionRecord->ExceptionInformation[0];
1156             // FIXME: This should be called through an asm wrapper to allow handling recursive unwinding
1157             ContextRecord->Rip = (ULONG64)Consolidate(ExceptionRecord);
1158         }
1159     }
1160 
1161     RtlpRestoreContextInternal(ContextRecord);
1162 }
1163