This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
/
shimprocess.cpp
1900 lines (1645 loc) · 66.3 KB
/
shimprocess.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//*****************************************************************************
// File: ShimProcess.cpp
//
//
// The V3 ICD debugging APIs have a lower abstraction level than V2.
// This provides V2 ICD debugging functionality on top of the V3 debugger object.
//*****************************************************************************
#include "stdafx.h"
#include "safewrap.h"
#include "check.h"
#include <limits.h>
#include "shimpriv.h"
#if !defined(FEATURE_CORESYSTEM)
#include <tlhelp32.h>
#endif
//---------------------------------------------------------------------------------------
//
// Ctor for a ShimProcess
//
// Notes:
// See InitializeDataTarget in header for details of how to instantiate a ShimProcess and hook it up.
// Initial ref count is 0. This is the convention used int the RS, and it plays well with semantics
// like immediately assigning to a smart pointer (which will bump the count up to 1).
ShimProcess::ShimProcess() :
m_ref(0),
m_fFirstManagedEvent(false),
m_fInCreateProcess(false),
m_fInLoadModule(false),
m_fIsInteropDebugging(false),
m_fIsDisposed(false),
m_loaderBPReceived(false)
{
m_ShimLock.Init("ShimLock", RSLock::cLockReentrant, RSLock::LL_SHIM_LOCK);
m_ShimProcessDisposeLock.Init(
"ShimProcessDisposeLock",
RSLock::cLockReentrant | RSLock::cLockNonDbgApi,
RSLock::LL_SHIM_PROCESS_DISPOSE_LOCK);
m_eventQueue.Init(&m_ShimLock);
m_pShimCallback.Assign(new ShimProxyCallback(this)); // Throws
m_fNeedFakeAttachEvents = false;
m_ContinueStatusChangedData.Clear();
m_pShimStackWalkHashTable = new ShimStackWalkHashTable();
m_pDupeEventsHashTable = new DuplicateCreationEventsHashTable();
m_machineInfo.Clear();
m_markAttachPendingEvent = WszCreateEvent(NULL, TRUE, FALSE, NULL);
if (m_markAttachPendingEvent == NULL)
{
ThrowLastError();
}
m_terminatingEvent = WszCreateEvent(NULL, TRUE, FALSE, NULL);
if (m_terminatingEvent == NULL)
{
ThrowLastError();
}
}
//---------------------------------------------------------------------------------------
//
// ShimProcess dtor. Invoked when reference count goes to 0.
//
// Assumptions:
// Dtors should not do any interesting work. If this object has been initialized,
// then call Dispose() first.
//
//
ShimProcess::~ShimProcess()
{
// Expected that this was either already disposed first, or not initialized.
_ASSERTE(m_pWin32EventThread == NULL);
_ASSERTE(m_ShimProcessDisposeLock.IsInit());
m_ShimProcessDisposeLock.Destroy();
if (m_markAttachPendingEvent != NULL)
{
CloseHandle(m_markAttachPendingEvent);
m_markAttachPendingEvent = NULL;
}
if (m_terminatingEvent != NULL)
{
CloseHandle(m_terminatingEvent);
m_terminatingEvent = NULL;
}
// Dtor will release m_pLiveDataTarget
}
//---------------------------------------------------------------------------------------
//
// Part of initialization to hook up to process.
//
// Arguments:
// pProcess - debuggee object to connect to. Maybe null if part of shutdown.
//
// Notes:
// This will take a strong reference to the process object.
// This is part of the initialization phase.
// This should only be called once.
//
//
void ShimProcess::SetProcess(ICorDebugProcess * pProcess)
{
PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(NULL);
// Data-target should already be setup before we try to connect to a process.
_ASSERTE(m_pLiveDataTarget != NULL);
// Reference is kept by m_pProcess;
m_pIProcess.Assign(pProcess);
// Get the private shim hooks. This just exists to access private functionality that has not
// yet been promoted to the ICorDebug interfaces.
m_pProcess = static_cast<CordbProcess *>(pProcess);
if (pProcess != NULL)
{
// Verify that DataTarget + new process have the same pid?
_ASSERTE(m_pProcess->GetProcessDescriptor()->m_Pid == m_pLiveDataTarget->GetPid());
}
}
//---------------------------------------------------------------------------------------
//
// Create a Data-Target around the live process.
//
// Arguments:
// processId - OS process ID to connect to. Must be a local, same platform, process.
//
// Return Value:
// S_OK on success.
//
// Assumptions:
// This is part of the initialization dance.
//
// Notes:
// Only call this once, during the initialization dance.
//
HRESULT ShimProcess::InitializeDataTarget(const ProcessDescriptor * pProcessDescriptor)
{
_ASSERTE(m_pLiveDataTarget == NULL);
HRESULT hr = BuildPlatformSpecificDataTarget(GetMachineInfo(), pProcessDescriptor, &m_pLiveDataTarget);
if (FAILED(hr))
{
_ASSERTE(m_pLiveDataTarget == NULL);
return hr;
}
m_pLiveDataTarget->HookContinueStatusChanged(ShimProcess::ContinueStatusChanged, this);
// Ref on pDataTarget is now 1.
_ASSERTE(m_pLiveDataTarget != NULL);
return S_OK;
}
//---------------------------------------------------------------------------------------
//
// Determines if current thread is the Win32 Event Thread
//
// Return Value:
// True iff current thread is win32 event thread, else false.
//
// Notes:
// The win32 event thread is created by code:ShimProcess::CreateAndStartWin32ET
//
bool ShimProcess::IsWin32EventThread()
{
return (m_pWin32EventThread != NULL) && m_pWin32EventThread->IsWin32EventThread();
}
//---------------------------------------------------------------------------------------
//
// Add a reference
//
void ShimProcess::AddRef()
{
InterlockedIncrement(&m_ref);
}
//---------------------------------------------------------------------------------------
//
// Release a reference.
//
// Notes:
// When ref goes to 0, object is deleted.
//
void ShimProcess::Release()
{
LONG ref = InterlockedDecrement(&m_ref);
if (ref == 0)
{
delete this;
}
}
//---------------------------------------------------------------------------------------
//
// Dispose (Neuter) the object.
//
//
// Assumptions:
// This is called to gracefully shutdown the ShimProcess object.
// This must be called before destruction if the object was initialized.
//
// Notes:
// This will release all external resources, including getting the win32 event thread to exit.
// This can safely be called multiple times.
//
void ShimProcess::Dispose()
{
// Serialize Dispose with any other locked access to the shim. This helps
// protect against the debugger detaching while we're in the middle of
// doing stuff on the ShimProcess
RSLockHolder lockHolder(&m_ShimProcessDisposeLock);
m_fIsDisposed = true;
// Can't shut down the W32ET if we're on it.
_ASSERTE(!IsWin32EventThread());
m_eventQueue.DeleteAll();
if (m_pWin32EventThread != NULL)
{
// This will block waiting for the thread to exit gracefully.
m_pWin32EventThread->Stop();
delete m_pWin32EventThread;
m_pWin32EventThread = NULL;
}
if (m_pLiveDataTarget != NULL)
{
m_pLiveDataTarget->Dispose();
m_pLiveDataTarget.Clear();
}
m_pIProcess.Clear();
m_pProcess = NULL;
_ASSERTE(m_ShimLock.IsInit());
m_ShimLock.Destroy();
if (m_pShimStackWalkHashTable != NULL)
{
// The hash table should be empty by now. ClearAllShimStackWalk() should have been called.
_ASSERTE(m_pShimStackWalkHashTable->GetCount() == 0);
delete m_pShimStackWalkHashTable;
m_pShimStackWalkHashTable = NULL;
}
if (m_pDupeEventsHashTable != NULL)
{
if (m_pDupeEventsHashTable->GetCount() > 0)
{
// loop through all the entries in the hash table, remove them, and delete them
for (DuplicateCreationEventsHashTable::Iterator pCurElem = m_pDupeEventsHashTable->Begin(),
pEndElem = m_pDupeEventsHashTable->End();
pCurElem != pEndElem;
pCurElem++)
{
DuplicateCreationEventEntry * pEntry = *pCurElem;
delete pEntry;
}
m_pDupeEventsHashTable->RemoveAll();
}
delete m_pDupeEventsHashTable;
m_pDupeEventsHashTable = NULL;
}
}
//---------------------------------------------------------------------------------------
// Track (and close) file handles from debug events.
//
// Arguments:
// pEvent - debug event
//
// Notes:
// Some debug events introduce file handles that the debugger needs to track and
// close on other debug events. For example, the LoadDll,CreateProcess debug
// events both give back a file handle that the debugger must close. This is generally
// done on the corresponding UnloadDll/ExitProcess debug events.
//
// Since we won't use the file handles, we'll just close them as soon as we get them.
// That way, we don't need to remember any state.
void ShimProcess::TrackFileHandleForDebugEvent(const DEBUG_EVENT * pEvent)
{
CONTRACTL
{
THROWS;
}
CONTRACTL_END;
HANDLE hFile = NULL;
switch(pEvent->dwDebugEventCode)
{
//
// Events that add a file handle
//
case CREATE_PROCESS_DEBUG_EVENT:
hFile = pEvent->u.CreateProcessInfo.hFile;
CloseHandle(hFile);
break;
case LOAD_DLL_DEBUG_EVENT:
hFile = pEvent->u.LoadDll.hFile;
CloseHandle(hFile);
break;
}
}
//---------------------------------------------------------------------------------------
// ThreadProc helper to drain event queue.
//
// Arguments:
// parameter - thread proc parameter, an ICorDebugProcess*
//
// Returns
// 0.
//
// Notes:
// This is useful when the shim queued a fake managed event (such as Control+C)
// and needs to get the debuggee to synchronize in order to start dispatching events.
// @dbgtodo sync: this will likely change as we iron out the Synchronization feature crew.
//
// We do this in a new thread proc to avoid thread restrictions:
// Can't call this on win32 event thread because that can't send the IPC event to
// make the aysnc-break request.
// Can't call this on the RCET because that can't send an async-break (see SendIPCEvent for details)
// So we just spin up a new thread to do the work.
//---------------------------------------------------------------------------------------
DWORD WINAPI CallStopGoThreadProc(LPVOID parameter)
{
ICorDebugProcess* pProc = reinterpret_cast<ICorDebugProcess *>(parameter);
// We expect these operations to succeed; but if they do fail, there's nothing we can really do about it.
// If it fails on process exit/neuter/detach, then it would be ignorable.
HRESULT hr;
// Calling Stop + Continue will synchronize the process and force any queued events to be called.
// Stop is synchronous and will block until debuggee is synchronized.
hr = pProc->Stop(INFINITE);
SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
// Continue will resume the debuggee. If there are queued events (which we expect in this case)
// then continue will drain the event queue instead of actually resuming the process.
hr = pProc->Continue(FALSE);
SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
// This thread just needs to trigger an event dispatch. Now that it's done that, it can exit.
return 0;
}
//---------------------------------------------------------------------------------------
// Does default event handling for native debug events.
//
// Arguments:
// pEvent - IN event ot handle
// pdwContinueStatus - IN /OUT - continuation status for event.
//
// Assumptions:
// Called when target is stopped. Caller still needs to Continue the debug event.
// This is called on the win32 event thread.
//
// Notes:
// Some native events require extra work before continuing. Eg, skip loader
// breakpoint, close certain handles, etc.
// This is only called in the manage-only case. In the interop-case, the
// debugger will get and handle these native debug events.
void ShimProcess::DefaultEventHandler(
const DEBUG_EVENT * pEvent,
DWORD * pdwContinueStatus)
{
CONTRACTL
{
THROWS;
}
CONTRACTL_END;
//
// Loader breakpoint
//
BOOL fFirstChance;
const EXCEPTION_RECORD * pRecord = NULL;
if (IsExceptionEvent(pEvent, &fFirstChance, &pRecord))
{
DWORD dwThreadId = GetThreadId(pEvent);
switch(pRecord->ExceptionCode)
{
case STATUS_BREAKPOINT:
{
if (!m_loaderBPReceived)
{
m_loaderBPReceived = true;
// Clear the loader breakpoint
*pdwContinueStatus = DBG_CONTINUE;
// After loader-breakpoint, notify that managed attach can begin.
// This is done to trigger a synchronization. The shim
// can then send the fake attach events once the target
// is synced.
// @dbgtodo sync: not needed once shim can
// work on sync APIs.
m_pProcess->QueueManagedAttachIfNeeded(); // throws
}
}
break;
/*
// If we handle the Ctlr-C event here and send the notification to the debugger, then we may break pre-V4
// behaviour because the debugger may handle the event and intercept the handlers registered in the debuggee
// process. So don't handle the event here and let the debuggee process handle it instead. See Dev10 issue
// 846455 for more info.
//
// However, when the re-arch is completed, we will need to work with VS to define what the right behaviour
// should be. We don't want to rely on in-process code to handle the Ctrl-C event.
case DBG_CONTROL_C:
{
// Queue a fake managed Ctrl+C event.
m_pShimCallback->ControlCTrap(GetProcess());
// Request an Async Break
// This is on Win32 Event Thread, so we can't call Stop / Continue.
// Instead, spawn a new threead, and have that call Stop/Continue, which
// will get the RCET to drain the event queue and dispatch the ControlCTrap we just queued.
{
DWORD dwDummyId;
CreateThread(NULL,
0,
CallStopGoThreadProc,
(LPVOID) GetProcess(),
0,
&dwDummyId);
}
// We don't worry about suspending the Control-C thread right now. The event is
// coming asynchronously, and so it's ok if the debuggee slips forward while
// we try to do a managed async break.
// Clear the control-C event.
*pdwContinueStatus = DBG_CONTINUE;
}
break;
*/
}
}
// Native debugging APIs have an undocumented expectation that you clear for OutputDebugString.
if (pEvent->dwDebugEventCode == OUTPUT_DEBUG_STRING_EVENT)
{
*pdwContinueStatus = DBG_CONTINUE;
}
//
// File handles.
//
TrackFileHandleForDebugEvent(pEvent);
}
//---------------------------------------------------------------------------------------
// Determine if we need to change the continue status
//
// Returns:
// True if the continue status was changed. Else false.
//
// Assumptions:
// This is single-threaded, which is enforced by it only be called on the win32et.
// The shim guarnatees only 1 outstanding debug-event at a time.
//
// Notes:
// See code:ShimProcess::ContinueStatusChangedWorker for big picture.
// Continue status is changed from a data-target callback which invokes
// code:ShimProcess::ContinueStatusChangedWorker.
// Call code:ShimProcess::ContinueStatusChangedData::Clear to clear the 'IsSet' bit.
//
bool ShimProcess::ContinueStatusChangedData::IsSet()
{
return m_dwThreadId != 0;
}
//---------------------------------------------------------------------------------------
// Clears the bit marking
//
// Assumptions:
// This is single-threaded, which is enforced by it only be called on the win32et.
// The shim guarantees only 1 outstanding debug-event at a time.
//
// Notes:
// See code:ShimProcess::ContinueStatusChangedWorker for big picture.
// This makes code:ShimProcess::ContinueStatusChangedData::IsSet return false.
// This can safely be called multiple times in a row.
//
void ShimProcess::ContinueStatusChangedData::Clear()
{
m_dwThreadId = 0;
}
//---------------------------------------------------------------------------------------
// Callback invoked from data-target when continue status is changed.
//
// Arguments:
// pUserData - data we supplied to the callback. a 'this' pointer.
// dwThreadId - the tid whose continue status is changing
// dwContinueStatus - the new continue status.
//
// Notes:
//
// Static
HRESULT ShimProcess::ContinueStatusChanged(void * pUserData, DWORD dwThreadId, CORDB_CONTINUE_STATUS dwContinueStatus)
{
ShimProcess * pThis = reinterpret_cast<ShimProcess *>(pUserData);
return pThis->ContinueStatusChangedWorker(dwThreadId, dwContinueStatus);
}
//---------------------------------------------------------------------------------------
// Real worker callback invoked from data-target when continue status is changed.
//
// Arguments:
// dwThreadId - the tid whose continue status is changing
// dwContinueStatus - the new continue status.
//
// Notes:
// ICorDebugProcess4::Filter returns an initial continue status (heavily biased to 'gn').
// Some ICorDebug operations may need to change the continue status that filter returned.
// For example, on windows, hijacking a thread at an unhandled exception would need to
// change the status to 'gh' (since continuing 2nd chance exception 'gn' will tear down the
// process and the hijack would never execute).
//
// Such operations will invoke into the data-target (code:ICorDebugMutableDataTarget::ContinueStatusChanged)
// to notify the debugger that the continue status was changed.
//
// The shim only executes such operations on the win32-event thread in a small window between
// WaitForDebugEvent and Continue. Therefore, we know:
// * the callback must come on the Win32EventThread (which means our handling the callback is
// single-threaded.
// * We only have 1 outstanding debug event to worry about at a time. This simplifies our tracking.
//
// The shim tracks the outstanding change request in m_ContinueStatusChangedData.
HRESULT ShimProcess::ContinueStatusChangedWorker(DWORD dwThreadId, CORDB_CONTINUE_STATUS dwContinueStatus)
{
// Should only be set once. This is only called on the win32 event thread, which protects against races.
_ASSERTE(IsWin32EventThread());
_ASSERTE(!m_ContinueStatusChangedData.IsSet());
m_ContinueStatusChangedData.m_dwThreadId = dwThreadId;
m_ContinueStatusChangedData.m_status = dwContinueStatus;
// Setting dwThreadId to non-zero should now mark this as set.
_ASSERTE(m_ContinueStatusChangedData.IsSet());
return S_OK;
}
//---------------------------------------------------------------------------------------
//
// Add a duplicate creation event entry for the specified key.
//
// Arguments:
// pKey - the key of the entry to be added; this is expected to be an
// ICDProcess/ICDAppDomain/ICDThread/ICDAssembly/ICDModule
//
// Assumptions:
// pKey is really an interface pointer of one of the types mentioned above
//
// Notes:
// We have to keep track of which creation events we have sent already because some runtime data structures
// are discoverable through enumeration before they send their creation events. As a result, we may have
// faked up a creation event for a data structure during attach, and then later on get another creation
// event for the same data structure. VS is not resilient in the face of multiple creation events for
// the same data structure.
//
// Needless to say this is a problem in attach scenarios only. However, keep in mind that for CoreCLR,
// launch really is early attach. For early attach, we get three creation events up front: a create
// process, a create appdomain, and a create thread.
//
void ShimProcess::AddDuplicateCreationEvent(void * pKey)
{
NewHolder<DuplicateCreationEventEntry> pEntry(new DuplicateCreationEventEntry(pKey));
m_pDupeEventsHashTable->Add(pEntry);
pEntry.SuppressRelease();
}
//---------------------------------------------------------------------------------------
//
// Check whether the specified key exists in the hash table. If so, remove it.
//
// Arguments:
// pKey - the key of the entry to check; this is expected to be an
// ICDProcess/ICDAppDomain/ICDThread/ICDAssembly/ICDModule
//
// Return Value:
// Returns true if the entry exists. The entry will have been removed because we can't have more than two
// duplicates for any given event.
//
// Assumptions:
// pKey is really an interface pointer of one of the types mentioned above
//
// Notes:
// See code:ShimProcess::AddDuplicateCreationEvent.
//
bool ShimProcess::RemoveDuplicateCreationEventIfPresent(void * pKey)
{
// We only worry about duplicate events in attach scenarios.
if (GetAttached())
{
// Only do the check if the hash table actually contains entries.
if (m_pDupeEventsHashTable->GetCount() > 0)
{
// Check if this is a dupe.
DuplicateCreationEventEntry * pResult = m_pDupeEventsHashTable->Lookup(pKey);
if (pResult != NULL)
{
// This is a dupe. We can't have a dupe twice, so remove it.
// This will help as a bit of optimization, since we will no longer check the hash table if
// its count reaches 0.
m_pDupeEventsHashTable->Remove(pKey);
delete pResult;
return true;
}
}
}
return false;
}
//---------------------------------------------------------------------------------------
// Gets the exception record format of the host
//
// Returns:
// The CorDebugRecordFormat for the host architecture.
//
// Notes:
// This corresponds to the definition EXCEPTION_RECORD on the host-architecture.
// It can be passed into ICorDebugProcess4::Filter.
CorDebugRecordFormat GetHostExceptionRecordFormat()
{
#if defined(BIT64)
return FORMAT_WINDOWS_EXCEPTIONRECORD64;
#else
return FORMAT_WINDOWS_EXCEPTIONRECORD32;
#endif
}
//---------------------------------------------------------------------------------------
// Main event handler for native debug events. Must also ensure Continue is called.
//
// Arguments:
// pEvent - debug event to handle
//
// Assumptions:
// Caller did a Flush() if needed.
//
// Notes:
// The main Handle native debug events.
// This must call back into ICD to let ICD filter the debug event (in case it's a managed notification).
//
// If we're interop-debugging (V2), then the debugger is expecting the debug events. In that case,
// we go through the V2 interop-debugging logic to queue / dispatch the events.
// If we're managed-only debugging, then the shim provides a default handler for the native debug.
// This includes some basic work (skipping the loader breakpoint, close certain handles, etc).
//---------------------------------------------------------------------------------------
HRESULT ShimProcess::HandleWin32DebugEvent(const DEBUG_EVENT * pEvent)
{
_ASSERTE(IsWin32EventThread());
//
// If this is an exception event, then we need to feed it into the CLR.
//
BOOL dwFirstChance = FALSE;
const EXCEPTION_RECORD * pRecord = NULL;
const DWORD dwThreadId = GetThreadId(pEvent);
bool fContinueNow = true;
// If true, we're continuing (unhandled) a 2nd-chance exception
bool fExceptionGoingUnhandled = false;
//
const DWORD kDONTCARE = 0;
DWORD dwContinueStatus = kDONTCARE;
if (IsExceptionEvent(pEvent, &dwFirstChance, &pRecord))
{
// As a diagnostic aid we can configure the debugger to assert when the debuggee does DebugBreak()
#ifdef DEBUG
static ConfigDWORD config;
DWORD fAssert = config.val(CLRConfig::INTERNAL_DbgAssertOnDebuggeeDebugBreak);
if (fAssert)
{
// If we got a 2nd-chance breakpoint, then it's extremely likely that it's from an
// _ASSERTE in the target and we really want to know about it now before we kill the
// target. The debuggee will exit once we continue (unless we are mixed-mode debugging), so alert now.
// This assert could be our only warning of various catastrophic failures in the left-side.
if (!dwFirstChance && (pRecord->ExceptionCode == STATUS_BREAKPOINT) && !m_fIsInteropDebugging)
{
DWORD pid = (m_pLiveDataTarget == NULL) ? 0 : m_pLiveDataTarget->GetPid();
CONSISTENCY_CHECK_MSGF(false,
("Unhandled breakpoint exception in debuggee (pid=%d (0x%x)) on thread %d(0x%x)\n"
"This may mean there was an assert in the debuggee on that thread.\n"
"\n"
"You should attach to that process (non-invasively) and get a callstack of that thread.\n"
"(This assert only occurs when CLRConfig::INTERNAL_DebuggerAssertOnDebuggeeDebugBreak is set)\n",
pid, pid, dwThreadId,dwThreadId));
}
}
#endif
// We pass the Shim's proxy callback object, which will just take the callbacks and queue them
// to an event-queue in the shim. When we get the sync-complete event, the shim
// will then drain the event queue and dispatch the events to the user's callback object.
const DWORD dwFlags = dwFirstChance ? 1 : 0;
m_ContinueStatusChangedData.Clear();
// If ICorDebug doesn't care about this exception, it will leave dwContinueStatus unchanged.
RSExtSmartPtr<ICorDebugProcess4> pProcess4;
GetProcess()->QueryInterface(IID_ICorDebugProcess4, (void**) &pProcess4);
HRESULT hrFilter = pProcess4->Filter(
(const BYTE*) pRecord,
sizeof(EXCEPTION_RECORD),
GetHostExceptionRecordFormat(),
dwFlags,
dwThreadId,
m_pShimCallback,
&dwContinueStatus);
if (FAILED(hrFilter))
{
// Filter failed (eg. DAC couldn't be loaded), return the
// error so it can become an unrecoverable error.
return hrFilter;
}
// For unhandled exceptions, hijacking if needed.
if (!dwFirstChance)
{
// May invoke data-target callback (which may call code:ShimProcess::ContinueStatusChanged) to change continue status.
if (!m_pProcess->HijackThreadForUnhandledExceptionIfNeeded(dwThreadId))
{
// We decided not to hijack, so this exception is going to go unhandled
fExceptionGoingUnhandled = true;
}
if (m_ContinueStatusChangedData.IsSet())
{
_ASSERTE(m_ContinueStatusChangedData.m_dwThreadId == dwThreadId);
// Claiming this now means we won't do any other processing on the exception event.
// This means the interop-debugging logic will never see 2nd-chance managed exceptions.
dwContinueStatus = m_ContinueStatusChangedData.m_status;
}
}
}
// Do standard event handling, including Handling loader-breakpoint,
// and callback into CordbProcess for Attach if needed.
HRESULT hrIgnore = S_OK;
EX_TRY
{
// For NonClr notifications, allow extra processing.
// This includes both non-exception events, and exception events that aren't
// specific CLR debugging services notifications.
if (dwContinueStatus == kDONTCARE)
{
if (m_fIsInteropDebugging)
{
// Interop-debugging logic will handle the continue.
fContinueNow = false;
#if defined(FEATURE_INTEROP_DEBUGGING)
// @dbgtodo interop: All the interop-debugging logic is still in CordbProcess.
// Call back into that. This will handle Continuing the debug event.
m_pProcess->HandleDebugEventForInteropDebugging(pEvent);
#else
_ASSERTE(!"Interop debugging not supported");
#endif
}
else
{
dwContinueStatus = DBG_EXCEPTION_NOT_HANDLED;
// For managed-only debugging, there's no user handler for native debug events,
// and so we still need to do some basic work on certain debug events.
DefaultEventHandler(pEvent, &dwContinueStatus);
// This is the managed-only case. No reason to keep the target win32 frozen, so continue it immediately.
_ASSERTE(fContinueNow);
}
}
}
EX_CATCH_HRESULT(hrIgnore);
// Dont' expect errors here (but could probably return it up to become an
// unrecoverable error if necessary). We still want to call Continue thought.
SIMPLIFYING_ASSUMPTION_SUCCEEDED(hrIgnore);
//
// Continue the debuggee if needed.
//
if (fContinueNow)
{
BOOL fContinueOk = GetNativePipeline()->ContinueDebugEvent(
GetProcessId(pEvent),
dwThreadId,
dwContinueStatus);
(void)fContinueOk; //prevent "unused variable" error from GCC
SIMPLIFYING_ASSUMPTION(fContinueOk);
if (fExceptionGoingUnhandled)
{
_ASSERTE(dwContinueStatus == DBG_EXCEPTION_NOT_HANDLED);
// We just passed a 2nd-chance exception back to the OS which may have now invoked
// Windows error-reporting logic which suspended all threads in the target. Since we're
// still debugging and may want to break, inspect state and even detach (eg. to attach
// a different sort of debugger that can handle the exception) we need to let our threads run.
// Note that when WER auto-invokes a debugger it doesn't suspend threads, so it doesn't really
// make sense for them to be suspended now when a debugger is already attached.
// A better solution may be to suspend this faulting thread before continuing the event, do an
// async-break and give the debugger a notification of an unhandled exception. But this will require
// an ICorDebug API change, and also makes it harder to reliably get the WER dialog box once we're
// ready for it.
// Unfortunately we have to wait for WerFault.exe to start and actually suspend the threads, and
// there doesn't appear to be any better way than to just sleep for a little here. In practice 200ms
// seems like more than enough, but this is so uncommon of a scenario that a half-second delay
// (just to be safe) isn't a problem.
// Provide an undocumented knob to turn this behavior off in the very rare case it's not what we want
// (eg. we're trying to debug something that races with crashing / terminating the process on multiple
// threads)
static ConfigDWORD config;
DWORD fSkipResume = config.val(CLRConfig::UNSUPPORTED_DbgDontResumeThreadsOnUnhandledException);
if (!fSkipResume)
{
::Sleep(500);
hrIgnore = GetNativePipeline()->EnsureThreadsRunning();
SIMPLIFYING_ASSUMPTION_SUCCEEDED(hrIgnore);
}
}
}
return S_OK;
}
// Trivial accessor to get the event queue.
ManagedEventQueue * ShimProcess::GetManagedEventQueue()
{
return &m_eventQueue;
}
// Combines GetManagedEventQueue() and Dequeue() into a single function
// that holds m_ShimProcessDisposeLock for the duration
ManagedEvent * ShimProcess::DequeueManagedEvent()
{
// Serialize this function with Dispoe()
RSLockHolder lockHolder(&m_ShimProcessDisposeLock);
if (m_fIsDisposed)
return NULL;
return m_eventQueue.Dequeue();
}
// Trivial accessor to get Shim's proxy callback object.
ShimProxyCallback * ShimProcess::GetShimCallback()
{
return m_pShimCallback;
}
// Trivial accessor to get the ICDProcess for the debuggee.
// A ShimProcess object can then provide V2 functionality by building it on V3 functionality
// exposed by the ICDProcess object.
ICorDebugProcess * ShimProcess::GetProcess()
{
return m_pIProcess;
}
// Trivial accessor to get the data-target for the debuggee.
// The data-target lets us access the debuggee, especially reading debuggee memory.
ICorDebugMutableDataTarget * ShimProcess::GetDataTarget()
{
return m_pLiveDataTarget;
};
// Trivial accessor to get the raw native event pipeline.
// In V3, ICorDebug no longer owns the event thread and it does not own the event pipeline either.
INativeEventPipeline * ShimProcess::GetNativePipeline()
{
return m_pWin32EventThread->GetNativePipeline();
}
// Trivial accessor to expose the W32ET thread to the CordbProcess so that it can emulate V2 behavior.
// In V3, ICorDebug no longer owns the event thread and it does not own the event pipeline either.
// The Win32 Event Thread is the only thread that can use the native pipeline
// see code:ShimProcess::GetNativePipeline.
CordbWin32EventThread * ShimProcess::GetWin32EventThread()
{
return m_pWin32EventThread;
}
// Trivial accessor to mark whether we're interop-debugging.
// Retreived via code:ShimProcess::IsInteropDebugging
void ShimProcess::SetIsInteropDebugging(bool fIsInteropDebugging)
{
m_fIsInteropDebugging = fIsInteropDebugging;
}
// Trivial accessor to check if we're interop-debugging.
// This affects how we handle native debug events.
// The significant usage of this is in code:ShimProcess::HandleWin32DebugEvent
bool ShimProcess::IsInteropDebugging()
{
return m_fIsInteropDebugging;
}
//---------------------------------------------------------------------------------------
// Begin queueing the fake attach events.
//
// Notes:
// See code:ShimProcess::QueueFakeAttachEvents for more about "fake attach events".
//
// This marks that we need to send fake attach events, and queus a CreateProcess.
// Caller calls code:ShimProcess::QueueFakeAttachEventsIfNeeded to finish queuing
// the rest of the fake attach events.
void ShimProcess::BeginQueueFakeAttachEvents()
{
m_fNeedFakeAttachEvents = true;
// Put a fake CreateProcess event in the queue.
// This will not be drained until we get a Sync-Complete from the Left-side.
GetShimCallback()->QueueCreateProcess(GetProcess());
AddDuplicateCreationEvent(GetProcess());
}
//---------------------------------------------------------------------------------------
// potentially Queue fake attach events like we did in V2.
//
// Arguments:
// fRealCreateProcessEvent - true if the shim is about to dispatch a real create process event (as opposed
// to one faked up by the shim itself)
//
// Notes:
// See code:ShimProcess::QueueFakeAttachEvents for details.
void ShimProcess::QueueFakeAttachEventsIfNeeded(bool fRealCreateProcessEvent)
{
// This was set high in code:ShimProcess::BeginQueueFakeAttachEvents
if (!m_fNeedFakeAttachEvents)
{
return;
}
m_fNeedFakeAttachEvents = false;
// If the first event we get after attaching is a create process event, then this is an early attach
// scenario and we don't need to queue any fake attach events.
if (!fRealCreateProcessEvent)
{
HRESULT hr = S_OK;
EX_TRY
{