xref: /reactos/sdk/lib/rtl/amd64/unwind.c (revision 8a978a17)
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 #define UWOP_SAVE_XMM 6
26 #define UWOP_SAVE_XMM_FAR 7
27 #define UWOP_SAVE_XMM128 8
28 #define UWOP_SAVE_XMM128_FAR 9
29 #define UWOP_PUSH_MACHFRAME 10
30 
31 
32 typedef unsigned char UBYTE;
33 
34 typedef union _UNWIND_CODE
35 {
36     struct
37     {
38         UBYTE CodeOffset;
39         UBYTE UnwindOp:4;
40         UBYTE OpInfo:4;
41     };
42     USHORT FrameOffset;
43 } UNWIND_CODE, *PUNWIND_CODE;
44 
45 typedef struct _UNWIND_INFO
46 {
47     UBYTE Version:3;
48     UBYTE Flags:5;
49     UBYTE SizeOfProlog;
50     UBYTE CountOfCodes;
51     UBYTE FrameRegister:4;
52     UBYTE FrameOffset:4;
53     UNWIND_CODE UnwindCode[1];
54 /*    union {
55         OPTIONAL ULONG ExceptionHandler;
56         OPTIONAL ULONG FunctionEntry;
57     };
58     OPTIONAL ULONG ExceptionData[];
59 */
60 } UNWIND_INFO, *PUNWIND_INFO;
61 
62 /* FUNCTIONS *****************************************************************/
63 
64 /*! RtlLookupFunctionTable
65  * \brief Locates the table of RUNTIME_FUNCTION entries for a code address.
66  * \param ControlPc
67  *            Address of the code, for which the table should be searched.
68  * \param ImageBase
69  *            Pointer to a DWORD64 that receives the base address of the
70  *            corresponding executable image.
71  * \param Length
72  *            Pointer to an ULONG that receives the number of table entries
73  *            present in the table.
74  */
75 PRUNTIME_FUNCTION
76 NTAPI
77 RtlLookupFunctionTable(
78     IN DWORD64 ControlPc,
79     OUT PDWORD64 ImageBase,
80     OUT PULONG Length)
81 {
82     PVOID Table;
83     ULONG Size;
84 
85     /* Find corresponding file header from code address */
86     if (!RtlPcToFileHeader((PVOID)ControlPc, (PVOID*)ImageBase))
87     {
88         /* Nothing found */
89         return NULL;
90     }
91 
92     /* Locate the exception directory */
93     Table = RtlImageDirectoryEntryToData((PVOID)*ImageBase,
94                                          TRUE,
95                                          IMAGE_DIRECTORY_ENTRY_EXCEPTION,
96                                          &Size);
97 
98     /* Return the number of entries */
99     *Length = Size / sizeof(RUNTIME_FUNCTION);
100 
101     /* Return the address of the table */
102     return Table;
103 }
104 
105 /*! RtlLookupFunctionEntry
106  * \brief Locates the RUNTIME_FUNCTION entry corresponding to a code address.
107  * \ref http://msdn.microsoft.com/en-us/library/ms680597(VS.85).aspx
108  * \todo Implement HistoryTable
109  */
110 PRUNTIME_FUNCTION
111 NTAPI
112 RtlLookupFunctionEntry(
113     IN DWORD64 ControlPc,
114     OUT PDWORD64 ImageBase,
115     OUT PUNWIND_HISTORY_TABLE HistoryTable)
116 {
117     PRUNTIME_FUNCTION FunctionTable, FunctionEntry;
118     ULONG TableLength;
119     ULONG IndexLo, IndexHi, IndexMid;
120 
121     /* Find the corresponding table */
122     FunctionTable = RtlLookupFunctionTable(ControlPc, ImageBase, &TableLength);
123 
124     /* Fail, if no table is found */
125     if (!FunctionTable)
126     {
127         return NULL;
128     }
129 
130     /* Use relative virtual address */
131     ControlPc -= *ImageBase;
132 
133     /* Do a binary search */
134     IndexLo = 0;
135     IndexHi = TableLength;
136     while (IndexHi > IndexLo)
137     {
138         IndexMid = (IndexLo + IndexHi) / 2;
139         FunctionEntry = &FunctionTable[IndexMid];
140 
141         if (ControlPc < FunctionEntry->BeginAddress)
142         {
143             /* Continue search in lower half */
144             IndexHi = IndexMid;
145         }
146         else if (ControlPc >= FunctionEntry->EndAddress)
147         {
148             /* Continue search in upper half */
149             IndexLo = IndexMid + 1;
150         }
151         else
152         {
153             /* ControlPc is within limits, return entry */
154             return FunctionEntry;
155         }
156     }
157 
158     /* Nothing found, return NULL */
159     return NULL;
160 }
161 
162 BOOLEAN
163 NTAPI
164 RtlAddFunctionTable(
165     IN PRUNTIME_FUNCTION FunctionTable,
166     IN DWORD EntryCount,
167     IN DWORD64 BaseAddress)
168 {
169     UNIMPLEMENTED;
170     return FALSE;
171 }
172 
173 BOOLEAN
174 NTAPI
175 RtlDeleteFunctionTable(
176     IN PRUNTIME_FUNCTION FunctionTable)
177 {
178     UNIMPLEMENTED;
179     return FALSE;
180 }
181 
182 BOOLEAN
183 NTAPI
184 RtlInstallFunctionTableCallback(
185     IN DWORD64 TableIdentifier,
186     IN DWORD64 BaseAddress,
187     IN DWORD Length,
188     IN PGET_RUNTIME_FUNCTION_CALLBACK Callback,
189     IN PVOID Context,
190     IN PCWSTR OutOfProcessCallbackDll)
191 {
192     UNIMPLEMENTED;
193     return FALSE;
194 }
195 
196 void
197 FORCEINLINE
198 SetReg(PCONTEXT Context, BYTE Reg, DWORD64 Value)
199 {
200     ((DWORD64*)(&Context->Rax))[Reg] = Value;
201 }
202 
203 DWORD64
204 FORCEINLINE
205 GetReg(PCONTEXT Context, BYTE Reg)
206 {
207     return ((DWORD64*)(&Context->Rax))[Reg];
208 }
209 
210 void
211 FORCEINLINE
212 PopReg(PCONTEXT Context, BYTE Reg)
213 {
214     DWORD64 Value = *(DWORD64*)Context->Rsp;
215     Context->Rsp += 8;
216     SetReg(Context, Reg, Value);
217 }
218 
219 /*! RtlpTryToUnwindEpilog
220  * \brief Helper function that tries to unwind epilog instructions.
221  * \return TRUE if we have been in an epilog and it could be unwound.
222  *         FALSE if the instructions were not allowed for an epilog.
223  * \ref
224  *  http://msdn.microsoft.com/en-us/library/8ydc79k6(VS.80).aspx
225  *  http://msdn.microsoft.com/en-us/library/tawsa7cb.aspx
226  * \todo
227  *  - Test and compare with Windows behaviour
228  */
229 BOOLEAN
230 static
231 __inline
232 RtlpTryToUnwindEpilog(
233     PCONTEXT Context,
234     ULONG64 ImageBase,
235     PRUNTIME_FUNCTION FunctionEntry)
236 {
237     CONTEXT LocalContext;
238     BYTE *InstrPtr;
239     DWORD Instr;
240     BYTE Reg, Mod;
241     ULONG64 EndAddress;
242 
243     /* Make a local copy of the context */
244     LocalContext = *Context;
245 
246     InstrPtr = (BYTE*)LocalContext.Rip;
247 
248     /* Check if first instruction of epilog is "add rsp, x" */
249     Instr = *(DWORD*)InstrPtr;
250     if ( (Instr & 0x00fffdff) == 0x00c48148 )
251     {
252         if ( (Instr & 0x0000ff00) == 0x8300 )
253         {
254             /* This is "add rsp, 0x??" */
255             LocalContext.Rsp += Instr >> 24;
256             InstrPtr += 4;
257         }
258         else
259         {
260             /* This is "add rsp, 0x???????? */
261             LocalContext.Rsp += *(DWORD*)(InstrPtr + 3);
262             InstrPtr += 7;
263         }
264     }
265     /* Check if first instruction of epilog is "lea rsp, ..." */
266     else if ( (Instr & 0x38fffe) == 0x208d48 )
267     {
268         /* Get the register */
269         Reg = ((Instr << 8) | (Instr >> 16)) & 0x7;
270 
271         LocalContext.Rsp = GetReg(&LocalContext, Reg);
272 
273         /* Get adressing mode */
274         Mod = (Instr >> 22) & 0x3;
275         if (Mod == 0)
276         {
277             /* No displacement */
278             InstrPtr += 3;
279         }
280         else if (Mod == 1)
281         {
282             /* 1 byte displacement */
283             LocalContext.Rsp += Instr >> 24;
284             InstrPtr += 4;
285         }
286         else if (Mod == 2)
287         {
288             /* 4 bytes displacement */
289             LocalContext.Rsp += *(DWORD*)(InstrPtr + 3);
290             InstrPtr += 7;
291         }
292     }
293 
294     /* Loop the following instructions before the ret */
295     EndAddress = FunctionEntry->EndAddress + ImageBase - 1;
296     while ((DWORD64)InstrPtr < EndAddress)
297     {
298         Instr = *(DWORD*)InstrPtr;
299 
300         /* Check for a simple pop */
301         if ( (Instr & 0xf8) == 0x58 )
302         {
303             /* Opcode pops a basic register from stack */
304             Reg = Instr & 0x7;
305             PopReg(&LocalContext, Reg);
306             InstrPtr++;
307             continue;
308         }
309 
310         /* Check for REX + pop */
311         if ( (Instr & 0xf8fb) == 0x5841 )
312         {
313             /* Opcode is pop r8 .. r15 */
314             Reg = ((Instr >> 8) & 0x7) + 8;
315             PopReg(&LocalContext, Reg);
316             InstrPtr += 2;
317             continue;
318         }
319 
320         /* Opcode not allowed for Epilog */
321         return FALSE;
322     }
323 
324     /* Check if we are at the ret instruction */
325     if ((DWORD64)InstrPtr != EndAddress)
326     {
327         /* If we went past the end of the function, something is broken! */
328         ASSERT((DWORD64)InstrPtr <= EndAddress);
329         return FALSE;
330     }
331 
332     /* Make sure this is really a ret instruction */
333     if (*InstrPtr != 0xc3)
334     {
335         ASSERT(FALSE);
336         return FALSE;
337     }
338 
339     /* Unwind is finished, pop new Rip from Stack */
340     LocalContext.Rip = *(DWORD64*)LocalContext.Rsp;
341     LocalContext.Rsp += sizeof(DWORD64);
342 
343     *Context = LocalContext;
344     return TRUE;
345 }
346 
347 PEXCEPTION_ROUTINE
348 NTAPI
349 RtlVirtualUnwind(
350     _In_ ULONG HandlerType,
351     _In_ ULONG64 ImageBase,
352     _In_ ULONG64 ControlPc,
353     _In_ PRUNTIME_FUNCTION FunctionEntry,
354     _Inout_ PCONTEXT Context,
355     _Outptr_ PVOID *HandlerData,
356     _Out_ PULONG64 EstablisherFrame,
357     _Inout_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers)
358 {
359     PUNWIND_INFO UnwindInfo;
360     ULONG_PTR CodeOffset;
361     ULONG i;
362     UNWIND_CODE UnwindCode;
363     BYTE Reg;
364 
365     /* Use relative virtual address */
366     ControlPc -= ImageBase;
367 
368     /* Sanity checks */
369     if ( (ControlPc < FunctionEntry->BeginAddress) ||
370          (ControlPc >= FunctionEntry->EndAddress) )
371     {
372         return NULL;
373     }
374 
375     /* Get a pointer to the unwind info */
376     UnwindInfo = RVA(ImageBase, FunctionEntry->UnwindData);
377 
378     /* Calculate relative offset to function start */
379     CodeOffset = ControlPc - FunctionEntry->BeginAddress;
380 
381     /* Check if we are in the function epilog and try to finish it */
382     if (CodeOffset > UnwindInfo->SizeOfProlog)
383     {
384         if (RtlpTryToUnwindEpilog(Context, ImageBase, FunctionEntry))
385         {
386             /* There's no exception routine */
387             return NULL;
388         }
389     }
390 
391     /* Skip all Ops with an offset greater than the current Offset */
392     i = 0;
393     while (i < UnwindInfo->CountOfCodes &&
394            CodeOffset < UnwindInfo->UnwindCode[i].CodeOffset)
395     {
396         UnwindCode = UnwindInfo->UnwindCode[i];
397         switch (UnwindCode.UnwindOp)
398         {
399             case UWOP_SAVE_NONVOL:
400             case UWOP_SAVE_XMM:
401             case UWOP_SAVE_XMM128:
402                 i += 2;
403                 break;
404 
405             case UWOP_SAVE_NONVOL_FAR:
406             case UWOP_SAVE_XMM_FAR:
407             case UWOP_SAVE_XMM128_FAR:
408                 i += 3;
409                 break;
410 
411             case UWOP_ALLOC_LARGE:
412                 i += UnwindCode.OpInfo ? 3 : 2;
413                 break;
414 
415             default:
416                 i++;
417         }
418     }
419 
420     /* Process the remaining unwind ops */
421     while (i < UnwindInfo->CountOfCodes)
422     {
423         UnwindCode = UnwindInfo->UnwindCode[i];
424         switch (UnwindCode.UnwindOp)
425         {
426             case UWOP_PUSH_NONVOL:
427                 Reg = UnwindCode.OpInfo;
428                 SetReg(Context, Reg, *(DWORD64*)Context->Rsp);
429                 Context->Rsp += sizeof(DWORD64);
430                 i++;
431                 break;
432 
433             case UWOP_ALLOC_LARGE:
434                 if (UnwindCode.OpInfo)
435                 {
436                     ULONG Offset = *(ULONG*)(&UnwindInfo->UnwindCode[i+1]);
437                     Context->Rsp += Offset;
438                     i += 3;
439                 }
440                 else
441                 {
442                     USHORT Offset = UnwindInfo->UnwindCode[i+1].FrameOffset;
443                     Context->Rsp += Offset * 8;
444                     i += 2;
445                 }
446                 break;
447 
448             case UWOP_ALLOC_SMALL:
449                 Context->Rsp += (UnwindCode.OpInfo + 1) * 8;
450                 i++;
451                 break;
452 
453             case UWOP_SET_FPREG:
454                 i++;
455                 break;
456 
457             case UWOP_SAVE_NONVOL:
458                 i += 2;
459                 break;
460 
461             case UWOP_SAVE_NONVOL_FAR:
462                 i += 3;
463                 break;
464 
465             case UWOP_SAVE_XMM:
466                 i += 2;
467                 break;
468 
469             case UWOP_SAVE_XMM_FAR:
470                 i += 3;
471                 break;
472 
473             case UWOP_SAVE_XMM128:
474                 i += 2;
475                 break;
476 
477             case UWOP_SAVE_XMM128_FAR:
478                 i += 3;
479                 break;
480 
481             case UWOP_PUSH_MACHFRAME:
482                 i += 1;
483                 break;
484         }
485     }
486 
487     /* Unwind is finished, pop new Rip from Stack */
488     Context->Rip = *(DWORD64*)Context->Rsp;
489     Context->Rsp += sizeof(DWORD64);
490 
491     return 0;
492 }
493 
494 VOID
495 NTAPI
496 RtlUnwindEx(
497     _In_opt_ PVOID TargetFrame,
498     _In_opt_ PVOID TargetIp,
499     _In_opt_ PEXCEPTION_RECORD ExceptionRecord,
500     _In_ PVOID ReturnValue,
501     _In_ PCONTEXT ContextRecord,
502     _In_opt_ struct _UNWIND_HISTORY_TABLE *HistoryTable)
503 {
504     __debugbreak();
505     return;
506 }
507 
508 VOID
509 NTAPI
510 RtlUnwind(
511   IN PVOID TargetFrame,
512   IN PVOID TargetIp,
513   IN PEXCEPTION_RECORD ExceptionRecord,
514   IN PVOID ReturnValue)
515 {
516     UNIMPLEMENTED;
517     return;
518 }
519 
520 ULONG
521 NTAPI
522 RtlWalkFrameChain(OUT PVOID *Callers,
523                   IN ULONG Count,
524                   IN ULONG Flags)
525 {
526     CONTEXT Context;
527     ULONG64 ControlPc, ImageBase, EstablisherFrame;
528     ULONG64 StackLow, StackHigh;
529     PVOID HandlerData;
530     ULONG i, FramesToSkip;
531     PRUNTIME_FUNCTION FunctionEntry;
532 
533     DPRINT("Enter RtlWalkFrameChain\n");
534 
535     /* The upper bits in Flags define how many frames to skip */
536     FramesToSkip = Flags >> 8;
537 
538     /* Capture the current Context */
539     RtlCaptureContext(&Context);
540     ControlPc = Context.Rip;
541 
542     /* Get the stack limits */
543     RtlpGetStackLimits(&StackLow, &StackHigh);
544 
545     /* Check if we want the user-mode stack frame */
546     if (Flags & 1)
547     {
548     }
549 
550     /* Loop the frames */
551     for (i = 0; i < FramesToSkip + Count; i++)
552     {
553         /* Lookup the FunctionEntry for the current ControlPc */
554         FunctionEntry = RtlLookupFunctionEntry(ControlPc, &ImageBase, NULL);
555 
556         /* Is this a leaf function? */
557         if (!FunctionEntry)
558         {
559             Context.Rip = *(DWORD64*)Context.Rsp;
560             Context.Rsp += sizeof(DWORD64);
561             DPRINT("leaf funtion, new Rip = %p, new Rsp = %p\n", (PVOID)Context.Rip, (PVOID)Context.Rsp);
562         }
563         else
564         {
565             RtlVirtualUnwind(0,
566                              ImageBase,
567                              ControlPc,
568                              FunctionEntry,
569                              &Context,
570                              &HandlerData,
571                              &EstablisherFrame,
572                              NULL);
573             DPRINT("normal funtion, new Rip = %p, new Rsp = %p\n", (PVOID)Context.Rip, (PVOID)Context.Rsp);
574         }
575 
576         /* Check if new Rip is valid */
577         if (!Context.Rip)
578         {
579             break;
580         }
581 
582         /* Check, if we have left our stack */
583         if ((Context.Rsp < StackLow) || (Context.Rsp > StackHigh))
584         {
585             break;
586         }
587 
588         /* Continue with new Rip */
589         ControlPc = Context.Rip;
590 
591         /* Save value, if we are past the frames to skip */
592         if (i >= FramesToSkip)
593         {
594             Callers[i - FramesToSkip] = (PVOID)ControlPc;
595         }
596     }
597 
598     DPRINT("RtlWalkFrameChain returns %ld\n", i);
599     return i;
600 }
601 
602 /*! RtlGetCallersAddress
603  * \ref http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Debug/RtlGetCallersAddress.html
604  */
605 #undef RtlGetCallersAddress
606 VOID
607 NTAPI
608 RtlGetCallersAddress(
609     OUT PVOID *CallersAddress,
610     OUT PVOID *CallersCaller )
611 {
612     PVOID Callers[4];
613     ULONG Number;
614 
615     /* Get callers:
616      * RtlWalkFrameChain -> RtlGetCallersAddress -> x -> y */
617     Number = RtlWalkFrameChain(Callers, 4, 0);
618 
619     *CallersAddress = (Number >= 3) ? Callers[2] : NULL;
620     *CallersCaller = (Number == 4) ? Callers[3] : NULL;
621 
622     return;
623 }
624 
625 // FIXME: move to different file
626 VOID
627 NTAPI
628 RtlRaiseException(IN PEXCEPTION_RECORD ExceptionRecord)
629 {
630     CONTEXT Context;
631     NTSTATUS Status = STATUS_INVALID_DISPOSITION;
632     ULONG64 ImageBase;
633     PRUNTIME_FUNCTION FunctionEntry;
634     PVOID HandlerData;
635     ULONG64 EstablisherFrame;
636 
637     /* Capture the context */
638     RtlCaptureContext(&Context);
639 
640     /* Get the function entry for this function */
641     FunctionEntry = RtlLookupFunctionEntry(Context.Rip,
642                                            &ImageBase,
643                                            NULL);
644 
645     /* Check if we found it */
646     if (FunctionEntry)
647     {
648         /* Unwind to the caller of this function */
649         RtlVirtualUnwind(UNW_FLAG_NHANDLER,
650                          ImageBase,
651                          Context.Rip,
652                          FunctionEntry,
653                          &Context,
654                          &HandlerData,
655                          &EstablisherFrame,
656                          NULL);
657 
658         /* Save the exception address */
659         ExceptionRecord->ExceptionAddress = (PVOID)Context.Rip;
660 
661         /* Write the context flag */
662         Context.ContextFlags = CONTEXT_FULL;
663 
664         /* Check if user mode debugger is active */
665         if (RtlpCheckForActiveDebugger())
666         {
667             /* Raise an exception immediately */
668             Status = ZwRaiseException(ExceptionRecord, &Context, TRUE);
669         }
670         else
671         {
672             /* Dispatch the exception and check if we should continue */
673             if (!RtlDispatchException(ExceptionRecord, &Context))
674             {
675                 /* Raise the exception */
676                 Status = ZwRaiseException(ExceptionRecord, &Context, FALSE);
677             }
678             else
679             {
680                 /* Continue, go back to previous context */
681                 Status = ZwContinue(&Context, FALSE);
682             }
683         }
684     }
685 
686     /* If we returned, raise a status */
687     RtlRaiseStatus(Status);
688 }
689 
690