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)
8 /* INCLUDES *****************************************************************/
15 #define UNWIND_HISTORY_TABLE_NONE 0
16 #define UNWIND_HISTORY_TABLE_GLOBAL 1
17 #define UNWIND_HISTORY_TABLE_LOCAL 2
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
32 typedef unsigned char UBYTE
;
34 typedef union _UNWIND_CODE
43 } UNWIND_CODE
, *PUNWIND_CODE
;
45 typedef struct _UNWIND_INFO
51 UBYTE FrameRegister
:4;
53 UNWIND_CODE UnwindCode
[1];
55 OPTIONAL ULONG ExceptionHandler;
56 OPTIONAL ULONG FunctionEntry;
58 OPTIONAL ULONG ExceptionData[];
60 } UNWIND_INFO
, *PUNWIND_INFO
;
62 /* FUNCTIONS *****************************************************************/
64 /*! RtlLookupFunctionTable
65 * \brief Locates the table of RUNTIME_FUNCTION entries for a code address.
67 * Address of the code, for which the table should be searched.
69 * Pointer to a DWORD64 that receives the base address of the
70 * corresponding executable image.
72 * Pointer to an ULONG that receives the number of table entries
73 * present in the table.
77 RtlLookupFunctionTable(
79 OUT PDWORD64 ImageBase
,
85 /* Find corresponding file header from code address */
86 if (!RtlPcToFileHeader((PVOID
)ControlPc
, (PVOID
*)ImageBase
))
92 /* Locate the exception directory */
93 Table
= RtlImageDirectoryEntryToData((PVOID
)*ImageBase
,
95 IMAGE_DIRECTORY_ENTRY_EXCEPTION
,
98 /* Return the number of entries */
99 *Length
= Size
/ sizeof(RUNTIME_FUNCTION
);
101 /* Return the address of the table */
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
112 RtlLookupFunctionEntry(
113 IN DWORD64 ControlPc
,
114 OUT PDWORD64 ImageBase
,
115 OUT PUNWIND_HISTORY_TABLE HistoryTable
)
117 PRUNTIME_FUNCTION FunctionTable
, FunctionEntry
;
119 ULONG IndexLo
, IndexHi
, IndexMid
;
121 /* Find the corresponding table */
122 FunctionTable
= RtlLookupFunctionTable(ControlPc
, ImageBase
, &TableLength
);
124 /* Fail, if no table is found */
130 /* Use relative virtual address */
131 ControlPc
-= *ImageBase
;
133 /* Do a binary search */
135 IndexHi
= TableLength
;
136 while (IndexHi
> IndexLo
)
138 IndexMid
= (IndexLo
+ IndexHi
) / 2;
139 FunctionEntry
= &FunctionTable
[IndexMid
];
141 if (ControlPc
< FunctionEntry
->BeginAddress
)
143 /* Continue search in lower half */
146 else if (ControlPc
>= FunctionEntry
->EndAddress
)
148 /* Continue search in upper half */
149 IndexLo
= IndexMid
+ 1;
153 /* ControlPc is within limits, return entry */
154 return FunctionEntry
;
158 /* Nothing found, return NULL */
165 IN PRUNTIME_FUNCTION FunctionTable
,
167 IN DWORD64 BaseAddress
)
175 RtlDeleteFunctionTable(
176 IN PRUNTIME_FUNCTION FunctionTable
)
184 RtlInstallFunctionTableCallback(
185 IN DWORD64 TableIdentifier
,
186 IN DWORD64 BaseAddress
,
188 IN PGET_RUNTIME_FUNCTION_CALLBACK Callback
,
190 IN PCWSTR OutOfProcessCallbackDll
)
198 SetReg(PCONTEXT Context
, BYTE Reg
, DWORD64 Value
)
200 ((DWORD64
*)(&Context
->Rax
))[Reg
] = Value
;
205 GetReg(PCONTEXT Context
, BYTE Reg
)
207 return ((DWORD64
*)(&Context
->Rax
))[Reg
];
212 PopReg(PCONTEXT Context
, BYTE Reg
)
214 DWORD64 Value
= *(DWORD64
*)Context
->Rsp
;
216 SetReg(Context
, Reg
, Value
);
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.
224 * http://msdn.microsoft.com/en-us/library/8ydc79k6(VS.80).aspx
225 * http://msdn.microsoft.com/en-us/library/tawsa7cb.aspx
227 * - Test and compare with Windows behaviour
232 RtlpTryToUnwindEpilog(
235 PRUNTIME_FUNCTION FunctionEntry
)
237 CONTEXT LocalContext
;
243 /* Make a local copy of the context */
244 LocalContext
= *Context
;
246 InstrPtr
= (BYTE
*)LocalContext
.Rip
;
248 /* Check if first instruction of epilog is "add rsp, x" */
249 Instr
= *(DWORD
*)InstrPtr
;
250 if ( (Instr
& 0x00fffdff) == 0x00c48148 )
252 if ( (Instr
& 0x0000ff00) == 0x8300 )
254 /* This is "add rsp, 0x??" */
255 LocalContext
.Rsp
+= Instr
>> 24;
260 /* This is "add rsp, 0x???????? */
261 LocalContext
.Rsp
+= *(DWORD
*)(InstrPtr
+ 3);
265 /* Check if first instruction of epilog is "lea rsp, ..." */
266 else if ( (Instr
& 0x38fffe) == 0x208d48 )
268 /* Get the register */
269 Reg
= ((Instr
<< 8) | (Instr
>> 16)) & 0x7;
271 LocalContext
.Rsp
= GetReg(&LocalContext
, Reg
);
273 /* Get adressing mode */
274 Mod
= (Instr
>> 22) & 0x3;
277 /* No displacement */
282 /* 1 byte displacement */
283 LocalContext
.Rsp
+= Instr
>> 24;
288 /* 4 bytes displacement */
289 LocalContext
.Rsp
+= *(DWORD
*)(InstrPtr
+ 3);
294 /* Loop the following instructions before the ret */
295 EndAddress
= FunctionEntry
->EndAddress
+ ImageBase
- 1;
296 while ((DWORD64
)InstrPtr
< EndAddress
)
298 Instr
= *(DWORD
*)InstrPtr
;
300 /* Check for a simple pop */
301 if ( (Instr
& 0xf8) == 0x58 )
303 /* Opcode pops a basic register from stack */
305 PopReg(&LocalContext
, Reg
);
310 /* Check for REX + pop */
311 if ( (Instr
& 0xf8fb) == 0x5841 )
313 /* Opcode is pop r8 .. r15 */
314 Reg
= ((Instr
>> 8) & 0x7) + 8;
315 PopReg(&LocalContext
, Reg
);
320 /* Opcode not allowed for Epilog */
324 /* Check if we are at the ret instruction */
325 if ((DWORD64
)InstrPtr
!= EndAddress
)
327 /* If we went past the end of the function, something is broken! */
328 ASSERT((DWORD64
)InstrPtr
<= EndAddress
);
332 /* Make sure this is really a ret instruction */
333 if (*InstrPtr
!= 0xc3)
339 /* Unwind is finished, pop new Rip from Stack */
340 LocalContext
.Rip
= *(DWORD64
*)LocalContext
.Rsp
;
341 LocalContext
.Rsp
+= sizeof(DWORD64
);
343 *Context
= LocalContext
;
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
)
359 PUNWIND_INFO UnwindInfo
;
360 ULONG_PTR CodeOffset
;
362 UNWIND_CODE UnwindCode
;
365 /* Use relative virtual address */
366 ControlPc
-= ImageBase
;
369 if ( (ControlPc
< FunctionEntry
->BeginAddress
) ||
370 (ControlPc
>= FunctionEntry
->EndAddress
) )
375 /* Get a pointer to the unwind info */
376 UnwindInfo
= RVA(ImageBase
, FunctionEntry
->UnwindData
);
378 /* Calculate relative offset to function start */
379 CodeOffset
= ControlPc
- FunctionEntry
->BeginAddress
;
381 /* Check if we are in the function epilog and try to finish it */
382 if (CodeOffset
> UnwindInfo
->SizeOfProlog
)
384 if (RtlpTryToUnwindEpilog(Context
, ImageBase
, FunctionEntry
))
386 /* There's no exception routine */
391 /* Skip all Ops with an offset greater than the current Offset */
393 while (i
< UnwindInfo
->CountOfCodes
&&
394 CodeOffset
< UnwindInfo
->UnwindCode
[i
].CodeOffset
)
396 UnwindCode
= UnwindInfo
->UnwindCode
[i
];
397 switch (UnwindCode
.UnwindOp
)
399 case UWOP_SAVE_NONVOL
:
401 case UWOP_SAVE_XMM128
:
405 case UWOP_SAVE_NONVOL_FAR
:
406 case UWOP_SAVE_XMM_FAR
:
407 case UWOP_SAVE_XMM128_FAR
:
411 case UWOP_ALLOC_LARGE
:
412 i
+= UnwindCode
.OpInfo
? 3 : 2;
420 /* Process the remaining unwind ops */
421 while (i
< UnwindInfo
->CountOfCodes
)
423 UnwindCode
= UnwindInfo
->UnwindCode
[i
];
424 switch (UnwindCode
.UnwindOp
)
426 case UWOP_PUSH_NONVOL
:
427 Reg
= UnwindCode
.OpInfo
;
428 SetReg(Context
, Reg
, *(DWORD64
*)Context
->Rsp
);
429 Context
->Rsp
+= sizeof(DWORD64
);
433 case UWOP_ALLOC_LARGE
:
434 if (UnwindCode
.OpInfo
)
436 ULONG Offset
= *(ULONG
*)(&UnwindInfo
->UnwindCode
[i
+1]);
437 Context
->Rsp
+= Offset
;
442 USHORT Offset
= UnwindInfo
->UnwindCode
[i
+1].FrameOffset
;
443 Context
->Rsp
+= Offset
* 8;
448 case UWOP_ALLOC_SMALL
:
449 Context
->Rsp
+= (UnwindCode
.OpInfo
+ 1) * 8;
457 case UWOP_SAVE_NONVOL
:
461 case UWOP_SAVE_NONVOL_FAR
:
469 case UWOP_SAVE_XMM_FAR
:
473 case UWOP_SAVE_XMM128
:
477 case UWOP_SAVE_XMM128_FAR
:
481 case UWOP_PUSH_MACHFRAME
:
487 /* Unwind is finished, pop new Rip from Stack */
488 Context
->Rip
= *(DWORD64
*)Context
->Rsp
;
489 Context
->Rsp
+= sizeof(DWORD64
);
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
)
511 IN PVOID TargetFrame
,
513 IN PEXCEPTION_RECORD ExceptionRecord
,
514 IN PVOID ReturnValue
)
522 RtlWalkFrameChain(OUT PVOID
*Callers
,
527 ULONG64 ControlPc
, ImageBase
, EstablisherFrame
;
528 ULONG64 StackLow
, StackHigh
;
530 ULONG i
, FramesToSkip
;
531 PRUNTIME_FUNCTION FunctionEntry
;
533 DPRINT("Enter RtlWalkFrameChain\n");
535 /* The upper bits in Flags define how many frames to skip */
536 FramesToSkip
= Flags
>> 8;
538 /* Capture the current Context */
539 RtlCaptureContext(&Context
);
540 ControlPc
= Context
.Rip
;
542 /* Get the stack limits */
543 RtlpGetStackLimits(&StackLow
, &StackHigh
);
545 /* Check if we want the user-mode stack frame */
550 /* Loop the frames */
551 for (i
= 0; i
< FramesToSkip
+ Count
; i
++)
553 /* Lookup the FunctionEntry for the current ControlPc */
554 FunctionEntry
= RtlLookupFunctionEntry(ControlPc
, &ImageBase
, NULL
);
556 /* Is this a leaf function? */
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
);
573 DPRINT("normal funtion, new Rip = %p, new Rsp = %p\n", (PVOID
)Context
.Rip
, (PVOID
)Context
.Rsp
);
576 /* Check if new Rip is valid */
582 /* Check, if we have left our stack */
583 if ((Context
.Rsp
< StackLow
) || (Context
.Rsp
> StackHigh
))
588 /* Continue with new Rip */
589 ControlPc
= Context
.Rip
;
591 /* Save value, if we are past the frames to skip */
592 if (i
>= FramesToSkip
)
594 Callers
[i
- FramesToSkip
] = (PVOID
)ControlPc
;
598 DPRINT("RtlWalkFrameChain returns %ld\n", i
);
602 /*! RtlGetCallersAddress
603 * \ref http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Debug/RtlGetCallersAddress.html
605 #undef RtlGetCallersAddress
608 RtlGetCallersAddress(
609 OUT PVOID
*CallersAddress
,
610 OUT PVOID
*CallersCaller
)
616 * RtlWalkFrameChain -> RtlGetCallersAddress -> x -> y */
617 Number
= RtlWalkFrameChain(Callers
, 4, 0);
619 *CallersAddress
= (Number
>= 3) ? Callers
[2] : NULL
;
620 *CallersCaller
= (Number
== 4) ? Callers
[3] : NULL
;
625 // FIXME: move to different file
628 RtlRaiseException(IN PEXCEPTION_RECORD ExceptionRecord
)
631 NTSTATUS Status
= STATUS_INVALID_DISPOSITION
;
633 PRUNTIME_FUNCTION FunctionEntry
;
635 ULONG64 EstablisherFrame
;
637 /* Capture the context */
638 RtlCaptureContext(&Context
);
640 /* Get the function entry for this function */
641 FunctionEntry
= RtlLookupFunctionEntry(Context
.Rip
,
645 /* Check if we found it */
648 /* Unwind to the caller of this function */
649 RtlVirtualUnwind(UNW_FLAG_NHANDLER
,
658 /* Save the exception address */
659 ExceptionRecord
->ExceptionAddress
= (PVOID
)Context
.Rip
;
661 /* Write the context flag */
662 Context
.ContextFlags
= CONTEXT_FULL
;
664 /* Check if user mode debugger is active */
665 if (RtlpCheckForActiveDebugger())
667 /* Raise an exception immediately */
668 Status
= ZwRaiseException(ExceptionRecord
, &Context
, TRUE
);
672 /* Dispatch the exception and check if we should continue */
673 if (!RtlDispatchException(ExceptionRecord
, &Context
))
675 /* Raise the exception */
676 Status
= ZwRaiseException(ExceptionRecord
, &Context
, FALSE
);
680 /* Continue, go back to previous context */
681 Status
= ZwContinue(&Context
, FALSE
);
686 /* If we returned, raise a status */
687 RtlRaiseStatus(Status
);