1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3// See the LICENSE file in the project root for more information.
4
5/*============================================================
6**
7** File: DebugDebugger.cpp
8**
9** Purpose: Native methods on System.Debug.Debugger
10**
11**
12
13===========================================================*/
14
15#include "common.h"
16
17#include <object.h>
18#include "ceeload.h"
19
20#include "excep.h"
21#include "frames.h"
22#include "vars.hpp"
23#include "field.h"
24#include "gcheaputilities.h"
25#include "jitinterface.h"
26#include "debugdebugger.h"
27#include "dbginterface.h"
28#include "cordebug.h"
29#include "corsym.h"
30#include "generics.h"
31#include "eemessagebox.h"
32#include "stackwalk.h"
33
34#ifndef DACCESS_COMPILE
35//----------------------------------------------------------------------------
36//
37// FindMostRecentUserCodeOnStack - find out the most recent user managed code on stack
38//
39//
40// Arguments:
41// pContext - [optional] pointer to the context to be restored the user code's context if found
42//
43// Return Value:
44// The most recent user managed code or NULL if not found.
45//
46// Note:
47// It is a heuristic approach to get the address of the user managed code that calls into
48// BCL like System.Diagnostics.Debugger.Break assuming that we can find the original user
49// code caller with stack walking.
50//
51// DoWatsonForUserBreak has the address returned from the helper frame that points to an
52// internal BCL helpful function doing permission check. From bucketing perspetive it is
53// more preferable to report the user managed code that invokes Debugger.Break instead.
54//
55// User managed code is managed code in non-system assembly. Currently, only mscorlib.dll
56// is marked as system assembly.
57//
58//----------------------------------------------------------------------------
59UINT_PTR FindMostRecentUserCodeOnStack(void)
60{
61 CONTRACTL
62 {
63 NOTHROW;
64 GC_NOTRIGGER;
65 CAN_TAKE_LOCK;
66 }
67 CONTRACTL_END;
68
69 Thread * pThread = GetThread();
70 _ASSERTE(pThread != NULL);
71
72 UINT_PTR address = NULL;
73
74 CONTEXT ctx;
75 REGDISPLAY rd;
76 SetUpRegdisplayForStackWalk(pThread, &ctx, &rd);
77
78 StackFrameIterator frameIter;
79 frameIter.Init(pThread, pThread->GetFrame(), &rd, FUNCTIONSONLY | LIGHTUNWIND);
80
81 while (frameIter.IsValid())
82 {
83 MethodDesc * pMD = frameIter.m_crawl.GetFunction();
84
85 // Is it not a system assembly? User manged user will not be in system assembly.
86 if ((pMD != NULL) && (!pMD->GetAssembly()->IsSystem()))
87 {
88 CrawlFrame * pCF = &(frameIter.m_crawl);
89 address = (UINT_PTR)GetControlPC(pCF->GetRegisterSet());
90 break;
91 }
92
93 if (frameIter.Next() != SWA_CONTINUE)
94 {
95 break;
96 }
97 }
98
99 return address;
100}
101
102
103// This does a user break, triggered by System.Diagnostics.Debugger.Break, or the IL opcode for break.
104//
105// Notes:
106// If a managed debugger is attached, this should send the managed UserBreak event.
107// Else if a native debugger is attached, this should send a native break event (kernel32!DebugBreak)
108// Else, this should invoke Watson.
109//
110// Historical trivia:
111// - In whidbey, this would still invoke Watson if a native-only debugger is attached.
112// - In arrowhead, the managed debugging pipeline switched to be built on the native pipeline.
113FCIMPL0(void, DebugDebugger::Break)
114{
115 FCALL_CONTRACT;
116
117#ifdef DEBUGGING_SUPPORTED
118 HELPER_METHOD_FRAME_BEGIN_0();
119
120#ifdef _DEBUG
121 {
122 static int fBreakOnDebugBreak = -1;
123 if (fBreakOnDebugBreak == -1)
124 fBreakOnDebugBreak = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_BreakOnDebugBreak);
125 _ASSERTE(fBreakOnDebugBreak == 0 && "BreakOnDebugBreak");
126 }
127
128 static BOOL fDbgInjectFEE = -1;
129 if (fDbgInjectFEE == -1)
130 fDbgInjectFEE = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgInjectFEE);
131#endif
132
133 // WatsonLastChance has its own complex (and changing) policy of how to behave if a debugger is attached.
134 // So caller should explicitly enforce any debugger-related policy before handing off to watson.
135 // Check managed-only first, since managed debugging may be built on native-debugging.
136 if (CORDebuggerAttached() INDEBUG(|| fDbgInjectFEE))
137 {
138 // A managed debugger is already attached -- let it handle the event.
139 g_pDebugInterface->SendUserBreakpoint(GetThread());
140 }
141 else if (IsDebuggerPresent())
142 {
143 // No managed debugger, but a native debug is attached. Explicitly fire a native user breakpoint.
144 // Don't rely on Watson support since that may have a different policy.
145
146 // Toggle to preemptive before firing the debug event. This allows the debugger to suspend this
147 // thread at the debug event.
148 GCX_PREEMP();
149
150 // This becomes an unmanaged breakpoint, such as int 3.
151 DebugBreak();
152 }
153 else
154 {
155 }
156
157 HELPER_METHOD_FRAME_END();
158#endif // DEBUGGING_SUPPORTED
159}
160FCIMPLEND
161
162FCIMPL0(FC_BOOL_RET, DebugDebugger::Launch)
163{
164 FCALL_CONTRACT;
165
166#ifdef DEBUGGING_SUPPORTED
167 if (CORDebuggerAttached())
168 {
169 FC_RETURN_BOOL(TRUE);
170 }
171 else if (g_pDebugInterface != NULL)
172 {
173 HRESULT hr = S_OK;
174
175 HELPER_METHOD_FRAME_BEGIN_RET_0();
176
177 hr = g_pDebugInterface->LaunchDebuggerForUser(GetThread(), NULL, TRUE, TRUE);
178
179 HELPER_METHOD_FRAME_END();
180
181 if (SUCCEEDED (hr))
182 {
183 FC_RETURN_BOOL(TRUE);
184 }
185 }
186#endif // DEBUGGING_SUPPORTED
187
188 FC_RETURN_BOOL(FALSE);
189}
190FCIMPLEND
191
192
193FCIMPL0(FC_BOOL_RET, DebugDebugger::IsDebuggerAttached)
194{
195 FCALL_CONTRACT;
196
197 FC_GC_POLL_RET();
198
199#ifdef DEBUGGING_SUPPORTED
200 FC_RETURN_BOOL(CORDebuggerAttached());
201#else // DEBUGGING_SUPPORTED
202 FC_RETURN_BOOL(FALSE);
203#endif
204}
205FCIMPLEND
206
207
208/*static*/ BOOL DebugDebugger::IsLoggingHelper()
209{
210 CONTRACTL
211 {
212 NOTHROW;
213 GC_NOTRIGGER;
214 SO_TOLERANT;
215 MODE_ANY;
216 }
217 CONTRACTL_END;
218
219#ifdef DEBUGGING_SUPPORTED
220 if (CORDebuggerAttached())
221 {
222 return (g_pDebugInterface->IsLoggingEnabled());
223 }
224#endif // DEBUGGING_SUPPORTED
225 return FALSE;
226}
227
228
229// Log to managed debugger.
230// It will send a managed log event, which will faithfully send the two string parameters here without
231// appending a newline to anything.
232// It will also call OutputDebugString() which will send a native debug event. The message
233// string there will be a composite of the two managed string parameters and may include a newline.
234FCIMPL3(void, DebugDebugger::Log,
235 INT32 Level,
236 StringObject* strModuleUNSAFE,
237 StringObject* strMessageUNSAFE
238 )
239{
240 CONTRACTL
241 {
242 FCALL_CHECK;
243 PRECONDITION(CheckPointer(strModuleUNSAFE, NULL_OK));
244 PRECONDITION(CheckPointer(strMessageUNSAFE, NULL_OK));
245 }
246 CONTRACTL_END;
247
248 STRINGREF strModule = (STRINGREF)ObjectToOBJECTREF(strModuleUNSAFE);
249 STRINGREF strMessage = (STRINGREF)ObjectToOBJECTREF(strMessageUNSAFE);
250
251 HELPER_METHOD_FRAME_BEGIN_2(strModule, strMessage);
252
253 // OutputDebugString will log to native/interop debugger.
254 if (strModule != NULL)
255 {
256 WszOutputDebugString(strModule->GetBuffer());
257 WszOutputDebugString(W(" : "));
258 }
259
260 if (strMessage != NULL)
261 {
262 WszOutputDebugString(strMessage->GetBuffer());
263 }
264
265 // If we're not logging a module prefix, then don't log the newline either.
266 // Thus if somebody is just logging messages, there won't be any extra newlines in there.
267 // If somebody is also logging category / module information, then this call to OutputDebugString is
268 // already prepending that to the message, so we append a newline for readability.
269 if (strModule != NULL)
270 {
271 WszOutputDebugString(W("\n"));
272 }
273
274
275#ifdef DEBUGGING_SUPPORTED
276
277 // Send message for logging only if the
278 // debugger is attached and logging is enabled
279 // for the given category
280 if (CORDebuggerAttached())
281 {
282 if (IsLoggingHelper() )
283 {
284 // Copy log message and category into our own SString to protect against GC
285 // Strings may contain embedded nulls, but we need to handle null-terminated
286 // strings, so use truncate now.
287 StackSString switchName;
288 if( strModule != NULL )
289 {
290 // truncate if necessary
291 COUNT_T iLen = (COUNT_T) wcslen(strModule->GetBuffer());
292 if (iLen > MAX_LOG_SWITCH_NAME_LEN)
293 {
294 iLen = MAX_LOG_SWITCH_NAME_LEN;
295 }
296 switchName.Set(strModule->GetBuffer(), iLen);
297 }
298
299 SString message;
300 if( strMessage != NULL )
301 {
302 message.Set(strMessage->GetBuffer(), (COUNT_T) wcslen(strMessage->GetBuffer()));
303 }
304
305 g_pDebugInterface->SendLogMessage (Level, &switchName, &message);
306 }
307 }
308
309#endif // DEBUGGING_SUPPORTED
310
311 HELPER_METHOD_FRAME_END();
312}
313FCIMPLEND
314
315
316FCIMPL0(FC_BOOL_RET, DebugDebugger::IsLogging)
317{
318 FCALL_CONTRACT;
319
320 FC_GC_POLL_RET();
321
322 FC_RETURN_BOOL(IsLoggingHelper());
323}
324FCIMPLEND
325
326
327FCIMPL4(void, DebugStackTrace::GetStackFramesInternal,
328 StackFrameHelper* pStackFrameHelperUNSAFE,
329 INT32 iSkip,
330 CLR_BOOL fNeedFileInfo,
331 Object* pExceptionUNSAFE
332 )
333{
334 CONTRACTL
335 {
336 FCALL_CHECK;
337 PRECONDITION(CheckPointer(pStackFrameHelperUNSAFE));
338 PRECONDITION(CheckPointer(pExceptionUNSAFE, NULL_OK));
339 }
340 CONTRACTL_END;
341
342 STACKFRAMEHELPERREF pStackFrameHelper = (STACKFRAMEHELPERREF)ObjectToOBJECTREF(pStackFrameHelperUNSAFE);
343 OBJECTREF pException = ObjectToOBJECTREF(pExceptionUNSAFE);
344 PTRARRAYREF dynamicMethodArrayOrig = NULL;
345
346 HELPER_METHOD_FRAME_BEGIN_2(pStackFrameHelper, pException);
347
348 GCPROTECT_BEGIN(dynamicMethodArrayOrig);
349
350 ASSERT(iSkip >= 0);
351
352 GetStackFramesData data;
353
354 data.pDomain = GetAppDomain();
355
356 data.skip = iSkip;
357
358 data.NumFramesRequested = pStackFrameHelper->iFrameCount;
359
360 if (pException == NULL)
361 {
362 // Thread is NULL if it's the current thread.
363 data.TargetThread = pStackFrameHelper->targetThread;
364 GetStackFrames(NULL, (void*)-1, &data);
365 }
366 else
367 {
368 // We also fetch the dynamic method array in a GC protected artifact to ensure
369 // that the resolver objects, if any, are kept alive incase the exception object
370 // is thrown again (resetting the dynamic method array reference in the object)
371 // that may result in resolver objects getting collected before they can be reachable again
372 // (from the code below).
373 GetStackFramesFromException(&pException, &data, &dynamicMethodArrayOrig);
374 }
375
376 if (data.cElements != 0)
377 {
378#if defined(FEATURE_ISYM_READER) && defined(FEATURE_COMINTEROP)
379 if (fNeedFileInfo)
380 {
381 // Calls to COM up ahead.
382 EnsureComStarted();
383 }
384#endif // FEATURE_ISYM_READER && FEATURE_COMINTEROP
385
386 // Allocate memory for the MethodInfo objects
387 BASEARRAYREF methodInfoArray = (BASEARRAYREF) AllocatePrimitiveArray(ELEMENT_TYPE_I, data.cElements);
388 SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgMethodHandle), (OBJECTREF)methodInfoArray,
389 pStackFrameHelper->GetAppDomain());
390
391 // Allocate memory for the Offsets
392 OBJECTREF offsets = AllocatePrimitiveArray(ELEMENT_TYPE_I4, data.cElements);
393 SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgiOffset), (OBJECTREF)offsets,
394 pStackFrameHelper->GetAppDomain());
395
396 // Allocate memory for the ILOffsets
397 OBJECTREF ilOffsets = AllocatePrimitiveArray(ELEMENT_TYPE_I4, data.cElements);
398 SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgiILOffset), (OBJECTREF)ilOffsets,
399 pStackFrameHelper->GetAppDomain());
400
401 // Allocate memory for the array of assembly file names
402 PTRARRAYREF assemblyPathArray = (PTRARRAYREF) AllocateObjectArray(data.cElements, g_pStringClass);
403 SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgAssemblyPath), (OBJECTREF)assemblyPathArray,
404 pStackFrameHelper->GetAppDomain());
405
406 // Allocate memory for the LoadedPeAddress
407 BASEARRAYREF loadedPeAddressArray = (BASEARRAYREF) AllocatePrimitiveArray(ELEMENT_TYPE_I, data.cElements);
408 SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgLoadedPeAddress), (OBJECTREF)loadedPeAddressArray,
409 pStackFrameHelper->GetAppDomain());
410
411 // Allocate memory for the LoadedPeSize
412 OBJECTREF loadedPeSizeArray = AllocatePrimitiveArray(ELEMENT_TYPE_I4, data.cElements);
413 SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgiLoadedPeSize), (OBJECTREF)loadedPeSizeArray,
414 pStackFrameHelper->GetAppDomain());
415
416 // Allocate memory for the InMemoryPdbAddress
417 BASEARRAYREF inMemoryPdbAddressArray = (BASEARRAYREF) AllocatePrimitiveArray(ELEMENT_TYPE_I, data.cElements);
418 SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgInMemoryPdbAddress), (OBJECTREF)inMemoryPdbAddressArray,
419 pStackFrameHelper->GetAppDomain());
420
421 // Allocate memory for the InMemoryPdbSize
422 OBJECTREF inMemoryPdbSizeArray = AllocatePrimitiveArray(ELEMENT_TYPE_I4, data.cElements);
423 SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgiInMemoryPdbSize), (OBJECTREF)inMemoryPdbSizeArray,
424 pStackFrameHelper->GetAppDomain());
425
426 // Allocate memory for the MethodTokens
427 OBJECTREF methodTokens = AllocatePrimitiveArray(ELEMENT_TYPE_I4, data.cElements);
428 SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgiMethodToken), (OBJECTREF)methodTokens,
429 pStackFrameHelper->GetAppDomain());
430
431 // Allocate memory for the Filename string objects
432 PTRARRAYREF filenameArray = (PTRARRAYREF) AllocateObjectArray(data.cElements, g_pStringClass);
433 SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgFilename), (OBJECTREF)filenameArray,
434 pStackFrameHelper->GetAppDomain());
435
436 // Allocate memory for the LineNumbers
437 OBJECTREF lineNumbers = AllocatePrimitiveArray(ELEMENT_TYPE_I4, data.cElements);
438 SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgiLineNumber), (OBJECTREF)lineNumbers,
439 pStackFrameHelper->GetAppDomain());
440
441 // Allocate memory for the ColumnNumbers
442 OBJECTREF columnNumbers = AllocatePrimitiveArray(ELEMENT_TYPE_I4, data.cElements);
443 SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgiColumnNumber), (OBJECTREF)columnNumbers,
444 pStackFrameHelper->GetAppDomain());
445
446 // Allocate memory for the flag indicating if this frame represents the last one from a foreign
447 // exception stack trace provided we have any such frames. Otherwise, set it to null.
448 // When StackFrameHelper.IsLastFrameFromForeignExceptionStackTrace is invoked in managed code,
449 // it will return false for the null case.
450 //
451 // This is an optimization for us to not allocate the BOOL array if we do not have any frames
452 // from a foreign stack trace.
453 OBJECTREF IsLastFrameFromForeignStackTraceFlags = NULL;
454 if (data.fDoWeHaveAnyFramesFromForeignStackTrace)
455 {
456 IsLastFrameFromForeignStackTraceFlags = AllocatePrimitiveArray(ELEMENT_TYPE_BOOLEAN, data.cElements);
457
458 SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgiLastFrameFromForeignExceptionStackTrace), (OBJECTREF)IsLastFrameFromForeignStackTraceFlags,
459 pStackFrameHelper->GetAppDomain());
460 }
461 else
462 {
463 SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->rgiLastFrameFromForeignExceptionStackTrace), NULL,
464 pStackFrameHelper->GetAppDomain());
465 }
466
467 // Determine if there are any dynamic methods in the stack trace. If there are,
468 // allocate an ObjectArray large enough to hold an ObjRef to each one.
469 unsigned iNumDynamics = 0;
470 unsigned iCurDynamic = 0;
471 for (int iElement=0; iElement < data.cElements; iElement++)
472 {
473 MethodDesc *pMethod = data.pElements[iElement].pFunc;
474 if (pMethod->IsLCGMethod())
475 {
476 iNumDynamics++;
477 }
478 else
479 if (pMethod->GetMethodTable()->Collectible())
480 {
481 iNumDynamics++;
482 }
483 }
484
485 if (iNumDynamics)
486 {
487 PTRARRAYREF dynamicDataArray = (PTRARRAYREF) AllocateObjectArray(iNumDynamics, g_pObjectClass);
488 SetObjectReference( (OBJECTREF *)&(pStackFrameHelper->dynamicMethods), (OBJECTREF)dynamicDataArray,
489 pStackFrameHelper->GetAppDomain());
490 }
491
492 int iNumValidFrames = 0;
493 for (int i = 0; i < data.cElements; i++)
494 {
495 // The managed stacktrace classes always returns typical method definition, so we don't need to bother providing exact instantiation.
496 // Generics::GetExactInstantiationsOfMethodAndItsClassFromCallInformation(data.pElements[i].pFunc, data.pElements[i].pExactGenericArgsToken, &pExactMethod, &thExactType);
497 MethodDesc* pFunc = data.pElements[i].pFunc;
498
499 // Strip the instantiation to make sure that the reflection never gets a bad method desc back.
500 if (pFunc->HasMethodInstantiation())
501 pFunc = pFunc->StripMethodInstantiation();
502 _ASSERTE(pFunc->IsRuntimeMethodHandle());
503
504 // Method handle
505 size_t *pElem = (size_t*)pStackFrameHelper->rgMethodHandle->GetDataPtr();
506 pElem[iNumValidFrames] = (size_t)pFunc;
507
508 // Native offset
509 I4 *pI4 = (I4 *)((I4ARRAYREF)pStackFrameHelper->rgiOffset)->GetDirectPointerToNonObjectElements();
510 pI4[iNumValidFrames] = data.pElements[i].dwOffset;
511
512 // IL offset
513 I4 *pILI4 = (I4 *)((I4ARRAYREF)pStackFrameHelper->rgiILOffset)->GetDirectPointerToNonObjectElements();
514 pILI4[iNumValidFrames] = data.pElements[i].dwILOffset;
515
516 if (data.fDoWeHaveAnyFramesFromForeignStackTrace)
517 {
518 // Set the BOOL indicating if the frame represents the last frame from a foreign exception stack trace.
519 U1 *pIsLastFrameFromForeignExceptionStackTraceU1 = (U1 *)((BOOLARRAYREF)pStackFrameHelper->rgiLastFrameFromForeignExceptionStackTrace)
520 ->GetDirectPointerToNonObjectElements();
521 pIsLastFrameFromForeignExceptionStackTraceU1 [iNumValidFrames] = (U1) data.pElements[i].fIsLastFrameFromForeignStackTrace;
522 }
523
524 MethodDesc *pMethod = data.pElements[i].pFunc;
525
526 // If there are any dynamic methods, and this one is one of them, store
527 // a reference to it's managed resolver to keep it alive.
528 if (iNumDynamics)
529 {
530 if (pMethod->IsLCGMethod())
531 {
532 DynamicMethodDesc *pDMD = pMethod->AsDynamicMethodDesc();
533 OBJECTREF pResolver = pDMD->GetLCGMethodResolver()->GetManagedResolver();
534 _ASSERTE(pResolver != NULL);
535
536 ((PTRARRAYREF)pStackFrameHelper->dynamicMethods)->SetAt(iCurDynamic++, pResolver);
537 }
538 else if (pMethod->GetMethodTable()->Collectible())
539 {
540 OBJECTREF pLoaderAllocator = pMethod->GetMethodTable()->GetLoaderAllocator()->GetExposedObject();
541 _ASSERTE(pLoaderAllocator != NULL);
542 ((PTRARRAYREF)pStackFrameHelper->dynamicMethods)->SetAt(iCurDynamic++, pLoaderAllocator);
543 }
544 }
545
546 Module *pModule = pMethod->GetModule();
547
548 // If it's an EnC method, then don't give back any line info, b/c the PDB is out of date.
549 // (We're using the stale PDB, not one w/ Edits applied).
550 // Since the MethodDesc is always the most recent, v1 instances of EnC methods on the stack
551 // will appeared to be Enc. This means we err on the side of not showing line numbers for EnC methods.
552 // If any method in the file was changed, then our line numbers could be wrong. Since we don't
553 // have udpated PDBs from EnC, we can at best look at the module's version number as a rough guess
554 // to if this file has been updated.
555 bool fIsEnc = false;
556#ifdef EnC_SUPPORTED
557 if (pModule->IsEditAndContinueEnabled())
558 {
559 EditAndContinueModule *eacm = (EditAndContinueModule *)pModule;
560 if (eacm->GetApplyChangesCount() != 1)
561 {
562 fIsEnc = true;
563 }
564 }
565#endif
566 BOOL fPortablePDB = TRUE;
567
568 // Check if the user wants the filenumber, linenumber info and that it is possible.
569 if (!fIsEnc && fNeedFileInfo)
570 {
571#ifdef FEATURE_ISYM_READER
572 BOOL fFileInfoSet = FALSE;
573 ULONG32 sourceLine = 0;
574 ULONG32 sourceColumn = 0;
575 WCHAR wszFileName[MAX_LONGPATH];
576 ULONG32 fileNameLength = 0;
577 {
578 // Note: we need to enable preemptive GC when accessing the unmanages symbol store.
579 GCX_PREEMP();
580
581 // Note: we use the NoThrow version of GetISymUnmanagedReader. If getting the unmanaged
582 // reader fails, then just leave the pointer NULL and leave any symbol info off of the
583 // stack trace.
584 ReleaseHolder<ISymUnmanagedReader> pISymUnmanagedReader(
585 pModule->GetISymUnmanagedReaderNoThrow());
586
587 if (pISymUnmanagedReader != NULL)
588 {
589 // Found a ISymUnmanagedReader for the regular PDB so don't attempt to
590 // read it as a portable PDB in mscorlib's StackFrameHelper.
591 fPortablePDB = FALSE;
592
593 ReleaseHolder<ISymUnmanagedMethod> pISymUnmanagedMethod;
594 HRESULT hr = pISymUnmanagedReader->GetMethod(pMethod->GetMemberDef(),
595 &pISymUnmanagedMethod);
596
597 if (SUCCEEDED(hr))
598 {
599 // get all the sequence points and the documents
600 // associated with those sequence points.
601 // from the doument get the filename using GetURL()
602 ULONG32 SeqPointCount = 0;
603 ULONG32 RealSeqPointCount = 0;
604
605 hr = pISymUnmanagedMethod->GetSequencePointCount(&SeqPointCount);
606 _ASSERTE (SUCCEEDED(hr) || (hr == E_OUTOFMEMORY) );
607
608 if (SUCCEEDED(hr) && SeqPointCount > 0)
609 {
610 // allocate memory for the objects to be fetched
611 NewArrayHolder<ULONG32> offsets (new (nothrow) ULONG32 [SeqPointCount]);
612 NewArrayHolder<ULONG32> lines (new (nothrow) ULONG32 [SeqPointCount]);
613 NewArrayHolder<ULONG32> columns (new (nothrow) ULONG32 [SeqPointCount]);
614 NewArrayHolder<ULONG32> endlines (new (nothrow) ULONG32 [SeqPointCount]);
615 NewArrayHolder<ULONG32> endcolumns (new (nothrow) ULONG32 [SeqPointCount]);
616
617 // we free the array automatically, but we have to manually call release
618 // on each element in the array when we're done with it.
619 NewArrayHolder<ISymUnmanagedDocument*> documents (
620 (ISymUnmanagedDocument **)new PVOID [SeqPointCount]);
621
622 if ((offsets && lines && columns && documents && endlines && endcolumns))
623 {
624 hr = pISymUnmanagedMethod->GetSequencePoints (
625 SeqPointCount,
626 &RealSeqPointCount,
627 offsets,
628 (ISymUnmanagedDocument **)documents,
629 lines,
630 columns,
631 endlines,
632 endcolumns);
633
634 _ASSERTE(SUCCEEDED(hr) || (hr == E_OUTOFMEMORY) );
635
636 if (SUCCEEDED(hr))
637 {
638 _ASSERTE(RealSeqPointCount == SeqPointCount);
639
640#ifdef _DEBUG
641 {
642 // This is just some debugging code to help ensure that the array
643 // returned contains valid interface pointers.
644 for (ULONG32 i = 0; i < RealSeqPointCount; i++)
645 {
646 _ASSERTE(documents[i] != NULL);
647 documents[i]->AddRef();
648 documents[i]->Release();
649 }
650 }
651#endif
652
653 // This is the IL offset of the current frame
654 DWORD dwCurILOffset = data.pElements[i].dwILOffset;
655
656 // search for the correct IL offset
657 DWORD j;
658 for (j=0; j<RealSeqPointCount; j++)
659 {
660 // look for the entry matching the one we're looking for
661 if (offsets[j] >= dwCurILOffset)
662 {
663 // if this offset is > what we're looking for, adjust the index
664 if (offsets[j] > dwCurILOffset && j > 0)
665 {
666 j--;
667 }
668
669 break;
670 }
671 }
672
673 // If we didn't find a match, default to the last sequence point
674 if (j == RealSeqPointCount)
675 {
676 j--;
677 }
678
679 while (lines[j] == 0x00feefee && j > 0)
680 {
681 j--;
682 }
683
684#ifdef DEBUGGING_SUPPORTED
685 if (lines[j] != 0x00feefee)
686 {
687 sourceLine = lines [j];
688 sourceColumn = columns [j];
689 }
690 else
691#endif // DEBUGGING_SUPPORTED
692 {
693 sourceLine = 0;
694 sourceColumn = 0;
695 }
696
697 // Also get the filename from the document...
698 _ASSERTE (documents [j] != NULL);
699
700 hr = documents [j]->GetURL (MAX_LONGPATH, &fileNameLength, wszFileName);
701 _ASSERTE ( SUCCEEDED(hr) || (hr == E_OUTOFMEMORY) || (hr == HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY)) );
702
703 // indicate that the requisite information has been set!
704 fFileInfoSet = TRUE;
705
706 // release the documents set by GetSequencePoints
707 for (DWORD x=0; x<RealSeqPointCount; x++)
708 {
709 documents [x]->Release();
710 }
711 } // if got sequence points
712
713 } // if all memory allocations succeeded
714
715 // holders will now delete the arrays.
716 }
717 }
718 // Holder will release pISymUnmanagedMethod
719 }
720
721 } // GCX_PREEMP()
722
723 if (fFileInfoSet)
724 {
725 // Set the line and column numbers
726 I4 *pI4Line = (I4 *)((I4ARRAYREF)pStackFrameHelper->rgiLineNumber)->GetDirectPointerToNonObjectElements();
727 pI4Line[iNumValidFrames] = sourceLine;
728
729 I4 *pI4Column = (I4 *)((I4ARRAYREF)pStackFrameHelper->rgiColumnNumber)->GetDirectPointerToNonObjectElements();
730 pI4Column[iNumValidFrames] = sourceColumn;
731
732 // Set the file name
733 OBJECTREF obj = (OBJECTREF) StringObject::NewString(wszFileName);
734 pStackFrameHelper->rgFilename->SetAt(iNumValidFrames, obj);
735 }
736#endif // FEATURE_ISYM_READER
737
738 // If the above isym reader code did NOT set the source info either because it is ifdef'ed out (on xplat)
739 // or because the pdb is the new portable format on Windows then set the information needed to call the
740 // portable pdb reader in the StackTraceHelper.
741 if (fPortablePDB)
742 {
743 // Save MethodToken for the function
744 I4 *pMethodToken = (I4 *)((I4ARRAYREF)pStackFrameHelper->rgiMethodToken)->GetDirectPointerToNonObjectElements();
745 pMethodToken[iNumValidFrames] = pMethod->GetMemberDef();
746
747 PEFile *pPEFile = pModule->GetFile();
748
749 // Get the address and size of the loaded PE image
750 COUNT_T peSize;
751 PTR_CVOID peAddress = pPEFile->GetLoadedImageContents(&peSize);
752
753 // Save the PE address and size
754 PTR_CVOID *pLoadedPeAddress = (PTR_CVOID *)pStackFrameHelper->rgLoadedPeAddress->GetDataPtr();
755 pLoadedPeAddress[iNumValidFrames] = peAddress;
756
757 I4 *pLoadedPeSize = (I4 *)((I4ARRAYREF)pStackFrameHelper->rgiLoadedPeSize)->GetDirectPointerToNonObjectElements();
758 pLoadedPeSize[iNumValidFrames] = (I4)peSize;
759
760 // If there is a in memory symbol stream
761 CGrowableStream* stream = pModule->GetInMemorySymbolStream();
762 if (stream != NULL)
763 {
764 MemoryRange range = stream->GetRawBuffer();
765
766 // Save the in-memory PDB address and size
767 PTR_VOID *pInMemoryPdbAddress = (PTR_VOID *)pStackFrameHelper->rgInMemoryPdbAddress->GetDataPtr();
768 pInMemoryPdbAddress[iNumValidFrames] = range.StartAddress();
769
770 I4 *pInMemoryPdbSize = (I4 *)((I4ARRAYREF)pStackFrameHelper->rgiInMemoryPdbSize)->GetDirectPointerToNonObjectElements();
771 pInMemoryPdbSize[iNumValidFrames] = (I4)range.Size();
772 }
773 else
774 {
775 // Set the pdb path (assembly file name)
776 const SString& assemblyPath = pPEFile->GetPath();
777 if (!assemblyPath.IsEmpty())
778 {
779 OBJECTREF obj = (OBJECTREF)StringObject::NewString(assemblyPath);
780 pStackFrameHelper->rgAssemblyPath->SetAt(iNumValidFrames, obj);
781 }
782 }
783 }
784 }
785
786 iNumValidFrames++;
787 }
788
789 pStackFrameHelper->iFrameCount = iNumValidFrames;
790 }
791 else
792 {
793 pStackFrameHelper->iFrameCount = 0;
794 }
795
796 GCPROTECT_END();
797
798 HELPER_METHOD_FRAME_END();
799}
800FCIMPLEND
801
802FORCEINLINE void HolderDestroyStrongHandle(OBJECTHANDLE h) { if (h != NULL) DestroyStrongHandle(h); }
803typedef Wrapper<OBJECTHANDLE, DoNothing<OBJECTHANDLE>, HolderDestroyStrongHandle, NULL> StrongHandleHolder;
804
805// receives a custom notification object from the target and sends it to the RS via
806// code:Debugger::SendCustomDebuggerNotification
807// Argument: dataUNSAFE - a pointer the the custom notification object being sent
808FCIMPL1(void, DebugDebugger::CustomNotification, Object * dataUNSAFE)
809{
810 CONTRACTL
811 {
812 FCALL_CHECK;
813 }
814 CONTRACTL_END;
815
816 OBJECTREF pData = ObjectToOBJECTREF(dataUNSAFE);
817
818#ifdef DEBUGGING_SUPPORTED
819 // Send notification only if the debugger is attached
820 if (CORDebuggerAttached() )
821 {
822 HELPER_METHOD_FRAME_BEGIN_PROTECT(pData);
823
824 Thread * pThread = GetThread();
825 AppDomain * pAppDomain = pThread->GetDomain();
826
827 StrongHandleHolder objHandle = pAppDomain->CreateStrongHandle(pData);
828 MethodTable * pMT = pData->GetGCSafeMethodTable();
829 Module * pModule = pMT->GetModule();
830 DomainFile * pDomainFile = pModule->GetDomainFile(pAppDomain);
831 mdTypeDef classToken = pMT->GetCl();
832
833 pThread->SetThreadCurrNotification(objHandle);
834 g_pDebugInterface->SendCustomDebuggerNotification(pThread, pDomainFile, classToken);
835 pThread->ClearThreadCurrNotification();
836
837 TESTHOOKCALL(AppDomainCanBeUnloaded(pThread->GetDomain()->GetId().m_dwId, FALSE));
838 if (pThread->IsAbortRequested())
839 {
840 pThread->HandleThreadAbort();
841 }
842
843 HELPER_METHOD_FRAME_END();
844 }
845
846#endif // DEBUGGING_SUPPORTED
847
848}
849FCIMPLEND
850
851
852void DebugStackTrace::GetStackFramesHelper(Frame *pStartFrame,
853 void* pStopStack,
854 GetStackFramesData *pData
855 )
856{
857 CONTRACTL
858 {
859 MODE_COOPERATIVE;
860 GC_TRIGGERS;
861 THROWS;
862 }
863 CONTRACTL_END;
864
865 ASSERT (pData != NULL);
866
867 pData->cElements = 0;
868
869 // if the caller specified (< 20) frames are required, then allocate
870 // only that many
871 if ((pData->NumFramesRequested > 0) && (pData->NumFramesRequested < 20))
872 {
873 pData->cElementsAllocated = pData->NumFramesRequested;
874 }
875 else
876 {
877 pData->cElementsAllocated = 20;
878 }
879
880 // Allocate memory for the initial 'n' frames
881 pData->pElements = new DebugStackTraceElement[pData->cElementsAllocated];
882
883 if (pData->TargetThread == NULL ||
884 pData->TargetThread->GetInternal() == GetThread())
885 {
886 // Null target thread specifies current thread.
887 GetThread()->StackWalkFrames(GetStackFramesCallback, pData, FUNCTIONSONLY, pStartFrame);
888 }
889 else
890 {
891 Thread *pThread = pData->TargetThread->GetInternal();
892 _ASSERTE (pThread != NULL);
893
894 // Here's the timeline for the TS_UserSuspendPending and TS_SyncSuspended bits.
895 // 0) Neither TS_UserSuspendPending nor TS_SyncSuspended set.
896 // 1) The suspending thread grabs the thread store lock
897 // then sets TS_UserSuspendPending
898 // then puts in place trip wires for the suspendee (if it is in managed code)
899 // and releases the thread store lock.
900 // 2) The suspending thread waits for the "SafeEvent".
901 // 3) The suspendee continues execution until it tries to enter preemptive mode.
902 // If it trips over the wires put in place by the suspending thread,
903 // it will try to enter preemptive mode.
904 // 4) The suspendee sets TS_SyncSuspended and the "SafeEvent".
905 // Then it waits for m_UserSuspendEvent.
906 // 5) AT THIS POINT, IT IS SAFE TO WALK THE SUSPENDEE'S STACK.
907 // 6) Now, some thread wants to resume the suspendee.
908 // The resuming thread takes the thread store lock
909 // then clears the TS_UserSuspendPending flag
910 // then sets m_UserSuspendEvent
911 // and releases the thread store lock.
912 // 7) The suspendee clears the TS_SyncSuspended flag.
913 //
914 // In other words, it is safe to trace the thread's stack IF we're holding the
915 // thread store lock AND TS_UserSuspendPending is set AND TS_SyncSuspended is set.
916 //
917 // This is because:
918 // - If we were not holding the thread store lock, the thread could be resumed
919 // underneath us.
920 // - As long as only TS_UserSuspendPending is set (and the thread is in cooperative
921 // mode), the thread can still be executing managed code until it trips.
922 // - When only TS_SyncSuspended is set, we race against it resuming execution.
923
924 ThreadStoreLockHolder tsl;
925
926 // We erect a barrier so that if the thread tries to disable preemptive GC,
927 // it will look at the TS_UserSuspendPending flag. Otherwise, it could resume
928 // execution of managed code during our stack walk.
929 TSSuspendHolder shTrap;
930
931 Thread::ThreadState state = pThread->GetSnapshotState();
932 if (state & (Thread::TS_Unstarted|Thread::TS_Dead|Thread::TS_Detached))
933 {
934 goto LSafeToTrace;
935 }
936
937 // CoreCLR does not support user-requested thread suspension
938 _ASSERTE(!(state & Thread::TS_UserSuspendPending));
939
940 COMPlusThrow(kThreadStateException, IDS_EE_THREAD_BAD_STATE);
941
942 LSafeToTrace:
943 pThread->StackWalkFrames(GetStackFramesCallback,
944 pData,
945 FUNCTIONSONLY|ALLOW_ASYNC_STACK_WALK,
946 pStartFrame);
947 }
948
949 // Do a 2nd pass outside of any locks.
950 // This will compute IL offsets.
951 for(INT32 i = 0; i < pData->cElements; i++)
952 {
953 pData->pElements[i].InitPass2();
954 }
955
956}
957
958
959void DebugStackTrace::GetStackFrames(Frame *pStartFrame,
960 void* pStopStack,
961 GetStackFramesData *pData
962 )
963{
964 CONTRACTL
965 {
966 THROWS;
967 GC_TRIGGERS;
968 MODE_COOPERATIVE;
969 }
970 CONTRACTL_END;
971
972 GetStackFramesHelper(pStartFrame, pStopStack, pData);
973}
974
975
976StackWalkAction DebugStackTrace::GetStackFramesCallback(CrawlFrame* pCf, VOID* data)
977{
978 CONTRACTL
979 {
980 THROWS;
981 GC_TRIGGERS;
982 MODE_COOPERATIVE;
983 }
984 CONTRACTL_END;
985
986 GetStackFramesData* pData = (GetStackFramesData*)data;
987
988 if (pData->pDomain != pCf->GetAppDomain())
989 {
990 return SWA_CONTINUE;
991 }
992
993 if (pData->skip > 0)
994 {
995 pData->skip--;
996 return SWA_CONTINUE;
997 }
998
999 // <REVISIT_TODO>@todo: How do we know what kind of frame we have?</REVISIT_TODO>
1000 // Can we always assume FramedMethodFrame?
1001 // NOT AT ALL!!!, but we can assume it's a function
1002 // because we asked the stackwalker for it!
1003 MethodDesc* pFunc = pCf->GetFunction();
1004
1005 if (pData->cElements >= pData->cElementsAllocated)
1006 {
1007
1008 DebugStackTraceElement* pTemp = new (nothrow) DebugStackTraceElement[2*pData->cElementsAllocated];
1009
1010 if (!pTemp)
1011 {
1012 return SWA_ABORT;
1013 }
1014
1015 memcpy(pTemp, pData->pElements, pData->cElementsAllocated * sizeof(DebugStackTraceElement));
1016
1017 delete [] pData->pElements;
1018
1019 pData->pElements = pTemp;
1020 pData->cElementsAllocated *= 2;
1021 }
1022
1023 PCODE ip;
1024 DWORD dwNativeOffset;
1025
1026 if (pCf->IsFrameless())
1027 {
1028 // Real method with jitted code.
1029 dwNativeOffset = pCf->GetRelOffset();
1030 ip = GetControlPC(pCf->GetRegisterSet());
1031 }
1032 else
1033 {
1034 ip = NULL;
1035 dwNativeOffset = 0;
1036 }
1037
1038 pData->pElements[pData->cElements].InitPass1(
1039 dwNativeOffset,
1040 pFunc,
1041 ip);
1042
1043 // We'll init the IL offsets outside the TSL lock.
1044
1045
1046 ++pData->cElements;
1047
1048 // Since we may be asynchronously walking another thread's stack,
1049 // check (frequently) for stack-buffer-overrun corruptions after
1050 // any long operation
1051 pCf->CheckGSCookies();
1052
1053 // check if we already have the number of frames that the user had asked for
1054 if ((pData->NumFramesRequested != 0) && (pData->NumFramesRequested <= pData->cElements))
1055 {
1056 return SWA_ABORT;
1057 }
1058
1059 return SWA_CONTINUE;
1060}
1061#endif // !DACCESS_COMPILE
1062
1063void DebugStackTrace::GetStackFramesFromException(OBJECTREF * e,
1064 GetStackFramesData *pData,
1065 PTRARRAYREF * pDynamicMethodArray /*= NULL*/
1066 )
1067{
1068 CONTRACTL
1069 {
1070 THROWS;
1071 GC_TRIGGERS;
1072 MODE_COOPERATIVE;
1073 PRECONDITION (IsProtectedByGCFrame (e));
1074 PRECONDITION ((pDynamicMethodArray == NULL) || IsProtectedByGCFrame (pDynamicMethodArray));
1075 SUPPORTS_DAC;
1076 }
1077 CONTRACTL_END;
1078
1079 ASSERT (pData != NULL);
1080
1081 // Reasonable default, will indicate error on failure
1082 pData->cElements = 0;
1083
1084#ifndef DACCESS_COMPILE
1085 // for DAC builds this has already been validated
1086 // Get the class for the exception
1087 MethodTable *pExcepClass = (*e)->GetMethodTable();
1088
1089 _ASSERTE(IsException(pExcepClass)); // what is the pathway for this?
1090 if (!IsException(pExcepClass))
1091 {
1092 return;
1093 }
1094#endif // DACCESS_COMPILE
1095
1096 // Now get the _stackTrace reference
1097 StackTraceArray traceData;
1098 EXCEPTIONREF(*e)->GetStackTrace(traceData, pDynamicMethodArray);
1099
1100 GCPROTECT_BEGIN(traceData);
1101 // The number of frame info elements in the stack trace info
1102 pData->cElements = static_cast<int>(traceData.Size());
1103
1104 // By default, assume that we have no frames from foreign exception stack trace.
1105 pData->fDoWeHaveAnyFramesFromForeignStackTrace = FALSE;
1106
1107 // Now we know the size, allocate the information for the data struct
1108 if (pData->cElements != 0)
1109 {
1110 // Allocate the memory to contain the data
1111 pData->pElements = new DebugStackTraceElement[pData->cElements];
1112
1113 // Fill in the data
1114 for (unsigned i = 0; i < (unsigned)pData->cElements; i++)
1115 {
1116 StackTraceElement const & cur = traceData[i];
1117
1118 // If we come across any frame representing foreign exception stack trace,
1119 // then set the flag indicating so. This will be used to allocate the
1120 // corresponding array in StackFrameHelper.
1121 if (cur.fIsLastFrameFromForeignStackTrace)
1122 {
1123 pData->fDoWeHaveAnyFramesFromForeignStackTrace = TRUE;
1124 }
1125
1126 // Fill out the MethodDesc*
1127 MethodDesc *pMD = cur.pFunc;
1128 _ASSERTE(pMD);
1129
1130 // Calculate the native offset
1131 // This doesn't work for framed methods, since internal calls won't
1132 // push frames and the method body is therefore non-contiguous.
1133 // Currently such methods always return an IP of 0, so they're easy
1134 // to spot.
1135 DWORD dwNativeOffset;
1136
1137 if (cur.ip)
1138 {
1139 dwNativeOffset = (DWORD)(cur.ip - (UINT_PTR)pMD->GetNativeCode());
1140 }
1141 else
1142 {
1143 dwNativeOffset = 0;
1144 }
1145
1146 pData->pElements[i].InitPass1(dwNativeOffset, pMD, (PCODE) cur.ip
1147 , cur.fIsLastFrameFromForeignStackTrace
1148 );
1149#ifndef DACCESS_COMPILE
1150 pData->pElements[i].InitPass2();
1151#endif
1152 }
1153 }
1154 else
1155 {
1156 pData->pElements = NULL;
1157 }
1158 GCPROTECT_END();
1159
1160 return;
1161}
1162
1163// Init a stack-trace element.
1164// Initialization done potentially under the TSL.
1165void DebugStackTrace::DebugStackTraceElement::InitPass1(
1166 DWORD dwNativeOffset,
1167 MethodDesc *pFunc,
1168 PCODE ip
1169 , BOOL fIsLastFrameFromForeignStackTrace /*= FALSE*/
1170)
1171{
1172 LIMITED_METHOD_CONTRACT;
1173 _ASSERTE(pFunc != NULL);
1174
1175 // May have a null IP for ecall frames. If IP is null, then dwNativeOffset should be 0 too.
1176 _ASSERTE ( (ip != NULL) || (dwNativeOffset == 0) );
1177
1178 this->pFunc = pFunc;
1179 this->dwOffset = dwNativeOffset;
1180 this->ip = ip;
1181 this->fIsLastFrameFromForeignStackTrace = fIsLastFrameFromForeignStackTrace;
1182}
1183
1184#ifndef DACCESS_COMPILE
1185
1186// Initialization done outside the TSL.
1187// This may need to call locking operations that aren't safe under the TSL.
1188void DebugStackTrace::DebugStackTraceElement::InitPass2()
1189{
1190 CONTRACTL
1191 {
1192 MODE_ANY;
1193 GC_TRIGGERS;
1194 THROWS;
1195 }
1196 CONTRACTL_END;
1197
1198 _ASSERTE(!ThreadStore::HoldingThreadStore());
1199
1200 bool bRes = false;
1201
1202#ifdef DEBUGGING_SUPPORTED
1203 // Calculate the IL offset using the debugging services
1204 if ((this->ip != NULL) && g_pDebugInterface)
1205 {
1206 bRes = g_pDebugInterface->GetILOffsetFromNative(
1207 pFunc, (LPCBYTE) this->ip, this->dwOffset, &this->dwILOffset);
1208 }
1209
1210#endif // !DEBUGGING_SUPPORTED
1211
1212 // If there was no mapping information, then set to an invalid value
1213 if (!bRes)
1214 {
1215 this->dwILOffset = (DWORD)-1;
1216 }
1217}
1218
1219#endif // !DACCESS_COMPILE
1220