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
31 #define UNW_FLAG_NHANDLER 0
32 #define UNW_FLAG_EHANDLER 1
33 #define UNW_FLAG_UHANDLER 2
34 #define UNW_FLAG_CHAININFO 4
36 typedef unsigned char UBYTE
;
38 typedef union _UNWIND_CODE
47 } UNWIND_CODE
, *PUNWIND_CODE
;
49 typedef struct _UNWIND_INFO
55 UBYTE FrameRegister
:4;
57 UNWIND_CODE UnwindCode
[1];
59 OPTIONAL ULONG ExceptionHandler;
60 OPTIONAL ULONG FunctionEntry;
62 OPTIONAL ULONG ExceptionData[];
64 } UNWIND_INFO
, *PUNWIND_INFO
;
66 /* FUNCTIONS *****************************************************************/
68 /*! RtlLookupFunctionTable
69 * \brief Locates the table of RUNTIME_FUNCTION entries for a code address.
71 * Address of the code, for which the table should be searched.
73 * Pointer to a DWORD64 that receives the base address of the
74 * corresponding executable image.
76 * Pointer to an ULONG that receives the number of table entries
77 * present in the table.
81 RtlLookupFunctionTable(
83 OUT PDWORD64 ImageBase
,
89 /* Find corresponding file header from code address */
90 if (!RtlPcToFileHeader((PVOID
)ControlPc
, (PVOID
*)ImageBase
))
96 /* Locate the exception directory */
97 Table
= RtlImageDirectoryEntryToData((PVOID
)*ImageBase
,
99 IMAGE_DIRECTORY_ENTRY_EXCEPTION
,
102 /* Return the number of entries */
103 *Length
= Size
/ sizeof(RUNTIME_FUNCTION
);
105 /* Return the address of the table */
109 /*! RtlLookupFunctionEntry
110 * \brief Locates the RUNTIME_FUNCTION entry corresponding to a code address.
111 * \ref http://msdn.microsoft.com/en-us/library/ms680597(VS.85).aspx
112 * \todo Implement HistoryTable
116 RtlLookupFunctionEntry(
117 IN DWORD64 ControlPc
,
118 OUT PDWORD64 ImageBase
,
119 OUT PUNWIND_HISTORY_TABLE HistoryTable
)
121 PRUNTIME_FUNCTION FunctionTable
, FunctionEntry
;
123 ULONG IndexLo
, IndexHi
, IndexMid
;
125 /* Find the corresponding table */
126 FunctionTable
= RtlLookupFunctionTable(ControlPc
, ImageBase
, &TableLength
);
128 /* Fail, if no table is found */
134 /* Use relative virtual address */
135 ControlPc
-= *ImageBase
;
137 /* Do a binary search */
139 IndexHi
= TableLength
;
140 while (IndexHi
> IndexLo
)
142 IndexMid
= (IndexLo
+ IndexHi
) / 2;
143 FunctionEntry
= &FunctionTable
[IndexMid
];
145 if (ControlPc
< FunctionEntry
->BeginAddress
)
147 /* Continue search in lower half */
150 else if (ControlPc
>= FunctionEntry
->EndAddress
)
152 /* Continue search in upper half */
153 IndexLo
= IndexMid
+ 1;
157 /* ControlPc is within limits, return entry */
158 return FunctionEntry
;
162 /* Nothing found, return NULL */
169 IN PRUNTIME_FUNCTION FunctionTable
,
171 IN DWORD64 BaseAddress
)
179 RtlDeleteFunctionTable(
180 IN PRUNTIME_FUNCTION FunctionTable
)
188 RtlInstallFunctionTableCallback(
189 IN DWORD64 TableIdentifier
,
190 IN DWORD64 BaseAddress
,
192 IN PGET_RUNTIME_FUNCTION_CALLBACK Callback
,
194 IN PCWSTR OutOfProcessCallbackDll
)
202 SetReg(PCONTEXT Context
, BYTE Reg
, DWORD64 Value
)
204 ((DWORD64
*)(&Context
->Rax
))[Reg
] = Value
;
209 GetReg(PCONTEXT Context
, BYTE Reg
)
211 return ((DWORD64
*)(&Context
->Rax
))[Reg
];
216 PopReg(PCONTEXT Context
, BYTE Reg
)
218 DWORD64 Value
= *(DWORD64
*)Context
->Rsp
;
220 SetReg(Context
, Reg
, Value
);
223 /*! RtlpTryToUnwindEpilog
224 * \brief Helper function that tries to unwind epilog instructions.
225 * \return TRUE if we have been in an epilog and it could be unwound.
226 * FALSE if the instructions were not allowed for an epilog.
228 * http://msdn.microsoft.com/en-us/library/8ydc79k6(VS.80).aspx
229 * http://msdn.microsoft.com/en-us/library/tawsa7cb.aspx
231 * - Test and compare with Windows behaviour
236 RtlpTryToUnwindEpilog(
239 PRUNTIME_FUNCTION FunctionEntry
)
241 CONTEXT LocalContext
;
247 /* Make a local copy of the context */
248 LocalContext
= *Context
;
250 InstrPtr
= (BYTE
*)LocalContext
.Rip
;
252 /* Check if first instruction of epilog is "add rsp, x" */
253 Instr
= *(DWORD
*)InstrPtr
;
254 if ( (Instr
& 0x00fffdff) == 0x00c48148 )
256 if ( (Instr
& 0x0000ff00) == 0x8300 )
258 /* This is "add rsp, 0x??" */
259 LocalContext
.Rsp
+= Instr
>> 24;
264 /* This is "add rsp, 0x???????? */
265 LocalContext
.Rsp
+= *(DWORD
*)(InstrPtr
+ 3);
269 /* Check if first instruction of epilog is "lea rsp, ..." */
270 else if ( (Instr
& 0x38fffe) == 0x208d48 )
272 /* Get the register */
273 Reg
= ((Instr
<< 8) | (Instr
>> 16)) & 0x7;
275 LocalContext
.Rsp
= GetReg(&LocalContext
, Reg
);
277 /* Get adressing mode */
278 Mod
= (Instr
>> 22) & 0x3;
281 /* No displacement */
286 /* 1 byte displacement */
287 LocalContext
.Rsp
+= Instr
>> 24;
292 /* 4 bytes displacement */
293 LocalContext
.Rsp
+= *(DWORD
*)(InstrPtr
+ 3);
298 /* Loop the following instructions */
299 EndAddress
= FunctionEntry
->EndAddress
+ ImageBase
;
300 while((DWORD64
)InstrPtr
< EndAddress
)
302 Instr
= *(DWORD
*)InstrPtr
;
304 /* Check for a simple pop */
305 if ( (Instr
& 0xf8) == 0x58 )
307 /* Opcode pops a basic register from stack */
309 PopReg(&LocalContext
, Reg
);
314 /* Check for REX + pop */
315 if ( (Instr
& 0xf8fb) == 0x5841 )
317 /* Opcode is pop r8 .. r15 */
318 Reg
= (Instr
& 0x7) + 8;
319 PopReg(&LocalContext
, Reg
);
324 /* Check for retn / retf */
325 if ( (Instr
& 0xf7) == 0xc3 )
327 /* We are finished */
331 /* Opcode not allowed for Epilog */
335 /* Unwind is finished, pop new Rip from Stack */
336 LocalContext
.Rip
= *(DWORD64
*)LocalContext
.Rsp
;
337 LocalContext
.Rsp
+= sizeof(DWORD64
);
339 *Context
= LocalContext
;
347 IN ULONG HandlerType
,
348 IN ULONG64 ImageBase
,
349 IN ULONG64 ControlPc
,
350 IN PRUNTIME_FUNCTION FunctionEntry
,
351 IN OUT PCONTEXT Context
,
352 OUT PVOID
*HandlerData
,
353 OUT PULONG64 EstablisherFrame
,
354 IN OUT PKNONVOLATILE_CONTEXT_POINTERS ContextPointers
)
356 PUNWIND_INFO UnwindInfo
;
357 ULONG_PTR CodeOffset
;
359 UNWIND_CODE UnwindCode
;
362 /* Use relative virtual address */
363 ControlPc
-= ImageBase
;
366 if ( (ControlPc
< FunctionEntry
->BeginAddress
) ||
367 (ControlPc
>= FunctionEntry
->EndAddress
) )
372 /* Get a pointer to the unwind info */
373 UnwindInfo
= RVA(ImageBase
, FunctionEntry
->UnwindData
);
375 /* Calculate relative offset to function start */
376 CodeOffset
= ControlPc
- FunctionEntry
->BeginAddress
;
378 /* Check if we are in the function epilog and try to finish it */
379 if (CodeOffset
> UnwindInfo
->SizeOfProlog
)
381 if (RtlpTryToUnwindEpilog(Context
, ImageBase
, FunctionEntry
))
383 /* There's no exception routine */
388 /* Skip all Ops with an offset greater than the current Offset */
390 while (i
< UnwindInfo
->CountOfCodes
&&
391 CodeOffset
< UnwindInfo
->UnwindCode
[i
].CodeOffset
)
393 UnwindCode
= UnwindInfo
->UnwindCode
[i
];
394 switch (UnwindCode
.UnwindOp
)
396 case UWOP_SAVE_NONVOL
:
398 case UWOP_SAVE_XMM128
:
402 case UWOP_SAVE_NONVOL_FAR
:
403 case UWOP_SAVE_XMM_FAR
:
404 case UWOP_SAVE_XMM128_FAR
:
408 case UWOP_ALLOC_LARGE
:
409 i
+= UnwindCode
.OpInfo
? 3 : 2;
417 /* Process the left Ops */
418 while (i
< UnwindInfo
->CountOfCodes
)
420 UnwindCode
= UnwindInfo
->UnwindCode
[i
];
421 switch (UnwindCode
.UnwindOp
)
423 case UWOP_PUSH_NONVOL
:
424 Reg
= UnwindCode
.OpInfo
;
425 SetReg(Context
, Reg
, *(DWORD64
*)Context
->Rsp
);
426 Context
->Rsp
+= sizeof(DWORD64
);
430 case UWOP_ALLOC_LARGE
:
431 if (UnwindCode
.OpInfo
)
433 ULONG Offset
= *(ULONG
*)(&UnwindInfo
->UnwindCode
[i
+1]);
434 Context
->Rsp
+= Offset
;
439 USHORT Offset
= UnwindInfo
->UnwindCode
[i
+1].FrameOffset
;
440 Context
->Rsp
+= Offset
* 8;
445 case UWOP_ALLOC_SMALL
:
446 Context
->Rsp
+= (UnwindCode
.OpInfo
+ 1) * 8;
454 case UWOP_SAVE_NONVOL
:
458 case UWOP_SAVE_NONVOL_FAR
:
466 case UWOP_SAVE_XMM_FAR
:
470 case UWOP_SAVE_XMM128
:
474 case UWOP_SAVE_XMM128_FAR
:
478 case UWOP_PUSH_MACHFRAME
:
484 /* Unwind is finished, pop new Rip from Stack */
485 Context
->Rip
= *(DWORD64
*)Context
->Rsp
;
486 Context
->Rsp
+= sizeof(DWORD64
);
494 IN ULONG64 TargetFrame
,
496 IN PEXCEPTION_RECORD ExceptionRecord
,
497 IN PVOID ReturnValue
,
498 OUT PCONTEXT OriginalContext
,
499 IN PUNWIND_HISTORY_TABLE HistoryTable
)
508 IN PVOID TargetFrame
,
510 IN PEXCEPTION_RECORD ExceptionRecord
,
511 IN PVOID ReturnValue
)
519 RtlWalkFrameChain(OUT PVOID
*Callers
,
524 ULONG64 ControlPc
, ImageBase
, EstablisherFrame
;
525 ULONG64 StackLow
, StackHigh
;
528 PRUNTIME_FUNCTION FunctionEntry
;
530 DPRINT("Enter RtlWalkFrameChain\n");
532 /* Capture the current Context */
533 RtlCaptureContext(&Context
);
534 ControlPc
= Context
.Rip
;
536 /* Get the stack limits */
537 RtlpGetStackLimits(&StackLow
, &StackHigh
);
539 /* Check if we want the user-mode stack frame */
544 /* Loop the frames */
545 for (i
= 0; i
< Count
; i
++)
547 /* Lookup the FunctionEntry for the current ControlPc */
548 FunctionEntry
= RtlLookupFunctionEntry(ControlPc
, &ImageBase
, NULL
);
550 /* Is this a leaf function? */
553 Context
.Rip
= *(DWORD64
*)Context
.Rsp
;
554 Context
.Rsp
+= sizeof(DWORD64
);
555 DPRINT("leaf funtion, new Rip = %p, new Rsp = %p\n", (PVOID
)Context
.Rip
, (PVOID
)Context
.Rsp
);
567 DPRINT("normal funtion, new Rip = %p, new Rsp = %p\n", (PVOID
)Context
.Rip
, (PVOID
)Context
.Rsp
);
570 /* Check if new Rip is valid */
576 /* Check, if we have left our stack */
577 if ((Context
.Rsp
< StackLow
) || (Context
.Rsp
> StackHigh
))
582 /* Save this frame and continue with new Rip */
583 ControlPc
= Context
.Rip
;
584 Callers
[i
] = (PVOID
)ControlPc
;
587 DPRINT("RtlWalkFrameChain returns %ld\n", i
);
591 /*! RtlGetCallersAddress
592 * \ref http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Debug/RtlGetCallersAddress.html
594 #undef RtlGetCallersAddress
597 RtlGetCallersAddress(
598 OUT PVOID
*CallersAddress
,
599 OUT PVOID
*CallersCaller
)
605 * RtlWalkFrameChain -> RtlGetCallersAddress -> x -> y */
606 Number
= RtlWalkFrameChain(Callers
, 4, 0);
610 *CallersAddress
= (Number
>= 3) ? Callers
[2] : NULL
;
614 *CallersCaller
= (Number
== 4) ? Callers
[3] : NULL
;
620 // FIXME: move to different file
623 RtlRaiseException(IN PEXCEPTION_RECORD ExceptionRecord
)
626 NTSTATUS Status
= STATUS_INVALID_DISPOSITION
;
628 PRUNTIME_FUNCTION FunctionEntry
;
630 ULONG64 EstablisherFrame
;
632 /* Capture the context */
633 RtlCaptureContext(&Context
);
635 /* Get the function entry for this function */
636 FunctionEntry
= RtlLookupFunctionEntry(Context
.Rip
,
640 /* Check if we found it */
643 /* Unwind to the caller of this function */
644 RtlVirtualUnwind(UNW_FLAG_NHANDLER
,
653 /* Save the exception address */
654 ExceptionRecord
->ExceptionAddress
= (PVOID
)Context
.Rip
;
656 /* Write the context flag */
657 Context
.ContextFlags
= CONTEXT_FULL
;
659 /* Check if user mode debugger is active */
660 if (RtlpCheckForActiveDebugger())
662 /* Raise an exception immediately */
663 Status
= ZwRaiseException(ExceptionRecord
, &Context
, TRUE
);
667 /* Dispatch the exception and check if we should continue */
668 if (!RtlDispatchException(ExceptionRecord
, &Context
))
670 /* Raise the exception */
671 Status
= ZwRaiseException(ExceptionRecord
, &Context
, FALSE
);
675 /* Continue, go back to previous context */
676 Status
= ZwContinue(&Context
, FALSE
);
681 /* If we returned, raise a status */
682 RtlRaiseStatus(Status
);