Fix #1977: always create RBP chains on Unix
authorBruce Forstall <brucefo@microsoft.com>
Thu, 31 Mar 2016 04:43:57 +0000 (21:43 -0700)
committerBruce Forstall <brucefo@microsoft.com>
Tue, 5 Apr 2016 18:35:49 +0000 (11:35 -0700)
The JIT will now always create RBP chains on Unix platforms. This includes in functions
the use localloc.

To do this, the VM is extended with a new Unix-only AMD64 unwind code: UWOP_SET_FPREG_LARGE.
The existing unwind code which is used to establish a frame pointer, UWOP_SET_FPREG, requires
that the frame pointer, when established, be no more than 240 bytes offset from the stack pointer.
This doesn't work well for frames that use localloc. (Large frames without localloc are ok,
because we don't report the frame pointer in the unwind info except for in functions with
localloc or EnC.)

The new unwind code has a 32-bit range. If used, UNWIND_INFO.FrameRegister
must be set to the frame pointer register, and UNWIND_INFO.FrameOffset must be set to 15
(its maximum value). This code is followed by two UNWIND_CODEs that are combined to form
its 32-bit offset. The high 4 bits must be zero. This offset is then scaled by 16. This
result is used as the FP register offset from SP at the time the frame pointer is established.

src/inc/clrnt.h
src/jit/codegenxarch.cpp
src/jit/emitxarch.cpp
src/jit/unwindamd64.cpp
src/unwinder/amd64/unwinder_amd64.cpp

index fd7e56b..bd25ab3 100644 (file)
@@ -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
 };
 
 //
index 7239d70..c1d97d3 100644 (file)
@@ -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;
 }
 
index 7e17ab7..2b0a314 100644 (file)
@@ -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);
index b5658ab..6a5b199 100644 (file)
@@ -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);
index b3dd1ab..c04db21 100644 (file)
@@ -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;