summaryrefslogtreecommitdiff
path: root/src/debug/ee/debuggermodule.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/debug/ee/debuggermodule.cpp')
-rw-r--r--src/debug/ee/debuggermodule.cpp444
1 files changed, 444 insertions, 0 deletions
diff --git a/src/debug/ee/debuggermodule.cpp b/src/debug/ee/debuggermodule.cpp
new file mode 100644
index 0000000000..25504263b1
--- /dev/null
+++ b/src/debug/ee/debuggermodule.cpp
@@ -0,0 +1,444 @@
+// 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.
+//*****************************************************************************
+// File: DebuggerModule.cpp
+//
+
+//
+// Stuff for tracking DebuggerModules.
+//
+//*****************************************************************************
+
+#include "stdafx.h"
+#include "../inc/common.h"
+#include "perflog.h"
+#include "eeconfig.h" // This is here even for retail & free builds...
+#include "vars.hpp"
+#include <limits.h>
+#include "ilformatter.h"
+#include "debuginfostore.h"
+
+
+/* ------------------------------------------------------------------------ *
+ * Debugger Module routines
+ * ------------------------------------------------------------------------ */
+
+// <TODO> (8/12/2002)
+// We need to stop lying to the debugger about not sharing Modules.
+// Primary Modules allow a transition to that. Once we stop lying,
+// then all modules will be their own Primary.
+// </TODO>
+// Select the primary module.
+// Primary Modules are selected DebuggerModules that map 1:1 w/ Module*.
+// If the runtime module is not shared, then we're our own Primary Module.
+// If the Runtime module is shared, the primary module is some specific instance.
+// Note that a domain-neutral module can be loaded into multiple domains without
+// being loaded into the default domain, and so there is no "primary module" as far
+// as the CLR is concerned - we just pick any one and call it primary.
+void DebuggerModule::PickPrimaryModule()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ Debugger::DebuggerDataLockHolder ch(g_pDebugger);
+
+ LOG((LF_CORDB, LL_INFO100000, "DM::PickPrimaryModule, this=0x%p\n", this));
+
+ // We're our own primary module, unless something else proves otherwise.
+ // Note that we should be able to skip all of this if this module is not domain neutral
+ m_pPrimaryModule = this;
+
+ // This should be thread safe because our creation for the DebuggerModules
+ // are serialized.
+
+ // Lookup our Runtime Module. If it's already in there,
+ // then
+ DebuggerModuleTable * pTable = g_pDebugger->GetModuleTable();
+
+ // If the table doesn't exist yet, then we must be a primary module.
+ if (pTable == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO100000, "DM::PickPrimaryModule, this=0x%p, table not created yet\n", this));
+ return;
+ }
+
+ // Look through existing module list to find a common primary DebuggerModule
+ // for the given EE Module. We don't know what order we'll traverse in.
+
+ HASHFIND f;
+ for (DebuggerModule * m = pTable->GetFirstModule(&f);
+ m != NULL;
+ m = pTable->GetNextModule(&f))
+ {
+
+ if (m->GetRuntimeModule() == this->GetRuntimeModule())
+ {
+ // Make sure we're picking another primary module.
+ if (m->GetPrimaryModule() == m)
+ {
+ // If we find another one, it must be domain neutral
+ _ASSERTE( m_pRuntimeModule->GetAssembly()->IsDomainNeutral() );
+
+ m_pPrimaryModule = m;
+ LOG((LF_CORDB, LL_INFO100000, "DM::PickPrimaryModule, this=0x%p, primary=0x%p\n", this, m));
+ return;
+ }
+ }
+ } // end for
+
+ // If we got here, then this instance is a Primary Module.
+ LOG((LF_CORDB, LL_INFO100000, "DM::PickPrimaryModule, this=%p is first, primary.\n", this));
+}
+
+void DebuggerModule::SetCanChangeJitFlags(bool fCanChangeJitFlags)
+{
+ m_fCanChangeJitFlags = fCanChangeJitFlags;
+}
+
+#ifndef DACCESS_COMPILE
+
+
+DebuggerModuleTable::DebuggerModuleTable() : CHashTableAndData<CNewZeroData>(101)
+{
+ WRAPPER_NO_CONTRACT;
+
+ SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE;
+ NewInit(101, sizeof(DebuggerModuleEntry), 101);
+}
+
+DebuggerModuleTable::~DebuggerModuleTable()
+{
+ WRAPPER_NO_CONTRACT;
+
+ _ASSERTE(ThreadHoldsLock());
+ Clear();
+}
+
+
+#ifdef _DEBUG
+bool DebuggerModuleTable::ThreadHoldsLock()
+{
+ // In shutdown (g_fProcessDetach), the shutdown thread implicitly holds all locks.
+ return g_fProcessDetach || g_pDebugger->HasDebuggerDataLock();
+}
+#endif
+
+//
+// RemoveModules removes any module loaded into the given appdomain from the hash. This is used when we send an
+// ExitAppdomain event to ensure that there are no leftover modules in the hash. This can happen when we have shared
+// modules that aren't properly accounted for in the CLR. We miss sending UnloadModule events for those modules, so
+// we clean them up with this method.
+//
+void DebuggerModuleTable::RemoveModules(AppDomain *pAppDomain)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO1000, "DMT::RM removing all modules from AD 0x%08x\n", pAppDomain));
+
+ _ASSERTE(ThreadHoldsLock());
+
+ HASHFIND hf;
+ DebuggerModuleEntry *pDME = (DebuggerModuleEntry *) FindFirstEntry(&hf);
+
+ while (pDME != NULL)
+ {
+ DebuggerModule *pDM = pDME->module;
+
+ if (pDM->GetAppDomain() == pAppDomain)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DMT::RM removing DebuggerModule 0x%08x\n", pDM));
+
+ // Defer to the normal logic in RemoveModule for the actual removal. This accuratley simulates what
+ // happens when we process an UnloadModule event.
+ RemoveModule(pDM->GetRuntimeModule(), pAppDomain);
+
+ // Start back at the first entry since we just modified the hash.
+ pDME = (DebuggerModuleEntry *) FindFirstEntry(&hf);
+ }
+ else
+ {
+ pDME = (DebuggerModuleEntry *) FindNextEntry(&hf);
+ }
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "DMT::RM done removing all modules from AD 0x%08x\n", pAppDomain));
+}
+
+void DebuggerModuleTable::Clear()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(ThreadHoldsLock());
+
+ HASHFIND hf;
+ DebuggerModuleEntry *pDME;
+
+ pDME = (DebuggerModuleEntry *) FindFirstEntry(&hf);
+
+ while (pDME)
+ {
+ DebuggerModule *pDM = pDME->module;
+ Module *pEEM = pDM->GetRuntimeModule();
+
+ TRACE_FREE(pDME->module);
+ DeleteInteropSafe(pDM);
+ Delete(HASH(pEEM), (HASHENTRY *) pDME);
+
+ pDME = (DebuggerModuleEntry *) FindFirstEntry(&hf);
+ }
+
+ CHashTableAndData<CNewZeroData>::Clear();
+}
+
+void DebuggerModuleTable::AddModule(DebuggerModule *pModule)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(ThreadHoldsLock());
+
+ _ASSERTE(pModule != NULL);
+
+ LOG((LF_CORDB, LL_EVERYTHING, "DMT::AM: DebuggerMod:0x%x Module:0x%x AD:0x%x\n",
+ pModule, pModule->GetRuntimeModule(), pModule->GetAppDomain()));
+
+ DebuggerModuleEntry * pEntry = (DebuggerModuleEntry *) Add(HASH(pModule->GetRuntimeModule()));
+ if (pEntry == NULL)
+ {
+ ThrowOutOfMemory();
+ }
+
+ pEntry->module = pModule;
+
+ // Don't need to update the primary module since it was set when we created the module.
+ _ASSERTE(pModule->GetPrimaryModule() != NULL);
+}
+
+//-----------------------------------------------------------------------------
+// Remove a DebuggerModule from the module table.
+// This occurs in response to AppDomain unload.
+// Note that this doesn't necessarily mean the EE Module is being unloaded (it may be shared)
+//-----------------------------------------------------------------------------
+void DebuggerModuleTable::RemoveModule(Module* module, AppDomain *pAppDomain)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(module != NULL);
+ _ASSERTE(ThreadHoldsLock());
+
+ DebuggerModule * pDeletedModule = NULL;
+
+ LOG((LF_CORDB, LL_EVERYTHING, "DMT::RM: mod:0x%x AD:0x%x neutral:0x%x\n",
+ module, pAppDomain, module->GetAssembly()->IsDomainNeutral() ));
+
+ // If this is a domain neutral module, then scan the complete list of DebuggerModules looking
+ // for the one with a matching appdomain id.
+ // Note: we have to make sure to lookup the module with the app domain parameter if the module lives in a shared
+ // assembly or the system assembly. <BUGNUM>Bugs 65943 & 81728.</BUGNUM>
+ _ASSERTE( SystemDomain::SystemAssembly()->IsDomainNeutral() );
+ if (module->GetAssembly()->IsDomainNeutral())
+ {
+ // This module is being unloaded from a specific AppDomain, but may still exist in other AppDomains
+
+ HASHFIND findmodule;
+ DebuggerModuleEntry *moduleentry;
+
+ for (moduleentry = (DebuggerModuleEntry*) FindFirstEntry(&findmodule);
+ moduleentry != NULL;
+ moduleentry = (DebuggerModuleEntry*) FindNextEntry(&findmodule))
+ {
+ DebuggerModule *pModule = moduleentry->module;
+
+ if ((pModule->GetRuntimeModule() == module) &&
+ (pModule->GetAppDomain() == pAppDomain))
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "DMT::RM: found 0x%x (DM:0x%x)\n",
+ moduleentry, moduleentry->module));
+
+ pDeletedModule = pModule;
+
+ // Remove from table
+ Delete(HASH(module), (HASHENTRY *)moduleentry);
+
+ break;
+ }
+ }
+ // we should always find the module!!
+ _ASSERTE (moduleentry != NULL);
+ }
+ else
+ {
+ // This module is not shared among multiple AppDomains
+
+ DebuggerModuleEntry *entry
+ = (DebuggerModuleEntry *) Find(HASH(module), KEY(module));
+
+ _ASSERTE(entry != NULL); // it had better be in there!
+
+ if (entry != NULL) // if its not, we fail gracefully in a free build
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "DMT::RM: found 0x%x (DM:0x%x)\n",
+ entry, entry->module));
+
+ pDeletedModule = entry->module;
+
+ // Remove from table
+ Delete(HASH(module), (HASHENTRY *)entry);
+
+ // There should not be any other entry in the table for the same module
+ _ASSERTE( Find(HASH(module), KEY(module)) == NULL );
+ }
+ }
+
+ _ASSERTE(pDeletedModule != NULL);
+
+ // Update the primary module pointers. If any other module had this as a
+ // primary module, then we have to update that pointer (since we can't
+ // have our primary module be deleted!)
+ {
+ HASHFIND findmodule;
+ DebuggerModuleEntry *moduleentry;
+
+ DebuggerModule * pNewPrimary = NULL;
+
+ for (moduleentry = (DebuggerModuleEntry*) FindFirstEntry(&findmodule);
+ moduleentry != NULL;
+ moduleentry = (DebuggerModuleEntry*) FindNextEntry(&findmodule))
+ {
+ DebuggerModule *pOther = moduleentry->module;
+ _ASSERTE(pOther != NULL);
+ _ASSERTE(pOther != pDeletedModule);
+
+ // If pOther's primary was just deleted, then update it.
+ if (pOther->GetPrimaryModule() == pDeletedModule)
+ {
+ if (pNewPrimary == NULL)
+ {
+ pNewPrimary = pOther;
+ LOG((LF_CORDB, LL_INFO1000, "DMT::RM changed primary module from 0x%p to 0x%p\n", pDeletedModule, pNewPrimary));
+ }
+ pOther->SetPrimaryModule(pNewPrimary);
+ }
+ } // end for
+ }
+
+ DeleteInteropSafe(pDeletedModule);
+}
+
+
+#endif // DACCESS_COMPILE
+
+DebuggerModule *DebuggerModuleTable::GetModule(Module* module)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(module != NULL);
+ _ASSERTE(ThreadHoldsLock());
+
+ DebuggerModuleEntry *entry
+ = (DebuggerModuleEntry *) Find(HASH(module), KEY(module));
+ if (entry == NULL)
+ return NULL;
+ else
+ return entry->module;
+}
+
+// We should never look for a NULL Module *
+DebuggerModule *DebuggerModuleTable::GetModule(Module* module, AppDomain* pAppDomain)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(module != NULL);
+ _ASSERTE(ThreadHoldsLock());
+
+
+ HASHFIND findmodule;
+ DebuggerModuleEntry *moduleentry;
+
+ for (moduleentry = (DebuggerModuleEntry*) FindFirstEntry(&findmodule);
+ moduleentry != NULL;
+ moduleentry = (DebuggerModuleEntry*) FindNextEntry(&findmodule))
+ {
+ DebuggerModule *pModule = moduleentry->module;
+
+ if ((pModule->GetRuntimeModule() == module) &&
+ (pModule->GetAppDomain() == pAppDomain))
+ return pModule;
+ }
+
+ // didn't find any match! So return a matching module for any app domain
+ return NULL;
+}
+
+DebuggerModule *DebuggerModuleTable::GetFirstModule(HASHFIND *info)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(ThreadHoldsLock());
+
+ DebuggerModuleEntry *entry = (DebuggerModuleEntry *) FindFirstEntry(info);
+ if (entry == NULL)
+ return NULL;
+ else
+ return entry->module;
+}
+
+DebuggerModule *DebuggerModuleTable::GetNextModule(HASHFIND *info)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(ThreadHoldsLock());
+
+ DebuggerModuleEntry *entry = (DebuggerModuleEntry *) FindNextEntry(info);
+ if (entry == NULL)
+ return NULL;
+ else
+ return entry->module;
+}
+
+