// 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 #include "ilformatter.h" #include "debuginfostore.h" /* ------------------------------------------------------------------------ * * Debugger Module routines * ------------------------------------------------------------------------ */ // (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. // // 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(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::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. Bugs 65943 & 81728. _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; }