| 1 | // Licensed to the .NET Foundation under one or more agreements. | 
|---|
| 2 | // The .NET Foundation licenses this file to you under the MIT license. | 
|---|
| 3 | // See the LICENSE file in the project root for more information. | 
|---|
| 4 |  | 
|---|
| 5 | #ifndef DbgAppDomain_H | 
|---|
| 6 | #define DbgAppDomain_H | 
|---|
| 7 |  | 
|---|
| 8 | // Forward declaration | 
|---|
| 9 | class AppDomain; | 
|---|
| 10 |  | 
|---|
| 11 |  | 
|---|
| 12 | // AppDomainInfo contains information about an AppDomain | 
|---|
| 13 | // All pointers are for the left side, and we do not own any of the memory | 
|---|
| 14 | struct AppDomainInfo | 
|---|
| 15 | { | 
|---|
| 16 | ULONG       m_id;    // unique identifier | 
|---|
| 17 | int         m_iNameLengthInBytes; | 
|---|
| 18 | LPCWSTR     m_szAppDomainName; | 
|---|
| 19 | AppDomain  *m_pAppDomain; // only used by LS | 
|---|
| 20 |  | 
|---|
| 21 | // NOTE: These functions are just helpers and must not add a VTable | 
|---|
| 22 | // to this struct (since we need to read this out-of-proc) | 
|---|
| 23 |  | 
|---|
| 24 | // Provide a clean definition of an empty entry | 
|---|
| 25 | inline bool IsEmpty() const | 
|---|
| 26 | { | 
|---|
| 27 | return m_szAppDomainName == NULL; | 
|---|
| 28 | } | 
|---|
| 29 |  | 
|---|
| 30 | #ifndef RIGHT_SIDE_COMPILE | 
|---|
| 31 | // Mark this entry as empty. | 
|---|
| 32 | inline void FreeEntry() | 
|---|
| 33 | { | 
|---|
| 34 | m_szAppDomainName = NULL; | 
|---|
| 35 | } | 
|---|
| 36 |  | 
|---|
| 37 | // Set the string name and length. | 
|---|
| 38 | // If szName is null, it is adjusted to a global constant. | 
|---|
| 39 | // This also causes the entry to be considered valid | 
|---|
| 40 | inline void SetName(LPCWSTR szName) | 
|---|
| 41 | { | 
|---|
| 42 | if (szName != NULL) | 
|---|
| 43 | m_szAppDomainName = szName; | 
|---|
| 44 | else | 
|---|
| 45 | m_szAppDomainName = W( "<NoName>"); | 
|---|
| 46 |  | 
|---|
| 47 | m_iNameLengthInBytes = (int) (wcslen(m_szAppDomainName) + 1) * sizeof(WCHAR); | 
|---|
| 48 | } | 
|---|
| 49 | #endif | 
|---|
| 50 | }; | 
|---|
| 51 |  | 
|---|
| 52 | // Enforce the AppDomain IPC block binary layout doesn't change between versions. | 
|---|
| 53 | // Only an issue for x86 since that's the only platform w/ multiple versions. | 
|---|
| 54 | #if defined(DBG_TARGET_X86) | 
|---|
| 55 | static_assert_no_msg(offsetof(AppDomainInfo, m_id) == 0x0); | 
|---|
| 56 | static_assert_no_msg(offsetof(AppDomainInfo, m_iNameLengthInBytes) == 0x4); | 
|---|
| 57 | static_assert_no_msg(offsetof(AppDomainInfo, m_szAppDomainName) == 0x8); | 
|---|
| 58 | static_assert_no_msg(offsetof(AppDomainInfo, m_pAppDomain) == 0xc); | 
|---|
| 59 | #endif | 
|---|
| 60 |  | 
|---|
| 61 |  | 
|---|
| 62 |  | 
|---|
| 63 | // The RemoteHANDLE encapsulates the PAL specific handling of handles to avoid PAL specific ifdefs | 
|---|
| 64 | // everywhere else in the code. | 
|---|
| 65 | // There are two common initialization patterns: | 
|---|
| 66 | // | 
|---|
| 67 | // 1. Publishing of local handle for other processes, the value of the wrapper is a local handle | 
|---|
| 68 | //    in *this* process at the end: | 
|---|
| 69 | //    - In this process, call SetLocal(hHandle) to initialize the handle. | 
|---|
| 70 | //    - In the other processes, call DuplicateToLocalProcess to get a local copy of the handle. | 
|---|
| 71 | // | 
|---|
| 72 | // 2. Injecting of local handle into other process, the value of the wrapper is a local handle | 
|---|
| 73 | //    in the *other* process at the end: | 
|---|
| 74 | //    - In this process, call DuplicateToRemoteProcess(hProcess, hHandle) to initialize the handle. | 
|---|
| 75 | //    - In the other process, call ImportToOtherProcess() to finish the initialization of the wrapper | 
|---|
| 76 | //      with a local copy of the handle. | 
|---|
| 77 | // | 
|---|
| 78 | // Once initialized, the wrapper can be used the same way as a regular HANDLE in the process | 
|---|
| 79 | // it was initialized for. There is casting operator HANDLE to achieve that. | 
|---|
| 80 |  | 
|---|
| 81 | struct RemoteHANDLE { | 
|---|
| 82 | HANDLE              m_hLocal; | 
|---|
| 83 |  | 
|---|
| 84 | operator HANDLE& () | 
|---|
| 85 | { | 
|---|
| 86 | return m_hLocal; | 
|---|
| 87 | } | 
|---|
| 88 |  | 
|---|
| 89 | void Close() | 
|---|
| 90 | { | 
|---|
| 91 | HANDLE hHandle = m_hLocal; | 
|---|
| 92 | if (hHandle != NULL) { | 
|---|
| 93 | m_hLocal = NULL; | 
|---|
| 94 | CloseHandle(hHandle); | 
|---|
| 95 | } | 
|---|
| 96 | } | 
|---|
| 97 |  | 
|---|
| 98 | // Sets the local value of the handle. DuplicateToLocalProcess can be used later | 
|---|
| 99 | // by the remote process to acquire the remote handle. | 
|---|
| 100 | BOOL SetLocal(HANDLE hHandle) | 
|---|
| 101 | { | 
|---|
| 102 | m_hLocal = hHandle; | 
|---|
| 103 | return TRUE; | 
|---|
| 104 | } | 
|---|
| 105 |  | 
|---|
| 106 | // Duplicates the current handle value to remote process. ImportToLocalProcess | 
|---|
| 107 | // should be called in the remote process before the handle is used in the remote process. | 
|---|
| 108 | // NOTE: right now this is used for duplicating the debugger's process handle into the LS so | 
|---|
| 109 | //       that the LS can know when the RS has exited; thus we are only specifying SYNCHRONIZE | 
|---|
| 110 | //       access to mitigate any security concerns. | 
|---|
| 111 | BOOL DuplicateToRemoteProcess(HANDLE hProcess, HANDLE hHandle) | 
|---|
| 112 | { | 
|---|
| 113 | return DuplicateHandle(GetCurrentProcess(), hHandle, hProcess, &m_hLocal, | 
|---|
| 114 | SYNCHRONIZE, FALSE, 0); | 
|---|
| 115 | } | 
|---|
| 116 |  | 
|---|
| 117 | // Duplicates the current handle value to local process. To be used in combination with SetLocal. | 
|---|
| 118 | BOOL DuplicateToLocalProcess(HANDLE hProcess, HANDLE* pHandle) | 
|---|
| 119 | { | 
|---|
| 120 | return DuplicateHandle(hProcess, m_hLocal, GetCurrentProcess(), pHandle, | 
|---|
| 121 | NULL, FALSE, DUPLICATE_SAME_ACCESS); | 
|---|
| 122 | } | 
|---|
| 123 |  | 
|---|
| 124 | void CloseInRemoteProcess(HANDLE hProcess) | 
|---|
| 125 | { | 
|---|
| 126 | HANDLE hHandle = m_hLocal; | 
|---|
| 127 | m_hLocal = NULL; | 
|---|
| 128 |  | 
|---|
| 129 | HANDLE hTmp; | 
|---|
| 130 | if (DuplicateHandle(hProcess, hHandle, GetCurrentProcess(), &hTmp, | 
|---|
| 131 | NULL, FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE)) | 
|---|
| 132 | { | 
|---|
| 133 | CloseHandle(hTmp); | 
|---|
| 134 | } | 
|---|
| 135 | } | 
|---|
| 136 |  | 
|---|
| 137 | // Imports the handle to local process. To be used in combination with DuplicateToRemoteProcess. | 
|---|
| 138 | HANDLE ImportToLocalProcess() | 
|---|
| 139 | { | 
|---|
| 140 | return m_hLocal; | 
|---|
| 141 | } | 
|---|
| 142 | }; | 
|---|
| 143 |  | 
|---|
| 144 |  | 
|---|
| 145 | // AppDomain publishing server support: | 
|---|
| 146 | // Information about all appdomains in the process will be maintained | 
|---|
| 147 | // in the shared memory block for use by the debugger, etc. | 
|---|
| 148 | // This structure defines the layout of the information that will | 
|---|
| 149 | // be maintained. | 
|---|
| 150 | struct AppDomainEnumerationIPCBlock | 
|---|
| 151 | { | 
|---|
| 152 | // !!! The binary format of this layout must remain the same across versions so that | 
|---|
| 153 | // !!! a V2.0 publisher can inspect a v1.0 app. | 
|---|
| 154 |  | 
|---|
| 155 | // lock for serialization while manipulating AppDomain list. | 
|---|
| 156 | RemoteHANDLE        m_hMutex; | 
|---|
| 157 |  | 
|---|
| 158 | // Number of slots in AppDomainListElement array | 
|---|
| 159 | int                 m_iTotalSlots; | 
|---|
| 160 | int                 m_iNumOfUsedSlots; | 
|---|
| 161 | int                 m_iLastFreedSlot; | 
|---|
| 162 | int                 m_iSizeInBytes; // Size of AppDomainInfo in bytes | 
|---|
| 163 |  | 
|---|
| 164 | // We can use psapi!GetModuleFileNameEx to get the module name. | 
|---|
| 165 | // This provides an alternative. | 
|---|
| 166 | int                 m_iProcessNameLengthInBytes; | 
|---|
| 167 | WCHAR              *m_szProcessName; | 
|---|
| 168 |  | 
|---|
| 169 | AppDomainInfo      *m_rgListOfAppDomains; | 
|---|
| 170 | BOOL                m_fLockInvalid; | 
|---|
| 171 |  | 
|---|
| 172 |  | 
|---|
| 173 | #ifndef RIGHT_SIDE_COMPILE | 
|---|
| 174 | /************************************************************************* | 
|---|
| 175 | * Locks the list | 
|---|
| 176 | *************************************************************************/ | 
|---|
| 177 | BOOL Lock() | 
|---|
| 178 | { | 
|---|
| 179 | DWORD dwRes = WaitForSingleObject(m_hMutex, 3000); | 
|---|
| 180 | if (dwRes == WAIT_TIMEOUT) | 
|---|
| 181 | { | 
|---|
| 182 | // Nobody should get stuck holding this lock. | 
|---|
| 183 | // If we timeout on the wait, then either: | 
|---|
| 184 | // - it's a really bad race and somebody got preempted for a long time | 
|---|
| 185 | // - perhaps somebody's doing a DOS attack and holding onto the mutex. | 
|---|
| 186 | m_fLockInvalid = TRUE; | 
|---|
| 187 | } | 
|---|
| 188 |  | 
|---|
| 189 |  | 
|---|
| 190 | // The only time this can happen is if we're in shutdown and a thread | 
|---|
| 191 | // that held this lock is killed.  If this happens, assume that this | 
|---|
| 192 | // IPC block is in an invalid state and return FALSE to indicate | 
|---|
| 193 | // that people shouldn't do anything with the block anymore. | 
|---|
| 194 | if (dwRes == WAIT_ABANDONED) | 
|---|
| 195 | { | 
|---|
| 196 | m_fLockInvalid = TRUE; | 
|---|
| 197 | } | 
|---|
| 198 |  | 
|---|
| 199 | if (m_fLockInvalid) | 
|---|
| 200 | { | 
|---|
| 201 | Unlock(); | 
|---|
| 202 | } | 
|---|
| 203 |  | 
|---|
| 204 | return (dwRes == WAIT_OBJECT_0 && !m_fLockInvalid); | 
|---|
| 205 | } | 
|---|
| 206 |  | 
|---|
| 207 | /************************************************************************* | 
|---|
| 208 | * Unlocks the list | 
|---|
| 209 | *************************************************************************/ | 
|---|
| 210 | void Unlock() | 
|---|
| 211 | { | 
|---|
| 212 | // Lock may or may not be valid at this point. Thus Release may fail, | 
|---|
| 213 | // but we'll just ignore that. | 
|---|
| 214 | ReleaseMutex(m_hMutex); | 
|---|
| 215 | } | 
|---|
| 216 |  | 
|---|
| 217 | /************************************************************************* | 
|---|
| 218 | * Gets a free AppDomainInfo entry, and will allocate room if there are | 
|---|
| 219 | * no free slots left. | 
|---|
| 220 | *************************************************************************/ | 
|---|
| 221 | AppDomainInfo *GetFreeEntry() | 
|---|
| 222 | { | 
|---|
| 223 | // first check to see if there is space available. If not, then realloc. | 
|---|
| 224 | if (m_iNumOfUsedSlots == m_iTotalSlots) | 
|---|
| 225 | { | 
|---|
| 226 | // need to realloc | 
|---|
| 227 | AppDomainInfo *pTemp = | 
|---|
| 228 | new (nothrow) AppDomainInfo [m_iTotalSlots*2]; | 
|---|
| 229 |  | 
|---|
| 230 | if (pTemp == NULL) | 
|---|
| 231 | { | 
|---|
| 232 | return (NULL); | 
|---|
| 233 | } | 
|---|
| 234 |  | 
|---|
| 235 | memcpy (pTemp, m_rgListOfAppDomains, m_iSizeInBytes); | 
|---|
| 236 |  | 
|---|
| 237 | delete [] m_rgListOfAppDomains; | 
|---|
| 238 |  | 
|---|
| 239 | // Initialize the increased portion of the realloced memory | 
|---|
| 240 | int iNewSlotSize = m_iTotalSlots * 2; | 
|---|
| 241 |  | 
|---|
| 242 | for (int iIndex = m_iTotalSlots; iIndex < iNewSlotSize; iIndex++) | 
|---|
| 243 | pTemp[iIndex].FreeEntry(); | 
|---|
| 244 |  | 
|---|
| 245 | m_rgListOfAppDomains = pTemp; | 
|---|
| 246 | m_iTotalSlots = iNewSlotSize; | 
|---|
| 247 | m_iSizeInBytes *= 2; | 
|---|
| 248 | } | 
|---|
| 249 |  | 
|---|
| 250 | // Walk the list looking for an empty slot. Start from the last | 
|---|
| 251 | // one which was freed. | 
|---|
| 252 | { | 
|---|
| 253 | int i = m_iLastFreedSlot; | 
|---|
| 254 |  | 
|---|
| 255 | do | 
|---|
| 256 | { | 
|---|
| 257 | // Pointer to the entry being examined | 
|---|
| 258 | AppDomainInfo *pADInfo = &(m_rgListOfAppDomains[i]); | 
|---|
| 259 |  | 
|---|
| 260 | // is the slot available? | 
|---|
| 261 | if (pADInfo->IsEmpty()) | 
|---|
| 262 | return (pADInfo); | 
|---|
| 263 |  | 
|---|
| 264 | i = (i + 1) % m_iTotalSlots; | 
|---|
| 265 |  | 
|---|
| 266 | } while (i != m_iLastFreedSlot); | 
|---|
| 267 | } | 
|---|
| 268 |  | 
|---|
| 269 | _ASSERTE(! "ADInfo::GetFreeEntry: should never get here."); | 
|---|
| 270 | return (NULL); | 
|---|
| 271 | } | 
|---|
| 272 |  | 
|---|
| 273 | /************************************************************************* | 
|---|
| 274 | * Returns an AppDomainInfo slot to the free list. | 
|---|
| 275 | *************************************************************************/ | 
|---|
| 276 | void FreeEntry(AppDomainInfo *pADInfo) | 
|---|
| 277 | { | 
|---|
| 278 | _ASSERTE(pADInfo >= m_rgListOfAppDomains && | 
|---|
| 279 | pADInfo < m_rgListOfAppDomains + m_iSizeInBytes); | 
|---|
| 280 | _ASSERTE(((size_t)pADInfo - (size_t)m_rgListOfAppDomains) % | 
|---|
| 281 | sizeof(AppDomainInfo) == 0); | 
|---|
| 282 |  | 
|---|
| 283 | // Mark this slot as free | 
|---|
| 284 | pADInfo->FreeEntry(); | 
|---|
| 285 |  | 
|---|
| 286 | #ifdef _DEBUG | 
|---|
| 287 | memset(pADInfo, 0, sizeof(AppDomainInfo)); | 
|---|
| 288 | #endif | 
|---|
| 289 |  | 
|---|
| 290 | // decrement the used slot count | 
|---|
| 291 | m_iNumOfUsedSlots--; | 
|---|
| 292 |  | 
|---|
| 293 | // Save the last freed slot. | 
|---|
| 294 | m_iLastFreedSlot = (int)((size_t)pADInfo - (size_t)m_rgListOfAppDomains) / | 
|---|
| 295 | sizeof(AppDomainInfo); | 
|---|
| 296 | } | 
|---|
| 297 |  | 
|---|
| 298 | /************************************************************************* | 
|---|
| 299 | * Finds an AppDomainInfo entry corresponding to the AppDomain pointer. | 
|---|
| 300 | * Returns NULL if no such entry exists. | 
|---|
| 301 | *************************************************************************/ | 
|---|
| 302 | AppDomainInfo *FindEntry(AppDomain *pAD) | 
|---|
| 303 | { | 
|---|
| 304 | // Walk the list looking for a matching entry | 
|---|
| 305 | for (int i = 0; i < m_iTotalSlots; i++) | 
|---|
| 306 | { | 
|---|
| 307 | AppDomainInfo *pADInfo = &(m_rgListOfAppDomains[i]); | 
|---|
| 308 |  | 
|---|
| 309 | if (!pADInfo->IsEmpty() && | 
|---|
| 310 | pADInfo->m_pAppDomain == pAD) | 
|---|
| 311 | return pADInfo; | 
|---|
| 312 | } | 
|---|
| 313 |  | 
|---|
| 314 | return (NULL); | 
|---|
| 315 | } | 
|---|
| 316 |  | 
|---|
| 317 | /************************************************************************* | 
|---|
| 318 | * Returns the first AppDomainInfo entry in the list.  Returns NULL if | 
|---|
| 319 | * no such entry exists. | 
|---|
| 320 | *************************************************************************/ | 
|---|
| 321 | AppDomainInfo *FindFirst() | 
|---|
| 322 | { | 
|---|
| 323 | // Walk the list looking for a non-empty entry | 
|---|
| 324 | for (int i = 0; i < m_iTotalSlots; i++) | 
|---|
| 325 | { | 
|---|
| 326 | AppDomainInfo *pADInfo = &(m_rgListOfAppDomains[i]); | 
|---|
| 327 |  | 
|---|
| 328 | if (!pADInfo->IsEmpty()) | 
|---|
| 329 | return pADInfo; | 
|---|
| 330 | } | 
|---|
| 331 |  | 
|---|
| 332 | return (NULL); | 
|---|
| 333 | } | 
|---|
| 334 |  | 
|---|
| 335 | /************************************************************************* | 
|---|
| 336 | * Returns the next AppDomainInfo entry after pADInfo.  Returns NULL if | 
|---|
| 337 | * pADInfo was the last in the list. | 
|---|
| 338 | *************************************************************************/ | 
|---|
| 339 | AppDomainInfo *FindNext(AppDomainInfo *pADInfo) | 
|---|
| 340 | { | 
|---|
| 341 | _ASSERTE(pADInfo >= m_rgListOfAppDomains && | 
|---|
| 342 | pADInfo < m_rgListOfAppDomains + m_iSizeInBytes); | 
|---|
| 343 | _ASSERTE(((size_t)pADInfo - (size_t)m_rgListOfAppDomains) % | 
|---|
| 344 | sizeof(AppDomainInfo) == 0); | 
|---|
| 345 |  | 
|---|
| 346 | // Walk the list looking for the next non-empty entry | 
|---|
| 347 | for (int i = (int)((size_t)pADInfo - (size_t)m_rgListOfAppDomains) | 
|---|
| 348 | / sizeof(AppDomainInfo) + 1; | 
|---|
| 349 | i < m_iTotalSlots; | 
|---|
| 350 | i++) | 
|---|
| 351 | { | 
|---|
| 352 | AppDomainInfo *pADInfoTemp = &(m_rgListOfAppDomains[i]); | 
|---|
| 353 |  | 
|---|
| 354 | if (!pADInfoTemp->IsEmpty()) | 
|---|
| 355 | return pADInfoTemp; | 
|---|
| 356 | } | 
|---|
| 357 |  | 
|---|
| 358 | return (NULL); | 
|---|
| 359 | } | 
|---|
| 360 | #endif // RIGHT_SIDE_COMPILE | 
|---|
| 361 | }; | 
|---|
| 362 |  | 
|---|
| 363 | // Enforce the AppDomain IPC block binary layout doesn't change between versions. | 
|---|
| 364 | // Only an issue for x86 since that's the only platform w/ multiple versions. | 
|---|
| 365 | #if defined(DBG_TARGET_X86) | 
|---|
| 366 | static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_hMutex) == 0x0); | 
|---|
| 367 | static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_iTotalSlots) == 0x4); | 
|---|
| 368 | static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_iNumOfUsedSlots) == 0x8); | 
|---|
| 369 | static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_iLastFreedSlot) == 0xc); | 
|---|
| 370 | static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_iSizeInBytes) == 0x10); | 
|---|
| 371 | static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_iProcessNameLengthInBytes) == 0x14); | 
|---|
| 372 | static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_szProcessName) == 0x18); | 
|---|
| 373 | static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_rgListOfAppDomains) == 0x1c); | 
|---|
| 374 | static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_fLockInvalid) == 0x20); | 
|---|
| 375 | #endif | 
|---|
| 376 |  | 
|---|
| 377 | #endif //DbgAppDomain_H | 
|---|
| 378 |  | 
|---|
| 379 |  | 
|---|
| 380 |  | 
|---|
| 381 |  | 
|---|