| 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 | // File: RCThread.cpp |
| 6 | // |
| 7 | |
| 8 | // |
| 9 | // Runtime Controller Thread |
| 10 | // |
| 11 | //***************************************************************************** |
| 12 | |
| 13 | #include "stdafx.h" |
| 14 | #include "threadsuspend.h" |
| 15 | #ifndef FEATURE_PAL |
| 16 | |
| 17 | #include "securitywrapper.h" |
| 18 | #endif |
| 19 | #include <aclapi.h> |
| 20 | #include <hosting.h> |
| 21 | |
| 22 | #include "eemessagebox.h" |
| 23 | #include "genericstackprobe.h" |
| 24 | |
| 25 | #ifndef SM_REMOTESESSION |
| 26 | #define SM_REMOTESESSION 0x1000 |
| 27 | #endif |
| 28 | |
| 29 | #include <limits.h> |
| 30 | |
| 31 | #ifdef _DEBUG |
| 32 | // Declare statics |
| 33 | EEThreadId DebuggerRCThread::s_DbgHelperThreadId; |
| 34 | #endif |
| 35 | |
| 36 | // |
| 37 | // Constructor |
| 38 | // |
| 39 | DebuggerRCThread::DebuggerRCThread(Debugger * pDebugger) |
| 40 | : m_debugger(pDebugger), |
| 41 | m_pDCB(NULL), |
| 42 | m_thread(NULL), |
| 43 | m_run(true), |
| 44 | m_threadControlEvent(NULL), |
| 45 | m_helperThreadCanGoEvent(NULL), |
| 46 | m_fDetachRightSide(false) |
| 47 | { |
| 48 | CONTRACTL |
| 49 | { |
| 50 | SO_INTOLERANT; |
| 51 | WRAPPER(THROWS); |
| 52 | GC_NOTRIGGER; |
| 53 | CONSTRUCTOR_CHECK; |
| 54 | } |
| 55 | CONTRACTL_END; |
| 56 | |
| 57 | _ASSERTE(pDebugger != NULL); |
| 58 | |
| 59 | for( int i = 0; i < IPC_TARGET_COUNT;i++) |
| 60 | { |
| 61 | m_rgfInitRuntimeOffsets[i] = true; |
| 62 | } |
| 63 | |
| 64 | // Initialize this here because we Destroy it in the DTOR. |
| 65 | // Note that this function can't fail. |
| 66 | } |
| 67 | |
| 68 | |
| 69 | // |
| 70 | // Destructor. Cleans up all of the open handles the RC thread uses. |
| 71 | // This expects that the RC thread has been stopped and has terminated |
| 72 | // before being called. |
| 73 | // |
| 74 | DebuggerRCThread::~DebuggerRCThread() |
| 75 | { |
| 76 | CONTRACTL |
| 77 | { |
| 78 | SO_INTOLERANT; |
| 79 | NOTHROW; |
| 80 | GC_NOTRIGGER; |
| 81 | DESTRUCTOR_CHECK; |
| 82 | } |
| 83 | CONTRACTL_END; |
| 84 | |
| 85 | LOG((LF_CORDB,LL_INFO1000, "DebuggerRCThread::~DebuggerRCThread\n" )); |
| 86 | |
| 87 | // We explicitly leak the debugger object on shutdown. See Debugger::StopDebugger for details. |
| 88 | _ASSERTE(!"RCThread dtor should not be called." ); |
| 89 | } |
| 90 | |
| 91 | |
| 92 | |
| 93 | //--------------------------------------------------------------------------------------- |
| 94 | // |
| 95 | // Close the IPC events associated with a debugger connection |
| 96 | // |
| 97 | // Notes: |
| 98 | // The only IPC connection supported is OOP. |
| 99 | // |
| 100 | //--------------------------------------------------------------------------------------- |
| 101 | void DebuggerRCThread::CloseIPCHandles() |
| 102 | { |
| 103 | CONTRACTL |
| 104 | { |
| 105 | SO_NOT_MAINLINE; |
| 106 | NOTHROW; |
| 107 | GC_NOTRIGGER; |
| 108 | } |
| 109 | CONTRACTL_END; |
| 110 | |
| 111 | if( m_pDCB != NULL) |
| 112 | { |
| 113 | m_pDCB->m_rightSideProcessHandle.Close(); |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | //----------------------------------------------------------------------------- |
| 118 | // Helper to get the proper decorated name |
| 119 | // Caller ensures that pBufSize is large enough. We'll assert just to check, |
| 120 | // but no runtime failure. |
| 121 | // pBuf - the output buffer to write the decorated name in |
| 122 | // cBufSizeInChars - the size of the buffer in characters, including the null. |
| 123 | // pPrefx - The undecorated name of the event. |
| 124 | //----------------------------------------------------------------------------- |
| 125 | void GetPidDecoratedName(__out_ecount(cBufSizeInChars) WCHAR * pBuf, |
| 126 | int cBufSizeInChars, |
| 127 | const WCHAR * pPrefix) |
| 128 | { |
| 129 | LIMITED_METHOD_CONTRACT; |
| 130 | |
| 131 | DWORD pid = GetCurrentProcessId(); |
| 132 | |
| 133 | GetPidDecoratedName(pBuf, cBufSizeInChars, pPrefix, pid); |
| 134 | } |
| 135 | |
| 136 | |
| 137 | |
| 138 | |
| 139 | //----------------------------------------------------------------------------- |
| 140 | // Simple wrapper to create win32 events. |
| 141 | // This helps make DebuggerRCThread::Init pretty, beccause we |
| 142 | // create lots of events there. |
| 143 | // These will either: |
| 144 | // 1) Create/Open and return an event |
| 145 | // 2) or throw an exception. |
| 146 | // @todo - should these be CLREvents? ClrCreateManualEvent / ClrCreateAutoEvent |
| 147 | //----------------------------------------------------------------------------- |
| 148 | HANDLE CreateWin32EventOrThrow( |
| 149 | LPSECURITY_ATTRIBUTES lpEventAttributes, |
| 150 | EEventResetType eType, |
| 151 | BOOL bInitialState |
| 152 | ) |
| 153 | { |
| 154 | CONTRACT(HANDLE) |
| 155 | { |
| 156 | THROWS; |
| 157 | GC_NOTRIGGER; |
| 158 | PRECONDITION(CheckPointer(lpEventAttributes, NULL_OK)); |
| 159 | POSTCONDITION(RETVAL != NULL); |
| 160 | } |
| 161 | CONTRACT_END; |
| 162 | |
| 163 | HANDLE h = NULL; |
| 164 | h = WszCreateEvent(lpEventAttributes, (BOOL) eType, bInitialState, NULL); |
| 165 | |
| 166 | if (h == NULL) |
| 167 | ThrowLastError(); |
| 168 | |
| 169 | RETURN h; |
| 170 | } |
| 171 | |
| 172 | //----------------------------------------------------------------------------- |
| 173 | // Open an event. Another helper for DebuggerRCThread::Init |
| 174 | //----------------------------------------------------------------------------- |
| 175 | HANDLE OpenWin32EventOrThrow( |
| 176 | DWORD dwDesiredAccess, |
| 177 | BOOL bInheritHandle, |
| 178 | LPCWSTR lpName |
| 179 | ) |
| 180 | { |
| 181 | CONTRACT(HANDLE) |
| 182 | { |
| 183 | THROWS; |
| 184 | GC_NOTRIGGER; |
| 185 | POSTCONDITION(RETVAL != NULL); |
| 186 | } |
| 187 | CONTRACT_END; |
| 188 | |
| 189 | HANDLE h = WszOpenEvent( |
| 190 | dwDesiredAccess, |
| 191 | bInheritHandle, |
| 192 | lpName |
| 193 | ); |
| 194 | if (h == NULL) |
| 195 | ThrowLastError(); |
| 196 | |
| 197 | RETURN h; |
| 198 | } |
| 199 | |
| 200 | //--------------------------------------------------------------------------------------- |
| 201 | // |
| 202 | // Init |
| 203 | // |
| 204 | // Initialize the IPC block. |
| 205 | // |
| 206 | // Arguments: |
| 207 | // hRsea - Handle to Right-Side Event Available event. |
| 208 | // hRser - Handle to Right-Side Event Read event. |
| 209 | // hLsea - Handle to Left-Side Event Available event. |
| 210 | // hLser - Handle to Left-Side Event Read event. |
| 211 | // hLsuwe - Handle to Left-Side unmanaged wait event. |
| 212 | // |
| 213 | // Notes: |
| 214 | // The Init method works since there are no virtual functions - don't add any virtual functions without |
| 215 | // changing this! |
| 216 | // We assume ownership of the handles as soon as we're called; regardless of our success. |
| 217 | // On failure, we throw. |
| 218 | // Initialization of the debugger control block occurs partly on the left side and partly on |
| 219 | // the right side. This initialization occurs in parallel, so it's unsafe to make assumptions about |
| 220 | // the order in which the fields will be initialized. |
| 221 | // |
| 222 | // |
| 223 | //--------------------------------------------------------------------------------------- |
| 224 | HRESULT DebuggerIPCControlBlock::Init( |
| 225 | HANDLE hRsea, |
| 226 | HANDLE hRser, |
| 227 | HANDLE hLsea, |
| 228 | HANDLE hLser, |
| 229 | HANDLE hLsuwe |
| 230 | ) |
| 231 | { |
| 232 | CONTRACTL |
| 233 | { |
| 234 | SO_INTOLERANT; |
| 235 | THROWS; |
| 236 | GC_NOTRIGGER; |
| 237 | } |
| 238 | CONTRACTL_END; |
| 239 | |
| 240 | // NOTE this works since there are no virtual functions - don't add any without changing this! |
| 241 | // Although we assume the IPC block is zero-initialized by the OS upon creation, we still need to clear |
| 242 | // the memory here to protect ourselves from DOS attack. One scenario is when a malicious debugger |
| 243 | // pre-creates a bogus IPC block. This means that our synchronization scheme won't work in DOS |
| 244 | // attack scenarios, but we will be messed up anyway. |
| 245 | // WARNING!!! m_DCBSize is used as a semaphore and is set to non-zero to signal that initialization of the |
| 246 | // WARNING!!! DCB is complete. if you remove the below memset be sure to initialize m_DCBSize to zero in the ctor! |
| 247 | memset( this, 0, sizeof( DebuggerIPCControlBlock) ); |
| 248 | |
| 249 | // Setup version checking info. |
| 250 | m_verMajor = CLR_BUILD_VERSION; |
| 251 | m_verMinor = CLR_BUILD_VERSION_QFE; |
| 252 | |
| 253 | #ifdef _DEBUG |
| 254 | m_checkedBuild = true; |
| 255 | #else |
| 256 | m_checkedBuild = false; |
| 257 | #endif |
| 258 | m_bHostingInFiber = false; |
| 259 | |
| 260 | // Are we in fiber mode? In Whidbey, we do not support launch a fiber mode process |
| 261 | // nor do we support attach to a fiber mode process. |
| 262 | // |
| 263 | if (g_CORDebuggerControlFlags & DBCF_FIBERMODE) |
| 264 | { |
| 265 | m_bHostingInFiber = true; |
| 266 | } |
| 267 | |
| 268 | #if !defined(FEATURE_DBGIPC_TRANSPORT_VM) |
| 269 | // Copy RSEA and RSER into the control block. |
| 270 | if (!m_rightSideEventAvailable.SetLocal(hRsea)) |
| 271 | { |
| 272 | ThrowLastError(); |
| 273 | } |
| 274 | |
| 275 | if (!m_rightSideEventRead.SetLocal(hRser)) |
| 276 | { |
| 277 | ThrowLastError(); |
| 278 | } |
| 279 | |
| 280 | if (!m_leftSideUnmanagedWaitEvent.SetLocal(hLsuwe)) |
| 281 | { |
| 282 | ThrowLastError(); |
| 283 | } |
| 284 | #endif // !FEATURE_DBGIPC_TRANSPORT_VM |
| 285 | |
| 286 | |
| 287 | // Mark the debugger special thread list as not dirty, empty and null. |
| 288 | m_specialThreadListDirty = false; |
| 289 | m_specialThreadListLength = 0; |
| 290 | m_specialThreadList = NULL; |
| 291 | |
| 292 | m_shutdownBegun = false; |
| 293 | |
| 294 | return S_OK; |
| 295 | } |
| 296 | |
| 297 | void DebuggerRCThread::WatchForStragglers(void) |
| 298 | { |
| 299 | WRAPPER_NO_CONTRACT; |
| 300 | |
| 301 | _ASSERTE(m_threadControlEvent != NULL); |
| 302 | LOG((LF_CORDB,LL_INFO100000, "DRCT::WFS:setting event to watch " |
| 303 | "for stragglers\n" )); |
| 304 | |
| 305 | SetEvent(m_threadControlEvent); |
| 306 | } |
| 307 | |
| 308 | //--------------------------------------------------------------------------------------- |
| 309 | // |
| 310 | // Init sets up all the objects that the RC thread will need to run. |
| 311 | // |
| 312 | // |
| 313 | // Return Value: |
| 314 | // S_OK on success. May also throw. |
| 315 | // |
| 316 | // Assumptions: |
| 317 | // Called during startup, even if we're not debugging. |
| 318 | // |
| 319 | // |
| 320 | //--------------------------------------------------------------------------------------- |
| 321 | HRESULT DebuggerRCThread::Init(void) |
| 322 | { |
| 323 | CONTRACTL |
| 324 | { |
| 325 | SO_INTOLERANT; |
| 326 | THROWS; |
| 327 | GC_NOTRIGGER; |
| 328 | PRECONDITION(!ThisIsHelperThreadWorker()); // initialized by main thread |
| 329 | } |
| 330 | CONTRACTL_END; |
| 331 | |
| 332 | |
| 333 | LOG((LF_CORDB, LL_EVERYTHING, "DebuggerRCThreadInit called\n" )); |
| 334 | |
| 335 | DWORD dwStatus; |
| 336 | if (m_debugger == NULL) |
| 337 | { |
| 338 | ThrowHR(E_INVALIDARG); |
| 339 | } |
| 340 | |
| 341 | // Init should only be called once. |
| 342 | if (g_pRCThread != NULL) |
| 343 | { |
| 344 | ThrowHR(E_FAIL); |
| 345 | } |
| 346 | |
| 347 | g_pRCThread = this; |
| 348 | |
| 349 | m_favorData.Init(); // throws |
| 350 | |
| 351 | |
| 352 | // Create the thread control event. |
| 353 | m_threadControlEvent = CreateWin32EventOrThrow(NULL, kAutoResetEvent, FALSE); |
| 354 | |
| 355 | // Create the helper thread can go event. |
| 356 | m_helperThreadCanGoEvent = CreateWin32EventOrThrow(NULL, kManualResetEvent, TRUE); |
| 357 | |
| 358 | m_pDCB = new(nothrow) DebuggerIPCControlBlock; |
| 359 | |
| 360 | // Don't fail out because the shared memory failed to create |
| 361 | #if _DEBUG |
| 362 | if (m_pDCB == NULL) |
| 363 | { |
| 364 | LOG((LF_CORDB, LL_INFO10000, |
| 365 | "DRCT::I: Failed to get Debug IPC block.\n" )); |
| 366 | } |
| 367 | #endif // _DEBUG |
| 368 | |
| 369 | HRESULT hr; |
| 370 | |
| 371 | #if defined(FEATURE_DBGIPC_TRANSPORT_VM) |
| 372 | |
| 373 | if (m_pDCB) |
| 374 | { |
| 375 | hr = m_pDCB->Init(NULL, NULL, NULL, NULL, NULL); |
| 376 | _ASSERTE(SUCCEEDED(hr)); // throws on error. |
| 377 | } |
| 378 | #else //FEATURE_DBGIPC_TRANSPORT_VM |
| 379 | |
| 380 | // Create the events that the thread will need to receive events |
| 381 | // from the out of process piece on the right side. |
| 382 | // We will not fail out if CreateEvent fails for RSEA or RSER. Because |
| 383 | // the worst case is that debugger cannot attach to debuggee. |
| 384 | // |
| 385 | HandleHolder rightSideEventAvailable(WszCreateEvent(NULL, (BOOL) kAutoResetEvent, FALSE, NULL)); |
| 386 | |
| 387 | // Security fix: |
| 388 | // We need to check the last error to see if the event was precreated or not |
| 389 | // If so, we need to release the handle right now. |
| 390 | // |
| 391 | dwStatus = GetLastError(); |
| 392 | if (dwStatus == ERROR_ALREADY_EXISTS) |
| 393 | { |
| 394 | // clean up the handle now |
| 395 | rightSideEventAvailable.Clear(); |
| 396 | } |
| 397 | |
| 398 | HandleHolder rightSideEventRead(WszCreateEvent(NULL, (BOOL) kAutoResetEvent, FALSE, NULL)); |
| 399 | |
| 400 | // Security fix: |
| 401 | // We need to check the last error to see if the event was precreated or not |
| 402 | // If so, we need to release the handle right now. |
| 403 | // |
| 404 | dwStatus = GetLastError(); |
| 405 | if (dwStatus == ERROR_ALREADY_EXISTS) |
| 406 | { |
| 407 | // clean up the handle now |
| 408 | rightSideEventRead.Clear(); |
| 409 | } |
| 410 | |
| 411 | |
| 412 | HandleHolder leftSideUnmanagedWaitEvent(CreateWin32EventOrThrow(NULL, kManualResetEvent, FALSE)); |
| 413 | |
| 414 | // Copy RSEA and RSER into the control block only if shared memory is created without error. |
| 415 | if (m_pDCB) |
| 416 | { |
| 417 | // Since Init() gets ownership of handles as soon as it's called, we can |
| 418 | // release our ownership now. |
| 419 | rightSideEventAvailable.SuppressRelease(); |
| 420 | rightSideEventRead.SuppressRelease(); |
| 421 | leftSideUnmanagedWaitEvent.SuppressRelease(); |
| 422 | |
| 423 | // NOTE: initialization of the debugger control block occurs partly on the left side and partly on |
| 424 | // the right side. This initialization occurs in parallel, so it's unsafe to make assumptions about |
| 425 | // the order in which the fields will be initialized. |
| 426 | hr = m_pDCB->Init(rightSideEventAvailable, |
| 427 | rightSideEventRead, |
| 428 | NULL, |
| 429 | NULL, |
| 430 | leftSideUnmanagedWaitEvent); |
| 431 | |
| 432 | _ASSERTE(SUCCEEDED(hr)); // throws on error. |
| 433 | } |
| 434 | #endif //FEATURE_DBGIPC_TRANSPORT_VM |
| 435 | |
| 436 | if(m_pDCB) |
| 437 | { |
| 438 | // We have to ensure that most of the runtime offsets for the out-of-proc DCB are initialized right away. This is |
| 439 | // needed to support certian races during an interop attach. Since we can't know whether an interop attach will ever |
| 440 | // happen or not, we are forced to do this now. Note: this is really too early, as some data structures haven't been |
| 441 | // initialized yet! |
| 442 | hr = EnsureRuntimeOffsetsInit(IPC_TARGET_OUTOFPROC); |
| 443 | _ASSERTE(SUCCEEDED(hr)); // throw on error |
| 444 | |
| 445 | // Note: we have to mark that we need the runtime offsets re-initialized for the out-of-proc DCB. This is because |
| 446 | // things like the patch table aren't initialized yet. Calling NeedRuntimeOffsetsReInit() ensures that this happens |
| 447 | // before we really need the patch table. |
| 448 | NeedRuntimeOffsetsReInit(IPC_TARGET_OUTOFPROC); |
| 449 | |
| 450 | m_pDCB->m_helperThreadStartAddr = (void *) DebuggerRCThread::ThreadProcStatic; |
| 451 | m_pDCB->m_helperRemoteStartAddr = (void *) DebuggerRCThread::ThreadProcRemote; |
| 452 | m_pDCB->m_leftSideProtocolCurrent = CorDB_LeftSideProtocolCurrent; |
| 453 | m_pDCB->m_leftSideProtocolMinSupported = CorDB_LeftSideProtocolMinSupported; |
| 454 | |
| 455 | LOG((LF_CORDB, LL_INFO10, |
| 456 | "DRCT::I: version info: %d.%d.%d current protocol=%d, min protocol=%d\n" , |
| 457 | m_pDCB->m_verMajor, |
| 458 | m_pDCB->m_verMinor, |
| 459 | m_pDCB->m_checkedBuild, |
| 460 | m_pDCB->m_leftSideProtocolCurrent, |
| 461 | m_pDCB->m_leftSideProtocolMinSupported)); |
| 462 | |
| 463 | // Left-side always creates helper-thread. |
| 464 | // @dbgtodo inspection - by end of V3, LS will never create helper-thread :) |
| 465 | m_pDCB->m_rightSideShouldCreateHelperThread = false; |
| 466 | |
| 467 | // m_DCBSize is used as a semaphore to indicate that the DCB is fully initialized. |
| 468 | // let's ensure that it's updated after all the other fields. |
| 469 | MemoryBarrier(); |
| 470 | m_pDCB->m_DCBSize = sizeof(DebuggerIPCControlBlock); |
| 471 | } |
| 472 | |
| 473 | return S_OK; |
| 474 | } |
| 475 | |
| 476 | #ifndef FEATURE_PAL |
| 477 | |
| 478 | // This function is used to verify the security descriptor on an event |
| 479 | // matches our expectation to prevent attack. This should be called when |
| 480 | // we opened an event by name and assumed that the RS creates the event. |
| 481 | // That means the event's dacl should match our default policy - current user |
| 482 | // and admin. It can be narrower. By default, the DACL looks like the debugger |
| 483 | // process user, debuggee user, and admin. |
| 484 | // |
| 485 | HRESULT DebuggerRCThread::VerifySecurityOnRSCreatedEvents( |
| 486 | HANDLE sse, |
| 487 | HANDLE lsea, |
| 488 | HANDLE lser) |
| 489 | { |
| 490 | |
| 491 | CONTRACTL |
| 492 | { |
| 493 | SO_NOT_MAINLINE; |
| 494 | NOTHROW; |
| 495 | GC_NOTRIGGER; |
| 496 | } |
| 497 | CONTRACTL_END; |
| 498 | |
| 499 | STRESS_LOG0(LF_CORDB,LL_INFO1000,"DRCT::VerifySecurityOnRSCreatedEvents\n" ); |
| 500 | |
| 501 | if (lsea == NULL || lser == NULL) |
| 502 | { |
| 503 | // no valid handle, does not need to verify. |
| 504 | // The caller will close the handles |
| 505 | return E_FAIL; |
| 506 | } |
| 507 | |
| 508 | HRESULT hr = S_OK; |
| 509 | |
| 510 | SIZE_T i; |
| 511 | ACCESS_ALLOWED_ACE *pAllowAceSSE = NULL; |
| 512 | ACCESS_ALLOWED_ACE *pAllowAceLSEA = NULL; |
| 513 | ACCESS_ALLOWED_ACE *pAllowAceLSER = NULL; |
| 514 | |
| 515 | |
| 516 | EX_TRY |
| 517 | { |
| 518 | // Get security descriptors for the handles. |
| 519 | Win32SecurityDescriptor sdSSE; |
| 520 | sdSSE.InitFromHandle(sse); |
| 521 | |
| 522 | Win32SecurityDescriptor sdLSEA; |
| 523 | sdLSEA.InitFromHandle(lsea); |
| 524 | |
| 525 | Win32SecurityDescriptor sdLSER; |
| 526 | sdLSER.InitFromHandle(lser); |
| 527 | |
| 528 | |
| 529 | |
| 530 | |
| 531 | // Make sure all 3 have the same creator |
| 532 | // We've already verifed in CreateSetupSyncEvent that the SSE's owner is in the DACL. |
| 533 | if (!Sid::Equals(sdSSE.GetOwner(), sdLSEA.GetOwner()) || |
| 534 | !Sid::Equals(sdSSE.GetOwner(), sdLSER.GetOwner())) |
| 535 | { |
| 536 | // Not equal! return now with failure code. |
| 537 | STRESS_LOG1(LF_CORDB,LL_INFO1000,"DRCT::VSORSCE failed on EqualSid - 0x%08x\n" , hr); |
| 538 | ThrowHR(E_FAIL); |
| 539 | } |
| 540 | |
| 541 | // DACL_SECURITY_INFORMATION |
| 542 | // Now verify the DACL. It should be only two of them at most. One of them is the |
| 543 | // target process SID. |
| 544 | Dacl daclSSE = sdSSE.GetDacl(); |
| 545 | Dacl daclLSEA = sdLSEA.GetDacl(); |
| 546 | Dacl daclLSER = sdLSER.GetDacl(); |
| 547 | |
| 548 | |
| 549 | // Now all of these three ACL should be alike. There should be at most two of entries |
| 550 | // there. One if the debugger process's SID and one if debuggee sid. |
| 551 | if ((daclSSE.GetAceCount() != 1) && (daclSSE.GetAceCount() != 2)) |
| 552 | { |
| 553 | ThrowHR(E_FAIL); |
| 554 | } |
| 555 | |
| 556 | |
| 557 | // All of the ace count should equal for all events. |
| 558 | if ((daclSSE.GetAceCount() != daclLSEA.GetAceCount()) || |
| 559 | (daclSSE.GetAceCount() != daclLSER.GetAceCount())) |
| 560 | { |
| 561 | ThrowHR(E_FAIL); |
| 562 | } |
| 563 | |
| 564 | // Now check the ACE inside.These should be all equal |
| 565 | for (i = 0; i < daclSSE.GetAceCount(); i++) |
| 566 | { |
| 567 | ACE_HEADER *pAce; |
| 568 | |
| 569 | // Get the ace from the SSE |
| 570 | pAce = daclSSE.GetAce(i); |
| 571 | if (pAce->AceType != ACCESS_ALLOWED_ACE_TYPE) |
| 572 | { |
| 573 | ThrowHR(E_FAIL); |
| 574 | } |
| 575 | pAllowAceSSE = (ACCESS_ALLOWED_ACE*)pAce; |
| 576 | |
| 577 | // Get the ace from LSEA |
| 578 | pAce = daclLSEA.GetAce(i); |
| 579 | if (pAce->AceType != ACCESS_ALLOWED_ACE_TYPE) |
| 580 | { |
| 581 | ThrowHR(E_FAIL); |
| 582 | } |
| 583 | pAllowAceLSEA = (ACCESS_ALLOWED_ACE*)pAce; |
| 584 | |
| 585 | // This is the SID |
| 586 | // We can call EqualSid on this pAllowAce->SidStart |
| 587 | if (EqualSid((PSID)&(pAllowAceSSE->SidStart), (PSID)&(pAllowAceLSEA->SidStart)) == FALSE) |
| 588 | { |
| 589 | // ACE not equal. Fail out. |
| 590 | ThrowHR(E_FAIL); |
| 591 | } |
| 592 | |
| 593 | // Get the ace from LSER |
| 594 | pAce = daclLSER.GetAce(i); |
| 595 | if (pAce->AceType != ACCESS_ALLOWED_ACE_TYPE) |
| 596 | { |
| 597 | ThrowHR(E_FAIL); |
| 598 | } |
| 599 | pAllowAceLSER = (ACCESS_ALLOWED_ACE*)pAce; |
| 600 | |
| 601 | if (EqualSid((PSID)&(pAllowAceSSE->SidStart), (PSID)&(pAllowAceLSER->SidStart)) == FALSE) |
| 602 | { |
| 603 | // ACE not equal. Fail out. |
| 604 | ThrowHR(E_FAIL); |
| 605 | } |
| 606 | } // end for loop. |
| 607 | |
| 608 | |
| 609 | // The last ACE should be target process. That is it should be |
| 610 | // our process's sid! |
| 611 | // |
| 612 | if (pAllowAceLSER == NULL) |
| 613 | { |
| 614 | ThrowHR(E_FAIL);; // fail if we don't have the ACE. |
| 615 | } |
| 616 | { |
| 617 | SidBuffer sbCurrentProcess; |
| 618 | sbCurrentProcess.InitFromProcess(GetCurrentProcessId()); |
| 619 | if (!Sid::Equals(sbCurrentProcess.GetSid(), (PSID)&(pAllowAceLSER->SidStart))) |
| 620 | { |
| 621 | ThrowHR(E_FAIL); |
| 622 | } |
| 623 | } |
| 624 | } |
| 625 | EX_CATCH |
| 626 | { |
| 627 | // If we threw an exception, then the verification failed. |
| 628 | hr = E_FAIL; |
| 629 | } |
| 630 | EX_END_CATCH(RethrowTerminalExceptions); |
| 631 | |
| 632 | if (FAILED(hr)) |
| 633 | { |
| 634 | STRESS_LOG1(LF_CORDB,LL_INFO1000,"DRCT::VSORSCE failed with - 0x%08x\n" , hr); |
| 635 | } |
| 636 | |
| 637 | return hr; |
| 638 | } |
| 639 | |
| 640 | #endif // FEATURE_PAL |
| 641 | |
| 642 | //--------------------------------------------------------------------------------------- |
| 643 | // |
| 644 | // Setup the Runtime Offsets struct. |
| 645 | // |
| 646 | // Arguments: |
| 647 | // pDebuggerIPCControlBlock - Pointer to the debugger's portion of the IPC |
| 648 | // block, which this routine will write into the offsets of various parts of |
| 649 | // the runtime. |
| 650 | // |
| 651 | // Return Value: |
| 652 | // S_OK on success. |
| 653 | // |
| 654 | //--------------------------------------------------------------------------------------- |
| 655 | HRESULT DebuggerRCThread::SetupRuntimeOffsets(DebuggerIPCControlBlock * pDebuggerIPCControlBlock) |
| 656 | { |
| 657 | CONTRACTL |
| 658 | { |
| 659 | SO_INTOLERANT; |
| 660 | NOTHROW; |
| 661 | GC_NOTRIGGER; |
| 662 | |
| 663 | PRECONDITION(ThisMaybeHelperThread()); |
| 664 | } |
| 665 | CONTRACTL_END; |
| 666 | |
| 667 | // Allocate the struct if needed. We just fill in any existing one. |
| 668 | DebuggerIPCRuntimeOffsets * pDebuggerRuntimeOffsets = pDebuggerIPCControlBlock->m_pRuntimeOffsets; |
| 669 | |
| 670 | if (pDebuggerRuntimeOffsets == NULL) |
| 671 | { |
| 672 | // Perhaps we should preallocate this. This is the only allocation |
| 673 | // that would force SendIPCEvent to throw an exception. It'd be very |
| 674 | // nice to have |
| 675 | CONTRACT_VIOLATION(ThrowsViolation); |
| 676 | pDebuggerRuntimeOffsets = new DebuggerIPCRuntimeOffsets(); |
| 677 | _ASSERTE(pDebuggerRuntimeOffsets != NULL); // throws on oom |
| 678 | } |
| 679 | |
| 680 | // Fill out the struct. |
| 681 | #ifdef FEATURE_INTEROP_DEBUGGING |
| 682 | pDebuggerRuntimeOffsets->m_genericHijackFuncAddr = Debugger::GenericHijackFunc; |
| 683 | // Set flares - these only exist for interop debugging. |
| 684 | pDebuggerRuntimeOffsets->m_signalHijackStartedBPAddr = (void*) SignalHijackStartedFlare; |
| 685 | pDebuggerRuntimeOffsets->m_excepForRuntimeHandoffStartBPAddr = (void*) ExceptionForRuntimeHandoffStartFlare; |
| 686 | pDebuggerRuntimeOffsets->m_excepForRuntimeHandoffCompleteBPAddr = (void*) ExceptionForRuntimeHandoffCompleteFlare; |
| 687 | pDebuggerRuntimeOffsets->m_signalHijackCompleteBPAddr = (void*) SignalHijackCompleteFlare; |
| 688 | pDebuggerRuntimeOffsets->m_excepNotForRuntimeBPAddr = (void*) ExceptionNotForRuntimeFlare; |
| 689 | pDebuggerRuntimeOffsets->m_notifyRSOfSyncCompleteBPAddr = (void*) NotifyRightSideOfSyncCompleteFlare; |
| 690 | pDebuggerRuntimeOffsets->m_debuggerWordTLSIndex = g_debuggerWordTLSIndex; |
| 691 | |
| 692 | #if !defined(FEATURE_CORESYSTEM) |
| 693 | // Grab the address of RaiseException in kernel32 because we have to play some games with exceptions |
| 694 | // that are generated there (just another reason why mixed mode debugging is shady). See bug 476768. |
| 695 | HMODULE hModule = WszGetModuleHandle(W("kernel32.dll" )); |
| 696 | _ASSERTE(hModule != NULL); |
| 697 | PREFAST_ASSUME(hModule != NULL); |
| 698 | pDebuggerRuntimeOffsets->m_raiseExceptionAddr = GetProcAddress(hModule, "RaiseException" ); |
| 699 | _ASSERTE(pDebuggerRuntimeOffsets->m_raiseExceptionAddr != NULL); |
| 700 | hModule = NULL; |
| 701 | #else |
| 702 | pDebuggerRuntimeOffsets->m_raiseExceptionAddr = NULL; |
| 703 | #endif |
| 704 | #endif // FEATURE_INTEROP_DEBUGGING |
| 705 | |
| 706 | pDebuggerRuntimeOffsets->m_pPatches = DebuggerController::GetPatchTable(); |
| 707 | pDebuggerRuntimeOffsets->m_pPatchTableValid = (BOOL*)DebuggerController::GetPatchTableValidAddr(); |
| 708 | pDebuggerRuntimeOffsets->m_offRgData = DebuggerPatchTable::GetOffsetOfEntries(); |
| 709 | pDebuggerRuntimeOffsets->m_offCData = DebuggerPatchTable::GetOffsetOfCount(); |
| 710 | pDebuggerRuntimeOffsets->m_cbPatch = sizeof(DebuggerControllerPatch); |
| 711 | pDebuggerRuntimeOffsets->m_offAddr = offsetof(DebuggerControllerPatch, address); |
| 712 | pDebuggerRuntimeOffsets->m_offOpcode = offsetof(DebuggerControllerPatch, opcode); |
| 713 | pDebuggerRuntimeOffsets->m_cbOpcode = sizeof(PRD_TYPE); |
| 714 | pDebuggerRuntimeOffsets->m_offTraceType = offsetof(DebuggerControllerPatch, trace.type); |
| 715 | pDebuggerRuntimeOffsets->m_traceTypeUnmanaged = TRACE_UNMANAGED; |
| 716 | |
| 717 | // @dbgtodo inspection - this should all go away or be obtained from DacDbi Primitives. |
| 718 | g_pEEInterface->GetRuntimeOffsets(&pDebuggerRuntimeOffsets->m_TLSIndex, |
| 719 | &pDebuggerRuntimeOffsets->m_TLSIsSpecialIndex, |
| 720 | &pDebuggerRuntimeOffsets->m_TLSCantStopIndex, |
| 721 | &pDebuggerRuntimeOffsets->m_EEThreadStateOffset, |
| 722 | &pDebuggerRuntimeOffsets->m_EEThreadStateNCOffset, |
| 723 | &pDebuggerRuntimeOffsets->m_EEThreadPGCDisabledOffset, |
| 724 | &pDebuggerRuntimeOffsets->m_EEThreadPGCDisabledValue, |
| 725 | &pDebuggerRuntimeOffsets->m_EEThreadFrameOffset, |
| 726 | &pDebuggerRuntimeOffsets->m_EEThreadMaxNeededSize, |
| 727 | &pDebuggerRuntimeOffsets->m_EEThreadSteppingStateMask, |
| 728 | &pDebuggerRuntimeOffsets->m_EEMaxFrameValue, |
| 729 | &pDebuggerRuntimeOffsets->m_EEThreadDebuggerFilterContextOffset, |
| 730 | &pDebuggerRuntimeOffsets->m_EEThreadCantStopOffset, |
| 731 | &pDebuggerRuntimeOffsets->m_EEFrameNextOffset, |
| 732 | &pDebuggerRuntimeOffsets->m_EEIsManagedExceptionStateMask); |
| 733 | |
| 734 | // Remember the struct in the control block. |
| 735 | pDebuggerIPCControlBlock->m_pRuntimeOffsets = pDebuggerRuntimeOffsets; |
| 736 | |
| 737 | return S_OK; |
| 738 | } |
| 739 | |
| 740 | struct DebugFilterParam |
| 741 | { |
| 742 | DebuggerIPCEvent *event; |
| 743 | }; |
| 744 | |
| 745 | // Filter called when we throw an exception while Handling events. |
| 746 | static LONG _debugFilter(LPEXCEPTION_POINTERS ep, PVOID pv) |
| 747 | { |
| 748 | LOG((LF_CORDB, LL_INFO10, |
| 749 | "Unhandled exception in Debugger::HandleIPCEvent\n" )); |
| 750 | |
| 751 | SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE; |
| 752 | |
| 753 | #if defined(_DEBUG) || !defined(FEATURE_CORESYSTEM) |
| 754 | DebuggerIPCEvent *event = ((DebugFilterParam *)pv)->event; |
| 755 | |
| 756 | DWORD pid = GetCurrentProcessId(); |
| 757 | DWORD tid = GetCurrentThreadId(); |
| 758 | |
| 759 | DebuggerIPCEventType type = (DebuggerIPCEventType) (event->type & DB_IPCE_TYPE_MASK); |
| 760 | #endif // _DEBUG || !FEATURE_CORESYSTEM |
| 761 | |
| 762 | // We should never AV here. In a debug build, throw up an assert w/ lots of useful (private) info. |
| 763 | #ifdef _DEBUG |
| 764 | { |
| 765 | // We can't really use SStrings on the helper thread; though if we're at this point, we've already died. |
| 766 | // So go ahead and risk it and use them anyways. |
| 767 | SString sStack; |
| 768 | StackScratchBuffer buffer; |
| 769 | GetStackTraceAtContext(sStack, ep->ContextRecord); |
| 770 | const CHAR *string = NULL; |
| 771 | |
| 772 | EX_TRY |
| 773 | { |
| 774 | string = sStack.GetANSI(buffer); |
| 775 | } |
| 776 | EX_CATCH |
| 777 | { |
| 778 | string = "*Could not retrieve stack*" ; |
| 779 | } |
| 780 | EX_END_CATCH(RethrowTerminalExceptions); |
| 781 | |
| 782 | CONSISTENCY_CHECK_MSGF(false, |
| 783 | ("Unhandled exception on the helper thread.\nEvent=%s(0x%x)\nCode=0x%0x, Ip=0x%p, .cxr=%p, .exr=%p.\n pid=0x%x (%d), tid=0x%x (%d).\n-----\nStack of exception:\n%s\n----\n" , |
| 784 | IPCENames::GetName(type), type, |
| 785 | ep->ExceptionRecord->ExceptionCode, GetIP(ep->ContextRecord), ep->ContextRecord, ep->ExceptionRecord, |
| 786 | pid, pid, tid, tid, |
| 787 | string)); |
| 788 | } |
| 789 | #endif |
| 790 | |
| 791 | // this message box doesn't work well on coresystem... we actually get in a recursive exception handling loop |
| 792 | #ifndef FEATURE_CORESYSTEM |
| 793 | // We took an AV on the helper thread. This is a catastrophic situation so we can |
| 794 | // simply call the EE's catastrophic message box to display the error. |
| 795 | EEMessageBoxCatastrophic( |
| 796 | IDS_DEBUG_UNHANDLEDEXCEPTION_IPC, IDS_DEBUG_SERVICE_CAPTION, |
| 797 | type, |
| 798 | ep->ExceptionRecord->ExceptionCode, |
| 799 | GetIP(ep->ContextRecord), |
| 800 | pid, pid, tid, tid); |
| 801 | #endif |
| 802 | |
| 803 | // For debugging, we can change the behavior by manually setting eax. |
| 804 | // EXCEPTION_EXECUTE_HANDLER=1, EXCEPTION_CONTINUE_SEARCH=0, EXCEPTION_CONTINUE_EXECUTION=-1 |
| 805 | return EXCEPTION_CONTINUE_SEARCH; |
| 806 | } |
| 807 | |
| 808 | #ifdef _DEBUG |
| 809 | // Tracking to ensure that we don't call New() for the normal (non interop-safe heap) |
| 810 | // on the helper thread. We also can't do a normal allocation when we have hard |
| 811 | // suspended any other thread (since it could hold the OS heap lock). |
| 812 | |
| 813 | // TODO: this probably belongs in the EE itself, not here in the debugger stuff. |
| 814 | |
| 815 | void AssertAllocationAllowed() |
| 816 | { |
| 817 | #ifdef USE_INTEROPSAFE_HEAP |
| 818 | // Don't forget to preserve error status! |
| 819 | DWORD err = GetLastError(); |
| 820 | |
| 821 | // We can mark certain |
| 822 | if (g_DbgSuppressAllocationAsserts == 0) |
| 823 | { |
| 824 | |
| 825 | // if we have hard suspended any threads. We want to assert as it could cause deadlock |
| 826 | // since those suspended threads may hold the OS heap lock |
| 827 | if (g_fEEStarted) { |
| 828 | _ASSERTE (!EEAllocationDisallowed()); |
| 829 | } |
| 830 | |
| 831 | // Can't call IsDbgHelperSpecialThread() here b/c that changes program state. |
| 832 | // So we use our |
| 833 | if (DebuggerRCThread::s_DbgHelperThreadId.IsCurrentThread()) |
| 834 | { |
| 835 | // In case assert allocates, bump up the 'OK' counter to avoid an infinite recursion. |
| 836 | SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE; |
| 837 | |
| 838 | _ASSERTE(false || !"New called on Helper Thread" ); |
| 839 | |
| 840 | } |
| 841 | } |
| 842 | SetLastError(err); |
| 843 | #endif |
| 844 | } |
| 845 | #endif |
| 846 | |
| 847 | |
| 848 | //--------------------------------------------------------------------------------------- |
| 849 | // |
| 850 | // Primary function of the Runtime Controller thread. First, we let |
| 851 | // the Debugger Interface know that we're up and running. Then, we run |
| 852 | // the main loop. |
| 853 | // |
| 854 | //--------------------------------------------------------------------------------------- |
| 855 | void DebuggerRCThread::ThreadProc(void) |
| 856 | { |
| 857 | CONTRACTL |
| 858 | { |
| 859 | SO_INTOLERANT; |
| 860 | NOTHROW; |
| 861 | GC_TRIGGERS; // Debugger::SuspendComplete can trigger GC |
| 862 | |
| 863 | // Although we're the helper thread, we haven't set it yet. |
| 864 | DISABLED(PRECONDITION(ThisIsHelperThreadWorker())); |
| 865 | |
| 866 | INSTANCE_CHECK; |
| 867 | } |
| 868 | CONTRACTL_END; |
| 869 | |
| 870 | STRESS_LOG_RESERVE_MEM (0); |
| 871 | // This message actually serves a purpose (which is why it is always run) |
| 872 | // The Stress log is run during hijacking, when other threads can be suspended |
| 873 | // at arbitrary locations (including when holding a lock that NT uses to serialize |
| 874 | // all memory allocations). By sending a message now, we insure that the stress |
| 875 | // log will not allocate memory at these critical times an avoid deadlock. |
| 876 | { |
| 877 | SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE; |
| 878 | STRESS_LOG0(LF_CORDB|LF_ALWAYS, LL_ALWAYS, "Debugger Thread spinning up\n" ); |
| 879 | |
| 880 | // Call this to force creation of the TLS slots on helper-thread. |
| 881 | IsDbgHelperSpecialThread(); |
| 882 | } |
| 883 | |
| 884 | #ifdef _DEBUG |
| 885 | // Track the helper thread. |
| 886 | s_DbgHelperThreadId.SetToCurrentThread(); |
| 887 | #endif |
| 888 | CantAllocHolder caHolder; |
| 889 | |
| 890 | |
| 891 | #ifdef _DEBUG |
| 892 | // Cause wait in the helper thread startup. This lets us test against certain races. |
| 893 | // 1 = 6 sec. (shorter than Poll) |
| 894 | // 2 = 12 sec (longer than Poll). |
| 895 | // 3 = infinite - never comes up. |
| 896 | static int fDelayHelper = -1; |
| 897 | |
| 898 | if (fDelayHelper == -1) |
| 899 | { |
| 900 | fDelayHelper = UnsafeGetConfigDWORD(CLRConfig::INTERNAL_DbgDelayHelper); |
| 901 | } |
| 902 | |
| 903 | if (fDelayHelper) |
| 904 | { |
| 905 | DWORD dwSleep = 6000; |
| 906 | |
| 907 | switch(fDelayHelper) |
| 908 | { |
| 909 | case 1: dwSleep = 6000; break; |
| 910 | case 2: dwSleep = 12000; break; |
| 911 | case 3: dwSleep = INFINITE; break; |
| 912 | } |
| 913 | |
| 914 | ClrSleepEx(dwSleep, FALSE); |
| 915 | } |
| 916 | #endif |
| 917 | |
| 918 | LOG((LF_CORDB, LL_INFO1000, "DRCT::TP: helper thread spinning up...\n" )); |
| 919 | |
| 920 | // In case the shared memory is not initialized properly, it will be noop |
| 921 | if (m_pDCB == NULL) |
| 922 | { |
| 923 | return; |
| 924 | } |
| 925 | |
| 926 | // Lock the debugger before spinning up. |
| 927 | Debugger::DebuggerLockHolder debugLockHolder(m_debugger); |
| 928 | |
| 929 | if (m_pDCB->m_helperThreadId != 0) |
| 930 | { |
| 931 | // someone else has created a helper thread, we're outta here |
| 932 | // the most likely scenario here is that there was some kind of |
| 933 | // race between remotethread creation and localthread creation |
| 934 | |
| 935 | LOG((LF_CORDB, LL_EVERYTHING, "Second debug helper thread creation detected, thread will safely suicide\n" )); |
| 936 | // dbgLockHolder goes out of scope - implicit Release |
| 937 | return; |
| 938 | } |
| 939 | |
| 940 | // this thread took the lock and there is no existing m_helperThreadID therefore |
| 941 | // this *IS* the helper thread and nobody else can be the helper thread |
| 942 | |
| 943 | // the handle was created by the Start method |
| 944 | _ASSERTE(m_thread != NULL); |
| 945 | |
| 946 | #ifdef _DEBUG |
| 947 | // Make sure that we have the proper permissions. |
| 948 | { |
| 949 | DWORD dwWaitResult = WaitForSingleObject(m_thread, 0); |
| 950 | _ASSERTE(dwWaitResult == WAIT_TIMEOUT); |
| 951 | } |
| 952 | #endif |
| 953 | |
| 954 | // Mark that we're the true helper thread. Now that we've marked |
| 955 | // this, no other threads will ever become the temporary helper |
| 956 | // thread. |
| 957 | m_pDCB->m_helperThreadId = GetCurrentThreadId(); |
| 958 | |
| 959 | LOG((LF_CORDB, LL_INFO1000, "DRCT::TP: helper thread id is 0x%x helperThreadId\n" , |
| 960 | m_pDCB->m_helperThreadId)); |
| 961 | |
| 962 | // If there is a temporary helper thread, then we need to wait for |
| 963 | // it to finish being the helper thread before we can become the |
| 964 | // helper thread. |
| 965 | if (m_pDCB->m_temporaryHelperThreadId != 0) |
| 966 | { |
| 967 | LOG((LF_CORDB, LL_INFO1000, |
| 968 | "DRCT::TP: temporary helper thread 0x%x is in the way, " |
| 969 | "waiting...\n" , |
| 970 | m_pDCB->m_temporaryHelperThreadId)); |
| 971 | |
| 972 | debugLockHolder.Release(); |
| 973 | |
| 974 | // Wait for the temporary helper thread to finish up. |
| 975 | DWORD dwWaitResult = WaitForSingleObject(m_helperThreadCanGoEvent, INFINITE); |
| 976 | (void)dwWaitResult; //prevent "unused variable" error from GCC |
| 977 | |
| 978 | LOG((LF_CORDB, LL_INFO1000, "DRCT::TP: done waiting for temp help to finish up.\n" )); |
| 979 | |
| 980 | _ASSERTE(dwWaitResult == WAIT_OBJECT_0); |
| 981 | _ASSERTE(m_pDCB->m_temporaryHelperThreadId==0); |
| 982 | } |
| 983 | else |
| 984 | { |
| 985 | LOG((LF_CORDB, LL_INFO1000, "DRCT::TP: no temp help in the way...\n" )); |
| 986 | |
| 987 | debugLockHolder.Release(); |
| 988 | } |
| 989 | |
| 990 | // Run the main loop as the true helper thread. |
| 991 | MainLoop(); |
| 992 | } |
| 993 | |
| 994 | void DebuggerRCThread::RightSideDetach(void) |
| 995 | { |
| 996 | _ASSERTE( m_fDetachRightSide == false ); |
| 997 | m_fDetachRightSide = true; |
| 998 | #if !defined(FEATURE_DBGIPC_TRANSPORT_VM) |
| 999 | CloseIPCHandles(); |
| 1000 | #endif // !FEATURE_DBGIPC_TRANSPORT_VM |
| 1001 | } |
| 1002 | |
| 1003 | // |
| 1004 | // These defines control how many times we spin while waiting for threads to sync and how often. Note its higher in |
| 1005 | // debug builds to allow extra time for threads to sync. |
| 1006 | // |
| 1007 | #define CorDB_SYNC_WAIT_TIMEOUT 20 // 20ms |
| 1008 | |
| 1009 | #ifdef _DEBUG |
| 1010 | #define CorDB_MAX_SYNC_SPIN_COUNT (10000 / CorDB_SYNC_WAIT_TIMEOUT) // (10 seconds) |
| 1011 | #else |
| 1012 | #define CorDB_MAX_SYNC_SPIN_COUNT (3000 / CorDB_SYNC_WAIT_TIMEOUT) // (3 seconds) |
| 1013 | #endif |
| 1014 | |
| 1015 | // |
| 1016 | // NDPWhidbey issue 10749 - Due to a compiler change for vc7.1, |
| 1017 | // Don't inline this function! |
| 1018 | // PAL_TRY allocates space on the stack and so can not be used within a loop, |
| 1019 | // else we'll slowly leak stack space w/ each interation and get an overflow. |
| 1020 | // So make this its own function to enforce that we free the stack space between |
| 1021 | // iterations. |
| 1022 | // |
| 1023 | bool HandleIPCEventWrapper(Debugger* pDebugger, DebuggerIPCEvent *e) |
| 1024 | { |
| 1025 | struct Param : DebugFilterParam |
| 1026 | { |
| 1027 | Debugger* pDebugger; |
| 1028 | bool wasContinue; |
| 1029 | } param; |
| 1030 | param.event = e; |
| 1031 | param.pDebugger = pDebugger; |
| 1032 | param.wasContinue = false; |
| 1033 | PAL_TRY(Param *, pParam, ¶m) |
| 1034 | { |
| 1035 | pParam->wasContinue = pParam->pDebugger->HandleIPCEvent(pParam->event); |
| 1036 | } |
| 1037 | PAL_EXCEPT_FILTER(_debugFilter) |
| 1038 | { |
| 1039 | LOG((LF_CORDB, LL_INFO10, "Unhandled exception caught in Debugger::HandleIPCEvent\n" )); |
| 1040 | } |
| 1041 | PAL_ENDTRY |
| 1042 | |
| 1043 | return param.wasContinue; |
| 1044 | } |
| 1045 | |
| 1046 | bool DebuggerRCThread::HandleRSEA() |
| 1047 | { |
| 1048 | CONTRACTL |
| 1049 | { |
| 1050 | SO_NOT_MAINLINE; |
| 1051 | NOTHROW; |
| 1052 | if (g_pEEInterface->GetThread() != NULL) { GC_TRIGGERS; } else { GC_NOTRIGGER; } |
| 1053 | PRECONDITION(ThisIsHelperThreadWorker()); |
| 1054 | } |
| 1055 | CONTRACTL_END; |
| 1056 | |
| 1057 | LOG((LF_CORDB,LL_INFO10000, "RSEA from out of process (right side)\n" )); |
| 1058 | DebuggerIPCEvent * e; |
| 1059 | #if !defined(FEATURE_DBGIPC_TRANSPORT_VM) |
| 1060 | // Make room for any Right Side event on the stack. |
| 1061 | BYTE buffer[CorDBIPC_BUFFER_SIZE]; |
| 1062 | e = (DebuggerIPCEvent *) buffer; |
| 1063 | |
| 1064 | // If the RSEA is signaled, then handle the event from the Right Side. |
| 1065 | memcpy(e, GetIPCEventReceiveBuffer(), CorDBIPC_BUFFER_SIZE); |
| 1066 | #else |
| 1067 | // Be sure to fetch the event into the official receive buffer since some event handlers assume it's there |
| 1068 | // regardless of the the event buffer pointer passed to them. |
| 1069 | e = GetIPCEventReceiveBuffer(); |
| 1070 | g_pDbgTransport->GetNextEvent(e, CorDBIPC_BUFFER_SIZE); |
| 1071 | #endif // !FEATURE_DBGIPC_TRANSPOPRT |
| 1072 | |
| 1073 | #if !defined(FEATURE_DBGIPC_TRANSPORT_VM) |
| 1074 | // If no reply is required, then let the Right Side go since we've got a copy of the event now. |
| 1075 | _ASSERTE(!e->asyncSend || !e->replyRequired); |
| 1076 | |
| 1077 | if (!e->replyRequired && !e->asyncSend) |
| 1078 | { |
| 1079 | LOG((LF_CORDB, LL_INFO1000, "DRCT::ML: no reply required, letting Right Side go.\n" )); |
| 1080 | |
| 1081 | BOOL succ = SetEvent(m_pDCB->m_rightSideEventRead); |
| 1082 | |
| 1083 | if (!succ) |
| 1084 | CORDBDebuggerSetUnrecoverableWin32Error(m_debugger, 0, true); |
| 1085 | } |
| 1086 | #ifdef LOGGING |
| 1087 | else if (e->asyncSend) |
| 1088 | LOG((LF_CORDB, LL_INFO1000, "DRCT::ML: async send.\n" )); |
| 1089 | else |
| 1090 | LOG((LF_CORDB, LL_INFO1000, "DRCT::ML: reply required, holding Right Side...\n" )); |
| 1091 | #endif |
| 1092 | #endif // !FEATURE_DBGIPC_TRANSPORT_VM |
| 1093 | |
| 1094 | // Pass the event to the debugger for handling. Returns true if the event was a Continue event and we can |
| 1095 | // stop looking for stragglers. We wrap this whole thing in an exception handler to help us debug faults. |
| 1096 | bool wasContinue = false; |
| 1097 | |
| 1098 | wasContinue = HandleIPCEventWrapper(m_debugger, e); |
| 1099 | |
| 1100 | return wasContinue; |
| 1101 | } |
| 1102 | |
| 1103 | //--------------------------------------------------------------------------------------- |
| 1104 | // |
| 1105 | // Main loop of the Runtime Controller thread. It waits for IPC events |
| 1106 | // and dishes them out to the Debugger object for processing. |
| 1107 | // |
| 1108 | // Some of this logic is copied in Debugger::VrpcToVls |
| 1109 | // |
| 1110 | //--------------------------------------------------------------------------------------- |
| 1111 | void DebuggerRCThread::MainLoop() |
| 1112 | { |
| 1113 | // This function can only be called on native Debugger helper thread. |
| 1114 | // |
| 1115 | |
| 1116 | CONTRACTL |
| 1117 | { |
| 1118 | SO_INTOLERANT; |
| 1119 | NOTHROW; |
| 1120 | |
| 1121 | PRECONDITION(m_thread != NULL); |
| 1122 | PRECONDITION(ThisIsHelperThreadWorker()); |
| 1123 | PRECONDITION(IsDbgHelperSpecialThread()); // Can only be called on native debugger helper thread |
| 1124 | PRECONDITION((!ThreadStore::HoldingThreadStore()) || g_fProcessDetach); |
| 1125 | } |
| 1126 | CONTRACTL_END; |
| 1127 | |
| 1128 | LOG((LF_CORDB, LL_INFO1000, "DRCT::ML:: running main loop\n" )); |
| 1129 | |
| 1130 | // Anybody doing helper duty is in a can't-stop range, period. |
| 1131 | // Our helper thread is already in a can't-stop range, so this is particularly useful for |
| 1132 | // threads doing helper duty. |
| 1133 | CantStopHolder cantStopHolder; |
| 1134 | |
| 1135 | HANDLE rghWaitSet[DRCT_COUNT_FINAL]; |
| 1136 | |
| 1137 | #ifdef _DEBUG |
| 1138 | DWORD dwSyncSpinCount = 0; |
| 1139 | #endif |
| 1140 | |
| 1141 | // We start out just listening on RSEA and the thread control event... |
| 1142 | unsigned int cWaitCount = DRCT_COUNT_INITIAL; |
| 1143 | DWORD dwWaitTimeout = INFINITE; |
| 1144 | rghWaitSet[DRCT_CONTROL_EVENT] = m_threadControlEvent; |
| 1145 | rghWaitSet[DRCT_FAVORAVAIL] = GetFavorAvailableEvent(); |
| 1146 | #if !defined(FEATURE_DBGIPC_TRANSPORT_VM) |
| 1147 | rghWaitSet[DRCT_RSEA] = m_pDCB->m_rightSideEventAvailable; |
| 1148 | #else |
| 1149 | rghWaitSet[DRCT_RSEA] = g_pDbgTransport->GetIPCEventReadyEvent(); |
| 1150 | #endif // !FEATURE_DBGIPC_TRANSPORT_VM |
| 1151 | |
| 1152 | CONTRACT_VIOLATION(ThrowsViolation);// HndCreateHandle throws, and this loop is not backstopped by any EH |
| 1153 | |
| 1154 | // Lock holder. Don't take it yet. We take lock on this when we succeeded suspended runtime. |
| 1155 | // We will release the lock later when continue happens and runtime resumes |
| 1156 | Debugger::DebuggerLockHolder debugLockHolderSuspended(m_debugger, false); |
| 1157 | |
| 1158 | while (m_run) |
| 1159 | { |
| 1160 | LOG((LF_CORDB, LL_INFO1000, "DRCT::ML: waiting for event.\n" )); |
| 1161 | |
| 1162 | #if !defined(FEATURE_DBGIPC_TRANSPORT_VM) |
| 1163 | // If there is a debugger attached, wait on its handle, too... |
| 1164 | if ((cWaitCount == DRCT_COUNT_INITIAL) && |
| 1165 | m_pDCB->m_rightSideProcessHandle.ImportToLocalProcess() != NULL) |
| 1166 | { |
| 1167 | _ASSERTE((cWaitCount + 1) == DRCT_COUNT_FINAL); |
| 1168 | rghWaitSet[DRCT_DEBUGGER_EVENT] = m_pDCB->m_rightSideProcessHandle; |
| 1169 | cWaitCount = DRCT_COUNT_FINAL; |
| 1170 | } |
| 1171 | #endif // !FEATURE_DBGIPC_TRANSPORT_VM |
| 1172 | |
| 1173 | |
| 1174 | if (m_fDetachRightSide) |
| 1175 | { |
| 1176 | m_fDetachRightSide = false; |
| 1177 | |
| 1178 | #if !defined(FEATURE_DBGIPC_TRANSPORT_VM) |
| 1179 | _ASSERTE(cWaitCount == DRCT_COUNT_FINAL); |
| 1180 | _ASSERTE((cWaitCount - 1) == DRCT_COUNT_INITIAL); |
| 1181 | |
| 1182 | rghWaitSet[DRCT_DEBUGGER_EVENT] = NULL; |
| 1183 | cWaitCount = DRCT_COUNT_INITIAL; |
| 1184 | #endif // !FEATURE_DBGIPC_TRANSPORT_VM |
| 1185 | } |
| 1186 | |
| 1187 | // Wait for an event from the Right Side. |
| 1188 | DWORD dwWaitResult = WaitForMultipleObjectsEx(cWaitCount, rghWaitSet, FALSE, dwWaitTimeout, FALSE); |
| 1189 | |
| 1190 | if (!m_run) |
| 1191 | { |
| 1192 | continue; |
| 1193 | } |
| 1194 | |
| 1195 | |
| 1196 | if (dwWaitResult == WAIT_OBJECT_0 + DRCT_DEBUGGER_EVENT) |
| 1197 | { |
| 1198 | // If the handle of the right side process is signaled, then we've lost our controlling debugger. We |
| 1199 | // terminate this process immediatley in such a case. |
| 1200 | LOG((LF_CORDB, LL_INFO1000, "DRCT::ML: terminating this process. Right Side has exited.\n" )); |
| 1201 | SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE; |
| 1202 | EEPOLICY_HANDLE_FATAL_ERROR(0); |
| 1203 | _ASSERTE(!"Should never reach this point." ); |
| 1204 | } |
| 1205 | else if (dwWaitResult == WAIT_OBJECT_0 + DRCT_FAVORAVAIL) |
| 1206 | { |
| 1207 | // execute the callback set by DoFavor() |
| 1208 | FAVORCALLBACK fpCallback = GetFavorFnPtr(); |
| 1209 | // We never expect the callback to be null unless some other component |
| 1210 | // wrongly signals our event (see DD 463807). |
| 1211 | // In case we messed up, we will not set the FavorReadEvent and will hang favor requesting thread. |
| 1212 | if (fpCallback) |
| 1213 | { |
| 1214 | (*fpCallback)(GetFavorData()); |
| 1215 | SetEvent(GetFavorReadEvent()); |
| 1216 | } |
| 1217 | } |
| 1218 | else if (dwWaitResult == WAIT_OBJECT_0 + DRCT_RSEA) |
| 1219 | { |
| 1220 | bool fWasContinue = HandleRSEA(); |
| 1221 | |
| 1222 | if (fWasContinue) |
| 1223 | { |
| 1224 | |
| 1225 | // If they called continue, then we must have released the TSL. |
| 1226 | _ASSERTE(!ThreadStore::HoldingThreadStore() || g_fProcessDetach); |
| 1227 | |
| 1228 | // Let's release the lock here since runtime is resumed. |
| 1229 | debugLockHolderSuspended.Release(); |
| 1230 | |
| 1231 | // This debugger thread shoud not be holding debugger locks anymore |
| 1232 | _ASSERTE(!g_pDebugger->ThreadHoldsLock()); |
| 1233 | #ifdef _DEBUG |
| 1234 | // Always reset the syncSpinCount to 0 on a continue so that we have the maximum number of possible |
| 1235 | // spins the next time we need to sync. |
| 1236 | dwSyncSpinCount = 0; |
| 1237 | #endif |
| 1238 | |
| 1239 | if (dwWaitTimeout != INFINITE) |
| 1240 | { |
| 1241 | LOG((LF_CORDB, LL_INFO1000, "DRCT::ML:: don't check for stragglers due to continue.\n" )); |
| 1242 | |
| 1243 | dwWaitTimeout = INFINITE; |
| 1244 | } |
| 1245 | |
| 1246 | } |
| 1247 | } |
| 1248 | else if (dwWaitResult == WAIT_OBJECT_0 + DRCT_CONTROL_EVENT) |
| 1249 | { |
| 1250 | LOG((LF_CORDB, LL_INFO1000, "DRCT::ML:: straggler event set.\n" )); |
| 1251 | |
| 1252 | ThreadStoreLockHolder tsl; |
| 1253 | Debugger::DebuggerLockHolder debugLockHolder(m_debugger); |
| 1254 | // Make sure that we're still synchronizing... |
| 1255 | if (m_debugger->IsSynchronizing()) |
| 1256 | { |
| 1257 | LOG((LF_CORDB, LL_INFO1000, "DRCT::ML:: dropping the timeout.\n" )); |
| 1258 | |
| 1259 | dwWaitTimeout = CorDB_SYNC_WAIT_TIMEOUT; |
| 1260 | |
| 1261 | // |
| 1262 | // Skip waiting the first time and just give it a go. Note: Implicit |
| 1263 | // release of the debugger and thread store lock, because we are leaving its scope. |
| 1264 | // |
| 1265 | goto LWaitTimedOut; |
| 1266 | } |
| 1267 | #ifdef LOGGING |
| 1268 | else |
| 1269 | LOG((LF_CORDB, LL_INFO1000, "DRCT::ML:: told to wait, but not syncing anymore.\n" )); |
| 1270 | #endif |
| 1271 | // dbgLockHolder goes out of scope - implicit Release |
| 1272 | // tsl goes out of scope - implicit Release |
| 1273 | } |
| 1274 | else if (dwWaitResult == WAIT_TIMEOUT) |
| 1275 | { |
| 1276 | |
| 1277 | LWaitTimedOut: |
| 1278 | |
| 1279 | LOG((LF_CORDB, LL_INFO1000, "DRCT::ML:: wait timed out.\n" )); |
| 1280 | |
| 1281 | ThreadStore::LockThreadStore(); |
| 1282 | // Debugger::DebuggerLockHolder debugLockHolder(m_debugger); |
| 1283 | // Explicitly get the lock here since we try to check to see if |
| 1284 | // have suspended. We will release the lock if we are not suspended yet. |
| 1285 | // |
| 1286 | debugLockHolderSuspended.Acquire(); |
| 1287 | |
| 1288 | // We should still be synchronizing, otherwise we would not have timed out. |
| 1289 | _ASSERTE(m_debugger->IsSynchronizing()); |
| 1290 | |
| 1291 | LOG((LF_CORDB, LL_INFO1000, "DRCT::ML:: sweeping the thread list.\n" )); |
| 1292 | |
| 1293 | #ifdef _DEBUG |
| 1294 | // If we fail to suspend the CLR, don't bother waiting for a BVT to timeout, |
| 1295 | // fire up an assert up now. |
| 1296 | // Threads::m_DebugWillSyncCount+1 is the number of outstanding threads. |
| 1297 | // We're trying to suspend any thread w/ TS_DebugWillSync set. |
| 1298 | if (dwSyncSpinCount++ > CorDB_MAX_SYNC_SPIN_COUNT) |
| 1299 | { |
| 1300 | _ASSERTE_MSG(false, "Timeout trying to suspend CLR for debugging. Possibly a deadlock.\n" \ |
| 1301 | "You can ignore this assert to continue waiting\n" ); |
| 1302 | dwSyncSpinCount = 0; |
| 1303 | } |
| 1304 | #endif |
| 1305 | |
| 1306 | // Don't call Sweep if we're doing helper thread duty. |
| 1307 | // If we're doing helper thread duty, then we already Suspended the CLR, and we already hold the TSL. |
| 1308 | bool fSuspended; |
| 1309 | { |
| 1310 | // SweepThreadsForDebug() may call new!!! ARGG!!! |
| 1311 | SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE; |
| 1312 | fSuspended = g_pEEInterface->SweepThreadsForDebug(false); |
| 1313 | } |
| 1314 | |
| 1315 | if (fSuspended) |
| 1316 | { |
| 1317 | STRESS_LOG0(LF_CORDB, LL_INFO1000, "DRCT::ML:: wait set empty after sweep.\n" ); |
| 1318 | |
| 1319 | // There are no more threads to wait for, so go ahead and send the sync complete event. |
| 1320 | m_debugger->SuspendComplete(); |
| 1321 | dwWaitTimeout = INFINITE; |
| 1322 | |
| 1323 | // Note: we hold the thread store lock now and debugger lock... |
| 1324 | |
| 1325 | // We also hold debugger lock the whole time that Runtime is stopped. We will release the debugger lock |
| 1326 | // when we receive the Continue event that resumes the runtime. |
| 1327 | |
| 1328 | _ASSERTE(ThreadStore::HoldingThreadStore() || g_fProcessDetach); |
| 1329 | } |
| 1330 | else |
| 1331 | { |
| 1332 | // If we're doing helper thread duty, then we expect to have been suspended already. |
| 1333 | // And so the sweep should always succeed. |
| 1334 | STRESS_LOG0(LF_CORDB, LL_INFO1000, "DRCT::ML:: threads still syncing after sweep.\n" ); |
| 1335 | debugLockHolderSuspended.Release(); |
| 1336 | ThreadStore::UnlockThreadStore(); |
| 1337 | } |
| 1338 | // debugLockHolderSuspended does not go out of scope. It has to be either released explicitly on the line above or |
| 1339 | // we intend to hold the lock till we hit continue event. |
| 1340 | |
| 1341 | } |
| 1342 | } |
| 1343 | |
| 1344 | STRESS_LOG0(LF_CORDB, LL_INFO1000, "DRCT::ML:: Exiting.\n" ); |
| 1345 | } |
| 1346 | |
| 1347 | //--------------------------------------------------------------------------------------- |
| 1348 | // |
| 1349 | // Main loop of the temporary Helper thread. It waits for IPC events |
| 1350 | // and dishes them out to the Debugger object for processing. |
| 1351 | // |
| 1352 | // Notes: |
| 1353 | // When we enter here, we are holding debugger lock and thread store lock. |
| 1354 | // The debugger lock was SuppressRelease in DoHelperThreadDuty. The continue event |
| 1355 | // that we are waiting for will trigger the corresponding release. |
| 1356 | // |
| 1357 | // IMPORTANT!!! READ ME!!!! |
| 1358 | // This MainLoop is similiar to MainLoop function above but simplified to deal with only |
| 1359 | // some scenario. So if you change here, you should look at MainLoop to see if same change is |
| 1360 | // required. |
| 1361 | //--------------------------------------------------------------------------------------- |
| 1362 | void DebuggerRCThread::TemporaryHelperThreadMainLoop() |
| 1363 | { |
| 1364 | CONTRACTL |
| 1365 | { |
| 1366 | SO_NOT_MAINLINE; |
| 1367 | NOTHROW; |
| 1368 | |
| 1369 | |
| 1370 | // If we come in here, this managed thread is trying to do helper thread duty. |
| 1371 | // It should be holding the debugger lock!!! |
| 1372 | // |
| 1373 | PRECONDITION(m_debugger->ThreadHoldsLock()); |
| 1374 | PRECONDITION((ThreadStore::HoldingThreadStore()) || g_fProcessDetach); |
| 1375 | PRECONDITION(ThisIsTempHelperThread()); |
| 1376 | } |
| 1377 | CONTRACTL_END; |
| 1378 | |
| 1379 | STRESS_LOG0(LF_CORDB, LL_INFO1000, "DRCT::THTML:: Doing helper thread duty, running main loop.\n" ); |
| 1380 | // Anybody doing helper duty is in a can't-stop range, period. |
| 1381 | // Our helper thread is already in a can't-stop range, so this is particularly useful for |
| 1382 | // threads doing helper duty. |
| 1383 | CantStopHolder cantStopHolder; |
| 1384 | |
| 1385 | HANDLE rghWaitSet[DRCT_COUNT_FINAL]; |
| 1386 | |
| 1387 | #ifdef _DEBUG |
| 1388 | DWORD dwSyncSpinCount = 0; |
| 1389 | #endif |
| 1390 | |
| 1391 | // We start out just listening on RSEA and the thread control event... |
| 1392 | unsigned int cWaitCount = DRCT_COUNT_INITIAL; |
| 1393 | DWORD dwWaitTimeout = INFINITE; |
| 1394 | rghWaitSet[DRCT_CONTROL_EVENT] = m_threadControlEvent; |
| 1395 | rghWaitSet[DRCT_FAVORAVAIL] = GetFavorAvailableEvent(); |
| 1396 | #if !defined(FEATURE_DBGIPC_TRANSPORT_VM) |
| 1397 | rghWaitSet[DRCT_RSEA] = m_pDCB->m_rightSideEventAvailable; |
| 1398 | #else //FEATURE_DBGIPC_TRANSPORT_VM |
| 1399 | rghWaitSet[DRCT_RSEA] = g_pDbgTransport->GetIPCEventReadyEvent(); |
| 1400 | #endif // !FEATURE_DBGIPC_TRANSPORT_VM |
| 1401 | |
| 1402 | CONTRACT_VIOLATION(ThrowsViolation);// HndCreateHandle throws, and this loop is not backstopped by any EH |
| 1403 | |
| 1404 | while (m_run) |
| 1405 | { |
| 1406 | LOG((LF_CORDB, LL_INFO1000, "DRCT::ML: waiting for event.\n" )); |
| 1407 | |
| 1408 | // Wait for an event from the Right Side. |
| 1409 | DWORD dwWaitResult = WaitForMultipleObjectsEx(cWaitCount, rghWaitSet, FALSE, dwWaitTimeout, FALSE); |
| 1410 | |
| 1411 | if (!m_run) |
| 1412 | { |
| 1413 | continue; |
| 1414 | } |
| 1415 | |
| 1416 | |
| 1417 | if (dwWaitResult == WAIT_OBJECT_0 + DRCT_DEBUGGER_EVENT) |
| 1418 | { |
| 1419 | // If the handle of the right side process is signaled, then we've lost our controlling debugger. We |
| 1420 | // terminate this process immediatley in such a case. |
| 1421 | LOG((LF_CORDB, LL_INFO1000, "DRCT::THTML: terminating this process. Right Side has exited.\n" )); |
| 1422 | |
| 1423 | TerminateProcess(GetCurrentProcess(), 0); |
| 1424 | _ASSERTE(!"Should never reach this point." ); |
| 1425 | } |
| 1426 | else if (dwWaitResult == WAIT_OBJECT_0 + DRCT_FAVORAVAIL) |
| 1427 | { |
| 1428 | // execute the callback set by DoFavor() |
| 1429 | (*GetFavorFnPtr())(GetFavorData()); |
| 1430 | |
| 1431 | SetEvent(GetFavorReadEvent()); |
| 1432 | } |
| 1433 | else if (dwWaitResult == WAIT_OBJECT_0 + DRCT_RSEA) |
| 1434 | { |
| 1435 | // @todo: |
| 1436 | // We are only interested in dealing with Continue event here... |
| 1437 | // Once we remove the HelperThread duty, this will just go away. |
| 1438 | // |
| 1439 | bool fWasContinue = HandleRSEA(); |
| 1440 | |
| 1441 | if (fWasContinue) |
| 1442 | { |
| 1443 | // If they called continue, then we must have released the TSL. |
| 1444 | _ASSERTE(!ThreadStore::HoldingThreadStore() || g_fProcessDetach); |
| 1445 | |
| 1446 | #ifdef _DEBUG |
| 1447 | // Always reset the syncSpinCount to 0 on a continue so that we have the maximum number of possible |
| 1448 | // spins the next time we need to sync. |
| 1449 | dwSyncSpinCount = 0; |
| 1450 | #endif |
| 1451 | |
| 1452 | // HelperThread duty is finished. We have got a Continue message |
| 1453 | goto LExit; |
| 1454 | } |
| 1455 | } |
| 1456 | else if (dwWaitResult == WAIT_OBJECT_0 + DRCT_CONTROL_EVENT) |
| 1457 | { |
| 1458 | LOG((LF_CORDB, LL_INFO1000, "DRCT::THTML:: straggler event set.\n" )); |
| 1459 | |
| 1460 | // Make sure that we're still synchronizing... |
| 1461 | _ASSERTE(m_debugger->IsSynchronizing()); |
| 1462 | LOG((LF_CORDB, LL_INFO1000, "DRCT::THTML:: dropping the timeout.\n" )); |
| 1463 | |
| 1464 | dwWaitTimeout = CorDB_SYNC_WAIT_TIMEOUT; |
| 1465 | |
| 1466 | // |
| 1467 | // Skip waiting the first time and just give it a go. Note: Implicit |
| 1468 | // release of the lock, because we are leaving its scope. |
| 1469 | // |
| 1470 | goto LWaitTimedOut; |
| 1471 | } |
| 1472 | else if (dwWaitResult == WAIT_TIMEOUT) |
| 1473 | { |
| 1474 | |
| 1475 | LWaitTimedOut: |
| 1476 | |
| 1477 | LOG((LF_CORDB, LL_INFO1000, "DRCT::THTML:: wait timed out.\n" )); |
| 1478 | |
| 1479 | // We should still be synchronizing, otherwise we would not have timed out. |
| 1480 | _ASSERTE(m_debugger->IsSynchronizing()); |
| 1481 | |
| 1482 | LOG((LF_CORDB, LL_INFO1000, "DRCT::THTML:: sweeping the thread list.\n" )); |
| 1483 | |
| 1484 | #ifdef _DEBUG |
| 1485 | // If we fail to suspend the CLR, don't bother waiting for a BVT to timeout, |
| 1486 | // fire up an assert up now. |
| 1487 | // Threads::m_DebugWillSyncCount+1 is the number of outstanding threads. |
| 1488 | // We're trying to suspend any thread w/ TS_DebugWillSync set. |
| 1489 | if (dwSyncSpinCount++ > CorDB_MAX_SYNC_SPIN_COUNT) |
| 1490 | { |
| 1491 | _ASSERTE(false || !"Timeout trying to suspend CLR for debugging. Possibly a deadlock. " |
| 1492 | "You can ignore this assert to continue waiting\n" ); |
| 1493 | dwSyncSpinCount = 0; |
| 1494 | } |
| 1495 | #endif |
| 1496 | |
| 1497 | STRESS_LOG0(LF_CORDB, LL_INFO1000, "DRCT::THTML:: wait set empty after sweep.\n" ); |
| 1498 | |
| 1499 | // We are holding Debugger lock (Look at the SuppressRelease on the DoHelperThreadDuty) |
| 1500 | // The debugger lock will be released on the Continue event which we will then |
| 1501 | // exit the loop. |
| 1502 | |
| 1503 | // There are no more threads to wait for, so go ahead and send the sync complete event. |
| 1504 | m_debugger->SuspendComplete(); |
| 1505 | dwWaitTimeout = INFINITE; |
| 1506 | |
| 1507 | // Note: we hold the thread store lock now and debugger lock... |
| 1508 | _ASSERTE(ThreadStore::HoldingThreadStore() || g_fProcessDetach); |
| 1509 | |
| 1510 | } |
| 1511 | } |
| 1512 | |
| 1513 | LExit: |
| 1514 | |
| 1515 | STRESS_LOG0(LF_CORDB, LL_INFO1000, "DRCT::THTML:: Exiting.\n" ); |
| 1516 | } |
| 1517 | |
| 1518 | |
| 1519 | |
| 1520 | // |
| 1521 | // This is the thread's real thread proc. It simply calls to the |
| 1522 | // thread proc on the RCThread object. |
| 1523 | // |
| 1524 | /*static*/ DWORD WINAPI DebuggerRCThread::ThreadProcRemote(LPVOID) |
| 1525 | { |
| 1526 | // We just wrap create a local thread and we're outta here |
| 1527 | WRAPPER_NO_CONTRACT; |
| 1528 | |
| 1529 | ClrFlsSetThreadType(ThreadType_DbgHelper); |
| 1530 | |
| 1531 | LOG((LF_CORDB, LL_EVERYTHING, "ThreadProcRemote called\n" )); |
| 1532 | #ifdef _DEBUG |
| 1533 | dbgOnly_IdentifySpecialEEThread(); |
| 1534 | #endif |
| 1535 | |
| 1536 | // this method can be called both by a local createthread or a remote create thread |
| 1537 | // so we must use the g_RCThread global to find the (unique!) this pointer |
| 1538 | // we cannot count on the parameter. |
| 1539 | |
| 1540 | DebuggerRCThread* t = (DebuggerRCThread*)g_pRCThread; |
| 1541 | |
| 1542 | // This remote thread is created by the debugger process |
| 1543 | // and so its ACLs will reflect permissions for the user running |
| 1544 | // the debugger. If this process is running in the context of a |
| 1545 | // different user then this (the now running) process will not be |
| 1546 | // able to do operations on that (remote) thread. |
| 1547 | // |
| 1548 | // To avoid this problem, if we are the remote thread, then |
| 1549 | // we simply launch a new, local, thread right here and let |
| 1550 | // the remote thread die. This new thread is created the same |
| 1551 | // way as always, and since it is created by this process |
| 1552 | // this process will be able to synchronize with it and so forth |
| 1553 | |
| 1554 | t->Start(); // this thread is remote, we must start a new thread |
| 1555 | |
| 1556 | return 0; |
| 1557 | } |
| 1558 | |
| 1559 | // |
| 1560 | // This is the thread's real thread proc. It simply calls to the |
| 1561 | // thread proc on the RCThread object. |
| 1562 | // |
| 1563 | /*static*/ DWORD WINAPI DebuggerRCThread::ThreadProcStatic(LPVOID) |
| 1564 | { |
| 1565 | // We just wrap the instance method DebuggerRCThread::ThreadProc |
| 1566 | WRAPPER_NO_CONTRACT; |
| 1567 | |
| 1568 | BEGIN_SO_INTOLERANT_CODE_NO_THROW_CHECK_THREAD_FORCE_SO(); |
| 1569 | |
| 1570 | ClrFlsSetThreadType(ThreadType_DbgHelper); |
| 1571 | |
| 1572 | LOG((LF_CORDB, LL_EVERYTHING, "ThreadProcStatic called\n" )); |
| 1573 | |
| 1574 | #ifdef _DEBUG |
| 1575 | dbgOnly_IdentifySpecialEEThread(); |
| 1576 | #endif |
| 1577 | |
| 1578 | DebuggerRCThread* t = (DebuggerRCThread*)g_pRCThread; |
| 1579 | |
| 1580 | t->ThreadProc(); // this thread is local, go and become the helper |
| 1581 | |
| 1582 | END_SO_INTOLERANT_CODE; |
| 1583 | |
| 1584 | return 0; |
| 1585 | } |
| 1586 | |
| 1587 | RCThreadLazyInit * DebuggerRCThread::GetLazyData() |
| 1588 | { |
| 1589 | return g_pDebugger->GetRCThreadLazyData(); |
| 1590 | } |
| 1591 | |
| 1592 | |
| 1593 | // |
| 1594 | // Start actually creates and starts the RC thread. It waits for the thread |
| 1595 | // to come up and perform initial synchronization with the Debugger |
| 1596 | // Interface before returning. |
| 1597 | // |
| 1598 | HRESULT DebuggerRCThread::Start(void) |
| 1599 | { |
| 1600 | CONTRACTL |
| 1601 | { |
| 1602 | SO_INTOLERANT; |
| 1603 | NOTHROW; |
| 1604 | GC_NOTRIGGER; |
| 1605 | } |
| 1606 | CONTRACTL_END; |
| 1607 | |
| 1608 | HRESULT hr = S_OK; |
| 1609 | |
| 1610 | LOG((LF_CORDB, LL_EVERYTHING, "DebuggerRCThread::Start called...\n" )); |
| 1611 | |
| 1612 | DWORD helperThreadId; |
| 1613 | |
| 1614 | if (m_thread != NULL) |
| 1615 | { |
| 1616 | LOG((LF_CORDB, LL_EVERYTHING, "DebuggerRCThread::Start declined to start another helper thread...\n" )); |
| 1617 | return S_OK; |
| 1618 | } |
| 1619 | |
| 1620 | Debugger::DebuggerLockHolder debugLockHolder(m_debugger); |
| 1621 | |
| 1622 | if (m_thread == NULL) |
| 1623 | { |
| 1624 | // Create suspended so that we can sniff the tid before the thread actually runs. |
| 1625 | // This may not be before the native thread-create event, but should be before everything else. |
| 1626 | // Note: strange as it may seem, the Right Side depends on us |
| 1627 | // using CreateThread to create the helper thread here. If you |
| 1628 | // ever change this to some other thread creation routine, you |
| 1629 | // need to update the logic in process.cpp where we discover the |
| 1630 | // helper thread on CREATE_THREAD_DEBUG_EVENTs... |
| 1631 | m_thread = CreateThread(NULL, 0, DebuggerRCThread::ThreadProcStatic, |
| 1632 | NULL, CREATE_SUSPENDED, &helperThreadId ); |
| 1633 | |
| 1634 | if (m_thread == NULL) |
| 1635 | { |
| 1636 | LOG((LF_CORDB, LL_EVERYTHING, "DebuggerRCThread failed, err=%d\n" , GetLastError())); |
| 1637 | hr = HRESULT_FROM_GetLastError(); |
| 1638 | |
| 1639 | } |
| 1640 | else |
| 1641 | { |
| 1642 | LOG((LF_CORDB, LL_EVERYTHING, "DebuggerRCThread start was successful, id=%d\n" , helperThreadId)); |
| 1643 | } |
| 1644 | |
| 1645 | // This gets published immediately. |
| 1646 | DebuggerIPCControlBlock* dcb = GetDCB(); |
| 1647 | PREFIX_ASSUME(dcb != NULL); |
| 1648 | dcb->m_realHelperThreadId = helperThreadId; |
| 1649 | |
| 1650 | #ifdef _DEBUG |
| 1651 | // Record the OS Thread ID for debugging purposes. |
| 1652 | m_DbgHelperThreadOSTid = helperThreadId ; |
| 1653 | #endif |
| 1654 | |
| 1655 | if (m_thread != NULL) |
| 1656 | { |
| 1657 | ResumeThread(m_thread); |
| 1658 | } |
| 1659 | |
| 1660 | } |
| 1661 | |
| 1662 | // unlock debugger lock is implied. |
| 1663 | |
| 1664 | return hr; |
| 1665 | } |
| 1666 | |
| 1667 | |
| 1668 | //--------------------------------------------------------------------------------------- |
| 1669 | // |
| 1670 | // Stop causes the RC thread to stop receiving events and exit. |
| 1671 | // It does not wait for it to exit before returning (hence "AsyncStop" instead of "Stop"). |
| 1672 | // |
| 1673 | // Return Value: |
| 1674 | // Always S_OK at the moment. |
| 1675 | // |
| 1676 | //--------------------------------------------------------------------------------------- |
| 1677 | HRESULT DebuggerRCThread::AsyncStop(void) |
| 1678 | { |
| 1679 | CONTRACTL |
| 1680 | { |
| 1681 | SO_INTOLERANT; |
| 1682 | NOTHROW; |
| 1683 | GC_NOTRIGGER; |
| 1684 | |
| 1685 | #ifdef _TARGET_X86_ |
| 1686 | PRECONDITION(!ThisIsHelperThreadWorker()); |
| 1687 | #else |
| 1688 | PRECONDITION(!ThisIsHelperThreadWorker()); |
| 1689 | #endif |
| 1690 | } |
| 1691 | CONTRACTL_END; |
| 1692 | |
| 1693 | HRESULT hr = S_OK; |
| 1694 | |
| 1695 | m_run = FALSE; |
| 1696 | |
| 1697 | // We need to get the helper thread out of its wait loop. So ping the thread-control event. |
| 1698 | // (Don't ping RSEA since that event should be used only for IPC communication). |
| 1699 | // Don't bother waiting for it to exit. |
| 1700 | SetEvent(this->m_threadControlEvent); |
| 1701 | |
| 1702 | return hr; |
| 1703 | } |
| 1704 | |
| 1705 | //--------------------------------------------------------------------------------------- |
| 1706 | // |
| 1707 | // This method checks that the runtime offset has been loaded, and if not, loads it. |
| 1708 | // |
| 1709 | //--------------------------------------------------------------------------------------- |
| 1710 | HRESULT inline DebuggerRCThread::EnsureRuntimeOffsetsInit(IpcTarget ipcTarget) |
| 1711 | { |
| 1712 | CONTRACTL |
| 1713 | { |
| 1714 | SO_INTOLERANT; |
| 1715 | NOTHROW; |
| 1716 | GC_NOTRIGGER; |
| 1717 | |
| 1718 | PRECONDITION(ThisMaybeHelperThread()); |
| 1719 | } |
| 1720 | CONTRACTL_END; |
| 1721 | |
| 1722 | HRESULT hr = S_OK; |
| 1723 | |
| 1724 | if (m_rgfInitRuntimeOffsets[ipcTarget] == true) |
| 1725 | { |
| 1726 | hr = SetupRuntimeOffsets(m_pDCB); |
| 1727 | _ASSERTE(SUCCEEDED(hr)); // throws on failure |
| 1728 | |
| 1729 | // RuntimeOffsets structure is setup. |
| 1730 | m_rgfInitRuntimeOffsets[ipcTarget] = false; |
| 1731 | } |
| 1732 | |
| 1733 | return hr; |
| 1734 | } |
| 1735 | |
| 1736 | // |
| 1737 | // Call this function to tell the rc thread that we need the runtime offsets re-initialized at the next avaliable time. |
| 1738 | // |
| 1739 | void DebuggerRCThread::NeedRuntimeOffsetsReInit(IpcTarget i) |
| 1740 | { |
| 1741 | LIMITED_METHOD_CONTRACT; |
| 1742 | |
| 1743 | m_rgfInitRuntimeOffsets[i] = true; |
| 1744 | } |
| 1745 | |
| 1746 | //--------------------------------------------------------------------------------------- |
| 1747 | // |
| 1748 | // Send an debug event to the Debugger. This may be either a notification |
| 1749 | // or a reply to a debugger query. |
| 1750 | // |
| 1751 | // Arguments: |
| 1752 | // iTarget - which connection. This must be IPC_TARGET_OUTOFPROC. |
| 1753 | // |
| 1754 | // Return Value: |
| 1755 | // S_OK on success |
| 1756 | // |
| 1757 | // Notes: |
| 1758 | // SendIPCEvent is used by the Debugger object to send IPC events to |
| 1759 | // the Debugger Interface. It waits for acknowledgement from the DI |
| 1760 | // before returning. |
| 1761 | // |
| 1762 | // This assumes that the event send buffer has been properly |
| 1763 | // filled in. All it does it wake up the DI and let it know that its |
| 1764 | // safe to copy the event out of this process. |
| 1765 | // |
| 1766 | // This function may block indefinitely if the controlling debugger |
| 1767 | // suddenly went away. |
| 1768 | // |
| 1769 | // @dbgtodo inspection - this is all a nop around SendRawEvent! |
| 1770 | // |
| 1771 | //--------------------------------------------------------------------------------------- |
| 1772 | HRESULT DebuggerRCThread::SendIPCEvent() |
| 1773 | { |
| 1774 | CONTRACTL |
| 1775 | { |
| 1776 | SO_NOT_MAINLINE; |
| 1777 | NOTHROW; |
| 1778 | GC_NOTRIGGER; // duh, we're in preemptive.. |
| 1779 | |
| 1780 | if (m_debugger->m_isBlockedOnGarbageCollectionEvent) |
| 1781 | { |
| 1782 | // |
| 1783 | // If m_debugger->m_isBlockedOnGarbageCollectionEvent is true, then it must be reporting |
| 1784 | // either the BeforeGarbageCollection event or the AfterGarbageCollection event |
| 1785 | // The thread is in preemptive mode during BeforeGarbageCollection |
| 1786 | // The thread is in cooperative mode during AfterGarbageCollection |
| 1787 | // In either case, the thread mode doesn't really matter because GC has already taken control |
| 1788 | // of execution. |
| 1789 | // |
| 1790 | // Despite the fact that we are actually in preemptive mode during BeforeGarbageCollection, |
| 1791 | // because IsGCThread() is true, the EEContract::DoCheck() will happily accept the fact we are |
| 1792 | // testing for MODE_COOPERATIVE. |
| 1793 | // |
| 1794 | MODE_COOPERATIVE; |
| 1795 | } |
| 1796 | else |
| 1797 | { |
| 1798 | if (ThisIsHelperThreadWorker()) |
| 1799 | { |
| 1800 | // When we're stopped, the helper could actually be contracted as either mode-cooperative |
| 1801 | // or mode-preemptive! |
| 1802 | // If we're the helper thread, we're only sending events while we're stopped. |
| 1803 | // Our callers will be mode-cooperative, so call this mode_cooperative to avoid a bunch |
| 1804 | // of unncessary contract violations. |
| 1805 | MODE_COOPERATIVE; |
| 1806 | } |
| 1807 | else |
| 1808 | { |
| 1809 | // Managed threads sending debug events should always be in preemptive mode. |
| 1810 | MODE_PREEMPTIVE; |
| 1811 | } |
| 1812 | } |
| 1813 | PRECONDITION(ThisMaybeHelperThread()); |
| 1814 | } |
| 1815 | CONTRACTL_END; |
| 1816 | |
| 1817 | |
| 1818 | // one right side |
| 1819 | _ASSERTE(m_debugger->ThreadHoldsLock()); |
| 1820 | |
| 1821 | HRESULT hr = S_OK; |
| 1822 | |
| 1823 | // All the initialization is already done in code:DebuggerRCThread.Init, |
| 1824 | // so we can just go ahead and send the event. |
| 1825 | |
| 1826 | DebuggerIPCEvent* pManagedEvent = GetIPCEventSendBuffer(); |
| 1827 | |
| 1828 | STRESS_LOG2(LF_CORDB, LL_INFO1000, "D::SendIPCEvent %s to outofproc appD 0x%x,\n" , |
| 1829 | IPCENames::GetName(pManagedEvent->type), |
| 1830 | VmPtrToCookie(pManagedEvent->vmAppDomain)); |
| 1831 | |
| 1832 | // increase the debug counter |
| 1833 | DbgLog((DebuggerIPCEventType)(pManagedEvent->type & DB_IPCE_TYPE_MASK)); |
| 1834 | |
| 1835 | g_pDebugger->SendRawEvent(pManagedEvent); |
| 1836 | |
| 1837 | return hr; |
| 1838 | } |
| 1839 | |
| 1840 | // |
| 1841 | // Return true if the helper thread is up & running |
| 1842 | // |
| 1843 | bool DebuggerRCThread::IsRCThreadReady() |
| 1844 | { |
| 1845 | LIMITED_METHOD_CONTRACT; |
| 1846 | |
| 1847 | if (GetDCB() == NULL) |
| 1848 | { |
| 1849 | return false; |
| 1850 | } |
| 1851 | |
| 1852 | int idHelper = GetDCB()->m_helperThreadId; |
| 1853 | |
| 1854 | // The simplest check. If the threadid isn't set, we're not ready. |
| 1855 | if (idHelper == 0) |
| 1856 | { |
| 1857 | LOG((LF_CORDB, LL_EVERYTHING, "DRCT::IsReady - Helper not ready since DCB says id = 0.\n" )); |
| 1858 | return false; |
| 1859 | } |
| 1860 | |
| 1861 | // a more subtle check. It's possible the thread was up, but then |
| 1862 | // an bad call to ExitProcess suddenly terminated the helper thread, |
| 1863 | // leaving the threadid still non-0. So check the actual thread object |
| 1864 | // and make sure it's still around. |
| 1865 | int ret = WaitForSingleObject(m_thread, 0); |
| 1866 | LOG((LF_CORDB, LL_EVERYTHING, "DRCT::IsReady - wait(0x%x)=%d, GetLastError() = %d\n" , m_thread, ret, GetLastError())); |
| 1867 | |
| 1868 | if (ret != WAIT_TIMEOUT) |
| 1869 | { |
| 1870 | return false; |
| 1871 | } |
| 1872 | |
| 1873 | return true; |
| 1874 | } |
| 1875 | |
| 1876 | |
| 1877 | HRESULT DebuggerRCThread::ReDaclEvents(PSECURITY_DESCRIPTOR pSecurityDescriptor) |
| 1878 | { |
| 1879 | LIMITED_METHOD_CONTRACT; |
| 1880 | |
| 1881 | #ifndef FEATURE_PAL |
| 1882 | if (m_pDCB != NULL) |
| 1883 | { |
| 1884 | if (m_pDCB->m_rightSideEventAvailable) |
| 1885 | { |
| 1886 | if (SetKernelObjectSecurity(m_pDCB->m_rightSideEventAvailable, |
| 1887 | DACL_SECURITY_INFORMATION, |
| 1888 | pSecurityDescriptor) == 0) |
| 1889 | { |
| 1890 | // failed! |
| 1891 | return HRESULT_FROM_GetLastError(); |
| 1892 | } |
| 1893 | } |
| 1894 | if (m_pDCB->m_rightSideEventRead) |
| 1895 | { |
| 1896 | if (SetKernelObjectSecurity(m_pDCB->m_rightSideEventRead, |
| 1897 | DACL_SECURITY_INFORMATION, |
| 1898 | pSecurityDescriptor) == 0) |
| 1899 | { |
| 1900 | // failed! |
| 1901 | return HRESULT_FROM_GetLastError(); |
| 1902 | } |
| 1903 | } |
| 1904 | } |
| 1905 | #endif // FEATURE_PAL |
| 1906 | |
| 1907 | return S_OK; |
| 1908 | } |
| 1909 | |
| 1910 | |
| 1911 | // |
| 1912 | // A normal thread may hit a stack overflow and so we want to do |
| 1913 | // any stack-intensive work on the Helper thread so that we don't |
| 1914 | // use up the grace memory. |
| 1915 | // Note that DoFavor will block until the fp is executed |
| 1916 | // |
| 1917 | void DebuggerRCThread::DoFavor(FAVORCALLBACK fp, void * pData) |
| 1918 | { |
| 1919 | CONTRACTL |
| 1920 | { |
| 1921 | SO_INTOLERANT; |
| 1922 | NOTHROW; |
| 1923 | GC_TRIGGERS; |
| 1924 | |
| 1925 | PRECONDITION(!ThisIsHelperThreadWorker()); |
| 1926 | |
| 1927 | #ifdef PREFAST |
| 1928 | // Prefast issue |
| 1929 | // error C2664: 'CHECK CheckPointer(TypeHandle,IsNullOK)' : cannot convert parameter 1 from |
| 1930 | // 'DebuggerRCThread::FAVORCALLBACK' to 'TypeHandle' |
| 1931 | #else |
| 1932 | PRECONDITION(CheckPointer(fp)); |
| 1933 | PRECONDITION(CheckPointer(pData, NULL_OK)); |
| 1934 | #endif |
| 1935 | } |
| 1936 | CONTRACTL_END; |
| 1937 | |
| 1938 | // We are being called on managed thread only. |
| 1939 | // |
| 1940 | |
| 1941 | // We'll have problems if another thread comes in and |
| 1942 | // deletes the RCThread object on us while we're in this call. |
| 1943 | if (IsRCThreadReady()) |
| 1944 | { |
| 1945 | // If the helper thread calls this, we deadlock. |
| 1946 | // (Since we wait on an event that only the helper thread sets) |
| 1947 | _ASSERTE(GetRCThreadId() != GetCurrentThreadId()); |
| 1948 | |
| 1949 | // Only lock if we're waiting on the helper thread. |
| 1950 | // This should be the only place the FavorLock is used. |
| 1951 | // Note this is never called on the helper thread. |
| 1952 | CrstHolder ch(GetFavorLock()); |
| 1953 | |
| 1954 | SetFavorFnPtr(fp, pData); |
| 1955 | |
| 1956 | // Our main message loop operating on the Helper thread will |
| 1957 | // pickup that event, call the fp, and set the Read event |
| 1958 | SetEvent(GetFavorAvailableEvent()); |
| 1959 | |
| 1960 | LOG((LF_CORDB, LL_INFO10000, "DRCT::DF - Waiting on FavorReadEvent for favor 0x%08x\n" , fp)); |
| 1961 | |
| 1962 | // Wait for either the FavorEventRead to be set (which means that the favor |
| 1963 | // was executed by the helper thread) or the helper thread's handle (which means |
| 1964 | // that the helper thread exited without doing the favor, so we should do it) |
| 1965 | // |
| 1966 | // Note we are assuming that there's only 2 ways the helper thread can exit: |
| 1967 | // 1) Someone calls ::ExitProcess, killing all threads. That will kill us too, so we're "ok". |
| 1968 | // 2) Someone calls Stop(), causing the helper to exit gracefully. That's ok too. The helper |
| 1969 | // didn't execute the Favor (else the FREvent would have been set first) and so we can. |
| 1970 | // |
| 1971 | // Beware of problems: |
| 1972 | // 1) If the helper can block, we may deadlock. |
| 1973 | // 2) If the helper can exit magically (or if we change the Wait to include a timeout) , |
| 1974 | // the helper thread may have not executed the favor, partially executed the favor, |
| 1975 | // or totally executed the favor but not yet signaled the FavorReadEvent. We don't |
| 1976 | // know what it did, so we don't know what we can do; so we're in an unstable state. |
| 1977 | |
| 1978 | const HANDLE waitset [] = { GetFavorReadEvent(), m_thread }; |
| 1979 | |
| 1980 | // the favor worker thread will require a transition to cooperative mode in order to complete its work and we will |
| 1981 | // wait for the favor to complete before terminating the process. if there is a GC in progress the favor thread |
| 1982 | // will be blocked and if the thread requesting the favor is in cooperative mode we'll deadlock, so we switch to |
| 1983 | // preemptive mode before waiting for the favor to complete (see Dev11 72349). |
| 1984 | GCX_PREEMP(); |
| 1985 | |
| 1986 | DWORD ret = WaitForMultipleObjectsEx( |
| 1987 | NumItems(waitset), |
| 1988 | waitset, |
| 1989 | FALSE, |
| 1990 | INFINITE, |
| 1991 | FALSE |
| 1992 | ); |
| 1993 | |
| 1994 | DWORD wn = (ret - WAIT_OBJECT_0); |
| 1995 | if (wn == 0) // m_FavorEventRead |
| 1996 | { |
| 1997 | // Favor was executed, nothing to do here. |
| 1998 | LOG((LF_CORDB, LL_INFO10000, "DRCT::DF - favor 0x%08x finished, ret = %d\n" , fp, ret)); |
| 1999 | } |
| 2000 | else |
| 2001 | { |
| 2002 | LOG((LF_CORDB, LL_INFO10000, "DRCT::DF - lost helper thread during wait, " |
| 2003 | "doing favor 0x%08x on current thread\n" , fp)); |
| 2004 | |
| 2005 | // Since we have no timeout, we shouldn't be able to get an error on the wait, |
| 2006 | // but just in case ... |
| 2007 | _ASSERTE(ret != WAIT_FAILED); |
| 2008 | _ASSERTE((wn == 1) && !"DoFavor - unexpected return from WFMO" ); |
| 2009 | |
| 2010 | // Thread exited without doing favor, so execute it on our thread. |
| 2011 | // If we're here because of a stack overflow, this may push us over the edge, |
| 2012 | // but there's nothing else we can really do |
| 2013 | (*fp)(pData); |
| 2014 | |
| 2015 | ResetEvent(GetFavorAvailableEvent()); |
| 2016 | } |
| 2017 | |
| 2018 | // m_fpFavor & m_pFavorData are meaningless now. We could set them |
| 2019 | // to NULL, but we may as well leave them as is to leave a trail. |
| 2020 | |
| 2021 | } |
| 2022 | else |
| 2023 | { |
| 2024 | LOG((LF_CORDB, LL_INFO10000, "DRCT::DF - helper thread not ready, " |
| 2025 | "doing favor 0x%08x on current thread\n" , fp)); |
| 2026 | // If helper isn't ready yet, go ahead and execute the favor |
| 2027 | // on the callee's space |
| 2028 | (*fp)(pData); |
| 2029 | } |
| 2030 | |
| 2031 | // Drop a log message so that we know if we survived a stack overflow or not |
| 2032 | LOG((LF_CORDB, LL_INFO10000, "DRCT::DF - Favor 0x%08x completed successfully\n" , fp)); |
| 2033 | } |
| 2034 | |
| 2035 | |
| 2036 | // |
| 2037 | // SendIPCReply simply indicates to the Right Side that a reply to a |
| 2038 | // two-way event is ready to be read and that the last event sent from |
| 2039 | // the Right Side has been fully processed. |
| 2040 | // |
| 2041 | // NOTE: this assumes that the event receive buffer has been properly |
| 2042 | // filled in. All it does it wake up the DI and let it know that its |
| 2043 | // safe to copy the event out of this process. |
| 2044 | // |
| 2045 | HRESULT DebuggerRCThread::SendIPCReply() |
| 2046 | { |
| 2047 | HRESULT hr = S_OK; |
| 2048 | |
| 2049 | #ifdef LOGGING |
| 2050 | DebuggerIPCEvent* event = GetIPCEventReceiveBuffer(); |
| 2051 | |
| 2052 | LOG((LF_CORDB, LL_INFO10000, "D::SIPCR: replying with %s.\n" , |
| 2053 | IPCENames::GetName(event->type))); |
| 2054 | #endif |
| 2055 | |
| 2056 | #if !defined(FEATURE_DBGIPC_TRANSPORT_VM) |
| 2057 | BOOL succ = SetEvent(m_pDCB->m_rightSideEventRead); |
| 2058 | if (!succ) |
| 2059 | { |
| 2060 | hr = CORDBDebuggerSetUnrecoverableWin32Error(m_debugger, 0, false); |
| 2061 | } |
| 2062 | #else // !FEATURE_DBGIPC_TRANSPORT_VM |
| 2063 | hr = g_pDbgTransport->SendEvent(GetIPCEventReceiveBuffer()); |
| 2064 | if (FAILED(hr)) |
| 2065 | { |
| 2066 | m_debugger->UnrecoverableError(hr, |
| 2067 | 0, |
| 2068 | __FILE__, |
| 2069 | __LINE__, |
| 2070 | false); |
| 2071 | } |
| 2072 | #endif // !FEATURE_DBGIPC_TRANSPORT_VM |
| 2073 | |
| 2074 | return hr; |
| 2075 | } |
| 2076 | |
| 2077 | // |
| 2078 | // EarlyHelperThreadDeath handles the case where the helper |
| 2079 | // thread has been ripped out from underneath of us by |
| 2080 | // ExitProcess or TerminateProcess. These calls are bad, whacking |
| 2081 | // all threads except the caller in the process. This can happen, for |
| 2082 | // instance, when an app calls ExitProcess. All threads are wacked, |
| 2083 | // the main thread calls all DLL main's, and the EE starts shutting |
| 2084 | // down in its DLL main with the helper thread terminated. |
| 2085 | // |
| 2086 | void DebuggerRCThread::EarlyHelperThreadDeath(void) |
| 2087 | { |
| 2088 | LOG((LF_CORDB, LL_INFO10000, "DRCT::EHTD\n" )); |
| 2089 | |
| 2090 | // If we ever spun up a thread... |
| 2091 | if (m_thread != NULL && m_pDCB) |
| 2092 | { |
| 2093 | Debugger::DebuggerLockHolder debugLockHolder(m_debugger); |
| 2094 | |
| 2095 | m_pDCB->m_helperThreadId = 0; |
| 2096 | |
| 2097 | LOG((LF_CORDB, LL_INFO10000, "DRCT::EHTD helperThreadId\n" )); |
| 2098 | // dbgLockHolder goes out of scope - implicit Release |
| 2099 | } |
| 2100 | } |
| 2101 | |
| 2102 | |