[RTL] Improve RtlVirtualUnwind

* Add support for version 2 unwind info
* Implement UnwindOpSlots() and use it (based on https://github.com/dotnet/coreclr/blob/master/src/unwinder/amd64/unwinder_amd64.cpp)
* Fix handling of UWOP_PUSH_MACHFRAME
This commit is contained in:
Timo Kreuzer 2018-03-01 14:14:13 +01:00
parent 5075f7d746
commit 1d58e84736

View file

@ -22,8 +22,13 @@
#define UWOP_SET_FPREG 3 #define UWOP_SET_FPREG 3
#define UWOP_SAVE_NONVOL 4 #define UWOP_SAVE_NONVOL 4
#define UWOP_SAVE_NONVOL_FAR 5 #define UWOP_SAVE_NONVOL_FAR 5
#if 0 // These are deprecated / not for x64
#define UWOP_SAVE_XMM 6 #define UWOP_SAVE_XMM 6
#define UWOP_SAVE_XMM_FAR 7 #define UWOP_SAVE_XMM_FAR 7
#else
#define UWOP_EPILOG 6
#define UWOP_SPARE_CODE 7
#endif
#define UWOP_SAVE_XMM128 8 #define UWOP_SAVE_XMM128 8
#define UWOP_SAVE_XMM128_FAR 9 #define UWOP_SAVE_XMM128_FAR 9
#define UWOP_PUSH_MACHFRAME 10 #define UWOP_PUSH_MACHFRAME 10
@ -193,27 +198,121 @@ RtlInstallFunctionTableCallback(
return FALSE; return FALSE;
} }
static
__inline
ULONG
UnwindOpSlots(
_In_ UNWIND_CODE UnwindCode)
{
static const UCHAR UnwindOpExtraSlotTable[] =
{
0, // UWOP_PUSH_NONVOL
1, // UWOP_ALLOC_LARGE (or 3, special cased in lookup code)
0, // UWOP_ALLOC_SMALL
0, // UWOP_SET_FPREG
1, // UWOP_SAVE_NONVOL
2, // UWOP_SAVE_NONVOL_FAR
1, // UWOP_EPILOG // previously UWOP_SAVE_XMM
2, // UWOP_SPARE_CODE // previously UWOP_SAVE_XMM_FAR
1, // UWOP_SAVE_XMM128
2, // UWOP_SAVE_XMM128_FAR
0, // UWOP_PUSH_MACHFRAME
2, // UWOP_SET_FPREG_LARGE
};
if ((UnwindCode.UnwindOp == UWOP_ALLOC_LARGE) &&
(UnwindCode.OpInfo != 0))
{
return 3;
}
else
{
return UnwindOpExtraSlotTable[UnwindCode.UnwindOp] + 1;
}
}
static
__inline
void void
FORCEINLINE SetReg(
SetReg(PCONTEXT Context, BYTE Reg, DWORD64 Value) _Inout_ PCONTEXT Context,
_In_ BYTE Reg,
_In_ DWORD64 Value)
{ {
((DWORD64*)(&Context->Rax))[Reg] = Value; ((DWORD64*)(&Context->Rax))[Reg] = Value;
} }
static
__inline
void
SetRegFromStackValue(
_Inout_ PCONTEXT Context,
_Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers,
_In_ BYTE Reg,
_In_ PDWORD64 ValuePointer)
{
SetReg(Context, Reg, *ValuePointer);
if (ContextPointers != NULL)
{
ContextPointers->IntegerContext[Reg] = ValuePointer;
}
}
static
__inline
DWORD64 DWORD64
FORCEINLINE GetReg(
GetReg(PCONTEXT Context, BYTE Reg) _In_ PCONTEXT Context,
_In_ BYTE Reg)
{ {
return ((DWORD64*)(&Context->Rax))[Reg]; return ((DWORD64*)(&Context->Rax))[Reg];
} }
static
__inline
void void
FORCEINLINE PopReg(
PopReg(PCONTEXT Context, BYTE Reg) _Inout_ PCONTEXT Context,
_Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers,
_In_ BYTE Reg)
{ {
DWORD64 Value = *(DWORD64*)Context->Rsp; SetRegFromStackValue(Context, ContextPointers, Reg, (PDWORD64)Context->Rsp);
Context->Rsp += 8; Context->Rsp += sizeof(DWORD64);
SetReg(Context, Reg, Value); }
static
__inline
void
SetXmmReg(
_Inout_ PCONTEXT Context,
_In_ BYTE Reg,
_In_ M128A Value)
{
((M128A*)(&Context->Xmm0))[Reg] = Value;
}
static
__inline
void
SetXmmRegFromStackValue(
_Out_ PCONTEXT Context,
_Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers,
_In_ BYTE Reg,
_In_ M128A *ValuePointer)
{
SetXmmReg(Context, Reg, *ValuePointer);
if (ContextPointers != NULL)
{
ContextPointers->FloatingContext[Reg] = ValuePointer;
}
}
static
__inline
M128A
GetXmmReg(PCONTEXT Context, BYTE Reg)
{
return ((M128A*)(&Context->Xmm0))[Reg];
} }
/*! RtlpTryToUnwindEpilog /*! RtlpTryToUnwindEpilog
@ -221,18 +320,19 @@ PopReg(PCONTEXT Context, BYTE Reg)
* \return TRUE if we have been in an epilog and it could be unwound. * \return TRUE if we have been in an epilog and it could be unwound.
* FALSE if the instructions were not allowed for an epilog. * FALSE if the instructions were not allowed for an epilog.
* \ref * \ref
* http://msdn.microsoft.com/en-us/library/8ydc79k6(VS.80).aspx * https://docs.microsoft.com/en-us/cpp/build/unwind-procedure
* http://msdn.microsoft.com/en-us/library/tawsa7cb.aspx * https://docs.microsoft.com/en-us/cpp/build/prolog-and-epilog
* \todo * \todo
* - Test and compare with Windows behaviour * - Test and compare with Windows behaviour
*/ */
BOOLEAN
static static
__inline __inline
BOOLEAN
RtlpTryToUnwindEpilog( RtlpTryToUnwindEpilog(
PCONTEXT Context, _Inout_ PCONTEXT Context,
ULONG64 ImageBase, _Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers,
PRUNTIME_FUNCTION FunctionEntry) _In_ ULONG64 ImageBase,
_In_ PRUNTIME_FUNCTION FunctionEntry)
{ {
CONTEXT LocalContext; CONTEXT LocalContext;
BYTE *InstrPtr; BYTE *InstrPtr;
@ -302,7 +402,7 @@ RtlpTryToUnwindEpilog(
{ {
/* Opcode pops a basic register from stack */ /* Opcode pops a basic register from stack */
Reg = Instr & 0x7; Reg = Instr & 0x7;
PopReg(&LocalContext, Reg); PopReg(&LocalContext, ContextPointers, Reg);
InstrPtr++; InstrPtr++;
continue; continue;
} }
@ -312,7 +412,7 @@ RtlpTryToUnwindEpilog(
{ {
/* Opcode is pop r8 .. r15 */ /* Opcode is pop r8 .. r15 */
Reg = ((Instr >> 8) & 0x7) + 8; Reg = ((Instr >> 8) & 0x7) + 8;
PopReg(&LocalContext, Reg); PopReg(&LocalContext, ContextPointers, Reg);
InstrPtr += 2; InstrPtr += 2;
continue; continue;
} }
@ -321,6 +421,10 @@ RtlpTryToUnwindEpilog(
return FALSE; return FALSE;
} }
// check for popfq
// also allow end with jmp imm, jmp [target], iretq
/* Check if we are at the ret instruction */ /* Check if we are at the ret instruction */
if ((DWORD64)InstrPtr != EndAddress) if ((DWORD64)InstrPtr != EndAddress)
{ {
@ -344,6 +448,49 @@ RtlpTryToUnwindEpilog(
return TRUE; return TRUE;
} }
/*!
\ref https://docs.microsoft.com/en-us/cpp/build/unwind-data-definitions-in-c
*/
static
ULONG64
GetEstablisherFrame(
_In_ PCONTEXT Context,
_In_ PUNWIND_INFO UnwindInfo,
_In_ ULONG_PTR CodeOffset)
{
ULONG i;
/* Check if we have a frame register */
if (UnwindInfo->FrameRegister == 0)
{
/* No frame register means we use Rsp */
return Context->Rsp;
}
if ((CodeOffset >= UnwindInfo->SizeOfProlog) ||
((UnwindInfo->Flags & UNW_FLAG_CHAININFO) != 0))
{
return GetReg(Context, UnwindInfo->FrameRegister) -
UnwindInfo->FrameOffset * 16;
}
/* Loop all unwind ops */
for (i = 0;
i < UnwindInfo->CountOfCodes;
i += UnwindOpSlots(UnwindInfo->UnwindCode[i]))
{
/* Check for SET_FPREG */
if (UnwindInfo->UnwindCode[i].UnwindOp == UWOP_SET_FPREG)
{
return GetReg(Context, UnwindInfo->FrameRegister) -
UnwindInfo->FrameOffset * 16;
}
}
return Context->Rsp;
}
PEXCEPTION_ROUTINE PEXCEPTION_ROUTINE
NTAPI NTAPI
RtlVirtualUnwind( RtlVirtualUnwind(
@ -354,13 +501,14 @@ RtlVirtualUnwind(
_Inout_ PCONTEXT Context, _Inout_ PCONTEXT Context,
_Outptr_ PVOID *HandlerData, _Outptr_ PVOID *HandlerData,
_Out_ PULONG64 EstablisherFrame, _Out_ PULONG64 EstablisherFrame,
_Inout_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers) _Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers)
{ {
PUNWIND_INFO UnwindInfo; PUNWIND_INFO UnwindInfo;
ULONG_PTR CodeOffset; ULONG_PTR CodeOffset;
ULONG i; ULONG i, Offset;
UNWIND_CODE UnwindCode; UNWIND_CODE UnwindCode;
BYTE Reg; BYTE Reg;
PULONG LanguageHandler;
/* Use relative virtual address */ /* Use relative virtual address */
ControlPc -= ImageBase; ControlPc -= ImageBase;
@ -375,13 +523,29 @@ RtlVirtualUnwind(
/* Get a pointer to the unwind info */ /* Get a pointer to the unwind info */
UnwindInfo = RVA(ImageBase, FunctionEntry->UnwindData); UnwindInfo = RVA(ImageBase, FunctionEntry->UnwindData);
/* Check for chained info */
if (UnwindInfo->Flags & UNW_FLAG_CHAININFO)
{
UNIMPLEMENTED_DBGBREAK();
/* See https://docs.microsoft.com/en-us/cpp/build/chained-unwind-info-structures */
FunctionEntry = (PRUNTIME_FUNCTION)&(UnwindInfo->UnwindCode[(UnwindInfo->CountOfCodes + 1) & ~1]);
UnwindInfo = RVA(ImageBase, FunctionEntry->UnwindData);
}
/* The language specific handler data follows the unwind info */
LanguageHandler = ALIGN_UP_POINTER_BY(&UnwindInfo->UnwindCode[UnwindInfo->CountOfCodes], sizeof(ULONG));
*HandlerData = (LanguageHandler + 1);
/* Calculate relative offset to function start */ /* Calculate relative offset to function start */
CodeOffset = ControlPc - FunctionEntry->BeginAddress; CodeOffset = ControlPc - FunctionEntry->BeginAddress;
*EstablisherFrame = GetEstablisherFrame(Context, UnwindInfo, CodeOffset);
/* Check if we are in the function epilog and try to finish it */ /* Check if we are in the function epilog and try to finish it */
if (CodeOffset > UnwindInfo->SizeOfProlog) if (CodeOffset > UnwindInfo->SizeOfProlog)
{ {
if (RtlpTryToUnwindEpilog(Context, ImageBase, FunctionEntry)) if (RtlpTryToUnwindEpilog(Context, ContextPointers, ImageBase, FunctionEntry))
{ {
/* There's no exception routine */ /* There's no exception routine */
return NULL; return NULL;
@ -390,31 +554,10 @@ RtlVirtualUnwind(
/* Skip all Ops with an offset greater than the current Offset */ /* Skip all Ops with an offset greater than the current Offset */
i = 0; i = 0;
while (i < UnwindInfo->CountOfCodes && while ((i < UnwindInfo->CountOfCodes) &&
CodeOffset < UnwindInfo->UnwindCode[i].CodeOffset) (UnwindInfo->UnwindCode[i].CodeOffset > CodeOffset))
{ {
UnwindCode = UnwindInfo->UnwindCode[i]; i += UnwindOpSlots(UnwindInfo->UnwindCode[i]);
switch (UnwindCode.UnwindOp)
{
case UWOP_SAVE_NONVOL:
case UWOP_SAVE_XMM:
case UWOP_SAVE_XMM128:
i += 2;
break;
case UWOP_SAVE_NONVOL_FAR:
case UWOP_SAVE_XMM_FAR:
case UWOP_SAVE_XMM128_FAR:
i += 3;
break;
case UWOP_ALLOC_LARGE:
i += UnwindCode.OpInfo ? 3 : 2;
break;
default:
i++;
}
} }
/* Process the remaining unwind ops */ /* Process the remaining unwind ops */
@ -425,21 +568,20 @@ RtlVirtualUnwind(
{ {
case UWOP_PUSH_NONVOL: case UWOP_PUSH_NONVOL:
Reg = UnwindCode.OpInfo; Reg = UnwindCode.OpInfo;
SetReg(Context, Reg, *(DWORD64*)Context->Rsp); PopReg(Context, ContextPointers, Reg);
Context->Rsp += sizeof(DWORD64);
i++; i++;
break; break;
case UWOP_ALLOC_LARGE: case UWOP_ALLOC_LARGE:
if (UnwindCode.OpInfo) if (UnwindCode.OpInfo)
{ {
ULONG Offset = *(ULONG*)(&UnwindInfo->UnwindCode[i+1]); Offset = *(ULONG*)(&UnwindInfo->UnwindCode[i+1]);
Context->Rsp += Offset; Context->Rsp += Offset;
i += 3; i += 3;
} }
else else
{ {
USHORT Offset = UnwindInfo->UnwindCode[i+1].FrameOffset; Offset = UnwindInfo->UnwindCode[i+1].FrameOffset;
Context->Rsp += Offset * 8; Context->Rsp += Offset * 8;
i += 2; i += 2;
} }
@ -451,44 +593,79 @@ RtlVirtualUnwind(
break; break;
case UWOP_SET_FPREG: case UWOP_SET_FPREG:
Reg = UnwindInfo->FrameRegister;
Context->Rsp = GetReg(Context, Reg) - UnwindInfo->FrameOffset * 16;
i++; i++;
break; break;
case UWOP_SAVE_NONVOL: case UWOP_SAVE_NONVOL:
Reg = UnwindCode.OpInfo;
Offset = *(USHORT*)(&UnwindInfo->UnwindCode[i + 1]);
SetRegFromStackValue(Context, ContextPointers, Reg, (DWORD64*)Context->Rsp + Offset);
i += 2; i += 2;
break; break;
case UWOP_SAVE_NONVOL_FAR: case UWOP_SAVE_NONVOL_FAR:
Reg = UnwindCode.OpInfo;
Offset = *(ULONG*)(&UnwindInfo->UnwindCode[i + 1]);
SetRegFromStackValue(Context, ContextPointers, Reg, (DWORD64*)Context->Rsp + Offset);
i += 3; i += 3;
break; break;
case UWOP_SAVE_XMM: case UWOP_EPILOG:
i += 1;
break;
case UWOP_SPARE_CODE:
ASSERT(FALSE);
i += 2; i += 2;
break; break;
case UWOP_SAVE_XMM_FAR:
i += 3;
break;
case UWOP_SAVE_XMM128: case UWOP_SAVE_XMM128:
Reg = UnwindCode.OpInfo;
Offset = *(USHORT*)(&UnwindInfo->UnwindCode[i + 1]);
SetXmmRegFromStackValue(Context, ContextPointers, Reg, (M128A*)(Context->Rsp + Offset));
i += 2; i += 2;
break; break;
case UWOP_SAVE_XMM128_FAR: case UWOP_SAVE_XMM128_FAR:
Reg = UnwindCode.OpInfo;
Offset = *(ULONG*)(&UnwindInfo->UnwindCode[i + 1]);
SetXmmRegFromStackValue(Context, ContextPointers, Reg, (M128A*)(Context->Rsp + Offset));
i += 3; i += 3;
break; break;
case UWOP_PUSH_MACHFRAME: case UWOP_PUSH_MACHFRAME:
i += 1; /* OpInfo is 1, when an error code was pushed, otherwise 0. */
break; Context->Rsp += UnwindCode.OpInfo * sizeof(DWORD64);
/* Now pop the MACHINE_FRAME (Yes, "magic numbers", deal with it) */
Context->Rip = *(PDWORD64)(Context->Rsp + 0x00);
Context->SegCs = *(PDWORD64)(Context->Rsp + 0x08);
Context->EFlags = *(PDWORD64)(Context->Rsp + 0x10);
Context->SegSs = *(PDWORD64)(Context->Rsp + 0x20);
Context->Rsp = *(PDWORD64)(Context->Rsp + 0x18);
ASSERT((i + 1) == UnwindInfo->CountOfCodes);
goto Exit;
} }
} }
/* Unwind is finished, pop new Rip from Stack */ /* Unwind is finished, pop new Rip from Stack */
Context->Rip = *(DWORD64*)Context->Rsp; if (Context->Rsp != 0)
Context->Rsp += sizeof(DWORD64); {
Context->Rip = *(DWORD64*)Context->Rsp;
Context->Rsp += sizeof(DWORD64);
}
return 0; Exit:
/* Check if we have a handler and return it */
if (UnwindInfo->Flags & (UNW_FLAG_EHANDLER | UNW_FLAG_UHANDLER))
{
return RVA(ImageBase, *LanguageHandler);
}
return NULL;
} }
VOID VOID