| 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 |  |