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