Skip to content

Commit 843d66b

Browse files
author
Mike McLaughlin
authored
Fix unhandled exception line number issues (dotnet#2269)
Fix unhandled exception line number issues There are a few paths to get the place (DebugStackTrace::DebugStackTraceElement::InitPass2) where the offset/ip needs an adjustment: 1) The System.Diagnostics.StackTrace constructors that take an exception object. The stack trace in the exception object is used (from the _stackTrace field). 2) Processing an unhandled exception for display (ExceptionTracker::ProcessOSExceptionNotification). 3) The System.Diagnostics.StackTrace constructors that don't take an exception object that get the stack trace of the current thread. For cases #1 and #2 the StackTraceInfo/StackTraceElement structs are built when the stack trace for an exception is generated and is put in the private _stackTrace Exception object field. The IP in each StackTraceElement is decremented for hardware exceptions and not for software exceptions because the CrawlFrame isInterrupted/hasFaulted fields are not initialized (always false). This is backwards for h/w exceptions leaf node frames but really can't be changed to be compatible with other code in the runtime and SOS. The fIsLastFrameFromForeignStackTrace BOOL in the StackTraceElement/DebugStackTraceElement structs have been replaced with INT "flags" field defined by the StackTraceElementFlags enum. There is a new flag that is set (STEF_IP_ADJUSTED) if the IP has been already adjusted/decremented. This flag is used to adjust the native offset when it is converted to an IL offset for source/line number lookup in DebugStackTraceElement::InitPass2(). When the stack trace for an exception is rendered to a string (via the GetStackFramesInternal FCALL) the internal GetStackFramesData/DebugStackTraceElement structs are initialized. This new "flags" field is passed from the StackTraceElement to the DebugStackTraceElement struct. For case #3 all this happens in the GetStackFramesInternal FCALL called from the managed constructor building the GetStackFramesData/DebugStackTraceElement structs directly. Fixes issues dotnet#27765 and dotnet#25740. Fix IL offset map search.
1 parent b3fa13f commit 843d66b

File tree

9 files changed

+99
-32
lines changed

9 files changed

+99
-32
lines changed

src/coreclr/src/debug/daccess/dacdbiimpl.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3633,7 +3633,7 @@ void DacDbiInterfaceImpl::GetStackFramesFromException(VMPTR_Object vmObject, Dac
36333633
currentFrame.vmDomainFile.SetHostPtr(pDomainFile);
36343634
currentFrame.ip = currentElement.ip;
36353635
currentFrame.methodDef = currentElement.pFunc->GetMemberDef();
3636-
currentFrame.isLastForeignExceptionFrame = currentElement.fIsLastFrameFromForeignStackTrace;
3636+
currentFrame.isLastForeignExceptionFrame = (currentElement.flags & STEF_LAST_FRAME_FROM_FOREIGN_STACK_TRACE) != 0;
36373637
}
36383638
}
36393639
}

