diff options
author | Bruce Forstall <brucefo@microsoft.com> | 2016-04-05 13:09:07 -0700 |
---|---|---|
committer | Bruce Forstall <brucefo@microsoft.com> | 2016-04-05 13:09:07 -0700 |
commit | b54f7b545907b7b56a338a54d14b01dab937cfc1 (patch) | |
tree | d71ce2c6ca72f7fa1477a4a26ceb6bd13834db88 | |
parent | caa979f987c90be2075183a9a819ae110957a2ee (diff) | |
parent | a3f23bd7de3fd60b2a5bf2f56ba3c38a72c50435 (diff) | |
download | coreclr-b54f7b545907b7b56a338a54d14b01dab937cfc1.tar.gz coreclr-b54f7b545907b7b56a338a54d14b01dab937cfc1.tar.bz2 coreclr-b54f7b545907b7b56a338a54d14b01dab937cfc1.zip |
Merge pull request #4019 from BruceForstall/Fix1977
Fix #1977: always create RBP chains on Unix
-rw-r--r-- | src/inc/clrnt.h | 25 | ||||
-rw-r--r-- | src/jit/codegenxarch.cpp | 12 | ||||
-rw-r--r-- | src/jit/emitxarch.cpp | 4 | ||||
-rw-r--r-- | src/jit/unwindamd64.cpp | 61 | ||||
-rw-r--r-- | src/unwinder/amd64/unwinder_amd64.cpp | 64 |
5 files changed, 153 insertions, 13 deletions
diff --git a/src/inc/clrnt.h b/src/inc/clrnt.h index fd7e56b61b..bd25ab39ff 100644 --- a/src/inc/clrnt.h +++ b/src/inc/clrnt.h @@ -816,7 +816,24 @@ typedef enum _UNWIND_OP_CODES { UWOP_SPARE_CODE, UWOP_SAVE_XMM128, UWOP_SAVE_XMM128_FAR, - UWOP_PUSH_MACHFRAME + UWOP_PUSH_MACHFRAME, + +#ifdef PLATFORM_UNIX + // UWOP_SET_FPREG_LARGE is a CLR Unix-only extension to the Windows AMD64 unwind codes. + // It is not part of the standard Windows AMD64 unwind codes specification. + // UWOP_SET_FPREG allows for a maximum of a 240 byte offset between RSP and the + // frame pointer, when the frame pointer is established. UWOP_SET_FPREG_LARGE + // has a 32-bit range scaled by 16. When UWOP_SET_FPREG_LARGE is used, + // UNWIND_INFO.FrameRegister must be set to the frame pointer register, and + // UNWIND_INFO.FrameOffset must be set to 15 (its maximum value). UWOP_SET_FPREG_LARGE + // is followed by two UNWIND_CODEs that are combined to form a 32-bit offset (the same + // as UWOP_SAVE_NONVOL_FAR). This offset is then scaled by 16. The result must be less + // than 2^32 (that is, the top 4 bits of the unscaled 32-bit number must be zero). This + // result is used as the frame pointer register offset from RSP at the time the frame pointer + // is established. Either UWOP_SET_FPREG or UWOP_SET_FPREG_LARGE can be used, but not both. + + UWOP_SET_FPREG_LARGE, +#endif // PLATFORM_UNIX } UNWIND_OP_CODES, *PUNWIND_OP_CODES; static const UCHAR UnwindOpExtraSlotTable[] = { @@ -830,7 +847,11 @@ static const UCHAR UnwindOpExtraSlotTable[] = { 2, // UWOP_SPARE_CODE // previously 64-bit UWOP_SAVE_XMM_FAR 1, // UWOP_SAVE_XMM128 2, // UWOP_SAVE_XMM128_FAR - 0 // UWOP_PUSH_MACHFRAME + 0, // UWOP_PUSH_MACHFRAME + +#ifdef PLATFORM_UNIX + 2, // UWOP_SET_FPREG_LARGE +#endif // PLATFORM_UNIX }; // diff --git a/src/jit/codegenxarch.cpp b/src/jit/codegenxarch.cpp index 7239d705bf..c1d97d3670 100644 --- a/src/jit/codegenxarch.cpp +++ b/src/jit/codegenxarch.cpp @@ -7667,6 +7667,16 @@ int CodeGenInterface::genSPtoFPdelta() { int delta; +#ifdef PLATFORM_UNIX + + // We require frame chaining on Unix to support native tool unwinding (such as + // unwinding by the native debugger). We have a CLR-only extension to the + // unwind codes (UWOP_SET_FPREG_LARGE) to support SP->FP offsets larger than 240. + // If Unix ever supports EnC, the RSP == RBP assumption will have to be reevaluated. + delta = genTotalFrameSize(); + +#else // !PLATFORM_UNIX + // As per Amd64 ABI, RBP offset from initial RSP can be between 0 and 240 if // RBP needs to be reported in unwind codes. This case would arise for methods // with localloc. @@ -7691,6 +7701,8 @@ int CodeGenInterface::genSPtoFPdelta() delta = genTotalFrameSize(); } +#endif // !PLATFORM_UNIX + return delta; } diff --git a/src/jit/emitxarch.cpp b/src/jit/emitxarch.cpp index 7e17ab7a4c..2b0a3141ec 100644 --- a/src/jit/emitxarch.cpp +++ b/src/jit/emitxarch.cpp @@ -1676,12 +1676,14 @@ UNATIVE_OFFSET emitter::emitInsSizeSV(size_t code, int var, int dsp) if (EBPbased) { -#ifdef _TARGET_AMD64_ +#if defined(_TARGET_AMD64_) && !defined(PLATFORM_UNIX) // If localloc is not used, then ebp chaining is done and hence // offset of locals will be at negative offsets, Otherwise offsets // will be positive. In future, when RBP gets positioned in the // middle of the frame so as to optimize instruction encoding size, // the below asserts needs to be modified appropriately. + // However, for Unix platforms, we always do frame pointer chaining, + // so offsets from the frame pointer will always be negative. if (emitComp->compLocallocUsed || emitComp->opts.compDbgEnC) { noway_assert((int)offs >= 0); diff --git a/src/jit/unwindamd64.cpp b/src/jit/unwindamd64.cpp index b5658ab716..6a5b199a07 100644 --- a/src/jit/unwindamd64.cpp +++ b/src/jit/unwindamd64.cpp @@ -380,17 +380,41 @@ void Compiler::unwindSetFrameRegWindows(regNumber reg, unsigned offset) assert(func->unwindHeader.Version == 1); // Can't call this before unwindBegProlog assert(func->unwindHeader.CountOfUnwindCodes == 0); // Can't call this after unwindReserve - assert(func->unwindCodeSlot > sizeof(UNWIND_CODE)); - UNWIND_CODE * code = (UNWIND_CODE*)&func->unwindCodes[func->unwindCodeSlot -= sizeof(UNWIND_CODE)]; unsigned int cbProlog = unwindGetCurrentOffset(func); noway_assert((BYTE)cbProlog == cbProlog); - code->CodeOffset = (BYTE)cbProlog; - code->UnwindOp = UWOP_SET_FPREG; - code->OpInfo = 0; + func->unwindHeader.FrameRegister = (BYTE)reg; - assert(offset <= 240); - assert(offset % 16 == 0); - func->unwindHeader.FrameOffset = offset / 16; + +#ifdef PLATFORM_UNIX + if (offset > 240) + { + // On Unix only, we have a CLR-only extension to the AMD64 unwind codes: UWOP_SET_FPREG_LARGE. + // It has a 32-bit offset (scaled). You must set UNWIND_INFO.FrameOffset to 15. The 32-bit + // offset follows in 2 UNWIND_CODE fields. + + assert(func->unwindCodeSlot > (sizeof(UNWIND_CODE) + sizeof(ULONG))); + ULONG* codedSize = (ULONG*)&func->unwindCodes[func->unwindCodeSlot -= sizeof(ULONG)]; + assert(offset % 16 == 0); + *codedSize = offset / 16; + + UNWIND_CODE* code = (UNWIND_CODE*)&func->unwindCodes[func->unwindCodeSlot -= sizeof(UNWIND_CODE)]; + code->CodeOffset = (BYTE)cbProlog; + code->OpInfo = 0; + code->UnwindOp = UWOP_SET_FPREG_LARGE; + func->unwindHeader.FrameOffset = 15; + } + else +#endif // PLATFORM_UNIX + { + assert(func->unwindCodeSlot > sizeof(UNWIND_CODE)); + UNWIND_CODE* code = (UNWIND_CODE*)&func->unwindCodes[func->unwindCodeSlot -= sizeof(UNWIND_CODE)]; + code->CodeOffset = (BYTE)cbProlog; + code->OpInfo = 0; + code->UnwindOp = UWOP_SET_FPREG; + assert(offset <= 240); + assert(offset % 16 == 0); + func->unwindHeader.FrameOffset = offset / 16; + } } #ifdef UNIX_AMD64_ABI @@ -545,6 +569,7 @@ void DumpUnwindInfo(bool isHotCode, UNATIVE_OFFSET startOffset, UNATIVE_OFFSET e for (unsigned i = 0; i < pHeader->CountOfUnwindCodes; i++) { + unsigned offset; const UNWIND_CODE * const pCode = &(pHeader->UnwindCode[i]); switch (pCode->UnwindOp) { @@ -588,6 +613,26 @@ void DumpUnwindInfo(bool isHotCode, UNATIVE_OFFSET startOffset, UNATIVE_OFFSET e pCode->CodeOffset, pCode->UnwindOp, pCode->OpInfo); // This should be zero break; +#ifdef PLATFORM_UNIX + + case UWOP_SET_FPREG_LARGE: + printf(" CodeOffset: 0x%02X UnwindOp: UWOP_SET_FPREG_LARGE (%u) OpInfo: Unused (%u)\n", + pCode->CodeOffset, pCode->UnwindOp, pCode->OpInfo); // This should be zero + i++; + offset = *(ULONG*)&(pHeader->UnwindCode[i]); + i++; + printf(" Scaled Offset: %u * 16 = %u = 0x%08X\n", + offset, + offset * 16, + offset * 16); + if ((offset & 0xF0000000) != 0) + { + printf(" Illegal unscaled offset: too large\n"); + } + break; + +#endif // PLATFORM_UNIX + case UWOP_SAVE_NONVOL: printf(" CodeOffset: 0x%02X UnwindOp: UWOP_SAVE_NONVOL (%u) OpInfo: %s (%u)\n", pCode->CodeOffset, pCode->UnwindOp, getRegName(pCode->OpInfo), pCode->OpInfo); diff --git a/src/unwinder/amd64/unwinder_amd64.cpp b/src/unwinder/amd64/unwinder_amd64.cpp index b3dd1ab1ac..c04db21dfd 100644 --- a/src/unwinder/amd64/unwinder_amd64.cpp +++ b/src/unwinder/amd64/unwinder_amd64.cpp @@ -730,9 +730,15 @@ Return Value: // UnwindOp = UnwindInfo->UnwindCode[Index].UnwindOp; +#ifdef PLATFORM_UNIX + if (UnwindOp > UWOP_SET_FPREG_LARGE) { + return E_UNEXPECTED; + } +#else // !PLATFORM_UNIX if (UnwindOp > UWOP_PUSH_MACHFRAME) { return E_UNEXPECTED; } +#endif // !PLATFORM_UNIX OpInfo = UnwindInfo->UnwindCode[Index].OpInfo; if (PrologOffset >= UnwindInfo->UnwindCode[Index].CodeOffset) { @@ -799,6 +805,27 @@ Return Value: ContextRecord->Rsp = IntegerRegister[UnwindInfo->FrameRegister]; ContextRecord->Rsp -= UnwindInfo->FrameOffset * 16; break; + +#ifdef PLATFORM_UNIX + + // + // Establish the the frame pointer register using a large size displacement. + // UNWIND_INFO.FrameOffset must be 15 (the maximum value, corresponding to a scaled + // offset of 15 * 16 == 240). The next two codes contain a 32-bit offset, which + // is also scaled by 16, since the stack must remain 16-bit aligned. + // + + case UWOP_SET_FPREG_LARGE: + UNWINDER_ASSERT(UnwindInfo->FrameOffset == 15); + Index += 2; + FrameOffset = UnwindInfo->UnwindCode[Index - 1].FrameOffset; + FrameOffset += UnwindInfo->UnwindCode[Index].FrameOffset << 16; + UNWINDER_ASSERT((FrameOffset & 0xF0000000) == 0); + ContextRecord->Rsp = IntegerRegister[UnwindInfo->FrameRegister]; + ContextRecord->Rsp -= FrameOffset * 16; + break; + +#endif // PLATFORM_UNIX // // Save nonvolatile integer register on the stack using a @@ -1055,6 +1082,7 @@ Arguments: ULONG EpilogueSize; PEXCEPTION_ROUTINE FoundHandler; ULONG FrameRegister; + ULONG FrameOffset; ULONG Index; BOOL InEpilogue; PULONG64 IntegerAddress; @@ -1115,23 +1143,55 @@ Arguments: } else if ((PrologOffset >= UnwindInfo->SizeOfProlog) || ((UnwindInfo->Flags & UNW_FLAG_CHAININFO) != 0)) { + FrameOffset = UnwindInfo->FrameOffset; + +#ifdef PLATFORM_UNIX + // If UnwindInfo->FrameOffset == 15 (the maximum value), then there might be a UWOP_SET_FPREG_LARGE. + // However, it is still legal for a UWOP_SET_FPREG to set UnwindInfo->FrameOffset == 15 (since this + // was always part of the specification), so we need to look through the UnwindCode array to determine + // if there is indeed a UWOP_SET_FPREG_LARGE. If we don't find UWOP_SET_FPREG_LARGE, then just use + // (scaled) FrameOffset of 240, as before. (We don't verify there is a UWOP_SET_FPREG code, but we could.) + if (FrameOffset == 15) { + Index = 0; + while (Index < UnwindInfo->CountOfUnwindCodes) { + UnwindOp = UnwindInfo->UnwindCode[Index]; + if (UnwindOp.UnwindOp == UWOP_SET_FPREG_LARGE) { + FrameOffset = UnwindInfo->UnwindCode[Index + 1].FrameOffset; + FrameOffset += UnwindInfo->UnwindCode[Index + 2].FrameOffset << 16; + break; + } + + Index += UnwindOpSlots(UnwindOp); + } + } +#endif // PLATFORM_UNIX + *EstablisherFrame = (&ContextRecord->Rax)[UnwindInfo->FrameRegister]; - *EstablisherFrame -= UnwindInfo->FrameOffset * 16; + *EstablisherFrame -= FrameOffset * 16; } else { + FrameOffset = UnwindInfo->FrameOffset; Index = 0; while (Index < UnwindInfo->CountOfUnwindCodes) { UnwindOp = UnwindInfo->UnwindCode[Index]; if (UnwindOp.UnwindOp == UWOP_SET_FPREG) { break; } +#ifdef PLATFORM_UNIX + else if (UnwindOp.UnwindOp == UWOP_SET_FPREG_LARGE) { + UNWINDER_ASSERT(UnwindInfo->FrameOffset == 15); + FrameOffset = UnwindInfo->UnwindCode[Index + 1].FrameOffset; + FrameOffset += UnwindInfo->UnwindCode[Index + 2].FrameOffset << 16; + break; + } +#endif // PLATFORM_UNIX Index += UnwindOpSlots(UnwindOp); } if (PrologOffset >= UnwindInfo->UnwindCode[Index].CodeOffset) { *EstablisherFrame = (&ContextRecord->Rax)[UnwindInfo->FrameRegister]; - *EstablisherFrame -= UnwindInfo->FrameOffset * 16; + *EstablisherFrame -= FrameOffset * 16; } else { *EstablisherFrame = ContextRecord->Rsp; |