summaryrefslogtreecommitdiff
path: root/src/ToolBox/SOS/Strike/strike.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ToolBox/SOS/Strike/strike.cpp')
-rw-r--r--src/ToolBox/SOS/Strike/strike.cpp14462
1 files changed, 14462 insertions, 0 deletions
diff --git a/src/ToolBox/SOS/Strike/strike.cpp b/src/ToolBox/SOS/Strike/strike.cpp
new file mode 100644
index 0000000000..731e2f505d
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/strike.cpp
@@ -0,0 +1,14462 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// ==++==
+//
+
+//
+// ==--==
+
+// ===========================================================================
+// STRIKE.CPP
+// ===========================================================================
+//
+// History:
+// 09/07/99 Microsoft Created
+//
+//************************************************************************************************
+// SOS is the native debugging extension designed to support investigations into CLR (mis-)
+// behavior by both users of the runtime as well as the code owners. It allows inspection of
+// internal structures, of user visible entities, as well as execution control.
+//
+// This is the main SOS file hosting the implementation of all the exposed commands. A good
+// starting point for understanding the semantics of these commands is the sosdocs.txt file.
+//
+// #CrossPlatformSOS
+// SOS currently supports cross platform debugging from x86 to ARM. It takes a different approach
+// from the DAC: whereas for the DAC we produce one binary for each supported host-target
+// architecture pair, for SOS we produce only one binary for each host architecture; this one
+// binary contains code for all supported target architectures. In doing this SOS depends on two
+// assumptions:
+// . that the debugger will load the appropriate DAC, and
+// . that the host and target word size is identical.
+// The second assumption is identical to the DAC assumption, and there will be considerable effort
+// required (in the EE, the DAC, and SOS) if we ever need to remove it.
+//
+// In an ideal world SOS would be able to retrieve all platform specific information it needs
+// either from the debugger or from DAC. However, SOS has taken some subtle and not so subtle
+// dependencies on the CLR and the target platform.
+// To resolve this problem, SOS now abstracts the target behind the IMachine interface, and uses
+// calls on IMachine to take target-specific actions. It implements X86Machine, ARMMachine, and
+// AMD64Machine. An instance of these exists in each appropriate host (e.g. the X86 version of SOS
+// contains instaces of X86Machine and ARMMachine, the ARM version contains an instance of
+// ARMMachine, and the AMD64 version contains an instance of AMD64Machine). The code included in
+// each version if determined by the SosTarget*** MSBuild symbols, and SOS_TARGET_*** conditional
+// compilation symbols (as specified in sos.targets).
+//
+// Most of the target specific code is hosted in disasm.h/.cpp, and disasmX86.cpp, disasmARM.cpp.
+// Some code currently under _TARGET_*** ifdefs may need to be reviewed/revisited.
+//
+// Issues:
+// The one-binary-per-host decision does have some drawbacks:
+// . Currently including system headers or even CLR headers will only account for the host
+// target, IOW, when building the X86 version of SOS, CONTEXT will refer to the X86 CONTEXT
+// structure, so we need to be careful when debugging ARM targets. The CONTEXT issue is
+// partially resolved by CROSS_PLATFORM_CONTEXT (there is still a need to be very careful
+// when handling arrays of CONTEXTs - see _EFN_StackTrace for details on this).
+// . For larger includes (e.g. GC info), we will need to include files in specific namespaces,
+// with specific _TARGET_*** macros defined in order to avoid name clashes and ensure correct
+// system types are used.
+// -----------------------------------------------------------------------------------------------
+
+#define DO_NOT_DISABLE_RAND //this is a standalone tool, and can use rand()
+
+#include <windows.h>
+#include <winver.h>
+#include <winternl.h>
+#include <psapi.h>
+#ifndef FEATURE_PAL
+#include <list>
+#endif // !FEATURE_PAL
+#include <wchar.h>
+
+#include "platformspecific.h"
+
+#define NOEXTAPI
+#define KDEXT_64BIT
+#include <wdbgexts.h>
+#undef DECLARE_API
+#undef StackTrace
+
+#include <dbghelp.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stddef.h>
+
+#include "strike.h"
+#include "sos.h"
+
+#ifndef STRESS_LOG
+#define STRESS_LOG
+#endif // STRESS_LOG
+#define STRESS_LOG_READONLY
+#include "stresslog.h"
+
+#include "util.h"
+
+#include "corhdr.h"
+#include "cor.h"
+#include "cordebug.h"
+#include "dacprivate.h"
+#include "corexcep.h"
+
+#define CORHANDLE_MASK 0x1
+#define SWITCHED_OUT_FIBER_OSID 0xbaadf00d;
+
+#define DEFINE_EXT_GLOBALS
+
+#include "data.h"
+#include "disasm.h"
+
+#include "predeftlsslot.h"
+
+#include "hillclimbing.h"
+
+#include "sos_md.h"
+
+#ifndef FEATURE_PAL
+
+#include "ExpressionNode.h"
+#include "WatchCmd.h"
+
+#include <set>
+#include <algorithm>
+#include <vector>
+
+#include "tls.h"
+
+typedef struct _VM_COUNTERS {
+ SIZE_T PeakVirtualSize;
+ SIZE_T VirtualSize;
+ ULONG PageFaultCount;
+ SIZE_T PeakWorkingSetSize;
+ SIZE_T WorkingSetSize;
+ SIZE_T QuotaPeakPagedPoolUsage;
+ SIZE_T QuotaPagedPoolUsage;
+ SIZE_T QuotaPeakNonPagedPoolUsage;
+ SIZE_T QuotaNonPagedPoolUsage;
+ SIZE_T PagefileUsage;
+ SIZE_T PeakPagefileUsage;
+} VM_COUNTERS;
+typedef VM_COUNTERS *PVM_COUNTERS;
+
+const PROCESSINFOCLASS ProcessVmCounters = static_cast<PROCESSINFOCLASS>(3);
+
+#endif // !FEATURE_PAL
+
+BOOL CallStatus;
+BOOL ControlC = FALSE;
+
+IMetaDataDispenserEx *pDisp = NULL;
+WCHAR g_mdName[mdNameLen];
+
+#ifndef FEATURE_PAL
+HMODULE g_hInstance = NULL;
+#include <vector>
+#include <algorithm>
+#endif // !FEATURE_PAL
+
+#ifdef _MSC_VER
+#pragma warning(disable:4244) // conversion from 'unsigned int' to 'unsigned short', possible loss of data
+#pragma warning(disable:4189) // local variable is initialized but not referenced
+#endif
+
+#ifdef FEATURE_PAL
+#define SOSPrefix ""
+#else
+#define SOSPrefix "!"
+#endif
+
+#if defined _X86_ && !defined FEATURE_PAL
+// disable FPO for X86 builds
+#pragma optimize("y", off)
+#endif
+
+#undef assert
+
+#ifdef _MSC_VER
+#pragma warning(default:4244)
+#pragma warning(default:4189)
+#endif
+
+#ifndef FEATURE_PAL
+#include "ntinfo.h"
+#endif // FEATURE_PAL
+
+#ifndef IfFailRet
+#define IfFailRet(EXPR) do { Status = (EXPR); if(FAILED(Status)) { return (Status); } } while (0)
+#endif
+
+#ifdef FEATURE_PAL
+
+#define NOTHROW
+#define MINIDUMP_NOT_SUPPORTED()
+
+#else // !FEATURE_PAL
+
+#define MINIDUMP_NOT_SUPPORTED() \
+ if (IsMiniDumpFile()) \
+ { \
+ ExtOut("This command is not supported in a minidump without full memory\n"); \
+ ExtOut("To try the command anyway, run !MinidumpMode 0\n"); \
+ return Status; \
+ }
+
+#define NOTHROW (std::nothrow)
+
+#include "safemath.h"
+
+DECLARE_API (MinidumpMode)
+{
+ INIT_API ();
+ DWORD_PTR Value=0;
+
+ CMDValue arg[] =
+ { // vptr, type
+ {&Value, COHEX}
+ };
+
+ size_t nArg;
+ if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+ if (nArg == 0)
+ {
+ // Print status of current mode
+ ExtOut("Current mode: %s - unsafe minidump commands are %s.\n",
+ g_InMinidumpSafeMode ? "1" : "0",
+ g_InMinidumpSafeMode ? "disabled" : "enabled");
+ }
+ else
+ {
+ if (Value != 0 && Value != 1)
+ {
+ ExtOut("Mode must be 0 or 1\n");
+ return Status;
+ }
+
+ g_InMinidumpSafeMode = (BOOL) Value;
+ ExtOut("Unsafe minidump commands are %s.\n",
+ g_InMinidumpSafeMode ? "disabled" : "enabled");
+ }
+
+ return Status;
+}
+
+#endif // FEATURE_PAL
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to get the MethodDesc for a given eip *
+* *
+\**********************************************************************/
+DECLARE_API(IP2MD)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ BOOL dml = FALSE;
+ TADDR IP = 0;
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+#ifndef FEATURE_PAL
+ {"/d", &dml, COBOOL, FALSE},
+#endif
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&IP, COHEX},
+ };
+ size_t nArg;
+
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+ EnableDMLHolder dmlHolder(dml);
+
+ if (IP == 0)
+ {
+ ExtOut("%s is not IP\n", args);
+ return Status;
+ }
+
+ CLRDATA_ADDRESS cdaStart = TO_CDADDR(IP);
+ CLRDATA_ADDRESS pMD;
+
+
+ if ((Status = g_sos->GetMethodDescPtrFromIP(cdaStart, &pMD)) != S_OK)
+ {
+ ExtOut("Failed to request MethodData, not in JIT code range\n");
+ return Status;
+ }
+
+ DMLOut("MethodDesc: %s\n", DMLMethodDesc(pMD));
+ DumpMDInfo(TO_TADDR(pMD), cdaStart, FALSE /* fStackTraceFormat */);
+
+ WCHAR filename[MAX_LONGPATH];
+ ULONG linenum;
+ // symlines will be non-zero only if SYMOPT_LOAD_LINES was set in the symbol options
+ ULONG symlines = 0;
+ if (SUCCEEDED(g_ExtSymbols->GetSymbolOptions(&symlines)))
+ {
+ symlines &= SYMOPT_LOAD_LINES;
+ }
+
+ if (symlines != 0 &&
+ SUCCEEDED(GetLineByOffset(TO_CDADDR(IP), &linenum, filename, _countof(filename))))
+ {
+ ExtOut("Source file: %S @ %d\n", filename, linenum);
+ }
+
+ return Status;
+}
+
+// (MAX_STACK_FRAMES is also used by x86 to prevent infinite loops in _EFN_StackTrace)
+#define MAX_STACK_FRAMES 1000
+
+#ifdef _TARGET_WIN64_
+
+// I use a global set of frames for stack walking on win64 because the debugger's
+// GetStackTrace function doesn't provide a way to find out the total size of a stackwalk,
+// and I'd like to have a reasonably big maximum without overflowing the stack by declaring
+// the buffer locally and I also want to get a managed trace in a low memory environment
+// (so no dynamic allocation if possible).
+DEBUG_STACK_FRAME g_Frames[MAX_STACK_FRAMES];
+AMD64_CONTEXT g_X64FrameContexts[MAX_STACK_FRAMES];
+
+static HRESULT
+GetContextStackTrace(PULONG pnumFrames)
+{
+ PDEBUG_CONTROL4 debugControl4;
+ HRESULT hr;
+
+ // Do we have advanced capability?
+ if ((hr = g_ExtControl->QueryInterface(__uuidof(IDebugControl4), (void **)&debugControl4)) == S_OK)
+ {
+ // GetContextStackTrace fills g_X64FrameContexts as an array of
+ // contexts packed as target architecture contexts. We cannot
+ // safely cast this as an array of CROSS_PLATFORM_CONTEXT, since
+ // sizeof(CROSS_PLATFORM_CONTEXT) != sizeof(TGT_CONTEXT)
+ hr = debugControl4->GetContextStackTrace(
+ NULL,
+ 0,
+ g_Frames,
+ MAX_STACK_FRAMES,
+ g_X64FrameContexts,
+ MAX_STACK_FRAMES*g_targetMachine->GetContextSize(),
+ g_targetMachine->GetContextSize(),
+ pnumFrames);
+
+ debugControl4->Release();
+ }
+ return hr;
+}
+
+#endif // _TARGET_WIN64_
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function displays the stack trace. It looks at each DWORD *
+* on stack. If the DWORD is a return address, the symbol name or
+* managed function name is displayed. *
+* *
+\**********************************************************************/
+void DumpStackInternal(DumpStackFlag *pDSFlag)
+{
+ ReloadSymbolWithLineInfo();
+
+ ULONG64 StackOffset;
+ g_ExtRegisters->GetStackOffset (&StackOffset);
+ if (pDSFlag->top == 0) {
+ pDSFlag->top = TO_TADDR(StackOffset);
+ }
+ size_t value;
+ while (g_ExtData->ReadVirtual(TO_CDADDR(pDSFlag->top), &value, sizeof(size_t), NULL) != S_OK) {
+ if (IsInterrupt())
+ return;
+ pDSFlag->top = NextOSPageAddress(pDSFlag->top);
+ }
+
+#ifndef FEATURE_PAL
+ if (pDSFlag->end == 0) {
+ // Find the current stack range
+ NT_TIB teb;
+ ULONG64 dwTebAddr=0;
+
+ g_ExtSystem->GetCurrentThreadTeb(&dwTebAddr);
+ if (SafeReadMemory(TO_TADDR(dwTebAddr), &teb, sizeof(NT_TIB), NULL))
+ {
+ if (pDSFlag->top > TO_TADDR(teb.StackLimit)
+ && pDSFlag->top <= TO_TADDR(teb.StackBase))
+ {
+ if (pDSFlag->end == 0 || pDSFlag->end > TO_TADDR(teb.StackBase))
+ pDSFlag->end = TO_TADDR(teb.StackBase);
+ }
+ }
+ }
+#endif // FEATURE_PAL
+
+ if (pDSFlag->end == 0)
+ {
+ ExtOut("TEB information is not available so a stack size of 0xFFFF is assumed\n");
+ pDSFlag->end = pDSFlag->top + 0xFFFF;
+ }
+
+ if (pDSFlag->end < pDSFlag->top)
+ {
+ ExtOut("Wrong option: stack selection wrong\n");
+ return;
+ }
+
+ DumpStackWorker(*pDSFlag);
+}
+
+
+DECLARE_API(DumpStack)
+{
+ INIT_API_NO_RET_ON_FAILURE();
+
+ MINIDUMP_NOT_SUPPORTED();
+
+ DumpStackFlag DSFlag;
+ DSFlag.fEEonly = FALSE;
+ DSFlag.fSuppressSrcInfo = FALSE;
+ DSFlag.top = 0;
+ DSFlag.end = 0;
+
+ BOOL dml = FALSE;
+ CMDOption option[] = {
+ // name, vptr, type, hasValue
+ {"-EE", &DSFlag.fEEonly, COBOOL, FALSE},
+ {"-n", &DSFlag.fSuppressSrcInfo, COBOOL, FALSE},
+#ifndef FEATURE_PAL
+ {"/d", &dml, COBOOL, FALSE}
+#endif
+ };
+ CMDValue arg[] = {
+ // vptr, type
+ {&DSFlag.top, COHEX},
+ {&DSFlag.end, COHEX}
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ return Status;
+
+ // symlines will be non-zero only if SYMOPT_LOAD_LINES was set in the symbol options
+ ULONG symlines = 0;
+ if (!DSFlag.fSuppressSrcInfo && SUCCEEDED(g_ExtSymbols->GetSymbolOptions(&symlines)))
+ {
+ symlines &= SYMOPT_LOAD_LINES;
+ }
+ DSFlag.fSuppressSrcInfo = DSFlag.fSuppressSrcInfo || (symlines == 0);
+
+ EnableDMLHolder enabledml(dml);
+
+ ULONG id = 0;
+ g_ExtSystem->GetCurrentThreadSystemId(&id);
+ ExtOut("OS Thread Id: 0x%x ", id);
+ g_ExtSystem->GetCurrentThreadId(&id);
+ ExtOut("(%d)\n", id);
+
+ DumpStackInternal(&DSFlag);
+ return Status;
+}
+
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function displays the stack trace for threads that EE knows *
+* from ThreadStore. *
+* *
+\**********************************************************************/
+DECLARE_API (EEStack)
+{
+ INIT_API();
+
+ MINIDUMP_NOT_SUPPORTED();
+
+ DumpStackFlag DSFlag;
+ DSFlag.fEEonly = FALSE;
+ DSFlag.fSuppressSrcInfo = FALSE;
+ DSFlag.top = 0;
+ DSFlag.end = 0;
+
+ BOOL bShortList = FALSE;
+ BOOL dml = FALSE;
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"-EE", &DSFlag.fEEonly, COBOOL, FALSE},
+ {"-short", &bShortList, COBOOL, FALSE},
+#ifndef FEATURE_PAL
+ {"/d", &dml, COBOOL, FALSE}
+#endif
+ };
+
+ if (!GetCMDOption(args, option, _countof(option), NULL, 0, NULL))
+ {
+ return Status;
+ }
+
+ EnableDMLHolder enableDML(dml);
+
+ ULONG Tid;
+ g_ExtSystem->GetCurrentThreadId(&Tid);
+
+ DacpThreadStoreData ThreadStore;
+ if ((Status = ThreadStore.Request(g_sos)) != S_OK)
+ {
+ ExtOut("Failed to request ThreadStore\n");
+ return Status;
+ }
+
+ CLRDATA_ADDRESS CurThread = ThreadStore.firstThread;
+ while (CurThread)
+ {
+ if (IsInterrupt())
+ break;
+
+ DacpThreadData Thread;
+ if ((Status = Thread.Request(g_sos, CurThread)) != S_OK)
+ {
+ ExtOut("Failed to request Thread at %p\n", CurThread);
+ return Status;
+ }
+
+ ULONG id=0;
+ if (g_ExtSystem->GetThreadIdBySystemId (Thread.osThreadId, &id) != S_OK)
+ {
+ CurThread = Thread.nextThread;
+ continue;
+ }
+
+ ExtOut("---------------------------------------------\n");
+ ExtOut("Thread %3d\n", id);
+ BOOL doIt = FALSE;
+
+
+#define TS_Hijacked 0x00000080
+
+ if (!bShortList)
+ {
+ doIt = TRUE;
+ }
+ else if ((Thread.lockCount > 0) || (Thread.state & TS_Hijacked))
+ {
+ // TODO: bring back || (int)vThread.m_pFrame != -1 {
+ doIt = TRUE;
+ }
+ else
+ {
+ ULONG64 IP;
+ g_ExtRegisters->GetInstructionOffset (&IP);
+ JITTypes jitType;
+ TADDR methodDesc;
+ TADDR gcinfoAddr;
+ IP2MethodDesc (TO_TADDR(IP), methodDesc, jitType, gcinfoAddr);
+ if (methodDesc)
+ {
+ doIt = TRUE;
+ }
+ }
+
+ if (doIt)
+ {
+ g_ExtSystem->SetCurrentThreadId(id);
+ DSFlag.top = 0;
+ DSFlag.end = 0;
+ DumpStackInternal(&DSFlag);
+ }
+
+ CurThread = Thread.nextThread;
+ }
+
+ g_ExtSystem->SetCurrentThreadId(Tid);
+ return Status;
+}
+
+HRESULT DumpStackObjectsRaw(size_t nArg, __in_z LPSTR exprBottom, __in_z LPSTR exprTop, BOOL bVerify)
+{
+ size_t StackTop = 0;
+ size_t StackBottom = 0;
+ if (nArg==0)
+ {
+ ULONG64 StackOffset;
+ g_ExtRegisters->GetStackOffset(&StackOffset);
+
+ StackTop = TO_TADDR(StackOffset);
+ }
+ else
+ {
+ StackTop = GetExpression(exprTop);
+ if (StackTop == 0)
+ {
+ ExtOut("wrong option: %s\n", exprTop);
+ return E_FAIL;
+ }
+
+ if (nArg==2)
+ {
+ StackBottom = GetExpression(exprBottom);
+ if (StackBottom == 0)
+ {
+ ExtOut("wrong option: %s\n", exprBottom);
+ return E_FAIL;
+ }
+ }
+ }
+
+#ifndef FEATURE_PAL
+ NT_TIB teb;
+ ULONG64 dwTebAddr=0;
+ HRESULT hr = g_ExtSystem->GetCurrentThreadTeb(&dwTebAddr);
+ if (SUCCEEDED(hr) && SafeReadMemory (TO_TADDR(dwTebAddr), &teb, sizeof (NT_TIB), NULL))
+ {
+ if (StackTop > TO_TADDR(teb.StackLimit) && StackTop <= TO_TADDR(teb.StackBase))
+ {
+ if (StackBottom == 0 || StackBottom > TO_TADDR(teb.StackBase))
+ StackBottom = TO_TADDR(teb.StackBase);
+ }
+ }
+#endif
+
+ if (StackBottom == 0)
+ StackBottom = StackTop + 0xFFFF;
+
+ if (StackBottom < StackTop)
+ {
+ ExtOut("Wrong option: stack selection wrong\n");
+ return E_FAIL;
+ }
+
+ // We can use the gc snapshot to eliminate object addresses that are
+ // not on the gc heap.
+ if (!g_snapshot.Build())
+ {
+ ExtOut("Unable to determine bounds of gc heap\n");
+ return E_FAIL;
+ }
+
+ // Print thread ID.
+ ULONG id = 0;
+ g_ExtSystem->GetCurrentThreadSystemId (&id);
+ ExtOut("OS Thread Id: 0x%x ", id);
+ g_ExtSystem->GetCurrentThreadId (&id);
+ ExtOut("(%d)\n", id);
+
+ DumpStackObjectsHelper(StackTop, StackBottom, bVerify);
+ return S_OK;
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to dump the address and name of all *
+* Managed Objects on the stack. *
+* *
+\**********************************************************************/
+DECLARE_API(DumpStackObjects)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+ StringHolder exprTop, exprBottom;
+
+ BOOL bVerify = FALSE;
+ BOOL dml = FALSE;
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"-verify", &bVerify, COBOOL, FALSE},
+#ifndef FEATURE_PAL
+ {"/d", &dml, COBOOL, FALSE}
+#endif
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&exprTop.data, COSTRING},
+ {&exprBottom.data, COSTRING}
+ };
+ size_t nArg;
+
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ EnableDMLHolder enableDML(dml);
+
+ return DumpStackObjectsRaw(nArg, exprBottom.data, exprTop.data, bVerify);
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to dump the contents of a MethodDesc *
+* for a given address *
+* *
+\**********************************************************************/
+DECLARE_API(DumpMD)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ DWORD_PTR dwStartAddr = NULL;
+ BOOL dml = FALSE;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+#ifndef FEATURE_PAL
+ {"/d", &dml, COBOOL, FALSE},
+#endif
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&dwStartAddr, COHEX},
+ };
+ size_t nArg;
+
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+
+ DumpMDInfo(dwStartAddr);
+
+ return Status;
+}
+
+BOOL GatherDynamicInfo(TADDR DynamicMethodObj, DacpObjectData *codeArray,
+ DacpObjectData *tokenArray, TADDR *ptokenArrayAddr)
+{
+ BOOL bRet = FALSE;
+ int iOffset;
+ DacpObjectData objData; // temp object
+
+ if (codeArray == NULL || tokenArray == NULL)
+ return bRet;
+
+ if (objData.Request(g_sos, TO_CDADDR(DynamicMethodObj)) != S_OK)
+ return bRet;
+
+ iOffset = GetObjFieldOffset(DynamicMethodObj, objData.MethodTable, W("m_resolver"));
+ if (iOffset <= 0)
+ return bRet;
+
+ TADDR resolverPtr;
+ if (FAILED(MOVE(resolverPtr, DynamicMethodObj + iOffset)))
+ return bRet;
+
+ if (objData.Request(g_sos, TO_CDADDR(resolverPtr)) != S_OK)
+ return bRet;
+
+ iOffset = GetObjFieldOffset(resolverPtr, objData.MethodTable, W("m_code"));
+ if (iOffset <= 0)
+ return bRet;
+
+ TADDR codePtr;
+ if (FAILED(MOVE(codePtr, resolverPtr + iOffset)))
+ return bRet;
+
+ if (codeArray->Request(g_sos, TO_CDADDR(codePtr)) != S_OK)
+ return bRet;
+
+ if (codeArray->dwComponentSize != 1)
+ return bRet;
+
+ // We also need the resolution table
+ iOffset = GetObjFieldOffset (resolverPtr, objData.MethodTable, W("m_scope"));
+ if (iOffset <= 0)
+ return bRet;
+
+ TADDR scopePtr;
+ if (FAILED(MOVE(scopePtr, resolverPtr + iOffset)))
+ return bRet;
+
+ if (objData.Request(g_sos, TO_CDADDR(scopePtr)) != S_OK)
+ return bRet;
+
+ iOffset = GetObjFieldOffset (scopePtr, objData.MethodTable, W("m_tokens"));
+ if (iOffset <= 0)
+ return bRet;
+
+ TADDR tokensPtr;
+ if (FAILED(MOVE(tokensPtr, scopePtr + iOffset)))
+ return bRet;
+
+ if (objData.Request(g_sos, TO_CDADDR(tokensPtr)) != S_OK)
+ return bRet;
+
+ iOffset = GetObjFieldOffset(tokensPtr, objData.MethodTable, W("_items"));
+ if (iOffset <= 0)
+ return bRet;
+
+ TADDR itemsPtr;
+ MOVE (itemsPtr, tokensPtr + iOffset);
+
+ *ptokenArrayAddr = itemsPtr;
+
+ if (tokenArray->Request(g_sos, TO_CDADDR(itemsPtr)) != S_OK)
+ return bRet;
+
+ bRet = TRUE; // whew.
+ return bRet;
+}
+
+DECLARE_API(DumpIL)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+ DWORD_PTR dwStartAddr = NULL;
+ DWORD_PTR dwDynamicMethodObj = NULL;
+ BOOL dml = FALSE;
+ BOOL fILPointerDirectlySpecified = FALSE;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"/d", &dml, COBOOL, FALSE},
+ {"/i", &fILPointerDirectlySpecified, COBOOL, FALSE},
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&dwStartAddr, COHEX},
+ };
+ size_t nArg;
+
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+ if (dwStartAddr == NULL)
+ {
+ ExtOut("Must pass a valid expression\n");
+ return Status;
+ }
+
+ if (fILPointerDirectlySpecified)
+ {
+ return DecodeILFromAddress(NULL, dwStartAddr);
+ }
+
+ if (!g_snapshot.Build())
+ {
+ ExtOut("Unable to build snapshot of the garbage collector state\n");
+ return Status;
+ }
+
+ if (g_snapshot.GetHeap(dwStartAddr) != NULL)
+ {
+ dwDynamicMethodObj = dwStartAddr;
+ }
+
+ if (dwDynamicMethodObj == NULL)
+ {
+ // We have been given a MethodDesc
+ DacpMethodDescData MethodDescData;
+ if (MethodDescData.Request(g_sos, TO_CDADDR(dwStartAddr)) != S_OK)
+ {
+ ExtOut("%p is not a MethodDesc\n", SOS_PTR(dwStartAddr));
+ return Status;
+ }
+
+ if (MethodDescData.bIsDynamic && MethodDescData.managedDynamicMethodObject)
+ {
+ dwDynamicMethodObj = TO_TADDR(MethodDescData.managedDynamicMethodObject);
+ if (dwDynamicMethodObj == NULL)
+ {
+ ExtOut("Unable to print IL for DynamicMethodDesc %p\n", SOS_PTR(dwDynamicMethodObj));
+ return Status;
+ }
+ }
+ else
+ {
+ // This is not a dynamic method, print the IL for it.
+ // Get the module
+ DacpModuleData dmd;
+ if (dmd.Request(g_sos, MethodDescData.ModulePtr) != S_OK)
+ {
+ ExtOut("Unable to get module\n");
+ return Status;
+ }
+
+ ToRelease<IMetaDataImport> pImport = MDImportForModule(&dmd);
+ if (pImport == NULL)
+ {
+ ExtOut("bad import\n");
+ return Status;
+ }
+
+ ULONG pRva;
+ DWORD dwFlags;
+ if (pImport->GetRVA(MethodDescData.MDToken, &pRva, &dwFlags) != S_OK)
+ {
+ ExtOut("error in import\n");
+ return Status;
+ }
+
+ CLRDATA_ADDRESS ilAddrClr;
+ if (g_sos->GetILForModule(MethodDescData.ModulePtr, pRva, &ilAddrClr) != S_OK)
+ {
+ ExtOut("FindIL failed\n");
+ return Status;
+ }
+
+ TADDR ilAddr = TO_TADDR(ilAddrClr);
+ IfFailRet(DecodeILFromAddress(pImport, ilAddr));
+ }
+ }
+
+ if (dwDynamicMethodObj != NULL)
+ {
+ // We have a DynamicMethod managed object, let us visit the town and paint.
+ DacpObjectData codeArray;
+ DacpObjectData tokenArray;
+ DWORD_PTR tokenArrayAddr;
+ if (!GatherDynamicInfo (dwDynamicMethodObj, &codeArray, &tokenArray, &tokenArrayAddr))
+ {
+ DMLOut("Error gathering dynamic info from object at %s.\n", DMLObject(dwDynamicMethodObj));
+ return Status;
+ }
+
+ // Read the memory into a local buffer
+ BYTE *pArray = new NOTHROW BYTE[(SIZE_T)codeArray.dwNumComponents];
+ if (pArray == NULL)
+ {
+ ExtOut("Not enough memory to read IL\n");
+ return Status;
+ }
+
+ Status = g_ExtData->ReadVirtual(UL64_TO_CDA(codeArray.ArrayDataPtr), pArray, (ULONG)codeArray.dwNumComponents, NULL);
+ if (Status != S_OK)
+ {
+ ExtOut("Failed to read memory\n");
+ delete [] pArray;
+ return Status;
+ }
+
+ // Now we have a local copy of the IL, and a managed array for token resolution.
+ // Visit our IL parser with this info.
+ ExtOut("This is dynamic IL. Exception info is not reported at this time.\n");
+ ExtOut("If a token is unresolved, run \"!do <addr>\" on the addr given\n");
+ ExtOut("in parenthesis. You can also look at the token table yourself, by\n");
+ ExtOut("running \"!DumpArray %p\".\n\n", SOS_PTR(tokenArrayAddr));
+ DecodeDynamicIL(pArray, (ULONG)codeArray.dwNumComponents, tokenArray);
+
+ delete [] pArray;
+ }
+ return Status;
+}
+
+void DumpSigWorker (
+ DWORD_PTR dwSigAddr,
+ DWORD_PTR dwModuleAddr,
+ BOOL fMethod)
+{
+ //
+ // Find the length of the signature and copy it into the debugger process.
+ //
+
+ ULONG cbSig = 0;
+ const ULONG cbSigInc = 256;
+ ArrayHolder<COR_SIGNATURE> pSig = new NOTHROW COR_SIGNATURE[cbSigInc];
+ if (pSig == NULL)
+ {
+ ReportOOM();
+ return;
+ }
+
+ CQuickBytes sigString;
+ for (;;)
+ {
+ if (IsInterrupt())
+ return;
+
+ ULONG cbCopied;
+ if (!SafeReadMemory(TO_TADDR(dwSigAddr + cbSig), pSig + cbSig, cbSigInc, &cbCopied))
+ return;
+ cbSig += cbCopied;
+
+ sigString.ReSize(0);
+ GetSignatureStringResults result;
+ if (fMethod)
+ result = GetMethodSignatureString(pSig, cbSig, dwModuleAddr, &sigString);
+ else
+ result = GetSignatureString(pSig, cbSig, dwModuleAddr, &sigString);
+
+ if (GSS_ERROR == result)
+ return;
+
+ if (GSS_SUCCESS == result)
+ break;
+
+ // If we didn't get the full amount back, and we failed to parse the
+ // signature, it's not valid because of insufficient data
+ if (cbCopied < 256)
+ {
+ ExtOut("Invalid signature\n");
+ return;
+ }
+
+#ifdef _PREFAST_
+#pragma warning(push)
+#pragma warning(disable:6280) // "Suppress PREFast warning about mismatch alloc/free"
+#endif
+
+ PCOR_SIGNATURE pSigNew = (PCOR_SIGNATURE)realloc(pSig, cbSig+cbSigInc);
+
+#ifdef _PREFAST_
+#pragma warning(pop)
+#endif
+
+ if (pSigNew == NULL)
+ {
+ ExtOut("Out of memory\n");
+ return;
+ }
+
+ pSig = pSigNew;
+ }
+
+ ExtOut("%S\n", (PCWSTR)sigString.Ptr());
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to dump a signature object. *
+* *
+\**********************************************************************/
+DECLARE_API(DumpSig)
+{
+ INIT_API();
+
+ MINIDUMP_NOT_SUPPORTED();
+
+ //
+ // Fetch arguments
+ //
+
+ StringHolder sigExpr;
+ StringHolder moduleExpr;
+ CMDValue arg[] =
+ {
+ {&sigExpr.data, COSTRING},
+ {&moduleExpr.data, COSTRING}
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+ if (nArg != 2)
+ {
+ ExtOut("!DumpSig <sigaddr> <moduleaddr>\n");
+ return Status;
+ }
+
+ DWORD_PTR dwSigAddr = GetExpression(sigExpr.data);
+ DWORD_PTR dwModuleAddr = GetExpression(moduleExpr.data);
+
+ if (dwSigAddr == 0 || dwModuleAddr == 0)
+ {
+ ExtOut("Invalid parameters %s %s\n", sigExpr.data, moduleExpr.data);
+ return Status;
+ }
+
+ DumpSigWorker(dwSigAddr, dwModuleAddr, TRUE);
+ return Status;
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to dump a portion of a signature object. *
+* *
+\**********************************************************************/
+DECLARE_API(DumpSigElem)
+{
+ INIT_API();
+
+ MINIDUMP_NOT_SUPPORTED();
+
+
+ //
+ // Fetch arguments
+ //
+
+ StringHolder sigExpr;
+ StringHolder moduleExpr;
+ CMDValue arg[] =
+ {
+ {&sigExpr.data, COSTRING},
+ {&moduleExpr.data, COSTRING}
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ if (nArg != 2)
+ {
+ ExtOut("!DumpSigElem <sigaddr> <moduleaddr>\n");
+ return Status;
+ }
+
+ DWORD_PTR dwSigAddr = GetExpression(sigExpr.data);
+ DWORD_PTR dwModuleAddr = GetExpression(moduleExpr.data);
+
+ if (dwSigAddr == 0 || dwModuleAddr == 0)
+ {
+ ExtOut("Invalid parameters %s %s\n", sigExpr.data, moduleExpr.data);
+ return Status;
+ }
+
+ DumpSigWorker(dwSigAddr, dwModuleAddr, FALSE);
+ return Status;
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to dump the contents of an EEClass from *
+* a given address
+* *
+\**********************************************************************/
+DECLARE_API(DumpClass)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ DWORD_PTR dwStartAddr = 0;
+ BOOL dml = FALSE;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+#ifndef FEATURE_PAL
+ {"/d", &dml, COBOOL, FALSE},
+#endif
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&dwStartAddr, COHEX}
+ };
+
+ size_t nArg;
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ if (nArg == 0)
+ {
+ ExtOut("Missing EEClass address\n");
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+
+ CLRDATA_ADDRESS methodTable;
+ if ((Status=g_sos->GetMethodTableForEEClass(TO_CDADDR(dwStartAddr), &methodTable)) != S_OK)
+ {
+ ExtOut("Invalid EEClass address\n");
+ return Status;
+ }
+
+ DacpMethodTableData mtdata;
+ if ((Status=mtdata.Request(g_sos, TO_CDADDR(methodTable)))!=S_OK)
+ {
+ ExtOut("EEClass has an invalid MethodTable address\n");
+ return Status;
+ }
+
+ sos::MethodTable mt = TO_TADDR(methodTable);
+ ExtOut("Class Name: %S\n", mt.GetName());
+
+ WCHAR fileName[MAX_LONGPATH];
+ FileNameForModule(TO_TADDR(mtdata.Module), fileName);
+ ExtOut("mdToken: %p\n", mtdata.cl);
+ ExtOut("File: %S\n", fileName);
+
+ CLRDATA_ADDRESS ParentEEClass = NULL;
+ if (mtdata.ParentMethodTable)
+ {
+ DacpMethodTableData mtdataparent;
+ if ((Status=mtdataparent.Request(g_sos, TO_CDADDR(mtdata.ParentMethodTable)))!=S_OK)
+ {
+ ExtOut("EEClass has an invalid MethodTable address\n");
+ return Status;
+ }
+ ParentEEClass = mtdataparent.Class;
+ }
+
+ DMLOut("Parent Class: %s\n", DMLClass(ParentEEClass));
+ DMLOut("Module: %s\n", DMLModule(mtdata.Module));
+ DMLOut("Method Table: %s\n", DMLMethodTable(methodTable));
+ ExtOut("Vtable Slots: %x\n", mtdata.wNumVirtuals);
+ ExtOut("Total Method Slots: %x\n", mtdata.wNumVtableSlots);
+ ExtOut("Class Attributes: %x ", mtdata.dwAttrClass);
+
+ if (IsTdInterface(mtdata.dwAttrClass))
+ ExtOut("Interface, ");
+ if (IsTdAbstract(mtdata.dwAttrClass))
+ ExtOut("Abstract, ");
+ if (IsTdImport(mtdata.dwAttrClass))
+ ExtOut("ComImport, ");
+
+ ExtOut("\n");
+
+ DacpMethodTableTransparencyData transparency;
+ if (SUCCEEDED(transparency.Request(g_sos, methodTable)))
+ {
+ ExtOut("Transparency: %s\n", GetTransparency(transparency));
+ }
+
+ DacpMethodTableFieldData vMethodTableFields;
+ if (SUCCEEDED(vMethodTableFields.Request(g_sos, methodTable)))
+ {
+ ExtOut("NumInstanceFields: %x\n", vMethodTableFields.wNumInstanceFields);
+ ExtOut("NumStaticFields: %x\n", vMethodTableFields.wNumStaticFields);
+
+ if (vMethodTableFields.wNumThreadStaticFields != 0)
+ {
+ ExtOut("NumThreadStaticFields: %x\n", vMethodTableFields.wNumThreadStaticFields);
+ }
+
+
+ if (vMethodTableFields.wContextStaticsSize)
+ {
+ ExtOut("ContextStaticOffset: %x\n", vMethodTableFields.wContextStaticOffset);
+ ExtOut("ContextStaticsSize: %x\n", vMethodTableFields.wContextStaticsSize);
+ }
+
+
+ if (vMethodTableFields.wNumInstanceFields + vMethodTableFields.wNumStaticFields > 0)
+ {
+ DisplayFields(methodTable, &mtdata, &vMethodTableFields, NULL, TRUE, FALSE);
+ }
+ }
+
+ return Status;
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to dump the contents of a MethodTable *
+* from a given address *
+* *
+\**********************************************************************/
+DECLARE_API(DumpMT)
+{
+ DWORD_PTR dwStartAddr=0;
+ DWORD_PTR dwOriginalAddr;
+
+ INIT_API();
+
+ MINIDUMP_NOT_SUPPORTED();
+
+ BOOL bDumpMDTable = FALSE;
+ BOOL dml = FALSE;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"-MD", &bDumpMDTable, COBOOL, FALSE},
+#ifndef FEATURE_PAL
+ {"/d", &dml, COBOOL, FALSE}
+#endif
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&dwStartAddr, COHEX}
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+ TableOutput table(2, 16, AlignLeft, false);
+
+ if (nArg == 0)
+ {
+ Print("Missing MethodTable address\n");
+ return Status;
+ }
+
+ dwOriginalAddr = dwStartAddr;
+ dwStartAddr = dwStartAddr&~3;
+
+ if (!IsMethodTable(dwStartAddr))
+ {
+ Print(dwOriginalAddr, " is not a MethodTable\n");
+ return Status;
+ }
+
+ DacpMethodTableData vMethTable;
+ vMethTable.Request(g_sos, TO_CDADDR(dwStartAddr));
+
+ if (vMethTable.bIsFree)
+ {
+ Print("Free MethodTable\n");
+ return Status;
+ }
+
+ table.WriteRow("EEClass:", EEClassPtr(vMethTable.Class));
+
+ table.WriteRow("Module:", ModulePtr(vMethTable.Module));
+
+ sos::MethodTable mt = (TADDR)dwStartAddr;
+ table.WriteRow("Name:", mt.GetName());
+
+ WCHAR fileName[MAX_LONGPATH];
+ FileNameForModule(TO_TADDR(vMethTable.Module), fileName);
+ table.WriteRow("mdToken:", Pointer(vMethTable.cl));
+ table.WriteRow("File:", fileName[0] ? fileName : W("Unknown Module"));
+
+ table.WriteRow("BaseSize:", PrefixHex(vMethTable.BaseSize));
+ table.WriteRow("ComponentSize:", PrefixHex(vMethTable.ComponentSize));
+ table.WriteRow("Slots in VTable:", Decimal(vMethTable.wNumMethods));
+
+ table.SetColWidth(0, 29);
+ table.WriteRow("Number of IFaces in IFaceMap:", Decimal(vMethTable.wNumInterfaces));
+
+ if (bDumpMDTable)
+ {
+ table.ReInit(4, POINTERSIZE_HEX, AlignRight);
+ table.SetColAlignment(3, AlignLeft);
+ table.SetColWidth(2, 6);
+
+ Print("--------------------------------------\n");
+ Print("MethodDesc Table\n");
+
+ table.WriteRow("Entry", "MethodDesc", "JIT", "Name");
+
+ for (DWORD n = 0; n < vMethTable.wNumMethods; n++)
+ {
+ JITTypes jitType;
+ DWORD_PTR methodDesc=0;
+ DWORD_PTR gcinfoAddr;
+
+ CLRDATA_ADDRESS entry;
+ if (g_sos->GetMethodTableSlot(dwStartAddr, n, &entry) != S_OK)
+ {
+ PrintLn("<error getting slot ", Decimal(n), ">");
+ continue;
+ }
+
+ IP2MethodDesc((DWORD_PTR)entry, methodDesc, jitType, gcinfoAddr);
+ table.WriteColumn(0, entry);
+ table.WriteColumn(1, MethodDescPtr(methodDesc));
+
+ if (jitType == TYPE_UNKNOWN && methodDesc != NULL)
+ {
+ // We can get a more accurate jitType from NativeCodeAddr of the methoddesc,
+ // because the methodtable entry hasn't always been patched.
+ DacpMethodDescData tmpMethodDescData;
+ if (tmpMethodDescData.Request(g_sos, TO_CDADDR(methodDesc)) == S_OK)
+ {
+ DacpCodeHeaderData codeHeaderData;
+ if (codeHeaderData.Request(g_sos,tmpMethodDescData.NativeCodeAddr) == S_OK)
+ {
+ jitType = (JITTypes) codeHeaderData.JITType;
+ }
+ }
+ }
+
+ const char *pszJitType = "NONE";
+ if (jitType == TYPE_JIT)
+ pszJitType = "JIT";
+ else if (jitType == TYPE_PJIT)
+ pszJitType = "PreJIT";
+ else
+ {
+ DacpMethodDescData MethodDescData;
+ if (MethodDescData.Request(g_sos, TO_CDADDR(methodDesc)) == S_OK)
+ {
+ // Is it an fcall?
+ if ((TO_TADDR(MethodDescData.NativeCodeAddr) >= TO_TADDR(moduleInfo[MSCORWKS].baseAddr)) &&
+ ((TO_TADDR(MethodDescData.NativeCodeAddr) < TO_TADDR(moduleInfo[MSCORWKS].baseAddr + moduleInfo[MSCORWKS].size))))
+ {
+ pszJitType = "FCALL";
+ }
+ }
+ }
+
+ table.WriteColumn(2, pszJitType);
+
+ NameForMD_s(methodDesc,g_mdName,mdNameLen);
+ table.WriteColumn(3, g_mdName);
+ }
+ }
+ return Status;
+}
+
+extern size_t Align (size_t nbytes);
+
+HRESULT PrintVC(TADDR taMT, TADDR taObject, BOOL bPrintFields = TRUE)
+{
+ HRESULT Status;
+ DacpMethodTableData mtabledata;
+ if ((Status = mtabledata.Request(g_sos, TO_CDADDR(taMT)))!=S_OK)
+ return Status;
+
+ size_t size = mtabledata.BaseSize;
+ if ((Status=g_sos->GetMethodTableName(TO_CDADDR(taMT), mdNameLen, g_mdName, NULL))!=S_OK)
+ return Status;
+
+ ExtOut("Name: %S\n", g_mdName);
+ DMLOut("MethodTable: %s\n", DMLMethodTable(taMT));
+ DMLOut("EEClass: %s\n", DMLClass(mtabledata.Class));
+ ExtOut("Size: %d(0x%x) bytes\n", size, size);
+
+ FileNameForModule(TO_TADDR(mtabledata.Module), g_mdName);
+ ExtOut("File: %S\n", g_mdName[0] ? g_mdName : W("Unknown Module"));
+
+ if (bPrintFields)
+ {
+ DacpMethodTableFieldData vMethodTableFields;
+ if ((Status = vMethodTableFields.Request(g_sos,TO_CDADDR(taMT)))!=S_OK)
+ return Status;
+
+ ExtOut("Fields:\n");
+
+ if (vMethodTableFields.wNumInstanceFields + vMethodTableFields.wNumStaticFields > 0)
+ DisplayFields(TO_CDADDR(taMT), &mtabledata, &vMethodTableFields, taObject, TRUE, TRUE);
+ }
+
+ return S_OK;
+}
+
+void PrintRuntimeTypeInfo(TADDR p_rtObject, const DacpObjectData & rtObjectData)
+{
+ // Get the method table
+ int iOffset = GetObjFieldOffset(p_rtObject, rtObjectData.MethodTable, W("m_handle"));
+ if (iOffset > 0)
+ {
+ TADDR mtPtr;
+ if (SUCCEEDED(GetMTOfObject(p_rtObject + iOffset, &mtPtr)))
+ {
+ sos::MethodTable mt = mtPtr;
+ ExtOut("Type Name: %S\n", mt.GetName());
+ DMLOut("Type MT: %s\n", DMLMethodTable(mtPtr));
+ }
+ }
+}
+
+HRESULT PrintObj(TADDR taObj, BOOL bPrintFields = TRUE)
+{
+ if (!sos::IsObject(taObj, true))
+ {
+ ExtOut("<Note: this object has an invalid CLASS field>\n");
+ }
+
+ DacpObjectData objData;
+ HRESULT Status;
+ if ((Status=objData.Request(g_sos, TO_CDADDR(taObj))) != S_OK)
+ {
+ ExtOut("Invalid object\n");
+ return Status;
+ }
+
+ if (objData.ObjectType==OBJ_FREE)
+ {
+ ExtOut("Free Object\n");
+ DWORD_PTR size = (DWORD_PTR)objData.Size;
+ ExtOut("Size: %" POINTERSIZE_TYPE "d(0x%" POINTERSIZE_TYPE "x) bytes\n", size, size);
+ return S_OK;
+ }
+
+ sos::Object obj = taObj;
+ ExtOut("Name: %S\n", obj.GetTypeName());
+ DMLOut("MethodTable: %s\n", DMLMethodTable(objData.MethodTable));
+
+
+ DacpMethodTableData mtabledata;
+ if ((Status=mtabledata.Request(g_sos,objData.MethodTable)) == S_OK)
+ {
+ DMLOut("EEClass: %s\n", DMLClass(mtabledata.Class));
+ }
+ else
+ {
+ ExtOut("Invalid EEClass address\n");
+ return Status;
+ }
+
+ if (objData.RCW != NULL)
+ {
+ DMLOut("RCW: %s\n", DMLRCWrapper(objData.RCW));
+ }
+ if (objData.CCW != NULL)
+ {
+ DMLOut("CCW: %s\n", DMLCCWrapper(objData.CCW));
+ }
+
+ DWORD_PTR size = (DWORD_PTR)objData.Size;
+ ExtOut("Size: %" POINTERSIZE_TYPE "d(0x%" POINTERSIZE_TYPE "x) bytes\n", size, size);
+
+ if (_wcscmp(obj.GetTypeName(), W("System.RuntimeType")) == 0)
+ {
+ PrintRuntimeTypeInfo(taObj, objData);
+ }
+
+ if (_wcscmp(obj.GetTypeName(), W("System.RuntimeType+RuntimeTypeCache")) == 0)
+ {
+ // Get the method table
+ int iOffset = GetObjFieldOffset (taObj, objData.MethodTable, W("m_runtimeType"));
+ if (iOffset > 0)
+ {
+ TADDR rtPtr;
+ if (MOVE(rtPtr, taObj + iOffset) == S_OK)
+ {
+ DacpObjectData rtObjectData;
+ if ((Status=rtObjectData.Request(g_sos, TO_CDADDR(rtPtr))) != S_OK)
+ {
+ ExtOut("Error when reading RuntimeType field\n");
+ return Status;
+ }
+
+ PrintRuntimeTypeInfo(rtPtr, rtObjectData);
+ }
+ }
+ }
+
+ if (objData.ObjectType==OBJ_ARRAY)
+ {
+ ExtOut("Array: Rank %d, Number of elements %" POINTERSIZE_TYPE "d, Type %s",
+ objData.dwRank, (DWORD_PTR)objData.dwNumComponents, ElementTypeName(objData.ElementType));
+
+ IfDMLOut(" (<exec cmd=\"!DumpArray /d %p\">Print Array</exec>)", SOS_PTR(taObj));
+ ExtOut("\n");
+
+ if (objData.ElementType == ELEMENT_TYPE_I1 ||
+ objData.ElementType == ELEMENT_TYPE_U1 ||
+ objData.ElementType == ELEMENT_TYPE_CHAR)
+ {
+ bool wide = objData.ElementType == ELEMENT_TYPE_CHAR;
+
+ // Get the size of the character array, but clamp it to a reasonable length.
+ TADDR pos = taObj + (2 * sizeof(DWORD_PTR));
+ DWORD_PTR num;
+ moveN(num, taObj + sizeof(DWORD_PTR));
+
+ if (IsDMLEnabled())
+ DMLOut("<exec cmd=\"%s %x L%x\">Content</exec>: ", (wide) ? "dw" : "db", pos, num);
+ else
+ ExtOut("Content: ");
+ CharArrayContent(pos, (ULONG)(num <= 128 ? num : 128), wide);
+ ExtOut("\n");
+ }
+ }
+ else
+ {
+ FileNameForModule(TO_TADDR(mtabledata.Module), g_mdName);
+ ExtOut("File: %S\n", g_mdName[0] ? g_mdName : W("Unknown Module"));
+ }
+
+ if (objData.ObjectType == OBJ_STRING)
+ {
+ ExtOut("String: ");
+ StringObjectContent(taObj);
+ ExtOut("\n");
+ }
+ else if (objData.ObjectType == OBJ_OBJECT)
+ {
+ ExtOut("Object\n");
+ }
+
+ if (bPrintFields)
+ {
+ DacpMethodTableFieldData vMethodTableFields;
+ if ((Status = vMethodTableFields.Request(g_sos,TO_CDADDR(objData.MethodTable)))!=S_OK)
+ return Status;
+
+ ExtOut("Fields:\n");
+ if (vMethodTableFields.wNumInstanceFields + vMethodTableFields.wNumStaticFields > 0)
+ {
+ DisplayFields(objData.MethodTable, &mtabledata, &vMethodTableFields, taObj, TRUE, FALSE);
+ }
+ else
+ {
+ ExtOut("None\n");
+ }
+ }
+
+ sos::ThinLockInfo lockInfo;
+ if (obj.GetThinLock(lockInfo))
+ {
+ ExtOut("ThinLock owner %x (%p), Recursive %x\n", lockInfo.ThreadId,
+ SOS_PTR(lockInfo.ThreadPtr), lockInfo.Recursion);
+ }
+
+ return S_OK;
+}
+
+BOOL IndicesInRange (DWORD * indices, DWORD * lowerBounds, DWORD * bounds, DWORD rank)
+{
+ int i = 0;
+ if (!ClrSafeInt<int>::subtraction((int)rank, 1, i))
+ {
+ ExtOut("<integer underflow>\n");
+ return FALSE;
+ }
+
+ for (; i >= 0; i--)
+ {
+ if (indices[i] >= bounds[i] + lowerBounds[i])
+ {
+ if (i == 0)
+ {
+ return FALSE;
+ }
+
+ indices[i] = lowerBounds[i];
+ indices[i - 1]++;
+ }
+ }
+
+ return TRUE;
+}
+
+void ExtOutIndices (DWORD * indices, DWORD rank)
+{
+ for (DWORD i = 0; i < rank; i++)
+ {
+ ExtOut("[%d]", indices[i]);
+ }
+}
+
+size_t OffsetFromIndices (DWORD * indices, DWORD * lowerBounds, DWORD * bounds, DWORD rank)
+{
+ _ASSERTE(rank >= 0);
+ size_t multiplier = 1;
+ size_t offset = 0;
+ int i = 0;
+ if (!ClrSafeInt<int>::subtraction((int)rank, 1, i))
+ {
+ ExtOut("<integer underflow>\n");
+ return 0;
+ }
+
+ for (; i >= 0; i--)
+ {
+ DWORD curIndex = indices[i] - lowerBounds[i];
+ offset += curIndex * multiplier;
+ multiplier *= bounds[i];
+ }
+
+ return offset;
+}
+HRESULT PrintArray(DacpObjectData& objData, DumpArrayFlags& flags, BOOL isPermSetPrint);
+#ifdef _DEBUG
+HRESULT PrintPermissionSet (TADDR p_PermSet)
+{
+ HRESULT Status = S_OK;
+
+ DacpObjectData PermSetData;
+ if ((Status=PermSetData.Request(g_sos, TO_CDADDR(p_PermSet))) != S_OK)
+ {
+ ExtOut("Invalid object\n");
+ return Status;
+ }
+
+
+ sos::MethodTable mt = TO_TADDR(PermSetData.MethodTable);
+ if (_wcscmp (W("System.Security.PermissionSet"), mt.GetName()) != 0 && _wcscmp(W("System.Security.NamedPermissionSet"), mt.GetName()) != 0)
+ {
+ ExtOut("Invalid PermissionSet object\n");
+ return S_FALSE;
+ }
+
+ ExtOut("PermissionSet object: %p\n", SOS_PTR(p_PermSet));
+
+ // Print basic info
+
+ // Walk the fields, printing some fields in a special way.
+
+ int iOffset = GetObjFieldOffset (p_PermSet, PermSetData.MethodTable, W("m_Unrestricted"));
+
+ if (iOffset > 0)
+ {
+ BYTE unrestricted;
+ MOVE(unrestricted, p_PermSet + iOffset);
+ if (unrestricted)
+ ExtOut("Unrestricted: TRUE\n");
+ else
+ ExtOut("Unrestricted: FALSE\n");
+ }
+
+ iOffset = GetObjFieldOffset (p_PermSet, PermSetData.MethodTable, W("m_permSet"));
+ if (iOffset > 0)
+ {
+ TADDR tbSetPtr;
+ MOVE(tbSetPtr, p_PermSet + iOffset);
+ if (tbSetPtr != NULL)
+ {
+ DacpObjectData tbSetData;
+ if ((Status=tbSetData.Request(g_sos, TO_CDADDR(tbSetPtr))) != S_OK)
+ {
+ ExtOut("Invalid object\n");
+ return Status;
+ }
+
+ iOffset = GetObjFieldOffset (tbSetPtr, tbSetData.MethodTable, W("m_Set"));
+ if (iOffset > 0)
+ {
+ DWORD_PTR PermsArrayPtr;
+ MOVE(PermsArrayPtr, tbSetPtr + iOffset);
+ if (PermsArrayPtr != NULL)
+ {
+ // Print all the permissions in the array
+ DacpObjectData objData;
+ if ((Status=objData.Request(g_sos, TO_CDADDR(PermsArrayPtr))) != S_OK)
+ {
+ ExtOut("Invalid object\n");
+ return Status;
+ }
+ DumpArrayFlags flags;
+ flags.bDetail = TRUE;
+ return PrintArray(objData, flags, TRUE);
+ }
+ }
+
+ iOffset = GetObjFieldOffset (tbSetPtr, tbSetData.MethodTable, W("m_Obj"));
+ if (iOffset > 0)
+ {
+ DWORD_PTR PermObjPtr;
+ MOVE(PermObjPtr, tbSetPtr + iOffset);
+ if (PermObjPtr != NULL)
+ {
+ // Print the permission object
+ return PrintObj(PermObjPtr);
+ }
+ }
+
+
+ }
+ }
+ return Status;
+}
+
+#endif // _DEBUG
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to dump the contents of an object from a *
+* given address
+* *
+\**********************************************************************/
+DECLARE_API(DumpArray)
+{
+ INIT_API();
+
+ DumpArrayFlags flags;
+
+ MINIDUMP_NOT_SUPPORTED();
+
+ BOOL dml = FALSE;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"-start", &flags.startIndex, COSIZE_T, TRUE},
+ {"-length", &flags.Length, COSIZE_T, TRUE},
+ {"-details", &flags.bDetail, COBOOL, FALSE},
+ {"-nofields", &flags.bNoFieldsForElement, COBOOL, FALSE},
+#ifndef FEATURE_PAL
+ {"/d", &dml, COBOOL, FALSE},
+#endif
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&flags.strObject, COSTRING}
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+ DWORD_PTR p_Object = GetExpression (flags.strObject);
+ if (p_Object == 0)
+ {
+ ExtOut("Invalid parameter %s\n", flags.strObject);
+ return Status;
+ }
+
+ if (!sos::IsObject(p_Object, true))
+ {
+ ExtOut("<Note: this object has an invalid CLASS field>\n");
+ }
+
+ DacpObjectData objData;
+ if ((Status=objData.Request(g_sos, TO_CDADDR(p_Object))) != S_OK)
+ {
+ ExtOut("Invalid object\n");
+ return Status;
+ }
+
+ if (objData.ObjectType != OBJ_ARRAY)
+ {
+ ExtOut("Not an array, please use !DumpObj instead\n");
+ return S_OK;
+ }
+ return PrintArray(objData, flags, FALSE);
+}
+
+
+HRESULT PrintArray(DacpObjectData& objData, DumpArrayFlags& flags, BOOL isPermSetPrint)
+{
+ HRESULT Status = S_OK;
+
+ if (objData.dwRank != 1 && (flags.Length != (DWORD_PTR)-1 ||flags.startIndex != 0))
+ {
+ ExtOut("For multi-dimension array, length and start index are supported\n");
+ return S_OK;
+ }
+
+ if (flags.startIndex > objData.dwNumComponents)
+ {
+ ExtOut("Start index out of range\n");
+ return S_OK;
+ }
+
+ if (!flags.bDetail && flags.bNoFieldsForElement)
+ {
+ ExtOut("-nofields has no effect unless -details is specified\n");
+ }
+
+ DWORD i;
+ if (!isPermSetPrint)
+ {
+ // TODO: don't depend on this being a MethodTable
+ NameForMT_s(TO_TADDR(objData.ElementTypeHandle), g_mdName, mdNameLen);
+
+ ExtOut("Name: %S[", g_mdName);
+ for (i = 1; i < objData.dwRank; i++)
+ ExtOut(",");
+ ExtOut("]\n");
+
+ DMLOut("MethodTable: %s\n", DMLMethodTable(objData.MethodTable));
+
+ {
+ DacpMethodTableData mtdata;
+ if (SUCCEEDED(mtdata.Request(g_sos, objData.MethodTable)))
+ {
+ DMLOut("EEClass: %s\n", DMLClass(mtdata.Class));
+ }
+ }
+
+ DWORD_PTR size = (DWORD_PTR)objData.Size;
+ ExtOut("Size: %" POINTERSIZE_TYPE "d(0x%" POINTERSIZE_TYPE "x) bytes\n", size, size);
+
+ ExtOut("Array: Rank %d, Number of elements %" POINTERSIZE_TYPE "d, Type %s\n",
+ objData.dwRank, (DWORD_PTR)objData.dwNumComponents, ElementTypeName(objData.ElementType));
+ DMLOut("Element Methodtable: %s\n", DMLMethodTable(objData.ElementTypeHandle));
+ }
+
+ BOOL isElementValueType = IsElementValueType(objData.ElementType);
+
+ DWORD dwRankAllocSize;
+ if (!ClrSafeInt<DWORD>::multiply(sizeof(DWORD), objData.dwRank, dwRankAllocSize))
+ {
+ ExtOut("Integer overflow on array rank\n");
+ return Status;
+ }
+
+ DWORD *lowerBounds = (DWORD *)alloca(dwRankAllocSize);
+ if (!SafeReadMemory(objData.ArrayLowerBoundsPtr, lowerBounds, dwRankAllocSize, NULL))
+ {
+ ExtOut("Failed to read lower bounds info from the array\n");
+ return S_OK;
+ }
+
+ DWORD *bounds = (DWORD *)alloca(dwRankAllocSize);
+ if (!SafeReadMemory (objData.ArrayBoundsPtr, bounds, dwRankAllocSize, NULL))
+ {
+ ExtOut("Failed to read bounds info from the array\n");
+ return S_OK;
+ }
+
+ //length is only supported for single-dimension array
+ if (objData.dwRank == 1 && flags.Length != (DWORD_PTR)-1)
+ {
+ bounds[0] = _min(bounds[0], (DWORD)(flags.Length + flags.startIndex) - lowerBounds[0]);
+ }
+
+ DWORD *indices = (DWORD *)alloca(dwRankAllocSize);
+ for (i = 0; i < objData.dwRank; i++)
+ {
+ indices[i] = lowerBounds[i];
+ }
+
+ //start index is only supported for single-dimension array
+ if (objData.dwRank == 1)
+ {
+ indices[0] = (DWORD)flags.startIndex;
+ }
+
+ //Offset should be calculated by OffsetFromIndices. However because of the way
+ //how we grow indices, incrementing offset by one happens to match indices in every iteration
+ for (size_t offset = OffsetFromIndices (indices, lowerBounds, bounds, objData.dwRank);
+ IndicesInRange (indices, lowerBounds, bounds, objData.dwRank);
+ indices[objData.dwRank - 1]++, offset++)
+ {
+ if (IsInterrupt())
+ {
+ ExtOut("interrupted by user\n");
+ break;
+ }
+
+ TADDR elementAddress = TO_TADDR(objData.ArrayDataPtr + offset * objData.dwComponentSize);
+ TADDR p_Element = NULL;
+ if (isElementValueType)
+ {
+ p_Element = elementAddress;
+ }
+ else if (!SafeReadMemory (elementAddress, &p_Element, sizeof (p_Element), NULL))
+ {
+ ExtOut("Failed to read element at ");
+ ExtOutIndices(indices, objData.dwRank);
+ ExtOut("\n");
+ continue;
+ }
+
+ if (p_Element)
+ {
+ ExtOutIndices(indices, objData.dwRank);
+
+ if (isElementValueType)
+ {
+ DMLOut( " %s\n", DMLValueClass(objData.MethodTable, p_Element));
+ }
+ else
+ {
+ DMLOut(" %s\n", DMLObject(p_Element));
+ }
+ }
+ else if (!isPermSetPrint)
+ {
+ ExtOutIndices(indices, objData.dwRank);
+ ExtOut(" null\n");
+ }
+
+ if (flags.bDetail)
+ {
+ IncrementIndent();
+ if (isElementValueType)
+ {
+ PrintVC(TO_TADDR(objData.ElementTypeHandle), elementAddress, !flags.bNoFieldsForElement);
+ }
+ else if (p_Element != NULL)
+ {
+ PrintObj(p_Element, !flags.bNoFieldsForElement);
+ }
+ DecrementIndent();
+ }
+ }
+
+ return S_OK;
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to dump the contents of an object from a *
+* given address
+* *
+\**********************************************************************/
+DECLARE_API(DumpObj)
+{
+ INIT_API();
+
+ MINIDUMP_NOT_SUPPORTED();
+
+ BOOL dml = FALSE;
+ BOOL bNoFields = FALSE;
+ BOOL bRefs = FALSE;
+ StringHolder str_Object;
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"-nofields", &bNoFields, COBOOL, FALSE},
+ {"-refs", &bRefs, COBOOL, FALSE},
+#ifndef FEATURE_PAL
+ {"/d", &dml, COBOOL, FALSE},
+#endif
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&str_Object.data, COSTRING}
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ DWORD_PTR p_Object = GetExpression(str_Object.data);
+ EnableDMLHolder dmlHolder(dml);
+ if (p_Object == 0)
+ {
+ ExtOut("Invalid parameter %s\n", args);
+ return Status;
+ }
+
+ Status = PrintObj(p_Object, !bNoFields);
+
+ if (SUCCEEDED(Status) && bRefs)
+ {
+ ExtOut("GC Refs:\n");
+ TableOutput out(2, POINTERSIZE_HEX, AlignRight, 4);
+ out.WriteRow("offset", "object");
+ for (sos::RefIterator itr(TO_TADDR(p_Object)); itr; ++itr)
+ out.WriteRow(Hex(itr.GetOffset()), ObjectPtr(*itr));
+ }
+
+ return Status;
+}
+
+CLRDATA_ADDRESS isExceptionObj(CLRDATA_ADDRESS mtObj)
+{
+ // We want to follow back until we get the mt for System.Exception
+ DacpMethodTableData dmtd;
+ CLRDATA_ADDRESS walkMT = mtObj;
+ while(walkMT != NULL)
+ {
+ if (dmtd.Request(g_sos, walkMT) != S_OK)
+ {
+ break;
+ }
+ if (walkMT == g_special_usefulGlobals.ExceptionMethodTable)
+ {
+ return walkMT;
+ }
+ walkMT = dmtd.ParentMethodTable;
+ }
+ return NULL;
+}
+
+CLRDATA_ADDRESS isSecurityExceptionObj(CLRDATA_ADDRESS mtObj)
+{
+ // We want to follow back until we get the mt for System.Exception
+ DacpMethodTableData dmtd;
+ CLRDATA_ADDRESS walkMT = mtObj;
+ while(walkMT != NULL)
+ {
+ if (dmtd.Request(g_sos, walkMT) != S_OK)
+ {
+ break;
+ }
+ NameForMT_s(TO_TADDR(walkMT), g_mdName, mdNameLen);
+ if (_wcscmp(W("System.Security.SecurityException"), g_mdName) == 0)
+ {
+ return walkMT;
+ }
+ walkMT = dmtd.ParentMethodTable;
+ }
+ return NULL;
+}
+
+// Fill the passed in buffer with a text header for generated exception information.
+// Returns the number of characters in the wszBuffer array on exit.
+// If NULL is passed for wszBuffer, just returns the number of characters needed.
+size_t AddExceptionHeader (__out_ecount_opt(bufferLength) WCHAR *wszBuffer, size_t bufferLength)
+{
+#ifdef _TARGET_WIN64_
+ const WCHAR *wszHeader = W(" SP IP Function\n");
+#else
+ const WCHAR *wszHeader = W(" SP IP Function\n");
+#endif // _TARGET_WIN64_
+ if (wszBuffer)
+ {
+ swprintf_s(wszBuffer, bufferLength, wszHeader);
+ }
+ return _wcslen(wszHeader);
+}
+
+struct StackTraceElement
+{
+ UINT_PTR ip;
+ UINT_PTR sp;
+ DWORD_PTR pFunc; // MethodDesc
+#if defined(FEATURE_EXCEPTIONDISPATCHINFO)
+ // TRUE if this element represents the last frame of the foreign
+ // exception stack trace.
+ BOOL fIsLastFrameFromForeignStackTrace;
+#endif // defined(FEATURE_EXCEPTIONDISPATCHINFO)
+
+};
+
+#include "sos_stacktrace.h"
+
+class StringOutput
+{
+public:
+ CQuickString cs;
+ StringOutput()
+ {
+ cs.Alloc(1024);
+ cs.String()[0] = L'\0';
+ }
+
+ BOOL Append(__in_z LPCWSTR pszStr)
+ {
+ size_t iInputLen = _wcslen (pszStr);
+ size_t iCurLen = _wcslen (cs.String());
+ if ((iCurLen + iInputLen + 1) > cs.Size())
+ {
+ if (cs.ReSize(iCurLen + iInputLen + 1) != S_OK)
+ {
+ return FALSE;
+ }
+ }
+
+ wcsncat_s (cs.String(), cs.Size(), pszStr, _TRUNCATE);
+ return TRUE;
+ }
+
+ size_t Length()
+ {
+ return _wcslen(cs.String());
+ }
+
+ WCHAR *String()
+ {
+ return cs.String();
+ }
+};
+
+static HRESULT DumpMDInfoBuffer(DWORD_PTR dwStartAddr, DWORD Flags, ULONG64 Esp, ULONG64 IPAddr, StringOutput& so);
+
+// Using heuristics to determine if an exception object represented an async (hardware) or a
+// managed exception
+// We need to use these heuristics when the System.Exception object is not the active exception
+// on some thread, but it's something found somewhere on the managed heap.
+
+// uses the MapWin32FaultToCOMPlusException to figure out how we map async exceptions
+// to managed exceptions and their HRESULTs
+static const HRESULT AsyncHResultValues[] =
+{
+ COR_E_ARITHMETIC, // kArithmeticException
+ COR_E_OVERFLOW, // kOverflowException
+ COR_E_DIVIDEBYZERO, // kDivideByZeroException
+ COR_E_FORMAT, // kFormatException
+ COR_E_NULLREFERENCE, // kNullReferenceException
+ E_POINTER, // kAccessViolationException
+ // the EE is raising the next exceptions more often than the OS will raise an async
+ // exception for these conditions, so in general treat these as Synchronous
+ // COR_E_INDEXOUTOFRANGE, // kIndexOutOfRangeException
+ // COR_E_OUTOFMEMORY, // kOutOfMemoryException
+ // COR_E_STACKOVERFLOW, // kStackOverflowException
+ COR_E_DATAMISALIGNED, // kDataMisalignedException
+
+};
+BOOL IsAsyncException(TADDR taObj, TADDR mtObj)
+{
+ // by default we'll treat exceptions as synchronous
+ UINT32 xcode = EXCEPTION_COMPLUS;
+ int iOffset = GetObjFieldOffset (taObj, mtObj, W("_xcode"));
+ if (iOffset > 0)
+ {
+ HRESULT hr = MOVE(xcode, taObj + iOffset);
+ if (hr != S_OK)
+ {
+ xcode = EXCEPTION_COMPLUS;
+ goto Done;
+ }
+ }
+
+ if (xcode == EXCEPTION_COMPLUS)
+ {
+ HRESULT ehr = 0;
+ iOffset = GetObjFieldOffset (taObj, mtObj, W("_HResult"));
+ if (iOffset > 0)
+ {
+ HRESULT hr = MOVE(ehr, taObj + iOffset);
+ if (hr != S_OK)
+ {
+ xcode = EXCEPTION_COMPLUS;
+ goto Done;
+ }
+ for (size_t idx = 0; idx < _countof(AsyncHResultValues); ++idx)
+ {
+ if (ehr == AsyncHResultValues[idx])
+ {
+ xcode = ehr;
+ break;
+ }
+ }
+ }
+ }
+Done:
+ return xcode != EXCEPTION_COMPLUS;
+}
+
+// Overload that mirrors the code above when the ExceptionObjectData was already retrieved from LS
+BOOL IsAsyncException(const DacpExceptionObjectData & excData)
+{
+ if (excData.XCode != EXCEPTION_COMPLUS)
+ return TRUE;
+
+ HRESULT ehr = excData.HResult;
+ for (size_t idx = 0; idx < _countof(AsyncHResultValues); ++idx)
+ {
+ if (ehr == AsyncHResultValues[idx])
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+
+#define SOS_STACKTRACE_SHOWEXPLICITFRAMES 0x00000002
+size_t FormatGeneratedException (DWORD_PTR dataPtr,
+ UINT bytes,
+ __out_ecount_opt(bufferLength) WCHAR *wszBuffer,
+ size_t bufferLength,
+ BOOL bAsync,
+ BOOL bNestedCase = FALSE,
+ BOOL bLineNumbers = FALSE)
+{
+ UINT count = bytes / sizeof(StackTraceElement);
+ size_t Length = 0;
+
+ if (wszBuffer && bufferLength > 0)
+ {
+ wszBuffer[0] = L'\0';
+ }
+
+ // Buffer is calculated for sprintf below (" %p %p %S\n");
+ WCHAR wszLineBuffer[mdNameLen + 8 + sizeof(size_t)*2 + MAX_LONGPATH + 8];
+
+ if (count == 0)
+ {
+ return 0;
+ }
+
+ if (bNestedCase)
+ {
+ // If we are computing the call stack for a nested exception, we
+ // don't want to print the last frame, because the outer exception
+ // will have that frame.
+ count--;
+ }
+
+ for (UINT i = 0; i < count; i++)
+ {
+ StackTraceElement ste;
+ MOVE (ste, dataPtr + i*sizeof(StackTraceElement));
+
+ // ste.ip must be adjusted because of an ancient workaround in the exception
+ // infrastructure. The workaround is that the exception needs to have
+ // an ip address that will map to the line number where the exception was thrown.
+ // (It doesn't matter that it's not a valid instruction). (see /vm/excep.cpp)
+ //
+ // This "counterhack" is not 100% accurate
+ // The biggest issue is that !PrintException must work with exception objects
+ // that may not be currently active; as a consequence we cannot rely on the
+ // state of some "current thread" to infer whether the IP values stored in
+ // the exception object have been adjusted or not. If we could, we may examine
+ // the topmost "Frame" and make the decision based on whether it's a
+ // FaultingExceptionFrame or not.
+ // 1. On IA64 the IP values are never adjusted by the EE so there's nothing
+ // to adjust back.
+ // 2. On AMD64:
+ // (a) if the exception was an async (hardware) exception add 1 to all
+ // IP values in the exception object
+ // (b) if the exception was a managed exception (either raised by the
+ // EE or thrown by managed code) do not adjust any IP values
+ // 3. On X86:
+ // (a) if the exception was an async (hardware) exception add 1 to
+ // all but the topmost IP value in the exception object
+ // (b) if the exception was a managed exception (either raised by
+ // the EE or thrown by managed code) add 1 to all IP values in
+ // the exception object
+#if defined(_TARGET_AMD64_)
+ if (bAsync)
+ {
+ ste.ip += 1;
+ }
+#elif defined(_TARGET_X86_)
+ if (IsDbgTargetX86() && (!bAsync || i != 0))
+ {
+ ste.ip += 1;
+ }
+#endif // defined(_TARGET_AMD64_) || defined(_TARGET__X86_)
+
+ StringOutput so;
+ HRESULT Status = DumpMDInfoBuffer(ste.pFunc, SOS_STACKTRACE_SHOWADDRESSES|SOS_STACKTRACE_SHOWEXPLICITFRAMES, ste.sp, ste.ip, so);
+
+ // If DumpMDInfoBuffer failed (due to out of memory or missing metadata),
+ // or did not update so (when ste is an explicit frames), do not update wszBuffer
+ if (Status == S_OK)
+ {
+ WCHAR filename[MAX_LONGPATH] = W("");
+ ULONG linenum = 0;
+ if (bLineNumbers &&
+ SUCCEEDED(GetLineByOffset(TO_CDADDR(ste.ip), &linenum, filename, _countof(filename))))
+ {
+ swprintf_s(wszLineBuffer, _countof(wszLineBuffer), W(" %s [%s @ %d]\n"), so.String(), filename, linenum);
+ }
+ else
+ {
+ swprintf_s(wszLineBuffer, _countof(wszLineBuffer), W(" %s\n"), so.String());
+ }
+
+ Length += _wcslen(wszLineBuffer);
+
+ if (wszBuffer)
+ {
+ wcsncat_s(wszBuffer, bufferLength, wszLineBuffer, _TRUNCATE);
+ }
+ }
+ }
+
+ return Length;
+}
+
+// ExtOut has an internal limit for the string size
+void SosExtOutLargeString(__inout_z __inout_ecount_opt(len) WCHAR * pwszLargeString, size_t len)
+{
+ const size_t chunkLen = 2048;
+
+ WCHAR *pwsz = pwszLargeString; // beginning of a chunk
+ size_t count = len/chunkLen;
+ // write full chunks
+ for (size_t idx = 0; idx < count; ++idx)
+ {
+ WCHAR *pch = pwsz + chunkLen; // after the chunk
+ // zero terminate the chunk
+ WCHAR ch = *pch;
+ *pch = L'\0';
+
+ ExtOut("%S", pwsz);
+
+ // restore whacked char
+ *pch = ch;
+
+ // advance to next chunk
+ pwsz += chunkLen;
+ }
+
+ // last chunk
+ ExtOut("%S", pwsz);
+}
+
+HRESULT FormatException(TADDR taObj, BOOL bLineNumbers = FALSE)
+{
+ HRESULT Status = S_OK;
+
+ DacpObjectData objData;
+ if ((Status=objData.Request(g_sos, TO_CDADDR(taObj))) != S_OK)
+ {
+ ExtOut("Invalid object\n");
+ return Status;
+ }
+
+ // Make sure it is an exception object, and get the MT of Exception
+ CLRDATA_ADDRESS exceptionMT = isExceptionObj(objData.MethodTable);
+ if (exceptionMT == NULL)
+ {
+ ExtOut("Not a valid exception object\n");
+ return Status;
+ }
+
+ DMLOut("Exception object: %s\n", DMLObject(taObj));
+
+ if (NameForMT_s(TO_TADDR(objData.MethodTable), g_mdName, mdNameLen))
+ {
+ ExtOut("Exception type: %S\n", g_mdName);
+ }
+ else
+ {
+ ExtOut("Exception type: <Unknown>\n");
+ }
+
+ // Print basic info
+
+ // First try to get exception object data using ISOSDacInterface2
+ DacpExceptionObjectData excData;
+ BOOL bGotExcData = SUCCEEDED(excData.Request(g_sos, TO_CDADDR(taObj)));
+
+ // Walk the fields, printing some fields in a special way.
+ // HR, InnerException, Message, StackTrace, StackTraceString
+
+ {
+ TADDR taMsg = 0;
+ if (bGotExcData)
+ {
+ taMsg = TO_TADDR(excData.Message);
+ }
+ else
+ {
+ int iOffset = GetObjFieldOffset(taObj, objData.MethodTable, W("_message"));
+ if (iOffset > 0)
+ {
+ MOVE (taMsg, taObj + iOffset);
+ }
+ }
+
+ ExtOut("Message: ");
+
+ if (taMsg)
+ StringObjectContent(taMsg);
+ else
+ ExtOut("<none>");
+
+ ExtOut("\n");
+ }
+
+ {
+ TADDR taInnerExc = 0;
+ if (bGotExcData)
+ {
+ taInnerExc = TO_TADDR(excData.InnerException);
+ }
+ else
+ {
+ int iOffset = GetObjFieldOffset(taObj, objData.MethodTable, W("_innerException"));
+ if (iOffset > 0)
+ {
+ MOVE (taInnerExc, taObj + iOffset);
+ }
+ }
+
+ ExtOut("InnerException: ");
+ if (taInnerExc)
+ {
+ TADDR taMT;
+ if (SUCCEEDED(GetMTOfObject(taInnerExc, &taMT)))
+ {
+ NameForMT_s(taMT, g_mdName, mdNameLen);
+ ExtOut("%S, ", g_mdName);
+ if (IsDMLEnabled())
+ DMLOut("Use <exec cmd=\"!PrintException /d %p\">!PrintException %p</exec> to see more.\n", taInnerExc, taInnerExc);
+ else
+ ExtOut("Use !PrintException %p to see more.\n", SOS_PTR(taInnerExc));
+ }
+ else
+ {
+ ExtOut("<invalid MethodTable of inner exception>");
+ }
+ }
+ else
+ {
+ ExtOut("<none>\n");
+ }
+ }
+
+ BOOL bAsync = bGotExcData ? IsAsyncException(excData)
+ : IsAsyncException(taObj, TO_TADDR(objData.MethodTable));
+
+ {
+ TADDR taStackTrace = 0;
+ if (bGotExcData)
+ {
+ taStackTrace = TO_TADDR(excData.StackTrace);
+ }
+ else
+ {
+ int iOffset = GetObjFieldOffset (taObj, objData.MethodTable, W("_stackTrace"));
+ if (iOffset > 0)
+ {
+ MOVE(taStackTrace, taObj + iOffset);
+ }
+ }
+
+ ExtOut("StackTrace (generated):\n");
+ if (taStackTrace)
+ {
+ DWORD arrayLen;
+ HRESULT hr = MOVE(arrayLen, taStackTrace + sizeof(DWORD_PTR));
+
+ if (arrayLen != 0 && hr == S_OK)
+ {
+#ifdef _TARGET_WIN64_
+ DWORD_PTR dataPtr = taStackTrace + sizeof(DWORD_PTR) + sizeof(DWORD) + sizeof(DWORD);
+#else
+ DWORD_PTR dataPtr = taStackTrace + sizeof(DWORD_PTR) + sizeof(DWORD);
+#endif // _TARGET_WIN64_
+ size_t stackTraceSize = 0;
+ MOVE (stackTraceSize, dataPtr);
+
+ DWORD cbStackSize = static_cast<DWORD>(stackTraceSize * sizeof(StackTraceElement));
+ dataPtr += sizeof(size_t) + sizeof(size_t); // skip the array header, then goes the data
+
+ if (stackTraceSize == 0)
+ {
+ ExtOut("Unable to decipher generated stack trace\n");
+ }
+ else
+ {
+ size_t iHeaderLength = AddExceptionHeader (NULL, 0);
+ size_t iLength = FormatGeneratedException (dataPtr, cbStackSize, NULL, 0, bAsync, FALSE, bLineNumbers);
+ WCHAR *pwszBuffer = new NOTHROW WCHAR[iHeaderLength + iLength + 1];
+ if (pwszBuffer)
+ {
+ AddExceptionHeader(pwszBuffer, iHeaderLength + 1);
+ FormatGeneratedException(dataPtr, cbStackSize, pwszBuffer + iHeaderLength, iLength + 1, bAsync, FALSE, bLineNumbers);
+ SosExtOutLargeString(pwszBuffer, iHeaderLength + iLength + 1);
+ delete[] pwszBuffer;
+ }
+ ExtOut("\n");
+ }
+ }
+ else
+ {
+ ExtOut("<Not Available>\n");
+ }
+ }
+ else
+ {
+ ExtOut("<none>\n");
+ }
+ }
+
+ {
+ TADDR taStackString;
+ if (bGotExcData)
+ {
+ taStackString = TO_TADDR(excData.StackTraceString);
+ }
+ else
+ {
+ int iOffset = GetObjFieldOffset (taObj, objData.MethodTable, W("_stackTraceString"));
+ MOVE (taStackString, taObj + iOffset);
+ }
+
+ ExtOut("StackTraceString: ");
+ if (taStackString)
+ {
+ StringObjectContent(taStackString);
+ ExtOut("\n\n"); // extra newline looks better
+ }
+ else
+ {
+ ExtOut("<none>\n");
+ }
+ }
+
+ {
+ DWORD hResult;
+ if (bGotExcData)
+ {
+ hResult = excData.HResult;
+ }
+ else
+ {
+ int iOffset = GetObjFieldOffset (taObj, objData.MethodTable, W("_HResult"));
+ MOVE (hResult, taObj + iOffset);
+ }
+
+ ExtOut("HResult: %lx\n", hResult);
+ }
+
+ if (isSecurityExceptionObj(objData.MethodTable) != NULL)
+ {
+ // We have a SecurityException Object: print out the debugString if present
+ int iOffset = GetObjFieldOffset (taObj, objData.MethodTable, W("m_debugString"));
+ if (iOffset > 0)
+ {
+ TADDR taDebugString;
+ MOVE (taDebugString, taObj + iOffset);
+
+ if (taDebugString)
+ {
+ ExtOut("SecurityException Message: ");
+ StringObjectContent(taDebugString);
+ ExtOut("\n\n"); // extra newline looks better
+ }
+ }
+ }
+
+ return Status;
+}
+
+DECLARE_API(PrintException)
+{
+ INIT_API();
+
+ BOOL dml = FALSE;
+ BOOL bShowNested = FALSE;
+ BOOL bLineNumbers = FALSE;
+ BOOL bCCW = FALSE;
+ StringHolder strObject;
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"-nested", &bShowNested, COBOOL, FALSE},
+ {"-lines", &bLineNumbers, COBOOL, FALSE},
+ {"-l", &bLineNumbers, COBOOL, FALSE},
+ {"-ccw", &bCCW, COBOOL, FALSE},
+#ifndef FEATURE_PAL
+ {"/d", &dml, COBOOL, FALSE}
+#endif
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&strObject, COSTRING}
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+ if (bLineNumbers)
+ {
+ ULONG symlines = 0;
+ if (SUCCEEDED(g_ExtSymbols->GetSymbolOptions(&symlines)))
+ {
+ symlines &= SYMOPT_LOAD_LINES;
+ }
+ if (symlines == 0)
+ {
+ ExtOut("In order for the option -lines to enable display of source information\n"
+ "the debugger must be configured to load the line number information from\n"
+ "the symbol files. Use the \".lines; .reload\" command to achieve this.\n");
+ // don't even try
+ bLineNumbers = FALSE;
+ }
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+ DWORD_PTR p_Object = NULL;
+ if (nArg == 0)
+ {
+ if (bCCW)
+ {
+ ExtOut("No CCW pointer specified\n");
+ return Status;
+ }
+
+ // Look at the last exception object on this thread
+
+ CLRDATA_ADDRESS threadAddr = GetCurrentManagedThread();
+ DacpThreadData Thread;
+
+ if ((threadAddr == NULL) || (Thread.Request(g_sos, threadAddr) != S_OK))
+ {
+ ExtOut("The current thread is unmanaged\n");
+ return Status;
+ }
+
+ DWORD_PTR dwAddr = NULL;
+ if ((!SafeReadMemory(TO_TADDR(Thread.lastThrownObjectHandle),
+ &dwAddr,
+ sizeof(dwAddr), NULL)) || (dwAddr==NULL))
+ {
+ ExtOut("There is no current managed exception on this thread\n");
+ }
+ else
+ {
+ p_Object = dwAddr;
+ }
+ }
+ else
+ {
+ p_Object = GetExpression(strObject.data);
+ if (p_Object == 0)
+ {
+ if (bCCW)
+ {
+ ExtOut("Invalid CCW pointer %s\n", args);
+ }
+ else
+ {
+ ExtOut("Invalid exception object %s\n", args);
+ }
+ return Status;
+ }
+
+ if (bCCW)
+ {
+ // check if the address is a CCW pointer and then
+ // get the exception object from it
+ DacpCCWData ccwData;
+ if (ccwData.Request(g_sos, p_Object) == S_OK)
+ {
+ p_Object = TO_TADDR(ccwData.managedObject);
+ }
+ }
+ }
+
+ if (p_Object)
+ {
+ FormatException(p_Object, bLineNumbers);
+ }
+
+ // Are there nested exceptions?
+ CLRDATA_ADDRESS threadAddr = GetCurrentManagedThread();
+ DacpThreadData Thread;
+
+ if ((threadAddr == NULL) || (Thread.Request(g_sos, threadAddr) != S_OK))
+ {
+ ExtOut("The current thread is unmanaged\n");
+ return Status;
+ }
+
+ if (Thread.firstNestedException)
+ {
+ if (!bShowNested)
+ {
+ ExtOut("There are nested exceptions on this thread. Run with -nested for details\n");
+ return Status;
+ }
+
+ CLRDATA_ADDRESS currentNested = Thread.firstNestedException;
+ do
+ {
+ CLRDATA_ADDRESS obj = 0, next = 0;
+ Status = g_sos->GetNestedExceptionData(currentNested, &obj, &next);
+
+ if (Status != S_OK)
+ {
+ ExtOut("Error retrieving nested exception info %p\n", SOS_PTR(currentNested));
+ return Status;
+ }
+
+ if (IsInterrupt())
+ {
+ ExtOut("<aborted>\n");
+ return Status;
+ }
+
+ ExtOut("\nNested exception -------------------------------------------------------------\n");
+ Status = FormatException((DWORD_PTR) obj, bLineNumbers);
+ if (Status != S_OK)
+ {
+ return Status;
+ }
+
+ currentNested = next;
+ }
+ while(currentNested != NULL);
+ }
+ return Status;
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to dump the contents of an object from a *
+* given address
+* *
+\**********************************************************************/
+DECLARE_API(DumpVC)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ DWORD_PTR p_MT = NULL;
+ DWORD_PTR p_Object = NULL;
+ BOOL dml = FALSE;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+#ifndef FEATURE_PAL
+ {"/d", &dml, COBOOL, FALSE}
+#endif
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&p_MT, COHEX},
+ {&p_Object, COHEX},
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+ if (nArg!=2)
+ {
+ ExtOut("Usage: !DumpVC <Method Table> <Value object start addr>\n");
+ return Status;
+ }
+
+ if (!IsMethodTable(p_MT))
+ {
+ ExtOut("Not a managed object\n");
+ return S_OK;
+ }
+
+ return PrintVC(p_MT, p_Object);
+}
+
+#ifndef FEATURE_PAL
+
+#ifdef FEATURE_COMINTEROP
+
+DECLARE_API(DumpRCW)
+{
+ INIT_API();
+
+ BOOL dml = FALSE;
+ StringHolder strObject;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"/d", &dml, COBOOL, FALSE}
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&strObject, COSTRING}
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+ if (nArg == 0)
+ {
+ ExtOut("Missing RCW address\n");
+ return Status;
+ }
+ else
+ {
+ DWORD_PTR p_RCW = GetExpression(strObject.data);
+ if (p_RCW == 0)
+ {
+ ExtOut("Invalid RCW %s\n", args);
+ }
+ else
+ {
+ DacpRCWData rcwData;
+ if ((Status = rcwData.Request(g_sos, p_RCW)) != S_OK)
+ {
+ ExtOut("Error requesting RCW data\n");
+ return Status;
+ }
+ BOOL isDCOMProxy;
+ if (FAILED(rcwData.IsDCOMProxy(g_sos, p_RCW, &isDCOMProxy)))
+ {
+ isDCOMProxy = FALSE;
+ }
+
+ DMLOut("Managed object: %s\n", DMLObject(rcwData.managedObject));
+ DMLOut("Creating thread: %p\n", SOS_PTR(rcwData.creatorThread));
+ ExtOut("IUnknown pointer: %p\n", SOS_PTR(rcwData.unknownPointer));
+ ExtOut("COM Context: %p\n", SOS_PTR(rcwData.ctxCookie));
+ ExtOut("Managed ref count: %d\n", rcwData.refCount);
+ ExtOut("IUnknown V-table pointer : %p (captured at RCW creation time)\n", SOS_PTR(rcwData.vtablePtr));
+
+ ExtOut("Flags: %s%s%s%s%s%s%s%s\n",
+ (rcwData.isDisconnected? "IsDisconnected " : ""),
+ (rcwData.supportsIInspectable? "SupportsIInspectable " : ""),
+ (rcwData.isAggregated? "IsAggregated " : ""),
+ (rcwData.isContained? "IsContained " : ""),
+ (rcwData.isJupiterObject? "IsJupiterObject " : ""),
+ (rcwData.isFreeThreaded? "IsFreeThreaded " : ""),
+ (rcwData.identityPointer == TO_CDADDR(p_RCW)? "IsUnique " : ""),
+ (isDCOMProxy ? "IsDCOMProxy " : "")
+ );
+
+ // Jupiter data hidden by default
+ if (rcwData.isJupiterObject)
+ {
+ ExtOut("IJupiterObject: %p\n", SOS_PTR(rcwData.jupiterObject));
+ }
+
+ ExtOut("COM interface pointers:\n");
+
+ ArrayHolder<DacpCOMInterfacePointerData> pArray = new NOTHROW DacpCOMInterfacePointerData[rcwData.interfaceCount];
+ if (pArray == NULL)
+ {
+ ReportOOM();
+ return Status;
+ }
+
+ if ((Status = g_sos->GetRCWInterfaces(p_RCW, rcwData.interfaceCount, pArray, NULL)) != S_OK)
+ {
+ ExtOut("Error requesting COM interface pointers\n");
+ return Status;
+ }
+
+ ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s Type\n", "IP", "Context", "MT");
+ for (int i = 0; i < rcwData.interfaceCount; i++)
+ {
+ // Ignore any NULL MethodTable interface cache. At this point only IJupiterObject
+ // is saved as NULL MethodTable at first slot, and we've already printed outs its
+ // value earlier.
+ if (pArray[i].methodTable == NULL)
+ continue;
+
+ NameForMT_s(TO_TADDR(pArray[i].methodTable), g_mdName, mdNameLen);
+
+ DMLOut("%p %p %s %S\n", SOS_PTR(pArray[i].interfacePtr), SOS_PTR(pArray[i].comContext), DMLMethodTable(pArray[i].methodTable), g_mdName);
+ }
+ }
+ }
+
+ return Status;
+}
+
+DECLARE_API(DumpCCW)
+{
+ INIT_API();
+
+ BOOL dml = FALSE;
+ StringHolder strObject;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"/d", &dml, COBOOL, FALSE}
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&strObject, COSTRING}
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+ if (nArg == 0)
+ {
+ ExtOut("Missing CCW address\n");
+ return Status;
+ }
+ else
+ {
+ DWORD_PTR p_CCW = GetExpression(strObject.data);
+ if (p_CCW == 0)
+ {
+ ExtOut("Invalid CCW %s\n", args);
+ }
+ else
+ {
+ DacpCCWData ccwData;
+ if ((Status = ccwData.Request(g_sos, p_CCW)) != S_OK)
+ {
+ ExtOut("Error requesting CCW data\n");
+ return Status;
+ }
+
+ if (ccwData.ccwAddress != p_CCW)
+ ExtOut("CCW: %p\n", SOS_PTR(ccwData.ccwAddress));
+
+ DMLOut("Managed object: %s\n", DMLObject(ccwData.managedObject));
+ ExtOut("Outer IUnknown: %p\n", SOS_PTR(ccwData.outerIUnknown));
+ ExtOut("Ref count: %d%s\n", ccwData.refCount, ccwData.isNeutered ? " (NEUTERED)" : "");
+ ExtOut("Flags: %s%s\n",
+ (ccwData.isExtendsCOMObject? "IsExtendsCOMObject " : ""),
+ (ccwData.isAggregated? "IsAggregated " : "")
+ );
+
+ // Jupiter information hidden by default
+ if (ccwData.jupiterRefCount > 0)
+ {
+ ExtOut("Jupiter ref count: %d%s%s%s%s\n",
+ ccwData.jupiterRefCount,
+ (ccwData.isPegged || ccwData.isGlobalPegged) ? ", Pegged by" : "",
+ ccwData.isPegged ? " Jupiter " : "",
+ (ccwData.isPegged && ccwData.isGlobalPegged) ? "&" : "",
+ ccwData.isGlobalPegged ? " CLR " : ""
+ );
+ }
+
+ ExtOut("RefCounted Handle: %p%s\n",
+ SOS_PTR(ccwData.handle),
+ (ccwData.hasStrongRef ? " (STRONG)" : " (WEAK)"));
+
+ ExtOut("COM interface pointers:\n");
+
+ ArrayHolder<DacpCOMInterfacePointerData> pArray = new NOTHROW DacpCOMInterfacePointerData[ccwData.interfaceCount];
+ if (pArray == NULL)
+ {
+ ReportOOM();
+ return Status;
+ }
+
+ if ((Status = g_sos->GetCCWInterfaces(p_CCW, ccwData.interfaceCount, pArray, NULL)) != S_OK)
+ {
+ ExtOut("Error requesting COM interface pointers\n");
+ return Status;
+ }
+
+ ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s Type\n", "IP", "MT", "Type");
+ for (int i = 0; i < ccwData.interfaceCount; i++)
+ {
+ if (pArray[i].methodTable == NULL)
+ {
+ wcscpy_s(g_mdName, mdNameLen, W("IDispatch/IUnknown"));
+ }
+ else
+ {
+ NameForMT_s(TO_TADDR(pArray[i].methodTable), g_mdName, mdNameLen);
+ }
+
+ DMLOut("%p %s %S\n", pArray[i].interfacePtr, DMLMethodTable(pArray[i].methodTable), g_mdName);
+ }
+ }
+ }
+
+ return Status;
+}
+
+#endif // FEATURE_COMINTEROP
+
+#ifdef _DEBUG
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to dump the contents of a PermissionSet *
+* from a given address. *
+* *
+\**********************************************************************/
+/*
+ COMMAND: dumppermissionset.
+ !DumpPermissionSet <PermissionSet object address>
+
+ This command allows you to examine a PermissionSet object. Note that you can
+ also use DumpObj such an object in greater detail. DumpPermissionSet attempts
+ to extract all the relevant information from a PermissionSet that you might be
+ interested in when performing Code Access Security (CAS) related debugging.
+
+ Here is a simple PermissionSet object:
+
+ 0:000> !DumpPermissionSet 014615f4
+ PermissionSet object: 014615f4
+ Unrestricted: TRUE
+
+ Note that this is an unrestricted PermissionSet object that does not contain
+ any individual permissions.
+
+ Here is another example of a PermissionSet object, one that is not unrestricted
+ and contains a single permission:
+
+ 0:003> !DumpPermissionSet 01469fa8
+ PermissionSet object: 01469fa8
+ Unrestricted: FALSE
+ Name: System.Security.Permissions.ReflectionPermission
+ MethodTable: 5b731308
+ EEClass: 5b7e0d78
+ Size: 12(0xc) bytes
+ (C:\WINDOWS\Microsoft.NET\Framework\v2.0.x86chk\assembly\GAC_32\mscorlib\2.0.
+ 0.0__b77a5c561934e089\mscorlib.dll)
+
+ Fields:
+ MT Field Offset Type VT Attr Value Name
+ 5b73125c 4001d66 4 System.Int32 0 instance 2 m_flags
+
+ Here is another example of an unrestricted PermissionSet, one that contains
+ several permissions. The numbers in parentheses before each Permission object
+ represents the index of that Permission in the PermissionSet.
+
+ 0:003> !DumpPermissionSet 01467bd8
+ PermissionSet object: 01467bd8
+ Unrestricted: FALSE
+ [1] 01467e90
+ Name: System.Security.Permissions.FileDialogPermission
+ MethodTable: 5b73023c
+ EEClass: 5b7dfb18
+ Size: 12(0xc) bytes
+ (C:\WINDOWS\Microsoft.NET\Framework\v2.0.x86chk\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
+ Fields:
+ MT Field Offset Type VT Attr Value Name
+ 5b730190 4001cc2 4 System.Int32 0 instance 1 access
+ [4] 014682a8
+ Name: System.Security.Permissions.ReflectionPermission
+ MethodTable: 5b731308
+ EEClass: 5b7e0d78
+ Size: 12(0xc) bytes
+ (C:\WINDOWS\Microsoft.NET\Framework\v2.0.x86chk\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
+ Fields:
+ MT Field Offset Type VT Attr Value Name
+ 5b73125c 4001d66 4 System.Int32 0 instance 0 m_flags
+ [17] 0146c060
+ Name: System.Diagnostics.EventLogPermission
+ MethodTable: 569841c4
+ EEClass: 56a03e5c
+ Size: 28(0x1c) bytes
+ (C:\WINDOWS\Microsoft.NET\Framework\v2.0.x86chk\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll)
+ Fields:
+ MT Field Offset Type VT Attr Value Name
+ 5b6d65d4 4003078 4 System.Object[] 0 instance 0146c190 tagNames
+ 5b6c9ed8 4003079 8 System.Type 0 instance 0146c17c permissionAccessType
+ 5b6cd928 400307a 10 System.Boolean 0 instance 0 isUnrestricted
+ 5b6c45f8 400307b c ...ections.Hashtable 0 instance 0146c1a4 rootTable
+ 5b6c090c 4003077 bfc System.String 0 static 00000000 computerName
+ 56984434 40030e7 14 ...onEntryCollection 0 instance 00000000 innerCollection
+ [18] 0146ceb4
+ Name: System.Net.WebPermission
+ MethodTable: 5696dfc4
+ EEClass: 569e256c
+ Size: 20(0x14) bytes
+ (C:\WINDOWS\Microsoft.NET\Framework\v2.0.x86chk\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll)
+ Fields:
+ MT Field Offset Type VT Attr Value Name
+ 5b6cd928 400238e c System.Boolean 0 instance 0 m_Unrestricted
+ 5b6cd928 400238f d System.Boolean 0 instance 0 m_UnrestrictedConnect
+ 5b6cd928 4002390 e System.Boolean 0 instance 0 m_UnrestrictedAccept
+ 5b6c639c 4002391 4 ...ections.ArrayList 0 instance 0146cf3c m_connectList
+ 5b6c639c 4002392 8 ...ections.ArrayList 0 instance 0146cf54 m_acceptList
+ 569476f8 4002393 8a4 ...Expressions.Regex 0 static 00000000 s_MatchAllRegex
+ [19] 0146a5fc
+ Name: System.Net.DnsPermission
+ MethodTable: 56966408
+ EEClass: 569d3c08
+ Size: 12(0xc) bytes
+ (C:\WINDOWS\Microsoft.NET\Framework\v2.0.x86chk\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll)
+ Fields:
+ MT Field Offset Type VT Attr Value Name
+ 5b6cd928 4001d2c 4 System.Boolean 0 instance 1 m_noRestriction
+ [20] 0146d8ec
+ Name: System.Web.AspNetHostingPermission
+ MethodTable: 569831bc
+ EEClass: 56a02ccc
+ Size: 12(0xc) bytes
+ (C:\WINDOWS\Microsoft.NET\Framework\v2.0.x86chk\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll)
+ Fields:
+ MT Field Offset Type VT Attr Value Name
+ 56983090 4003074 4 System.Int32 0 instance 600 _level
+ [21] 0146e394
+ Name: System.Net.NetworkInformation.NetworkInformationPermission
+ MethodTable: 5697ac70
+ EEClass: 569f7104
+ Size: 16(0x10) bytes
+ (C:\WINDOWS\Microsoft.NET\Framework\v2.0.x86chk\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll)
+ Fields:
+ MT Field Offset Type VT Attr Value Name
+ 5697ab38 4002c34 4 System.Int32 0 instance 0 access
+ 5b6cd928 4002c35 8 System.Boolean 0 instance 0 unrestricted
+
+
+ The abbreviation !dps can be used for brevity.
+
+ \\
+*/
+DECLARE_API(DumpPermissionSet)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ DWORD_PTR p_Object = NULL;
+
+ CMDValue arg[] =
+ {
+ {&p_Object, COHEX}
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+ if (nArg!=1)
+ {
+ ExtOut("Usage: !DumpPermissionSet <PermissionSet object addr>\n");
+ return Status;
+ }
+
+
+ return PrintPermissionSet(p_Object);
+}
+
+#endif // _DEBUG
+
+void GCPrintGenerationInfo(DacpGcHeapDetails &heap);
+void GCPrintSegmentInfo(DacpGcHeapDetails &heap, DWORD_PTR &total_size);
+
+#endif // FEATURE_PAL
+
+void DisplayInvalidStructuresMessage()
+{
+ ExtOut("The garbage collector data structures are not in a valid state for traversal.\n");
+ ExtOut("It is either in the \"plan phase,\" where objects are being moved around, or\n");
+ ExtOut("we are at the initialization or shutdown of the gc heap. Commands related to \n");
+ ExtOut("displaying, finding or traversing objects as well as gc heap segments may not \n");
+ ExtOut("work properly. !dumpheap and !verifyheap may incorrectly complain of heap \n");
+ ExtOut("consistency errors.\n");
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function dumps GC heap size. *
+* *
+\**********************************************************************/
+DECLARE_API(EEHeap)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ BOOL dml = FALSE;
+ BOOL showgc = FALSE;
+ BOOL showloader = FALSE;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"-gc", &showgc, COBOOL, FALSE},
+ {"-loader", &showloader, COBOOL, FALSE},
+ {"/d", &dml, COBOOL, FALSE},
+ };
+
+ if (!GetCMDOption(args, option, _countof(option), NULL, 0, NULL))
+ {
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+ if (showloader || !showgc)
+ {
+ // Loader heap.
+ DWORD_PTR allHeapSize = 0;
+ DWORD_PTR wasted = 0;
+ DacpAppDomainStoreData adsData;
+ if ((Status=adsData.Request(g_sos))!=S_OK)
+ {
+ ExtOut("Unable to get AppDomain information\n");
+ return Status;
+ }
+
+ // The first one is the system domain.
+ ExtOut("Loader Heap:\n");
+ IfFailRet(PrintDomainHeapInfo("System Domain", adsData.systemDomain, &allHeapSize, &wasted));
+ IfFailRet(PrintDomainHeapInfo("Shared Domain", adsData.sharedDomain, &allHeapSize, &wasted));
+
+ ArrayHolder<CLRDATA_ADDRESS> pArray = new NOTHROW CLRDATA_ADDRESS[adsData.DomainCount];
+
+ if (pArray==NULL)
+ {
+ ReportOOM();
+ return Status;
+ }
+
+ if ((Status=g_sos->GetAppDomainList(adsData.DomainCount, pArray, NULL))!=S_OK)
+ {
+ ExtOut("Unable to get the array of all AppDomains.\n");
+ return Status;
+ }
+
+ for (int n=0;n<adsData.DomainCount;n++)
+ {
+ if (IsInterrupt())
+ break;
+
+ char domain[16];
+ sprintf_s(domain, _countof(domain), "Domain %d", n+1);
+
+ IfFailRet(PrintDomainHeapInfo(domain, pArray[n], &allHeapSize, &wasted));
+
+ }
+
+ // Jit code heap
+ ExtOut("--------------------------------------\n");
+ ExtOut("Jit code heap:\n");
+
+ if (IsMiniDumpFile())
+ {
+ ExtOut("<no information>\n");
+ }
+ else
+ {
+ allHeapSize += JitHeapInfo();
+ }
+
+
+ // Module Data
+ {
+ int numModule;
+ ArrayHolder<DWORD_PTR> moduleList = ModuleFromName(NULL, &numModule);
+ if (moduleList == NULL)
+ {
+ ExtOut("Failed to request module list.\n");
+ }
+ else
+ {
+ // Module Thunk Heaps
+ ExtOut("--------------------------------------\n");
+ ExtOut("Module Thunk heaps:\n");
+ allHeapSize += PrintModuleHeapInfo(moduleList, numModule, ModuleHeapType_ThunkHeap, &wasted);
+
+ // Module Lookup Table Heaps
+ ExtOut("--------------------------------------\n");
+ ExtOut("Module Lookup Table heaps:\n");
+ allHeapSize += PrintModuleHeapInfo(moduleList, numModule, ModuleHeapType_LookupTableHeap, &wasted);
+ }
+ }
+
+ ExtOut("--------------------------------------\n");
+ ExtOut("Total LoaderHeap size: ");
+ PrintHeapSize(allHeapSize, wasted);
+ ExtOut("=======================================\n");
+ }
+
+ if (showgc || !showloader)
+ {
+ // GC Heap
+ DWORD dwNHeaps = 1;
+
+ if (!GetGcStructuresValid())
+ {
+ DisplayInvalidStructuresMessage();
+ }
+
+ DacpGcHeapData gcheap;
+ if (gcheap.Request(g_sos) != S_OK)
+ {
+ ExtOut("Error requesting GC Heap data\n");
+ return Status;
+ }
+
+ if (gcheap.bServerMode)
+ {
+ dwNHeaps = gcheap.HeapCount;
+ }
+
+ ExtOut("Number of GC Heaps: %d\n", dwNHeaps);
+ DWORD_PTR totalSize = 0;
+ if (!gcheap.bServerMode)
+ {
+ DacpGcHeapDetails heapDetails;
+ if (heapDetails.Request(g_sos) != S_OK)
+ {
+ ExtOut("Error requesting details\n");
+ return Status;
+ }
+
+ GCHeapInfo (heapDetails, totalSize);
+ ExtOut("Total Size: ");
+ PrintHeapSize(totalSize, 0);
+ }
+ else
+ {
+ DWORD dwAllocSize;
+ if (!ClrSafeInt<DWORD>::multiply(sizeof(CLRDATA_ADDRESS), dwNHeaps, dwAllocSize))
+ {
+ ExtOut("Failed to get GCHeaps: integer overflow\n");
+ return Status;
+ }
+
+ CLRDATA_ADDRESS *heapAddrs = (CLRDATA_ADDRESS*)alloca(dwAllocSize);
+ if (g_sos->GetGCHeapList(dwNHeaps, heapAddrs, NULL) != S_OK)
+ {
+ ExtOut("Failed to get GCHeaps\n");
+ return Status;
+ }
+
+ DWORD n;
+ for (n = 0; n < dwNHeaps; n ++)
+ {
+ DacpGcHeapDetails heapDetails;
+ if (heapDetails.Request(g_sos, heapAddrs[n]) != S_OK)
+ {
+ ExtOut("Error requesting details\n");
+ return Status;
+ }
+ ExtOut("------------------------------\n");
+ ExtOut("Heap %d (%p)\n", n, SOS_PTR(heapAddrs[n]));
+ DWORD_PTR heapSize = 0;
+ GCHeapInfo (heapDetails, heapSize);
+ totalSize += heapSize;
+ ExtOut("Heap Size: " WIN86_8SPACES);
+ PrintHeapSize(heapSize, 0);
+ }
+ }
+ ExtOut("------------------------------\n");
+ ExtOut("GC Heap Size: " WIN86_8SPACES);
+ PrintHeapSize(totalSize, 0);
+ }
+ return Status;
+}
+
+void PrintGCStat(HeapStat *inStat, const char* label=NULL)
+{
+ if (inStat)
+ {
+ bool sorted = false;
+ try
+ {
+ inStat->Sort();
+ sorted = true;
+ inStat->Print(label);
+ }
+ catch(...)
+ {
+ ExtOut("Exception occurred while trying to %s the GC stats.\n", sorted ? "print" : "sort");
+ }
+
+ inStat->Delete();
+ }
+}
+
+#ifndef FEATURE_PAL
+
+DECLARE_API(TraverseHeap)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ BOOL bXmlFormat = FALSE;
+ BOOL bVerify = FALSE;
+ StringHolder Filename;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"-xml", &bXmlFormat, COBOOL, FALSE},
+ {"-verify", &bVerify, COBOOL, FALSE},
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&Filename.data, COSTRING},
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ if (nArg != 1)
+ {
+ ExtOut("usage: HeapTraverse [-xml] filename\n");
+ return Status;
+ }
+
+ if (!g_snapshot.Build())
+ {
+ ExtOut("Unable to build snapshot of the garbage collector state\n");
+ return Status;
+ }
+
+ FILE* file = NULL;
+ if (fopen_s(&file, Filename.data, "w") != 0) {
+ ExtOut("Unable to open file\n");
+ return Status;
+ }
+
+ if (!bVerify)
+ ExtOut("Assuming a uncorrupted GC heap. If this is a crash dump consider -verify option\n");
+
+ HeapTraverser traverser(bVerify != FALSE);
+
+ ExtOut("Writing %s format to file %s\n", bXmlFormat ? "Xml" : "CLRProfiler", Filename.data);
+ ExtOut("Gathering types...\n");
+
+ // TODO: there may be a canonical list of methodtables in the runtime that we can
+ // traverse instead of exploring the gc heap for that list. We could then simplify the
+ // tree structure to a sorted list of methodtables, and the index is the ID.
+
+ // TODO: "Traversing object members" code should be generalized and shared between
+ // !gcroot and !traverseheap. Also !dumpheap can begin using GCHeapsTraverse.
+
+ if (!traverser.Initialize())
+ {
+ ExtOut("Error initializing heap traversal\n");
+ fclose(file);
+ return Status;
+ }
+
+ if (!traverser.CreateReport (file, bXmlFormat ? FORMAT_XML : FORMAT_CLRPROFILER))
+ {
+ ExtOut("Unable to write heap report\n");
+ fclose(file);
+ return Status;
+ }
+
+ fclose(file);
+ ExtOut("\nfile %s saved\n", Filename.data);
+
+ return Status;
+}
+
+#endif // FEATURE_PAL
+
+struct PrintRuntimeTypeArgs
+{
+ DWORD_PTR mtOfRuntimeType;
+ int handleFieldOffset;
+ DacpAppDomainStoreData adstore;
+};
+
+void PrintRuntimeTypes(DWORD_PTR objAddr,size_t Size,DWORD_PTR methodTable,LPVOID token)
+{
+ PrintRuntimeTypeArgs *pArgs = (PrintRuntimeTypeArgs *)token;
+
+ if (pArgs->mtOfRuntimeType == NULL)
+ {
+ NameForMT_s(methodTable, g_mdName, mdNameLen);
+
+ if (_wcscmp(g_mdName, W("System.RuntimeType")) == 0)
+ {
+ pArgs->mtOfRuntimeType = methodTable;
+ pArgs->handleFieldOffset = GetObjFieldOffset(objAddr, methodTable, W("m_handle"));
+ if (pArgs->handleFieldOffset <= 0)
+ ExtOut("Error getting System.RuntimeType.m_handle offset\n");
+
+ pArgs->adstore.Request(g_sos);
+ }
+ }
+
+ if ((methodTable == pArgs->mtOfRuntimeType) && (pArgs->handleFieldOffset > 0))
+ {
+ // Get the method table and display the information.
+ DWORD_PTR mtPtr;
+ if (MOVE(mtPtr, objAddr + pArgs->handleFieldOffset) == S_OK)
+ {
+ DMLOut(DMLObject(objAddr));
+
+ CLRDATA_ADDRESS appDomain = GetAppDomainForMT(mtPtr);
+ if (appDomain != NULL)
+ {
+ if (appDomain == pArgs->adstore.sharedDomain)
+ ExtOut(" %" POINTERSIZE "s", "Shared");
+
+ else if (appDomain == pArgs->adstore.systemDomain)
+ ExtOut(" %" POINTERSIZE "s", "System");
+ else
+ DMLOut(" %s", DMLDomain(appDomain));
+ }
+ else
+ {
+ ExtOut(" %" POINTERSIZE "s", "?");
+ }
+
+ NameForMT_s(mtPtr, g_mdName, mdNameLen);
+ DMLOut(" %s %S\n", DMLMethodTable(mtPtr), g_mdName);
+ }
+ }
+}
+
+
+DECLARE_API(DumpRuntimeTypes)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ BOOL dml = FALSE;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"/d", &dml, COBOOL, FALSE},
+ };
+
+ if (!GetCMDOption(args, option, _countof(option), NULL, 0, NULL))
+ return Status;
+
+ EnableDMLHolder dmlHolder(dml);
+
+ ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s Type Name \n",
+ "Address", "Domain", "MT");
+ ExtOut("------------------------------------------------------------------------------\n");
+
+ PrintRuntimeTypeArgs pargs;
+ ZeroMemory(&pargs, sizeof(PrintRuntimeTypeArgs));
+
+ GCHeapsTraverse(PrintRuntimeTypes, (LPVOID)&pargs);
+ return Status;
+}
+
+#define MIN_FRAGMENTATIONBLOCK_BYTES (1024*512)
+namespace sos
+{
+ class FragmentationBlock
+ {
+ public:
+ FragmentationBlock(TADDR addr, size_t size, TADDR next, TADDR mt)
+ : mAddress(addr), mSize(size), mNext(next), mNextMT(mt)
+ {
+ }
+
+ inline TADDR GetAddress() const
+ {
+ return mAddress;
+ }
+ inline size_t GetSize() const
+ {
+ return mSize;
+ }
+
+ inline TADDR GetNextObject() const
+ {
+ return mNext;
+ }
+
+ inline TADDR GetNextMT() const
+ {
+ return mNextMT;
+ }
+
+ private:
+ TADDR mAddress;
+ size_t mSize;
+ TADDR mNext;
+ TADDR mNextMT;
+ };
+}
+
+class DumpHeapImpl
+{
+public:
+ DumpHeapImpl(PCSTR args)
+ : mStart(0), mStop(0), mMT(0), mMinSize(0), mMaxSize(~0),
+ mStat(FALSE), mStrings(FALSE), mVerify(FALSE),
+ mThinlock(FALSE), mShort(FALSE), mDML(FALSE),
+ mLive(FALSE), mDead(FALSE), mType(NULL)
+ {
+ ArrayHolder<char> type = NULL;
+
+ TADDR minTemp = 0;
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"-mt", &mMT, COHEX, TRUE}, // dump objects with a given MethodTable
+ {"-type", &type, COSTRING, TRUE}, // list objects of specified type
+ {"-stat", &mStat, COBOOL, FALSE}, // dump a summary of types and the number of instances of each
+ {"-strings", &mStrings, COBOOL, FALSE}, // dump a summary of string objects
+ {"-verify", &mVerify, COBOOL, FALSE}, // verify heap objects (!heapverify)
+ {"-thinlock", &mThinlock, COBOOL, FALSE},// list only thinlocks
+ {"-short", &mShort, COBOOL, FALSE}, // list only addresses
+ {"-min", &mMinSize, COHEX, TRUE}, // min size of objects to display
+ {"-max", &mMaxSize, COHEX, TRUE}, // max size of objects to display
+ {"-live", &mLive, COHEX, FALSE}, // only print live objects
+ {"-dead", &mDead, COHEX, FALSE}, // only print dead objects
+#ifndef FEATURE_PAL
+ {"/d", &mDML, COBOOL, FALSE}, // Debugger Markup Language
+#endif
+ };
+
+ CMDValue arg[] =
+ { // vptr, type
+ {&mStart, COHEX},
+ {&mStop, COHEX}
+ };
+
+ size_t nArgs = 0;
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArgs))
+ sos::Throw<sos::Exception>("Failed to parse command line arguments.");
+
+ if (mStart == 0)
+ mStart = minTemp;
+
+ if (mStop == 0)
+ mStop = sos::GCHeap::HeapEnd;
+
+ if (type && mMT)
+ {
+ sos::Throw<sos::Exception>("Cannot specify both -mt and -type");
+ }
+
+ if (mLive && mDead)
+ {
+ sos::Throw<sos::Exception>("Cannot specify both -live and -dead.");
+ }
+
+ if (mMinSize > mMaxSize)
+ {
+ sos::Throw<sos::Exception>("wrong argument");
+ }
+
+ // If the user gave us a type, convert it to unicode and clean up "type".
+ if (type && !mStrings)
+ {
+ size_t iLen = strlen(type) + 1;
+ mType = new WCHAR[iLen];
+ MultiByteToWideChar(CP_ACP, 0, type, -1, mType, (int)iLen);
+ }
+ }
+
+ ~DumpHeapImpl()
+ {
+ if (mType)
+ delete [] mType;
+ }
+
+ void Run()
+ {
+ // enable Debugger Markup Language
+ EnableDMLHolder dmlholder(mDML);
+ sos::GCHeap gcheap;
+
+ if (!gcheap.AreGCStructuresValid())
+ DisplayInvalidStructuresMessage();
+
+ if (IsMiniDumpFile())
+ {
+ ExtOut("In a minidump without full memory, most gc heap structures will not be valid.\n");
+ ExtOut("If you need this functionality, get a full memory dump with \".dump /ma mydump.dmp\"\n");
+ }
+
+#ifndef FEATURE_PAL
+ if (mLive || mDead)
+ {
+ GCRootImpl gcroot;
+ mLiveness = gcroot.GetLiveObjects();
+ }
+#endif
+
+ // Some of the "specialty" versions of DumpHeap have slightly
+ // different implementations than the standard version of DumpHeap.
+ // We seperate them out to not clutter the standard DumpHeap function.
+ if (mShort)
+ DumpHeapShort(gcheap);
+ else if (mThinlock)
+ DumpHeapThinlock(gcheap);
+ else if (mStrings)
+ DumpHeapStrings(gcheap);
+ else
+ DumpHeap(gcheap);
+
+ if (mVerify)
+ ValidateSyncTable(gcheap);
+ }
+
+ static bool ValidateSyncTable(sos::GCHeap &gcheap)
+ {
+ bool succeeded = true;
+ for (sos::SyncBlkIterator itr; itr; ++itr)
+ {
+ sos::CheckInterrupt();
+
+ if (!itr->IsFree())
+ {
+ if (!sos::IsObject(itr->GetObject(), true))
+ {
+ ExtOut("SyncBlock %d corrupted, points to invalid object %p\n",
+ itr->GetIndex(), SOS_PTR(itr->GetObject()));
+ succeeded = false;
+ }
+ else
+ {
+ // Does the object header point to this syncblock index?
+ sos::Object obj = itr->GetObject();
+ ULONG header = 0;
+
+ if (!obj.TryGetHeader(header))
+ {
+ ExtOut("Failed to get object header for object %p while inspecting syncblock at index %d.\n",
+ SOS_PTR(itr->GetObject()), itr->GetIndex());
+ succeeded = false;
+ }
+ else
+ {
+ bool valid = false;
+ if ((header & BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX) != 0 && (header & BIT_SBLK_IS_HASHCODE) == 0)
+ {
+ ULONG index = header & MASK_SYNCBLOCKINDEX;
+ valid = (ULONG)itr->GetIndex() == index;
+ }
+
+ if (!valid)
+ {
+ ExtOut("Object header for %p should have a SyncBlock index of %d.\n",
+ SOS_PTR(itr->GetObject()), itr->GetIndex());
+ succeeded = false;
+ }
+ }
+ }
+ }
+ }
+
+ return succeeded;
+ }
+private:
+ DumpHeapImpl(const DumpHeapImpl &);
+
+ bool Verify(const sos::ObjectIterator &itr)
+ {
+ if (mVerify)
+ {
+ char buffer[1024];
+ if (!itr.Verify(buffer, _countof(buffer)))
+ {
+ ExtOut(buffer);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ bool IsCorrectType(const sos::Object &obj)
+ {
+ if (mMT != NULL)
+ return mMT == obj.GetMT();
+
+ if (mType != NULL)
+ {
+ WString name = obj.GetTypeName();
+ return _wcsstr(name.c_str(), mType) != NULL;
+ }
+
+ return true;
+ }
+
+ bool IsCorrectSize(const sos::Object &obj)
+ {
+ size_t size = obj.GetSize();
+ return size >= mMinSize && size <= mMaxSize;
+ }
+
+ bool IsCorrectLiveness(const sos::Object &obj)
+ {
+#ifndef FEATURE_PAL
+ if (mLive && mLiveness.find(obj.GetAddress()) == mLiveness.end())
+ return false;
+
+ if (mDead && (mLiveness.find(obj.GetAddress()) != mLiveness.end() || obj.IsFree()))
+ return false;
+#endif
+ return true;
+ }
+
+
+
+ inline void PrintHeader()
+ {
+ ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %8s\n", "Address", "MT", "Size");
+ }
+
+ void DumpHeap(sos::GCHeap &gcheap)
+ {
+ HeapStat stats;
+
+ // For heap fragmentation tracking.
+ TADDR lastFreeObj = NULL;
+ size_t lastFreeSize = 0;
+
+ if (!mStat)
+ PrintHeader();
+
+ for (sos::ObjectIterator itr = gcheap.WalkHeap(mStart, mStop); itr; ++itr)
+ {
+ if (!Verify(itr))
+ return;
+
+ bool onLOH = itr.IsCurrObjectOnLOH();
+
+ // Check for free objects to report fragmentation
+ if (lastFreeObj != NULL)
+ ReportFreeObject(lastFreeObj, lastFreeSize, itr->GetAddress(), itr->GetMT());
+
+ if (!onLOH && itr->IsFree())
+ {
+ lastFreeObj = *itr;
+ lastFreeSize = itr->GetSize();
+ }
+ else
+ {
+ lastFreeObj = NULL;
+ }
+
+ if (IsCorrectType(*itr) && IsCorrectSize(*itr) && IsCorrectLiveness(*itr))
+ {
+ stats.Add((DWORD_PTR)itr->GetMT(), (DWORD)itr->GetSize());
+ if (!mStat)
+ DMLOut("%s %s %8d%s\n", DMLObject(itr->GetAddress()), DMLDumpHeapMT(itr->GetMT()), itr->GetSize(),
+ itr->IsFree() ? " Free":" ");
+ }
+ }
+
+ if (!mStat)
+ ExtOut("\n");
+
+ stats.Sort();
+ stats.Print();
+
+ PrintFragmentationReport();
+ }
+
+ struct StringSetEntry
+ {
+ StringSetEntry() : count(0), size(0)
+ {
+ str[0] = 0;
+ }
+
+ StringSetEntry(__in_ecount(64) WCHAR tmp[64], size_t _size)
+ : count(1), size(_size)
+ {
+ memcpy(str, tmp, sizeof(str));
+ }
+
+ void Add(size_t _size) const
+ {
+ count++;
+ size += _size;
+ }
+
+ mutable size_t count;
+ mutable size_t size;
+ WCHAR str[64];
+
+ bool operator<(const StringSetEntry &rhs) const
+ {
+ return _wcscmp(str, rhs.str) < 0;
+ }
+ };
+
+
+ static bool StringSetCompare(const StringSetEntry &a1, const StringSetEntry &a2)
+ {
+ return a1.size < a2.size;
+ }
+
+ void DumpHeapStrings(sos::GCHeap &gcheap)
+ {
+#ifdef FEATURE_PAL
+ ExtOut("Not implemented.\n");
+#else
+ const int offset = sos::Object::GetStringDataOffset();
+ typedef std::set<StringSetEntry> Set;
+ Set set; // A set keyed off of the string's text
+
+ StringSetEntry tmp; // Temp string used to keep track of the set
+ ULONG fetched = 0;
+
+ TableOutput out(3, POINTERSIZE_HEX, AlignRight);
+ for (sos::ObjectIterator itr = gcheap.WalkHeap(mStart, mStop); itr; ++itr)
+ {
+ if (IsInterrupt())
+ break;
+
+ if (itr->IsString() && IsCorrectSize(*itr) && IsCorrectLiveness(*itr))
+ {
+ CLRDATA_ADDRESS addr = itr->GetAddress();
+ size_t size = itr->GetSize();
+
+ if (!mStat)
+ out.WriteRow(ObjectPtr(addr), Pointer(itr->GetMT()), Decimal(size));
+
+ // Don't bother calculating the size of the string, just read the full 64 characters of the buffer. The null
+ // terminator we read will terminate the string.
+ HRESULT hr = g_ExtData->ReadVirtual(TO_CDADDR(addr+offset), tmp.str, sizeof(WCHAR)*(_countof(tmp.str)-1), &fetched);
+ if (SUCCEEDED(hr))
+ {
+ // Ensure we null terminate the string. Note that this will not overrun the buffer as we only
+ // wrote a max of 63 characters into the 64 character buffer.
+ tmp.str[fetched/sizeof(WCHAR)] = 0;
+ Set::iterator sitr = set.find(tmp);
+ if (sitr == set.end())
+ {
+ tmp.size = size;
+ tmp.count = 1;
+ set.insert(tmp);
+ }
+ else
+ {
+ sitr->Add(size);
+ }
+ }
+ }
+ }
+
+ ExtOut("\n");
+
+ // Now flatten the set into a vector. This is much faster than keeping two sets, or using a multimap.
+ typedef std::vector<StringSetEntry> Vect;
+ Vect v(set.begin(), set.end());
+ std::sort(v.begin(), v.end(), &DumpHeapImpl::StringSetCompare);
+
+ // Now print out the data. The call to Flatten ensures that we don't print newlines to break up the
+ // output in strange ways.
+ for (Vect::iterator vitr = v.begin(); vitr != v.end(); ++vitr)
+ {
+ if (IsInterrupt())
+ break;
+
+ Flatten(vitr->str, (unsigned int)_wcslen(vitr->str));
+ out.WriteRow(Decimal(vitr->size), Decimal(vitr->count), vitr->str);
+ }
+#endif // FEATURE_PAL
+ }
+
+ void DumpHeapShort(sos::GCHeap &gcheap)
+ {
+ for (sos::ObjectIterator itr = gcheap.WalkHeap(mStart, mStop); itr; ++itr)
+ {
+ if (!Verify(itr))
+ return;
+
+ if (IsCorrectType(*itr) && IsCorrectSize(*itr) && IsCorrectLiveness(*itr))
+ DMLOut("%s\n", DMLObject(itr->GetAddress()));
+ }
+ }
+
+ void DumpHeapThinlock(sos::GCHeap &gcheap)
+ {
+ int count = 0;
+
+ PrintHeader();
+ for (sos::ObjectIterator itr = gcheap.WalkHeap(mStart, mStop); itr; ++itr)
+ {
+ if (!Verify(itr))
+ return;
+
+ sos::ThinLockInfo lockInfo;
+ if (IsCorrectType(*itr) && itr->GetThinLock(lockInfo))
+ {
+ DMLOut("%s %s %8d", DMLObject(itr->GetAddress()), DMLDumpHeapMT(itr->GetMT()), itr->GetSize());
+ ExtOut(" ThinLock owner %x (%p) Recursive %x\n", lockInfo.ThreadId,
+ SOS_PTR(lockInfo.ThreadPtr), lockInfo.Recursion);
+
+ count++;
+ }
+ }
+
+ ExtOut("Found %d objects.\n", count);
+ }
+
+private:
+ TADDR mStart,
+ mStop,
+ mMT,
+ mMinSize,
+ mMaxSize;
+
+ BOOL mStat,
+ mStrings,
+ mVerify,
+ mThinlock,
+ mShort,
+ mDML,
+ mLive,
+ mDead;
+
+
+ WCHAR *mType;
+
+private:
+#if !defined(FEATURE_PAL)
+ // Windows only
+ std::unordered_set<TADDR> mLiveness;
+ typedef std::list<sos::FragmentationBlock> FragmentationList;
+ FragmentationList mFrag;
+
+ void InitFragmentationList()
+ {
+ mFrag.clear();
+ }
+
+ void ReportFreeObject(TADDR addr, size_t size, TADDR next, TADDR mt)
+ {
+ if (size >= MIN_FRAGMENTATIONBLOCK_BYTES)
+ mFrag.push_back(sos::FragmentationBlock(addr, size, next, mt));
+ }
+
+ void PrintFragmentationReport()
+ {
+ if (mFrag.size() > 0)
+ {
+ ExtOut("Fragmented blocks larger than 0.5 MB:\n");
+ ExtOut("%" POINTERSIZE "s %8s %16s\n", "Addr", "Size", "Followed by");
+
+ for (FragmentationList::const_iterator itr = mFrag.begin(); itr != mFrag.end(); ++itr)
+ {
+ sos::MethodTable mt = itr->GetNextMT();
+ ExtOut("%p %6.1fMB " WIN64_8SPACES "%p %S\n",
+ SOS_PTR(itr->GetAddress()),
+ ((double)itr->GetSize()) / 1024.0 / 1024.0,
+ SOS_PTR(itr->GetNextObject()),
+ mt.GetName());
+ }
+ }
+ }
+#else
+ void InitFragmentationList() {}
+ void ReportFreeObject(TADDR, TADDR, size_t, TADDR) {}
+ void PrintFragmentationReport() {}
+#endif
+};
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function dumps all objects on GC heap. It also displays *
+* statistics of objects. If GC heap is corrupted, it will stop at
+* the bad place. (May not work if GC is in progress.) *
+* *
+\**********************************************************************/
+DECLARE_API(DumpHeap)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ if (!g_snapshot.Build())
+ {
+ ExtOut("Unable to build snapshot of the garbage collector state\n");
+ return E_FAIL;
+ }
+
+ try
+ {
+ DumpHeapImpl dumpHeap(args);
+ dumpHeap.Run();
+
+ return S_OK;
+ }
+ catch(const sos::Exception &e)
+ {
+ ExtOut("%s\n", e.what());
+ return E_FAIL;
+ }
+}
+
+DECLARE_API(VerifyHeap)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ if (!g_snapshot.Build())
+ {
+ ExtOut("Unable to build snapshot of the garbage collector state\n");
+ return E_FAIL;
+ }
+
+ try
+ {
+ bool succeeded = true;
+ char buffer[1024];
+ sos::GCHeap gcheap;
+ sos::ObjectIterator itr = gcheap.WalkHeap();
+
+ while (itr)
+ {
+ if (itr.Verify(buffer, _countof(buffer)))
+ {
+ ++itr;
+ }
+ else
+ {
+ succeeded = false;
+ ExtOut(buffer);
+ itr.MoveToNextObjectCarefully();
+ }
+ }
+
+ if (!DumpHeapImpl::ValidateSyncTable(gcheap))
+ succeeded = false;
+
+ if (succeeded)
+ ExtOut("No heap corruption detected.\n");
+
+ return S_OK;
+ }
+ catch(const sos::Exception &e)
+ {
+ ExtOut("%s\n", e.what());
+ return E_FAIL;
+ }
+}
+
+#ifndef FEATURE_PAL
+
+enum failure_get_memory
+{
+ fgm_no_failure = 0,
+ fgm_reserve_segment = 1,
+ fgm_commit_segment_beg = 2,
+ fgm_commit_eph_segment = 3,
+ fgm_grow_table = 4,
+ fgm_commit_table = 5
+};
+
+enum oom_reason
+{
+ oom_no_failure = 0,
+ oom_budget = 1,
+ oom_cant_commit = 2,
+ oom_cant_reserve = 3,
+ oom_loh = 4,
+ oom_low_mem = 5,
+ oom_unproductive_full_gc = 6
+};
+
+static const char *const str_oom[] =
+{
+ "There was no managed OOM due to allocations on the GC heap", // oom_no_failure
+ "This is likely to be a bug in GC", // oom_budget
+ "Didn't have enough memory to commit", // oom_cant_commit
+ "This is likely to be a bug in GC", // oom_cant_reserve
+ "Didn't have enough memory to allocate an LOH segment", // oom_loh
+ "Low on memory during GC", // oom_low_mem
+ "Could not do a full GC" // oom_unproductive_full_gc
+};
+
+static const char *const str_fgm[] =
+{
+ "There was no failure to allocate memory", // fgm_no_failure
+ "Failed to reserve memory", // fgm_reserve_segment
+ "Didn't have enough memory to commit beginning of the segment", // fgm_commit_segment_beg
+ "Didn't have enough memory to commit the new ephemeral segment", // fgm_commit_eph_segment
+ "Didn't have enough memory to grow the internal GC datastructures", // fgm_grow_table
+ "Didn't have enough memory to commit the internal GC datastructures", // fgm_commit_table
+};
+
+void PrintOOMInfo(DacpOomData* oomData)
+{
+ ExtOut("Managed OOM occurred after GC #%d (Requested to allocate %d bytes)\n",
+ oomData->gc_index, oomData->alloc_size);
+
+ if ((oomData->reason == oom_budget) ||
+ (oomData->reason == oom_cant_reserve))
+ {
+ // TODO: This message needs to be updated with more precious info.
+ ExtOut("%s, please contact PSS\n", str_oom[oomData->reason]);
+ }
+ else
+ {
+ ExtOut("Reason: %s\n", str_oom[oomData->reason]);
+ }
+
+ // Now print out the more detailed memory info if any.
+ if (oomData->fgm != fgm_no_failure)
+ {
+ ExtOut("Detail: %s: %s (%d bytes)",
+ (oomData->loh_p ? "LOH" : "SOH"),
+ str_fgm[oomData->fgm],
+ oomData->size);
+
+ if ((oomData->fgm == fgm_commit_segment_beg) ||
+ (oomData->fgm == fgm_commit_eph_segment) ||
+ (oomData->fgm == fgm_grow_table) ||
+ (oomData->fgm == fgm_commit_table))
+ {
+ // If it's a commit error (fgm_grow_table can indicate a reserve
+ // or a commit error since we make one VirtualAlloc call to
+ // reserve and commit), we indicate the available commit
+ // space if we recorded it.
+ if (oomData->available_pagefile_mb)
+ {
+ ExtOut(" - on GC entry available commit space was %d MB",
+ oomData->available_pagefile_mb);
+ }
+ }
+
+ ExtOut("\n");
+ }
+}
+
+DECLARE_API(AnalyzeOOM)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+#ifndef FEATURE_PAL
+
+ if (!InitializeHeapData ())
+ {
+ ExtOut("GC Heap not initialized yet.\n");
+ return S_OK;
+ }
+
+ BOOL bHasManagedOOM = FALSE;
+ DacpOomData oomData;
+ memset (&oomData, 0, sizeof(oomData));
+ if (!IsServerBuild())
+ {
+ if (oomData.Request(g_sos) != S_OK)
+ {
+ ExtOut("Error requesting OOM data\n");
+ return E_FAIL;
+ }
+ if (oomData.reason != oom_no_failure)
+ {
+ bHasManagedOOM = TRUE;
+ PrintOOMInfo(&oomData);
+ }
+ }
+ else
+ {
+ DWORD dwNHeaps = GetGcHeapCount();
+ DWORD dwAllocSize;
+ if (!ClrSafeInt<DWORD>::multiply(sizeof(CLRDATA_ADDRESS), dwNHeaps, dwAllocSize))
+ {
+ ExtOut("Failed to get GCHeaps: integer overflow\n");
+ return Status;
+ }
+
+ CLRDATA_ADDRESS *heapAddrs = (CLRDATA_ADDRESS*)alloca(dwAllocSize);
+ if (g_sos->GetGCHeapList(dwNHeaps, heapAddrs, NULL) != S_OK)
+ {
+ ExtOut("Failed to get GCHeaps\n");
+ return Status;
+ }
+
+ for (DWORD n = 0; n < dwNHeaps; n ++)
+ {
+ if (oomData.Request(g_sos, heapAddrs[n]) != S_OK)
+ {
+ ExtOut("Heap %d: Error requesting OOM data\n", n);
+ return E_FAIL;
+ }
+ if (oomData.reason != oom_no_failure)
+ {
+ if (!bHasManagedOOM)
+ {
+ bHasManagedOOM = TRUE;
+ }
+ ExtOut("---------Heap %#-2d---------\n", n);
+ PrintOOMInfo(&oomData);
+ }
+ }
+ }
+
+ if (!bHasManagedOOM)
+ {
+ ExtOut("%s\n", str_oom[oomData.reason]);
+ }
+
+ return S_OK;
+#else
+ _ASSERTE(false);
+ return E_FAIL;
+#endif // FEATURE_PAL
+}
+
+DECLARE_API(VerifyObj)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ TADDR taddrObj = 0;
+ TADDR taddrMT;
+ size_t objSize;
+
+ BOOL bValid = FALSE;
+ BOOL dml = FALSE;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"/d", &dml, COBOOL, FALSE},
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&taddrObj, COHEX}
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+ BOOL bContainsPointers;
+
+ if (FAILED(GetMTOfObject(taddrObj, &taddrMT)) ||
+ !GetSizeEfficient(taddrObj, taddrMT, FALSE, objSize, bContainsPointers))
+ {
+ ExtOut("object %#p does not have valid method table\n", SOS_PTR(taddrObj));
+ goto Exit;
+ }
+
+ // we need to build g_snapshot as it is later used in GetGeneration
+ if (!g_snapshot.Build())
+ {
+ ExtOut("Unable to build snapshot of the garbage collector state\n");
+ goto Exit;
+ }
+ DacpGcHeapDetails *pheapDetails = g_snapshot.GetHeap(taddrObj);
+ bValid = VerifyObject(*pheapDetails, taddrObj, taddrMT, objSize, TRUE);
+
+Exit:
+ if (bValid)
+ {
+ ExtOut("object %#p is a valid object\n", SOS_PTR(taddrObj));
+ }
+
+ return Status;
+}
+
+void LNODisplayOutput(LPCWSTR tag, TADDR pMT, TADDR currentObj, size_t size)
+{
+ sos::Object obj(currentObj, pMT);
+ DMLOut("%S %s %12d (0x%x)\t%S\n", tag, DMLObject(currentObj), size, size, obj.GetTypeName());
+}
+
+DECLARE_API(ListNearObj)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+#if !defined(FEATURE_PAL)
+
+ TADDR taddrArg = 0;
+ TADDR taddrObj = 0;
+ // we may want to provide a more exact version of searching for the
+ // previous object in the heap, using the brick table, instead of
+ // looking for what may be valid method tables...
+ //BOOL bExact;
+ //CMDOption option[] =
+ //{
+ // // name, vptr, type, hasValue
+ // {"-exact", &bExact, COBOOL, FALSE}
+ //};
+
+ BOOL dml = FALSE;
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"/d", &dml, COBOOL, FALSE},
+ };
+ CMDValue arg[] =
+ {
+ // vptr, type
+ {&taddrArg, COHEX}
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg) || nArg != 1)
+ {
+ ExtOut("Usage: !ListNearObj <obj_address>\n");
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+
+ if (!g_snapshot.Build())
+ {
+ ExtOut("Unable to build snapshot of the garbage collector state\n");
+ return Status;
+ }
+
+ taddrObj = Align(taddrArg);
+
+ DacpGcHeapDetails *heap = g_snapshot.GetHeap(taddrArg);
+ if (heap == NULL)
+ {
+ ExtOut("Address %p does not lie in the managed heap\n", SOS_PTR(taddrObj));
+ return Status;
+ }
+
+ TADDR_SEGINFO trngSeg = {0, 0, 0};
+ TADDR_RANGE allocCtx = {0, 0};
+ BOOL bLarge;
+ int gen;
+ if (!GCObjInHeap(taddrObj, *heap, trngSeg, gen, allocCtx, bLarge))
+ {
+ ExtOut("Failed to find the segment of the managed heap where the object %p resides\n",
+ SOS_PTR(taddrObj));
+ return Status;
+ }
+
+ TADDR objMT = NULL;
+ size_t objSize = 0;
+ BOOL bObj = FALSE;
+ TADDR taddrCur;
+ TADDR curMT = 0;
+ size_t curSize = 0;
+ BOOL bCur = FALSE;
+ TADDR taddrNxt;
+ TADDR nxtMT = 0;
+ size_t nxtSize = 0;
+ BOOL bNxt = FALSE;
+ BOOL bContainsPointers;
+
+ std::vector<TADDR> candidate;
+ candidate.reserve(10);
+
+ // since we'll be reading back I'll prime the read cache to a buffer before the current address
+ MOVE(taddrCur, _max(trngSeg.start, taddrObj-DT_OS_PAGE_SIZE));
+
+ // ===== Look for a good candidate preceeding taddrObj
+
+ for (taddrCur = taddrObj - sizeof(TADDR); taddrCur >= trngSeg.start; taddrCur -= sizeof(TADDR))
+ {
+ // currently we don't pay attention to allocation contexts. if this
+ // proves to be an issue we need to reconsider the code below
+ if (SUCCEEDED(GetMTOfObject(taddrCur, &curMT)) &&
+ GetSizeEfficient(taddrCur, curMT, bLarge, curSize, bContainsPointers))
+ {
+ // remember this as one of the possible "good" objects preceeding taddrObj
+ candidate.push_back(taddrCur);
+
+ std::vector<TADDR>::iterator it =
+ std::find(candidate.begin(), candidate.end(), taddrCur+curSize);
+ if (it != candidate.end())
+ {
+ // We found a chain of two objects preceeding taddrObj. We'll
+ // trust this is a good indication that the two objects are valid.
+ // What is not valid is possibly the object following the second
+ // one...
+ taddrCur = *it;
+ GetMTOfObject(taddrCur, &curMT);
+ GetSizeEfficient(taddrCur, curMT, bLarge, curSize, bContainsPointers);
+ bCur = TRUE;
+ break;
+ }
+ }
+ }
+
+ if (!bCur && !candidate.empty())
+ {
+ // pick the closest object to taddrObj
+ taddrCur = *(candidate.begin());
+ GetMTOfObject(taddrCur, &curMT);
+ GetSizeEfficient(taddrCur, curMT, bLarge, curSize, bContainsPointers);
+ // we have a candidate, even if not confirmed
+ bCur = TRUE;
+ }
+
+ taddrNxt = taddrObj;
+ if (taddrArg == taddrObj)
+ {
+ taddrNxt += sizeof(TADDR);
+ }
+
+ // ===== Now look at taddrObj
+ if (taddrObj == taddrArg)
+ {
+ // only look at taddrObj if it's the same as what user passed in, meaning it's aligned.
+ if (SUCCEEDED(GetMTOfObject(taddrObj, &objMT)) &&
+ GetSizeEfficient(taddrObj, objMT, bLarge, objSize, bContainsPointers))
+ {
+ bObj = TRUE;
+ taddrNxt = taddrObj+objSize;
+ }
+ }
+
+ if ((taddrCur + curSize > taddrArg) && taddrCur + curSize < trngSeg.end)
+ {
+ if (SUCCEEDED(GetMTOfObject(taddrCur + curSize, &nxtMT)) &&
+ GetSizeEfficient(taddrObj, objMT, bLarge, objSize, bContainsPointers))
+ {
+ taddrNxt = taddrCur+curSize;
+ }
+ }
+
+ // ===== And finally move on to elements following taddrObj
+
+ for (; taddrNxt < trngSeg.end; taddrNxt += sizeof(TADDR))
+ {
+ if (SUCCEEDED(GetMTOfObject(taddrNxt, &nxtMT)) &&
+ GetSizeEfficient(taddrNxt, nxtMT, bLarge, nxtSize, bContainsPointers))
+ {
+ bNxt = TRUE;
+ break;
+ }
+ }
+
+ if (bCur)
+ LNODisplayOutput(W("Before: "), curMT, taddrCur, curSize);
+ else
+ ExtOut("Before: couldn't find any object between %#p and %#p\n",
+ SOS_PTR(trngSeg.start), SOS_PTR(taddrArg));
+
+ if (bObj)
+ LNODisplayOutput(W("Current:"), objMT, taddrObj, objSize);
+
+ if (bNxt)
+ LNODisplayOutput(W("After: "), nxtMT, taddrNxt, nxtSize);
+ else
+ ExtOut("After: couldn't find any object between %#p and %#p\n",
+ SOS_PTR(taddrArg), SOS_PTR(trngSeg.end));
+
+ if (bCur && bNxt &&
+ (((taddrCur+curSize == taddrObj) && (taddrObj+objSize == taddrNxt)) || (taddrCur+curSize == taddrNxt)))
+ {
+ ExtOut("Heap local consistency confirmed.\n");
+ }
+ else
+ {
+ ExtOut("Heap local consistency not confirmed.\n");
+ }
+
+ return Status;
+
+#else
+
+ _ASSERTE(false);
+ return E_FAIL;
+
+#endif // FEATURE_PAL
+}
+
+
+DECLARE_API(GCHeapStat)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+
+#ifndef FEATURE_PAL
+
+ BOOL bIncUnreachable = FALSE;
+ BOOL dml = FALSE;
+
+ CMDOption option[] = {
+ // name, vptr, type, hasValue
+ {"-inclUnrooted", &bIncUnreachable, COBOOL, FALSE},
+ {"-iu", &bIncUnreachable, COBOOL, FALSE},
+ {"/d", &dml, COBOOL, FALSE}
+ };
+
+ if (!GetCMDOption(args, option, _countof(option), NULL, 0, NULL))
+ {
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+ ExtOut("%-8s %12s %12s %12s %12s\n", "Heap", "Gen0", "Gen1", "Gen2", "LOH");
+
+ if (!IsServerBuild())
+ {
+ float tempf;
+ DacpGcHeapDetails heapDetails;
+ if (heapDetails.Request(g_sos) != S_OK)
+ {
+ ExtErr("Error requesting gc heap details\n");
+ return Status;
+ }
+
+ HeapUsageStat hpUsage;
+ if (GCHeapUsageStats(heapDetails, bIncUnreachable, &hpUsage))
+ {
+ ExtOut("Heap%-4d %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u\n", 0,
+ hpUsage.genUsage[0].allocd, hpUsage.genUsage[1].allocd,
+ hpUsage.genUsage[2].allocd, hpUsage.genUsage[3].allocd);
+ ExtOut("\nFree space: Percentage\n");
+ ExtOut("Heap%-4d %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u", 0,
+ hpUsage.genUsage[0].freed, hpUsage.genUsage[1].freed,
+ hpUsage.genUsage[2].freed, hpUsage.genUsage[3].freed);
+ tempf = ((float)(hpUsage.genUsage[0].freed+hpUsage.genUsage[1].freed+hpUsage.genUsage[2].freed)) /
+ (hpUsage.genUsage[0].allocd+hpUsage.genUsage[1].allocd+hpUsage.genUsage[2].allocd);
+ ExtOut("SOH:%3d%% LOH:%3d%%\n", (int)(100 * tempf),
+ (int)(100*((float)hpUsage.genUsage[3].freed) / (hpUsage.genUsage[3].allocd)));
+ if (bIncUnreachable)
+ {
+ ExtOut("\nUnrooted objects: Percentage\n");
+ ExtOut("Heap%-4d %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u", 0,
+ hpUsage.genUsage[0].unrooted, hpUsage.genUsage[1].unrooted,
+ hpUsage.genUsage[2].unrooted, hpUsage.genUsage[3].unrooted);
+ tempf = ((float)(hpUsage.genUsage[0].unrooted+hpUsage.genUsage[1].unrooted+hpUsage.genUsage[2].unrooted)) /
+ (hpUsage.genUsage[0].allocd+hpUsage.genUsage[1].allocd+hpUsage.genUsage[2].allocd);
+ ExtOut("SOH:%3d%% LOH:%3d%%\n", (int)(100 * tempf),
+ (int)(100*((float)hpUsage.genUsage[3].unrooted) / (hpUsage.genUsage[3].allocd)));
+ }
+ }
+ }
+ else
+ {
+ float tempf;
+ DacpGcHeapData gcheap;
+ if (gcheap.Request(g_sos) != S_OK)
+ {
+ ExtErr("Error requesting GC Heap data\n");
+ return Status;
+ }
+
+ DWORD dwAllocSize;
+ DWORD dwNHeaps = gcheap.HeapCount;
+ if (!ClrSafeInt<DWORD>::multiply(sizeof(CLRDATA_ADDRESS), dwNHeaps, dwAllocSize))
+ {
+ ExtErr("Failed to get GCHeaps: integer overflow\n");
+ return Status;
+ }
+
+ CLRDATA_ADDRESS *heapAddrs = (CLRDATA_ADDRESS*)alloca(dwAllocSize);
+ if (g_sos->GetGCHeapList(dwNHeaps, heapAddrs, NULL) != S_OK)
+ {
+ ExtErr("Failed to get GCHeaps\n");
+ return Status;
+ }
+
+ ArrayHolder<HeapUsageStat> hpUsage = new NOTHROW HeapUsageStat[dwNHeaps];
+ if (hpUsage == NULL)
+ {
+ ReportOOM();
+ return Status;
+ }
+
+ // aggregate stats accross heaps / generation
+ GenUsageStat genUsageStat[4] = {0, 0, 0, 0};
+
+ for (DWORD n = 0; n < dwNHeaps; n ++)
+ {
+ DacpGcHeapDetails heapDetails;
+ if (heapDetails.Request(g_sos, heapAddrs[n]) != S_OK)
+ {
+ ExtErr("Error requesting gc heap details\n");
+ return Status;
+ }
+
+ if (GCHeapUsageStats(heapDetails, bIncUnreachable, &hpUsage[n]))
+ {
+ for (int i = 0; i < 4; ++i)
+ {
+ genUsageStat[i].allocd += hpUsage[n].genUsage[i].allocd;
+ genUsageStat[i].freed += hpUsage[n].genUsage[i].freed;
+ if (bIncUnreachable)
+ {
+ genUsageStat[i].unrooted += hpUsage[n].genUsage[i].unrooted;
+ }
+ }
+ }
+ }
+
+ for (DWORD n = 0; n < dwNHeaps; n ++)
+ {
+ ExtOut("Heap%-4d %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u\n", n,
+ hpUsage[n].genUsage[0].allocd, hpUsage[n].genUsage[1].allocd,
+ hpUsage[n].genUsage[2].allocd, hpUsage[n].genUsage[3].allocd);
+ }
+ ExtOut("Total %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u\n",
+ genUsageStat[0].allocd, genUsageStat[1].allocd,
+ genUsageStat[2].allocd, genUsageStat[3].allocd);
+
+ ExtOut("\nFree space: Percentage\n");
+ for (DWORD n = 0; n < dwNHeaps; n ++)
+ {
+ ExtOut("Heap%-4d %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u", n,
+ hpUsage[n].genUsage[0].freed, hpUsage[n].genUsage[1].freed,
+ hpUsage[n].genUsage[2].freed, hpUsage[n].genUsage[3].freed);
+
+ tempf = ((float)(hpUsage[n].genUsage[0].freed+hpUsage[n].genUsage[1].freed+hpUsage[n].genUsage[2].freed)) /
+ (hpUsage[n].genUsage[0].allocd+hpUsage[n].genUsage[1].allocd+hpUsage[n].genUsage[2].allocd);
+ ExtOut("SOH:%3d%% LOH:%3d%%\n", (int)(100 * tempf),
+ (int)(100*((float)hpUsage[n].genUsage[3].freed) / (hpUsage[n].genUsage[3].allocd))
+ );
+ }
+ ExtOut("Total %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u\n",
+ genUsageStat[0].freed, genUsageStat[1].freed,
+ genUsageStat[2].freed, genUsageStat[3].freed);
+
+ if (bIncUnreachable)
+ {
+ ExtOut("\nUnrooted objects: Percentage\n");
+ for (DWORD n = 0; n < dwNHeaps; n ++)
+ {
+ ExtOut("Heap%-4d %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u", n,
+ hpUsage[n].genUsage[0].unrooted, hpUsage[n].genUsage[1].unrooted,
+ hpUsage[n].genUsage[2].unrooted, hpUsage[n].genUsage[3].unrooted);
+
+ tempf = ((float)(hpUsage[n].genUsage[0].unrooted+hpUsage[n].genUsage[1].unrooted+hpUsage[n].genUsage[2].unrooted)) /
+ (hpUsage[n].genUsage[0].allocd+hpUsage[n].genUsage[1].allocd+hpUsage[n].genUsage[2].allocd);
+ ExtOut("SOH:%3d%% LOH:%3d%%\n", (int)(100 * tempf),
+ (int)(100*((float)hpUsage[n].genUsage[3].unrooted) / (hpUsage[n].genUsage[3].allocd)));
+ }
+ ExtOut("Total %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u\n",
+ genUsageStat[0].unrooted, genUsageStat[1].unrooted,
+ genUsageStat[2].unrooted, genUsageStat[3].unrooted);
+ }
+
+ }
+
+ return Status;
+
+#else
+
+ _ASSERTE(false);
+ return E_FAIL;
+
+#endif // FEATURE_PAL
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function dumps what is in the syncblock cache. By default *
+* it dumps all active syncblocks. Using -all to dump all syncblocks
+* *
+\**********************************************************************/
+DECLARE_API(SyncBlk)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ BOOL bDumpAll = FALSE;
+ size_t nbAsked = 0;
+ BOOL dml = FALSE;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"-all", &bDumpAll, COBOOL, FALSE},
+ {"/d", &dml, COBOOL, FALSE}
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&nbAsked, COSIZE_T}
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+ DacpSyncBlockData syncBlockData;
+ if (syncBlockData.Request(g_sos,1) != S_OK)
+ {
+ ExtOut("Error requesting SyncBlk data\n");
+ return Status;
+ }
+
+ DWORD dwCount = syncBlockData.SyncBlockCount;
+
+ ExtOut("Index" WIN64_8SPACES " SyncBlock MonitorHeld Recursion Owning Thread Info" WIN64_8SPACES " SyncBlock Owner\n");
+ ULONG freeCount = 0;
+ ULONG CCWCount = 0;
+ ULONG RCWCount = 0;
+ ULONG CFCount = 0;
+ for (DWORD nb = 1; nb <= dwCount; nb++)
+ {
+ if (IsInterrupt())
+ return Status;
+
+ if (nbAsked && nb != nbAsked)
+ {
+ continue;
+ }
+
+ if (syncBlockData.Request(g_sos,nb) != S_OK)
+ {
+ ExtOut("SyncBlock %d is invalid%s\n", nb,
+ (nb != nbAsked) ? ", continuing..." : "");
+ continue;
+ }
+
+ BOOL bPrint = (bDumpAll || nb == nbAsked || (syncBlockData.MonitorHeld > 0 && !syncBlockData.bFree));
+
+ if (bPrint)
+ {
+ ExtOut("%5d ", nb);
+ if (!syncBlockData.bFree || nb != nbAsked)
+ {
+ ExtOut("%p ", syncBlockData.SyncBlockPointer);
+ ExtOut("%11d ", syncBlockData.MonitorHeld);
+ ExtOut("%9d ", syncBlockData.Recursion);
+ ExtOut("%p ", syncBlockData.HoldingThread);
+
+ if (syncBlockData.HoldingThread == ~0ul)
+ {
+ ExtOut(" orphaned ");
+ }
+ else if (syncBlockData.HoldingThread != NULL)
+ {
+ DacpThreadData Thread;
+ if ((Status = Thread.Request(g_sos, syncBlockData.HoldingThread)) != S_OK)
+ {
+ ExtOut("Failed to request Thread at %p\n", syncBlockData.HoldingThread);
+ return Status;
+ }
+
+ DMLOut(DMLThreadID(Thread.osThreadId));
+ ULONG id;
+ if (g_ExtSystem->GetThreadIdBySystemId(Thread.osThreadId, &id) == S_OK)
+ {
+ ExtOut("%4d ", id);
+ }
+ else
+ {
+ ExtOut(" XXX ");
+ }
+ }
+ else
+ {
+ ExtOut(" none ");
+ }
+
+ if (syncBlockData.bFree)
+ {
+ ExtOut(" %8d", 0); // TODO: do we need to print the free synctable list?
+ }
+ else
+ {
+ sos::Object obj = TO_TADDR(syncBlockData.Object);
+ DMLOut(" %s %S", DMLObject(syncBlockData.Object), obj.GetTypeName());
+ }
+ }
+ }
+
+ if (syncBlockData.bFree)
+ {
+ freeCount ++;
+ if (bPrint) {
+ ExtOut(" Free");
+ }
+ }
+ else
+ {
+#ifdef FEATURE_COMINTEROP
+ if (syncBlockData.COMFlags) {
+ switch (syncBlockData.COMFlags) {
+ case SYNCBLOCKDATA_COMFLAGS_CCW:
+ CCWCount ++;
+ break;
+ case SYNCBLOCKDATA_COMFLAGS_RCW:
+ RCWCount ++;
+ break;
+ case SYNCBLOCKDATA_COMFLAGS_CF:
+ CFCount ++;
+ break;
+ }
+ }
+#endif // FEATURE_COMINTEROP
+ }
+
+ if (syncBlockData.MonitorHeld > 1)
+ {
+ // TODO: implement this
+ /*
+ ExtOut(" ");
+ DWORD_PTR pHead = (DWORD_PTR)vSyncBlock.m_Link.m_pNext;
+ DWORD_PTR pNext = pHead;
+ Thread vThread;
+
+ while (1)
+ {
+ if (IsInterrupt())
+ return Status;
+ DWORD_PTR pWaitEventLink = pNext - offsetLinkSB;
+ WaitEventLink vWaitEventLink;
+ vWaitEventLink.Fill(pWaitEventLink);
+ if (!CallStatus) {
+ break;
+ }
+ DWORD_PTR dwAddr = (DWORD_PTR)vWaitEventLink.m_Thread;
+ ExtOut("%x ", dwAddr);
+ vThread.Fill (dwAddr);
+ if (!CallStatus) {
+ break;
+ }
+ if (bPrint)
+ DMLOut("%s,", DMLThreadID(vThread.m_OSThreadId));
+ pNext = (DWORD_PTR)vWaitEventLink.m_LinkSB.m_pNext;
+ if (pNext == 0)
+ break;
+ }
+ */
+ }
+
+ if (bPrint)
+ ExtOut("\n");
+ }
+
+ ExtOut("-----------------------------\n");
+ ExtOut("Total %d\n", dwCount);
+ ExtOut("CCW %d\n", CCWCount);
+ ExtOut("RCW %d\n", RCWCount);
+ ExtOut("ComClassFactory %d\n", CFCount);
+ ExtOut("Free %d\n", freeCount);
+
+ return Status;
+}
+
+#ifdef FEATURE_COMINTEROP
+struct VisitRcwArgs
+{
+ BOOL bDetail;
+ UINT MTACount;
+ UINT STACount;
+ ULONG FTMCount;
+};
+
+void VisitRcw(CLRDATA_ADDRESS RCW,CLRDATA_ADDRESS Context,CLRDATA_ADDRESS Thread, BOOL bIsFreeThreaded, LPVOID token)
+{
+ VisitRcwArgs *pArgs = (VisitRcwArgs *) token;
+
+ if (pArgs->bDetail)
+ {
+ if (pArgs->MTACount == 0 && pArgs->STACount == 0 && pArgs->FTMCount == 0)
+ {
+ // First time, print a header
+ ExtOut("RuntimeCallableWrappers (RCW) to be cleaned:\n");
+ ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s Apartment\n",
+ "RCW", "CONTEXT", "THREAD");
+ }
+ LPCSTR szThreadApartment;
+ if (bIsFreeThreaded)
+ {
+ szThreadApartment = "(FreeThreaded)";
+ pArgs->FTMCount++;
+ }
+ else if (Thread == NULL)
+ {
+ szThreadApartment = "(MTA)";
+ pArgs->MTACount++;
+ }
+ else
+ {
+ szThreadApartment = "(STA)";
+ pArgs->STACount++;
+ }
+
+ ExtOut("%" POINTERSIZE "p %" POINTERSIZE "p %" POINTERSIZE "p %9s\n",
+ SOS_PTR(RCW),
+ SOS_PTR(Context),
+ SOS_PTR(Thread),
+ szThreadApartment);
+ }
+}
+
+DECLARE_API(RCWCleanupList)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ DWORD_PTR p_CleanupList = GetExpression(args);
+
+ VisitRcwArgs travArgs;
+ ZeroMemory(&travArgs,sizeof(VisitRcwArgs));
+ travArgs.bDetail = TRUE;
+
+ // We need to detect when !RCWCleanupList is called with an expression which evaluates to 0
+ // (to print out an Invalid parameter message), but at the same time we need to allow an
+ // empty argument list which would result in p_CleanupList equaling 0.
+ if (p_CleanupList || strlen(args) == 0)
+ {
+ HRESULT hr = g_sos->TraverseRCWCleanupList(p_CleanupList, (VISITRCWFORCLEANUP)VisitRcw, &travArgs);
+
+ if (SUCCEEDED(hr))
+ {
+ ExtOut("Free-Threaded Interfaces to be released: %d\n", travArgs.FTMCount);
+ ExtOut("MTA Interfaces to be released: %d\n", travArgs.MTACount);
+ ExtOut("STA Interfaces to be released: %d\n", travArgs.STACount);
+ }
+ else
+ {
+ ExtOut("An error occurred while traversing the cleanup list.\n");
+ }
+ }
+ else
+ {
+ ExtOut("Invalid parameter %s\n", args);
+ }
+
+ return Status;
+}
+#endif // FEATURE_COMINTEROP
+
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to dump the contents of the finalizer *
+* queue. *
+* *
+\**********************************************************************/
+DECLARE_API(FinalizeQueue)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ BOOL bDetail = FALSE;
+ BOOL bAllReady = FALSE;
+ BOOL bShort = FALSE;
+ BOOL dml = FALSE;
+ TADDR taddrMT = 0;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"-detail", &bDetail, COBOOL, FALSE},
+ {"-allReady", &bAllReady, COBOOL, FALSE},
+ {"-short", &bShort, COBOOL, FALSE},
+ {"/d", &dml, COBOOL, FALSE},
+ {"-mt", &taddrMT, COHEX, TRUE},
+ };
+
+ if (!GetCMDOption(args, option, _countof(option), NULL, 0, NULL))
+ {
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+ if (!bShort)
+ {
+ DacpSyncBlockCleanupData dsbcd;
+ CLRDATA_ADDRESS sbCurrent = NULL;
+ ULONG cleanCount = 0;
+ while ((dsbcd.Request(g_sos,sbCurrent) == S_OK) && dsbcd.SyncBlockPointer)
+ {
+ if (bDetail)
+ {
+ if (cleanCount == 0) // print first time only
+ {
+ ExtOut("SyncBlocks to be cleaned by the finalizer thread:\n");
+ ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s\n",
+ "SyncBlock", "RCW", "CCW", "ComClassFactory");
+ }
+
+ ExtOut("%" POINTERSIZE "p %" POINTERSIZE "p %" POINTERSIZE "p %" POINTERSIZE "p\n",
+ (ULONG64) dsbcd.SyncBlockPointer,
+ (ULONG64) dsbcd.blockRCW,
+ (ULONG64) dsbcd.blockCCW,
+ (ULONG64) dsbcd.blockClassFactory);
+ }
+
+ cleanCount++;
+ sbCurrent = dsbcd.nextSyncBlock;
+ if (sbCurrent == NULL)
+ {
+ break;
+ }
+ }
+
+ ExtOut("SyncBlocks to be cleaned up: %d\n", cleanCount);
+
+#ifdef FEATURE_COMINTEROP
+ VisitRcwArgs travArgs;
+ ZeroMemory(&travArgs,sizeof(VisitRcwArgs));
+ travArgs.bDetail = bDetail;
+ g_sos->TraverseRCWCleanupList(0, (VISITRCWFORCLEANUP) VisitRcw, &travArgs);
+ ExtOut("Free-Threaded Interfaces to be released: %d\n", travArgs.FTMCount);
+ ExtOut("MTA Interfaces to be released: %d\n", travArgs.MTACount);
+ ExtOut("STA Interfaces to be released: %d\n", travArgs.STACount);
+#endif // FEATURE_COMINTEROP
+
+// noRCW:
+ ExtOut("----------------------------------\n");
+ }
+
+ // GC Heap
+ DWORD dwNHeaps = GetGcHeapCount();
+
+ HeapStat hpStat;
+
+ if (!IsServerBuild())
+ {
+ DacpGcHeapDetails heapDetails;
+ if (heapDetails.Request(g_sos) != S_OK)
+ {
+ ExtOut("Error requesting details\n");
+ return Status;
+ }
+
+ GatherOneHeapFinalization(heapDetails, &hpStat, bAllReady, bShort);
+ }
+ else
+ {
+ DWORD dwAllocSize;
+ if (!ClrSafeInt<DWORD>::multiply(sizeof(CLRDATA_ADDRESS), dwNHeaps, dwAllocSize))
+ {
+ ExtOut("Failed to get GCHeaps: integer overflow\n");
+ return Status;
+ }
+
+ CLRDATA_ADDRESS *heapAddrs = (CLRDATA_ADDRESS*)alloca(dwAllocSize);
+ if (g_sos->GetGCHeapList(dwNHeaps, heapAddrs, NULL) != S_OK)
+ {
+ ExtOut("Failed to get GCHeaps\n");
+ return Status;
+ }
+
+ for (DWORD n = 0; n < dwNHeaps; n ++)
+ {
+ DacpGcHeapDetails heapDetails;
+ if (heapDetails.Request(g_sos, heapAddrs[n]) != S_OK)
+ {
+ ExtOut("Error requesting details\n");
+ return Status;
+ }
+
+ ExtOut("------------------------------\n");
+ ExtOut("Heap %d\n", n);
+ GatherOneHeapFinalization(heapDetails, &hpStat, bAllReady, bShort);
+ }
+ }
+
+ if (!bShort)
+ {
+ if (bAllReady)
+ {
+ PrintGCStat(&hpStat, "Statistics for all finalizable objects that are no longer rooted:\n");
+ }
+ else
+ {
+ PrintGCStat(&hpStat, "Statistics for all finalizable objects (including all objects ready for finalization):\n");
+ }
+ }
+
+ return Status;
+}
+
+#endif // FEATURE_PAL
+
+enum {
+ // These are the values set in m_dwTransientFlags.
+ // Note that none of these flags survive a prejit save/restore.
+
+ M_CRST_NOTINITIALIZED = 0x00000001, // Used to prevent destruction of garbage m_crst
+ M_LOOKUPCRST_NOTINITIALIZED = 0x00000002,
+
+ SUPPORTS_UPDATEABLE_METHODS = 0x00000020,
+ CLASSES_FREED = 0x00000040,
+ HAS_PHONY_IL_RVAS = 0x00000080,
+ IS_EDIT_AND_CONTINUE = 0x00000200,
+};
+
+void ModuleMapTraverse(UINT index, CLRDATA_ADDRESS methodTable, LPVOID token)
+{
+ ULONG32 rid = (ULONG32)(size_t)token;
+ NameForMT_s(TO_TADDR(methodTable), g_mdName, mdNameLen);
+
+ DMLOut("%s 0x%08x %S\n", DMLMethodTable(methodTable), (ULONG32)TokenFromRid(rid, index), g_mdName);
+}
+
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to dump the contents of a Module *
+* for a given address *
+* *
+\**********************************************************************/
+DECLARE_API(DumpModule)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+
+ DWORD_PTR p_ModuleAddr = NULL;
+ BOOL bMethodTables = FALSE;
+ BOOL dml = FALSE;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"-mt", &bMethodTables, COBOOL, FALSE},
+#ifndef FEATURE_PAL
+ {"/d", &dml, COBOOL, FALSE}
+#endif
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&p_ModuleAddr, COHEX}
+ };
+
+ size_t nArg;
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+ if (nArg != 1)
+ {
+ ExtOut("Usage: DumpModule [-mt] <Module Address>\n");
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+ DacpModuleData module;
+ if ((Status=module.Request(g_sos, TO_CDADDR(p_ModuleAddr)))!=S_OK)
+ {
+ ExtOut("Fail to fill Module %p\n", SOS_PTR(p_ModuleAddr));
+ return Status;
+ }
+
+ WCHAR FileName[MAX_LONGPATH];
+ FileNameForModule (&module, FileName);
+ ExtOut("Name: %S\n", FileName[0] ? FileName : W("Unknown Module"));
+
+ ExtOut("Attributes: ");
+ if (module.bIsPEFile)
+ ExtOut("PEFile ");
+ if (module.bIsReflection)
+ ExtOut("Reflection ");
+ if (module.dwTransientFlags & SUPPORTS_UPDATEABLE_METHODS)
+ ExtOut("SupportsUpdateableMethods");
+ ExtOut("\n");
+
+ DMLOut("Assembly: %s\n", DMLAssembly(module.Assembly));
+
+ ExtOut("LoaderHeap: %p\n", SOS_PTR(module.pLookupTableHeap));
+ ExtOut("TypeDefToMethodTableMap: %p\n", SOS_PTR(module.TypeDefToMethodTableMap));
+ ExtOut("TypeRefToMethodTableMap: %p\n", SOS_PTR(module.TypeRefToMethodTableMap));
+ ExtOut("MethodDefToDescMap: %p\n", SOS_PTR(module.MethodDefToDescMap));
+ ExtOut("FieldDefToDescMap: %p\n", SOS_PTR(module.FieldDefToDescMap));
+ ExtOut("MemberRefToDescMap: %p\n", SOS_PTR(module.MemberRefToDescMap));
+ ExtOut("FileReferencesMap: %p\n", SOS_PTR(module.FileReferencesMap));
+ ExtOut("AssemblyReferencesMap: %p\n", SOS_PTR(module.ManifestModuleReferencesMap));
+
+ if (module.ilBase && module.metadataStart)
+ ExtOut("MetaData start address: %p (%d bytes)\n", SOS_PTR(module.metadataStart), module.metadataSize);
+
+ if (bMethodTables)
+ {
+ ExtOut("\nTypes defined in this module\n\n");
+ ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %s\n", "MT", "TypeDef", "Name");
+
+ ExtOut("------------------------------------------------------------------------------\n");
+ g_sos->TraverseModuleMap(TYPEDEFTOMETHODTABLE, TO_CDADDR(p_ModuleAddr), ModuleMapTraverse, (LPVOID)mdTypeDefNil);
+
+ ExtOut("\nTypes referenced in this module\n\n");
+ ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %s\n", "MT", "TypeRef", "Name");
+
+ ExtOut("------------------------------------------------------------------------------\n");
+ g_sos->TraverseModuleMap(TYPEREFTOMETHODTABLE, TO_CDADDR(p_ModuleAddr), ModuleMapTraverse, (LPVOID)mdTypeDefNil);
+ }
+
+ return Status;
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to dump the contents of a Domain *
+* for a given address *
+* *
+\**********************************************************************/
+DECLARE_API(DumpDomain)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ DWORD_PTR p_DomainAddr = 0;
+ BOOL dml = FALSE;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+#ifndef FEATURE_PAL
+ {"/d", &dml, COBOOL, FALSE},
+#endif
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&p_DomainAddr, COHEX},
+ };
+ size_t nArg;
+
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+
+ DacpAppDomainStoreData adsData;
+ if ((Status=adsData.Request(g_sos))!=S_OK)
+ {
+ ExtOut("Unable to get AppDomain information\n");
+ return Status;
+ }
+
+ if (p_DomainAddr)
+ {
+ DacpAppDomainData appDomain1;
+ if ((Status=appDomain1.Request(g_sos, TO_CDADDR(p_DomainAddr)))!=S_OK)
+ {
+ ExtOut("Fail to fill AppDomain\n");
+ return Status;
+ }
+
+ ExtOut("--------------------------------------\n");
+
+ if (p_DomainAddr == adsData.sharedDomain)
+ {
+ DMLOut("Shared Domain: %s\n", DMLDomain(adsData.sharedDomain));
+ }
+ else if (p_DomainAddr == adsData.systemDomain)
+ {
+ DMLOut("System Domain: %s\n", DMLDomain(adsData.systemDomain));
+ }
+ else
+ {
+ DMLOut("Domain %d:%s %s\n", appDomain1.dwId, (appDomain1.dwId >= 10) ? "" : " ", DMLDomain(p_DomainAddr));
+ }
+
+ DomainInfo(&appDomain1);
+ return Status;
+ }
+
+ ExtOut("--------------------------------------\n");
+ DMLOut("System Domain: %s\n", DMLDomain(adsData.systemDomain));
+ DacpAppDomainData appDomain;
+ if ((Status=appDomain.Request(g_sos,adsData.systemDomain))!=S_OK)
+ {
+ ExtOut("Unable to get system domain info.\n");
+ return Status;
+ }
+ DomainInfo(&appDomain);
+
+ ExtOut("--------------------------------------\n");
+ DMLOut("Shared Domain: %s\n", DMLDomain(adsData.sharedDomain));
+ if ((Status=appDomain.Request(g_sos, adsData.sharedDomain))!=S_OK)
+ {
+ ExtOut("Unable to get shared domain info\n");
+ return Status;
+ }
+ DomainInfo(&appDomain);
+
+ ArrayHolder<CLRDATA_ADDRESS> pArray = new NOTHROW CLRDATA_ADDRESS[adsData.DomainCount];
+ if (pArray==NULL)
+ {
+ ReportOOM();
+ return Status;
+ }
+
+ if ((Status=g_sos->GetAppDomainList(adsData.DomainCount, pArray, NULL))!=S_OK)
+ {
+ ExtOut("Unable to get array of AppDomains\n");
+ return Status;
+ }
+
+ for (int n=0;n<adsData.DomainCount;n++)
+ {
+ if (IsInterrupt())
+ break;
+
+ if ((Status=appDomain.Request(g_sos, pArray[n])) != S_OK)
+ {
+ ExtOut("Failed to get appdomain %p, error %lx\n", SOS_PTR(pArray[n]), Status);
+ return Status;
+ }
+
+ ExtOut("--------------------------------------\n");
+ DMLOut("Domain %d:%s %s\n", appDomain.dwId, (appDomain.dwId >= 10) ? "" : " ", DMLDomain(pArray[n]));
+ DomainInfo(&appDomain);
+ }
+
+ return Status;
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to dump the contents of a Assembly *
+* for a given address *
+* *
+\**********************************************************************/
+DECLARE_API(DumpAssembly)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ DWORD_PTR p_AssemblyAddr = 0;
+ BOOL dml = FALSE;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+#ifndef FEATURE_PAL
+ {"/d", &dml, COBOOL, FALSE},
+#endif
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&p_AssemblyAddr, COHEX},
+ };
+ size_t nArg;
+
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+
+ if (p_AssemblyAddr == 0)
+ {
+ ExtOut("Invalid Assembly %s\n", args);
+ return Status;
+ }
+
+ DacpAssemblyData Assembly;
+ if ((Status=Assembly.Request(g_sos, TO_CDADDR(p_AssemblyAddr)))!=S_OK)
+ {
+ ExtOut("Fail to fill Assembly\n");
+ return Status;
+ }
+ DMLOut("Parent Domain: %s\n", DMLDomain(Assembly.ParentDomain));
+ if (g_sos->GetAssemblyName(TO_CDADDR(p_AssemblyAddr), mdNameLen, g_mdName, NULL)==S_OK)
+ ExtOut("Name: %S\n", g_mdName);
+ else
+ ExtOut("Name: Unknown\n");
+
+ AssemblyInfo(&Assembly);
+ return Status;
+}
+
+
+String GetHostingCapabilities(DWORD hostConfig)
+{
+ String result;
+
+ bool bAnythingPrinted = false;
+
+#define CHK_AND_PRINT(hType,hStr) \
+ if (hostConfig & (hType)) { \
+ if (bAnythingPrinted) result += ", "; \
+ result += hStr; \
+ bAnythingPrinted = true; \
+ }
+
+ CHK_AND_PRINT(CLRMEMORYHOSTED, "Memory");
+ CHK_AND_PRINT(CLRTASKHOSTED, "Task");
+ CHK_AND_PRINT(CLRSYNCHOSTED, "Sync");
+ CHK_AND_PRINT(CLRTHREADPOOLHOSTED, "Threadpool");
+ CHK_AND_PRINT(CLRIOCOMPLETIONHOSTED, "IOCompletion");
+ CHK_AND_PRINT(CLRASSEMBLYHOSTED, "Assembly");
+ CHK_AND_PRINT(CLRGCHOSTED, "GC");
+ CHK_AND_PRINT(CLRSECURITYHOSTED, "Security");
+
+#undef CHK_AND_PRINT
+
+ return result;
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to dump the managed threads *
+* *
+\**********************************************************************/
+HRESULT PrintThreadsFromThreadStore(BOOL bMiniDump, BOOL bPrintLiveThreadsOnly)
+{
+ HRESULT Status;
+
+ DacpThreadStoreData ThreadStore;
+ if ((Status = ThreadStore.Request(g_sos)) != S_OK)
+ {
+ Print("Failed to request ThreadStore\n");
+ return Status;
+ }
+
+ TableOutput table(2, 17);
+
+ table.WriteRow("ThreadCount:", Decimal(ThreadStore.threadCount));
+ table.WriteRow("UnstartedThread:", Decimal(ThreadStore.unstartedThreadCount));
+ table.WriteRow("BackgroundThread:", Decimal(ThreadStore.backgroundThreadCount));
+ table.WriteRow("PendingThread:", Decimal(ThreadStore.pendingThreadCount));
+ table.WriteRow("DeadThread:", Decimal(ThreadStore.deadThreadCount));
+
+ if (ThreadStore.fHostConfig & ~CLRHOSTED)
+ {
+ String hosting = "yes";
+
+ hosting += " (";
+ hosting += GetHostingCapabilities(ThreadStore.fHostConfig);
+ hosting += ")";
+
+ table.WriteRow("Hosted Runtime:", hosting);
+ }
+ else
+ {
+ table.WriteRow("Hosted Runtime:", "no");
+ }
+
+ const bool hosted = (ThreadStore.fHostConfig & CLRTASKHOSTED) != 0;
+ table.ReInit(hosted ? 12 : 11, POINTERSIZE_HEX);
+ table.SetWidths(10, 4, 4, 4, _max(9, POINTERSIZE_HEX),
+ 8, 11, 1+POINTERSIZE_HEX*2, POINTERSIZE_HEX,
+ 5, 3, POINTERSIZE_HEX);
+
+ table.SetColAlignment(0, AlignRight);
+ table.SetColAlignment(1, AlignRight);
+ table.SetColAlignment(2, AlignRight);
+ table.SetColAlignment(4, AlignRight);
+
+ table.WriteColumn(8, "Lock");
+ table.WriteRow("", "ID", "OSID", "ThreadOBJ", "State", "GC Mode", "GC Alloc Context", "Domain", "Count", "Apt");
+
+ if (hosted)
+ table.WriteColumn("Fiber");
+
+ table.WriteColumn("Exception");
+
+ DacpThreadData Thread;
+ CLRDATA_ADDRESS CurThread = ThreadStore.firstThread;
+ while (CurThread)
+ {
+ if (IsInterrupt())
+ break;
+
+ if ((Status = Thread.Request(g_sos, CurThread)) != S_OK)
+ {
+ PrintLn("Failed to request Thread at ", Pointer(CurThread));
+ return Status;
+ }
+
+ BOOL bSwitchedOutFiber = Thread.osThreadId == SWITCHED_OUT_FIBER_OSID;
+ if (!IsKernelDebugger())
+ {
+ ULONG id = 0;
+
+ if (bSwitchedOutFiber)
+ {
+ table.WriteColumn(0, "<<<< ");
+ }
+ else if (g_ExtSystem->GetThreadIdBySystemId(Thread.osThreadId, &id) == S_OK)
+ {
+ table.WriteColumn(0, Decimal(id));
+ }
+ else if (bPrintLiveThreadsOnly)
+ {
+ CurThread = Thread.nextThread;
+ continue;
+ }
+ else
+ {
+ table.WriteColumn(0, "XXXX ");
+ }
+ }
+
+ table.WriteColumn(1, Decimal(Thread.corThreadId));
+ table.WriteColumn(2, ThreadID(bSwitchedOutFiber ? 0 : Thread.osThreadId));
+ table.WriteColumn(3, Pointer(CurThread));
+ table.WriteColumn(4, ThreadState(Thread.state));
+ table.WriteColumn(5, Thread.preemptiveGCDisabled == 1 ? "Cooperative" : "Preemptive");
+ table.WriteColumnFormat(6, "%p:%p", Thread.allocContextPtr, Thread.allocContextLimit);
+
+ if (Thread.domain)
+ {
+ table.WriteColumn(7, AppDomainPtr(Thread.domain));
+ }
+ else
+ {
+ CLRDATA_ADDRESS domain = 0;
+ if (FAILED(g_sos->GetDomainFromContext(Thread.context, &domain)))
+ table.WriteColumn(7, "<error>");
+ else
+ table.WriteColumn(7, AppDomainPtr(domain));
+ }
+
+ table.WriteColumn(8, Decimal(Thread.lockCount));
+
+ // Apartment state
+#ifndef FEATURE_PAL
+ DWORD_PTR OleTlsDataAddr;
+ if (!bSwitchedOutFiber
+ && SafeReadMemory(Thread.teb + offsetof(TEB, ReservedForOle),
+ &OleTlsDataAddr,
+ sizeof(OleTlsDataAddr), NULL) && OleTlsDataAddr != 0)
+ {
+ DWORD AptState;
+ if (SafeReadMemory(OleTlsDataAddr+offsetof(SOleTlsData,dwFlags),
+ &AptState,
+ sizeof(AptState), NULL))
+ {
+ if (AptState & OLETLS_APARTMENTTHREADED)
+ table.WriteColumn(9, "STA");
+ else if (AptState & OLETLS_MULTITHREADED)
+ table.WriteColumn(9, "MTA");
+ else if (AptState & OLETLS_INNEUTRALAPT)
+ table.WriteColumn(9, "NTA");
+ else
+ table.WriteColumn(9, "Ukn");
+ }
+ else
+ {
+ table.WriteColumn(9, "Ukn");
+ }
+ }
+ else
+#endif // FEATURE_PAL
+ table.WriteColumn(9, "Ukn");
+
+ if (hosted)
+ table.WriteColumn(10, Thread.fiberData);
+
+ WString lastCol;
+ if (CurThread == ThreadStore.finalizerThread)
+ lastCol += W("(Finalizer) ");
+ if (CurThread == ThreadStore.gcThread)
+ lastCol += W("(GC) ");
+
+ const int TS_TPWorkerThread = 0x01000000; // is this a threadpool worker thread?
+ const int TS_CompletionPortThread = 0x08000000; // is this is a completion port thread?
+
+ if (Thread.state & TS_TPWorkerThread)
+ lastCol += W("(Threadpool Worker) ");
+ else if (Thread.state & TS_CompletionPortThread)
+ lastCol += W("(Threadpool Completion Port) ");
+
+
+ TADDR taLTOH;
+ if (Thread.lastThrownObjectHandle && SafeReadMemory(TO_TADDR(Thread.lastThrownObjectHandle),
+ &taLTOH, sizeof(taLTOH), NULL) && taLTOH)
+ {
+ TADDR taMT;
+ if (SafeReadMemory(taLTOH, &taMT, sizeof(taMT), NULL))
+ {
+ if (NameForMT_s(taMT, g_mdName, mdNameLen))
+ lastCol += WString(g_mdName) + W(" ") + ExceptionPtr(taLTOH);
+ else
+ lastCol += WString(W("<Invalid Object> (")) + Pointer(taLTOH) + W(")");
+
+ // Print something if there are nested exceptions on the thread
+ if (Thread.firstNestedException)
+ lastCol += W(" (nested exceptions)");
+ }
+ }
+
+ table.WriteColumn(lastCol);
+ CurThread = Thread.nextThread;
+ }
+
+ return Status;
+}
+
+#ifndef FEATURE_PAL
+HRESULT PrintSpecialThreads()
+{
+ Print("\n");
+
+ DWORD dwCLRTLSDataIndex = 0;
+ HRESULT Status = g_sos->GetTLSIndex(&dwCLRTLSDataIndex);
+
+ if (!SUCCEEDED (Status))
+ {
+ Print("Failed to retrieve Tls Data index\n");
+ return Status;
+ }
+
+
+ ULONG ulOriginalThreadID = 0;
+ Status = g_ExtSystem->GetCurrentThreadId (&ulOriginalThreadID);
+ if (!SUCCEEDED (Status))
+ {
+ Print("Failed to require current Thread ID\n");
+ return Status;
+ }
+
+ ULONG ulTotalThreads = 0;
+ Status = g_ExtSystem->GetNumberThreads (&ulTotalThreads);
+ if (!SUCCEEDED (Status))
+ {
+ Print("Failed to require total thread number\n");
+ return Status;
+ }
+
+ TableOutput table(3, 4, AlignRight, 5);
+ table.WriteRow("", "OSID", "Special thread type");
+
+ for (ULONG ulThread = 0; ulThread < ulTotalThreads; ulThread++)
+ {
+ ULONG Id = 0;
+ ULONG SysId = 0;
+ HRESULT threadStatus = g_ExtSystem->GetThreadIdsByIndex(ulThread, 1, &Id, &SysId);
+ if (!SUCCEEDED (threadStatus))
+ {
+ PrintLn("Failed to get thread ID for thread ", Decimal(ulThread));
+ continue;
+ }
+
+ threadStatus = g_ExtSystem->SetCurrentThreadId(Id);
+ if (!SUCCEEDED (threadStatus))
+ {
+ PrintLn("Failed to switch to thread ", ThreadID(SysId));
+ continue;
+ }
+
+ CLRDATA_ADDRESS cdaTeb = 0;
+ threadStatus = g_ExtSystem->GetCurrentThreadTeb(&cdaTeb);
+ if (!SUCCEEDED (threadStatus))
+ {
+ PrintLn("Failed to get Teb for Thread ", ThreadID(SysId));
+ continue;
+ }
+
+ TADDR CLRTLSDataAddr = 0;
+
+#ifdef FEATURE_IMPLICIT_TLS
+ TADDR tlsArrayAddr = NULL;
+ if (!SafeReadMemory (TO_TADDR(cdaTeb) + WINNT_OFFSETOF__TEB__ThreadLocalStoragePointer , &tlsArrayAddr, sizeof (void**), NULL))
+ {
+ PrintLn("Failed to get Tls expansion slots for thread ", ThreadID(SysId));
+ continue;
+ }
+
+ TADDR moduleTlsDataAddr = 0;
+
+ if (!SafeReadMemory (tlsArrayAddr + sizeof (void*) * dwCLRTLSDataIndex, &moduleTlsDataAddr, sizeof (void**), NULL))
+ {
+ PrintLn("Failed to get Tls expansion slots for thread ", ThreadID(SysId));
+ continue;
+ }
+
+ CLRTLSDataAddr = moduleTlsDataAddr + OFFSETOF__TLS__tls_EETlsData;
+#else
+ if (dwCLRTLSDataIndex < TLS_MINIMUM_AVAILABLE)
+ {
+ CLRTLSDataAddr = TO_TADDR(cdaTeb) + offsetof(TEB, TlsSlots) + sizeof (void*) * dwCLRTLSDataIndex;
+ }
+ else
+ {
+ //if TLS index is bigger than TLS_MINIMUM_AVAILABLE, the TLS slot lives in ExpansionSlots
+ TADDR TebExpsionAddr = NULL;
+ if (!SafeReadMemory (TO_TADDR(cdaTeb) + offsetof(TEB, TlsExpansionSlots) , &TebExpsionAddr, sizeof (void**), NULL))
+ {
+ PrintLn("Failed to get Tls expansion slots for thread ", ThreadID(SysId));
+ continue;
+ }
+
+ if (TebExpsionAddr == NULL)
+ {
+ continue;
+ }
+
+ CLRTLSDataAddr = TebExpsionAddr + sizeof (void*) * (dwCLRTLSDataIndex - TLS_MINIMUM_AVAILABLE);
+ }
+#endif // FEATURE_IMPLICIT_TLS
+
+ TADDR CLRTLSData = NULL;
+ if (!SafeReadMemory (CLRTLSDataAddr, &CLRTLSData, sizeof (TADDR), NULL))
+ {
+ PrintLn("Failed to get CLR Tls data for thread ", ThreadID(SysId));
+ continue;
+ }
+
+ if (CLRTLSData == NULL)
+ {
+ continue;
+ }
+
+ size_t ThreadType = 0;
+ if (!SafeReadMemory (CLRTLSData + sizeof (TADDR) * TlsIdx_ThreadType, &ThreadType, sizeof (size_t), NULL))
+ {
+ PrintLn("Failed to get thread type info not found for thread ", ThreadID(SysId));
+ continue;
+ }
+
+ if (ThreadType == 0)
+ {
+ continue;
+ }
+
+ table.WriteColumn(0, Decimal(Id));
+ table.WriteColumn(1, ThreadID(SysId));
+
+ String type;
+ if (ThreadType & ThreadType_GC)
+ {
+ type += "GC ";
+ }
+ if (ThreadType & ThreadType_Timer)
+ {
+ type += "Timer ";
+ }
+ if (ThreadType & ThreadType_Gate)
+ {
+ type += "Gate ";
+ }
+ if (ThreadType & ThreadType_DbgHelper)
+ {
+ type += "DbgHelper ";
+ }
+ if (ThreadType & ThreadType_Shutdown)
+ {
+ type += "Shutdown ";
+ }
+ if (ThreadType & ThreadType_DynamicSuspendEE)
+ {
+ type += "SuspendEE ";
+ }
+ if (ThreadType & ThreadType_Finalizer)
+ {
+ type += "Finalizer ";
+ }
+ if (ThreadType & ThreadType_ADUnloadHelper)
+ {
+ type += "ADUnloadHelper ";
+ }
+ if (ThreadType & ThreadType_ShutdownHelper)
+ {
+ type += "ShutdownHelper ";
+ }
+ if (ThreadType & ThreadType_Threadpool_IOCompletion)
+ {
+ type += "IOCompletion ";
+ }
+ if (ThreadType & ThreadType_Threadpool_Worker)
+ {
+ type += "ThreadpoolWorker ";
+ }
+ if (ThreadType & ThreadType_Wait)
+ {
+ type += "Wait ";
+ }
+ if (ThreadType & ThreadType_ProfAPI_Attach)
+ {
+ type += "ProfilingAPIAttach ";
+ }
+ if (ThreadType & ThreadType_ProfAPI_Detach)
+ {
+ type += "ProfilingAPIDetach ";
+ }
+
+ table.WriteColumn(2, type);
+ }
+
+ Status = g_ExtSystem->SetCurrentThreadId (ulOriginalThreadID);
+ if (!SUCCEEDED (Status))
+ {
+ ExtOut("Failed to switch to original thread\n");
+ return Status;
+ }
+
+ return Status;
+}
+#endif //FEATURE_PAL
+
+struct ThreadStateTable
+{
+ unsigned int State;
+ const char * Name;
+};
+static const struct ThreadStateTable ThreadStates[] =
+{
+ {0x1, "Thread Abort Requested"},
+ {0x2, "GC Suspend Pending"},
+ {0x4, "User Suspend Pending"},
+ {0x8, "Debug Suspend Pending"},
+ {0x10, "GC On Transitions"},
+ {0x20, "Legal to Join"},
+ {0x40, "Yield Requested"},
+ {0x80, "Hijacked by the GC"},
+ {0x100, "Blocking GC for Stack Overflow"},
+ {0x200, "Background"},
+ {0x400, "Unstarted"},
+ {0x800, "Dead"},
+ {0x1000, "CLR Owns"},
+ {0x2000, "CoInitialized"},
+ {0x4000, "In Single Threaded Apartment"},
+ {0x8000, "In Multi Threaded Apartment"},
+ {0x10000, "Reported Dead"},
+ {0x20000, "Fully initialized"},
+ {0x40000, "Task Reset"},
+ {0x80000, "Sync Suspended"},
+ {0x100000, "Debug Will Sync"},
+ {0x200000, "Stack Crawl Needed"},
+ {0x400000, "Suspend Unstarted"},
+ {0x800000, "Aborted"},
+ {0x1000000, "Thread Pool Worker Thread"},
+ {0x2000000, "Interruptible"},
+ {0x4000000, "Interrupted"},
+ {0x8000000, "Completion Port Thread"},
+ {0x10000000, "Abort Initiated"},
+ {0x20000000, "Finalized"},
+ {0x40000000, "Failed to Start"},
+ {0x80000000, "Detached"},
+};
+
+DECLARE_API(ThreadState)
+{
+ INIT_API_NODAC();
+
+ size_t state = GetExpression(args);
+ int count = 0;
+
+ if (state)
+ {
+
+ for (unsigned int i = 0; i < _countof(ThreadStates); ++i)
+ if (state & ThreadStates[i].State)
+ {
+ ExtOut(" %s\n", ThreadStates[i].Name);
+ count++;
+ }
+ }
+
+ // If we did not find any thread states, print out a message to let the user
+ // know that the function is working correctly.
+ if (count == 0)
+ ExtOut(" No thread states for '%s'\n", args);
+
+ return Status;
+}
+
+DECLARE_API(Threads)
+{
+ INIT_API();
+
+ BOOL bPrintSpecialThreads = FALSE;
+ BOOL bPrintLiveThreadsOnly = FALSE;
+ BOOL dml = FALSE;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"-special", &bPrintSpecialThreads, COBOOL, FALSE},
+ {"-live", &bPrintLiveThreadsOnly, COBOOL, FALSE},
+#ifndef FEATURE_PAL
+ {"/d", &dml, COBOOL, FALSE},
+#endif
+ };
+ if (!GetCMDOption(args, option, _countof(option), NULL, 0, NULL))
+ {
+ return Status;
+ }
+
+ // We need to support minidumps for this command.
+ BOOL bMiniDump = IsMiniDumpFile();
+
+ if (bMiniDump && bPrintSpecialThreads)
+ {
+ Print("Special thread information is not available in mini dumps.\n");
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+
+ try
+ {
+ Status = PrintThreadsFromThreadStore(bMiniDump, bPrintLiveThreadsOnly);
+ if (!bMiniDump && bPrintSpecialThreads)
+ {
+#ifdef FEATURE_PAL
+ Print("\n-special not supported.\n");
+#else //FEATURE_PAL
+ HRESULT Status2 = PrintSpecialThreads();
+ if (!SUCCEEDED(Status2))
+ Status = Status2;
+#endif //FEATURE_PAL
+ }
+ }
+ catch (sos::Exception &e)
+ {
+ ExtOut("%s\n", e.what());
+ }
+
+ return Status;
+}
+
+#ifndef FEATURE_PAL
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to dump the Watson Buckets. *
+* *
+\**********************************************************************/
+DECLARE_API(WatsonBuckets)
+{
+ INIT_API();
+
+ // We don't need to support minidumps for this command.
+ if (IsMiniDumpFile())
+ {
+ ExtOut("Not supported on mini dumps.\n");
+ }
+
+ // Get the current managed thread.
+ CLRDATA_ADDRESS threadAddr = GetCurrentManagedThread();
+ DacpThreadData Thread;
+
+ if ((threadAddr == NULL) || ((Status = Thread.Request(g_sos, threadAddr)) != S_OK))
+ {
+ ExtOut("The current thread is unmanaged\n");
+ return Status;
+ }
+
+ // Get the definition of GenericModeBlock.
+#include <msodw.h>
+ GenericModeBlock gmb;
+
+ if ((Status = g_sos->GetClrWatsonBuckets(threadAddr, &gmb)) != S_OK)
+ {
+ ExtOut("Can't get Watson Buckets\n");
+ return Status;
+ }
+
+ ExtOut("Watson Bucket parameters:\n");
+ ExtOut("b1: %S\n", gmb.wzP1);
+ ExtOut("b2: %S\n", gmb.wzP2);
+ ExtOut("b3: %S\n", gmb.wzP3);
+ ExtOut("b4: %S\n", gmb.wzP4);
+ ExtOut("b5: %S\n", gmb.wzP5);
+ ExtOut("b6: %S\n", gmb.wzP6);
+ ExtOut("b7: %S\n", gmb.wzP7);
+ ExtOut("b8: %S\n", gmb.wzP8);
+ ExtOut("b9: %S\n", gmb.wzP9);
+
+ return Status;
+} // WatsonBuckets()
+#endif // FEATURE_PAL
+
+struct PendingBreakpoint
+{
+ WCHAR szModuleName[MAX_LONGPATH];
+ WCHAR szFunctionName[mdNameLen];
+ WCHAR szFilename[MAX_LONGPATH];
+ DWORD lineNumber;
+ TADDR pModule;
+ DWORD ilOffset;
+ mdMethodDef methodToken;
+ void SetModule(TADDR module)
+ {
+ pModule = module;
+ }
+
+ bool ModuleMatches(TADDR compare)
+ {
+ return (compare == pModule);
+ }
+
+ PendingBreakpoint *pNext;
+ PendingBreakpoint() : lineNumber(0), ilOffset(0), methodToken(0), pNext(NULL)
+ {
+ szModuleName[0] = L'\0';
+ szFunctionName[0] = L'\0';
+ szFilename[0] = L'\0';
+ }
+};
+
+void IssueDebuggerBPCommand ( CLRDATA_ADDRESS addr )
+{
+ const int MaxBPsCached = 1024;
+ static CLRDATA_ADDRESS alreadyPlacedBPs[MaxBPsCached];
+ static int curLimit = 0;
+
+ // on ARM the debugger requires breakpoint addresses to be sanitized
+ if (IsDbgTargetArm())
+#ifndef FEATURE_PAL
+ addr &= ~THUMB_CODE;
+#else
+ addr |= THUMB_CODE; // lldb expects thumb code bit set
+#endif
+
+ // if we overflowed our cache consider all new BPs unique...
+ BOOL bUnique = curLimit >= MaxBPsCached;
+ if (!bUnique)
+ {
+ bUnique = TRUE;
+ for (int i = 0; i < curLimit; ++i)
+ {
+ if (alreadyPlacedBPs[i] == addr)
+ {
+ bUnique = FALSE;
+ break;
+ }
+ }
+ }
+ if (bUnique)
+ {
+ char buffer[64]; // sufficient for "bp <pointersize>"
+ static WCHAR wszNameBuffer[1024]; // should be large enough
+
+ // get the MethodDesc name
+ CLRDATA_ADDRESS pMD;
+ if (g_sos->GetMethodDescPtrFromIP(addr, &pMD) != S_OK
+ || g_sos->GetMethodDescName(pMD, 1024, wszNameBuffer, NULL) != S_OK)
+ {
+ wcscpy_s(wszNameBuffer, _countof(wszNameBuffer), W("UNKNOWN"));
+ }
+
+#ifndef FEATURE_PAL
+ sprintf_s(buffer, _countof(buffer), "bp %p", (void*) (size_t) addr);
+#else
+ sprintf_s(buffer, _countof(buffer), "breakpoint set --address 0x%p", (void*) (size_t) addr);
+#endif
+ ExtOut("Setting breakpoint: %s [%S]\n", buffer, wszNameBuffer);
+ g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, buffer, 0);
+
+ if (curLimit < MaxBPsCached)
+ {
+ alreadyPlacedBPs[curLimit++] = addr;
+ }
+ }
+}
+
+class Breakpoints
+{
+ PendingBreakpoint* m_breakpoints;
+public:
+ Breakpoints()
+ {
+ m_breakpoints = NULL;
+ }
+ ~Breakpoints()
+ {
+ PendingBreakpoint *pCur = m_breakpoints;
+ while(pCur)
+ {
+ PendingBreakpoint *pNext = pCur->pNext;
+ delete pCur;
+ pCur = pNext;
+ }
+ m_breakpoints = NULL;
+ }
+
+ void Add(__in_z LPWSTR szModule, __in_z LPWSTR szName, TADDR mod, DWORD ilOffset)
+ {
+ if (!IsIn(szModule, szName, mod))
+ {
+ PendingBreakpoint *pNew = new PendingBreakpoint();
+ wcscpy_s(pNew->szModuleName, MAX_LONGPATH, szModule);
+ wcscpy_s(pNew->szFunctionName, mdNameLen, szName);
+ pNew->SetModule(mod);
+ pNew->ilOffset = ilOffset;
+ pNew->pNext = m_breakpoints;
+ m_breakpoints = pNew;
+ }
+ }
+
+ void Add(__in_z LPWSTR szModule, __in_z LPWSTR szName, mdMethodDef methodToken, TADDR mod, DWORD ilOffset)
+ {
+ if (!IsIn(methodToken, mod, ilOffset))
+ {
+ PendingBreakpoint *pNew = new PendingBreakpoint();
+ wcscpy_s(pNew->szModuleName, MAX_LONGPATH, szModule);
+ wcscpy_s(pNew->szFunctionName, mdNameLen, szName);
+ pNew->methodToken = methodToken;
+ pNew->SetModule(mod);
+ pNew->ilOffset = ilOffset;
+ pNew->pNext = m_breakpoints;
+ m_breakpoints = pNew;
+ }
+ }
+
+ void Add(__in_z LPWSTR szFilename, DWORD lineNumber, TADDR mod)
+ {
+ if (!IsIn(szFilename, lineNumber, mod))
+ {
+ PendingBreakpoint *pNew = new PendingBreakpoint();
+ wcscpy_s(pNew->szFilename, MAX_LONGPATH, szFilename);
+ pNew->lineNumber = lineNumber;
+ pNew->SetModule(mod);
+ pNew->pNext = m_breakpoints;
+ m_breakpoints = pNew;
+ }
+ }
+
+ void Add(__in_z LPWSTR szFilename, DWORD lineNumber, mdMethodDef methodToken, TADDR mod, DWORD ilOffset)
+ {
+ if (!IsIn(methodToken, mod, ilOffset))
+ {
+ PendingBreakpoint *pNew = new PendingBreakpoint();
+ wcscpy_s(pNew->szFilename, MAX_LONGPATH, szFilename);
+ pNew->lineNumber = lineNumber;
+ pNew->methodToken = methodToken;
+ pNew->SetModule(mod);
+ pNew->ilOffset = ilOffset;
+ pNew->pNext = m_breakpoints;
+ m_breakpoints = pNew;
+ }
+ }
+
+ //returns true if updates are still needed for this module, FALSE if all BPs are now bound
+ BOOL Update(TADDR mod, BOOL isNewModule)
+ {
+ BOOL bNeedUpdates = FALSE;
+ PendingBreakpoint *pCur = NULL;
+
+ if(isNewModule)
+ {
+ SymbolReader symbolReader;
+ SymbolReader* pSymReader = &symbolReader;
+ if(LoadSymbolsForModule(mod, &symbolReader) != S_OK)
+ pSymReader = NULL;
+
+ // Get tokens for any modules that match. If there was a change,
+ // update notifications.
+ pCur = m_breakpoints;
+ while(pCur)
+ {
+ PendingBreakpoint *pNext = pCur->pNext;
+ ResolvePendingNonModuleBoundBreakpoint(mod, pCur, pSymReader);
+ pCur = pNext;
+ }
+ }
+
+ pCur = m_breakpoints;
+ while(pCur)
+ {
+ PendingBreakpoint *pNext = pCur->pNext;
+ if (ResolvePendingBreakpoint(mod, pCur))
+ {
+ bNeedUpdates = TRUE;
+ }
+ pCur = pNext;
+ }
+ return bNeedUpdates;
+ }
+
+ void RemovePendingForModule(TADDR mod)
+ {
+ PendingBreakpoint *pCur = m_breakpoints;
+ while(pCur)
+ {
+ PendingBreakpoint *pNext = pCur->pNext;
+ if (pCur->ModuleMatches(mod))
+ {
+ // Delete the current node, and keep going
+ Delete(pCur);
+ }
+
+ pCur = pNext;
+ }
+ }
+
+ void ListBreakpoints()
+ {
+ PendingBreakpoint *pCur = m_breakpoints;
+ size_t iBreakpointIndex = 1;
+ ExtOut(SOSPrefix "bpmd pending breakpoint list\n Breakpoint index - Location, ModuleID, Method Token\n");
+ while(pCur)
+ {
+ //windbg likes to format %p as always being 64 bits
+ ULONG64 modulePtr = (ULONG64)pCur->pModule;
+
+ if(pCur->szModuleName[0] != L'\0')
+ ExtOut("%d - %ws!%ws+%d, 0x%p, 0x%08x\n", iBreakpointIndex, pCur->szModuleName, pCur->szFunctionName, pCur->ilOffset, modulePtr, pCur->methodToken);
+ else
+ ExtOut("%d - %ws:%d, 0x%p, 0x%08x\n", iBreakpointIndex, pCur->szFilename, pCur->lineNumber, modulePtr, pCur->methodToken);
+ iBreakpointIndex++;
+ pCur = pCur->pNext;
+ }
+ }
+
+#ifndef FEATURE_PAL
+ void SaveBreakpoints(FILE* pFile)
+ {
+ PendingBreakpoint *pCur = m_breakpoints;
+ while(pCur)
+ {
+ if(pCur->szModuleName[0] != L'\0')
+ fprintf_s(pFile, "!bpmd %ws %ws %d\n", pCur->szModuleName, pCur->szFunctionName, pCur->ilOffset);
+ else
+ fprintf_s(pFile, "!bpmd %ws:%d\n", pCur->szFilename, pCur->lineNumber);
+ pCur = pCur->pNext;
+ }
+ }
+#endif
+
+ void CleanupNotifications()
+ {
+#ifdef FEATURE_PAL
+ if (m_breakpoints == NULL)
+ {
+ g_ExtServices->ClearExceptionCallback();
+ }
+#endif
+ }
+
+ void ClearBreakpoint(size_t breakPointToClear)
+ {
+ PendingBreakpoint *pCur = m_breakpoints;
+ size_t iBreakpointIndex = 1;
+ while(pCur)
+ {
+ if (breakPointToClear == iBreakpointIndex)
+ {
+ ExtOut("%d - %ws, %ws, %p\n", iBreakpointIndex, pCur->szModuleName, pCur->szFunctionName, pCur->pModule);
+ ExtOut("Cleared\n");
+ Delete(pCur);
+ break;
+ }
+ iBreakpointIndex++;
+ pCur = pCur->pNext;
+ }
+
+ if (pCur == NULL)
+ {
+ ExtOut("Invalid pending breakpoint index.\n");
+ }
+ CleanupNotifications();
+ }
+
+ void ClearAllBreakpoints()
+ {
+ size_t iBreakpointIndex = 1;
+ for (PendingBreakpoint *pCur = m_breakpoints; pCur != NULL; )
+ {
+ PendingBreakpoint* pNext = pCur->pNext;
+ Delete(pCur);
+ iBreakpointIndex++;
+ pCur = pNext;
+ }
+ CleanupNotifications();
+
+ ExtOut("All pending breakpoints cleared.\n");
+ }
+
+ HRESULT LoadSymbolsForModule(TADDR mod, SymbolReader* pSymbolReader)
+ {
+ HRESULT Status = S_OK;
+ ToRelease<IXCLRDataModule> pModule;
+ IfFailRet(g_sos->GetModule(mod, &pModule));
+
+ ToRelease<IMetaDataImport> pMDImport = NULL;
+ IfFailRet(pModule->QueryInterface(IID_IMetaDataImport, (LPVOID *) &pMDImport));
+
+ IfFailRet(pSymbolReader->LoadSymbols(pMDImport, pModule));
+
+ return S_OK;
+ }
+
+ HRESULT ResolvePendingNonModuleBoundBreakpoint(__in_z WCHAR* pFilename, DWORD lineNumber, TADDR mod, SymbolReader* pSymbolReader)
+ {
+ HRESULT Status = S_OK;
+ if(pSymbolReader == NULL)
+ return S_FALSE; // no symbols, can't bind here
+
+ mdMethodDef methodDef;
+ ULONG32 ilOffset;
+ if(FAILED(Status = pSymbolReader->ResolveSequencePoint(pFilename, lineNumber, mod, &methodDef, &ilOffset)))
+ {
+ return S_FALSE; // not binding in a module is typical
+ }
+
+ Add(pFilename, lineNumber, methodDef, mod, ilOffset);
+ return Status;
+ }
+
+ HRESULT ResolvePendingNonModuleBoundBreakpoint(__in_z WCHAR* pModuleName, __in_z WCHAR* pMethodName, TADDR mod, DWORD ilOffset)
+ {
+ HRESULT Status = S_OK;
+ char szName[mdNameLen];
+ int numModule;
+
+ ToRelease<IXCLRDataModule> module;
+ IfFailRet(g_sos->GetModule(mod, &module));
+
+ WideCharToMultiByte(CP_ACP, 0, pModuleName, (int)(_wcslen(pModuleName) + 1), szName, mdNameLen, NULL, NULL);
+
+ ArrayHolder<DWORD_PTR> moduleList = ModuleFromName(szName, &numModule);
+ if (moduleList == NULL)
+ {
+ ExtOut("Failed to request module list.\n");
+ return E_FAIL;
+ }
+
+ for (int i = 0; i < numModule; i++)
+ {
+ // If any one entry in moduleList matches, then the current PendingBreakpoint
+ // is the right one.
+ if(moduleList[i] != TO_TADDR(mod))
+ continue;
+
+ CLRDATA_ENUM h;
+ if (module->StartEnumMethodDefinitionsByName(pMethodName, 0, &h) == S_OK)
+ {
+ IXCLRDataMethodDefinition *pMeth = NULL;
+ while (module->EnumMethodDefinitionByName(&h, &pMeth) == S_OK)
+ {
+ mdMethodDef methodToken;
+ ToRelease<IXCLRDataModule> pUnusedModule;
+ IfFailRet(pMeth->GetTokenAndScope(&methodToken, &pUnusedModule));
+
+ Add(pModuleName, pMethodName, methodToken, mod, ilOffset);
+ pMeth->Release();
+ }
+ module->EndEnumMethodDefinitionsByName(h);
+ }
+ }
+ return S_OK;
+ }
+
+ // Return TRUE if there might be more instances that will be JITTED later
+ static BOOL ResolveMethodInstances(IXCLRDataMethodDefinition *pMeth, DWORD ilOffset)
+ {
+ BOOL bFoundCode = FALSE;
+ BOOL bNeedDefer = FALSE;
+ CLRDATA_ENUM h1;
+
+ if (pMeth->StartEnumInstances (NULL, &h1) == S_OK)
+ {
+ IXCLRDataMethodInstance *inst = NULL;
+ while (pMeth->EnumInstance (&h1, &inst) == S_OK)
+ {
+ BOOL foundByIlOffset = FALSE;
+ ULONG32 rangesNeeded = 0;
+ if(inst->GetAddressRangesByILOffset(ilOffset, 0, &rangesNeeded, NULL) == S_OK)
+ {
+ ArrayHolder<CLRDATA_ADDRESS_RANGE> ranges = new NOTHROW CLRDATA_ADDRESS_RANGE[rangesNeeded];
+ if (ranges != NULL)
+ {
+ if (inst->GetAddressRangesByILOffset(ilOffset, rangesNeeded, NULL, ranges) == S_OK)
+ {
+ for (DWORD i = 0; i < rangesNeeded; i++)
+ {
+ IssueDebuggerBPCommand(ranges[i].startAddress);
+ bFoundCode = TRUE;
+ foundByIlOffset = TRUE;
+ }
+ }
+ }
+ }
+
+ if (!foundByIlOffset && ilOffset == 0)
+ {
+ CLRDATA_ADDRESS addr = 0;
+ if (inst->GetRepresentativeEntryAddress(&addr) == S_OK)
+ {
+ IssueDebuggerBPCommand(addr);
+ bFoundCode = TRUE;
+ }
+ }
+ }
+ pMeth->EndEnumInstances (h1);
+ }
+
+ // if this is a generic method we need to add a defered bp
+ BOOL bGeneric = FALSE;
+ pMeth->HasClassOrMethodInstantiation(&bGeneric);
+
+ bNeedDefer = !bFoundCode || bGeneric;
+ // This is down here because we only need to call SetCodeNofiication once.
+ if (bNeedDefer)
+ {
+ if (pMeth->SetCodeNotification (CLRDATA_METHNOTIFY_GENERATED) != S_OK)
+ {
+ bNeedDefer = FALSE;
+ ExtOut("Failed to set code notification\n");
+ }
+ }
+ return bNeedDefer;
+ }
+
+private:
+ BOOL IsIn(__in_z LPWSTR szModule, __in_z LPWSTR szName, TADDR mod)
+ {
+ PendingBreakpoint *pCur = m_breakpoints;
+ while(pCur)
+ {
+ if (pCur->ModuleMatches(mod) &&
+ _wcsicmp(pCur->szModuleName, szModule) == 0 &&
+ _wcscmp(pCur->szFunctionName, szName) == 0)
+ {
+ return TRUE;
+ }
+ pCur = pCur->pNext;
+ }
+ return FALSE;
+ }
+
+ BOOL IsIn(__in_z LPWSTR szFilename, DWORD lineNumber, TADDR mod)
+ {
+ PendingBreakpoint *pCur = m_breakpoints;
+ while(pCur)
+ {
+ if (pCur->ModuleMatches(mod) &&
+ _wcsicmp(pCur->szFilename, szFilename) == 0 &&
+ pCur->lineNumber == lineNumber)
+ {
+ return TRUE;
+ }
+ pCur = pCur->pNext;
+ }
+ return FALSE;
+ }
+
+ BOOL IsIn(mdMethodDef token, TADDR mod, DWORD ilOffset)
+ {
+ PendingBreakpoint *pCur = m_breakpoints;
+ while(pCur)
+ {
+ if (pCur->ModuleMatches(mod) &&
+ pCur->methodToken == token &&
+ pCur->ilOffset == ilOffset)
+ {
+ return TRUE;
+ }
+ pCur = pCur->pNext;
+ }
+ return FALSE;
+ }
+
+ void Delete(PendingBreakpoint *pDelete)
+ {
+ PendingBreakpoint *pCur = m_breakpoints;
+ PendingBreakpoint *pPrev = NULL;
+ while(pCur)
+ {
+ if (pCur == pDelete)
+ {
+ if (pPrev == NULL)
+ {
+ m_breakpoints = pCur->pNext;
+ }
+ else
+ {
+ pPrev->pNext = pCur->pNext;
+ }
+ delete pCur;
+ return;
+ }
+ pPrev = pCur;
+ pCur = pCur->pNext;
+ }
+ }
+
+
+
+ HRESULT ResolvePendingNonModuleBoundBreakpoint(TADDR mod, PendingBreakpoint *pCur, SymbolReader* pSymbolReader)
+ {
+ // This function only works with pending breakpoints that are not module bound.
+ if (pCur->pModule == NULL)
+ {
+ if(pCur->szModuleName[0] != L'\0')
+ {
+ return ResolvePendingNonModuleBoundBreakpoint(pCur->szModuleName, pCur->szFunctionName, mod, pCur->ilOffset);
+ }
+ else
+ {
+ return ResolvePendingNonModuleBoundBreakpoint(pCur->szFilename, pCur->lineNumber, mod, pSymbolReader);
+ }
+ }
+ else
+ {
+ return S_OK;
+ }
+ }
+
+ // Returns TRUE if further instances may be jitted, FALSE if all instances are now resolved
+ BOOL ResolvePendingBreakpoint(TADDR addr, PendingBreakpoint *pCur)
+ {
+ // Only go forward if the module matches the current PendingBreakpoint
+ if (!pCur->ModuleMatches(addr))
+ {
+ return FALSE;
+ }
+
+ ToRelease<IXCLRDataModule> mod;
+ if (FAILED(g_sos->GetModule(addr, &mod)))
+ {
+ return FALSE;
+ }
+
+ if(pCur->methodToken == 0)
+ {
+ return FALSE;
+ }
+
+ ToRelease<IXCLRDataMethodDefinition> pMeth = NULL;
+ mod->GetMethodDefinitionByToken(pCur->methodToken, &pMeth);
+
+ // We may not need the code notification. Maybe it was ngen'd and we
+ // already have the method?
+ // We can delete the current entry if ResolveMethodInstances() set all BPs
+ return ResolveMethodInstances(pMeth, pCur->ilOffset);
+ }
+};
+
+Breakpoints g_bpoints;
+
+// Controls whether optimizations are disabled on module load and whether NGEN can be used
+BOOL g_fAllowJitOptimization = TRUE;
+
+// Controls whether a one-shot breakpoint should be inserted the next time
+// execution is about to enter a catch clause
+BOOL g_stopOnNextCatch = FALSE;
+
+// According to the latest debuggers these callbacks will not get called
+// unless the user (or an extension, like SOS :-)) had previously enabled
+// clrn with "sxe clrn".
+class CNotification : public IXCLRDataExceptionNotification4
+{
+ static int s_condemnedGen;
+
+ int m_count;
+ int m_dbgStatus;
+public:
+ CNotification()
+ : m_count(0)
+ , m_dbgStatus(DEBUG_STATUS_NO_CHANGE)
+ {}
+
+ int GetDebugStatus()
+ {
+ return m_dbgStatus;
+ }
+
+ STDMETHODIMP QueryInterface (REFIID iid, void **ppvObject)
+ {
+ if (ppvObject == NULL)
+ return E_INVALIDARG;
+
+ if (IsEqualIID(iid, IID_IUnknown)
+ || IsEqualIID(iid, IID_IXCLRDataExceptionNotification)
+ || IsEqualIID(iid, IID_IXCLRDataExceptionNotification2)
+ || IsEqualIID(iid, IID_IXCLRDataExceptionNotification3)
+ || IsEqualIID(iid, IID_IXCLRDataExceptionNotification4))
+ {
+ *ppvObject = static_cast<IXCLRDataExceptionNotification4*>(this);
+ AddRef();
+ return S_OK;
+ }
+ else
+ return E_NOINTERFACE;
+
+ }
+
+ STDMETHODIMP_(ULONG) AddRef(void) { return ++m_count; }
+ STDMETHODIMP_(ULONG) Release(void)
+ {
+ m_count--;
+ if (m_count < 0)
+ {
+ m_count = 0;
+ }
+ return m_count;
+ }
+
+
+ /*
+ * New code was generated or discarded for a method.:
+ */
+ STDMETHODIMP OnCodeGenerated(IXCLRDataMethodInstance* method)
+ {
+ // Some method has been generated, make a breakpoint and remove it.
+ ULONG32 len = mdNameLen;
+ LPWSTR szModuleName = (LPWSTR)alloca(mdNameLen * sizeof(WCHAR));
+ if (method->GetName(0, mdNameLen, &len, g_mdName) == S_OK)
+ {
+ ToRelease<IXCLRDataModule> pMod;
+ HRESULT hr = method->GetTokenAndScope(NULL, &pMod);
+ if (SUCCEEDED(hr))
+ {
+ len = mdNameLen;
+ if (pMod->GetName(mdNameLen, &len, szModuleName) == S_OK)
+ {
+ ExtOut("JITTED %S!%S\n", szModuleName, g_mdName);
+
+ // Add breakpoint, perhaps delete pending breakpoint
+ DacpGetModuleAddress dgma;
+ if (SUCCEEDED(dgma.Request(pMod)))
+ {
+ g_bpoints.Update(TO_TADDR(dgma.ModulePtr), FALSE);
+ }
+ else
+ {
+ ExtOut("Failed to request module address.\n");
+ }
+ }
+ }
+ }
+
+ m_dbgStatus = DEBUG_STATUS_GO_HANDLED;
+ return S_OK;
+ }
+
+ STDMETHODIMP OnCodeDiscarded(IXCLRDataMethodInstance* method)
+ {
+ return E_NOTIMPL;
+ }
+
+ /*
+ * The process or task reached the desired execution state.
+ */
+ STDMETHODIMP OnProcessExecution(ULONG32 state) { return E_NOTIMPL; }
+ STDMETHODIMP OnTaskExecution(IXCLRDataTask* task,
+ ULONG32 state) { return E_NOTIMPL; }
+
+ /*
+ * The given module was loaded or unloaded.
+ */
+ STDMETHODIMP OnModuleLoaded(IXCLRDataModule* mod)
+ {
+ DacpGetModuleAddress dgma;
+ if (SUCCEEDED(dgma.Request(mod)))
+ {
+ g_bpoints.Update(TO_TADDR(dgma.ModulePtr), TRUE);
+ }
+
+ if(!g_fAllowJitOptimization)
+ {
+ HRESULT hr;
+ ToRelease<IXCLRDataModule2> mod2;
+ if(FAILED(mod->QueryInterface(__uuidof(IXCLRDataModule2), (void**) &mod2)))
+ {
+ ExtOut("SOS: warning, optimizations for this module could not be suppressed because this CLR version doesn't support the functionality\n");
+ }
+ else if(FAILED(hr = mod2->SetJITCompilerFlags(CORDEBUG_JIT_DISABLE_OPTIMIZATION)))
+ {
+ if(hr == CORDBG_E_CANT_CHANGE_JIT_SETTING_FOR_ZAP_MODULE)
+ ExtOut("SOS: warning, optimizations for this module could not be surpressed because an optimized prejitted image was loaded\n");
+ else
+ ExtOut("SOS: warning, optimizations for this module could not be surpressed hr=0x%x\n", hr);
+ }
+ }
+
+ m_dbgStatus = DEBUG_STATUS_GO_HANDLED;
+ return S_OK;
+ }
+
+ STDMETHODIMP OnModuleUnloaded(IXCLRDataModule* mod)
+ {
+ DacpGetModuleAddress dgma;
+ if (SUCCEEDED(dgma.Request(mod)))
+ {
+ g_bpoints.RemovePendingForModule(TO_TADDR(dgma.ModulePtr));
+ }
+
+ m_dbgStatus = DEBUG_STATUS_GO_HANDLED;
+ return S_OK;
+ }
+
+ /*
+ * The given type was loaded or unloaded.
+ */
+ STDMETHODIMP OnTypeLoaded(IXCLRDataTypeInstance* typeInst)
+ { return E_NOTIMPL; }
+ STDMETHODIMP OnTypeUnloaded(IXCLRDataTypeInstance* typeInst)
+ { return E_NOTIMPL; }
+
+ STDMETHODIMP OnAppDomainLoaded(IXCLRDataAppDomain* domain)
+ { return E_NOTIMPL; }
+ STDMETHODIMP OnAppDomainUnloaded(IXCLRDataAppDomain* domain)
+ { return E_NOTIMPL; }
+ STDMETHODIMP OnException(IXCLRDataExceptionState* exception)
+ { return E_NOTIMPL; }
+
+ STDMETHODIMP OnGcEvent(GcEvtArgs gcEvtArgs)
+{
+ // by default don't stop on these notifications...
+ m_dbgStatus = DEBUG_STATUS_GO_HANDLED;
+
+ IXCLRDataProcess2* idp2 = NULL;
+ if (SUCCEEDED(g_clrData->QueryInterface(IID_IXCLRDataProcess2, (void**) &idp2)))
+ {
+ if (gcEvtArgs.typ == GC_MARK_END)
+ {
+ // erase notification request
+ GcEvtArgs gea = { GC_MARK_END, { 0 } };
+ idp2->SetGcNotification(gea);
+
+ s_condemnedGen = bitidx(gcEvtArgs.condemnedGeneration);
+
+ ExtOut("CLR notification: GC - Performing a gen %d collection. Determined surviving objects...\n", s_condemnedGen);
+
+ // GC_MARK_END notification means: give the user a chance to examine the debuggee
+ m_dbgStatus = DEBUG_STATUS_BREAK;
+ }
+ }
+
+ return S_OK;
+ }
+
+ /*
+ * Catch is about to be entered
+ */
+ STDMETHODIMP ExceptionCatcherEnter(IXCLRDataMethodInstance* method, DWORD catcherNativeOffset)
+ {
+ if(g_stopOnNextCatch)
+ {
+ CLRDATA_ADDRESS startAddr;
+ if(method->GetRepresentativeEntryAddress(&startAddr) == S_OK)
+ {
+ CHAR buffer[100];
+#ifndef FEATURE_PAL
+ sprintf_s(buffer, _countof(buffer), "bp /1 %p", (void*) (size_t) (startAddr+catcherNativeOffset));
+#else
+ sprintf_s(buffer, _countof(buffer), "breakpoint set --one-shot --address 0x%p", (void*) (size_t) (startAddr+catcherNativeOffset));
+#endif
+ g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, buffer, 0);
+ }
+ g_stopOnNextCatch = FALSE;
+ }
+
+ m_dbgStatus = DEBUG_STATUS_GO_HANDLED;
+ return S_OK;
+ }
+
+ static int GetCondemnedGen()
+ {
+ return s_condemnedGen;
+ }
+
+};
+
+int CNotification::s_condemnedGen = -1;
+
+BOOL CheckCLRNotificationEvent(DEBUG_LAST_EVENT_INFO_EXCEPTION* pdle)
+{
+ ISOSDacInterface4 *psos4 = NULL;
+ CLRDATA_ADDRESS arguments[3];
+ HRESULT Status;
+
+ if (SUCCEEDED(Status = g_sos->QueryInterface(__uuidof(ISOSDacInterface4), (void**) &psos4)))
+ {
+ int count = _countof(arguments);
+ int countNeeded = 0;
+
+ Status = psos4->GetClrNotification(arguments, count, &countNeeded);
+ psos4->Release();
+
+ if (SUCCEEDED(Status))
+ {
+ memset(&pdle->ExceptionRecord, 0, sizeof(pdle->ExceptionRecord));
+ pdle->FirstChance = TRUE;
+ pdle->ExceptionRecord.ExceptionCode = CLRDATA_NOTIFY_EXCEPTION;
+
+ _ASSERTE(count <= EXCEPTION_MAXIMUM_PARAMETERS);
+ for (int i = 0; i < count; i++)
+ {
+ pdle->ExceptionRecord.ExceptionInformation[i] = arguments[i];
+ }
+ // The rest of the ExceptionRecord isn't used by TranslateExceptionRecordToNotification
+ return TRUE;
+ }
+ // No pending exception notification
+ return FALSE;
+ }
+
+ // The new DAC based interface doesn't exists so ask the debugger for the last exception
+ // information. NOTE: this function doesn't work on xplat version when the coreclr symbols
+ // have been stripped.
+
+ ULONG Type, ProcessId, ThreadId;
+ ULONG ExtraInformationUsed;
+ Status = g_ExtControl->GetLastEventInformation(
+ &Type,
+ &ProcessId,
+ &ThreadId,
+ pdle,
+ sizeof(DEBUG_LAST_EVENT_INFO_EXCEPTION),
+ &ExtraInformationUsed,
+ NULL,
+ 0,
+ NULL);
+
+ if (Status != S_OK || Type != DEBUG_EVENT_EXCEPTION)
+ {
+ return FALSE;
+ }
+
+ if (!pdle->FirstChance || pdle->ExceptionRecord.ExceptionCode != CLRDATA_NOTIFY_EXCEPTION)
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+HRESULT HandleCLRNotificationEvent()
+{
+ /*
+ * Did we get module load notification? If so, check if any in our pending list
+ * need to be registered for jit notification.
+ *
+ * Did we get a jit notification? If so, check if any can be removed and
+ * real breakpoints be set.
+ */
+ DEBUG_LAST_EVENT_INFO_EXCEPTION dle;
+ CNotification Notification;
+
+ if (!CheckCLRNotificationEvent(&dle))
+ {
+#ifndef FEATURE_PAL
+ ExtOut("Expecting first chance CLRN exception\n");
+ return E_FAIL;
+#else
+ g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, "process continue", 0);
+ return S_OK;
+#endif
+ }
+
+ // Notification only needs to live for the lifetime of the call below, so it's a non-static
+ // local.
+ HRESULT Status = g_clrData->TranslateExceptionRecordToNotification(&dle.ExceptionRecord, &Notification);
+ if (Status != S_OK)
+ {
+ ExtErr("Error processing exception notification\n");
+ return Status;
+ }
+ else
+ {
+ switch (Notification.GetDebugStatus())
+ {
+ case DEBUG_STATUS_GO:
+ case DEBUG_STATUS_GO_HANDLED:
+ case DEBUG_STATUS_GO_NOT_HANDLED:
+#ifndef FEATURE_PAL
+ g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, "g", 0);
+#else
+ g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, "process continue", 0);
+#endif
+ break;
+ default:
+ break;
+ }
+ }
+
+ return S_OK;
+}
+
+#ifndef FEATURE_PAL
+
+DECLARE_API(HandleCLRN)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ return HandleCLRNotificationEvent();
+}
+
+#else // FEATURE_PAL
+
+HRESULT HandleExceptionNotification(ILLDBServices *client)
+{
+ INIT_API();
+ return HandleCLRNotificationEvent();
+}
+
+#endif // FEATURE_PAL
+
+DECLARE_API(bpmd)
+{
+ INIT_API_NOEE();
+ MINIDUMP_NOT_SUPPORTED();
+ int i;
+ char buffer[1024];
+
+ if (IsDumpFile())
+ {
+ ExtOut(SOSPrefix "bpmd is not supported on a dump file.\n");
+ return Status;
+ }
+
+
+ // We keep a list of managed breakpoints the user wants to set, and display pending bps
+ // bpmd. If you call bpmd <module name> <method> we will set or update an existing bp.
+ // bpmd acts as a feeder of breakpoints to bp when the time is right.
+ //
+
+ StringHolder DllName,TypeName;
+ int lineNumber = 0;
+ size_t Offset = 0;
+
+ DWORD_PTR pMD = NULL;
+ BOOL fNoFutureModule = FALSE;
+ BOOL fList = FALSE;
+ size_t clearItem = 0;
+ BOOL fClearAll = FALSE;
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"-md", &pMD, COHEX, TRUE},
+ {"-nofuturemodule", &fNoFutureModule, COBOOL, FALSE},
+ {"-list", &fList, COBOOL, FALSE},
+ {"-clear", &clearItem, COSIZE_T, TRUE},
+ {"-clearall", &fClearAll, COBOOL, FALSE},
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&DllName.data, COSTRING},
+ {&TypeName.data, COSTRING},
+ {&Offset, COSIZE_T},
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ bool fBadParam = false;
+ bool fIsFilename = false;
+ int commandsParsed = 0;
+
+ if (pMD != NULL)
+ {
+ if (nArg != 0)
+ {
+ fBadParam = true;
+ }
+ commandsParsed++;
+ }
+ if (fList)
+ {
+ commandsParsed++;
+ if (nArg != 0)
+ {
+ fBadParam = true;
+ }
+ }
+ if (fClearAll)
+ {
+ commandsParsed++;
+ if (nArg != 0)
+ {
+ fBadParam = true;
+ }
+ }
+ if (clearItem != 0)
+ {
+ commandsParsed++;
+ if (nArg != 0)
+ {
+ fBadParam = true;
+ }
+ }
+ if (1 <= nArg && nArg <= 3)
+ {
+ commandsParsed++;
+ // did we get dll and type name or file:line#? Search for a colon in the first arg
+ // to see if it is in fact a file:line#
+ CHAR* pColon = strchr(DllName.data, ':');
+#ifndef FEATURE_PAL
+ if (FAILED(g_ExtSymbols->GetModuleByModuleName(MAIN_CLR_MODULE_NAME_A, 0, NULL, NULL))) {
+#else
+ if (FAILED(g_ExtSymbols->GetModuleByModuleName(MAIN_CLR_DLL_NAME_A, 0, NULL, NULL))) {
+#endif
+ ExtOut("%s not loaded yet\n", MAIN_CLR_DLL_NAME_A);
+ return Status;
+ }
+
+ if(NULL != pColon)
+ {
+ fIsFilename = true;
+ *pColon = '\0';
+ pColon++;
+ if(1 != sscanf_s(pColon, "%d", &lineNumber))
+ {
+ ExtOut("Unable to parse line number\n");
+ fBadParam = true;
+ }
+ else if(lineNumber < 0)
+ {
+ ExtOut("Line number must be positive\n");
+ fBadParam = true;
+ }
+ if(nArg != 1) fBadParam = 1;
+ }
+ }
+
+ if (fBadParam || (commandsParsed != 1))
+ {
+ ExtOut("Usage: " SOSPrefix "bpmd -md <MethodDesc pointer>\n");
+ ExtOut("Usage: " SOSPrefix "bpmd [-nofuturemodule] <module name> <managed function name> [<il offset>]\n");
+ ExtOut("Usage: " SOSPrefix "bpmd <filename>:<line number>\n");
+ ExtOut("Usage: " SOSPrefix "bpmd -list\n");
+ ExtOut("Usage: " SOSPrefix "bpmd -clear <pending breakpoint number>\n");
+ ExtOut("Usage: " SOSPrefix "bpmd -clearall\n");
+#ifdef FEATURE_PAL
+ ExtOut("See \"soshelp bpmd\" for more details.\n");
+#else
+ ExtOut("See \"!help bpmd\" for more details.\n");
+#endif
+ return Status;
+ }
+
+ if (fList)
+ {
+ g_bpoints.ListBreakpoints();
+ return Status;
+ }
+ if (clearItem != 0)
+ {
+ g_bpoints.ClearBreakpoint(clearItem);
+ return Status;
+ }
+ if (fClearAll)
+ {
+ g_bpoints.ClearAllBreakpoints();
+ return Status;
+ }
+ // Add a breakpoint
+ // Do we already have this breakpoint?
+ // Or, before setting it, is the module perhaps already loaded and code
+ // is available? If so, don't add to our pending list, just go ahead and
+ // set the real breakpoint.
+
+ LPWSTR ModuleName = (LPWSTR)alloca(mdNameLen * sizeof(WCHAR));
+ LPWSTR FunctionName = (LPWSTR)alloca(mdNameLen * sizeof(WCHAR));
+ LPWSTR Filename = (LPWSTR)alloca(MAX_LONGPATH * sizeof(WCHAR));
+
+ BOOL bNeedNotificationExceptions = FALSE;
+
+ if (pMD == NULL)
+ {
+ int numModule = 0;
+ int numMethods = 0;
+
+ ArrayHolder<DWORD_PTR> moduleList = NULL;
+
+ if(!fIsFilename)
+ {
+ MultiByteToWideChar(CP_ACP, 0, DllName.data, -1, ModuleName, mdNameLen);
+ MultiByteToWideChar(CP_ACP, 0, TypeName.data, -1, FunctionName, mdNameLen);
+ }
+ else
+ {
+ MultiByteToWideChar(CP_ACP, 0, DllName.data, -1, Filename, MAX_LONGPATH);
+ }
+
+ // Get modules that may need a breakpoint bound
+ if ((Status = CheckEEDll()) == S_OK)
+ {
+ if ((Status = LoadClrDebugDll()) != S_OK)
+ {
+ // if the EE is loaded but DAC isn't we should stop.
+ DACMessage(Status);
+ return Status;
+ }
+ g_bDacBroken = FALSE; \
+
+ // Get the module list
+ moduleList = ModuleFromName(fIsFilename ? NULL : DllName.data, &numModule);
+
+ // Its OK if moduleList is NULL
+ // There is a very normal case when checking for modules after clr is loaded
+ // but before any AppDomains or assemblies are created
+ // for example:
+ // >sxe ld:clr
+ // >g
+ // ...
+ // ModLoad: clr.dll
+ // >!bpmd Foo.dll Foo.Bar
+ }
+ // If LoadClrDebugDll() succeeded make sure we release g_clrData
+ ToRelease<IXCLRDataProcess> spIDP(g_clrData);
+ ToRelease<ISOSDacInterface> spISD(g_sos);
+ ResetGlobals();
+
+ // we can get here with EE not loaded => 0 modules
+ // EE is loaded => 0 or more modules
+ ArrayHolder<DWORD_PTR> pMDs = NULL;
+ for (int iModule = 0; iModule < numModule; iModule++)
+ {
+ ToRelease<IXCLRDataModule> ModDef;
+ if (g_sos->GetModule(moduleList[iModule], &ModDef) != S_OK)
+ {
+ continue;
+ }
+
+ HRESULT symbolsLoaded = S_FALSE;
+ if(!fIsFilename)
+ {
+ g_bpoints.ResolvePendingNonModuleBoundBreakpoint(ModuleName, FunctionName, moduleList[iModule], (DWORD)Offset);
+ }
+ else
+ {
+ SymbolReader symbolReader;
+ symbolsLoaded = g_bpoints.LoadSymbolsForModule(moduleList[iModule], &symbolReader);
+ if(symbolsLoaded == S_OK &&
+ g_bpoints.ResolvePendingNonModuleBoundBreakpoint(Filename, lineNumber, moduleList[iModule], &symbolReader) == S_OK)
+ {
+ // if we have symbols then get the function name so we can lookup the MethodDescs
+ mdMethodDef methodDefToken;
+ ULONG32 ilOffset;
+ if(SUCCEEDED(symbolReader.ResolveSequencePoint(Filename, lineNumber, moduleList[iModule], &methodDefToken, &ilOffset)))
+ {
+ ToRelease<IXCLRDataMethodDefinition> pMethodDef = NULL;
+ if (SUCCEEDED(ModDef->GetMethodDefinitionByToken(methodDefToken, &pMethodDef)))
+ {
+ ULONG32 nameLen = 0;
+ pMethodDef->GetName(0, mdNameLen, &nameLen, FunctionName);
+
+ // get the size of the required buffer
+ int buffSize = WideCharToMultiByte(CP_ACP, 0, FunctionName, -1, TypeName.data, 0, NULL, NULL);
+
+ TypeName.data = new NOTHROW char[buffSize];
+ if (TypeName.data != NULL)
+ {
+ int bytesWritten = WideCharToMultiByte(CP_ACP, 0, FunctionName, -1, TypeName.data, buffSize, NULL, NULL);
+ _ASSERTE(bytesWritten == buffSize);
+ }
+ }
+ }
+ }
+ }
+
+ HRESULT gotMethodDescs = GetMethodDescsFromName(moduleList[iModule], ModDef, TypeName.data, &pMDs, &numMethods);
+ if (FAILED(gotMethodDescs) && (!fIsFilename))
+ {
+ // BPs via file name will enumerate through modules so there will be legitimate failures.
+ // for module/type name we already found a match so this shouldn't fail (this is the original behavior).
+ ExtOut("Error getting MethodDescs for module %p\n", moduleList[iModule]);
+ return Status;
+ }
+
+ // for filename+line number only print extra info if symbols for this module are loaded (it can get quite noisy otherwise).
+ if ((!fIsFilename) || (fIsFilename && symbolsLoaded == S_OK))
+ {
+ for (int i = 0; i < numMethods; i++)
+ {
+ if (pMDs[i] == MD_NOT_YET_LOADED)
+ {
+ continue;
+ }
+ ExtOut("MethodDesc = %p\n", SOS_PTR(pMDs[i]));
+ }
+ }
+
+ if (g_bpoints.Update(moduleList[iModule], FALSE))
+ {
+ bNeedNotificationExceptions = TRUE;
+ }
+ }
+
+ if (!fNoFutureModule)
+ {
+ // add a pending breakpoint that will find future loaded modules, and
+ // wait for the module load notification.
+ if (!fIsFilename)
+ {
+ g_bpoints.Add(ModuleName, FunctionName, NULL, (DWORD)Offset);
+ }
+ else
+ {
+ g_bpoints.Add(Filename, lineNumber, NULL);
+ }
+ bNeedNotificationExceptions = TRUE;
+
+ ULONG32 flags = 0;
+ g_clrData->GetOtherNotificationFlags(&flags);
+ flags |= (CLRDATA_NOTIFY_ON_MODULE_LOAD | CLRDATA_NOTIFY_ON_MODULE_UNLOAD);
+ g_clrData->SetOtherNotificationFlags(flags);
+ }
+ }
+ else /* We were given a MethodDesc already */
+ {
+ // if we've got an explicit MD, then we better have CLR and mscordacwks loaded
+ INIT_API_EE()
+ INIT_API_DAC();
+
+ DacpMethodDescData MethodDescData;
+ ExtOut("MethodDesc = %p\n", SOS_PTR(pMD));
+ if (MethodDescData.Request(g_sos, TO_CDADDR(pMD)) != S_OK)
+ {
+ ExtOut("%p is not a valid MethodDesc\n", SOS_PTR(pMD));
+ return Status;
+ }
+
+ if (MethodDescData.bHasNativeCode)
+ {
+ IssueDebuggerBPCommand((size_t) MethodDescData.NativeCodeAddr);
+ }
+ else if (MethodDescData.bIsDynamic)
+ {
+#ifndef FEATURE_PAL
+ // Dynamic methods don't have JIT notifications. This is something we must
+ // fix in the next release. Until then, you have a cumbersome user experience.
+ ExtOut("This DynamicMethodDesc is not yet JITTED. Placing memory breakpoint at %p\n",
+ MethodDescData.AddressOfNativeCodeSlot);
+
+ sprintf_s(buffer, _countof(buffer),
+#ifdef _TARGET_WIN64_
+ "ba w8"
+#else
+ "ba w4"
+#endif // _TARGET_WIN64_
+
+ " /1 %p \"bp poi(%p); g\"",
+ (void*) (size_t) MethodDescData.AddressOfNativeCodeSlot,
+ (void*) (size_t) MethodDescData.AddressOfNativeCodeSlot);
+
+ Status = g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, buffer, 0);
+ if (FAILED(Status))
+ {
+ ExtOut("Unable to set breakpoint with IDebugControl::Execute: %x\n",Status);
+ ExtOut("Attempted to run: %s\n", buffer);
+ }
+#else
+ ExtErr("This DynamicMethodDesc is not yet JITTED %p\n", MethodDescData.AddressOfNativeCodeSlot);
+#endif // FEATURE_PAL
+ }
+ else
+ {
+ // Must issue a pending breakpoint.
+ if (g_sos->GetMethodDescName(pMD, mdNameLen, FunctionName, NULL) != S_OK)
+ {
+ ExtOut("Unable to get method name for MethodDesc %p\n", SOS_PTR(pMD));
+ return Status;
+ }
+
+ FileNameForModule ((DWORD_PTR) MethodDescData.ModulePtr, ModuleName);
+
+ // We didn't find code, add a breakpoint.
+ g_bpoints.ResolvePendingNonModuleBoundBreakpoint(ModuleName, FunctionName, TO_TADDR(MethodDescData.ModulePtr), 0);
+ g_bpoints.Update(TO_TADDR(MethodDescData.ModulePtr), FALSE);
+ bNeedNotificationExceptions = TRUE;
+ }
+ }
+
+ if (bNeedNotificationExceptions)
+ {
+ ExtOut("Adding pending breakpoints...\n");
+#ifndef FEATURE_PAL
+ sprintf_s(buffer, _countof(buffer), "sxe -c \"!HandleCLRN\" clrn");
+ Status = g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, buffer, 0);
+#else
+ Status = g_ExtServices->SetExceptionCallback(HandleExceptionNotification);
+#endif // FEATURE_PAL
+ }
+
+ return Status;
+}
+
+#ifndef FEATURE_PAL
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to dump the managed threadpool *
+* *
+\**********************************************************************/
+DECLARE_API(ThreadPool)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ DacpThreadpoolData threadpool;
+
+ if ((Status = threadpool.Request(g_sos)) == S_OK)
+ {
+ BOOL doHCDump = FALSE;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"-ti", &doHCDump, COBOOL, FALSE}
+ };
+
+ if (!GetCMDOption(args, option, _countof(option), NULL, 0, NULL))
+ {
+ return Status;
+ }
+
+ ExtOut ("CPU utilization: %d%%\n", threadpool.cpuUtilization);
+ ExtOut ("Worker Thread:");
+ ExtOut (" Total: %d", threadpool.NumWorkingWorkerThreads + threadpool.NumIdleWorkerThreads + threadpool.NumRetiredWorkerThreads);
+ ExtOut (" Running: %d", threadpool.NumWorkingWorkerThreads);
+ ExtOut (" Idle: %d", threadpool.NumIdleWorkerThreads);
+ ExtOut (" MaxLimit: %d", threadpool.MaxLimitTotalWorkerThreads);
+ ExtOut (" MinLimit: %d", threadpool.MinLimitTotalWorkerThreads);
+ ExtOut ("\n");
+
+ int numWorkRequests = 0;
+ CLRDATA_ADDRESS workRequestPtr = threadpool.FirstUnmanagedWorkRequest;
+ DacpWorkRequestData workRequestData;
+ while (workRequestPtr)
+ {
+ if ((Status = workRequestData.Request(g_sos,workRequestPtr))!=S_OK)
+ {
+ ExtOut(" Failed to examine a WorkRequest\n");
+ return Status;
+ }
+ numWorkRequests++;
+ workRequestPtr = workRequestData.NextWorkRequest;
+ }
+
+ ExtOut ("Work Request in Queue: %d\n", numWorkRequests);
+ workRequestPtr = threadpool.FirstUnmanagedWorkRequest;
+ while (workRequestPtr)
+ {
+ if ((Status = workRequestData.Request(g_sos,workRequestPtr))!=S_OK)
+ {
+ ExtOut(" Failed to examine a WorkRequest\n");
+ return Status;
+ }
+
+ if (workRequestData.Function == threadpool.AsyncTimerCallbackCompletionFPtr)
+ ExtOut (" AsyncTimerCallbackCompletion TimerInfo@%p\n", SOS_PTR(workRequestData.Context));
+ else
+ ExtOut (" Unknown Function: %p Context: %p\n", SOS_PTR(workRequestData.Function),
+ SOS_PTR(workRequestData.Context));
+
+ workRequestPtr = workRequestData.NextWorkRequest;
+ }
+
+ if (doHCDump)
+ {
+ ExtOut ("--------------------------------------\n");
+ ExtOut ("\nThread Injection History\n");
+ if (threadpool.HillClimbingLogSize > 0)
+ {
+ static char const * const TransitionNames[] =
+ {
+ "Warmup",
+ "Initializing",
+ "RandomMove",
+ "ClimbingMove",
+ "ChangePoint",
+ "Stabilizing",
+ "Starvation",
+ "ThreadTimedOut",
+ "Undefined"
+ };
+
+ ExtOut("\n Time Transition New #Threads #Samples Throughput\n");
+ DacpHillClimbingLogEntry entry;
+
+ // get the most recent entry first, so we can calculate time offsets
+
+ int index = (threadpool.HillClimbingLogFirstIndex + threadpool.HillClimbingLogSize-1) % HillClimbingLogCapacity;
+ CLRDATA_ADDRESS entryPtr = threadpool.HillClimbingLog + (index * sizeof(HillClimbingLogEntry));
+ if ((Status = entry.Request(g_sos,entryPtr))!=S_OK)
+ {
+ ExtOut(" Failed to examine a HillClimbing log entry\n");
+ return Status;
+ }
+ DWORD endTime = entry.TickCount;
+
+ for (int i = 0; i < threadpool.HillClimbingLogSize; i++)
+ {
+ index = (i + threadpool.HillClimbingLogFirstIndex) % HillClimbingLogCapacity;
+ entryPtr = threadpool.HillClimbingLog + (index * sizeof(HillClimbingLogEntry));
+
+ if ((Status = entry.Request(g_sos,entryPtr))!=S_OK)
+ {
+ ExtOut(" Failed to examine a HillClimbing log entry\n");
+ return Status;
+ }
+
+ ExtOut("%8.2lf %-14s %12d %12d %11.2lf\n",
+ (double)(int)(entry.TickCount - endTime) / 1000.0,
+ TransitionNames[entry.Transition],
+ entry.NewControlSetting,
+ entry.LastHistoryCount,
+ entry.LastHistoryMean);
+ }
+ }
+ }
+
+ ExtOut ("--------------------------------------\n");
+ ExtOut ("Number of Timers: %d\n", threadpool.NumTimers);
+ ExtOut ("--------------------------------------\n");
+
+ ExtOut ("Completion Port Thread:");
+ ExtOut ("Total: %d", threadpool.NumCPThreads);
+ ExtOut (" Free: %d", threadpool.NumFreeCPThreads);
+ ExtOut (" MaxFree: %d", threadpool.MaxFreeCPThreads);
+ ExtOut (" CurrentLimit: %d", threadpool.CurrentLimitTotalCPThreads);
+ ExtOut (" MaxLimit: %d", threadpool.MaxLimitTotalCPThreads);
+ ExtOut (" MinLimit: %d", threadpool.MinLimitTotalCPThreads);
+ ExtOut ("\n");
+ }
+ else
+ {
+ ExtOut("Failed to request ThreadpoolMgr information\n");
+ }
+ return Status;
+}
+
+#endif // FEATURE_PAL
+
+DECLARE_API(FindAppDomain)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ DWORD_PTR p_Object = NULL;
+ BOOL dml = FALSE;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+#ifndef FEATURE_PAL
+ {"/d", &dml, COBOOL, FALSE},
+#endif
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&p_Object, COHEX},
+ };
+ size_t nArg;
+
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+
+ if ((p_Object == 0) || !sos::IsObject(p_Object))
+ {
+ ExtOut("%p is not a valid object\n", SOS_PTR(p_Object));
+ return Status;
+ }
+
+ DacpAppDomainStoreData adstore;
+ if (adstore.Request(g_sos) != S_OK)
+ {
+ ExtOut("Error getting AppDomain information\n");
+ return Status;
+ }
+
+ CLRDATA_ADDRESS appDomain = GetAppDomain (TO_CDADDR(p_Object));
+
+ if (appDomain != NULL)
+ {
+ DMLOut("AppDomain: %s\n", DMLDomain(appDomain));
+ if (appDomain == adstore.sharedDomain)
+ {
+ ExtOut("Name: Shared Domain\n");
+ ExtOut("ID: (shared domain)\n");
+ }
+ else if (appDomain == adstore.systemDomain)
+ {
+ ExtOut("Name: System Domain\n");
+ ExtOut("ID: (system domain)\n");
+ }
+ else
+ {
+ DacpAppDomainData domain;
+ if ((domain.Request(g_sos, appDomain) != S_OK) ||
+ (g_sos->GetAppDomainName(appDomain,mdNameLen,g_mdName, NULL)!=S_OK))
+ {
+ ExtOut("Error getting AppDomain %p.\n", SOS_PTR(appDomain));
+ return Status;
+ }
+
+ ExtOut("Name: %S\n", (g_mdName[0]!=L'\0') ? g_mdName : W("None"));
+ ExtOut("ID: %d\n", domain.dwId);
+ }
+ }
+ else
+ {
+ ExtOut("The type is declared in the shared domain and other\n");
+ ExtOut("methods of finding the AppDomain failed. Try running\n");
+ if (IsDMLEnabled())
+ DMLOut("<exec cmd=\"!gcroot /d %p\">!gcroot %p</exec>, and if you find a root on a\n", p_Object, p_Object);
+ else
+ ExtOut("!gcroot %p, and if you find a root on a\n", p_Object);
+ ExtOut("stack, check the AppDomain of that stack with !threads.\n");
+ ExtOut("Note that the Thread could have transitioned between\n");
+ ExtOut("multiple AppDomains.\n");
+ }
+
+ return Status;
+}
+
+#ifndef FEATURE_PAL
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to get the COM state (e.g. APT,contexe *
+* activity. *
+* *
+\**********************************************************************/
+#ifdef FEATURE_COMINTEROP
+DECLARE_API(COMState)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+
+ ULONG numThread;
+ ULONG maxId;
+ g_ExtSystem->GetTotalNumberThreads(&numThread,&maxId);
+
+ ULONG curId;
+ g_ExtSystem->GetCurrentThreadId(&curId);
+
+ SIZE_T AllocSize;
+ if (!ClrSafeInt<SIZE_T>::multiply(sizeof(ULONG), numThread, AllocSize))
+ {
+ ExtOut(" Error! integer overflow on numThread 0x%08x\n", numThread);
+ return Status;
+ }
+ ULONG *ids = (ULONG*)alloca(AllocSize);
+ ULONG *sysIds = (ULONG*)alloca(AllocSize);
+ g_ExtSystem->GetThreadIdsByIndex(0,numThread,ids,sysIds);
+#if defined(_TARGET_WIN64_)
+ ExtOut(" ID TEB APT APTId CallerTID Context\n");
+#else
+ ExtOut(" ID TEB APT APTId CallerTID Context\n");
+#endif
+ for (ULONG i = 0; i < numThread; i ++) {
+ g_ExtSystem->SetCurrentThreadId(ids[i]);
+ CLRDATA_ADDRESS cdaTeb;
+ g_ExtSystem->GetCurrentThreadTeb(&cdaTeb);
+ ExtOut("%3d %4x %p", ids[i], sysIds[i], SOS_PTR(CDA_TO_UL64(cdaTeb)));
+ // Apartment state
+ TADDR OleTlsDataAddr;
+ if (SafeReadMemory(TO_TADDR(cdaTeb) + offsetof(TEB,ReservedForOle),
+ &OleTlsDataAddr,
+ sizeof(OleTlsDataAddr), NULL) && OleTlsDataAddr != 0) {
+ DWORD AptState;
+ if (SafeReadMemory(OleTlsDataAddr+offsetof(SOleTlsData,dwFlags),
+ &AptState,
+ sizeof(AptState), NULL)) {
+ if (AptState & OLETLS_APARTMENTTHREADED) {
+ ExtOut(" STA");
+ }
+ else if (AptState & OLETLS_MULTITHREADED) {
+ ExtOut(" MTA");
+ }
+ else if (AptState & OLETLS_INNEUTRALAPT) {
+ ExtOut(" NTA");
+ }
+ else {
+ ExtOut(" Ukn");
+ }
+
+ // Read these fields only if we were able to read anything of the SOleTlsData structure
+ DWORD dwApartmentID;
+ if (SafeReadMemory(OleTlsDataAddr+offsetof(SOleTlsData,dwApartmentID),
+ &dwApartmentID,
+ sizeof(dwApartmentID), NULL)) {
+ ExtOut(" %8x", dwApartmentID);
+ }
+ else
+ ExtOut(" %8x", 0);
+
+ DWORD dwTIDCaller;
+ if (SafeReadMemory(OleTlsDataAddr+offsetof(SOleTlsData,dwTIDCaller),
+ &dwTIDCaller,
+ sizeof(dwTIDCaller), NULL)) {
+ ExtOut(" %8x", dwTIDCaller);
+ }
+ else
+ ExtOut(" %8x", 0);
+
+ size_t Context;
+ if (SafeReadMemory(OleTlsDataAddr+offsetof(SOleTlsData,pCurrentCtx),
+ &Context,
+ sizeof(Context), NULL)) {
+ ExtOut(" %p", SOS_PTR(Context));
+ }
+ else
+ ExtOut(" %p", SOS_PTR(0));
+
+ }
+ else
+ ExtOut(" Ukn");
+ }
+ else
+ ExtOut(" Ukn");
+ ExtOut("\n");
+ }
+
+ g_ExtSystem->SetCurrentThreadId(curId);
+ return Status;
+}
+#endif // FEATURE_COMINTEROP
+
+#endif // FEATURE_PAL
+
+BOOL traverseEh(UINT clauseIndex,UINT totalClauses,DACEHInfo *pEHInfo,LPVOID token)
+{
+ size_t methodStart = (size_t) token;
+
+ if (IsInterrupt())
+ {
+ return FALSE;
+ }
+
+ ExtOut("EHHandler %d: %s ", clauseIndex, EHTypeName(pEHInfo->clauseType));
+
+ LPCWSTR typeName = EHTypedClauseTypeName(pEHInfo);
+ if (typeName != NULL)
+ {
+ ExtOut("catch(%S) ", typeName);
+ }
+
+ if (IsClonedFinally(pEHInfo))
+ ExtOut("(cloned finally)");
+ else if (pEHInfo->isDuplicateClause)
+ ExtOut("(duplicate)");
+
+ ExtOut("\n");
+ ExtOut("Clause: ");
+
+ ULONG64 addrStart = pEHInfo->tryStartOffset + methodStart;
+ ULONG64 addrEnd = pEHInfo->tryEndOffset + methodStart;
+
+#ifdef _WIN64
+ ExtOut("[%08x`%08x, %08x`%08x]",
+ (ULONG)(addrStart >> 32), (ULONG)addrStart,
+ (ULONG)(addrEnd >> 32), (ULONG)addrEnd);
+#else
+ ExtOut("[%08x, %08x]", (ULONG)addrStart, (ULONG)addrEnd);
+#endif
+
+ ExtOut(" [%x, %x]\n",
+ (UINT32) pEHInfo->tryStartOffset,
+ (UINT32) pEHInfo->tryEndOffset);
+
+ ExtOut("Handler: ");
+
+ addrStart = pEHInfo->handlerStartOffset + methodStart;
+ addrEnd = pEHInfo->handlerEndOffset + methodStart;
+
+#ifdef _WIN64
+ ExtOut("[%08x`%08x, %08x`%08x]",
+ (ULONG)(addrStart >> 32), (ULONG)addrStart,
+ (ULONG)(addrEnd >> 32), (ULONG)addrEnd);
+#else
+ ExtOut("[%08x, %08x]", (ULONG)addrStart, (ULONG)addrEnd);
+#endif
+
+ ExtOut(" [%x, %x]\n",
+ (UINT32) pEHInfo->handlerStartOffset,
+ (UINT32) pEHInfo->handlerEndOffset);
+
+ if (pEHInfo->clauseType == EHFilter)
+ {
+ ExtOut("Filter: ");
+
+ addrStart = pEHInfo->filterOffset + methodStart;
+
+#ifdef _WIN64
+ ExtOut("[%08x`%08x]", (ULONG)(addrStart >> 32), (ULONG)addrStart);
+#else
+ ExtOut("[%08x]", (ULONG)addrStart);
+#endif
+
+ ExtOut(" [%x]\n",
+ (UINT32) pEHInfo->filterOffset);
+ }
+
+ ExtOut("\n");
+ return TRUE;
+}
+
+DECLARE_API(EHInfo)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ DWORD_PTR dwStartAddr = NULL;
+ BOOL dml = FALSE;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"/d", &dml, COBOOL, FALSE},
+ };
+
+ CMDValue arg[] =
+ { // vptr, type
+ {&dwStartAddr, COHEX},
+ };
+
+ size_t nArg;
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg) || (0 == nArg))
+ {
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+ DWORD_PTR tmpAddr = dwStartAddr;
+
+ if (!IsMethodDesc(dwStartAddr))
+ {
+ JITTypes jitType;
+ DWORD_PTR methodDesc;
+ DWORD_PTR gcinfoAddr;
+ IP2MethodDesc (dwStartAddr, methodDesc, jitType, gcinfoAddr);
+ tmpAddr = methodDesc;
+ }
+
+ DacpMethodDescData MD;
+ if ((tmpAddr == 0) || (MD.Request(g_sos, TO_CDADDR(tmpAddr)) != S_OK))
+ {
+ ExtOut("%p is not a MethodDesc\n", SOS_PTR(tmpAddr));
+ return Status;
+ }
+
+ if (1 == nArg && !MD.bHasNativeCode)
+ {
+ ExtOut("No EH info available\n");
+ return Status;
+ }
+
+ DacpCodeHeaderData codeHeaderData;
+ if (codeHeaderData.Request(g_sos, TO_CDADDR(MD.NativeCodeAddr)) != S_OK)
+ {
+ ExtOut("Unable to get codeHeader information\n");
+ return Status;
+ }
+
+ DMLOut("MethodDesc: %s\n", DMLMethodDesc(MD.MethodDescPtr));
+ DumpMDInfo(TO_TADDR(MD.MethodDescPtr));
+
+ ExtOut("\n");
+ Status = g_sos->TraverseEHInfo(TO_CDADDR(MD.NativeCodeAddr), traverseEh, (LPVOID)MD.NativeCodeAddr);
+
+ if (Status == E_ABORT)
+ {
+ ExtOut("<user aborted>\n");
+ }
+ else if (Status != S_OK)
+ {
+ ExtOut("Failed to perform EHInfo traverse\n");
+ }
+
+ return Status;
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to dump the GC encoding of a managed *
+* function. *
+* *
+\**********************************************************************/
+DECLARE_API(GCInfo)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+
+ TADDR taStartAddr = NULL;
+ TADDR taGCInfoAddr;
+ BOOL dml = FALSE;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"/d", &dml, COBOOL, FALSE},
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&taStartAddr, COHEX},
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg) || (0 == nArg))
+ {
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+ TADDR tmpAddr = taStartAddr;
+
+ if (!IsMethodDesc(taStartAddr))
+ {
+ JITTypes jitType;
+ TADDR methodDesc;
+ TADDR gcinfoAddr;
+ IP2MethodDesc(taStartAddr, methodDesc, jitType, gcinfoAddr);
+ tmpAddr = methodDesc;
+ }
+
+ DacpMethodDescData MD;
+ if ((tmpAddr == 0) || (MD.Request(g_sos, TO_CDADDR(tmpAddr)) != S_OK))
+ {
+ ExtOut("%p is not a valid MethodDesc\n", SOS_PTR(taStartAddr));
+ return Status;
+ }
+
+ if (1 == nArg && !MD.bHasNativeCode)
+ {
+ ExtOut("No GC info available\n");
+ return Status;
+ }
+
+ DacpCodeHeaderData codeHeaderData;
+
+ if (
+ // Try to get code header data from taStartAddr. This will get the code
+ // header corresponding to the IP address, even if the function was rejitted
+ (codeHeaderData.Request(g_sos, TO_CDADDR(taStartAddr)) != S_OK) &&
+
+ // If that didn't work, just try to use the code address that the MD
+ // points to. If the function was rejitted, this will only give you the
+ // original JITted code, but that's better than nothing
+ (codeHeaderData.Request(g_sos, TO_CDADDR(MD.NativeCodeAddr)) != S_OK)
+ )
+ {
+ // We always used to emit this (before rejit support), even if we couldn't get
+ // the code header, so keep on doing so.
+ ExtOut("entry point %p\n", SOS_PTR(MD.NativeCodeAddr));
+
+ // And now the error....
+ ExtOut("Unable to get codeHeader information\n");
+ return Status;
+ }
+
+ // We have the code header, so use it to determine the method start
+
+ ExtOut("entry point %p\n", SOS_PTR(codeHeaderData.MethodStart));
+
+ if (codeHeaderData.JITType == TYPE_UNKNOWN)
+ {
+ ExtOut("unknown Jit\n");
+ return Status;
+ }
+ else if (codeHeaderData.JITType == TYPE_JIT)
+ {
+ ExtOut("Normal JIT generated code\n");
+ }
+ else if (codeHeaderData.JITType == TYPE_PJIT)
+ {
+ ExtOut("preJIT generated code\n");
+ }
+
+ taGCInfoAddr = TO_TADDR(codeHeaderData.GCInfo);
+
+ ExtOut("GC info %p\n", SOS_PTR(taGCInfoAddr));
+
+ // assume that GC encoding table is never more than
+ // 40 + methodSize * 2
+ int tableSize = 0;
+ if (!ClrSafeInt<int>::multiply(codeHeaderData.MethodSize, 2, tableSize) ||
+ !ClrSafeInt<int>::addition(tableSize, 40, tableSize))
+ {
+ ExtOut("<integer overflow>\n");
+ return E_FAIL;
+ }
+ ArrayHolder<BYTE> table = new NOTHROW BYTE[tableSize];
+ if (table == NULL)
+ {
+ ExtOut("Could not allocate memory to read the gc info.\n");
+ return E_OUTOFMEMORY;
+ }
+
+ memset(table, 0, tableSize);
+ // We avoid using move here, because we do not want to return
+ if (!SafeReadMemory(taGCInfoAddr, table, tableSize, NULL))
+ {
+ ExtOut("Could not read memory %p\n", SOS_PTR(taGCInfoAddr));
+ return Status;
+ }
+
+ // Mutable table pointer since we need to pass the appropriate
+ // offset into the table to DumpGCTable.
+ GCInfoToken gcInfoToken = { table, GCINFO_VERSION };
+ unsigned int methodSize = (unsigned int)codeHeaderData.MethodSize;
+
+ g_targetMachine->DumpGCInfo(gcInfoToken, methodSize, ExtOut, true /*encBytes*/, true /*bPrintHeader*/);
+
+ return Status;
+}
+
+#if !defined(FEATURE_PAL)
+
+void DecodeGCTableEntry (const char *fmt, ...)
+{
+ GCEncodingInfo *pInfo = (GCEncodingInfo*)GetFiberData();
+ va_list va;
+
+ //
+ // Append the new data to the buffer
+ //
+
+ va_start(va, fmt);
+
+ int cch = _vsnprintf_s(&pInfo->buf[pInfo->cch], _countof(pInfo->buf) - pInfo->cch, _countof(pInfo->buf) - pInfo->cch - 1, fmt, va);
+ if (cch >= 0)
+ pInfo->cch += cch;
+
+ va_end(va);
+
+ pInfo->buf[pInfo->cch] = '\0';
+
+ //
+ // If there are complete lines in the buffer, decode them.
+ //
+
+ for (;;)
+ {
+ char *pNewLine = strchr(pInfo->buf, '\n');
+
+ if (!pNewLine)
+ break;
+
+ //
+ // The line should start with a 16-bit (x86) or 32-bit (non-x86) hex
+ // offset. strtoul returns ULONG_MAX or 0 on failure. 0 is a valid
+ // offset for the first encoding, or while the last offset was 0.
+ //
+
+ if (isxdigit(pInfo->buf[0]))
+ {
+ char *pEnd;
+ ULONG ofs = strtoul(pInfo->buf, &pEnd, 16);
+
+ if ( isspace(*pEnd)
+ && -1 != ofs
+ && ( -1 == pInfo->ofs
+ || 0 == pInfo->ofs
+ || ofs > 0))
+ {
+ pInfo->ofs = ofs;
+ *pNewLine = '\0';
+
+ SwitchToFiber(pInfo->pvMainFiber);
+ }
+ }
+ else if (0 == strncmp(pInfo->buf, "Untracked:", 10))
+ {
+ pInfo->ofs = 0;
+ *pNewLine = '\0';
+
+ SwitchToFiber(pInfo->pvMainFiber);
+ }
+
+ //
+ // Shift the remaining data to the start of the buffer
+ //
+
+ strcpy_s(pInfo->buf, _countof(pInfo->buf), pNewLine+1);
+ pInfo->cch = (int)strlen(pInfo->buf);
+ }
+}
+
+
+VOID CALLBACK DumpGCTableFiberEntry (LPVOID pvGCEncodingInfo)
+{
+ GCEncodingInfo *pInfo = (GCEncodingInfo*)pvGCEncodingInfo;
+ GCInfoToken gcInfoToken = { pInfo->table, GCINFO_VERSION };
+ g_targetMachine->DumpGCInfo(gcInfoToken, pInfo->methodSize, DecodeGCTableEntry, false /*encBytes*/, false /*bPrintHeader*/);
+
+ pInfo->fDoneDecoding = true;
+ SwitchToFiber(pInfo->pvMainFiber);
+}
+#endif // !FEATURE_PAL
+
+BOOL gatherEh(UINT clauseIndex,UINT totalClauses,DACEHInfo *pEHInfo,LPVOID token)
+{
+ SOSEHInfo *pInfo = (SOSEHInfo *) token;
+
+ if (pInfo == NULL)
+ {
+ return FALSE;
+ }
+
+ if (pInfo->m_pInfos == NULL)
+ {
+ // First time, initialize structure
+ pInfo->EHCount = totalClauses;
+ pInfo->m_pInfos = new NOTHROW DACEHInfo[totalClauses];
+ if (pInfo->m_pInfos == NULL)
+ {
+ ReportOOM();
+ return FALSE;
+ }
+ }
+
+ pInfo->m_pInfos[clauseIndex] = *((DACEHInfo*)pEHInfo);
+ return TRUE;
+}
+
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to unassembly a managed function. *
+* It tries to print symbolic info for function call, contants... *
+* *
+\**********************************************************************/
+DECLARE_API(u)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+
+ DWORD_PTR dwStartAddr = NULL;
+ BOOL fWithGCInfo = FALSE;
+ BOOL fWithEHInfo = FALSE;
+ BOOL bSuppressLines = FALSE;
+ BOOL bDisplayOffsets = FALSE;
+ BOOL dml = FALSE;
+ size_t nArg;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+#ifndef FEATURE_PAL
+ {"-gcinfo", &fWithGCInfo, COBOOL, FALSE},
+#endif
+ {"-ehinfo", &fWithEHInfo, COBOOL, FALSE},
+ {"-n", &bSuppressLines, COBOOL, FALSE},
+ {"-o", &bDisplayOffsets, COBOOL, FALSE},
+#ifndef FEATURE_PAL
+ {"/d", &dml, COBOOL, FALSE},
+#endif
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&dwStartAddr, COHEX},
+ };
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg) || (nArg < 1))
+ {
+ return Status;
+ }
+ // symlines will be non-zero only if SYMOPT_LOAD_LINES was set in the symbol options
+ ULONG symlines = 0;
+ if (!bSuppressLines && SUCCEEDED(g_ExtSymbols->GetSymbolOptions(&symlines)))
+ {
+ symlines &= SYMOPT_LOAD_LINES;
+ }
+ bSuppressLines = bSuppressLines || (symlines == 0);
+
+ EnableDMLHolder dmlHolder(dml);
+ // dwStartAddr is either some IP address or a MethodDesc. Start off assuming it's a
+ // MethodDesc.
+ DWORD_PTR methodDesc = dwStartAddr;
+ if (!IsMethodDesc(methodDesc))
+ {
+ // Not a methodDesc, so gotta find it ourselves
+ DWORD_PTR tmpAddr = dwStartAddr;
+ JITTypes jt;
+ DWORD_PTR gcinfoAddr;
+ IP2MethodDesc (tmpAddr, methodDesc, jt,
+ gcinfoAddr);
+ if (!methodDesc || jt == TYPE_UNKNOWN)
+ {
+ // It is not managed code.
+ ExtOut("Unmanaged code\n");
+ UnassemblyUnmanaged(dwStartAddr, bSuppressLines);
+ return Status;
+ }
+ }
+
+ DacpMethodDescData MethodDescData;
+ if ((Status=MethodDescData.Request(g_sos, TO_CDADDR(methodDesc))) != S_OK)
+ {
+ ExtOut("Failed to get method desc for %p.\n", SOS_PTR(dwStartAddr));
+ return Status;
+ }
+
+ if (!MethodDescData.bHasNativeCode)
+ {
+ ExtOut("Not jitted yet\n");
+ return Status;
+ }
+
+ // Get the appropriate code header. If we were passed an MD, then use
+ // MethodDescData.NativeCodeAddr to find the code header; if we were passed an IP, use
+ // that IP to find the code header. This ensures that, for rejitted functions, we
+ // disassemble the rejit version that the user explicitly specified with their IP.
+ DacpCodeHeaderData codeHeaderData;
+ if (codeHeaderData.Request(
+ g_sos,
+ TO_CDADDR(
+ (dwStartAddr == methodDesc) ? MethodDescData.NativeCodeAddr : dwStartAddr)
+ ) != S_OK)
+
+ {
+ ExtOut("Unable to get codeHeader information\n");
+ return Status;
+ }
+
+ if (codeHeaderData.MethodStart == 0)
+ {
+ ExtOut("not a valid MethodDesc\n");
+ return Status;
+ }
+
+ if (codeHeaderData.JITType == TYPE_UNKNOWN)
+ {
+ ExtOut("unknown Jit\n");
+ return Status;
+ }
+ else if (codeHeaderData.JITType == TYPE_JIT)
+ {
+ ExtOut("Normal JIT generated code\n");
+ }
+ else if (codeHeaderData.JITType == TYPE_PJIT)
+ {
+ ExtOut("preJIT generated code\n");
+ }
+
+ NameForMD_s(methodDesc, g_mdName, mdNameLen);
+ ExtOut("%S\n", g_mdName);
+ if (codeHeaderData.ColdRegionStart != NULL)
+ {
+ ExtOut("Begin %p, size %x. Cold region begin %p, size %x\n",
+ SOS_PTR(codeHeaderData.MethodStart), codeHeaderData.HotRegionSize,
+ SOS_PTR(codeHeaderData.ColdRegionStart), codeHeaderData.ColdRegionSize);
+ }
+ else
+ {
+ ExtOut("Begin %p, size %x\n", SOS_PTR(codeHeaderData.MethodStart), codeHeaderData.MethodSize);
+ }
+
+#if !defined(FEATURE_PAL)
+ //
+ // Set up to mix gc info with the code if requested
+ //
+
+ GCEncodingInfo gcEncodingInfo = {0};
+
+ // The actual GC Encoding Table, this is updated during the course of the function.
+ gcEncodingInfo.table = NULL;
+
+ // The holder to make sure we clean up the memory for the table
+ ArrayHolder<BYTE> table = NULL;
+
+ if (fWithGCInfo)
+ {
+ // assume that GC encoding table is never more than 40 + methodSize * 2
+ int tableSize = 0;
+ if (!ClrSafeInt<int>::multiply(codeHeaderData.MethodSize, 2, tableSize) ||
+ !ClrSafeInt<int>::addition(tableSize, 40, tableSize))
+ {
+ ExtOut("<integer overflow>\n");
+ return E_FAIL;
+ }
+
+
+ // Assign the new array to the mutable gcEncodingInfo table and to the
+ // table ArrayHolder to clean this up when the function exits.
+ table = gcEncodingInfo.table = new NOTHROW BYTE[tableSize];
+
+ if (gcEncodingInfo.table == NULL)
+ {
+ ExtOut("Could not allocate memory to read the gc info.\n");
+ return E_OUTOFMEMORY;
+ }
+
+ memset (gcEncodingInfo.table, 0, tableSize);
+ // We avoid using move here, because we do not want to return
+ if (!SafeReadMemory(TO_TADDR(codeHeaderData.GCInfo), gcEncodingInfo.table, tableSize, NULL))
+ {
+ ExtOut("Could not read memory %p\n", SOS_PTR(codeHeaderData.GCInfo));
+ return Status;
+ }
+
+ //
+ // Skip the info header
+ //
+ gcEncodingInfo.methodSize = (unsigned int)codeHeaderData.MethodSize;
+
+ //
+ // DumpGCTable will call gcPrintf for each encoding. We'd like a "give
+ // me the next encoding" interface, but we're stuck with the callback.
+ // To reconcile this without messing up too much code, we'll create a
+ // fiber to dump the gc table. When we need the next gc encoding,
+ // we'll switch to this fiber. The callback will note the next offset,
+ // and switch back to the main fiber.
+ //
+
+ gcEncodingInfo.ofs = -1;
+ gcEncodingInfo.hotSizeToAdd = 0;
+
+ gcEncodingInfo.pvMainFiber = ConvertThreadToFiber(NULL);
+ if (!gcEncodingInfo.pvMainFiber && ERROR_ALREADY_FIBER == GetLastError())
+ gcEncodingInfo.pvMainFiber = GetCurrentFiber();
+
+ if (!gcEncodingInfo.pvMainFiber)
+ return Status;
+
+ gcEncodingInfo.pvGCTableFiber = CreateFiber(0, DumpGCTableFiberEntry, &gcEncodingInfo);
+ if (!gcEncodingInfo.pvGCTableFiber)
+ return Status;
+
+ SwitchToFiber(gcEncodingInfo.pvGCTableFiber);
+ }
+#endif
+
+ SOSEHInfo *pInfo = NULL;
+ if (fWithEHInfo)
+ {
+ pInfo = new NOTHROW SOSEHInfo;
+ if (pInfo == NULL)
+ {
+ ReportOOM();
+ }
+ else if (g_sos->TraverseEHInfo(MethodDescData.NativeCodeAddr, gatherEh, (LPVOID)pInfo) != S_OK)
+ {
+ ExtOut("Failed to gather EHInfo data\n");
+ delete pInfo;
+ pInfo = NULL;
+ }
+ }
+
+ if (codeHeaderData.ColdRegionStart == NULL)
+ {
+ g_targetMachine->Unassembly (
+ (DWORD_PTR) codeHeaderData.MethodStart,
+ ((DWORD_PTR)codeHeaderData.MethodStart) + codeHeaderData.MethodSize,
+ dwStartAddr,
+ (DWORD_PTR) MethodDescData.GCStressCodeCopy,
+#if !defined(FEATURE_PAL)
+ fWithGCInfo ? &gcEncodingInfo :
+#endif
+ NULL,
+ pInfo,
+ bSuppressLines,
+ bDisplayOffsets
+ );
+ }
+ else
+ {
+ ExtOut("Hot region:\n");
+ g_targetMachine->Unassembly (
+ (DWORD_PTR) codeHeaderData.MethodStart,
+ ((DWORD_PTR)codeHeaderData.MethodStart) + codeHeaderData.HotRegionSize,
+ dwStartAddr,
+ (DWORD_PTR) MethodDescData.GCStressCodeCopy,
+#if !defined(FEATURE_PAL)
+ fWithGCInfo ? &gcEncodingInfo :
+#endif
+ NULL,
+ pInfo,
+ bSuppressLines,
+ bDisplayOffsets
+ );
+
+ ExtOut("Cold region:\n");
+
+#if !defined(FEATURE_PAL)
+ // Displaying gcinfo for a cold region requires knowing the size of
+ // the hot region preceeding.
+ gcEncodingInfo.hotSizeToAdd = codeHeaderData.HotRegionSize;
+#endif
+ g_targetMachine->Unassembly (
+ (DWORD_PTR) codeHeaderData.ColdRegionStart,
+ ((DWORD_PTR)codeHeaderData.ColdRegionStart) + codeHeaderData.ColdRegionSize,
+ dwStartAddr,
+ ((DWORD_PTR) MethodDescData.GCStressCodeCopy) + codeHeaderData.HotRegionSize,
+#if !defined(FEATURE_PAL)
+ fWithGCInfo ? &gcEncodingInfo :
+#endif
+ NULL,
+ pInfo,
+ bSuppressLines,
+ bDisplayOffsets
+ );
+
+ }
+
+ if (pInfo)
+ {
+ delete pInfo;
+ pInfo = NULL;
+ }
+
+#if !defined(FEATURE_PAL)
+ if (fWithGCInfo)
+ DeleteFiber(gcEncodingInfo.pvGCTableFiber);
+#endif
+
+ return Status;
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to dump the in-memory stress log *
+* !DumpLog [filename] *
+* will dump the stress log corresponding to the clr.dll *
+* loaded in the debuggee's VAS *
+* !DumpLog -addr <addr_of_StressLog::theLog> [filename] *
+* will dump the stress log associated with any DLL linked *
+* against utilcode.lib, most commonly mscordbi.dll *
+* (e.g. !DumpLog -addr mscordbi!StressLog::theLog) *
+* *
+\**********************************************************************/
+DECLARE_API(DumpLog)
+{
+ INIT_API_NO_RET_ON_FAILURE();
+
+ MINIDUMP_NOT_SUPPORTED();
+
+ const char* fileName = "StressLog.txt";
+
+ CLRDATA_ADDRESS StressLogAddress = NULL;
+
+ StringHolder sFileName, sLogAddr;
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"-addr", &sLogAddr.data, COSTRING, TRUE}
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&sFileName.data, COSTRING}
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+ if (nArg > 0 && sFileName.data != NULL)
+ {
+ fileName = sFileName.data;
+ }
+
+ // allow users to specify -addr mscordbdi!StressLog::theLog, for example.
+ if (sLogAddr.data != NULL)
+ {
+ StressLogAddress = GetExpression(sLogAddr.data);
+ }
+
+ if (StressLogAddress == NULL)
+ {
+ if (g_bDacBroken)
+ {
+#ifdef FEATURE_PAL
+ ExtOut("No stress log address. DAC is broken; can't get it\n");
+ return E_FAIL;
+#else
+ // Try to find stress log symbols
+ DWORD_PTR dwAddr = GetValueFromExpression(MAIN_CLR_MODULE_NAME_A "!StressLog::theLog");
+ StressLogAddress = dwAddr;
+#endif
+ }
+ else if (g_sos->GetStressLogAddress(&StressLogAddress) != S_OK)
+ {
+ ExtOut("Unable to find stress log via DAC\n");
+ return E_FAIL;
+ }
+ }
+
+ if (StressLogAddress == NULL)
+ {
+ ExtOut("Please provide the -addr argument for the address of the stress log, since no recognized runtime is loaded.\n");
+ return E_FAIL;
+ }
+
+ ExtOut("Attempting to dump Stress log to file '%s'\n", fileName);
+
+
+
+ Status = StressLog::Dump(StressLogAddress, fileName, g_ExtData);
+
+ if (Status == S_OK)
+ ExtOut("SUCCESS: Stress log dumped\n");
+ else if (Status == S_FALSE)
+ ExtOut("No Stress log in the image, no file written\n");
+ else
+ ExtOut("FAILURE: Stress log not dumped\n");
+
+ return Status;
+}
+
+#ifdef TRACE_GC
+
+DECLARE_API (DumpGCLog)
+{
+ INIT_API_NODAC();
+ MINIDUMP_NOT_SUPPORTED();
+
+ if (GetEEFlavor() == UNKNOWNEE)
+ {
+ ExtOut("CLR not loaded\n");
+ return Status;
+ }
+
+ const char* fileName = "GCLog.txt";
+
+ while (isspace (*args))
+ args ++;
+
+ if (*args != 0)
+ fileName = args;
+
+ DWORD_PTR dwAddr = GetValueFromExpression(MAIN_CLR_MODULE_NAME_A "!SVR::gc_log_buffer");
+ moveN (dwAddr, dwAddr);
+
+ if (dwAddr == 0)
+ {
+ dwAddr = GetValueFromExpression(MAIN_CLR_MODULE_NAME_A "!WKS::gc_log_buffer");
+ moveN (dwAddr, dwAddr);
+ if (dwAddr == 0)
+ {
+ ExtOut("Can't get either WKS or SVR GC's log file");
+ return E_FAIL;
+ }
+ }
+
+ ExtOut("Dumping GC log at %08x\n", dwAddr);
+
+ g_bDacBroken = FALSE;
+
+ ExtOut("Attempting to dump GC log to file '%s'\n", fileName);
+
+ Status = E_FAIL;
+
+ HANDLE hGCLog = CreateFileA(
+ fileName,
+ GENERIC_WRITE,
+ FILE_SHARE_READ,
+ NULL,
+ CREATE_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL);
+
+ if (hGCLog == INVALID_HANDLE_VALUE)
+ {
+ ExtOut("failed to create file: %d\n", GetLastError());
+ goto exit;
+ }
+
+ int iLogSize = 1024*1024;
+ BYTE* bGCLog = new NOTHROW BYTE[iLogSize];
+ if (bGCLog == NULL)
+ {
+ ReportOOM();
+ goto exit;
+ }
+
+ memset (bGCLog, 0, iLogSize);
+ if (!SafeReadMemory(dwAddr, bGCLog, iLogSize, NULL))
+ {
+ ExtOut("failed to read memory from %08x\n", dwAddr);
+ }
+
+ int iRealLogSize = iLogSize - 1;
+ while (iRealLogSize >= 0)
+ {
+ if (bGCLog[iRealLogSize] != '*')
+ {
+ break;
+ }
+
+ iRealLogSize--;
+ }
+
+ DWORD dwWritten = 0;
+ WriteFile (hGCLog, bGCLog, iRealLogSize + 1, &dwWritten, NULL);
+
+ Status = S_OK;
+
+exit:
+
+ if (hGCLog != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle (hGCLog);
+ }
+
+ if (Status == S_OK)
+ ExtOut("SUCCESS: Stress log dumped\n");
+ else if (Status == S_FALSE)
+ ExtOut("No Stress log in the image, no file written\n");
+ else
+ ExtOut("FAILURE: Stress log not dumped\n");
+
+ return Status;
+}
+#endif //TRACE_GC
+
+#ifndef FEATURE_PAL
+DECLARE_API (DumpGCConfigLog)
+{
+ INIT_API();
+#ifdef GC_CONFIG_DRIVEN
+ MINIDUMP_NOT_SUPPORTED();
+
+ if (GetEEFlavor() == UNKNOWNEE)
+ {
+ ExtOut("CLR not loaded\n");
+ return Status;
+ }
+
+ const char* fileName = "GCConfigLog.txt";
+
+ while (isspace (*args))
+ args ++;
+
+ if (*args != 0)
+ fileName = args;
+
+ if (!InitializeHeapData ())
+ {
+ ExtOut("GC Heap not initialized yet.\n");
+ return S_OK;
+ }
+
+ BOOL fIsServerGC = IsServerBuild();
+
+ DWORD_PTR dwAddr = 0;
+ DWORD_PTR dwAddrOffset = 0;
+
+ if (fIsServerGC)
+ {
+ dwAddr = GetValueFromExpression(MAIN_CLR_MODULE_NAME_A "!SVR::gc_config_log_buffer");
+ dwAddrOffset = GetValueFromExpression(MAIN_CLR_MODULE_NAME_A "!SVR::gc_config_log_buffer_offset");
+ }
+ else
+ {
+ dwAddr = GetValueFromExpression(MAIN_CLR_MODULE_NAME_A "!WKS::gc_config_log_buffer");
+ dwAddrOffset = GetValueFromExpression(MAIN_CLR_MODULE_NAME_A "!WKS::gc_config_log_buffer_offset");
+ }
+
+ moveN (dwAddr, dwAddr);
+ moveN (dwAddrOffset, dwAddrOffset);
+
+ if (dwAddr == 0)
+ {
+ ExtOut("Can't get either WKS or SVR GC's config log buffer");
+ return E_FAIL;
+ }
+
+ ExtOut("Dumping GC log at %08x\n", dwAddr);
+
+ g_bDacBroken = FALSE;
+
+ ExtOut("Attempting to dump GC log to file '%s'\n", fileName);
+
+ Status = E_FAIL;
+
+ HANDLE hGCLog = CreateFileA(
+ fileName,
+ GENERIC_WRITE,
+ FILE_SHARE_READ,
+ NULL,
+ OPEN_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL);
+
+ if (hGCLog == INVALID_HANDLE_VALUE)
+ {
+ ExtOut("failed to create file: %d\n", GetLastError());
+ goto exit;
+ }
+
+ {
+ int iLogSize = (int)dwAddrOffset;
+
+ ArrayHolder<BYTE> bGCLog = new NOTHROW BYTE[iLogSize];
+ if (bGCLog == NULL)
+ {
+ ReportOOM();
+ goto exit;
+ }
+
+ memset (bGCLog, 0, iLogSize);
+ if (!SafeReadMemory(dwAddr, bGCLog, iLogSize, NULL))
+ {
+ ExtOut("failed to read memory from %08x\n", dwAddr);
+ }
+
+ SetFilePointer (hGCLog, 0, 0, FILE_END);
+ DWORD dwWritten;
+ WriteFile (hGCLog, bGCLog, iLogSize, &dwWritten, NULL);
+ }
+
+ Status = S_OK;
+
+exit:
+
+ if (hGCLog != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle (hGCLog);
+ }
+
+ if (Status == S_OK)
+ ExtOut("SUCCESS: Stress log dumped\n");
+ else if (Status == S_FALSE)
+ ExtOut("No Stress log in the image, no file written\n");
+ else
+ ExtOut("FAILURE: Stress log not dumped\n");
+
+ return Status;
+#else
+ ExtOut("Not implemented\n");
+ return S_OK;
+#endif //GC_CONFIG_DRIVEN
+}
+#endif // FEATURE_PAL
+
+#ifdef GC_CONFIG_DRIVEN
+static const char * const str_interesting_data_points[] =
+{
+ "pre short", // 0
+ "post short", // 1
+ "merged pins", // 2
+ "converted pins", // 3
+ "pre pin", // 4
+ "post pin", // 5
+ "pre and post pin", // 6
+ "pre short padded", // 7
+ "post short padded", // 7
+};
+
+static const char * const str_heap_compact_reasons[] =
+{
+ "low on ephemeral space",
+ "high fragmetation",
+ "couldn't allocate gaps",
+ "user specfied compact LOH",
+ "last GC before OOM",
+ "induced compacting GC",
+ "fragmented gen0 (ephemeral GC)",
+ "high memory load (ephemeral GC)",
+ "high memory load and frag",
+ "very high memory load and frag",
+ "no gc mode"
+};
+
+static BOOL gc_heap_compact_reason_mandatory_p[] =
+{
+ TRUE, //compact_low_ephemeral = 0,
+ FALSE, //compact_high_frag = 1,
+ TRUE, //compact_no_gaps = 2,
+ TRUE, //compact_loh_forced = 3,
+ TRUE, //compact_last_gc = 4
+ TRUE, //compact_induced_compacting = 5,
+ FALSE, //compact_fragmented_gen0 = 6,
+ FALSE, //compact_high_mem_load = 7,
+ TRUE, //compact_high_mem_frag = 8,
+ TRUE, //compact_vhigh_mem_frag = 9,
+ TRUE //compact_no_gc_mode = 10
+};
+
+static const char * const str_heap_expand_mechanisms[] =
+{
+ "reused seg with normal fit",
+ "reused seg with best fit",
+ "expand promoting eph",
+ "expand with a new seg",
+ "no memory for a new seg",
+ "expand in next full GC"
+};
+
+static const char * const str_bit_mechanisms[] =
+{
+ "using mark list",
+ "demotion"
+};
+
+static const char * const str_gc_global_mechanisms[] =
+{
+ "concurrent GCs",
+ "compacting GCs",
+ "promoting GCs",
+ "GCs that did demotion",
+ "card bundles",
+ "elevation logic"
+};
+
+void PrintInterestingGCInfo(DacpGCInterestingInfoData* dataPerHeap)
+{
+ ExtOut("Interesting data points\n");
+ size_t* data = dataPerHeap->interestingDataPoints;
+ for (int i = 0; i < NUM_GC_DATA_POINTS; i++)
+ {
+ ExtOut("%20s: %d\n", str_interesting_data_points[i], data[i]);
+ }
+
+ ExtOut("\nCompacting reasons\n");
+ data = dataPerHeap->compactReasons;
+ for (int i = 0; i < MAX_COMPACT_REASONS_COUNT; i++)
+ {
+ ExtOut("[%s]%35s: %d\n", (gc_heap_compact_reason_mandatory_p[i] ? "M" : "W"), str_heap_compact_reasons[i], data[i]);
+ }
+
+ ExtOut("\nExpansion mechanisms\n");
+ data = dataPerHeap->expandMechanisms;
+ for (int i = 0; i < MAX_EXPAND_MECHANISMS_COUNT; i++)
+ {
+ ExtOut("%30s: %d\n", str_heap_expand_mechanisms[i], data[i]);
+ }
+
+ ExtOut("\nOther mechanisms enabled\n");
+ data = dataPerHeap->bitMechanisms;
+ for (int i = 0; i < MAX_GC_MECHANISM_BITS_COUNT; i++)
+ {
+ ExtOut("%20s: %d\n", str_bit_mechanisms[i], data[i]);
+ }
+}
+#endif //GC_CONFIG_DRIVEN
+
+DECLARE_API(DumpGCData)
+{
+ INIT_API();
+
+#ifdef GC_CONFIG_DRIVEN
+ MINIDUMP_NOT_SUPPORTED();
+
+ if (!InitializeHeapData ())
+ {
+ ExtOut("GC Heap not initialized yet.\n");
+ return S_OK;
+ }
+
+ DacpGCInterestingInfoData interestingInfo;
+ interestingInfo.RequestGlobal(g_sos);
+ for (int i = 0; i < MAX_GLOBAL_GC_MECHANISMS_COUNT; i++)
+ {
+ ExtOut("%-30s: %d\n", str_gc_global_mechanisms[i], interestingInfo.globalMechanisms[i]);
+ }
+
+ ExtOut("\n[info per heap]\n");
+
+ if (!IsServerBuild())
+ {
+ if (interestingInfo.Request(g_sos) != S_OK)
+ {
+ ExtOut("Error requesting interesting GC info\n");
+ return E_FAIL;
+ }
+
+ PrintInterestingGCInfo(&interestingInfo);
+ }
+ else
+ {
+ DWORD dwNHeaps = GetGcHeapCount();
+ DWORD dwAllocSize;
+ if (!ClrSafeInt<DWORD>::multiply(sizeof(CLRDATA_ADDRESS), dwNHeaps, dwAllocSize))
+ {
+ ExtOut("Failed to get GCHeaps: integer overflow\n");
+ return Status;
+ }
+
+ CLRDATA_ADDRESS *heapAddrs = (CLRDATA_ADDRESS*)alloca(dwAllocSize);
+ if (g_sos->GetGCHeapList(dwNHeaps, heapAddrs, NULL) != S_OK)
+ {
+ ExtOut("Failed to get GCHeaps\n");
+ return Status;
+ }
+
+ for (DWORD n = 0; n < dwNHeaps; n ++)
+ {
+ if (interestingInfo.Request(g_sos, heapAddrs[n]) != S_OK)
+ {
+ ExtOut("Heap %d: Error requesting interesting GC info\n", n);
+ return E_FAIL;
+ }
+
+ ExtOut("--------info for heap %d--------\n", n);
+ PrintInterestingGCInfo(&interestingInfo);
+ ExtOut("\n");
+ }
+ }
+
+ return S_OK;
+#else
+ ExtOut("Not implemented\n");
+ return S_OK;
+#endif //GC_CONFIG_DRIVEN
+}
+
+#ifndef FEATURE_PAL
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to dump the build number and type of the *
+* mscoree.dll *
+* *
+\**********************************************************************/
+DECLARE_API (EEVersion)
+{
+ INIT_API();
+
+ EEFLAVOR eef = GetEEFlavor();
+ if (eef == UNKNOWNEE) {
+ ExtOut("CLR not loaded\n");
+ return Status;
+ }
+
+ if (g_ExtSymbols2) {
+ VS_FIXEDFILEINFO version;
+
+ BOOL ret = GetEEVersion(&version);
+
+ if (ret)
+ {
+ if (version.dwFileVersionMS != (DWORD)-1)
+ {
+ ExtOut("%u.%u.%u.%u",
+ HIWORD(version.dwFileVersionMS),
+ LOWORD(version.dwFileVersionMS),
+ HIWORD(version.dwFileVersionLS),
+ LOWORD(version.dwFileVersionLS));
+ if (version.dwFileFlags & VS_FF_DEBUG)
+ {
+ ExtOut(" Checked or debug build");
+ }
+ else
+ {
+ BOOL fRet = IsRetailBuild ((size_t)moduleInfo[eef].baseAddr);
+
+ if (fRet)
+ ExtOut(" retail");
+ else
+ ExtOut(" free");
+ }
+
+ ExtOut("\n");
+ }
+ }
+ }
+
+ if (!InitializeHeapData ())
+ ExtOut("GC Heap not initialized, so GC mode is not determined yet.\n");
+ else if (IsServerBuild())
+ ExtOut("Server mode with %d gc heaps\n", GetGcHeapCount());
+ else
+ ExtOut("Workstation mode\n");
+
+ if (!GetGcStructuresValid())
+ {
+ ExtOut("In plan phase of garbage collection\n");
+ }
+
+ // Print SOS version
+ VS_FIXEDFILEINFO sosVersion;
+ if (GetSOSVersion(&sosVersion))
+ {
+ if (sosVersion.dwFileVersionMS != (DWORD)-1)
+ {
+ ExtOut("SOS Version: %u.%u.%u.%u",
+ HIWORD(sosVersion.dwFileVersionMS),
+ LOWORD(sosVersion.dwFileVersionMS),
+ HIWORD(sosVersion.dwFileVersionLS),
+ LOWORD(sosVersion.dwFileVersionLS));
+ if (sosVersion.dwFileFlags & VS_FF_DEBUG)
+ {
+ ExtOut(" Checked or debug build");
+ }
+ else
+ {
+ ExtOut(" retail build");
+ }
+
+ ExtOut("\n");
+ }
+ }
+ return Status;
+}
+#endif // FEATURE_PAL
+
+#ifndef FEATURE_PAL
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to print the environment setting for *
+* the current process. *
+* *
+\**********************************************************************/
+DECLARE_API (ProcInfo)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ if (IsDumpFile())
+ {
+ ExtOut("!ProcInfo is not supported on a dump file.\n");
+ return Status;
+ }
+
+#define INFO_ENV 0x00000001
+#define INFO_TIME 0x00000002
+#define INFO_MEM 0x00000004
+#define INFO_ALL 0xFFFFFFFF
+
+ DWORD fProcInfo = INFO_ALL;
+
+ if (_stricmp (args, "-env") == 0) {
+ fProcInfo = INFO_ENV;
+ }
+
+ if (_stricmp (args, "-time") == 0) {
+ fProcInfo = INFO_TIME;
+ }
+
+ if (_stricmp (args, "-mem") == 0) {
+ fProcInfo = INFO_MEM;
+ }
+
+ if (fProcInfo & INFO_ENV) {
+ ExtOut("---------------------------------------\n");
+ ExtOut("Environment\n");
+ ULONG64 pPeb;
+ g_ExtSystem->GetCurrentProcessPeb(&pPeb);
+
+ static ULONG Offset_ProcessParam = -1;
+ static ULONG Offset_Environment = -1;
+ if (Offset_ProcessParam == -1)
+ {
+ ULONG TypeId;
+ ULONG64 NtDllBase;
+ if (SUCCEEDED(g_ExtSymbols->GetModuleByModuleName ("ntdll",0,NULL,
+ &NtDllBase)))
+ {
+ if (SUCCEEDED(g_ExtSymbols->GetTypeId (NtDllBase, "PEB", &TypeId)))
+ {
+ if (FAILED (g_ExtSymbols->GetFieldOffset(NtDllBase, TypeId,
+ "ProcessParameters", &Offset_ProcessParam)))
+ Offset_ProcessParam = -1;
+ }
+ if (SUCCEEDED(g_ExtSymbols->GetTypeId (NtDllBase, "_RTL_USER_PROCESS_PARAMETERS", &TypeId)))
+ {
+ if (FAILED (g_ExtSymbols->GetFieldOffset(NtDllBase, TypeId,
+ "Environment", &Offset_Environment)))
+ Offset_Environment = -1;
+ }
+ }
+ }
+ // We can not get it from PDB. Use the fixed one.
+ if (Offset_ProcessParam == -1)
+ Offset_ProcessParam = offsetof (DT_PEB, ProcessParameters);
+
+ if (Offset_Environment == -1)
+ Offset_Environment = offsetof (DT_RTL_USER_PROCESS_PARAMETERS, Environment);
+
+
+ ULONG64 addr = pPeb + Offset_ProcessParam;
+ DWORD_PTR value;
+ g_ExtData->ReadVirtual(UL64_TO_CDA(addr), &value, sizeof(PVOID), NULL);
+ addr = value + Offset_Environment;
+ g_ExtData->ReadVirtual(UL64_TO_CDA(addr), &value, sizeof(PVOID), NULL);
+
+ static WCHAR buffer[DT_OS_PAGE_SIZE/2];
+ ULONG readBytes = DT_OS_PAGE_SIZE;
+ ULONG64 Page;
+ if ((g_ExtData->ReadDebuggerData( DEBUG_DATA_MmPageSize, &Page, sizeof(Page), NULL)) == S_OK
+ && Page > 0)
+ {
+ ULONG uPageSize = (ULONG)(ULONG_PTR)Page;
+ if (readBytes > uPageSize) {
+ readBytes = uPageSize;
+ }
+ }
+ addr = value;
+ while (1) {
+ if (IsInterrupt())
+ return Status;
+ if (FAILED(g_ExtData->ReadVirtual(UL64_TO_CDA(addr), &buffer, readBytes, NULL)))
+ break;
+ addr += readBytes;
+ WCHAR *pt = buffer;
+ WCHAR *end = pt;
+ while (pt < &buffer[DT_OS_PAGE_SIZE/2]) {
+ end = _wcschr (pt, L'\0');
+ if (end == NULL) {
+ char format[20];
+ sprintf_s (format,_countof (format), "%dS", &buffer[DT_OS_PAGE_SIZE/2] - pt);
+ ExtOut(format, pt);
+ break;
+ }
+ else if (end == pt) {
+ break;
+ }
+ ExtOut("%S\n", pt);
+ pt = end + 1;
+ }
+ if (end == pt) {
+ break;
+ }
+ }
+ }
+
+ HANDLE hProcess = INVALID_HANDLE_VALUE;
+ if (fProcInfo & (INFO_TIME | INFO_MEM)) {
+ ULONG64 handle;
+ g_ExtSystem->GetCurrentProcessHandle(&handle);
+ hProcess = (HANDLE)handle;
+ }
+
+ if (!IsDumpFile() && fProcInfo & INFO_TIME) {
+ FILETIME CreationTime;
+ FILETIME ExitTime;
+ FILETIME KernelTime;
+ FILETIME UserTime;
+
+ typedef BOOL (WINAPI *FntGetProcessTimes)(HANDLE, LPFILETIME, LPFILETIME, LPFILETIME, LPFILETIME);
+ static FntGetProcessTimes pFntGetProcessTimes = (FntGetProcessTimes)-1;
+ if (pFntGetProcessTimes == (FntGetProcessTimes)-1) {
+ HINSTANCE hstat = LoadLibrary ("Kernel32.dll");
+ if (hstat != 0)
+ {
+ pFntGetProcessTimes = (FntGetProcessTimes)GetProcAddress (hstat, "GetProcessTimes");
+ FreeLibrary (hstat);
+ }
+ else
+ pFntGetProcessTimes = NULL;
+ }
+
+ if (pFntGetProcessTimes && pFntGetProcessTimes (hProcess,&CreationTime,&ExitTime,&KernelTime,&UserTime)) {
+ ExtOut("---------------------------------------\n");
+ ExtOut("Process Times\n");
+ static char *Month[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep",
+ "Oct", "Nov", "Dec"};
+ SYSTEMTIME SystemTime;
+ FILETIME LocalFileTime;
+ if (FileTimeToLocalFileTime (&CreationTime,&LocalFileTime)
+ && FileTimeToSystemTime (&LocalFileTime,&SystemTime)) {
+ ExtOut("Process Started at: %4d %s %2d %d:%d:%d.%02d\n",
+ SystemTime.wYear, Month[SystemTime.wMonth-1], SystemTime.wDay,
+ SystemTime.wHour, SystemTime.wMinute,
+ SystemTime.wSecond, SystemTime.wMilliseconds/10);
+ }
+
+ DWORD nDay = 0;
+ DWORD nHour = 0;
+ DWORD nMin = 0;
+ DWORD nSec = 0;
+ DWORD nHundred = 0;
+
+ ULONG64 totalTime;
+
+ totalTime = KernelTime.dwLowDateTime + (((ULONG64)KernelTime.dwHighDateTime) << 32);
+ nDay = (DWORD)(totalTime/(24*3600*10000000ui64));
+ totalTime %= 24*3600*10000000ui64;
+ nHour = (DWORD)(totalTime/(3600*10000000ui64));
+ totalTime %= 3600*10000000ui64;
+ nMin = (DWORD)(totalTime/(60*10000000));
+ totalTime %= 60*10000000;
+ nSec = (DWORD)(totalTime/10000000);
+ totalTime %= 10000000;
+ nHundred = (DWORD)(totalTime/100000);
+ ExtOut("Kernel CPU time : %d days %02d:%02d:%02d.%02d\n",
+ nDay, nHour, nMin, nSec, nHundred);
+
+ DWORD sDay = nDay;
+ DWORD sHour = nHour;
+ DWORD sMin = nMin;
+ DWORD sSec = nSec;
+ DWORD sHundred = nHundred;
+
+ totalTime = UserTime.dwLowDateTime + (((ULONG64)UserTime.dwHighDateTime) << 32);
+ nDay = (DWORD)(totalTime/(24*3600*10000000ui64));
+ totalTime %= 24*3600*10000000ui64;
+ nHour = (DWORD)(totalTime/(3600*10000000ui64));
+ totalTime %= 3600*10000000ui64;
+ nMin = (DWORD)(totalTime/(60*10000000));
+ totalTime %= 60*10000000;
+ nSec = (DWORD)(totalTime/10000000);
+ totalTime %= 10000000;
+ nHundred = (DWORD)(totalTime/100000);
+ ExtOut("User CPU time : %d days %02d:%02d:%02d.%02d\n",
+ nDay, nHour, nMin, nSec, nHundred);
+
+ sDay += nDay;
+ sHour += nHour;
+ sMin += nMin;
+ sSec += nSec;
+ sHundred += nHundred;
+ if (sHundred >= 100) {
+ sSec += sHundred/100;
+ sHundred %= 100;
+ }
+ if (sSec >= 60) {
+ sMin += sSec/60;
+ sSec %= 60;
+ }
+ if (sMin >= 60) {
+ sHour += sMin/60;
+ sMin %= 60;
+ }
+ if (sHour >= 24) {
+ sDay += sHour/24;
+ sHour %= 24;
+ }
+ ExtOut("Total CPU time : %d days %02d:%02d:%02d.%02d\n",
+ sDay, sHour, sMin, sSec, sHundred);
+ }
+ }
+
+ if (!IsDumpFile() && fProcInfo & INFO_MEM) {
+ typedef
+ NTSTATUS
+ (NTAPI
+ *FntNtQueryInformationProcess)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
+
+ static FntNtQueryInformationProcess pFntNtQueryInformationProcess = (FntNtQueryInformationProcess)-1;
+ if (pFntNtQueryInformationProcess == (FntNtQueryInformationProcess)-1) {
+ HINSTANCE hstat = LoadLibrary ("ntdll.dll");
+ if (hstat != 0)
+ {
+ pFntNtQueryInformationProcess = (FntNtQueryInformationProcess)GetProcAddress (hstat, "NtQueryInformationProcess");
+ FreeLibrary (hstat);
+ }
+ else
+ pFntNtQueryInformationProcess = NULL;
+ }
+ VM_COUNTERS memory;
+ if (pFntNtQueryInformationProcess &&
+ NT_SUCCESS (pFntNtQueryInformationProcess (hProcess,ProcessVmCounters,&memory,sizeof(memory),NULL))) {
+ ExtOut("---------------------------------------\n");
+ ExtOut("Process Memory\n");
+ ExtOut("WorkingSetSize: %8d KB PeakWorkingSetSize: %8d KB\n",
+ memory.WorkingSetSize/1024, memory.PeakWorkingSetSize/1024);
+ ExtOut("VirtualSize: %8d KB PeakVirtualSize: %8d KB\n",
+ memory.VirtualSize/1024, memory.PeakVirtualSize/1024);
+ ExtOut("PagefileUsage: %8d KB PeakPagefileUsage: %8d KB\n",
+ memory.PagefileUsage/1024, memory.PeakPagefileUsage/1024);
+ }
+
+ MEMORYSTATUS memstat;
+ GlobalMemoryStatus (&memstat);
+ ExtOut("---------------------------------------\n");
+ ExtOut("%ld percent of memory is in use.\n\n",
+ memstat.dwMemoryLoad);
+ ExtOut("Memory Availability (Numbers in MB)\n\n");
+ ExtOut(" %8s %8s\n", "Total", "Avail");
+ ExtOut("Physical Memory %8d %8d\n", memstat.dwTotalPhys/1024/1024, memstat.dwAvailPhys/1024/1024);
+ ExtOut("Page File %8d %8d\n", memstat.dwTotalPageFile/1024/1024, memstat.dwAvailPageFile/1024/1024);
+ ExtOut("Virtual Memory %8d %8d\n", memstat.dwTotalVirtual/1024/1024, memstat.dwAvailVirtual/1024/1024);
+ }
+
+ return Status;
+}
+#endif // FEATURE_PAL
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to find the address of EE data for a *
+* metadata token. *
+* *
+\**********************************************************************/
+DECLARE_API(Token2EE)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ StringHolder DllName;
+ ULONG64 token = 0;
+ BOOL dml = FALSE;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+#ifndef FEATURE_PAL
+ {"/d", &dml, COBOOL, FALSE},
+#endif
+ };
+
+ CMDValue arg[] =
+ { // vptr, type
+ {&DllName.data, COSTRING},
+ {&token, COHEX}
+ };
+
+ size_t nArg;
+ if (!GetCMDOption(args,option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+ if (nArg!=2)
+ {
+ ExtOut("Usage: !Token2EE module_name mdToken\n");
+ ExtOut(" You can pass * for module_name to search all modules.\n");
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+ int numModule;
+ ArrayHolder<DWORD_PTR> moduleList = NULL;
+
+ if (strcmp(DllName.data, "*") == 0)
+ {
+ moduleList = ModuleFromName(NULL, &numModule);
+ }
+ else
+ {
+ moduleList = ModuleFromName(DllName.data, &numModule);
+ }
+
+ if (moduleList == NULL)
+ {
+ ExtOut("Failed to request module list.\n");
+ }
+ else
+ {
+ for (int i = 0; i < numModule; i ++)
+ {
+ if (IsInterrupt())
+ break;
+
+ if (i > 0)
+ {
+ ExtOut("--------------------------------------\n");
+ }
+
+ DWORD_PTR dwAddr = moduleList[i];
+ WCHAR FileName[MAX_LONGPATH];
+ FileNameForModule(dwAddr, FileName);
+
+ // We'd like a short form for this output
+ LPWSTR pszFilename = _wcsrchr (FileName, DIRECTORY_SEPARATOR_CHAR_W);
+ if (pszFilename == NULL)
+ {
+ pszFilename = FileName;
+ }
+ else
+ {
+ pszFilename++; // skip past the last "\" character
+ }
+
+ DMLOut("Module: %s\n", DMLModule(dwAddr));
+ ExtOut("Assembly: %S\n", pszFilename);
+
+ GetInfoFromModule(dwAddr, (ULONG)token);
+ }
+ }
+
+ return Status;
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to find the address of EE data for a *
+* metadata token. *
+* *
+\**********************************************************************/
+DECLARE_API(Name2EE)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ StringHolder DllName, TypeName;
+ BOOL dml = FALSE;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+#ifndef FEATURE_PAL
+ {"/d", &dml, COBOOL, FALSE},
+#endif
+ };
+
+ CMDValue arg[] =
+ { // vptr, type
+ {&DllName.data, COSTRING},
+ {&TypeName.data, COSTRING}
+ };
+ size_t nArg;
+
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+
+ if (nArg == 1)
+ {
+ // The input may be in the form <modulename>!<type>
+ // If so, do some surgery on the input params.
+
+ // There should be only 1 ! character
+ LPSTR pszSeperator = strchr (DllName.data, '!');
+ if (pszSeperator != NULL)
+ {
+ if (strchr (pszSeperator + 1, '!') == NULL)
+ {
+ size_t capacity_TypeName_data = strlen(pszSeperator + 1) + 1;
+ TypeName.data = new NOTHROW char[capacity_TypeName_data];
+ if (TypeName.data)
+ {
+ // get the type name,
+ strcpy_s (TypeName.data, capacity_TypeName_data, pszSeperator + 1);
+ // and truncate DllName
+ *pszSeperator = '\0';
+
+ // Do some extra validation
+ if (strlen (DllName.data) >= 1 &&
+ strlen (TypeName.data) > 1)
+ {
+ nArg = 2;
+ }
+ }
+ }
+ }
+ }
+
+ if (nArg != 2)
+ {
+ ExtOut("Usage: " SOSPrefix "name2ee module_name item_name\n");
+ ExtOut(" or " SOSPrefix "name2ee module_name!item_name\n");
+ ExtOut(" use * for module_name to search all loaded modules\n");
+ ExtOut("Examples: " SOSPrefix "name2ee mscorlib.dll System.String.ToString\n");
+ ExtOut(" " SOSPrefix "name2ee *!System.String\n");
+ return Status;
+ }
+
+ int numModule;
+ ArrayHolder<DWORD_PTR> moduleList = NULL;
+ if (strcmp(DllName.data, "*") == 0)
+ {
+ moduleList = ModuleFromName(NULL, &numModule);
+ }
+ else
+ {
+ moduleList = ModuleFromName(DllName.data, &numModule);
+ }
+
+
+ if (moduleList == NULL)
+ {
+ ExtOut("Failed to request module list.\n", DllName.data);
+ }
+ else
+ {
+ for (int i = 0; i < numModule; i ++)
+ {
+ if (IsInterrupt())
+ break;
+
+ if (i > 0)
+ {
+ ExtOut("--------------------------------------\n");
+ }
+
+ DWORD_PTR dwAddr = moduleList[i];
+ WCHAR FileName[MAX_LONGPATH];
+ FileNameForModule (dwAddr, FileName);
+
+ // We'd like a short form for this output
+ LPWSTR pszFilename = _wcsrchr (FileName, DIRECTORY_SEPARATOR_CHAR_W);
+ if (pszFilename == NULL)
+ {
+ pszFilename = FileName;
+ }
+ else
+ {
+ pszFilename++; // skip past the last "\" character
+ }
+
+ DMLOut("Module: %s\n", DMLModule(dwAddr));
+ ExtOut("Assembly: %S\n", pszFilename);
+ GetInfoFromName(dwAddr, TypeName.data);
+ }
+ }
+
+ return Status;
+}
+
+
+#ifndef FEATURE_PAL
+DECLARE_API(PathTo)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ DWORD_PTR root = NULL;
+ DWORD_PTR target = NULL;
+ BOOL dml = FALSE;
+ size_t nArg;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"/d", &dml, COBOOL, FALSE},
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&root, COHEX},
+ {&target, COHEX},
+ };
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ if (root == 0 || target == 0)
+ {
+ ExtOut("Invalid argument %s\n", args);
+ return Status;
+ }
+
+ GCRootImpl gcroot;
+ bool result = gcroot.PrintPathToObject(root, target);
+
+ if (!result)
+ ExtOut("Did not find a path from %p to %p.\n", SOS_PTR(root), SOS_PTR(target));
+
+ return Status;
+}
+#endif
+
+
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function finds all roots (on stack or in handles) for a *
+* given object. *
+* *
+\**********************************************************************/
+DECLARE_API(GCRoot)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ BOOL bNoStacks = FALSE;
+ DWORD_PTR obj = NULL;
+ BOOL dml = FALSE;
+ BOOL all = FALSE;
+ size_t nArg;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"-nostacks", &bNoStacks, COBOOL, FALSE},
+ {"-all", &all, COBOOL, FALSE},
+#ifndef FEATURE_PAL
+ {"/d", &dml, COBOOL, FALSE},
+#endif
+ };
+ CMDValue arg[] =
+
+ { // vptr, type
+ {&obj, COHEX}
+ };
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+ if (obj == 0)
+ {
+ ExtOut("Invalid argument %s\n", args);
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+ GCRootImpl gcroot;
+ int i = gcroot.PrintRootsForObject(obj, all == TRUE, bNoStacks == TRUE);
+
+ if (IsInterrupt())
+ ExtOut("Interrupted, data may be incomplete.\n");
+
+ if (all)
+ ExtOut("Found %d roots.\n", i);
+ else
+ ExtOut("Found %d unique roots (run '!GCRoot -all' to see all roots).\n", i);
+
+ return Status;
+}
+
+DECLARE_API(GCWhere)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ BOOL dml = FALSE;
+ BOOL bGetBrick;
+ BOOL bGetCard;
+ TADDR taddrObj = 0;
+ size_t nArg;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"-brick", &bGetBrick, COBOOL, FALSE},
+ {"-card", &bGetCard, COBOOL, FALSE},
+ {"/d", &dml, COBOOL, FALSE},
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&taddrObj, COHEX}
+ };
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+ // Obtain allocation context for each managed thread.
+ AllocInfo allocInfo;
+ allocInfo.Init();
+
+ TADDR_SEGINFO trngSeg = { 0, 0, 0 };
+ TADDR_RANGE allocCtx = { 0, 0 };
+ int gen = -1;
+ BOOL bLarge = FALSE;
+ BOOL bFound = FALSE;
+
+ size_t size = 0;
+ if (sos::IsObject(taddrObj))
+ {
+ TADDR taddrMT;
+ BOOL bContainsPointers;
+ if(FAILED(GetMTOfObject(taddrObj, &taddrMT)) ||
+ !GetSizeEfficient(taddrObj, taddrMT, FALSE, size, bContainsPointers))
+ {
+ ExtWarn("Couldn't get size for object %#p: possible heap corruption.\n",
+ SOS_PTR(taddrObj));
+ }
+ }
+
+ if (!IsServerBuild())
+ {
+ DacpGcHeapDetails heapDetails;
+ if (heapDetails.Request(g_sos) != S_OK)
+ {
+ ExtOut("Error requesting gc heap details\n");
+ return Status;
+ }
+
+ if (GCObjInHeap(taddrObj, heapDetails, trngSeg, gen, allocCtx, bLarge))
+ {
+ ExtOut("Address " WIN64_8SPACES " Gen Heap segment " WIN64_8SPACES " begin " WIN64_8SPACES " allocated " WIN64_8SPACES " size\n");
+ ExtOut("%p %d %2d %p %p %p 0x%x(%d)\n",
+ SOS_PTR(taddrObj), gen, 0, SOS_PTR(trngSeg.segAddr), SOS_PTR(trngSeg.start), SOS_PTR(trngSeg.end), size, size);
+ bFound = TRUE;
+ }
+ }
+ else
+ {
+ DacpGcHeapData gcheap;
+ if (gcheap.Request(g_sos) != S_OK)
+ {
+ ExtOut("Error requesting GC Heap data\n");
+ return Status;
+ }
+
+ DWORD dwAllocSize;
+ DWORD dwNHeaps = gcheap.HeapCount;
+ if (!ClrSafeInt<DWORD>::multiply(sizeof(CLRDATA_ADDRESS), dwNHeaps, dwAllocSize))
+ {
+ ExtOut("Failed to get GCHeaps: integer overflow\n");
+ return Status;
+ }
+
+ CLRDATA_ADDRESS *heapAddrs = (CLRDATA_ADDRESS*)alloca(dwAllocSize);
+ if (g_sos->GetGCHeapList(dwNHeaps, heapAddrs, NULL) != S_OK)
+ {
+ ExtOut("Failed to get GCHeaps\n");
+ return Status;
+ }
+
+ for (DWORD n = 0; n < dwNHeaps; n ++)
+ {
+ DacpGcHeapDetails heapDetails;
+ if (heapDetails.Request(g_sos, heapAddrs[n]) != S_OK)
+ {
+ ExtOut("Error requesting details\n");
+ return Status;
+ }
+
+ if (GCObjInHeap(taddrObj, heapDetails, trngSeg, gen, allocCtx, bLarge))
+ {
+ ExtOut("Address " WIN64_8SPACES " Gen Heap segment " WIN64_8SPACES " begin " WIN64_8SPACES " allocated" WIN64_8SPACES " size\n");
+ ExtOut("%p %d %2d %p %p %p 0x%x(%d)\n",
+ SOS_PTR(taddrObj), gen, n, SOS_PTR(trngSeg.segAddr), SOS_PTR(trngSeg.start), SOS_PTR(trngSeg.end), size, size);
+ bFound = TRUE;
+ break;
+ }
+ }
+ }
+
+ if (!bFound)
+ {
+ ExtOut("Address %#p not found in the managed heap.\n", SOS_PTR(taddrObj));
+ }
+
+ return Status;
+}
+
+#ifndef FEATURE_PAL
+
+DECLARE_API(FindRoots)
+{
+#ifndef FEATURE_PAL
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ if (IsDumpFile())
+ {
+ ExtOut("!FindRoots is not supported on a dump file.\n");
+ return Status;
+ }
+
+ LONG_PTR gen = -100; // initialized outside the legal range: [-1, 2]
+ StringHolder sgen;
+ TADDR taObj = NULL;
+ BOOL dml = FALSE;
+ size_t nArg;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"-gen", &sgen.data, COSTRING, TRUE},
+ {"/d", &dml, COBOOL, FALSE},
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&taObj, COHEX}
+ };
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+ if (sgen.data != NULL)
+ {
+ if (_stricmp(sgen.data, "any") == 0)
+ {
+ gen = -1;
+ }
+ else
+ {
+ gen = GetExpression(sgen.data);
+ }
+ }
+ if ((gen < -1 || gen > 2) && (taObj == 0))
+ {
+ ExtOut("Incorrect options. Usage:\n\t!FindRoots -gen <N>\n\t\twhere N is 0, 1, 2, or \"any\". OR\n\t!FindRoots <obj>\n");
+ return Status;
+ }
+
+ if (gen >= -1 && gen <= 2)
+ {
+ IXCLRDataProcess2* idp2 = NULL;
+ if (FAILED(g_clrData->QueryInterface(IID_IXCLRDataProcess2, (void**) &idp2)))
+ {
+ ExtOut("Your version of the runtime/DAC do not support this command.\n");
+ return Status;
+ }
+
+ // Request GC_MARK_END notifications from debuggee
+ GcEvtArgs gea = { GC_MARK_END, { ((gen == -1) ? 7 : (1 << gen)) } };
+ idp2->SetGcNotification(gea);
+ // ... and register the notification handler
+ g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, "sxe -c \"!HandleCLRN\" clrn", 0);
+ // the above notification is removed in CNotification::OnGcEvent()
+ }
+ else
+ {
+ // verify that the last event in the debugger was indeed a CLRN exception
+ DEBUG_LAST_EVENT_INFO_EXCEPTION dle;
+ CNotification Notification;
+
+ if (!CheckCLRNotificationEvent(&dle))
+ {
+ ExtOut("The command !FindRoots can only be used after the debugger stopped on a CLRN GC notification.\n");
+ ExtOut("At this time !GCRoot should be used instead.\n");
+ return Status;
+ }
+ // validate argument
+ if (!g_snapshot.Build())
+ {
+ ExtOut("Unable to build snapshot of the garbage collector state\n");
+ return Status;
+ }
+
+ if (g_snapshot.GetHeap(taObj) == NULL)
+ {
+ ExtOut("Address %#p is not in the managed heap.\n", SOS_PTR(taObj));
+ return Status;
+ }
+
+ int ogen = g_snapshot.GetGeneration(taObj);
+ if (ogen > CNotification::GetCondemnedGen())
+ {
+ DMLOut("Object %s will survive this collection:\n\tgen(%#p) = %d > %d = condemned generation.\n",
+ DMLObject(taObj), SOS_PTR(taObj), ogen, CNotification::GetCondemnedGen());
+ return Status;
+ }
+
+ GCRootImpl gcroot;
+ int roots = gcroot.FindRoots(CNotification::GetCondemnedGen(), taObj);
+
+ ExtOut("Found %d roots.\n", roots);
+ }
+
+ return Status;
+#else
+ return E_NOTIMPL;
+#endif
+}
+
+class GCHandleStatsForDomains
+{
+public:
+ const static int SHARED_DOMAIN_INDEX = 0;
+ const static int SYSTEM_DOMAIN_INDEX = 1;
+
+ GCHandleStatsForDomains()
+ : m_singleDomainMode(FALSE), m_numDomains(0), m_pStatistics(NULL), m_pDomainPointers(NULL)
+ {
+ }
+
+ ~GCHandleStatsForDomains()
+ {
+ if (m_pStatistics)
+ {
+ if (m_singleDomainMode)
+ delete m_pStatistics;
+ else
+ delete [] m_pStatistics;
+ }
+
+ if (m_pDomainPointers)
+ delete [] m_pDomainPointers;
+ }
+
+ BOOL Init(BOOL singleDomainMode)
+ {
+ m_singleDomainMode = singleDomainMode;
+ if (m_singleDomainMode)
+ {
+ m_numDomains = 1;
+ m_pStatistics = new NOTHROW GCHandleStatistics();
+ if (m_pStatistics == NULL)
+ return FALSE;
+ }
+ else
+ {
+ DacpAppDomainStoreData adsData;
+ if (adsData.Request(g_sos) != S_OK)
+ return FALSE;
+
+ m_numDomains = adsData.DomainCount + 2;
+ ArrayHolder<CLRDATA_ADDRESS> pArray = new NOTHROW CLRDATA_ADDRESS[adsData.DomainCount + 2];
+ if (pArray == NULL)
+ return FALSE;
+
+ pArray[SHARED_DOMAIN_INDEX] = adsData.sharedDomain;
+ pArray[SYSTEM_DOMAIN_INDEX] = adsData.systemDomain;
+
+ if (g_sos->GetAppDomainList(adsData.DomainCount, pArray+2, NULL) != S_OK)
+ return FALSE;
+
+ m_pDomainPointers = pArray.Detach();
+ m_pStatistics = new NOTHROW GCHandleStatistics[adsData.DomainCount + 2];
+ if (m_pStatistics == NULL)
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ GCHandleStatistics *LookupStatistics(CLRDATA_ADDRESS appDomainPtr) const
+ {
+ if (m_singleDomainMode)
+ {
+ // You can pass NULL appDomainPtr if you are in singleDomainMode
+ return m_pStatistics;
+ }
+ else
+ {
+ for (int i=0; i < m_numDomains; i++)
+ if (m_pDomainPointers[i] == appDomainPtr)
+ return m_pStatistics + i;
+ }
+
+ return NULL;
+ }
+
+
+ GCHandleStatistics *GetStatistics(int appDomainIndex) const
+ {
+ SOS_Assert(appDomainIndex >= 0);
+ SOS_Assert(appDomainIndex < m_numDomains);
+
+ return m_singleDomainMode ? m_pStatistics : m_pStatistics + appDomainIndex;
+ }
+
+ int GetNumDomains() const
+ {
+ return m_numDomains;
+ }
+
+ CLRDATA_ADDRESS GetDomain(int index) const
+ {
+ SOS_Assert(index >= 0);
+ SOS_Assert(index < m_numDomains);
+ return m_pDomainPointers[index];
+ }
+
+private:
+ BOOL m_singleDomainMode;
+ int m_numDomains;
+ GCHandleStatistics *m_pStatistics;
+ CLRDATA_ADDRESS *m_pDomainPointers;
+};
+
+class GCHandlesImpl
+{
+public:
+ GCHandlesImpl(PCSTR args)
+ : mPerDomain(FALSE), mStat(FALSE), mDML(FALSE), mType((int)~0)
+ {
+ ArrayHolder<char> type = NULL;
+ CMDOption option[] =
+ {
+ {"-perdomain", &mPerDomain, COBOOL, FALSE},
+ {"-stat", &mStat, COBOOL, FALSE},
+ {"-type", &type, COSTRING, TRUE},
+ {"/d", &mDML, COBOOL, FALSE},
+ };
+
+ if (!GetCMDOption(args,option,_countof(option),NULL,0,NULL))
+ sos::Throw<sos::Exception>("Failed to parse command line arguments.");
+
+ if (type != NULL)
+ if (_stricmp(type, "Pinned") == 0)
+ mType = HNDTYPE_PINNED;
+ else if (_stricmp(type, "RefCounted") == 0)
+ mType = HNDTYPE_REFCOUNTED;
+ else if (_stricmp(type, "WeakShort") == 0)
+ mType = HNDTYPE_WEAK_SHORT;
+ else if (_stricmp(type, "WeakLong") == 0)
+ mType = HNDTYPE_WEAK_LONG;
+ else if (_stricmp(type, "Strong") == 0)
+ mType = HNDTYPE_STRONG;
+ else if (_stricmp(type, "Variable") == 0)
+ mType = HNDTYPE_VARIABLE;
+ else if (_stricmp(type, "AsyncPinned") == 0)
+ mType = HNDTYPE_ASYNCPINNED;
+ else if (_stricmp(type, "SizedRef") == 0)
+ mType = HNDTYPE_SIZEDREF;
+ else if (_stricmp(type, "Dependent") == 0)
+ mType = HNDTYPE_DEPENDENT;
+ else if (_stricmp(type, "WeakWinRT") == 0)
+ mType = HNDTYPE_WEAK_WINRT;
+ else
+ sos::Throw<sos::Exception>("Unknown handle type '%s'.", type.GetPtr());
+ }
+
+ void Run()
+ {
+ EnableDMLHolder dmlHolder(mDML);
+
+ mOut.ReInit(6, POINTERSIZE_HEX, AlignRight);
+ mOut.SetWidths(5, POINTERSIZE_HEX, 11, POINTERSIZE_HEX, 8, POINTERSIZE_HEX);
+ mOut.SetColAlignment(1, AlignLeft);
+
+ if (mHandleStat.Init(!mPerDomain) == FALSE)
+ sos::Throw<sos::Exception>("Error getting per-appdomain handle information");
+
+ if (!mStat)
+ mOut.WriteRow("Handle", "Type", "Object", "Size", "Data", "Type");
+
+ WalkHandles();
+
+ for (int i=0; (i < mHandleStat.GetNumDomains()) && !IsInterrupt(); i++)
+ {
+ GCHandleStatistics *pStats = mHandleStat.GetStatistics(i);
+
+ if (mPerDomain)
+ {
+ Print( "------------------------------------------------------------------------------\n");
+ Print("GC Handle Statistics for AppDomain ", AppDomainPtr(mHandleStat.GetDomain(i)));
+
+ if (i == GCHandleStatsForDomains::SHARED_DOMAIN_INDEX)
+ Print(" (Shared Domain)\n");
+ else if (i == GCHandleStatsForDomains::SYSTEM_DOMAIN_INDEX)
+ Print(" (System Domain)\n");
+ else
+ Print("\n");
+ }
+
+ if (!mStat)
+ Print("\n");
+ PrintGCStat(&pStats->hs);
+
+ // Don't print handle stats if the user has filtered by type. All handles will be the same
+ // type, and the total count will be displayed by PrintGCStat.
+ if (mType == (unsigned int)~0)
+ {
+ Print("\n");
+ PrintGCHandleStats(pStats);
+ }
+ }
+ }
+
+private:
+ void WalkHandles()
+ {
+ ToRelease<ISOSHandleEnum> handles;
+ if (FAILED(g_sos->GetHandleEnum(&handles)))
+ {
+ if (IsMiniDumpFile())
+ sos::Throw<sos::Exception>("Unable to display GC handles.\nA minidump without full memory may not have this information.");
+ else
+ sos::Throw<sos::Exception>("Failed to walk the handle table.");
+ }
+
+ // GCC can't handle stacks which are too large.
+#ifndef FEATURE_PAL
+ SOSHandleData data[256];
+#else
+ SOSHandleData data[4];
+#endif
+
+ unsigned int fetched = 0;
+ HRESULT hr = S_OK;
+ do
+ {
+ if (FAILED(hr = handles->Next(_countof(data), data, &fetched)))
+ {
+ ExtOut("Error %x while walking the handle table.\n", hr);
+ break;
+ }
+
+ WalkHandles(data, fetched);
+ } while (_countof(data) == fetched);
+ }
+
+ void WalkHandles(SOSHandleData data[], unsigned int count)
+ {
+ for (unsigned int i = 0; i < count; ++i)
+ {
+ sos::CheckInterrupt();
+
+ if (mType != (unsigned int)~0 && mType != data[i].Type)
+ continue;
+
+ GCHandleStatistics *pStats = mHandleStat.LookupStatistics(data[i].AppDomain);
+ TADDR objAddr = 0;
+ TADDR mtAddr = 0;
+ size_t size = 0;
+ const WCHAR *mtName = 0;
+ const char *type = 0;
+
+ if (FAILED(MOVE(objAddr, data[i].Handle)))
+ {
+ objAddr = 0;
+ mtName = W("<error>");
+ }
+ else
+ {
+ sos::Object obj(TO_TADDR(objAddr));
+ mtAddr = obj.GetMT();
+ if (sos::MethodTable::IsFreeMT(mtAddr))
+ {
+ mtName = W("<free>");
+ }
+ else if (!sos::MethodTable::IsValid(mtAddr))
+ {
+ mtName = W("<error>");
+ }
+ else
+ {
+ size = obj.GetSize();
+ if (mType == (unsigned int)~0 || mType == data[i].Type)
+ pStats->hs.Add(obj.GetMT(), (DWORD)size);
+ }
+ }
+
+ switch(data[i].Type)
+ {
+ case HNDTYPE_PINNED:
+ type = "Pinned";
+ if (pStats) pStats->pinnedHandleCount++;
+ break;
+ case HNDTYPE_REFCOUNTED:
+ type = "RefCounted";
+ if (pStats) pStats->refCntHandleCount++;
+ break;
+ case HNDTYPE_STRONG:
+ type = "Strong";
+ if (pStats) pStats->strongHandleCount++;
+ break;
+ case HNDTYPE_WEAK_SHORT:
+ type = "WeakShort";
+ if (pStats) pStats->weakShortHandleCount++;
+ break;
+ case HNDTYPE_WEAK_LONG:
+ type = "WeakLong";
+ if (pStats) pStats->weakLongHandleCount++;
+ break;
+ case HNDTYPE_ASYNCPINNED:
+ type = "AsyncPinned";
+ if (pStats) pStats->asyncPinnedHandleCount++;
+ break;
+ case HNDTYPE_VARIABLE:
+ type = "Variable";
+ if (pStats) pStats->variableCount++;
+ break;
+ case HNDTYPE_SIZEDREF:
+ type = "SizedRef";
+ if (pStats) pStats->sizedRefCount++;
+ break;
+ case HNDTYPE_DEPENDENT:
+ type = "Dependent";
+ if (pStats) pStats->dependentCount++;
+ break;
+ case HNDTYPE_WEAK_WINRT:
+ type = "WeakWinRT";
+ if (pStats) pStats->weakWinRTHandleCount++;
+ break;
+ default:
+ DebugBreak();
+ type = "Unknown";
+ pStats->unknownHandleCount++;
+ break;
+ }
+
+ if (type && !mStat)
+ {
+ sos::MethodTable mt = mtAddr;
+ if (mtName == 0)
+ mtName = mt.GetName();
+
+ if (data[i].Type == HNDTYPE_REFCOUNTED)
+ mOut.WriteRow(data[i].Handle, type, ObjectPtr(objAddr), Decimal(size), Decimal(data[i].RefCount), mtName);
+ else if (data[i].Type == HNDTYPE_DEPENDENT)
+ mOut.WriteRow(data[i].Handle, type, ObjectPtr(objAddr), Decimal(size), ObjectPtr(data[i].Secondary), mtName);
+ else if (data[i].Type == HNDTYPE_WEAK_WINRT)
+ mOut.WriteRow(data[i].Handle, type, ObjectPtr(objAddr), Decimal(size), Pointer(data[i].Secondary), mtName);
+ else
+ mOut.WriteRow(data[i].Handle, type, ObjectPtr(objAddr), Decimal(size), "", mtName);
+ }
+ }
+ }
+
+ inline void PrintHandleRow(const char *text, int count)
+ {
+ if (count)
+ mOut.WriteRow(text, Decimal(count));
+ }
+
+ void PrintGCHandleStats(GCHandleStatistics *pStats)
+ {
+ Print("Handles:\n");
+ mOut.ReInit(2, 21, AlignLeft, 4);
+
+ PrintHandleRow("Strong Handles:", pStats->strongHandleCount);
+ PrintHandleRow("Pinned Handles:", pStats->pinnedHandleCount);
+ PrintHandleRow("Async Pinned Handles:", pStats->asyncPinnedHandleCount);
+ PrintHandleRow("Ref Count Handles:", pStats->refCntHandleCount);
+ PrintHandleRow("Weak Long Handles:", pStats->weakLongHandleCount);
+ PrintHandleRow("Weak Short Handles:", pStats->weakShortHandleCount);
+ PrintHandleRow("Weak WinRT Handles:", pStats->weakWinRTHandleCount);
+ PrintHandleRow("Variable Handles:", pStats->variableCount);
+ PrintHandleRow("SizedRef Handles:", pStats->sizedRefCount);
+ PrintHandleRow("Dependent Handles:", pStats->dependentCount);
+ PrintHandleRow("Other Handles:", pStats->unknownHandleCount);
+ }
+
+private:
+ BOOL mPerDomain, mStat, mDML;
+ unsigned int mType;
+ TableOutput mOut;
+ GCHandleStatsForDomains mHandleStat;
+};
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function dumps GC Handle statistics *
+* *
+\**********************************************************************/
+DECLARE_API(GCHandles)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ try
+ {
+ GCHandlesImpl gchandles(args);
+ gchandles.Run();
+ }
+ catch(const sos::Exception &e)
+ {
+ Print(e.what());
+ }
+
+ return Status;
+}
+
+BOOL derivedFrom(CLRDATA_ADDRESS mtObj, __in_z LPWSTR baseString)
+{
+ // We want to follow back until we get the mt for System.Exception
+ DacpMethodTableData dmtd;
+ CLRDATA_ADDRESS walkMT = mtObj;
+ while(walkMT != NULL)
+ {
+ if (dmtd.Request(g_sos, walkMT) != S_OK)
+ {
+ break;
+ }
+ NameForMT_s (TO_TADDR(walkMT), g_mdName, mdNameLen);
+ if (_wcscmp (baseString, g_mdName) == 0)
+ {
+ return TRUE;
+ }
+ walkMT = dmtd.ParentMethodTable;
+ }
+ return FALSE;
+}
+
+// This is an experimental and undocumented SOS API that attempts to step through code
+// stopping once jitted code is reached. It currently has some issues - it can take arbitrarily long
+// to reach jitted code and canceling it is terrible. At best it doesn't cancel, at worst it
+// kills the debugger. IsInterrupt() doesn't work nearly as nicely as one would hope :/
+#ifndef FEATURE_PAL
+DECLARE_API(TraceToCode)
+{
+ INIT_API_NOEE();
+
+ static ULONG64 g_clrBaseAddr = 0;
+
+
+ while(true)
+ {
+ if (IsInterrupt())
+ {
+ ExtOut("Interrupted\n");
+ return S_FALSE;
+ }
+
+ ULONG64 Offset;
+ g_ExtRegisters->GetInstructionOffset(&Offset);
+
+ DWORD codeType = 0;
+ ULONG64 base = 0;
+ CLRDATA_ADDRESS cdaStart = TO_CDADDR(Offset);
+ DacpMethodDescData MethodDescData;
+ if(g_ExtSymbols->GetModuleByOffset(Offset, 0, NULL, &base) == S_OK)
+ {
+ if(g_clrBaseAddr == 0)
+ {
+ g_ExtSymbols->GetModuleByModuleName (MAIN_CLR_MODULE_NAME_A,0,NULL,
+ &g_clrBaseAddr);
+ }
+ if(g_clrBaseAddr == base)
+ {
+ ExtOut("Compiled code in CLR\n");
+ codeType = 4;
+ }
+ else
+ {
+ ExtOut("Compiled code in module @ 0x%I64x\n", base);
+ codeType = 8;
+ }
+ }
+ else if (g_sos != NULL || LoadClrDebugDll()==S_OK)
+ {
+ CLRDATA_ADDRESS addr;
+ if(g_sos->GetMethodDescPtrFromIP(cdaStart, &addr) == S_OK)
+ {
+ WCHAR wszNameBuffer[1024]; // should be large enough
+
+ // get the MethodDesc name
+ if ((g_sos->GetMethodDescName(addr, 1024, wszNameBuffer, NULL) == S_OK) &&
+ _wcsncmp(W("DomainBoundILStubClass"), wszNameBuffer, 22)==0)
+ {
+ ExtOut("ILStub\n");
+ codeType = 2;
+ }
+ else
+ {
+ ExtOut("Jitted code\n");
+ codeType = 1;
+ }
+ }
+ else
+ {
+ ExtOut("Not compiled or jitted, assuming stub\n");
+ codeType = 16;
+ }
+ }
+ else
+ {
+ // not compiled but CLR isn't loaded... some other code generator?
+ return E_FAIL;
+ }
+
+ if(codeType == 1)
+ {
+ return S_OK;
+ }
+ else
+ {
+ Status = g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, "thr; .echo wait" ,0);
+ if (FAILED(Status))
+ {
+ ExtOut("Error tracing instruction\n");
+ return Status;
+ }
+ }
+ }
+
+ return Status;
+
+}
+#endif // FEATURE_PAL
+
+// This is an experimental and undocumented API that sets a debugger pseudo-register based
+// on the type of code at the given IP. It can be used in scripts to keep stepping until certain
+// kinds of code have been reached. Presumbably its slower than !TraceToCode but at least it
+// cancels much better
+#ifndef FEATURE_PAL
+DECLARE_API(GetCodeTypeFlags)
+{
+ INIT_API();
+
+
+ char buffer[100+mdNameLen];
+ size_t ip;
+ StringHolder PReg;
+
+ CMDValue arg[] = {
+ // vptr, type
+ {&ip, COSIZE_T},
+ {&PReg.data, COSTRING}
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ size_t preg = 1; // by default
+ if (nArg == 2)
+ {
+ preg = GetExpression(PReg.data);
+ if (preg > 19)
+ {
+ ExtOut("Pseudo-register number must be between 0 and 19\n");
+ return Status;
+ }
+ }
+
+ sprintf_s(buffer,_countof (buffer),
+ "r$t%d=0",
+ preg);
+ Status = g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, buffer ,0);
+ if (FAILED(Status))
+ {
+ ExtOut("Error initialized register $t%d to zero\n", preg);
+ return Status;
+ }
+
+ ULONG64 base = 0;
+ CLRDATA_ADDRESS cdaStart = TO_CDADDR(ip);
+ DWORD codeType = 0;
+ CLRDATA_ADDRESS addr;
+ if(g_sos->GetMethodDescPtrFromIP(cdaStart, &addr) == S_OK)
+ {
+ WCHAR wszNameBuffer[1024]; // should be large enough
+
+ // get the MethodDesc name
+ if (g_sos->GetMethodDescName(addr, 1024, wszNameBuffer, NULL) == S_OK &&
+ _wcsncmp(W("DomainBoundILStubClass"), wszNameBuffer, 22)==0)
+ {
+ ExtOut("ILStub\n");
+ codeType = 2;
+ }
+ else
+ {
+ ExtOut("Jitted code");
+ codeType = 1;
+ }
+ }
+ else if(g_ExtSymbols->GetModuleByOffset (ip, 0, NULL, &base) == S_OK)
+ {
+ ULONG64 clrBaseAddr = 0;
+ if(SUCCEEDED(g_ExtSymbols->GetModuleByModuleName (MAIN_CLR_MODULE_NAME_A,0,NULL, &clrBaseAddr)) && base==clrBaseAddr)
+ {
+ ExtOut("Compiled code in CLR");
+ codeType = 4;
+ }
+ else
+ {
+ ExtOut("Compiled code in module @ 0x%I64x\n", base);
+ codeType = 8;
+ }
+ }
+ else
+ {
+ ExtOut("Not compiled or jitted, assuming stub\n");
+ codeType = 16;
+ }
+
+ sprintf_s(buffer,_countof (buffer),
+ "r$t%d=%x",
+ preg, codeType);
+ Status = g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, buffer, 0);
+ if (FAILED(Status))
+ {
+ ExtOut("Error setting register $t%d\n", preg);
+ return Status;
+ }
+ return Status;
+
+}
+#endif // FEATURE_PAL
+
+DECLARE_API(StopOnException)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+
+ char buffer[100+mdNameLen];
+
+ BOOL fDerived = FALSE;
+ BOOL fCreate1 = FALSE;
+ BOOL fCreate2 = FALSE;
+
+ CMDOption option[] = {
+ // name, vptr, type, hasValue
+ {"-derived", &fDerived, COBOOL, FALSE}, // catch derived exceptions
+ {"-create", &fCreate1, COBOOL, FALSE}, // create 1st chance handler
+ {"-create2", &fCreate2, COBOOL, FALSE}, // create 2nd chance handler
+ };
+
+ StringHolder TypeName,PReg;
+
+ CMDValue arg[] = {
+ // vptr, type
+ {&TypeName.data, COSTRING},
+ {&PReg.data, COSTRING}
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+ if (IsDumpFile())
+ {
+ ExtOut("Live debugging session required\n");
+ return Status;
+ }
+ if (nArg < 1 || nArg > 2)
+ {
+ ExtOut("usage: StopOnException [-derived] [-create | -create2] <type name>\n");
+ ExtOut(" [<pseudo-register number for result>]\n");
+ ExtOut("ex: StopOnException -create System.OutOfMemoryException 1\n");
+ return Status;
+ }
+
+ size_t preg = 1; // by default
+ if (nArg == 2)
+ {
+ preg = GetExpression(PReg.data);
+ if (preg > 19)
+ {
+ ExtOut("Pseudo-register number must be between 0 and 19\n");
+ return Status;
+ }
+ }
+
+ sprintf_s(buffer,_countof (buffer),
+ "r$t%d=0",
+ preg);
+ Status = g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, buffer, 0);
+ if (FAILED(Status))
+ {
+ ExtOut("Error initialized register $t%d to zero\n", preg);
+ return Status;
+ }
+
+ if (fCreate1 || fCreate2)
+ {
+ sprintf_s(buffer,_countof (buffer),
+ "sxe %s \"!soe %s %s %d;.if(@$t%d==0) {g} .else {.echo '%s hit'}\" %x",
+ fCreate1 ? "-c" : "-c2",
+ fDerived ? "-derived" : "",
+ TypeName.data,
+ preg,
+ preg,
+ TypeName.data,
+ EXCEPTION_COMPLUS
+ );
+
+ Status = g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, buffer, 0);
+ if (FAILED(Status))
+ {
+ ExtOut("Error setting breakpoint: %s\n", buffer);
+ return Status;
+ }
+
+ ExtOut("Breakpoint set\n");
+ return Status;
+ }
+
+ // Find the last thrown exception on this thread.
+ // Does it match? If so, set the register.
+ CLRDATA_ADDRESS threadAddr = GetCurrentManagedThread();
+ DacpThreadData Thread;
+
+ if ((threadAddr == NULL) || (Thread.Request(g_sos, threadAddr) != S_OK))
+ {
+ ExtOut("The current thread is unmanaged\n");
+ return Status;
+ }
+
+ TADDR taLTOH;
+ if (!SafeReadMemory(Thread.lastThrownObjectHandle,
+ &taLTOH,
+ sizeof(taLTOH), NULL))
+ {
+ ExtOut("There is no current managed exception on this thread\n");
+ return Status;
+ }
+
+ if (taLTOH)
+ {
+ LPWSTR typeNameWide = (LPWSTR)alloca(mdNameLen * sizeof(WCHAR));
+ MultiByteToWideChar(CP_ACP,0,TypeName.data,-1,typeNameWide,mdNameLen);
+
+ TADDR taMT;
+ if (SafeReadMemory(taLTOH, &taMT, sizeof(taMT), NULL))
+ {
+ NameForMT_s (taMT, g_mdName, mdNameLen);
+ if ((_wcscmp(g_mdName,typeNameWide) == 0) ||
+ (fDerived && derivedFrom(taMT, typeNameWide)))
+ {
+ sprintf_s(buffer,_countof (buffer),
+ "r$t%d=1",
+ preg);
+ Status = g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, buffer, 0);
+ if (FAILED(Status))
+ {
+ ExtOut("Failed to execute the following command: %s\n", buffer);
+ }
+ }
+ }
+ }
+
+ return Status;
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function finds the size of an object or all roots. *
+* *
+\**********************************************************************/
+DECLARE_API(ObjSize)
+{
+#ifndef FEATURE_PAL
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ BOOL dml = FALSE;
+ StringHolder str_Object;
+
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"/d", &dml, COBOOL, FALSE},
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&str_Object.data, COSTRING}
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+ TADDR obj = GetExpression(str_Object.data);
+
+ GCRootImpl gcroot;
+ if (obj == 0)
+ {
+ gcroot.ObjSize();
+ }
+ else
+ {
+ if(!sos::IsObject(obj))
+ {
+ ExtOut("%p is not a valid object.\n", SOS_PTR(obj));
+ return Status;
+ }
+
+ size_t size = gcroot.ObjSize(obj);
+ TADDR mt = 0;
+ MOVE(mt, obj);
+ sos::MethodTable methodTable = mt;
+ ExtOut("sizeof(%p) = %d (0x%x) bytes (%S)\n", SOS_PTR(obj), size, size, methodTable.GetName());
+ }
+ return Status;
+#else
+ return E_NOTIMPL;
+#endif
+
+}
+
+#ifndef FEATURE_PAL
+// For FEATURE_PAL, MEMORY_BASIC_INFORMATION64 doesn't exist yet. TODO?
+DECLARE_API(GCHandleLeaks)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ ExtOut("-------------------------------------------------------------------------------\n");
+ ExtOut("GCHandleLeaks will report any GCHandles that couldn't be found in memory. \n");
+ ExtOut("Strong and Pinned GCHandles are reported at this time. You can safely abort the\n");
+ ExtOut("memory scan with Control-C or Control-Break. \n");
+ ExtOut("-------------------------------------------------------------------------------\n");
+
+ static DWORD_PTR array[2000];
+ UINT i;
+ BOOL dml = FALSE;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"/d", &dml, COBOOL, FALSE},
+ };
+
+ if (!GetCMDOption(args, option, _countof(option), NULL, 0, NULL))
+ {
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+
+ UINT iFinal = FindAllPinnedAndStrong(array,sizeof(array)/sizeof(DWORD_PTR));
+ ExtOut("Found %d handles:\n",iFinal);
+ for (i=1;i<=iFinal;i++)
+ {
+ ExtOut("%p\t", SOS_PTR(array[i-1]));
+ if ((i % 4) == 0)
+ ExtOut("\n");
+ }
+
+ ExtOut("\nSearching memory\n");
+ // Now search memory for this:
+ DWORD_PTR buffer[1024];
+ ULONG64 memCur = 0x0;
+ BOOL bAbort = FALSE;
+
+ //find out memory used by stress log
+ StressLogMem stressLog;
+ CLRDATA_ADDRESS StressLogAddress = NULL;
+ if (LoadClrDebugDll() != S_OK)
+ {
+ // Try to find stress log symbols
+ DWORD_PTR dwAddr = GetValueFromExpression(MAIN_CLR_MODULE_NAME_A "!StressLog::theLog");
+ StressLogAddress = dwAddr;
+ g_bDacBroken = TRUE;
+ }
+ else
+ {
+ if (g_sos->GetStressLogAddress(&StressLogAddress) != S_OK)
+ {
+ ExtOut("Unable to find stress log via DAC\n");
+ }
+ g_bDacBroken = FALSE;
+ }
+
+ if (stressLog.Init (StressLogAddress, g_ExtData))
+ {
+ ExtOut("Reference found in stress log will be ignored\n");
+ }
+ else
+ {
+ ExtOut("Failed to read whole or part of stress log, some references may come from stress log\n");
+ }
+
+
+ while (!bAbort)
+ {
+ NTSTATUS status;
+ MEMORY_BASIC_INFORMATION64 memInfo;
+
+ status = g_ExtData2->QueryVirtual(UL64_TO_CDA(memCur), &memInfo);
+
+ if( !NT_SUCCESS(status) )
+ {
+ break;
+ }
+
+ if (memInfo.State == MEM_COMMIT)
+ {
+ for (ULONG64 memIter = memCur; memIter < (memCur + memInfo.RegionSize); memIter+=sizeof(buffer))
+ {
+ if (IsInterrupt())
+ {
+ ExtOut("Quitting at %p due to user abort\n", SOS_PTR(memIter));
+ bAbort = TRUE;
+ break;
+ }
+
+ if ((memIter % 0x10000000)==0x0)
+ {
+ ExtOut("Searching %p...\n", SOS_PTR(memIter));
+ }
+
+ ULONG size = 0;
+ HRESULT ret;
+ ret = g_ExtData->ReadVirtual(UL64_TO_CDA(memIter), buffer, sizeof(buffer), &size);
+ if (ret == S_OK)
+ {
+ for (UINT x=0;x<1024;x++)
+ {
+ DWORD_PTR value = buffer[x];
+ // We don't care about the low bit. Also, the GCHandle class turns on the
+ // low bit for pinned handles, so without the statement below, we wouldn't
+ // notice pinned handles.
+ value = value & ~1;
+ for (i=0;i<iFinal;i++)
+ {
+ ULONG64 addrInDebugee = (ULONG64)memIter+(x*sizeof(DWORD_PTR));
+ if ((array[i] & ~1) == value)
+ {
+ if (stressLog.IsInStressLog (addrInDebugee))
+ {
+ ExtOut("Found %p in stress log at location %p, reference not counted\n", SOS_PTR(value), addrInDebugee);
+ }
+ else
+ {
+ ExtOut("Found %p at location %p\n", SOS_PTR(value), addrInDebugee);
+ array[i] |= 0x1;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ if (size > 0)
+ {
+ ExtOut("only read %x bytes at %p\n", size, SOS_PTR(memIter));
+ }
+ }
+ }
+ }
+
+ memCur += memInfo.RegionSize;
+ }
+
+ int numNotFound = 0;
+ for (i=0;i<iFinal;i++)
+ {
+ if ((array[i] & 0x1) == 0)
+ {
+ numNotFound++;
+ // ExtOut("WARNING: %p not found\n", SOS_PTR(array[i]));
+ }
+ }
+
+ if (numNotFound > 0)
+ {
+ ExtOut("------------------------------------------------------------------------------\n");
+ ExtOut("Some handles were not found. If the number of not-found handles grows over the\n");
+ ExtOut("lifetime of your application, you may have a GCHandle leak. This will cause \n");
+ ExtOut("the GC Heap to grow larger as objects are being kept alive, referenced only \n");
+ ExtOut("by the orphaned handle. If the number doesn't grow over time, note that there \n");
+ ExtOut("may be some noise in this output, as an unmanaged application may be storing \n");
+ ExtOut("the handle in a non-standard way, perhaps with some bits flipped. The memory \n");
+ ExtOut("scan wouldn't be able to find those. \n");
+ ExtOut("------------------------------------------------------------------------------\n");
+
+ ExtOut("Didn't find %d handles:\n", numNotFound);
+ int numPrinted=0;
+ for (i=0;i<iFinal;i++)
+ {
+ if ((array[i] & 0x1) == 0)
+ {
+ numPrinted++;
+ ExtOut("%p\t", SOS_PTR(array[i]));
+ if ((numPrinted % 4) == 0)
+ ExtOut("\n");
+ }
+ }
+ ExtOut("\n");
+ }
+ else
+ {
+ ExtOut("------------------------------------------------------------------------------\n");
+ ExtOut("All handles found");
+ if (bAbort)
+ ExtOut(" even though you aborted.\n");
+ else
+ ExtOut(".\n");
+ ExtOut("A leak may still exist because in a general scan of process memory SOS can't \n");
+ ExtOut("differentiate between garbage and valid structures, so you may have false \n");
+ ExtOut("positives. If you still suspect a leak, use this function over time to \n");
+ ExtOut("identify a possible trend. \n");
+ ExtOut("------------------------------------------------------------------------------\n");
+ }
+
+ return Status;
+}
+#endif // FEATURE_PAL
+
+#endif // FEATURE_PAL
+
+class ClrStackImplWithICorDebug
+{
+private:
+ static HRESULT DereferenceAndUnboxValue(ICorDebugValue * pValue, ICorDebugValue** ppOutputValue, BOOL * pIsNull = NULL)
+ {
+ HRESULT Status = S_OK;
+ *ppOutputValue = NULL;
+ if(pIsNull != NULL) *pIsNull = FALSE;
+
+ ToRelease<ICorDebugReferenceValue> pReferenceValue;
+ Status = pValue->QueryInterface(IID_ICorDebugReferenceValue, (LPVOID*) &pReferenceValue);
+ if (SUCCEEDED(Status))
+ {
+ BOOL isNull = FALSE;
+ IfFailRet(pReferenceValue->IsNull(&isNull));
+ if(!isNull)
+ {
+ ToRelease<ICorDebugValue> pDereferencedValue;
+ IfFailRet(pReferenceValue->Dereference(&pDereferencedValue));
+ return DereferenceAndUnboxValue(pDereferencedValue, ppOutputValue);
+ }
+ else
+ {
+ if(pIsNull != NULL) *pIsNull = TRUE;
+ *ppOutputValue = pValue;
+ (*ppOutputValue)->AddRef();
+ return S_OK;
+ }
+ }
+
+ ToRelease<ICorDebugBoxValue> pBoxedValue;
+ Status = pValue->QueryInterface(IID_ICorDebugBoxValue, (LPVOID*) &pBoxedValue);
+ if (SUCCEEDED(Status))
+ {
+ ToRelease<ICorDebugObjectValue> pUnboxedValue;
+ IfFailRet(pBoxedValue->GetObject(&pUnboxedValue));
+ return DereferenceAndUnboxValue(pUnboxedValue, ppOutputValue);
+ }
+ *ppOutputValue = pValue;
+ (*ppOutputValue)->AddRef();
+ return S_OK;
+ }
+
+ static BOOL ShouldExpandVariable(__in_z WCHAR* varToExpand, __in_z WCHAR* currentExpansion)
+ {
+ if(currentExpansion == NULL || varToExpand == NULL) return FALSE;
+
+ size_t varToExpandLen = _wcslen(varToExpand);
+ size_t currentExpansionLen = _wcslen(currentExpansion);
+ if(currentExpansionLen > varToExpandLen) return FALSE;
+ if(currentExpansionLen < varToExpandLen && varToExpand[currentExpansionLen] != L'.') return FALSE;
+ if(_wcsncmp(currentExpansion, varToExpand, currentExpansionLen) != 0) return FALSE;
+
+ return TRUE;
+ }
+
+ static BOOL IsEnum(ICorDebugValue * pInputValue)
+ {
+ ToRelease<ICorDebugValue> pValue;
+ if(FAILED(DereferenceAndUnboxValue(pInputValue, &pValue, NULL))) return FALSE;
+
+ WCHAR baseTypeName[mdNameLen];
+ ToRelease<ICorDebugValue2> pValue2;
+ ToRelease<ICorDebugType> pType;
+ ToRelease<ICorDebugType> pBaseType;
+
+ if(FAILED(pValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2))) return FALSE;
+ if(FAILED(pValue2->GetExactType(&pType))) return FALSE;
+ if(FAILED(pType->GetBase(&pBaseType)) || pBaseType == NULL) return FALSE;
+ if(FAILED(GetTypeOfValue(pBaseType, baseTypeName, mdNameLen))) return FALSE;
+
+ return (_wcsncmp(baseTypeName, W("System.Enum"), 11) == 0);
+ }
+
+ static HRESULT AddGenericArgs(ICorDebugType * pType, __inout_ecount(typeNameLen) WCHAR* typeName, ULONG typeNameLen)
+ {
+ bool isFirst = true;
+ ToRelease<ICorDebugTypeEnum> pTypeEnum;
+ if(SUCCEEDED(pType->EnumerateTypeParameters(&pTypeEnum)))
+ {
+ ULONG numTypes = 0;
+ ToRelease<ICorDebugType> pCurrentTypeParam;
+
+ while(SUCCEEDED(pTypeEnum->Next(1, &pCurrentTypeParam, &numTypes)))
+ {
+ if(numTypes == 0) break;
+
+ if(isFirst)
+ {
+ isFirst = false;
+ wcsncat_s(typeName, typeNameLen, W("&lt;"), typeNameLen);
+ }
+ else wcsncat_s(typeName, typeNameLen, W(","), typeNameLen);
+
+ WCHAR typeParamName[mdNameLen];
+ typeParamName[0] = L'\0';
+ GetTypeOfValue(pCurrentTypeParam, typeParamName, mdNameLen);
+ wcsncat_s(typeName, typeNameLen, typeParamName, typeNameLen);
+ }
+ if(!isFirst)
+ wcsncat_s(typeName, typeNameLen, W("&gt;"), typeNameLen);
+ }
+
+ return S_OK;
+ }
+
+ static HRESULT GetTypeOfValue(ICorDebugType * pType, __inout_ecount(typeNameLen) WCHAR* typeName, ULONG typeNameLen)
+ {
+ HRESULT Status = S_OK;
+
+ CorElementType corElemType;
+ IfFailRet(pType->GetType(&corElemType));
+
+ switch (corElemType)
+ {
+ //List of unsupported CorElementTypes:
+ //ELEMENT_TYPE_END = 0x0,
+ //ELEMENT_TYPE_VAR = 0x13, // a class type variable VAR <U1>
+ //ELEMENT_TYPE_GENERICINST = 0x15, // GENERICINST <generic type> <argCnt> <arg1> ... <argn>
+ //ELEMENT_TYPE_TYPEDBYREF = 0x16, // TYPEDREF (it takes no args) a typed referece to some other type
+ //ELEMENT_TYPE_MVAR = 0x1e, // a method type variable MVAR <U1>
+ //ELEMENT_TYPE_CMOD_REQD = 0x1F, // required C modifier : E_T_CMOD_REQD <mdTypeRef/mdTypeDef>
+ //ELEMENT_TYPE_CMOD_OPT = 0x20, // optional C modifier : E_T_CMOD_OPT <mdTypeRef/mdTypeDef>
+ //ELEMENT_TYPE_INTERNAL = 0x21, // INTERNAL <typehandle>
+ //ELEMENT_TYPE_MAX = 0x22, // first invalid element type
+ //ELEMENT_TYPE_MODIFIER = 0x40,
+ //ELEMENT_TYPE_SENTINEL = 0x01 | ELEMENT_TYPE_MODIFIER, // sentinel for varargs
+ //ELEMENT_TYPE_PINNED = 0x05 | ELEMENT_TYPE_MODIFIER,
+ //ELEMENT_TYPE_R4_HFA = 0x06 | ELEMENT_TYPE_MODIFIER, // used only internally for R4 HFA types
+ //ELEMENT_TYPE_R8_HFA = 0x07 | ELEMENT_TYPE_MODIFIER, // used only internally for R8 HFA types
+ default:
+ swprintf_s(typeName, typeNameLen, W("(Unhandled CorElementType: 0x%x)\0"), corElemType);
+ break;
+
+ case ELEMENT_TYPE_VALUETYPE:
+ case ELEMENT_TYPE_CLASS:
+ {
+ //Defaults in case we fail...
+ if(corElemType == ELEMENT_TYPE_VALUETYPE) swprintf_s(typeName, typeNameLen, W("struct\0"));
+ else swprintf_s(typeName, typeNameLen, W("class\0"));
+
+ mdTypeDef typeDef;
+ ToRelease<ICorDebugClass> pClass;
+ if(SUCCEEDED(pType->GetClass(&pClass)) && SUCCEEDED(pClass->GetToken(&typeDef)))
+ {
+ ToRelease<ICorDebugModule> pModule;
+ IfFailRet(pClass->GetModule(&pModule));
+
+ ToRelease<IUnknown> pMDUnknown;
+ ToRelease<IMetaDataImport> pMD;
+ IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown));
+ IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD));
+
+ if(SUCCEEDED(NameForToken_s(TokenFromRid(typeDef, mdtTypeDef), pMD, g_mdName, mdNameLen, false)))
+ swprintf_s(typeName, typeNameLen, W("%s\0"), g_mdName);
+ }
+ AddGenericArgs(pType, typeName, typeNameLen);
+ }
+ break;
+ case ELEMENT_TYPE_VOID:
+ swprintf_s(typeName, typeNameLen, W("void\0"));
+ break;
+ case ELEMENT_TYPE_BOOLEAN:
+ swprintf_s(typeName, typeNameLen, W("bool\0"));
+ break;
+ case ELEMENT_TYPE_CHAR:
+ swprintf_s(typeName, typeNameLen, W("char\0"));
+ break;
+ case ELEMENT_TYPE_I1:
+ swprintf_s(typeName, typeNameLen, W("signed byte\0"));
+ break;
+ case ELEMENT_TYPE_U1:
+ swprintf_s(typeName, typeNameLen, W("byte\0"));
+ break;
+ case ELEMENT_TYPE_I2:
+ swprintf_s(typeName, typeNameLen, W("short\0"));
+ break;
+ case ELEMENT_TYPE_U2:
+ swprintf_s(typeName, typeNameLen, W("unsigned short\0"));
+ break;
+ case ELEMENT_TYPE_I4:
+ swprintf_s(typeName, typeNameLen, W("int\0"));
+ break;
+ case ELEMENT_TYPE_U4:
+ swprintf_s(typeName, typeNameLen, W("unsigned int\0"));
+ break;
+ case ELEMENT_TYPE_I8:
+ swprintf_s(typeName, typeNameLen, W("long\0"));
+ break;
+ case ELEMENT_TYPE_U8:
+ swprintf_s(typeName, typeNameLen, W("unsigned long\0"));
+ break;
+ case ELEMENT_TYPE_R4:
+ swprintf_s(typeName, typeNameLen, W("float\0"));
+ break;
+ case ELEMENT_TYPE_R8:
+ swprintf_s(typeName, typeNameLen, W("double\0"));
+ break;
+ case ELEMENT_TYPE_OBJECT:
+ swprintf_s(typeName, typeNameLen, W("object\0"));
+ break;
+ case ELEMENT_TYPE_STRING:
+ swprintf_s(typeName, typeNameLen, W("string\0"));
+ break;
+ case ELEMENT_TYPE_I:
+ swprintf_s(typeName, typeNameLen, W("IntPtr\0"));
+ break;
+ case ELEMENT_TYPE_U:
+ swprintf_s(typeName, typeNameLen, W("UIntPtr\0"));
+ break;
+ case ELEMENT_TYPE_SZARRAY:
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_BYREF:
+ case ELEMENT_TYPE_PTR:
+ {
+ ToRelease<ICorDebugType> pFirstParameter;
+ if(SUCCEEDED(pType->GetFirstTypeParameter(&pFirstParameter)))
+ GetTypeOfValue(pFirstParameter, typeName, typeNameLen);
+ else
+ swprintf_s(typeName, typeNameLen, W("<unknown>\0"));
+
+ switch(corElemType)
+ {
+ case ELEMENT_TYPE_SZARRAY:
+ wcsncat_s(typeName, typeNameLen, W("[]\0"), typeNameLen);
+ return S_OK;
+ case ELEMENT_TYPE_ARRAY:
+ {
+ ULONG32 rank = 0;
+ pType->GetRank(&rank);
+ wcsncat_s(typeName, typeNameLen, W("["), typeNameLen);
+ for(ULONG32 i = 0; i < rank - 1; i++)
+ {
+ //
+ wcsncat_s(typeName, typeNameLen, W(","), typeNameLen);
+ }
+ wcsncat_s(typeName, typeNameLen, W("]\0"), typeNameLen);
+ }
+ return S_OK;
+ case ELEMENT_TYPE_BYREF:
+ wcsncat_s(typeName, typeNameLen, W("&\0"), typeNameLen);
+ return S_OK;
+ case ELEMENT_TYPE_PTR:
+ wcsncat_s(typeName, typeNameLen, W("*\0"), typeNameLen);
+ return S_OK;
+ default:
+ // note we can never reach here as this is a nested switch
+ // and corElemType can only be one of the values above
+ break;
+ }
+ }
+ break;
+ case ELEMENT_TYPE_FNPTR:
+ swprintf_s(typeName, typeNameLen, W("*(...)\0"));
+ break;
+ case ELEMENT_TYPE_TYPEDBYREF:
+ swprintf_s(typeName, typeNameLen, W("typedbyref\0"));
+ break;
+ }
+ return S_OK;
+ }
+
+ static HRESULT GetTypeOfValue(ICorDebugValue * pValue, __inout_ecount(typeNameLen) WCHAR* typeName, ULONG typeNameLen)
+ {
+ HRESULT Status = S_OK;
+
+ CorElementType corElemType;
+ IfFailRet(pValue->GetType(&corElemType));
+
+ ToRelease<ICorDebugType> pType;
+ ToRelease<ICorDebugValue2> pValue2;
+ if(SUCCEEDED(pValue->QueryInterface(IID_ICorDebugValue2, (void**) &pValue2)) && SUCCEEDED(pValue2->GetExactType(&pType)))
+ return GetTypeOfValue(pType, typeName, typeNameLen);
+ else
+ swprintf_s(typeName, typeNameLen, W("<unknown>\0"));
+
+ return S_OK;
+ }
+
+ static HRESULT PrintEnumValue(ICorDebugValue* pInputValue, BYTE* enumValue)
+ {
+ HRESULT Status = S_OK;
+
+ ToRelease<ICorDebugValue> pValue;
+ IfFailRet(DereferenceAndUnboxValue(pInputValue, &pValue, NULL));
+
+ mdTypeDef currentTypeDef;
+ ToRelease<ICorDebugClass> pClass;
+ ToRelease<ICorDebugValue2> pValue2;
+ ToRelease<ICorDebugType> pType;
+ ToRelease<ICorDebugModule> pModule;
+ IfFailRet(pValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2));
+ IfFailRet(pValue2->GetExactType(&pType));
+ IfFailRet(pType->GetClass(&pClass));
+ IfFailRet(pClass->GetModule(&pModule));
+ IfFailRet(pClass->GetToken(&currentTypeDef));
+
+ ToRelease<IUnknown> pMDUnknown;
+ ToRelease<IMetaDataImport> pMD;
+ IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown));
+ IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD));
+
+
+ //First, we need to figure out the underlying enum type so that we can correctly type cast the raw values of each enum constant
+ //We get that from the non-static field of the enum variable (I think the field is called __value or something similar)
+ ULONG numFields = 0;
+ HCORENUM fEnum = NULL;
+ mdFieldDef fieldDef;
+ CorElementType enumUnderlyingType = ELEMENT_TYPE_END;
+ while(SUCCEEDED(pMD->EnumFields(&fEnum, currentTypeDef, &fieldDef, 1, &numFields)) && numFields != 0)
+ {
+ DWORD fieldAttr = 0;
+ PCCOR_SIGNATURE pSignatureBlob = NULL;
+ ULONG sigBlobLength = 0;
+ if(SUCCEEDED(pMD->GetFieldProps(fieldDef, NULL, NULL, 0, NULL, &fieldAttr, &pSignatureBlob, &sigBlobLength, NULL, NULL, NULL)))
+ {
+ if((fieldAttr & fdStatic) == 0)
+ {
+ CorSigUncompressCallingConv(pSignatureBlob);
+ enumUnderlyingType = CorSigUncompressElementType(pSignatureBlob);
+ break;
+ }
+ }
+ }
+ pMD->CloseEnum(fEnum);
+
+
+ //Now that we know the underlying enum type, let's decode the enum variable into OR-ed, human readable enum contants
+ fEnum = NULL;
+ bool isFirst = true;
+ ULONG64 remainingValue = *((ULONG64*)enumValue);
+ while(SUCCEEDED(pMD->EnumFields(&fEnum, currentTypeDef, &fieldDef, 1, &numFields)) && numFields != 0)
+ {
+ ULONG nameLen = 0;
+ DWORD fieldAttr = 0;
+ WCHAR mdName[mdNameLen];
+ WCHAR typeName[mdNameLen];
+ UVCP_CONSTANT pRawValue = NULL;
+ ULONG rawValueLength = 0;
+ if(SUCCEEDED(pMD->GetFieldProps(fieldDef, NULL, mdName, mdNameLen, &nameLen, &fieldAttr, NULL, NULL, NULL, &pRawValue, &rawValueLength)))
+ {
+ DWORD enumValueRequiredAttributes = fdPublic | fdStatic | fdLiteral | fdHasDefault;
+ if((fieldAttr & enumValueRequiredAttributes) != enumValueRequiredAttributes)
+ continue;
+
+ ULONG64 currentConstValue = 0;
+ switch (enumUnderlyingType)
+ {
+ case ELEMENT_TYPE_CHAR:
+ case ELEMENT_TYPE_I1:
+ currentConstValue = (ULONG64)(*((CHAR*)pRawValue));
+ break;
+ case ELEMENT_TYPE_U1:
+ currentConstValue = (ULONG64)(*((BYTE*)pRawValue));
+ break;
+ case ELEMENT_TYPE_I2:
+ currentConstValue = (ULONG64)(*((SHORT*)pRawValue));
+ break;
+ case ELEMENT_TYPE_U2:
+ currentConstValue = (ULONG64)(*((USHORT*)pRawValue));
+ break;
+ case ELEMENT_TYPE_I4:
+ currentConstValue = (ULONG64)(*((INT32*)pRawValue));
+ break;
+ case ELEMENT_TYPE_U4:
+ currentConstValue = (ULONG64)(*((UINT32*)pRawValue));
+ break;
+ case ELEMENT_TYPE_I8:
+ currentConstValue = (ULONG64)(*((LONG*)pRawValue));
+ break;
+ case ELEMENT_TYPE_U8:
+ currentConstValue = (ULONG64)(*((ULONG*)pRawValue));
+ break;
+ case ELEMENT_TYPE_I:
+ currentConstValue = (ULONG64)(*((int*)pRawValue));
+ break;
+ case ELEMENT_TYPE_U:
+ case ELEMENT_TYPE_R4:
+ case ELEMENT_TYPE_R8:
+ // Technically U and the floating-point ones are options in the CLI, but not in the CLS or C#, so these are NYI
+ default:
+ currentConstValue = 0;
+ }
+
+ if((currentConstValue == remainingValue) || ((currentConstValue != 0) && ((currentConstValue & remainingValue) == currentConstValue)))
+ {
+ remainingValue &= ~currentConstValue;
+ if(isFirst)
+ {
+ ExtOut(" = %S", mdName);
+ isFirst = false;
+ }
+ else ExtOut(" | %S", mdName);
+ }
+ }
+ }
+ pMD->CloseEnum(fEnum);
+
+ return S_OK;
+ }
+
+ static HRESULT PrintStringValue(ICorDebugValue * pValue)
+ {
+ HRESULT Status;
+
+ ToRelease<ICorDebugStringValue> pStringValue;
+ IfFailRet(pValue->QueryInterface(IID_ICorDebugStringValue, (LPVOID*) &pStringValue));
+
+ ULONG32 cchValue;
+ IfFailRet(pStringValue->GetLength(&cchValue));
+ cchValue++; // Allocate one more for null terminator
+
+ CQuickString quickString;
+ quickString.Alloc(cchValue);
+
+ ULONG32 cchValueReturned;
+ IfFailRet(pStringValue->GetString(
+ cchValue,
+ &cchValueReturned,
+ quickString.String()));
+
+ ExtOut(" = \"%S\"\n", quickString.String());
+
+ return S_OK;
+ }
+
+ static HRESULT PrintSzArrayValue(ICorDebugValue * pValue, ICorDebugILFrame * pILFrame, IMetaDataImport * pMD, int indent, __in_z WCHAR* varToExpand, __inout_ecount(currentExpansionSize) WCHAR* currentExpansion, DWORD currentExpansionSize, int currentFrame)
+ {
+ HRESULT Status = S_OK;
+
+ ToRelease<ICorDebugArrayValue> pArrayValue;
+ IfFailRet(pValue->QueryInterface(IID_ICorDebugArrayValue, (LPVOID*) &pArrayValue));
+
+ ULONG32 nRank;
+ IfFailRet(pArrayValue->GetRank(&nRank));
+ if (nRank != 1)
+ {
+ return E_UNEXPECTED;
+ }
+
+ ULONG32 cElements;
+ IfFailRet(pArrayValue->GetCount(&cElements));
+
+ if (cElements == 0) ExtOut(" (empty)\n");
+ else if (cElements == 1) ExtOut(" (1 element)\n");
+ else ExtOut(" (%d elements)\n", cElements);
+
+ if(!ShouldExpandVariable(varToExpand, currentExpansion)) return S_OK;
+ size_t currentExpansionLen = _wcslen(currentExpansion);
+
+ for (ULONG32 i=0; i < cElements; i++)
+ {
+ for(int j = 0; j <= indent; j++) ExtOut(" ");
+ currentExpansion[currentExpansionLen] = L'\0';
+ swprintf_s(currentExpansion, mdNameLen, W("%s.[%d]\0"), currentExpansion, i);
+
+ bool printed = false;
+ CorElementType corElemType;
+ ToRelease<ICorDebugType> pFirstParameter;
+ ToRelease<ICorDebugValue2> pValue2;
+ ToRelease<ICorDebugType> pType;
+ if(SUCCEEDED(pArrayValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2)) && SUCCEEDED(pValue2->GetExactType(&pType)))
+ {
+ if(SUCCEEDED(pType->GetFirstTypeParameter(&pFirstParameter)) && SUCCEEDED(pFirstParameter->GetType(&corElemType)))
+ {
+ switch(corElemType)
+ {
+ //If the array element is something that we can expand with !clrstack, show information about the type of this element
+ case ELEMENT_TYPE_VALUETYPE:
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_SZARRAY:
+ {
+ WCHAR typeOfElement[mdNameLen];
+ GetTypeOfValue(pFirstParameter, typeOfElement, mdNameLen);
+ DMLOut(" |- %s = %S", DMLManagedVar(currentExpansion, currentFrame, i), typeOfElement);
+ printed = true;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ if(!printed) DMLOut(" |- %s", DMLManagedVar(currentExpansion, currentFrame, i));
+
+ ToRelease<ICorDebugValue> pElementValue;
+ IfFailRet(pArrayValue->GetElementAtPosition(i, &pElementValue));
+ IfFailRet(PrintValue(pElementValue, pILFrame, pMD, indent + 1, varToExpand, currentExpansion, currentExpansionSize, currentFrame));
+ }
+
+ return S_OK;
+ }
+
+ static HRESULT PrintValue(ICorDebugValue * pInputValue, ICorDebugILFrame * pILFrame, IMetaDataImport * pMD, int indent, __in_z WCHAR* varToExpand, __inout_ecount(currentExpansionSize) WCHAR* currentExpansion, DWORD currentExpansionSize, int currentFrame)
+ {
+ HRESULT Status = S_OK;
+
+ BOOL isNull = TRUE;
+ ToRelease<ICorDebugValue> pValue;
+ IfFailRet(DereferenceAndUnboxValue(pInputValue, &pValue, &isNull));
+
+ if(isNull)
+ {
+ ExtOut(" = null\n");
+ return S_OK;
+ }
+
+ ULONG32 cbSize;
+ IfFailRet(pValue->GetSize(&cbSize));
+ ArrayHolder<BYTE> rgbValue = new NOTHROW BYTE[cbSize];
+ if (rgbValue == NULL)
+ {
+ ReportOOM();
+ return E_OUTOFMEMORY;
+ }
+
+ memset(rgbValue.GetPtr(), 0, cbSize * sizeof(BYTE));
+
+ CorElementType corElemType;
+ IfFailRet(pValue->GetType(&corElemType));
+ if (corElemType == ELEMENT_TYPE_STRING)
+ {
+ return PrintStringValue(pValue);
+ }
+
+ if (corElemType == ELEMENT_TYPE_SZARRAY)
+ {
+ return PrintSzArrayValue(pValue, pILFrame, pMD, indent, varToExpand, currentExpansion, currentExpansionSize, currentFrame);
+ }
+
+ ToRelease<ICorDebugGenericValue> pGenericValue;
+ IfFailRet(pValue->QueryInterface(IID_ICorDebugGenericValue, (LPVOID*) &pGenericValue));
+ IfFailRet(pGenericValue->GetValue((LPVOID) &(rgbValue[0])));
+
+ if(IsEnum(pValue))
+ {
+ Status = PrintEnumValue(pValue, rgbValue);
+ ExtOut("\n");
+ return Status;
+ }
+
+ switch (corElemType)
+ {
+ default:
+ ExtOut(" (Unhandled CorElementType: 0x%x)\n", corElemType);
+ break;
+
+ case ELEMENT_TYPE_PTR:
+ ExtOut(" = <pointer>\n");
+ break;
+
+ case ELEMENT_TYPE_FNPTR:
+ {
+ CORDB_ADDRESS addr = 0;
+ ToRelease<ICorDebugReferenceValue> pReferenceValue = NULL;
+ if(SUCCEEDED(pValue->QueryInterface(IID_ICorDebugReferenceValue, (LPVOID*) &pReferenceValue)))
+ pReferenceValue->GetValue(&addr);
+ ExtOut(" = <function pointer 0x%x>\n", addr);
+ }
+ break;
+
+ case ELEMENT_TYPE_VALUETYPE:
+ case ELEMENT_TYPE_CLASS:
+ CORDB_ADDRESS addr;
+ if(SUCCEEDED(pValue->GetAddress(&addr)))
+ {
+ ExtOut(" @ 0x%I64x\n", addr);
+ }
+ else
+ {
+ ExtOut("\n");
+ }
+ ProcessFields(pValue, NULL, pILFrame, indent + 1, varToExpand, currentExpansion, currentExpansionSize, currentFrame);
+ break;
+
+ case ELEMENT_TYPE_BOOLEAN:
+ ExtOut(" = %s\n", rgbValue[0] == 0 ? "false" : "true");
+ break;
+
+ case ELEMENT_TYPE_CHAR:
+ ExtOut(" = '%C'\n", *(WCHAR *) &(rgbValue[0]));
+ break;
+
+ case ELEMENT_TYPE_I1:
+ ExtOut(" = %d\n", *(char*) &(rgbValue[0]));
+ break;
+
+ case ELEMENT_TYPE_U1:
+ ExtOut(" = %d\n", *(unsigned char*) &(rgbValue[0]));
+ break;
+
+ case ELEMENT_TYPE_I2:
+ ExtOut(" = %hd\n", *(short*) &(rgbValue[0]));
+ break;
+
+ case ELEMENT_TYPE_U2:
+ ExtOut(" = %hu\n", *(unsigned short*) &(rgbValue[0]));
+ break;
+
+ case ELEMENT_TYPE_I:
+ ExtOut(" = %d\n", *(int*) &(rgbValue[0]));
+ break;
+
+ case ELEMENT_TYPE_U:
+ ExtOut(" = %u\n", *(unsigned int*) &(rgbValue[0]));
+ break;
+
+ case ELEMENT_TYPE_I4:
+ ExtOut(" = %d\n", *(int*) &(rgbValue[0]));
+ break;
+
+ case ELEMENT_TYPE_U4:
+ ExtOut(" = %u\n", *(unsigned int*) &(rgbValue[0]));
+ break;
+
+ case ELEMENT_TYPE_I8:
+ ExtOut(" = %I64d\n", *(__int64*) &(rgbValue[0]));
+ break;
+
+ case ELEMENT_TYPE_U8:
+ ExtOut(" = %I64u\n", *(unsigned __int64*) &(rgbValue[0]));
+ break;
+
+ case ELEMENT_TYPE_R4:
+ ExtOut(" = %f\n", (double) *(float*) &(rgbValue[0]));
+ break;
+
+ case ELEMENT_TYPE_R8:
+ ExtOut(" = %f\n", *(double*) &(rgbValue[0]));
+ break;
+
+ case ELEMENT_TYPE_OBJECT:
+ ExtOut(" = object\n");
+ break;
+
+ // TODO: The following corElementTypes are not yet implemented here. Array
+ // might be interesting to add, though the others may be of rather limited use:
+ // ELEMENT_TYPE_ARRAY = 0x14, // MDARRAY <type> <rank> <bcount> <bound1> ... <lbcount> <lb1> ...
+ //
+ // ELEMENT_TYPE_GENERICINST = 0x15, // GENERICINST <generic type> <argCnt> <arg1> ... <argn>
+ }
+
+ return S_OK;
+ }
+
+ static HRESULT PrintParameters(BOOL bParams, BOOL bLocals, IMetaDataImport * pMD, mdTypeDef typeDef, mdMethodDef methodDef, ICorDebugILFrame * pILFrame, ICorDebugModule * pModule, __in_z WCHAR* varToExpand, int currentFrame)
+ {
+ HRESULT Status = S_OK;
+
+ ULONG cParams = 0;
+ ToRelease<ICorDebugValueEnum> pParamEnum;
+ IfFailRet(pILFrame->EnumerateArguments(&pParamEnum));
+ IfFailRet(pParamEnum->GetCount(&cParams));
+ if (cParams > 0 && bParams)
+ {
+ DWORD methAttr = 0;
+ IfFailRet(pMD->GetMethodProps(methodDef, NULL, NULL, 0, NULL, &methAttr, NULL, NULL, NULL, NULL));
+
+ ExtOut("\nPARAMETERS:\n");
+ for (ULONG i=0; i < cParams; i++)
+ {
+ ULONG paramNameLen = 0;
+ mdParamDef paramDef;
+ WCHAR paramName[mdNameLen] = W("\0");
+
+ if(i == 0 && (methAttr & mdStatic) == 0)
+ swprintf_s(paramName, mdNameLen, W("this\0"));
+ else
+ {
+ int idx = ((methAttr & mdStatic) == 0)? i : (i + 1);
+ if(SUCCEEDED(pMD->GetParamForMethodIndex(methodDef, idx, &paramDef)))
+ pMD->GetParamProps(paramDef, NULL, NULL, paramName, mdNameLen, &paramNameLen, NULL, NULL, NULL, NULL);
+ }
+ if(_wcslen(paramName) == 0)
+ swprintf_s(paramName, mdNameLen, W("param_%d\0"), i);
+
+ ToRelease<ICorDebugValue> pValue;
+ ULONG cArgsFetched;
+ Status = pParamEnum->Next(1, &pValue, &cArgsFetched);
+
+ if (FAILED(Status))
+ {
+ ExtOut(" + (Error 0x%x retrieving parameter '%S')\n", Status, paramName);
+ continue;
+ }
+
+ if (Status == S_FALSE)
+ {
+ break;
+ }
+
+ WCHAR typeName[mdNameLen] = W("\0");
+ GetTypeOfValue(pValue, typeName, mdNameLen);
+ DMLOut(" + %S %s", typeName, DMLManagedVar(paramName, currentFrame, paramName));
+
+ ToRelease<ICorDebugReferenceValue> pRefValue;
+ if(SUCCEEDED(pValue->QueryInterface(IID_ICorDebugReferenceValue, (void**)&pRefValue)) && pRefValue != NULL)
+ {
+ BOOL bIsNull = TRUE;
+ pRefValue->IsNull(&bIsNull);
+ if(bIsNull)
+ {
+ ExtOut(" = null\n");
+ continue;
+ }
+ }
+
+ WCHAR currentExpansion[mdNameLen];
+ swprintf_s(currentExpansion, mdNameLen, W("%s\0"), paramName);
+ if((Status=PrintValue(pValue, pILFrame, pMD, 0, varToExpand, currentExpansion, mdNameLen, currentFrame)) != S_OK)
+ ExtOut(" + (Error 0x%x printing parameter %d)\n", Status, i);
+ }
+ }
+ else if (cParams == 0 && bParams)
+ ExtOut("\nPARAMETERS: (none)\n");
+
+ ULONG cLocals = 0;
+ ToRelease<ICorDebugValueEnum> pLocalsEnum;
+ IfFailRet(pILFrame->EnumerateLocalVariables(&pLocalsEnum));
+ IfFailRet(pLocalsEnum->GetCount(&cLocals));
+ if (cLocals > 0 && bLocals)
+ {
+ bool symbolsAvailable = false;
+ SymbolReader symReader;
+ if(SUCCEEDED(symReader.LoadSymbols(pMD, pModule)))
+ symbolsAvailable = true;
+ ExtOut("\nLOCALS:\n");
+ for (ULONG i=0; i < cLocals; i++)
+ {
+ ULONG paramNameLen = 0;
+ WCHAR paramName[mdNameLen] = W("\0");
+
+ ToRelease<ICorDebugValue> pValue;
+ if(symbolsAvailable)
+ {
+ Status = symReader.GetNamedLocalVariable(pILFrame, i, paramName, mdNameLen, &pValue);
+ }
+ else
+ {
+ ULONG cArgsFetched;
+ Status = pLocalsEnum->Next(1, &pValue, &cArgsFetched);
+ }
+ if(_wcslen(paramName) == 0)
+ swprintf_s(paramName, mdNameLen, W("local_%d\0"), i);
+
+ if (FAILED(Status))
+ {
+ ExtOut(" + (Error 0x%x retrieving local variable '%S')\n", Status, paramName);
+ continue;
+ }
+
+ if (Status == S_FALSE)
+ {
+ break;
+ }
+
+ WCHAR typeName[mdNameLen] = W("\0");
+ GetTypeOfValue(pValue, typeName, mdNameLen);
+ DMLOut(" + %S %s", typeName, DMLManagedVar(paramName, currentFrame, paramName));
+
+ ToRelease<ICorDebugReferenceValue> pRefValue = NULL;
+ if(SUCCEEDED(pValue->QueryInterface(IID_ICorDebugReferenceValue, (void**)&pRefValue)) && pRefValue != NULL)
+ {
+ BOOL bIsNull = TRUE;
+ pRefValue->IsNull(&bIsNull);
+ if(bIsNull)
+ {
+ ExtOut(" = null\n");
+ continue;
+ }
+ }
+
+ WCHAR currentExpansion[mdNameLen];
+ swprintf_s(currentExpansion, mdNameLen, W("%s\0"), paramName);
+ if((Status=PrintValue(pValue, pILFrame, pMD, 0, varToExpand, currentExpansion, mdNameLen, currentFrame)) != S_OK)
+ ExtOut(" + (Error 0x%x printing local variable %d)\n", Status, i);
+ }
+ }
+ else if (cLocals == 0 && bLocals)
+ ExtOut("\nLOCALS: (none)\n");
+
+ if(bParams || bLocals)
+ ExtOut("\n");
+
+ return S_OK;
+ }
+
+ static HRESULT ProcessFields(ICorDebugValue* pInputValue, ICorDebugType* pTypeCast, ICorDebugILFrame * pILFrame, int indent, __in_z WCHAR* varToExpand, __inout_ecount(currentExpansionSize) WCHAR* currentExpansion, DWORD currentExpansionSize, int currentFrame)
+ {
+ if(!ShouldExpandVariable(varToExpand, currentExpansion)) return S_OK;
+ size_t currentExpansionLen = _wcslen(currentExpansion);
+
+ HRESULT Status = S_OK;
+
+ BOOL isNull = FALSE;
+ ToRelease<ICorDebugValue> pValue;
+ IfFailRet(DereferenceAndUnboxValue(pInputValue, &pValue, &isNull));
+
+ if(isNull) return S_OK;
+
+ mdTypeDef currentTypeDef;
+ ToRelease<ICorDebugClass> pClass;
+ ToRelease<ICorDebugValue2> pValue2;
+ ToRelease<ICorDebugType> pType;
+ ToRelease<ICorDebugModule> pModule;
+ IfFailRet(pValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2));
+ if(pTypeCast == NULL)
+ IfFailRet(pValue2->GetExactType(&pType));
+ else
+ {
+ pType = pTypeCast;
+ pType->AddRef();
+ }
+ IfFailRet(pType->GetClass(&pClass));
+ IfFailRet(pClass->GetModule(&pModule));
+ IfFailRet(pClass->GetToken(&currentTypeDef));
+
+ ToRelease<IUnknown> pMDUnknown;
+ ToRelease<IMetaDataImport> pMD;
+ IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown));
+ IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD));
+
+ WCHAR baseTypeName[mdNameLen] = W("\0");
+ ToRelease<ICorDebugType> pBaseType;
+ if(SUCCEEDED(pType->GetBase(&pBaseType)) && pBaseType != NULL && SUCCEEDED(GetTypeOfValue(pBaseType, baseTypeName, mdNameLen)))
+ {
+ if(_wcsncmp(baseTypeName, W("System.Enum"), 11) == 0)
+ return S_OK;
+ else if(_wcsncmp(baseTypeName, W("System.Object"), 13) != 0 && _wcsncmp(baseTypeName, W("System.ValueType"), 16) != 0)
+ {
+ currentExpansion[currentExpansionLen] = W('\0');
+ wcscat_s(currentExpansion, currentExpansionSize, W(".\0"));
+ wcscat_s(currentExpansion, currentExpansionSize, W("[basetype]"));
+ for(int i = 0; i < indent; i++) ExtOut(" ");
+ DMLOut(" |- %S %s\n", baseTypeName, DMLManagedVar(currentExpansion, currentFrame, W("[basetype]")));
+
+ if(ShouldExpandVariable(varToExpand, currentExpansion))
+ ProcessFields(pInputValue, pBaseType, pILFrame, indent + 1, varToExpand, currentExpansion, currentExpansionSize, currentFrame);
+ }
+ }
+
+
+ ULONG numFields = 0;
+ HCORENUM fEnum = NULL;
+ mdFieldDef fieldDef;
+ while(SUCCEEDED(pMD->EnumFields(&fEnum, currentTypeDef, &fieldDef, 1, &numFields)) && numFields != 0)
+ {
+ ULONG nameLen = 0;
+ DWORD fieldAttr = 0;
+ WCHAR mdName[mdNameLen];
+ WCHAR typeName[mdNameLen];
+ if(SUCCEEDED(pMD->GetFieldProps(fieldDef, NULL, mdName, mdNameLen, &nameLen, &fieldAttr, NULL, NULL, NULL, NULL, NULL)))
+ {
+ currentExpansion[currentExpansionLen] = W('\0');
+ wcscat_s(currentExpansion, currentExpansionSize, W(".\0"));
+ wcscat_s(currentExpansion, currentExpansionSize, mdName);
+
+ ToRelease<ICorDebugValue> pFieldVal;
+ if(fieldAttr & fdLiteral)
+ {
+ //TODO: Is it worth it??
+ //ExtOut(" |- const %S", mdName);
+ }
+ else
+ {
+ for(int i = 0; i < indent; i++) ExtOut(" ");
+
+ if (fieldAttr & fdStatic)
+ pType->GetStaticFieldValue(fieldDef, pILFrame, &pFieldVal);
+ else
+ {
+ ToRelease<ICorDebugObjectValue> pObjValue;
+ if (SUCCEEDED(pValue->QueryInterface(IID_ICorDebugObjectValue, (LPVOID*) &pObjValue)))
+ pObjValue->GetFieldValue(pClass, fieldDef, &pFieldVal);
+ }
+
+ if(pFieldVal != NULL)
+ {
+ typeName[0] = L'\0';
+ GetTypeOfValue(pFieldVal, typeName, mdNameLen);
+ DMLOut(" |- %S %s", typeName, DMLManagedVar(currentExpansion, currentFrame, mdName));
+ PrintValue(pFieldVal, pILFrame, pMD, indent, varToExpand, currentExpansion, currentExpansionSize, currentFrame);
+ }
+ else if(!(fieldAttr & fdLiteral))
+ ExtOut(" |- < unknown type > %S\n", mdName);
+ }
+ }
+ }
+ pMD->CloseEnum(fEnum);
+ return S_OK;
+ }
+
+public:
+
+ // This is the main worker function used if !clrstack is called with "-i" to indicate
+ // that the public ICorDebug* should be used instead of the private DAC interface. NOTE:
+ // Currently only bParams is supported. NOTE: This is a work in progress and the
+ // following would be good to do:
+ // * More thorough testing with interesting stacks, especially with transitions into
+ // and out of managed code.
+ // * Consider interleaving this code back into the main body of !clrstack if it turns
+ // out that there's a lot of duplication of code between these two functions.
+ // (Still unclear how things will look once locals is implemented.)
+ static HRESULT ClrStackFromPublicInterface(BOOL bParams, BOOL bLocals, BOOL bSuppressLines, __in_z WCHAR* varToExpand = NULL, int onlyShowFrame = -1)
+ {
+ HRESULT Status;
+
+ IfFailRet(InitCorDebugInterface());
+
+ ExtOut("\n\n\nDumping managed stack and managed variables using ICorDebug.\n");
+ ExtOut("=============================================================================\n");
+
+ ToRelease<ICorDebugThread> pThread;
+ ToRelease<ICorDebugThread3> pThread3;
+ ToRelease<ICorDebugStackWalk> pStackWalk;
+ ULONG ulThreadID = 0;
+ g_ExtSystem->GetCurrentThreadSystemId(&ulThreadID);
+
+ IfFailRet(g_pCorDebugProcess->GetThread(ulThreadID, &pThread));
+ IfFailRet(pThread->QueryInterface(IID_ICorDebugThread3, (LPVOID *) &pThread3));
+ IfFailRet(pThread3->CreateStackWalk(&pStackWalk));
+
+ InternalFrameManager internalFrameManager;
+ IfFailRet(internalFrameManager.Init(pThread3));
+
+ #if defined(_AMD64_)
+ ExtOut("%-16s %-16s %s\n", "Child SP", "IP", "Call Site");
+ #elif defined(_X86_)
+ ExtOut("%-8s %-8s %s\n", "Child SP", "IP", "Call Site");
+ #endif
+
+ int currentFrame = -1;
+
+ for (Status = S_OK; ; Status = pStackWalk->Next())
+ {
+ currentFrame++;
+
+ if (Status == CORDBG_S_AT_END_OF_STACK)
+ {
+ ExtOut("Stack walk complete.\n");
+ break;
+ }
+ IfFailRet(Status);
+
+ if (IsInterrupt())
+ {
+ ExtOut("<interrupted>\n");
+ break;
+ }
+
+ CROSS_PLATFORM_CONTEXT context;
+ ULONG32 cbContextActual;
+ if ((Status=pStackWalk->GetContext(
+ DT_CONTEXT_FULL,
+ sizeof(context),
+ &cbContextActual,
+ (BYTE *)&context))!=S_OK)
+ {
+ ExtOut("GetFrameContext failed: %lx\n",Status);
+ break;
+ }
+
+ // First find the info for the Frame object, if the current frame has an associated clr!Frame.
+ CLRDATA_ADDRESS sp = GetSP(context);
+ CLRDATA_ADDRESS ip = GetIP(context);
+
+ ToRelease<ICorDebugFrame> pFrame;
+ IfFailRet(pStackWalk->GetFrame(&pFrame));
+ if (Status == S_FALSE)
+ {
+ DMLOut("%p %s [NativeStackFrame]\n", SOS_PTR(sp), DMLIP(ip));
+ continue;
+ }
+
+ // TODO: What about internal frames preceding the above native stack frame?
+ // Should I just exclude the above native stack frame from the output?
+ // TODO: Compare caller frame (instead of current frame) against internal frame,
+ // to deal with issues of current frame's current SP being closer to leaf than
+ // EE Frames it pushes. By "caller" I mean not just managed caller, but the
+ // very next non-internal frame dbi would return (native or managed). OR...
+ // perhaps I should use GetStackRange() instead, to see if the internal frame
+ // appears leafier than the base-part of the range of the currently iterated
+ // stack frame? I think I like that better.
+ _ASSERTE(pFrame != NULL);
+ IfFailRet(internalFrameManager.PrintPrecedingInternalFrames(pFrame));
+
+ // Print the stack and instruction pointers.
+ DMLOut("%p %s ", SOS_PTR(sp), DMLIP(ip));
+
+ ToRelease<ICorDebugRuntimeUnwindableFrame> pRuntimeUnwindableFrame;
+ Status = pFrame->QueryInterface(IID_ICorDebugRuntimeUnwindableFrame, (LPVOID *) &pRuntimeUnwindableFrame);
+ if (SUCCEEDED(Status))
+ {
+ ExtOut("[RuntimeUnwindableFrame]\n");
+ continue;
+ }
+
+ // Print the method/Frame info
+
+ // TODO: IS THE FOLLOWING NECESSARY, OR AM I GUARANTEED THAT ALL INTERNAL FRAMES
+ // CAN BE FOUND VIA GetActiveInternalFrames?
+ ToRelease<ICorDebugInternalFrame> pInternalFrame;
+ Status = pFrame->QueryInterface(IID_ICorDebugInternalFrame, (LPVOID *) &pInternalFrame);
+ if (SUCCEEDED(Status))
+ {
+ // This is a clr!Frame.
+ LPCWSTR pwszFrameName = W("TODO: Implement GetFrameName");
+ ExtOut("[%S: p] ", pwszFrameName);
+ }
+
+ // Print the frame's associated function info, if it has any.
+ ToRelease<ICorDebugILFrame> pILFrame;
+ HRESULT hrILFrame = pFrame->QueryInterface(IID_ICorDebugILFrame, (LPVOID*) &pILFrame);
+
+ if (SUCCEEDED(hrILFrame))
+ {
+ ToRelease<ICorDebugFunction> pFunction;
+ Status = pFrame->GetFunction(&pFunction);
+ if (FAILED(Status))
+ {
+ // We're on a JITted frame, but there's no Function for it. So it must
+ // be...
+ ExtOut("[IL Stub or LCG]\n");
+ continue;
+ }
+
+ ToRelease<ICorDebugClass> pClass;
+ ToRelease<ICorDebugModule> pModule;
+ mdMethodDef methodDef;
+ IfFailRet(pFunction->GetClass(&pClass));
+ IfFailRet(pFunction->GetModule(&pModule));
+ IfFailRet(pFunction->GetToken(&methodDef));
+
+ WCHAR wszModuleName[100];
+ ULONG32 cchModuleNameActual;
+ IfFailRet(pModule->GetName(_countof(wszModuleName), &cchModuleNameActual, wszModuleName));
+
+ ToRelease<IUnknown> pMDUnknown;
+ ToRelease<IMetaDataImport> pMD;
+ ToRelease<IMDInternalImport> pMDInternal;
+ IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown));
+ IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD));
+ IfFailRet(GetMDInternalFromImport(pMD, &pMDInternal));
+
+ mdTypeDef typeDef;
+ IfFailRet(pClass->GetToken(&typeDef));
+
+ // Note that we don't need to pretty print the class, as class name is
+ // already printed from GetMethodName below
+
+ CQuickBytes functionName;
+ // TODO: WARNING: GetMethodName() appears to include lots of unexercised
+ // code, as evidenced by some fundamental bugs I found. It should either be
+ // thoroughly reviewed, or some other more exercised code path to grab the
+ // name should be used.
+ // TODO: If we do stay with GetMethodName, it should be updated to print
+ // generics properly. Today, it does not show generic type parameters, and
+ // if any arguments have a generic type, those arguments are just shown as
+ // "__Canon", even when they're value types.
+ GetMethodName(methodDef, pMD, &functionName);
+
+ DMLOut(DMLManagedVar(W("-a"), currentFrame, (LPWSTR)functionName.Ptr()));
+ ExtOut(" (%S)\n", wszModuleName);
+
+ if (SUCCEEDED(hrILFrame) && (bParams || bLocals))
+ {
+ if(onlyShowFrame == -1 || (onlyShowFrame >= 0 && currentFrame == onlyShowFrame))
+ IfFailRet(PrintParameters(bParams, bLocals, pMD, typeDef, methodDef, pILFrame, pModule, varToExpand, currentFrame));
+ }
+ }
+ }
+ ExtOut("=============================================================================\n");
+
+#ifdef FEATURE_PAL
+ // Temporary until we get a process exit notification plumbed from lldb
+ UninitCorDebugInterface();
+#endif
+ return S_OK;
+ }
+};
+
+WString BuildRegisterOutput(const SOSStackRefData &ref, bool printObj)
+{
+ WString res;
+
+ if (ref.HasRegisterInformation)
+ {
+ WCHAR reg[32];
+ HRESULT hr = g_sos->GetRegisterName(ref.Register, _countof(reg), reg, NULL);
+ if (SUCCEEDED(hr))
+ res = reg;
+ else
+ res = W("<unknown register>");
+
+ if (ref.Offset)
+ {
+ int offset = ref.Offset;
+ if (offset > 0)
+ {
+ res += W("+");
+ }
+ else
+ {
+ res += W("-");
+ offset = -offset;
+ }
+
+ res += Hex(offset);
+ }
+
+ res += W(": ");
+ }
+
+ if (ref.Address)
+ res += WString(Pointer(ref.Address));
+
+ if (printObj)
+ {
+ if (ref.Address)
+ res += W(" -> ");
+
+ res += WString(ObjectPtr(ref.Object));
+ }
+
+ if (ref.Flags & SOSRefPinned)
+ {
+ res += W(" (pinned)");
+ }
+
+ if (ref.Flags & SOSRefInterior)
+ {
+ res += W(" (interior)");
+ }
+
+ return res;
+}
+
+void PrintRef(const SOSStackRefData &ref, TableOutput &out)
+{
+ WString res = BuildRegisterOutput(ref);
+
+ if (ref.Object && (ref.Flags & SOSRefInterior) == 0)
+ {
+ WCHAR type[128];
+ sos::BuildTypeWithExtraInfo(TO_TADDR(ref.Object), _countof(type), type);
+
+ res += WString(W(" - ")) + type;
+ }
+
+ out.WriteColumn(2, res);
+}
+
+
+class ClrStackImpl
+{
+public:
+ static void PrintThread(ULONG osID, BOOL bParams, BOOL bLocals, BOOL bSuppressLines, BOOL bGC, BOOL bFull, BOOL bDisplayRegVals)
+ {
+ // Symbols variables
+ ULONG symlines = 0; // symlines will be non-zero only if SYMOPT_LOAD_LINES was set in the symbol options
+ if (!bSuppressLines && SUCCEEDED(g_ExtSymbols->GetSymbolOptions(&symlines)))
+ {
+ symlines &= SYMOPT_LOAD_LINES;
+ }
+
+ if (symlines == 0)
+ bSuppressLines = TRUE;
+
+ ToRelease<IXCLRDataStackWalk> pStackWalk;
+
+ HRESULT hr = CreateStackWalk(osID, &pStackWalk);
+ if (FAILED(hr) || pStackWalk == NULL)
+ {
+ ExtOut("Failed to start stack walk: %lx\n", hr);
+ return;
+ }
+
+#ifdef _TARGET_WIN64_
+ PDEBUG_STACK_FRAME currentNativeFrame = NULL;
+ ULONG numNativeFrames = 0;
+ if (bFull)
+ {
+ hr = GetContextStackTrace(&numNativeFrames);
+ if (FAILED(hr))
+ {
+ ExtOut("Failed to get native stack frames: %lx\n", hr);
+ return;
+ }
+ currentNativeFrame = &g_Frames[0];
+ }
+#endif // _TARGET_WIN64_
+
+ unsigned int refCount = 0, errCount = 0;
+ ArrayHolder<SOSStackRefData> pRefs = NULL;
+ ArrayHolder<SOSStackRefError> pErrs = NULL;
+ if (bGC && FAILED(GetGCRefs(osID, &pRefs, &refCount, &pErrs, &errCount)))
+ refCount = 0;
+
+ TableOutput out(3, POINTERSIZE_HEX, AlignRight);
+ out.WriteRow("Child SP", "IP", "Call Site");
+
+ do
+ {
+ if (IsInterrupt())
+ {
+ ExtOut("<interrupted>\n");
+ break;
+ }
+ CLRDATA_ADDRESS ip = 0, sp = 0;
+ hr = GetFrameLocation(pStackWalk, &ip, &sp);
+
+ DacpFrameData FrameData;
+ HRESULT frameDataResult = FrameData.Request(pStackWalk);
+ if (SUCCEEDED(frameDataResult) && FrameData.frameAddr)
+ sp = FrameData.frameAddr;
+
+#ifdef _TARGET_WIN64_
+ while ((numNativeFrames > 0) && (currentNativeFrame->StackOffset <= sp))
+ {
+ if (currentNativeFrame->StackOffset != sp)
+ {
+ PrintNativeStackFrame(out, currentNativeFrame, bSuppressLines);
+ }
+ currentNativeFrame++;
+ numNativeFrames--;
+ }
+#endif // _TARGET_WIN64_
+
+ // Print the stack pointer.
+ out.WriteColumn(0, sp);
+
+ // Print the method/Frame info
+ if (SUCCEEDED(frameDataResult) && FrameData.frameAddr)
+ {
+ // Skip the instruction pointer because it doesn't really mean anything for method frames
+ out.WriteColumn(1, bFull ? String("") : NativePtr(ip));
+
+ // This is a clr!Frame.
+ out.WriteColumn(2, GetFrameFromAddress(TO_TADDR(FrameData.frameAddr), pStackWalk, bFull));
+
+ // Print out gc references for the Frame.
+ for (unsigned int i = 0; i < refCount; ++i)
+ if (pRefs[i].Source == sp)
+ PrintRef(pRefs[i], out);
+
+ // Print out an error message if we got one.
+ for (unsigned int i = 0; i < errCount; ++i)
+ if (pErrs[i].Source == sp)
+ out.WriteColumn(2, "Failed to enumerate GC references.");
+ }
+ else
+ {
+ out.WriteColumn(1, InstructionPtr(ip));
+ out.WriteColumn(2, MethodNameFromIP(ip, bSuppressLines, bFull, bFull));
+
+ // Print out gc references. refCount will be zero if bGC is false (or if we
+ // failed to fetch gc reference information).
+ for (unsigned int i = 0; i < refCount; ++i)
+ if (pRefs[i].Source == ip && pRefs[i].StackPointer == sp)
+ PrintRef(pRefs[i], out);
+
+ // Print out an error message if we got one.
+ for (unsigned int i = 0; i < errCount; ++i)
+ if (pErrs[i].Source == sp)
+ out.WriteColumn(2, "Failed to enumerate GC references.");
+
+ if (bParams || bLocals)
+ PrintArgsAndLocals(pStackWalk, bParams, bLocals);
+ }
+
+ if (bDisplayRegVals)
+ PrintManagedFrameContext(pStackWalk);
+
+ } while (pStackWalk->Next() == S_OK);
+
+#ifdef _TARGET_WIN64_
+ while (numNativeFrames > 0)
+ {
+ PrintNativeStackFrame(out, currentNativeFrame, bSuppressLines);
+ currentNativeFrame++;
+ numNativeFrames--;
+ }
+#endif // _TARGET_WIN64_
+ }
+
+ static HRESULT PrintManagedFrameContext(IXCLRDataStackWalk *pStackWalk)
+ {
+ CROSS_PLATFORM_CONTEXT context;
+ HRESULT hr = pStackWalk->GetContext(DT_CONTEXT_FULL, g_targetMachine->GetContextSize(), NULL, (BYTE *)&context);
+ if (FAILED(hr) || hr == S_FALSE)
+ {
+ // GetFrameContext returns S_FALSE if the frame iterator is invalid. That's basically an error for us.
+ ExtOut("GetFrameContext failed: %lx\n", hr);
+ return E_FAIL;
+ }
+
+#if defined(SOS_TARGET_AMD64)
+ String outputFormat3 = " %3s=%016x %3s=%016x %3s=%016x\n";
+ String outputFormat2 = " %3s=%016x %3s=%016x\n";
+ ExtOut(outputFormat3, "rsp", context.Amd64Context.Rsp, "rbp", context.Amd64Context.Rbp, "rip", context.Amd64Context.Rip);
+ ExtOut(outputFormat3, "rax", context.Amd64Context.Rax, "rbx", context.Amd64Context.Rbx, "rcx", context.Amd64Context.Rcx);
+ ExtOut(outputFormat3, "rdx", context.Amd64Context.Rdx, "rsi", context.Amd64Context.Rsi, "rdi", context.Amd64Context.Rdi);
+ ExtOut(outputFormat3, "r8", context.Amd64Context.R8, "r9", context.Amd64Context.R9, "r10", context.Amd64Context.R10);
+ ExtOut(outputFormat3, "r11", context.Amd64Context.R11, "r12", context.Amd64Context.R12, "r13", context.Amd64Context.R13);
+ ExtOut(outputFormat2, "r14", context.Amd64Context.R14, "r15", context.Amd64Context.R15);
+#elif defined(SOS_TARGET_X86)
+ String outputFormat3 = " %3s=%08x %3s=%08x %3s=%08x\n";
+ String outputFormat2 = " %3s=%08x %3s=%08x\n";
+ ExtOut(outputFormat3, "esp", context.X86Context.Esp, "ebp", context.X86Context.Ebp, "eip", context.X86Context.Eip);
+ ExtOut(outputFormat3, "eax", context.X86Context.Eax, "ebx", context.X86Context.Ebx, "ecx", context.X86Context.Ecx);
+ ExtOut(outputFormat3, "edx", context.X86Context.Edx, "esi", context.X86Context.Esi, "edi", context.X86Context.Edi);
+#elif defined(SOS_TARGET_ARM)
+ String outputFormat3 = " %3s=%08x %3s=%08x %3s=%08x\n";
+ String outputFormat2 = " %s=%08x %s=%08x\n";
+ String outputFormat1 = " %s=%08x\n";
+ ExtOut(outputFormat3, "r0", context.ArmContext.R0, "r1", context.ArmContext.R1, "r2", context.ArmContext.R2);
+ ExtOut(outputFormat3, "r3", context.ArmContext.R3, "r4", context.ArmContext.R4, "r5", context.ArmContext.R5);
+ ExtOut(outputFormat3, "r6", context.ArmContext.R6, "r7", context.ArmContext.R7, "r8", context.ArmContext.R8);
+ ExtOut(outputFormat3, "r9", context.ArmContext.R9, "r10", context.ArmContext.R10, "r11", context.ArmContext.R11);
+ ExtOut(outputFormat1, "r12", context.ArmContext.R12);
+ ExtOut(outputFormat3, "sp", context.ArmContext.Sp, "lr", context.ArmContext.Lr, "pc", context.ArmContext.Pc);
+ ExtOut(outputFormat2, "cpsr", context.ArmContext.Cpsr, "fpsr", context.ArmContext.Fpscr);
+#elif defined(SOS_TARGET_ARM64)
+ String outputXRegFormat3 = " x%d=%016x x%d=%016x x%d=%016x\n";
+ String outputXRegFormat1 = " x%d=%016x\n";
+ String outputFormat3 = " %s=%016x %s=%016x %s=%016x\n";
+ String outputFormat2 = " %s=%08x %s=%08x\n";
+ DWORD64 *X = context.Arm64Context.X;
+ for (int i = 0; i < 9; i++)
+ {
+ ExtOut(outputXRegFormat3, i + 0, X[i + 0], i + 1, X[i + 1], i + 2, X[i + 2]);
+ }
+ ExtOut(outputXRegFormat1, 28, X[28]);
+ ExtOut(outputFormat3, "sp", context.ArmContext.Sp, "lr", context.ArmContext.Lr, "pc", context.ArmContext.Pc);
+ ExtOut(outputFormat2, "cpsr", context.ArmContext.Cpsr, "fpsr", context.ArmContext.Fpscr);
+#else
+ ExtOut("Can't display register values for this platform\n");
+#endif
+ return S_OK;
+
+ }
+
+ static HRESULT GetFrameLocation(IXCLRDataStackWalk *pStackWalk, CLRDATA_ADDRESS *ip, CLRDATA_ADDRESS *sp)
+ {
+ CROSS_PLATFORM_CONTEXT context;
+ HRESULT hr = pStackWalk->GetContext(DT_CONTEXT_FULL, g_targetMachine->GetContextSize(), NULL, (BYTE *)&context);
+ if (FAILED(hr) || hr == S_FALSE)
+ {
+ // GetFrameContext returns S_FALSE if the frame iterator is invalid. That's basically an error for us.
+ ExtOut("GetFrameContext failed: %lx\n", hr);
+ return E_FAIL;
+ }
+
+ // First find the info for the Frame object, if the current frame has an associated clr!Frame.
+ *ip = GetIP(context);
+ *sp = GetSP(context);
+
+ if (IsDbgTargetArm())
+ *ip = *ip & ~THUMB_CODE;
+
+ return S_OK;
+ }
+
+ static void PrintNativeStackFrame(TableOutput out, PDEBUG_STACK_FRAME frame, BOOL bSuppressLines)
+ {
+ char filename[MAX_LONGPATH + 1];
+ char symbol[1024];
+ ULONG64 displacement;
+
+ ULONG64 ip = frame->InstructionOffset;
+
+ out.WriteColumn(0, frame->StackOffset);
+ out.WriteColumn(1, NativePtr(ip));
+
+ HRESULT hr = g_ExtSymbols->GetNameByOffset(TO_CDADDR(ip), symbol, _countof(symbol), NULL, &displacement);
+ if (SUCCEEDED(hr) && symbol[0] != '\0')
+ {
+ String frameOutput;
+ frameOutput += symbol;
+
+ if (displacement)
+ {
+ frameOutput += " + ";
+ frameOutput += Decimal(displacement);
+ }
+
+ if (!bSuppressLines)
+ {
+ ULONG line;
+ hr = g_ExtSymbols->GetLineByOffset(TO_CDADDR(ip), &line, filename, _countof(filename), NULL, NULL);
+ if (SUCCEEDED(hr))
+ {
+ frameOutput += " at ";
+ frameOutput += filename;
+ frameOutput += ":";
+ frameOutput += Decimal(line);
+ }
+ }
+
+ out.WriteColumn(2, frameOutput);
+ }
+ else
+ {
+ out.WriteColumn(2, "");
+ }
+ }
+
+ static void PrintCurrentThread(BOOL bParams, BOOL bLocals, BOOL bSuppressLines, BOOL bGC, BOOL bNative, BOOL bDisplayRegVals)
+ {
+ ULONG id = 0;
+ ULONG osid = 0;
+
+ g_ExtSystem->GetCurrentThreadSystemId(&osid);
+ ExtOut("OS Thread Id: 0x%x ", osid);
+ g_ExtSystem->GetCurrentThreadId(&id);
+ ExtOut("(%d)\n", id);
+
+ PrintThread(osid, bParams, bLocals, bSuppressLines, bGC, bNative, bDisplayRegVals);
+ }
+private:
+
+ static HRESULT CreateStackWalk(ULONG osID, IXCLRDataStackWalk **ppStackwalk)
+ {
+ HRESULT hr = S_OK;
+ ToRelease<IXCLRDataTask> pTask;
+
+ if ((hr = g_ExtSystem->GetCurrentThreadSystemId(&osID)) != S_OK ||
+ (hr = g_clrData->GetTaskByOSThreadID(osID, &pTask)) != S_OK)
+ {
+ ExtOut("Unable to walk the managed stack. The current thread is likely not a \n");
+ ExtOut("managed thread. You can run !threads to get a list of managed threads in\n");
+ ExtOut("the process\n");
+ return hr;
+ }
+
+ return pTask->CreateStackWalk(CLRDATA_SIMPFRAME_UNRECOGNIZED |
+ CLRDATA_SIMPFRAME_MANAGED_METHOD |
+ CLRDATA_SIMPFRAME_RUNTIME_MANAGED_CODE |
+ CLRDATA_SIMPFRAME_RUNTIME_UNMANAGED_CODE,
+ ppStackwalk);
+ }
+
+ /* Prints the args and locals of for a thread's stack.
+ * Params:
+ * pStackWalk - the stack we are printing
+ * bArgs - whether to print args
+ * bLocals - whether to print locals
+ */
+ static void PrintArgsAndLocals(IXCLRDataStackWalk *pStackWalk, BOOL bArgs, BOOL bLocals)
+ {
+ ToRelease<IXCLRDataFrame> pFrame;
+ ToRelease<IXCLRDataValue> pVal;
+ ULONG32 argCount = 0;
+ ULONG32 localCount = 0;
+ HRESULT hr = S_OK;
+
+ hr = pStackWalk->GetFrame(&pFrame);
+
+ // Print arguments
+ if (SUCCEEDED(hr) && bArgs)
+ hr = pFrame->GetNumArguments(&argCount);
+
+ if (SUCCEEDED(hr) && bArgs)
+ hr = ShowArgs(argCount, pFrame, pVal);
+
+ // Print locals
+ if (SUCCEEDED(hr) && bLocals)
+ hr = pFrame->GetNumLocalVariables(&localCount);
+
+ if (SUCCEEDED(hr) && bLocals)
+ ShowLocals(localCount, pFrame, pVal);
+
+ ExtOut("\n");
+ }
+
+
+
+ /* Displays the arguments to a function
+ * Params:
+ * argy - the number of arguments the function has
+ * pFramey - the frame we are inspecting
+ * pVal - a pointer to the CLRDataValue we use to query for info about the args
+ */
+ static HRESULT ShowArgs(ULONG32 argy, IXCLRDataFrame *pFramey, IXCLRDataValue *pVal)
+ {
+ CLRDATA_ADDRESS addr = 0;
+ BOOL fPrintedLocation = FALSE;
+ ULONG64 outVar = 0;
+ ULONG32 tmp;
+ HRESULT hr = S_OK;
+
+ ArrayHolder<WCHAR> argName = new NOTHROW WCHAR[mdNameLen];
+ if (!argName)
+ {
+ ReportOOM();
+ return E_FAIL;
+ }
+
+ for (ULONG32 i=0; i < argy; i++)
+ {
+ if (i == 0)
+ {
+ ExtOut(" PARAMETERS:\n");
+ }
+
+ hr = pFramey->GetArgumentByIndex(i,
+ &pVal,
+ mdNameLen,
+ &tmp,
+ argName);
+
+ if (FAILED(hr))
+ return hr;
+
+ ExtOut(" ");
+
+ if (argName[0] != L'\0')
+ {
+ ExtOut("%S ", argName.GetPtr());
+ }
+
+ // At times we cannot print the value of a parameter (most
+ // common case being a non-primitive value type). In these
+ // cases we need to print the location of the parameter,
+ // so that we can later examine it (e.g. using !dumpvc)
+ {
+ bool result = SUCCEEDED(pVal->GetNumLocations(&tmp)) && tmp == 1;
+ if (result)
+ result = SUCCEEDED(pVal->GetLocationByIndex(0, &tmp, &addr));
+
+ if (result)
+ {
+ if (tmp == CLRDATA_VLOC_REGISTER)
+ {
+ ExtOut("(<CLR reg>) ");
+ }
+ else
+ {
+ ExtOut("(0x%p) ", SOS_PTR(CDA_TO_UL64(addr)));
+ }
+ fPrintedLocation = TRUE;
+ }
+ }
+
+ if (argName[0] != L'\0' || fPrintedLocation)
+ {
+ ExtOut("= ");
+ }
+
+ if (HRESULT_CODE(pVal->GetBytes(0,&tmp,NULL)) == ERROR_BUFFER_OVERFLOW)
+ {
+ ArrayHolder<BYTE> pByte = new NOTHROW BYTE[tmp + 1];
+ if (pByte == NULL)
+ {
+ ReportOOM();
+ return E_FAIL;
+ }
+
+ hr = pVal->GetBytes(tmp, &tmp, pByte);
+
+ if (FAILED(hr))
+ {
+ ExtOut("<unable to retrieve data>\n");
+ }
+ else
+ {
+ switch(tmp)
+ {
+ case 1: outVar = *((BYTE *)pByte.GetPtr()); break;
+ case 2: outVar = *((short *)pByte.GetPtr()); break;
+ case 4: outVar = *((DWORD *)pByte.GetPtr()); break;
+ case 8: outVar = *((ULONG64 *)pByte.GetPtr()); break;
+ default: outVar = 0;
+ }
+
+ if (outVar)
+ DMLOut("0x%s\n", DMLObject(outVar));
+ else
+ ExtOut("0x%p\n", SOS_PTR(outVar));
+ }
+
+ }
+ else
+ {
+ ExtOut("<no data>\n");
+ }
+
+ pVal->Release();
+ }
+
+ return S_OK;
+ }
+
+
+ /* Prints the locals of a frame.
+ * Params:
+ * localy - the number of locals in the frame
+ * pFramey - the frame we are inspecting
+ * pVal - a pointer to the CLRDataValue we use to query for info about the args
+ */
+ static HRESULT ShowLocals(ULONG32 localy, IXCLRDataFrame *pFramey, IXCLRDataValue *pVal)
+ {
+ for (ULONG32 i=0; i < localy; i++)
+ {
+ if (i == 0)
+ ExtOut(" LOCALS:\n");
+
+ HRESULT hr;
+ ExtOut(" ");
+
+ // local names don't work in Whidbey.
+ hr = pFramey->GetLocalVariableByIndex(i, &pVal, mdNameLen, NULL, g_mdName);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ ULONG32 numLocations;
+ if (SUCCEEDED(pVal->GetNumLocations(&numLocations)) &&
+ numLocations == 1)
+ {
+ ULONG32 flags;
+ CLRDATA_ADDRESS addr;
+ if (SUCCEEDED(pVal->GetLocationByIndex(0, &flags, &addr)))
+ {
+ if (flags == CLRDATA_VLOC_REGISTER)
+ {
+ ExtOut("<CLR reg> ");
+ }
+ else
+ {
+ ExtOut("0x%p ", SOS_PTR(CDA_TO_UL64(addr)));
+ }
+ }
+
+ // Can I get a name for the item?
+
+ ExtOut("= ");
+ }
+ ULONG32 dwSize = 0;
+ hr = pVal->GetBytes(0, &dwSize, NULL);
+
+ if (HRESULT_CODE(hr) == ERROR_BUFFER_OVERFLOW)
+ {
+ ArrayHolder<BYTE> pByte = new NOTHROW BYTE[dwSize + 1];
+ if (pByte == NULL)
+ {
+ ReportOOM();
+ return E_FAIL;
+ }
+
+ hr = pVal->GetBytes(dwSize,&dwSize,pByte);
+
+ if (FAILED(hr))
+ {
+ ExtOut("<unable to retrieve data>\n");
+ }
+ else
+ {
+ ULONG64 outVar = 0;
+ switch(dwSize)
+ {
+ case 1: outVar = *((BYTE *) pByte.GetPtr()); break;
+ case 2: outVar = *((short *) pByte.GetPtr()); break;
+ case 4: outVar = *((DWORD *) pByte.GetPtr()); break;
+ case 8: outVar = *((ULONG64 *) pByte.GetPtr()); break;
+ default: outVar = 0;
+ }
+
+ if (outVar)
+ DMLOut("0x%s\n", DMLObject(outVar));
+ else
+ ExtOut("0x%p\n", SOS_PTR(outVar));
+ }
+ }
+ else
+ {
+ ExtOut("<no data>\n");
+ }
+
+ pVal->Release();
+ }
+
+ return S_OK;
+ }
+
+};
+
+#ifndef FEATURE_PAL
+
+WatchCmd g_watchCmd;
+
+// The grand new !Watch command, private to Apollo for now
+DECLARE_API(Watch)
+{
+ INIT_API_NOEE();
+ BOOL bExpression = FALSE;
+ StringHolder addExpression;
+ StringHolder aExpression;
+ StringHolder saveName;
+ StringHolder sName;
+ StringHolder expression;
+ StringHolder filterName;
+ StringHolder renameOldName;
+ size_t expandIndex = -1;
+ size_t removeIndex = -1;
+ BOOL clear = FALSE;
+
+ size_t nArg = 0;
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"-add", &addExpression.data, COSTRING, TRUE},
+ {"-a", &aExpression.data, COSTRING, TRUE},
+ {"-save", &saveName.data, COSTRING, TRUE},
+ {"-s", &sName.data, COSTRING, TRUE},
+ {"-clear", &clear, COBOOL, FALSE},
+ {"-c", &clear, COBOOL, FALSE},
+ {"-expand", &expandIndex, COSIZE_T, TRUE},
+ {"-filter", &filterName.data, COSTRING, TRUE},
+ {"-r", &removeIndex, COSIZE_T, TRUE},
+ {"-remove", &removeIndex, COSIZE_T, TRUE},
+ {"-rename", &renameOldName.data, COSTRING, TRUE},
+ };
+
+ CMDValue arg[] =
+ { // vptr, type
+ {&expression.data, COSTRING}
+ };
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ if(addExpression.data != NULL || aExpression.data != NULL)
+ {
+ WCHAR pAddExpression[MAX_EXPRESSION];
+ swprintf_s(pAddExpression, MAX_EXPRESSION, W("%S"), addExpression.data != NULL ? addExpression.data : aExpression.data);
+ Status = g_watchCmd.Add(pAddExpression);
+ }
+ else if(removeIndex != -1)
+ {
+ if(removeIndex <= 0)
+ {
+ ExtOut("Index must be a postive decimal number\n");
+ }
+ else
+ {
+ Status = g_watchCmd.Remove((int)removeIndex);
+ if(Status == S_OK)
+ ExtOut("Watch expression #%d has been removed\n", removeIndex);
+ else if(Status == S_FALSE)
+ ExtOut("There is no watch expression with index %d\n", removeIndex);
+ else
+ ExtOut("Unknown failure 0x%x removing watch expression\n", Status);
+ }
+ }
+ else if(saveName.data != NULL || sName.data != NULL)
+ {
+ WCHAR pSaveName[MAX_EXPRESSION];
+ swprintf_s(pSaveName, MAX_EXPRESSION, W("%S"), saveName.data != NULL ? saveName.data : sName.data);
+ Status = g_watchCmd.SaveList(pSaveName);
+ }
+ else if(clear)
+ {
+ g_watchCmd.Clear();
+ }
+ else if(renameOldName.data != NULL)
+ {
+ if(nArg != 1)
+ {
+ ExtOut("Must provide an old and new name. Usage: !watch -rename <old_name> <new_name>.\n");
+ return S_FALSE;
+ }
+ WCHAR pOldName[MAX_EXPRESSION];
+ swprintf_s(pOldName, MAX_EXPRESSION, W("%S"), renameOldName.data);
+ WCHAR pNewName[MAX_EXPRESSION];
+ swprintf_s(pNewName, MAX_EXPRESSION, W("%S"), expression.data);
+ g_watchCmd.RenameList(pOldName, pNewName);
+ }
+ // print the tree, possibly with filtering and/or expansion
+ else if(expandIndex != -1 || expression.data == NULL)
+ {
+ WCHAR pExpression[MAX_EXPRESSION];
+ pExpression[0] = '\0';
+
+ if(expandIndex != -1)
+ {
+ if(expression.data != NULL)
+ {
+ swprintf_s(pExpression, MAX_EXPRESSION, W("%S"), expression.data);
+ }
+ else
+ {
+ ExtOut("No expression was provided. Usage !watch -expand <index> <expression>\n");
+ return S_FALSE;
+ }
+ }
+ WCHAR pFilterName[MAX_EXPRESSION];
+ pFilterName[0] = '\0';
+
+ if(filterName.data != NULL)
+ {
+ swprintf_s(pFilterName, MAX_EXPRESSION, W("%S"), filterName.data);
+ }
+
+ g_watchCmd.Print((int)expandIndex, pExpression, pFilterName);
+ }
+ else
+ {
+ ExtOut("Unrecognized argument: %s\n", expression.data);
+ }
+
+ return Status;
+}
+
+#endif // FEATURE_PAL
+
+DECLARE_API(ClrStack)
+{
+ INIT_API();
+
+ BOOL bAll = FALSE;
+ BOOL bParams = FALSE;
+ BOOL bLocals = FALSE;
+ BOOL bSuppressLines = FALSE;
+ BOOL bICorDebug = FALSE;
+ BOOL bGC = FALSE;
+ BOOL dml = FALSE;
+ BOOL bFull = FALSE;
+ BOOL bDisplayRegVals = FALSE;
+ DWORD frameToDumpVariablesFor = -1;
+ StringHolder cvariableName;
+ ArrayHolder<WCHAR> wvariableName = new NOTHROW WCHAR[mdNameLen];
+ if (wvariableName == NULL)
+ {
+ ReportOOM();
+ return E_OUTOFMEMORY;
+ }
+
+ memset(wvariableName, 0, sizeof(wvariableName));
+
+ size_t nArg = 0;
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"-a", &bAll, COBOOL, FALSE},
+ {"-p", &bParams, COBOOL, FALSE},
+ {"-l", &bLocals, COBOOL, FALSE},
+ {"-n", &bSuppressLines, COBOOL, FALSE},
+ {"-i", &bICorDebug, COBOOL, FALSE},
+ {"-gc", &bGC, COBOOL, FALSE},
+ {"-f", &bFull, COBOOL, FALSE},
+ {"-r", &bDisplayRegVals, COBOOL, FALSE },
+#ifndef FEATURE_PAL
+ {"/d", &dml, COBOOL, FALSE},
+#endif
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&cvariableName.data, COSTRING},
+ {&frameToDumpVariablesFor, COSIZE_T},
+ };
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+ if (bAll || bParams || bLocals)
+ {
+ // No parameter or local supports for minidump case!
+ MINIDUMP_NOT_SUPPORTED();
+ }
+
+ if (bAll)
+ {
+ bParams = bLocals = TRUE;
+ }
+
+ if (bICorDebug)
+ {
+ if(nArg > 0)
+ {
+ bool firstParamIsNumber = true;
+ for(DWORD i = 0; i < strlen(cvariableName.data); i++)
+ firstParamIsNumber = firstParamIsNumber && isdigit(cvariableName.data[i]);
+
+ if(firstParamIsNumber && nArg == 1)
+ {
+ frameToDumpVariablesFor = (DWORD)GetExpression(cvariableName.data);
+ cvariableName.data[0] = '\0';
+ }
+ }
+ if(cvariableName.data != NULL && strlen(cvariableName.data) > 0)
+ swprintf_s(wvariableName, mdNameLen, W("%S\0"), cvariableName.data);
+
+ if(_wcslen(wvariableName) > 0)
+ bParams = bLocals = TRUE;
+
+ EnableDMLHolder dmlHolder(TRUE);
+ return ClrStackImplWithICorDebug::ClrStackFromPublicInterface(bParams, bLocals, FALSE, wvariableName, frameToDumpVariablesFor);
+ }
+
+ ClrStackImpl::PrintCurrentThread(bParams, bLocals, bSuppressLines, bGC, bFull, bDisplayRegVals);
+
+ return S_OK;
+}
+
+#ifndef FEATURE_PAL
+
+BOOL IsMemoryInfoAvailable()
+{
+ ULONG Class;
+ ULONG Qualifier;
+ g_ExtControl->GetDebuggeeType(&Class,&Qualifier);
+ if (Qualifier == DEBUG_DUMP_SMALL)
+ {
+ g_ExtControl->GetDumpFormatFlags(&Qualifier);
+ if ((Qualifier & DEBUG_FORMAT_USER_SMALL_FULL_MEMORY) == 0)
+ {
+ if ((Qualifier & DEBUG_FORMAT_USER_SMALL_FULL_MEMORY_INFO) == 0)
+ {
+ return FALSE;
+ }
+ }
+ }
+ return TRUE;
+}
+
+DECLARE_API( VMMap )
+{
+ INIT_API();
+
+ if (IsMiniDumpFile() || !IsMemoryInfoAvailable())
+ {
+ ExtOut("!VMMap requires a full memory dump (.dump /ma) or a live process.\n");
+ }
+ else
+ {
+ vmmap();
+ }
+
+ return Status;
+} // DECLARE_API( vmmap )
+
+DECLARE_API( SOSFlush )
+{
+ INIT_API();
+
+ g_clrData->Flush();
+
+ return Status;
+} // DECLARE_API( SOSFlush )
+
+DECLARE_API( VMStat )
+{
+ INIT_API();
+
+ if (IsMiniDumpFile() || !IsMemoryInfoAvailable())
+ {
+ ExtOut("!VMStat requires a full memory dump (.dump /ma) or a live process.\n");
+ }
+ else
+ {
+ vmstat();
+ }
+
+ return Status;
+} // DECLARE_API( vmmap )
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function saves a dll to a file. *
+* *
+\**********************************************************************/
+DECLARE_API(SaveModule)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ StringHolder Location;
+ DWORD_PTR moduleAddr = NULL;
+ BOOL bIsImage;
+
+ CMDValue arg[] =
+ { // vptr, type
+ {&moduleAddr, COHEX},
+ {&Location.data, COSTRING}
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+ if (nArg != 2)
+ {
+ ExtOut("Usage: SaveModule <address> <file to save>\n");
+ return Status;
+ }
+ if (moduleAddr == 0) {
+ ExtOut ("Invalid arg\n");
+ return Status;
+ }
+
+ char* ptr = Location.data;
+
+ DWORD_PTR dllBase = 0;
+ ULONG64 base;
+ if (g_ExtSymbols->GetModuleByOffset(TO_CDADDR(moduleAddr),0,NULL,&base) == S_OK)
+ {
+ dllBase = TO_TADDR(base);
+ }
+ else if (IsModule(moduleAddr))
+ {
+ DacpModuleData module;
+ module.Request(g_sos, TO_CDADDR(moduleAddr));
+ dllBase = TO_TADDR(module.ilBase);
+ if (dllBase == 0)
+ {
+ ExtOut ("Module does not have base address\n");
+ return Status;
+ }
+ }
+ else
+ {
+ ExtOut ("%p is not a Module or base address\n", SOS_PTR(moduleAddr));
+ return Status;
+ }
+
+ MEMORY_BASIC_INFORMATION64 mbi;
+ if (FAILED(g_ExtData2->QueryVirtual(TO_CDADDR(dllBase), &mbi)))
+ {
+ ExtOut("Failed to retrieve information about segment %p", SOS_PTR(dllBase));
+ return Status;
+ }
+
+ // module loaded as an image or mapped as a flat file?
+ bIsImage = (mbi.Type == MEM_IMAGE);
+
+ IMAGE_DOS_HEADER DosHeader;
+ if (g_ExtData->ReadVirtual(TO_CDADDR(dllBase), &DosHeader, sizeof(DosHeader), NULL) != S_OK)
+ return S_FALSE;
+
+ IMAGE_NT_HEADERS Header;
+ if (g_ExtData->ReadVirtual(TO_CDADDR(dllBase + DosHeader.e_lfanew), &Header, sizeof(Header), NULL) != S_OK)
+ return S_FALSE;
+
+ DWORD_PTR sectionAddr = dllBase + DosHeader.e_lfanew + offsetof(IMAGE_NT_HEADERS,OptionalHeader)
+ + Header.FileHeader.SizeOfOptionalHeader;
+
+ IMAGE_SECTION_HEADER section;
+ struct MemLocation
+ {
+ DWORD_PTR VAAddr;
+ DWORD_PTR VASize;
+ DWORD_PTR FileAddr;
+ DWORD_PTR FileSize;
+ };
+
+ int nSection = Header.FileHeader.NumberOfSections;
+ ExtOut("%u sections in file\n",nSection);
+ MemLocation *memLoc = (MemLocation*)_alloca(nSection*sizeof(MemLocation));
+ int indxSec = -1;
+ int slot;
+ for (int n = 0; n < nSection; n++)
+ {
+ if (g_ExtData->ReadVirtual(TO_CDADDR(sectionAddr), &section, sizeof(section), NULL) == S_OK)
+ {
+ for (slot = 0; slot <= indxSec; slot ++)
+ if (section.PointerToRawData < memLoc[slot].FileAddr)
+ break;
+
+ for (int k = indxSec; k >= slot; k --)
+ memcpy(&memLoc[k+1], &memLoc[k], sizeof(MemLocation));
+
+ memLoc[slot].VAAddr = section.VirtualAddress;
+ memLoc[slot].VASize = section.Misc.VirtualSize;
+ memLoc[slot].FileAddr = section.PointerToRawData;
+ memLoc[slot].FileSize = section.SizeOfRawData;
+ ExtOut("section %d - VA=%x, VASize=%x, FileAddr=%x, FileSize=%x\n",
+ n, memLoc[slot].VAAddr,memLoc[slot]. VASize,memLoc[slot].FileAddr,
+ memLoc[slot].FileSize);
+ indxSec ++;
+ }
+ else
+ {
+ ExtOut("Fail to read PE section info\n");
+ return Status;
+ }
+ sectionAddr += sizeof(section);
+ }
+
+ if (ptr[0] == '\0')
+ {
+ ExtOut ("File not specified\n");
+ return Status;
+ }
+
+ PCSTR file = ptr;
+ ptr += strlen(ptr)-1;
+ while (isspace(*ptr))
+ {
+ *ptr = '\0';
+ ptr --;
+ }
+
+ HANDLE hFile = CreateFileA(file,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,0,NULL);
+ if (hFile == INVALID_HANDLE_VALUE)
+ {
+ ExtOut ("Fail to create file %s\n", file);
+ return Status;
+ }
+
+ ULONG pageSize = OSPageSize();
+ char *buffer = (char *)_alloca(pageSize);
+ DWORD nRead;
+ DWORD nWrite;
+
+ // NT PE Headers
+ TADDR dwAddr = dllBase;
+ TADDR dwEnd = dllBase + Header.OptionalHeader.SizeOfHeaders;
+ while (dwAddr < dwEnd)
+ {
+ nRead = pageSize;
+ if (dwEnd - dwAddr < nRead)
+ nRead = (ULONG)(dwEnd - dwAddr);
+
+ if (g_ExtData->ReadVirtual(TO_CDADDR(dwAddr), buffer, nRead, &nRead) == S_OK)
+ {
+ WriteFile(hFile,buffer,nRead,&nWrite,NULL);
+ }
+ else
+ {
+ ExtOut ("Fail to read memory\n");
+ goto end;
+ }
+ dwAddr += nRead;
+ }
+
+ for (slot = 0; slot <= indxSec; slot ++)
+ {
+ dwAddr = dllBase + (bIsImage ? memLoc[slot].VAAddr : memLoc[slot].FileAddr);
+ dwEnd = memLoc[slot].FileSize + dwAddr - 1;
+
+ while (dwAddr <= dwEnd)
+ {
+ nRead = pageSize;
+ if (dwEnd - dwAddr + 1 < pageSize)
+ nRead = (ULONG)(dwEnd - dwAddr + 1);
+
+ if (g_ExtData->ReadVirtual(TO_CDADDR(dwAddr), buffer, nRead, &nRead) == S_OK)
+ {
+ WriteFile(hFile,buffer,nRead,&nWrite,NULL);
+ }
+ else
+ {
+ ExtOut ("Fail to read memory\n");
+ goto end;
+ }
+ dwAddr += pageSize;
+ }
+ }
+end:
+ CloseHandle (hFile);
+ return Status;
+}
+
+#ifdef _DEBUG
+DECLARE_API(dbgout)
+{
+ INIT_API();
+
+ BOOL bOff = FALSE;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"-off", &bOff, COBOOL, FALSE},
+ };
+
+ if (!GetCMDOption(args, option, _countof(option), NULL, 0, NULL))
+ {
+ return Status;
+ }
+
+ Output::SetDebugOutputEnabled(!bOff);
+ return Status;
+}
+DECLARE_API(filthint)
+{
+ INIT_API();
+
+ BOOL bOff = FALSE;
+ DWORD_PTR filter = 0;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"-off", &bOff, COBOOL, FALSE},
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&filter, COHEX}
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, option, _countof(option),
+ arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+ if (bOff)
+ {
+ g_filterHint = 0;
+ return Status;
+ }
+
+ g_filterHint = filter;
+ return Status;
+}
+#endif // _DEBUG
+
+#endif // FEATURE_PAL
+
+static HRESULT DumpMDInfoBuffer(DWORD_PTR dwStartAddr, DWORD Flags, ULONG64 Esp,
+ ULONG64 IPAddr, StringOutput& so)
+{
+#define DOAPPEND(str) \
+ do { \
+ if (!so.Append((str))) { \
+ return E_OUTOFMEMORY; \
+ }} while (0)
+
+ // Should we skip explicit frames? They are characterized by Esp = 0, && Eip = 0 or 1.
+ // See comment in FormatGeneratedException() for explanation why on non_IA64 Eip is 1, and not 0
+ if (!(Flags & SOS_STACKTRACE_SHOWEXPLICITFRAMES) && (Esp == 0) && (IPAddr == 1))
+ {
+ return S_FALSE;
+ }
+
+ DacpMethodDescData MethodDescData;
+ if (MethodDescData.Request(g_sos, TO_CDADDR(dwStartAddr)) != S_OK)
+ {
+ return E_FAIL;
+ }
+
+ ArrayHolder<WCHAR> wszNameBuffer = new WCHAR[MAX_LONGPATH+1];
+
+ if (Flags & SOS_STACKTRACE_SHOWADDRESSES)
+ {
+ _snwprintf_s(wszNameBuffer, MAX_LONGPATH, MAX_LONGPATH, W("%p %p "), (void*)(size_t) Esp, (void*)(size_t) IPAddr); // _TRUNCATE
+ DOAPPEND(wszNameBuffer);
+ }
+
+ DacpModuleData dmd;
+ BOOL bModuleNameWorked = FALSE;
+ ULONG64 addrInModule = IPAddr;
+ if (dmd.Request(g_sos, MethodDescData.ModulePtr) == S_OK)
+ {
+ CLRDATA_ADDRESS base = 0;
+ if (g_sos->GetPEFileBase(dmd.File, &base) == S_OK)
+ {
+ if (base)
+ {
+ addrInModule = base;
+ }
+ }
+ }
+ ULONG Index;
+ ULONG64 base;
+ if (g_ExtSymbols->GetModuleByOffset(UL64_TO_CDA(addrInModule), 0, &Index, &base) == S_OK)
+ {
+ ArrayHolder<char> szModuleName = new char[MAX_LONGPATH+1];
+ if (g_ExtSymbols->GetModuleNames(Index, base, NULL, 0, NULL, szModuleName, MAX_LONGPATH, NULL, NULL, 0, NULL) == S_OK)
+ {
+ MultiByteToWideChar (CP_ACP, 0, szModuleName, MAX_LONGPATH, wszNameBuffer, MAX_LONGPATH);
+ DOAPPEND (wszNameBuffer);
+ bModuleNameWorked = TRUE;
+ }
+ }
+#ifdef FEATURE_PAL
+ else
+ {
+ if (g_sos->GetPEFileName(dmd.File, MAX_LONGPATH, wszNameBuffer, NULL) == S_OK)
+ {
+ if (wszNameBuffer[0] != W('\0'))
+ {
+ WCHAR *pJustName = _wcsrchr(wszNameBuffer, DIRECTORY_SEPARATOR_CHAR_W);
+ if (pJustName == NULL)
+ pJustName = wszNameBuffer - 1;
+
+ DOAPPEND(pJustName + 1);
+ bModuleNameWorked = TRUE;
+ }
+ }
+ }
+#endif // FEATURE_PAL
+
+ // Under certain circumstances DacpMethodDescData::GetMethodDescName()
+ // returns a module qualified method name
+ HRESULT hr = g_sos->GetMethodDescName(dwStartAddr, MAX_LONGPATH, wszNameBuffer, NULL);
+
+ WCHAR* pwszMethNameBegin = (hr != S_OK ? NULL : _wcschr(wszNameBuffer, L'!'));
+ if (!bModuleNameWorked && hr == S_OK && pwszMethNameBegin != NULL)
+ {
+ // if we weren't able to get the module name, but GetMethodDescName returned
+ // the module as part of the returned method name, use this data
+ DOAPPEND(wszNameBuffer);
+ }
+ else
+ {
+ if (!bModuleNameWorked)
+ {
+ DOAPPEND (W("UNKNOWN"));
+ }
+ DOAPPEND(W("!"));
+ if (hr == S_OK)
+ {
+ // the module name we retrieved above from debugger will take
+ // precedence over the name possibly returned by GetMethodDescName()
+ DOAPPEND(pwszMethNameBegin != NULL ? (pwszMethNameBegin+1) : (WCHAR *)wszNameBuffer);
+ }
+ else
+ {
+ DOAPPEND(W("UNKNOWN"));
+ }
+ }
+
+ ULONG64 Displacement = (IPAddr - MethodDescData.NativeCodeAddr);
+ if (Displacement)
+ {
+ _snwprintf_s(wszNameBuffer, MAX_LONGPATH, MAX_LONGPATH, W("+%#x"), Displacement); // _TRUNCATE
+ DOAPPEND (wszNameBuffer);
+ }
+
+ return S_OK;
+#undef DOAPPEND
+}
+
+BOOL AppendContext(LPVOID pTransitionContexts, size_t maxCount, size_t *pcurCount, size_t uiSizeOfContext,
+ CROSS_PLATFORM_CONTEXT *context)
+{
+ if (pTransitionContexts == NULL || *pcurCount >= maxCount)
+ {
+ ++(*pcurCount);
+ return FALSE;
+ }
+ if (uiSizeOfContext == sizeof(StackTrace_SimpleContext))
+ {
+ StackTrace_SimpleContext *pSimple = (StackTrace_SimpleContext *) pTransitionContexts;
+ g_targetMachine->FillSimpleContext(&pSimple[*pcurCount], context);
+ }
+ else if (uiSizeOfContext == g_targetMachine->GetContextSize())
+ {
+ // FillTargetContext ensures we only write uiSizeOfContext bytes in pTransitionContexts
+ // and not sizeof(CROSS_PLATFORM_CONTEXT) bytes (which would overrun).
+ g_targetMachine->FillTargetContext(pTransitionContexts, context, (int)(*pcurCount));
+ }
+ else
+ {
+ return FALSE;
+ }
+ ++(*pcurCount);
+ return TRUE;
+}
+
+HRESULT CALLBACK ImplementEFNStackTrace(
+ PDEBUG_CLIENT client,
+ __out_ecount_opt(*puiTextLength) WCHAR wszTextOut[],
+ size_t *puiTextLength,
+ LPVOID pTransitionContexts,
+ size_t *puiTransitionContextCount,
+ size_t uiSizeOfContext,
+ DWORD Flags)
+{
+
+#define DOAPPEND(str) if (!so.Append((str))) { \
+ Status = E_OUTOFMEMORY; \
+ goto Exit; \
+}
+
+ HRESULT Status = E_FAIL;
+ StringOutput so;
+ size_t transitionContextCount = 0;
+
+ if (puiTextLength == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ if (pTransitionContexts)
+ {
+ if (puiTransitionContextCount == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ // Do error checking on context size
+ if ((uiSizeOfContext != g_targetMachine->GetContextSize()) &&
+ (uiSizeOfContext != sizeof(StackTrace_SimpleContext)))
+ {
+ return E_INVALIDARG;
+ }
+ }
+
+ IXCLRDataStackWalk *pStackWalk = NULL;
+ IXCLRDataTask* Task;
+ ULONG ThreadId;
+
+ if ((Status = g_ExtSystem->GetCurrentThreadSystemId(&ThreadId)) != S_OK ||
+ (Status = g_clrData->GetTaskByOSThreadID(ThreadId, &Task)) != S_OK)
+ {
+ // Not a managed thread.
+ return SOS_E_NOMANAGEDCODE;
+ }
+
+ Status = Task->CreateStackWalk(CLRDATA_SIMPFRAME_UNRECOGNIZED |
+ CLRDATA_SIMPFRAME_MANAGED_METHOD |
+ CLRDATA_SIMPFRAME_RUNTIME_MANAGED_CODE |
+ CLRDATA_SIMPFRAME_RUNTIME_UNMANAGED_CODE,
+ &pStackWalk);
+
+ Task->Release();
+
+ if (Status != S_OK)
+ {
+ if (Status == E_FAIL)
+ {
+ return SOS_E_NOMANAGEDCODE;
+ }
+ return Status;
+ }
+
+#ifdef _TARGET_WIN64_
+ ULONG numFrames = 0;
+ BOOL bInNative = TRUE;
+
+ Status = GetContextStackTrace(&numFrames);
+ if (FAILED(Status))
+ {
+ goto Exit;
+ }
+
+ for (ULONG i = 0; i < numFrames; i++)
+ {
+ PDEBUG_STACK_FRAME pCur = g_Frames + i;
+
+ CLRDATA_ADDRESS pMD;
+ if (g_sos->GetMethodDescPtrFromIP(pCur->InstructionOffset, &pMD) == S_OK)
+ {
+ if (bInNative || transitionContextCount==0)
+ {
+ // We only want to list one transition frame if there are multiple frames.
+ bInNative = FALSE;
+
+ DOAPPEND (W("(TransitionMU)\n"));
+ // For each transition, we need to store the context information
+ if (puiTransitionContextCount)
+ {
+ // below we cast the i-th AMD64_CONTEXT to CROSS_PLATFORM_CONTEXT
+ AppendContext (pTransitionContexts, *puiTransitionContextCount,
+ &transitionContextCount, uiSizeOfContext, (CROSS_PLATFORM_CONTEXT*)(&(g_X64FrameContexts[i])));
+ }
+ else
+ {
+ transitionContextCount++;
+ }
+ }
+
+ Status = DumpMDInfoBuffer((DWORD_PTR) pMD, Flags,
+ pCur->StackOffset, pCur->InstructionOffset, so);
+ if (FAILED(Status))
+ {
+ goto Exit;
+ }
+ else if (Status == S_OK)
+ {
+ DOAPPEND (W("\n"));
+ }
+ // for S_FALSE do not append anything
+
+ }
+ else
+ {
+ if (!bInNative)
+ {
+ // We only want to list one transition frame if there are multiple frames.
+ bInNative = TRUE;
+
+ DOAPPEND (W("(TransitionUM)\n"));
+ // For each transition, we need to store the context information
+ if (puiTransitionContextCount)
+ {
+ AppendContext (pTransitionContexts, *puiTransitionContextCount,
+ &transitionContextCount, uiSizeOfContext, (CROSS_PLATFORM_CONTEXT*)(&(g_X64FrameContexts[i])));
+ }
+ else
+ {
+ transitionContextCount++;
+ }
+ }
+ }
+ }
+
+Exit:
+#else // _TARGET_WIN64_
+
+#ifdef _DEBUG
+ size_t prevLength = 0;
+ static WCHAR wszNameBuffer[1024]; // should be large enough
+ wcscpy_s(wszNameBuffer, 1024, W("Frame")); // default value
+#endif
+
+ BOOL bInNative = TRUE;
+
+ UINT frameCount = 0;
+ do
+ {
+ DacpFrameData FrameData;
+ if ((Status = FrameData.Request(pStackWalk)) != S_OK)
+ {
+ goto Exit;
+ }
+
+ CROSS_PLATFORM_CONTEXT context;
+ if ((Status=pStackWalk->GetContext(DT_CONTEXT_FULL, g_targetMachine->GetContextSize(),
+ NULL, (BYTE *)&context))!=S_OK)
+ {
+ goto Exit;
+ }
+
+ ExtDbgOut ( " * Ctx[BSI]: %08x %08x %08x ", GetBP(context), GetSP(context), GetIP(context) );
+
+ CLRDATA_ADDRESS pMD;
+ if (!FrameData.frameAddr)
+ {
+ if (bInNative || transitionContextCount==0)
+ {
+ // We only want to list one transition frame if there are multiple frames.
+ bInNative = FALSE;
+
+ DOAPPEND (W("(TransitionMU)\n"));
+ // For each transition, we need to store the context information
+ if (puiTransitionContextCount)
+ {
+ AppendContext (pTransitionContexts, *puiTransitionContextCount,
+ &transitionContextCount, uiSizeOfContext, &context);
+ }
+ else
+ {
+ transitionContextCount++;
+ }
+ }
+
+ // we may have a method, try to get the methoddesc
+ if (g_sos->GetMethodDescPtrFromIP(GetIP(context), &pMD)==S_OK)
+ {
+ Status = DumpMDInfoBuffer((DWORD_PTR) pMD, Flags,
+ GetSP(context), GetIP(context), so);
+ if (FAILED(Status))
+ {
+ goto Exit;
+ }
+ else if (Status == S_OK)
+ {
+ DOAPPEND (W("\n"));
+ }
+ // for S_FALSE do not append anything
+ }
+ }
+ else
+ {
+#ifdef _DEBUG
+ if (Output::IsDebugOutputEnabled())
+ {
+ DWORD_PTR vtAddr;
+ MOVE(vtAddr, TO_TADDR(FrameData.frameAddr));
+ if (g_sos->GetFrameName(TO_CDADDR(vtAddr), 1024, wszNameBuffer, NULL) == S_OK)
+ ExtDbgOut("[%ls: %08x] ", wszNameBuffer, FrameData.frameAddr);
+ else
+ ExtDbgOut("[Frame: %08x] ", FrameData.frameAddr);
+ }
+#endif
+ if (!bInNative)
+ {
+ // We only want to list one transition frame if there are multiple frames.
+ bInNative = TRUE;
+
+ DOAPPEND (W("(TransitionUM)\n"));
+ // For each transition, we need to store the context information
+ if (puiTransitionContextCount)
+ {
+ AppendContext (pTransitionContexts, *puiTransitionContextCount,
+ &transitionContextCount, uiSizeOfContext, &context);
+ }
+ else
+ {
+ transitionContextCount++;
+ }
+ }
+ }
+
+#ifdef _DEBUG
+ if (so.Length() > prevLength)
+ {
+ ExtDbgOut ( "%ls", so.String()+prevLength );
+ prevLength = so.Length();
+ }
+ else
+ ExtDbgOut ( "\n" );
+#endif
+
+ }
+ while ((frameCount++) < MAX_STACK_FRAMES && pStackWalk->Next()==S_OK);
+
+ Status = S_OK;
+
+Exit:
+#endif // _TARGET_WIN64_
+
+ if (pStackWalk)
+ {
+ pStackWalk->Release();
+ pStackWalk = NULL;
+ }
+
+ // We have finished. Does the user want to copy this data to a buffer?
+ if (Status == S_OK)
+ {
+ if(wszTextOut)
+ {
+ // They want at least partial output
+ wcsncpy_s (wszTextOut, *puiTextLength, so.String(), *puiTextLength-1); // _TRUNCATE
+ }
+ else
+ {
+ *puiTextLength = _wcslen (so.String()) + 1;
+ }
+
+ if (puiTransitionContextCount)
+ {
+ *puiTransitionContextCount = transitionContextCount;
+ }
+ }
+
+ return Status;
+}
+
+#ifdef FEATURE_PAL
+#define PAL_TRY_NAKED PAL_CPP_TRY
+#define PAL_EXCEPT_NAKED(disp) PAL_CPP_CATCH_ALL
+#define PAL_ENDTRY_NAKED PAL_CPP_ENDTRY
+#endif
+
+// TODO: Convert PAL_TRY_NAKED to something that works on the Mac.
+HRESULT CALLBACK ImplementEFNStackTraceTry(
+ PDEBUG_CLIENT client,
+ __out_ecount_opt(*puiTextLength) WCHAR wszTextOut[],
+ size_t *puiTextLength,
+ LPVOID pTransitionContexts,
+ size_t *puiTransitionContextCount,
+ size_t uiSizeOfContext,
+ DWORD Flags)
+{
+ HRESULT Status = E_FAIL;
+
+ PAL_TRY_NAKED
+ {
+ Status = ImplementEFNStackTrace(client, wszTextOut, puiTextLength,
+ pTransitionContexts, puiTransitionContextCount,
+ uiSizeOfContext, Flags);
+ }
+ PAL_EXCEPT_NAKED (EXCEPTION_EXECUTE_HANDLER)
+ {
+ }
+ PAL_ENDTRY_NAKED
+
+ return Status;
+}
+
+// See sos_stacktrace.h for the contract with the callers regarding the LPVOID arguments.
+HRESULT CALLBACK _EFN_StackTrace(
+ PDEBUG_CLIENT client,
+ __out_ecount_opt(*puiTextLength) WCHAR wszTextOut[],
+ size_t *puiTextLength,
+ __out_bcount_opt(uiSizeOfContext*(*puiTransitionContextCount)) LPVOID pTransitionContexts,
+ size_t *puiTransitionContextCount,
+ size_t uiSizeOfContext,
+ DWORD Flags)
+{
+ INIT_API();
+
+ Status = ImplementEFNStackTraceTry(client, wszTextOut, puiTextLength,
+ pTransitionContexts, puiTransitionContextCount,
+ uiSizeOfContext, Flags);
+
+ return Status;
+}
+
+
+BOOL FormatFromRemoteString(DWORD_PTR strObjPointer, __out_ecount(cchString) PWSTR wszBuffer, ULONG cchString)
+{
+ BOOL bRet = FALSE;
+
+ wszBuffer[0] = L'\0';
+
+ DacpObjectData objData;
+ if (objData.Request(g_sos, TO_CDADDR(strObjPointer))!=S_OK)
+ {
+ return bRet;
+ }
+
+ strobjInfo stInfo;
+
+ if (MOVE(stInfo, strObjPointer) != S_OK)
+ {
+ return bRet;
+ }
+
+ DWORD dwBufLength = 0;
+ if (!ClrSafeInt<DWORD>::addition(stInfo.m_StringLength, 1, dwBufLength))
+ {
+ ExtOut("<integer overflow>\n");
+ return bRet;
+ }
+
+ LPWSTR pwszBuf = new NOTHROW WCHAR[dwBufLength];
+ if (pwszBuf == NULL)
+ {
+ return bRet;
+ }
+
+ if (g_sos->GetObjectStringData(TO_CDADDR(strObjPointer), stInfo.m_StringLength+1, pwszBuf, NULL)!=S_OK)
+ {
+ delete [] pwszBuf;
+ return bRet;
+ }
+
+ // String is in format
+ // <SP><SP><SP>at <function name>(args,...)\n
+ // ...
+ // Parse and copy just <function name>(args,...)
+
+ LPWSTR pwszPointer = pwszBuf;
+
+ WCHAR PSZSEP[] = W(" at ");
+
+ UINT Length = 0;
+ while(1)
+ {
+ if (_wcsncmp(pwszPointer, PSZSEP, _countof(PSZSEP)-1) != 0)
+ {
+ delete [] pwszBuf;
+ return bRet;
+ }
+
+ pwszPointer += _wcslen(PSZSEP);
+ LPWSTR nextPos = _wcsstr(pwszPointer, PSZSEP);
+ if (nextPos == NULL)
+ {
+ // Done! Note that we are leaving the function before we add the last
+ // line of stack trace to the output string. This is on purpose because
+ // this string needs to be merged with a real trace, and the last line
+ // of the trace will be common to the real trace.
+ break;
+ }
+ WCHAR c = *nextPos;
+ *nextPos = L'\0';
+
+ // Buffer is calculated for sprintf below (" %p %p %S\n");
+ WCHAR wszLineBuffer[mdNameLen + 8 + sizeof(size_t)*2];
+
+ // Note that we don't add a newline because we have this embedded in wszLineBuffer
+ swprintf_s(wszLineBuffer, _countof(wszLineBuffer), W(" %p %p %s"), (void*)(size_t)-1, (void*)(size_t)-1, pwszPointer);
+ Length += (UINT)_wcslen(wszLineBuffer);
+
+ if (wszBuffer)
+ {
+ wcsncat_s(wszBuffer, cchString, wszLineBuffer, _TRUNCATE);
+ }
+
+ *nextPos = c;
+ // Move to the next line.
+ pwszPointer = nextPos;
+ }
+
+ delete [] pwszBuf;
+
+ // Return TRUE only if the stack string had any information that was successfully parsed.
+ // (Length > 0) is a good indicator of that.
+ bRet = (Length > 0);
+ return bRet;
+}
+
+HRESULT AppendExceptionInfo(CLRDATA_ADDRESS cdaObj,
+ __out_ecount(cchString) PWSTR wszStackString,
+ ULONG cchString,
+ BOOL bNestedCase) // If bNestedCase is TRUE, the last frame of the computed stack is left off
+{
+ DacpObjectData objData;
+ if (objData.Request(g_sos, cdaObj) != S_OK)
+ {
+ return E_FAIL;
+ }
+
+ // Make sure it is an exception object, and get the MT of Exception
+ CLRDATA_ADDRESS exceptionMT = isExceptionObj(objData.MethodTable);
+ if (exceptionMT == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ // First try to get exception object data using ISOSDacInterface2
+ DacpExceptionObjectData excData;
+ BOOL bGotExcData = SUCCEEDED(excData.Request(g_sos, cdaObj));
+
+ int iOffset;
+ // Is there a _remoteStackTraceString? We'll want to prepend that data.
+ // We only have string data, so IP/SP info has to be set to -1.
+ DWORD_PTR strPointer;
+ if (bGotExcData)
+ {
+ strPointer = TO_TADDR(excData.RemoteStackTraceString);
+ }
+ else
+ {
+ iOffset = GetObjFieldOffset (cdaObj, objData.MethodTable, W("_remoteStackTraceString"));
+ MOVE (strPointer, TO_TADDR(cdaObj) + iOffset);
+ }
+ if (strPointer)
+ {
+ WCHAR *pwszBuffer = new NOTHROW WCHAR[cchString];
+ if (pwszBuffer == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ if (FormatFromRemoteString(strPointer, pwszBuffer, cchString))
+ {
+ // Prepend this stuff to the string for the user
+ wcsncat_s(wszStackString, cchString, pwszBuffer, _TRUNCATE);
+ }
+ delete[] pwszBuffer;
+ }
+
+ BOOL bAsync = bGotExcData ? IsAsyncException(excData)
+ : IsAsyncException(TO_TADDR(cdaObj), TO_TADDR(objData.MethodTable));
+
+ DWORD_PTR arrayPtr;
+ if (bGotExcData)
+ {
+ arrayPtr = TO_TADDR(excData.StackTrace);
+ }
+ else
+ {
+ iOffset = GetObjFieldOffset (cdaObj, objData.MethodTable, W("_stackTrace"));
+ MOVE (arrayPtr, TO_TADDR(cdaObj) + iOffset);
+ }
+
+ if (arrayPtr)
+ {
+ DWORD arrayLen;
+ MOVE (arrayLen, arrayPtr + sizeof(DWORD_PTR));
+
+ if (arrayLen)
+ {
+#ifdef _TARGET_WIN64_
+ DWORD_PTR dataPtr = arrayPtr + sizeof(DWORD_PTR) + sizeof(DWORD) + sizeof(DWORD);
+#else
+ DWORD_PTR dataPtr = arrayPtr + sizeof(DWORD_PTR) + sizeof(DWORD);
+#endif // _TARGET_WIN64_
+ size_t stackTraceSize = 0;
+ MOVE (stackTraceSize, dataPtr); // data length is stored at the beginning of the array in this case
+
+ DWORD cbStackSize = static_cast<DWORD>(stackTraceSize * sizeof(StackTraceElement));
+ dataPtr += sizeof(size_t) + sizeof(size_t); // skip the array header, then goes the data
+
+ if (stackTraceSize != 0)
+ {
+ size_t iLength = FormatGeneratedException (dataPtr, cbStackSize, NULL, 0, bAsync, bNestedCase);
+ WCHAR *pwszBuffer = new NOTHROW WCHAR[iLength + 1];
+ if (pwszBuffer)
+ {
+ FormatGeneratedException(dataPtr, cbStackSize, pwszBuffer, iLength + 1, bAsync, bNestedCase);
+ wcsncat_s(wszStackString, cchString, pwszBuffer, _TRUNCATE);
+ delete[] pwszBuffer;
+ }
+ else
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+ }
+ }
+ return S_OK;
+}
+
+HRESULT ImplementEFNGetManagedExcepStack(
+ CLRDATA_ADDRESS cdaStackObj,
+ __out_ecount(cchString) PWSTR wszStackString,
+ ULONG cchString)
+{
+ HRESULT Status = E_FAIL;
+
+ if (wszStackString == NULL || cchString == 0)
+ {
+ return E_INVALIDARG;
+ }
+
+ CLRDATA_ADDRESS threadAddr = GetCurrentManagedThread();
+ DacpThreadData Thread;
+ BOOL bCanUseThreadContext = TRUE;
+
+ ZeroMemory(&Thread, sizeof(DacpThreadData));
+
+ if ((threadAddr == NULL) || (Thread.Request(g_sos, threadAddr) != S_OK))
+ {
+ // The current thread is unmanaged
+ bCanUseThreadContext = FALSE;
+ }
+
+ if (cdaStackObj == NULL)
+ {
+ if (!bCanUseThreadContext)
+ {
+ return E_INVALIDARG;
+ }
+
+ TADDR taLTOH = NULL;
+ if ((!SafeReadMemory(TO_TADDR(Thread.lastThrownObjectHandle),
+ &taLTOH,
+ sizeof(taLTOH), NULL)) || (taLTOH==NULL))
+ {
+ return Status;
+ }
+ else
+ {
+ cdaStackObj = TO_CDADDR(taLTOH);
+ }
+ }
+
+ // Put the stack trace header on
+ AddExceptionHeader(wszStackString, cchString);
+
+ // First is there a nested exception?
+ if (bCanUseThreadContext && Thread.firstNestedException)
+ {
+ CLRDATA_ADDRESS obj = 0, next = 0;
+ CLRDATA_ADDRESS currentNested = Thread.firstNestedException;
+ do
+ {
+ Status = g_sos->GetNestedExceptionData(currentNested, &obj, &next);
+
+ // deal with the inability to read a nested exception gracefully
+ if (Status != S_OK)
+ {
+ break;
+ }
+
+ Status = AppendExceptionInfo(obj, wszStackString, cchString, TRUE);
+ currentNested = next;
+ }
+ while(currentNested != NULL);
+ }
+
+ Status = AppendExceptionInfo(cdaStackObj, wszStackString, cchString, FALSE);
+
+ return Status;
+}
+
+// TODO: Enable this when ImplementEFNStackTraceTry is fixed.
+// This function, like VerifyDAC, exists for the purpose of testing
+// hard-to-get-to SOS APIs.
+DECLARE_API(VerifyStackTrace)
+{
+ INIT_API();
+
+ BOOL bVerifyManagedExcepStack = FALSE;
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"-ManagedExcepStack", &bVerifyManagedExcepStack, COBOOL, FALSE},
+ };
+
+ if (!GetCMDOption(args, option, _countof(option), NULL,0,NULL))
+ {
+ return Status;
+ }
+
+ if (bVerifyManagedExcepStack)
+ {
+ CLRDATA_ADDRESS threadAddr = GetCurrentManagedThread();
+ DacpThreadData Thread;
+
+ TADDR taExc = NULL;
+ if ((threadAddr == NULL) || (Thread.Request(g_sos, threadAddr) != S_OK))
+ {
+ ExtOut("The current thread is unmanaged\n");
+ return Status;
+ }
+
+ TADDR taLTOH = NULL;
+ if ((!SafeReadMemory(TO_TADDR(Thread.lastThrownObjectHandle),
+ &taLTOH,
+ sizeof(taLTOH), NULL)) || (taLTOH == NULL))
+ {
+ ExtOut("There is no current managed exception on this thread\n");
+ return Status;
+ }
+ else
+ {
+ taExc = taLTOH;
+ }
+
+ const SIZE_T cchStr = 4096;
+ WCHAR *wszStr = (WCHAR *)alloca(cchStr * sizeof(WCHAR));
+ if (ImplementEFNGetManagedExcepStack(TO_CDADDR(taExc), wszStr, cchStr) != S_OK)
+ {
+ ExtOut("Error!\n");
+ return Status;
+ }
+
+ ExtOut("_EFN_GetManagedExcepStack(%P, wszStr, sizeof(wszStr)) returned:\n", SOS_PTR(taExc));
+ ExtOut("%S\n", wszStr);
+
+ if (ImplementEFNGetManagedExcepStack((ULONG64)NULL, wszStr, cchStr) != S_OK)
+ {
+ ExtOut("Error!\n");
+ return Status;
+ }
+
+ ExtOut("_EFN_GetManagedExcepStack(NULL, wszStr, sizeof(wszStr)) returned:\n");
+ ExtOut("%S\n", wszStr);
+ }
+ else
+ {
+ size_t textLength = 0;
+ size_t contextLength = 0;
+ Status = ImplementEFNStackTraceTry(client,
+ NULL,
+ &textLength,
+ NULL,
+ &contextLength,
+ 0,
+ 0);
+
+ if (Status != S_OK)
+ {
+ ExtOut("Error: %lx\n", Status);
+ return Status;
+ }
+
+ ExtOut("Number of characters requested: %d\n", textLength);
+ WCHAR *wszBuffer = new NOTHROW WCHAR[textLength + 1];
+ if (wszBuffer == NULL)
+ {
+ ReportOOM();
+ return Status;
+ }
+
+ // For the transition contexts buffer the callers are expected to allocate
+ // contextLength * sizeof(TARGET_CONTEXT), and not
+ // contextLength * sizeof(CROSS_PLATFORM_CONTEXT). See sos_stacktrace.h for
+ // details.
+ LPBYTE pContexts = new NOTHROW BYTE[contextLength * g_targetMachine->GetContextSize()];
+
+ if (pContexts == NULL)
+ {
+ ReportOOM();
+ delete[] wszBuffer;
+ return Status;
+ }
+
+ Status = ImplementEFNStackTrace(client,
+ wszBuffer,
+ &textLength,
+ pContexts,
+ &contextLength,
+ g_targetMachine->GetContextSize(),
+ 0);
+
+ if (Status != S_OK)
+ {
+ ExtOut("Error: %lx\n", Status);
+ delete[] wszBuffer;
+ delete [] pContexts;
+ return Status;
+ }
+
+ ExtOut("%S\n", wszBuffer);
+
+ ExtOut("Context information:\n");
+ if (IsDbgTargetX86())
+ {
+ ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s\n",
+ "Ebp", "Esp", "Eip");
+ }
+ else if (IsDbgTargetAmd64())
+ {
+ ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s\n",
+ "Rbp", "Rsp", "Rip");
+ }
+ else if (IsDbgTargetArm())
+ {
+ ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s\n",
+ "FP", "SP", "PC");
+ }
+ else
+ {
+ ExtOut("Unsupported platform");
+ delete [] pContexts;
+ delete[] wszBuffer;
+ return S_FALSE;
+ }
+
+ for (size_t j=0; j < contextLength; j++)
+ {
+ CROSS_PLATFORM_CONTEXT *pCtx = (CROSS_PLATFORM_CONTEXT*)(pContexts + j*g_targetMachine->GetContextSize());
+ ExtOut("%p %p %p\n", GetBP(*pCtx), GetSP(*pCtx), GetIP(*pCtx));
+ }
+
+ delete [] pContexts;
+
+ StackTrace_SimpleContext *pSimple = new NOTHROW StackTrace_SimpleContext[contextLength];
+ if (pSimple == NULL)
+ {
+ ReportOOM();
+ delete[] wszBuffer;
+ return Status;
+ }
+
+ Status = ImplementEFNStackTrace(client,
+ wszBuffer,
+ &textLength,
+ pSimple,
+ &contextLength,
+ sizeof(StackTrace_SimpleContext),
+ 0);
+
+ if (Status != S_OK)
+ {
+ ExtOut("Error: %lx\n", Status);
+ delete[] wszBuffer;
+ delete [] pSimple;
+ return Status;
+ }
+
+ ExtOut("Simple Context information:\n");
+ if (IsDbgTargetX86())
+ ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s\n",
+ "Ebp", "Esp", "Eip");
+ else if (IsDbgTargetAmd64())
+ ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s\n",
+ "Rbp", "Rsp", "Rip");
+ else if (IsDbgTargetArm())
+ ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s\n",
+ "FP", "SP", "PC");
+ else
+ {
+ ExtOut("Unsupported platform");
+ delete[] wszBuffer;
+ delete [] pSimple;
+ return S_FALSE;
+ }
+ for (size_t j=0; j < contextLength; j++)
+ {
+ ExtOut("%p %p %p\n", SOS_PTR(pSimple[j].FrameOffset),
+ SOS_PTR(pSimple[j].StackOffset),
+ SOS_PTR(pSimple[j].InstructionOffset));
+ }
+ delete [] pSimple;
+ delete[] wszBuffer;
+ }
+
+ return Status;
+}
+
+#ifndef FEATURE_PAL
+
+// This is an internal-only Apollo extension to de-optimize the code
+DECLARE_API(SuppressJitOptimization)
+{
+ INIT_API_NOEE();
+ MINIDUMP_NOT_SUPPORTED();
+
+ StringHolder onOff;
+ CMDValue arg[] =
+ { // vptr, type
+ {&onOff.data, COSTRING},
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg))
+ {
+ return E_FAIL;
+ }
+
+ if(nArg == 1 && (_stricmp(onOff.data, "On") == 0))
+ {
+ // if CLR is already loaded, try to change the flags now
+ if(CheckEEDll() == S_OK)
+ {
+ SetNGENCompilerFlags(CORDEBUG_JIT_DISABLE_OPTIMIZATION);
+ }
+
+ if(!g_fAllowJitOptimization)
+ ExtOut("JIT optimization is already suppressed\n");
+ else
+ {
+ g_fAllowJitOptimization = FALSE;
+ g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, "sxe -c \"!HandleCLRN\" clrn", 0);
+ ExtOut("JIT optimization will be suppressed\n");
+ }
+
+
+ }
+ else if(nArg == 1 && (_stricmp(onOff.data, "Off") == 0))
+ {
+ // if CLR is already loaded, try to change the flags now
+ if(CheckEEDll() == S_OK)
+ {
+ SetNGENCompilerFlags(CORDEBUG_JIT_DEFAULT);
+ }
+
+ if(g_fAllowJitOptimization)
+ ExtOut("JIT optimization is already permitted\n");
+ else
+ {
+ g_fAllowJitOptimization = TRUE;
+ ExtOut("JIT optimization will be permitted\n");
+ }
+ }
+ else
+ {
+ ExtOut("Usage: !SuppressJitOptimization <on|off>\n");
+ }
+
+ return S_OK;
+}
+
+// Uses ICorDebug to set the state of desired NGEN compiler flags. This can suppress pre-jitted optimized
+// code
+HRESULT SetNGENCompilerFlags(DWORD flags)
+{
+ HRESULT hr;
+
+ ToRelease<ICorDebugProcess2> proc2;
+ if(FAILED(hr = InitCorDebugInterface()))
+ {
+ ExtOut("SOS: warning, prejitted code optimizations could not be changed. Failed to load ICorDebug HR = 0x%x\n", hr);
+ }
+ else if(FAILED(g_pCorDebugProcess->QueryInterface(__uuidof(ICorDebugProcess2), (void**) &proc2)))
+ {
+ if(flags != CORDEBUG_JIT_DEFAULT)
+ {
+ ExtOut("SOS: warning, prejitted code optimizations could not be changed. This CLR version doesn't support the functionality\n");
+ }
+ else
+ {
+ hr = S_OK;
+ }
+ }
+ else if(FAILED(hr = proc2->SetDesiredNGENCompilerFlags(flags)))
+ {
+ // Versions of CLR that don't have SetDesiredNGENCompilerFlags DAC-ized will return E_FAIL.
+ // This was first supported in the clr_triton branch around 4/1/12, Apollo release
+ // It will likely be supported in desktop CLR during Dev12
+ if(hr == E_FAIL)
+ {
+ if(flags != CORDEBUG_JIT_DEFAULT)
+ {
+ ExtOut("SOS: warning, prejitted code optimizations could not be changed. This CLR version doesn't support the functionality\n");
+ }
+ else
+ {
+ hr = S_OK;
+ }
+ }
+ else if(hr == CORDBG_E_NGEN_NOT_SUPPORTED)
+ {
+ if(flags != CORDEBUG_JIT_DEFAULT)
+ {
+ ExtOut("SOS: warning, prejitted code optimizations could not be changed. This CLR version doesn't support NGEN\n");
+ }
+ else
+ {
+ hr = S_OK;
+ }
+ }
+ else if(hr == CORDBG_E_MUST_BE_IN_CREATE_PROCESS)
+ {
+ DWORD currentFlags = 0;
+ if(FAILED(hr = proc2->GetDesiredNGENCompilerFlags(&currentFlags)))
+ {
+ ExtOut("SOS: warning, prejitted code optimizations could not be changed. GetDesiredNGENCompilerFlags failed hr=0x%x\n", hr);
+ }
+ else if(currentFlags != flags)
+ {
+ ExtOut("SOS: warning, prejitted code optimizations could not be changed at this time. This setting is fixed once CLR starts\n");
+ }
+ else
+ {
+ hr = S_OK;
+ }
+ }
+ else
+ {
+ ExtOut("SOS: warning, prejitted code optimizations could not be changed at this time. SetDesiredNGENCompilerFlags hr = 0x%x\n", hr);
+ }
+ }
+
+ return hr;
+}
+
+
+// This is an internal-only Apollo extension to save breakpoint/watch state
+DECLARE_API(SaveState)
+{
+ INIT_API_NOEE();
+ MINIDUMP_NOT_SUPPORTED();
+
+ StringHolder filePath;
+ CMDValue arg[] =
+ { // vptr, type
+ {&filePath.data, COSTRING},
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg))
+ {
+ return E_FAIL;
+ }
+
+ if(nArg == 0)
+ {
+ ExtOut("Usage: !SaveState <file_path>\n");
+ }
+
+ FILE* pFile;
+ errno_t error = fopen_s(&pFile, filePath.data, "w");
+ if(error != 0)
+ {
+ ExtOut("Failed to open file %s, error=0x%x\n", filePath.data, error);
+ return E_FAIL;
+ }
+
+ g_bpoints.SaveBreakpoints(pFile);
+ g_watchCmd.SaveListToFile(pFile);
+
+ fclose(pFile);
+ ExtOut("Session breakpoints and watch expressions saved to %s\n", filePath.data);
+ return S_OK;
+}
+
+#endif // FEATURE_PAL
+
+DECLARE_API(StopOnCatch)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ g_stopOnNextCatch = TRUE;
+ ULONG32 flags = 0;
+ g_clrData->GetOtherNotificationFlags(&flags);
+ flags |= CLRDATA_NOTIFY_ON_EXCEPTION_CATCH_ENTER;
+ g_clrData->SetOtherNotificationFlags(flags);
+ ExtOut("Debuggee will break the next time a managed exception is caught during execution\n");
+ return S_OK;
+}
+
+// This is an undocumented SOS extension command intended to help test SOS
+// It causes the Dml output to be printed to the console uninterpretted so
+// that a test script can read the commands which are hidden in the markup
+DECLARE_API(ExposeDML)
+{
+ Output::SetDMLExposed(true);
+ return S_OK;
+}
+
+// According to kksharma the Windows debuggers always sign-extend
+// arguments when calling externally, therefore StackObjAddr
+// conforms to CLRDATA_ADDRESS contract.
+HRESULT CALLBACK
+_EFN_GetManagedExcepStack(
+ PDEBUG_CLIENT client,
+ ULONG64 StackObjAddr,
+ __out_ecount (cbString) PSTR szStackString,
+ ULONG cbString
+ )
+{
+ INIT_API();
+
+ ArrayHolder<WCHAR> tmpStr = new NOTHROW WCHAR[cbString];
+ if (tmpStr == NULL)
+ {
+ ReportOOM();
+ return E_OUTOFMEMORY;
+ }
+
+ if (FAILED(Status = ImplementEFNGetManagedExcepStack(StackObjAddr, tmpStr, cbString)))
+ {
+ return Status;
+ }
+
+ if (WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, tmpStr, -1, szStackString, cbString, NULL, NULL) == 0)
+ {
+ return E_FAIL;
+ }
+
+ return S_OK;
+}
+
+// same as _EFN_GetManagedExcepStack, but returns the stack as a wide string.
+HRESULT CALLBACK
+_EFN_GetManagedExcepStackW(
+ PDEBUG_CLIENT client,
+ ULONG64 StackObjAddr,
+ __out_ecount(cchString) PWSTR wszStackString,
+ ULONG cchString
+ )
+{
+ INIT_API();
+
+ return ImplementEFNGetManagedExcepStack(StackObjAddr, wszStackString, cchString);
+}
+
+// According to kksharma the Windows debuggers always sign-extend
+// arguments when calling externally, therefore objAddr
+// conforms to CLRDATA_ADDRESS contract.
+HRESULT CALLBACK
+_EFN_GetManagedObjectName(
+ PDEBUG_CLIENT client,
+ ULONG64 objAddr,
+ __out_ecount (cbName) PSTR szName,
+ ULONG cbName
+ )
+{
+ INIT_API ();
+
+ if (!sos::IsObject(objAddr, false))
+ {
+ return E_INVALIDARG;
+ }
+
+ sos::Object obj = TO_TADDR(objAddr);
+
+ if (WideCharToMultiByte(CP_ACP, 0, obj.GetTypeName(), (int) (_wcslen(obj.GetTypeName()) + 1),
+ szName, cbName, NULL, NULL) == 0)
+ {
+ return E_FAIL;
+ }
+ return S_OK;
+}
+
+// According to kksharma the Windows debuggers always sign-extend
+// arguments when calling externally, therefore objAddr
+// conforms to CLRDATA_ADDRESS contract.
+HRESULT CALLBACK
+_EFN_GetManagedObjectFieldInfo(
+ PDEBUG_CLIENT client,
+ ULONG64 objAddr,
+ __out_ecount (mdNameLen) PSTR szFieldName,
+ PULONG64 pValue,
+ PULONG pOffset
+ )
+{
+ INIT_API();
+ DacpObjectData objData;
+ LPWSTR fieldName = (LPWSTR)alloca(mdNameLen * sizeof(WCHAR));
+
+ if (szFieldName == NULL || *szFieldName == '\0' ||
+ objAddr == NULL)
+ {
+ return E_FAIL;
+ }
+
+ if (pOffset == NULL && pValue == NULL)
+ {
+ // One of these needs to be valid
+ return E_FAIL;
+ }
+
+ if (FAILED(objData.Request(g_sos, objAddr)))
+ {
+ return E_FAIL;
+ }
+
+ MultiByteToWideChar(CP_ACP,0,szFieldName,-1,fieldName,mdNameLen);
+
+ int iOffset = GetObjFieldOffset (objAddr, objData.MethodTable, fieldName);
+ if (iOffset <= 0)
+ {
+ return E_FAIL;
+ }
+
+ if (pOffset)
+ {
+ *pOffset = (ULONG) iOffset;
+ }
+
+ if (pValue)
+ {
+ if (FAILED(g_ExtData->ReadVirtual(UL64_TO_CDA(objAddr + iOffset), pValue, sizeof(ULONG64), NULL)))
+ {
+ return E_FAIL;
+ }
+ }
+
+ return S_OK;
+}
+
+void PrintHelp (__in_z LPCSTR pszCmdName)
+{
+ static LPSTR pText = NULL;
+
+ if (pText == NULL) {
+#ifndef FEATURE_PAL
+ HGLOBAL hResource = NULL;
+ HRSRC hResInfo = FindResource (g_hInstance, TEXT ("DOCUMENTATION"), TEXT ("TEXT"));
+ if (hResInfo) hResource = LoadResource (g_hInstance, hResInfo);
+ if (hResource) pText = (LPSTR) LockResource (hResource);
+ if (pText == NULL)
+ {
+ ExtOut("Error loading documentation resource\n");
+ return;
+ }
+#else
+ int err = PAL_InitializeDLL();
+ if(err != 0)
+ {
+ ExtOut("Error initializing PAL\n");
+ return;
+ }
+ char lpFilename[MAX_LONGPATH + 12]; // + 12 to make enough room for strcat function.
+ strcpy_s(lpFilename, _countof(lpFilename), g_ExtServices->GetCoreClrDirectory());
+ strcat_s(lpFilename, _countof(lpFilename), "sosdocsunix.txt");
+
+ HANDLE hSosDocFile = CreateFileA(lpFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
+ if (hSosDocFile == INVALID_HANDLE_VALUE) {
+ ExtOut("Error finding documentation file\n");
+ return;
+ }
+
+ HANDLE hMappedSosDocFile = CreateFileMappingA(hSosDocFile, NULL, PAGE_READONLY, 0, 0, NULL);
+ CloseHandle(hSosDocFile);
+ if (hMappedSosDocFile == NULL) {
+ ExtOut("Error mapping documentation file\n");
+ return;
+ }
+
+ pText = (LPSTR)MapViewOfFile(hMappedSosDocFile, FILE_MAP_READ, 0, 0, 0);
+ CloseHandle(hMappedSosDocFile);
+ if (pText == NULL)
+ {
+ ExtOut("Error loading documentation file\n");
+ return;
+ }
+#endif
+ }
+
+ // Find our line in the text file
+ char searchString[MAX_LONGPATH];
+ sprintf_s(searchString, _countof(searchString), "COMMAND: %s.", pszCmdName);
+
+ LPSTR pStart = strstr(pText, searchString);
+ LPSTR pEnd = NULL;
+ if (!pStart)
+ {
+ ExtOut("Documentation for %s not found.\n", pszCmdName);
+ return;
+ }
+
+ // Go to the end of this line:
+ pStart = strchr(pStart, '\n');
+ if (!pStart)
+ {
+ ExtOut("Expected newline in documentation resource.\n");
+ return;
+ }
+
+ // Bypass the newline that pStart points to and setup pEnd for the loop below. We set
+ // pEnd to be the old pStart since we add one to it when we call strstr.
+ pEnd = pStart++;
+
+ // Find the first occurrence of \\ followed by an \r or an \n on a line by itself.
+ do
+ {
+ pEnd = strstr(pEnd+1, "\\\\");
+ } while (pEnd && ((pEnd[-1] != '\r' && pEnd[-1] != '\n') || (pEnd[3] != '\r' && pEnd[3] != '\n')));
+
+ if (pEnd)
+ {
+ // We have found a \\ followed by a \r or \n. Do not print out the character pEnd points
+ // to, as this will be the first \ (this is why we don't add one to the second parameter).
+ ExtOut("%.*s", pEnd - pStart, pStart);
+ }
+ else
+ {
+ // If pEnd is false then we have run to the end of the document. However, we did find
+ // the command to print, so we should simply print to the end of the file. We'll add
+ // an extra newline here in case the file does not contain one.
+ ExtOut("%s\n", pStart);
+ }
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function displays the commands available in strike and the *
+* arguments passed into each.
+* *
+\**********************************************************************/
+DECLARE_API(Help)
+{
+ // Call extension initialization functions directly, because we don't need the DAC dll to be initialized to get help.
+ HRESULT Status;
+ __ExtensionCleanUp __extensionCleanUp;
+ if ((Status = ExtQuery(client)) != S_OK) return Status;
+ ControlC = FALSE;
+
+ StringHolder commandName;
+ CMDValue arg[] =
+ {
+ {&commandName.data, COSTRING}
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+
+ ExtOut("-------------------------------------------------------------------------------\n");
+
+ if (nArg == 1)
+ {
+ // Convert commandName to lower-case
+ LPSTR curChar = commandName.data;
+ while (*curChar != '\0')
+ {
+ if ( ((unsigned) *curChar <= 0x7F) && isupper(*curChar))
+ {
+ *curChar = (CHAR) tolower(*curChar);
+ }
+ curChar++;
+ }
+
+ // Strip off leading "!" if the user put that.
+ curChar = commandName.data;
+ if (*curChar == '!')
+ curChar++;
+
+ PrintHelp (curChar);
+ }
+ else
+ {
+ PrintHelp ("contents");
+ }
+
+ return S_OK;
+}