src/coreclr/src/debug/daccess/task.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4091,9 +4091,11 @@ ClrDataMethodInstance::GetILOffsetsByAddress(
40914091
for (ULONG32 i = 0; i < numMap; i++)
40924092
{
40934093
if (codeOffset >= map[i].nativeStartOffset &&
4094-
(((LONG)map[i].ilOffset == ICorDebugInfo::EPILOG &&
4095-
!map[i].nativeEndOffset) ||
4096-
codeOffset < map[i].nativeEndOffset))
4094+
// Found the entry if it is the epilog or the last entry and nativeEndOffset == 0. For methods that don't
4095+
// have a epilog (i.e. IL_Throw is the last instruction) the last map entry has a valid IL offset (not EPILOG)
4096+
// and nativeEndOffset == 0.
4097+
((((LONG)map[i].ilOffset == ICorDebugInfo::EPILOG || i == (numMap - 1)) && map[i].nativeEndOffset == 0) ||
4098+
codeOffset < map[i].nativeEndOffset))
40974099
{
40984100
hits++;
40994101

src/coreclr/src/vm/clrex.h

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,23 @@ class AssemblySpec;
2222
class PEFile;
2323
class PEAssembly;
2424

25+
enum StackTraceElementFlags
26+
{
27+
// Set if this element represents the last frame of the foreign exception stack trace
28+
STEF_LAST_FRAME_FROM_FOREIGN_STACK_TRACE = 0x0001,
29+
30+
// Set if the "ip" field has already been adjusted (decremented)
31+
STEF_IP_ADJUSTED = 0x0002,
32+
};
33+
34+
// This struct is used by SOS in the diagnostic repo.
35+
// See: https://github.com/dotnet/diagnostics/blob/9ff35f13af2f03a68a166cfd53f1a4bb32425f2f/src/SOS/Strike/strike.cpp#L2245
2536
struct StackTraceElement
2637
{
2738
UINT_PTR ip;
2839
UINT_PTR sp;
2940
PTR_MethodDesc pFunc;
30-
// TRUE if this element represents the last frame of the foreign
31-
// exception stack trace.
32-
BOOL fIsLastFrameFromForeignStackTrace;
41+
INT flags; // This is StackTraceElementFlags but it needs to be "int" sized for compatibility with SOS.
3342

3443
bool operator==(StackTraceElement const & rhs) const
3544
{
@@ -44,6 +53,8 @@ struct StackTraceElement
4453
}
4554
};
4655

56+
// This struct is used by SOS in the diagnostic repo.
57+
// See: https://github.com/dotnet/diagnostics/blob/9ff35f13af2f03a68a166cfd53f1a4bb32425f2f/src/SOS/Strike/strike.cpp#L2669
4758
class StackTraceInfo
4859
{
4960
private:

src/coreclr/src/vm/debugdebugger.cpp

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ FCIMPL4(void, DebugStackTrace::GetStackFramesInternal,
510510
// Set the BOOL indicating if the frame represents the last frame from a foreign exception stack trace.
511511
U1 *pIsLastFrameFromForeignExceptionStackTraceU1 = (U1 *)((BOOLARRAYREF)pStackFrameHelper->rgiLastFrameFromForeignExceptionStackTrace)
512512
->GetDirectPointerToNonObjectElements();
513-
pIsLastFrameFromForeignExceptionStackTraceU1 [iNumValidFrames] = (U1) data.pElements[i].fIsLastFrameFromForeignStackTrace;
513+
pIsLastFrameFromForeignExceptionStackTraceU1 [iNumValidFrames] = (U1)(data.pElements[i].flags & STEF_LAST_FRAME_FROM_FOREIGN_STACK_TRACE);
514514
}
515515

516516
MethodDesc *pMethod = data.pElements[i].pFunc;
@@ -939,7 +939,7 @@ void DebugStackTrace::GetStackFramesHelper(Frame *pStartFrame,
939939

940940
// Do a 2nd pass outside of any locks.
941941
// This will compute IL offsets.
942-
for(INT32 i = 0; i < pData->cElements; i++)
942+
for (INT32 i = 0; i < pData->cElements; i++)
943943
{
944944
pData->pElements[i].InitPass2();
945945
}
@@ -1026,14 +1026,17 @@ StackWalkAction DebugStackTrace::GetStackFramesCallback(CrawlFrame* pCf, VOID* d
10261026
dwNativeOffset = 0;
10271027
}
10281028

1029+
// Pass on to InitPass2 that the IP has already been adjusted (decremented by 1)
1030+
INT flags = pCf->IsIPadjusted() ? STEF_IP_ADJUSTED : 0;
1031+
10291032
pData->pElements[pData->cElements].InitPass1(
10301033
dwNativeOffset,
10311034
pFunc,
1032-
ip);
1035+
ip,
1036+
flags);
10331037

10341038
// We'll init the IL offsets outside the TSL lock.
10351039

1036-
10371040
++pData->cElements;
10381041

10391042
// Since we may be asynchronously walking another thread's stack,
@@ -1109,7 +1112,7 @@ void DebugStackTrace::GetStackFramesFromException(OBJECTREF * e,
11091112
// If we come across any frame representing foreign exception stack trace,
11101113
// then set the flag indicating so. This will be used to allocate the
11111114
// corresponding array in StackFrameHelper.
1112-
if (cur.fIsLastFrameFromForeignStackTrace)
1115+
if ((cur.flags & STEF_LAST_FRAME_FROM_FOREIGN_STACK_TRACE) != 0)
11131116
{
11141117
pData->fDoWeHaveAnyFramesFromForeignStackTrace = TRUE;
11151118
}
@@ -1134,9 +1137,11 @@ void DebugStackTrace::GetStackFramesFromException(OBJECTREF * e,
11341137
dwNativeOffset = 0;
11351138
}
11361139

1137-
pData->pElements[i].InitPass1(dwNativeOffset, pMD, (PCODE) cur.ip
1138-
, cur.fIsLastFrameFromForeignStackTrace
1139-
);
1140+
pData->pElements[i].InitPass1(
1141+
dwNativeOffset,
1142+
pMD,
1143+
(PCODE)cur.ip,
1144+
cur.flags);
11401145
#ifndef DACCESS_COMPILE
11411146
pData->pElements[i].InitPass2();
11421147
#endif
@@ -1156,8 +1161,8 @@ void DebugStackTrace::GetStackFramesFromException(OBJECTREF * e,
11561161
void DebugStackTrace::DebugStackTraceElement::InitPass1(
11571162
DWORD dwNativeOffset,
11581163
MethodDesc *pFunc,
1159-
PCODE ip
1160-
, BOOL fIsLastFrameFromForeignStackTrace /*= FALSE*/
1164+
PCODE ip,
1165+
INT flags
11611166
)
11621167
{
11631168
LIMITED_METHOD_CONTRACT;
@@ -1169,7 +1174,7 @@ void DebugStackTrace::DebugStackTraceElement::InitPass1(
11691174
this->pFunc = pFunc;
11701175
this->dwOffset = dwNativeOffset;
11711176
this->ip = ip;
1172-
this->fIsLastFrameFromForeignStackTrace = fIsLastFrameFromForeignStackTrace;
1177+
this->flags = flags;
11731178
}
11741179

11751180
#ifndef DACCESS_COMPILE
@@ -1188,14 +1193,35 @@ void DebugStackTrace::DebugStackTraceElement::InitPass2()
11881193

11891194
_ASSERTE(!ThreadStore::HoldingThreadStore());
11901195

1191-
bool bRes = false;
1196+
bool bRes = false;
11921197

11931198
#ifdef DEBUGGING_SUPPORTED
11941199
// Calculate the IL offset using the debugging services
11951200
if ((this->ip != NULL) && g_pDebugInterface)
11961201
{
1202+
// To get the source line number of the actual code that threw an exception, the dwOffset needs to be
1203+
// adjusted in certain cases when calculating the IL offset.
1204+
//
1205+
// The dwOffset of the stack frame points to either:
1206+
//
1207+
// 1) The instruction that caused a hardware exception (div by zero, null ref, etc).
1208+
// 2) The instruction after the call to an internal runtime function (FCALL like IL_Throw, IL_Rethrow,
1209+
// JIT_OverFlow, etc.) that caused a software exception.
1210+
// 3) The instruction after the call to a managed function (non-leaf node).
1211+
//
1212+
// #2 and #3 are the cases that need to adjust dwOffset because they point after the call instruction
1213+
// and may point to the next (incorrect) IL instruction/source line. If STEF_IP_ADJUSTED is set,
1214+
// IP/dwOffset has already be decremented so don't decrement it again.
1215+
//
1216+
// When the dwOffset needs to be adjusted it is a lot simpler to decrement instead of trying to figure out
1217+
// the beginning of the instruction. It is enough for GetILOffsetFromNative to return the IL offset of the
1218+
// instruction throwing the exception.
1219+
bool fAdjustOffset = (this->flags & STEF_IP_ADJUSTED) == 0 && this->dwOffset >= STACKWALK_CONTROLPC_ADJUST_OFFSET;
11971220
bRes = g_pDebugInterface->GetILOffsetFromNative(
1198-
pFunc, (LPCBYTE) this->ip, this->dwOffset, &this->dwILOffset);
1221+
pFunc,
1222+
(LPCBYTE)this->ip,
1223+
fAdjustOffset ? this->dwOffset - STACKWALK_CONTROLPC_ADJUST_OFFSET : this->dwOffset,
1224+
&this->dwILOffset);
11991225
}
12001226

12011227
#endif // !DEBUGGING_SUPPORTED

src/coreclr/src/vm/debugdebugger.h

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -96,22 +96,20 @@ class DebugStackTrace
9696
private:
9797
#endif // DACCESS_COMPILE
9898
struct DebugStackTraceElement {
99-
DWORD dwOffset; // native offset
99+
DWORD dwOffset; // native offset
100100
DWORD dwILOffset;
101101
MethodDesc *pFunc;
102102
PCODE ip;
103-
// TRUE if this element represents the last frame of the foreign
104-
// exception stack trace.
105-
BOOL fIsLastFrameFromForeignStackTrace;
103+
INT flags; // StackStackElementFlags
106104

107105
// Initialization done under TSL.
108106
// This is used when first collecting the stack frame data.
109107
void InitPass1(
110108
DWORD dwNativeOffset,
111109
MethodDesc *pFunc,
112-
PCODE ip
113-
, BOOL fIsLastFrameFromForeignStackTrace = FALSE
114-
);
110+
PCODE ip,
111+
INT flags // StackStackElementFlags
112+
);
115113

116114
// Initialization done outside the TSL.
117115
// This will init the dwILOffset field (and potentially anything else
@@ -131,7 +129,7 @@ class DebugStackTrace
131129
DebugStackTraceElement* pElements;
132130
THREADBASEREF TargetThread;
133131
AppDomain *pDomain;
134-
BOOL fDoWeHaveAnyFramesFromForeignStackTrace;
132+
BOOL fDoWeHaveAnyFramesFromForeignStackTrace;
135133

136134

137135
GetStackFramesData() : skip(0),

src/coreclr/src/vm/excep.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2367,7 +2367,7 @@ void StackTraceInfo::SaveStackTrace(BOOL bAllowAllocMem, OBJECTHANDLE hThrowable
23672367
// "numCurrentFrames" can be zero if the user created an EDI using
23682368
// an unthrown exception.
23692369
StackTraceElement & refLastElementFromForeignStackTrace = gc.stackTrace[numCurrentFrames - 1];
2370-
refLastElementFromForeignStackTrace.fIsLastFrameFromForeignStackTrace = TRUE;
2370+
refLastElementFromForeignStackTrace.flags |= STEF_LAST_FRAME_FROM_FOREIGN_STACK_TRACE;
23712371
}
23722372
}
23732373

@@ -3555,14 +3555,15 @@ BOOL StackTraceInfo::AppendElement(BOOL bAllowAllocMem, UINT_PTR currentIP, UINT
35553555
// When we are building stack trace as we encounter managed frames during exception dispatch,
35563556
// then none of those frames represent a stack trace from a foreign exception (as they represent
35573557
// the current exception). Hence, set the corresponding flag to FALSE.
3558-
pStackTraceElem->fIsLastFrameFromForeignStackTrace = FALSE;
3558+
pStackTraceElem->flags = 0;
35593559

35603560
// This is a workaround to fix the generation of stack traces from exception objects so that
35613561
// they point to the line that actually generated the exception instead of the line
35623562
// following.
35633563
if (!(pCf->HasFaulted() || pCf->IsIPadjusted()) && pStackTraceElem->ip != 0)
35643564
{
35653565
pStackTraceElem->ip -= 1;
3566+
pStackTraceElem->flags |= STEF_IP_ADJUSTED;
35663567
}
35673568

35683569
++m_dFrameCount;

src/coreclr/src/vm/exceptionhandling.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1333,7 +1333,12 @@ void ExceptionTracker::InitializeCrawlFrameForExplicitFrame(CrawlFrame* pcfThisF
13331333

13341334
INDEBUG(memset(pcfThisFrame, 0xCC, sizeof(*pcfThisFrame)));
13351335

1336+
// Clear various flags
13361337
pcfThisFrame->isFrameless = false;
1338+
pcfThisFrame->isInterrupted = false;
1339+
pcfThisFrame->hasFaulted = false;
1340+
pcfThisFrame->isIPadjusted = false;
1341+
13371342
pcfThisFrame->pFrame = pFrame;
13381343
pcfThisFrame->pFunc = pFrame->GetFunction();
13391344

@@ -1417,6 +1422,12 @@ void ExceptionTracker::InitializeCrawlFrame(CrawlFrame* pcfThisFrame, Thread* pT
14171422
INDEBUG(memset(pcfThisFrame, 0xCC, sizeof(*pcfThisFrame)));
14181423
pcfThisFrame->pRD = pRD;
14191424

1425+
// Clear various flags
1426+
pcfThisFrame->pFunc = NULL;
1427+
pcfThisFrame->isInterrupted = false;
1428+
pcfThisFrame->hasFaulted = false;
1429+
pcfThisFrame->isIPadjusted = false;
1430+
14201431
#ifdef FEATURE_INTERPRETER
14211432
pcfThisFrame->pFrame = NULL;
14221433
#endif // FEATURE_INTERPRETER
@@ -1571,7 +1582,6 @@ void ExceptionTracker::InitializeCrawlFrame(CrawlFrame* pcfThisFrame, Thread* pT
15711582
}
15721583

15731584
pcfThisFrame->pThread = pThread;
1574-
pcfThisFrame->hasFaulted = false;
15751585

15761586
Frame* pTopFrame = pThread->GetFrame();
15771587
pcfThisFrame->isIPadjusted = (FRAME_TOP != pTopFrame) && (pTopFrame->GetVTablePtr() != FaultingExceptionFrame::GetMethodFrameVPtr());

src/coreclr/src/vm/stackwalk.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1499,7 +1499,7 @@ void StackFrameIterator::ResetCrawlFrame()
14991499
m_crawl.isFirst = true;
15001500
m_crawl.isInterrupted = false;
15011501
m_crawl.hasFaulted = false;
1502-
m_crawl.isIPadjusted = false; // can be removed
1502+
m_crawl.isIPadjusted = false;
15031503

15041504
m_crawl.isNativeMarker = false;
15051505
m_crawl.isProfilerDoStackSnapshot = !!(this->m_flags & PROFILER_DO_STACK_SNAPSHOT);

0 commit comments

Comments
 (0)