-
Notifications
You must be signed in to change notification settings - Fork 4.9k
/
Copy pathrsthread.cpp
10915 lines (9153 loc) · 344 KB
/
rsthread.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.
//*****************************************************************************
//
// File: rsthread.cpp
//
//*****************************************************************************
#include "stdafx.h"
#include "primitives.h"
#include <float.h>
#include <tls.h>
// Stack-based holder for RSPTRs that we allocated to give to the LS.
// If LS successfully takes ownership of them, then call SuppressRelease().
// Else, dtor will free them up.
// This is using a table protected by the ProcessLock().
template <class T>
class RsPtrHolder
{
T * m_pObject;
RsPointer<T> m_ptr;
public:
RsPtrHolder(T* pObject)
{
_ASSERTE(pObject != NULL);
m_ptr.AllocHandle(pObject->GetProcess(), pObject);
m_pObject = pObject;
}
// If owner didn't call SuppressRelease() to take ownership, then have dtor free it.
~RsPtrHolder()
{
if (!m_ptr.IsNull())
{
// @dbgtodo synchronization - push this up. Note that since this is in a dtor;
// need to order it well against RSLockHolder.
RSLockHolder lockHolder(m_pObject->GetProcess()->GetProcessLock());
T* pObjTest = m_ptr.UnWrapAndRemove(m_pObject->GetProcess());
(void)pObjTest; //prevent "unused variable" error from GCC
_ASSERTE(pObjTest == m_pObject);
}
}
RsPointer<T> Ptr()
{
return m_ptr;
}
void SuppressRelease()
{
m_ptr = RsPointer<T>::NullPtr();
}
};
/* ------------------------------------------------------------------------- *
* Managed Thread classes
* ------------------------------------------------------------------------- */
//---------------------------------------------------------------------------------------
//
// Instantiate a CordbThread object, which represents a managed thread.
//
// Arguments:
// process - non-null process object that this thread lives in.
// id - OS thread id of this thread.
// handle - OS Handle to the native thread in the debuggee.
//
//---------------------------------------------------------------------------------------
CordbThread::CordbThread(CordbProcess * pProcess, VMPTR_Thread vmThread) :
CordbBase(pProcess,
VmPtrToCookie(vmThread),
enumCordbThread),
m_pContext(NULL),
m_fContextFresh(false),
m_pAppDomain(NULL),
m_debugState(THREAD_RUN),
m_fFramesFresh(false),
m_fFloatStateValid(false),
m_floatStackTop(0),
m_fException(false),
m_EnCRemapFunctionIP(NULL),
m_userState(kInvalidUserState),
m_hCachedThread(INVALID_HANDLE_VALUE),
m_hCachedOutOfProcThread(INVALID_HANDLE_VALUE)
{
m_fHasUnhandledException = FALSE;
m_pExceptionRecord = NULL;
// Thread id may be a "fake" OS id for a CLRHosted thread.
m_vmThreadToken = vmThread;
// This id must be unique for the thread. V2 uses the current OS thread id.
// If we ever support fibers, then we need to use something more unique than that.
m_dwUniqueID = pProcess->GetDAC()->GetUniqueThreadID(vmThread); // may throw
LOG((LF_CORDB, LL_INFO1000, "CT::CT new thread 0x%p vmptr=0x%p id=0x%x\n",
this, m_vmThreadToken, m_dwUniqueID));
// Unique ID should never be 0.
_ASSERTE(m_dwUniqueID != 0);
m_vmLeftSideContext = VMPTR_CONTEXT::NullPtr();
m_vmExcepObjHandle = VMPTR_OBJECTHANDLE::NullPtr();
#if defined(_DEBUG)
for (unsigned int i = 0;
i < (sizeof(m_floatValues) / sizeof(m_floatValues[0]));
i++)
{
m_floatValues[i] = 0;
}
#endif
// Set AppDomain
VMPTR_AppDomain vmAppDomain = pProcess->GetDAC()->GetCurrentAppDomain(vmThread);
m_pAppDomain = pProcess->LookupOrCreateAppDomain(vmAppDomain);
_ASSERTE(m_pAppDomain != NULL);
}
CordbThread::~CordbThread()
{
// We've already been neutered, thus we don't need to call CleanupStack().
// That will have neutered + cleared frames + chains.
_ASSERTE(IsNeutered());
// Cleared in neuter
_ASSERTE(m_pContext == NULL);
_ASSERTE(m_hCachedThread == INVALID_HANDLE_VALUE);
_ASSERTE(m_pExceptionRecord == NULL);
}
// Neutered by the CordbProcess
void CordbThread::Neuter()
{
if (IsNeutered())
{
return;
}
_ASSERTE(GetProcess()->ThreadHoldsProcessLock());
delete m_pExceptionRecord;
m_pExceptionRecord = NULL;
// Neuter frames & Chains.
CleanupStack();
if (m_hCachedThread != INVALID_HANDLE_VALUE)
{
CloseHandle(m_hCachedThread);
m_hCachedThread = INVALID_HANDLE_VALUE;
}
if( m_pContext != NULL )
{
delete [] m_pContext;
m_pContext = NULL;
}
ClearStackFrameCache();
CordbBase::Neuter();
}
HRESULT CordbThread::QueryInterface(REFIID id, void ** ppInterface)
{
if (id == IID_ICorDebugThread)
{
*ppInterface = static_cast<ICorDebugThread *>(this);
}
else if (id == IID_ICorDebugThread2)
{
*ppInterface = static_cast<ICorDebugThread2 *>(this);
}
else if (id == IID_ICorDebugThread3)
{
*ppInterface = static_cast<ICorDebugThread3*>(this);
}
else if (id == IID_ICorDebugThread4)
{
*ppInterface = static_cast<ICorDebugThread4*>(this);
}
else if (id == IID_ICorDebugThread5)
{
*ppInterface = static_cast<ICorDebugThread5*>(this);
}
else if (id == IID_IUnknown)
{
*ppInterface = static_cast<IUnknown *>(static_cast<ICorDebugThread *>(this));
}
else
{
*ppInterface = NULL;
return E_NOINTERFACE;
}
ExternalAddRef();
return S_OK;
}
#ifdef _DEBUG
// Callback helper for code:CordbThread::DbgAssertThreadDeleted
//
// Arguments:
// vmThread - thread from enumeration of threads in the target.
// pUserData - the CordbThread for the thread that's deleted
//
// static
void CordbThread::DbgAssertThreadDeletedCallback(VMPTR_Thread vmThread, void * pUserData)
{
CordbThread * pThis = reinterpret_cast<CordbThread *>(pUserData);
INTERNAL_DAC_CALLBACK(pThis->GetProcess());
VMPTR_Thread vmThreadDelete = pThis->m_vmThreadToken;
CONSISTENCY_CHECK_MSGF((vmThread != vmThreadDelete),
("A Thread Exit event was sent, but it still shows up in the enumeration.\n vmThreadDelete=%p\n",
VmPtrToCookie(vmThreadDelete)));
}
// Debug-only helper to Assert that this thread is no longer discoverable in DacDbi enumerations
// This is designed to enforce the code:IDacDbiInterface#Enumeration rules for enumerations.
void CordbThread::DbgAssertThreadDeleted()
{
// Enumerate through all threads and ensure the deleted threads don't show up.
GetProcess()->GetDAC()->EnumerateThreads(
DbgAssertThreadDeletedCallback,
this);
}
#endif // _DEBUG
//---------------------------------------------------------------------------------------
// Mark that this thread has an unhandled native exception on it.
//
// Arguments
// pRecord - exception record of 2nd-chance exception that we're hijacking at. This will
// get deep copied into the CordbThread object in case it's needed for hijacking later.
//
// Notes:
// This bit is cleared in code:CordbThread::HijackForUnhandledException
void CordbThread::SetUnhandledNativeException(const EXCEPTION_RECORD * pExceptionRecord)
{
m_fHasUnhandledException = true;
if (m_pExceptionRecord == NULL)
{
m_pExceptionRecord = new EXCEPTION_RECORD(); // throws
}
memcpy(m_pExceptionRecord, pExceptionRecord, sizeof(EXCEPTION_RECORD));
}
//-----------------------------------------------------------------------------
// Returns true if the thread has an unhandled exception
// This is during the window after code:CordbThread::SetUnhandledNativeException is called,
// but before code:CordbThread::HijackForUnhandledException
bool CordbThread::HasUnhandledNativeException()
{
return m_fHasUnhandledException;
}
//---------------------------------------------------------------------------------------
// Determine if the thread's latest exception is a managed exception
//
// Notes:
// The CLR's UnhandledExceptionFilter has to make this same determination.
//
BOOL CordbThread::IsThreadExceptionManaged()
{
CONTRACTL
{
THROWS;
}
CONTRACTL_END;
// A Thread's latest exception is managed if the VM Thread object has a managed object
// for the thread's Current Exception property. The CLR's Exception system is very diligent
// about tracking and clearing the thread's managed exception property. The runtime will clear
// the object if the exception is caught by unmanaged code (it can do this in the 2nd-pass).
// It's the presence of a throwable that makes the difference between a managed
// exception event and an unmanaged exception event.
VMPTR_OBJECTHANDLE vmObject = GetProcess()->GetDAC()->GetCurrentException(m_vmThreadToken);
bool fHasThrowable = !vmObject.IsNull();
return fHasThrowable;
}
// ----------------------------------------------------------------------------
// CordbThread::CreateCordbRegisterSet
//
// Description:
// This is a private hook for the shim to create a CordbRegisterSet for a ShimChain.
//
// Arguments:
// * pContext - the CONTEXT to be converted; this must be the leaf CONTEXT of a chain
// * fLeaf - whether the chain is the leaf chain or not
// * reason - the chain reason; this is needed for legacy reasons (see below)
// * ppRegSet - out parameter; return the newly created ICDRegisterSet
//
// Notes:
// * Note that the fQuickUnwind argument of the ctor of CordbRegisterSet is only true
// for an enter-managed chain. We need to keep the same behaviour here. That's why we need the
// chain reason.
//
void CordbThread::CreateCordbRegisterSet(DT_CONTEXT * pContext,
BOOL fLeaf,
CorDebugChainReason reason,
ICorDebugRegisterSet ** ppRegSet)
{
CONTRACTL
{
THROWS;
}
CONTRACTL_END;
PUBLIC_REENTRANT_API_ENTRY_FOR_SHIM(GetProcess());
IfFailThrow(EnsureThreadIsAlive());
// The CordbRegisterSet is responsible for freeing this memory.
NewHolder<DebuggerREGDISPLAY> pDRD(new DebuggerREGDISPLAY());
// convert the CONTEXT to a DebuggerREGDISPLAY
IDacDbiInterface * pDAC = GetProcess()->GetDAC();
pDAC->ConvertContextToDebuggerRegDisplay(pContext, pDRD, fLeaf);
// create the CordbRegisterSet
RSInitHolder<CordbRegisterSet> pRS(new CordbRegisterSet(pDRD,
this,
(fLeaf == TRUE),
(reason == CHAIN_ENTER_MANAGED),
true));
pDRD.SuppressRelease();
pRS.TransferOwnershipExternal(ppRegSet);
}
// ----------------------------------------------------------------------------
// CordbThread::ConvertFrameForILMethodWithoutMetadata
//
// Description:
// This is a private hook for the shim to convert an ICDFrame into an ICDInternalFrame for a dynamic
// method. There are two cases where we need this:
// 1) In Arrowhead, dynamic methods are exposed as first-class stack frames, not internal frames. Thus,
// the shim needs a way to convert an ICDNativeFrame for a dynamic method in Arrowhead to an
// ICDInternalFrame of type STUBFRAME_LIGHTWEIGHT_FUNCTION in V2. Furthermore, IL stubs,
// which are also considered as a type of dynamic methods, are not exposed in V2 at all.
//
// 2) In V2, PrestubMethodFrames (PMFs) can be exposed as one of two things: a chain of type
// CHAIN_CLASS_INIT in most cases, or an internal frame of type STUBFRAME_LIGHTWEIGHT_FUNCTION if
// the method being jitted is a dynamic method. There is no way to make this distinction at the
// public ICD level.
//
// Arguments:
// * pNativeFrame - the native frame to be converted
// * ppInternalFrame - out parameter; the converted internal frame; could be NULL (see Notes below)
//
// Returns:
// Return TRUE if conversion has occurred. Note that even if the return value is TRUE, ppInternalFrame
// could be NULL. See Notes below.
//
// Notes:
// * There are two main types of dynamic methods: ones which are generated by the runtime itself for
// internal purposes (i.e. IL stubs), and ones which are generated by the user. ppInternalFrame
// is NULL for IL stubs. We need this functionality because IL stubs are not exposed at all in V2.
//
BOOL CordbThread::ConvertFrameForILMethodWithoutMetadata(ICorDebugFrame * pFrame,
ICorDebugInternalFrame2 ** ppInternalFrame2)
{
PUBLIC_REENTRANT_API_ENTRY_FOR_SHIM(GetProcess());
_ASSERTE(ppInternalFrame2 != NULL);
*ppInternalFrame2 = NULL;
HRESULT hr = E_FAIL;
CordbFrame * pRealFrame = CordbFrame::GetCordbFrameFromInterface(pFrame);
CordbInternalFrame * pInternalFrame = pRealFrame->GetAsInternalFrame();
if (pInternalFrame != NULL)
{
// The input is an internal frame.
// Check its frame type.
CorDebugInternalFrameType type;
hr = pInternalFrame->GetFrameType(&type);
IfFailThrow(hr);
if (type != STUBFRAME_JIT_COMPILATION)
{
// No conversion is necessary.
return FALSE;
}
else
{
// We are indeed dealing with a PrestubMethodFrame.
return pInternalFrame->ConvertInternalFrameForILMethodWithoutMetadata(ppInternalFrame2);
}
}
else
{
// The input is a native frame.
CordbNativeFrame * pNativeFrame = pRealFrame->GetAsNativeFrame();
_ASSERTE(pNativeFrame != NULL);
return pNativeFrame->ConvertNativeFrameForILMethodWithoutMetadata(ppInternalFrame2);
}
}
//-----------------------------------------------------------------------------
// Hijack a thread at an unhandled exception. This lets it execute the
// CLR's Unhandled Exception Filter (which will send the managed 2nd-chance exception event)
//
// Notes:
// OS will not execute Unhandled Exception Filter (UEF) when debugger is attached.
// The CLR's UEF does useful work, like dispatching 2nd-chance managed exception event
// and allowing Func-eval and Continuable Exceptions for unhandled exceptions.
// So hijack the thread, and the hijack will then execute the CLR's UEF just
// like the OS would.
void CordbThread::HijackForUnhandledException()
{
CONTRACTL
{
THROWS;
}
CONTRACTL_END;
_ASSERTE(m_pExceptionRecord != NULL);
_ASSERTE(m_fHasUnhandledException);
m_fHasUnhandledException = false;
ULONG32 dwThreadId = GetVolatileOSThreadID();
// Note that the data-target is not atomic, and we have no rollback mechanism.
// We have to do several writes. If the data-target fails the writes half-way through the
// target will be inconsistent.
// We don't bother remembering the original context. LS hijack will have the
// context on its stack and will pass it to RS just like it does for filter-context.
GetProcess()->GetDAC()->Hijack(
m_vmThreadToken,
dwThreadId,
m_pExceptionRecord,
NULL, // LS will have the context.
0, // size of context
EHijackReason::kUnhandledException,
NULL,
NULL);
// Notify debugger to clear the exception.
// This will invoke the data-target.
GetProcess()->ContinueStatusChanged(dwThreadId, DBG_CONTINUE);
}
HRESULT CordbThread::GetProcess(ICorDebugProcess ** ppProcess)
{
PUBLIC_API_ENTRY(this);
VALIDATE_POINTER_TO_OBJECT(ppProcess, ICorDebugProcess **);
FAIL_IF_NEUTERED(this);
*ppProcess = GetProcess();
GetProcess()->ExternalAddRef();
return S_OK;
}
// Public implementation of ICorDebugThread::GetID
// Back in V1.0, GetID originally meant the OS thread ID that this managed thread was running on.
// In theory, that can change (fibers, logical thread scheduling, etc). However, in practice, in V1.0, it would
// not. Thus debuggers took a depedency on GetID being constant.
// In V2, this returns an opaque handle that is unique to this thread and stable for this thread's lifetime.
//
// Compare to code:CordbThread::GetVolatileOSThreadID, which returns the actual OS thread Id (which may change).
HRESULT CordbThread::GetID(DWORD * pdwThreadId)
{
PUBLIC_API_ENTRY(this);
VALIDATE_POINTER_TO_OBJECT(pdwThreadId, DWORD *);
FAIL_IF_NEUTERED(this);
*pdwThreadId = GetUniqueId();
return S_OK;
}
// Returns a unique ID that's stable for the life of this thread.
// In a non-hosted scenarios, this can be the OS thread id.
DWORD CordbThread::GetUniqueId()
{
return m_dwUniqueID;
}
// Implementation of public API, ICorDebugThread::GetHandle
// @dbgtodo ICDThread - deprecate in V3, offload to Shim
HRESULT CordbThread::GetHandle(HANDLE * phThreadHandle)
{
PUBLIC_API_ENTRY(this);
VALIDATE_POINTER_TO_OBJECT(phThreadHandle, HANDLE *);
FAIL_IF_NEUTERED(this);
ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
if (GetProcess()->GetShim() == NULL)
{
_ASSERTE(!"CordbThread::GetHandle() should be not be called on the new architecture");
*phThreadHandle = NULL;
return E_NOTIMPL;
}
#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
HRESULT hr = S_OK;
EX_TRY
{
HANDLE hThread;
InternalGetHandle(&hThread); // throws on error
*phThreadHandle = hThread;
}
EX_CATCH_HRESULT(hr);
#else // FEATURE_DBGIPC_TRANSPORT_DI
// In the old SL implementation of Mac debugging, we return a thread handle faked up by the PAL on the Mac.
// The returned handle is meaningless. Here we explicitly return E_NOTIMPL. We plan to deprecate this
// function in Dev10 anyway.
//
// @dbgtodo Mac - Check with VS to see if they need the thread handle, e.g. for waiting on thread
// termination.
HRESULT hr = E_NOTIMPL;
#endif // !FEATURE_DBGIPC_TRANSPORT_DI
return hr;
}
// Note that we can return invalid handle
void CordbThread::InternalGetHandle(HANDLE * phThread)
{
INTERNAL_SYNC_API_ENTRY(GetProcess());
RefreshHandle(phThread);
}
//---------------------------------------------------------------------------------------
//
// This is a simple helper to check if a frame lives on the stack of the current thread.
//
// Arguments:
// pFrame - the stack frame to check
//
// Return Value:
// whether the frame lives on the stack of the current thread
//
// Assumption:
// This function assumes that the stack frames are valid, i.e. the stack frames have not been
// made dirty since the last stackwalk.
//
bool CordbThread::OwnsFrame(CordbFrame * pFrame)
{
// preliminary checking
if ( (pFrame != NULL) &&
(!pFrame->IsNeutered()) &&
(pFrame->m_pThread == this)
)
{
//
// Note that this is one of the two remaining places where we need to use the cached stack frames.
// Theoretically, since this is not an exact check anyway, we could just use the thread's stack
// range instead of looping through all the individual frames. However, since we need to maintain
// the stack frame cache for code:CordbThread::GetActiveFunctions, we might as well use the cache here.
//
// make sure this thread actually have frames to check
if (m_stackFrames.Count() != 0)
{
// get the stack range of this thread
FramePointer fpLeaf = (*(m_stackFrames.Get(0)))->GetFramePointer();
FramePointer fpRoot = (*(m_stackFrames.Get(m_stackFrames.Count() - 1)))->GetFramePointer();
FramePointer fpCurrent = pFrame->GetFramePointer();
// compare the stack range against the frame pointer of the specified frame
if (IsEqualOrCloserToLeaf(fpLeaf, fpCurrent) && IsEqualOrCloserToRoot(fpRoot, fpCurrent))
{
return true;
}
}
}
return false;
}
//---------------------------------------------------------------------------------------
//
// This routine is a internal helper function for ICorDebugThread2::GetTaskId.
//
// Arguments:
// pHandle - return thread handle here after fetching from the left side.
//
// Return Value:
// hr - It can fail with CORDBG_E_THREAD_NOT_SCHEDULED.
//
// Notes:
// This method will most likely be deprecated in V3.0. We can't always return the thread handle.
// For example, what does it mean to return a thread handle in remote debugging scenarios?
//
void CordbThread::RefreshHandle(HANDLE * phThread)
{
// here is where we will put code in to fetch the thread handle from the left side.
// This should only happen when CLRTask is hosted.
// Make sure that we are setting the right HR when thread is being switched out.
THROW_IF_NEUTERED(this);
INTERNAL_SYNC_API_ENTRY(GetProcess());
if (phThread == NULL)
{
ThrowHR(E_INVALIDARG);
}
*phThread = INVALID_HANDLE_VALUE;
IDacDbiInterface * pDAC = GetProcess()->GetDAC();
HANDLE hThread = pDAC->GetThreadHandle(m_vmThreadToken);
_ASSERTE(hThread != INVALID_HANDLE_VALUE);
PREFAST_ASSUME(hThread != NULL);
// need to dup handle here
if (hThread == m_hCachedOutOfProcThread)
{
*phThread = m_hCachedThread;
}
else
{
BOOL fSuccess = TRUE;
if (m_hCachedThread != INVALID_HANDLE_VALUE)
{
// clear the previous cache
CloseHandle(m_hCachedThread);
m_hCachedOutOfProcThread = INVALID_HANDLE_VALUE;
m_hCachedThread = INVALID_HANDLE_VALUE;
}
// now duplicate the out-of-proc handle
fSuccess = DuplicateHandle(GetProcess()->UnsafeGetProcessHandle(),
hThread,
GetCurrentProcess(),
&m_hCachedThread,
0,
FALSE,
DUPLICATE_SAME_ACCESS);
*phThread = m_hCachedThread;
if (fSuccess)
{
m_hCachedOutOfProcThread = hThread;
}
else
{
ThrowLastError();
}
}
} // CordbThread::RefreshHandle
//---------------------------------------------------------------------------------------
//
// This routine sets the debug state of a thread.
//
// Arguments:
// state - The debug state to set to.
//
// Return Value:
// Normal HRESULT semantics.
//
HRESULT CordbThread::SetDebugState(CorDebugThreadState state)
{
PUBLIC_API_ENTRY(this);
FAIL_IF_NEUTERED(this);
ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
LOG((LF_CORDB, LL_INFO1000, "CT::SDS: thread=0x%08x 0x%x, state=%d\n", this, m_id, state));
// @dbgtodo- , sync - decide on how to suspend a thread. V2 leverages synchronization
// (see below). For V3, do we just hard suspend the thread?
if (GetProcess()->GetShim() == NULL)
{
return E_NOTIMPL;
}
HRESULT hr = S_OK;
EX_TRY
{
hr = EnsureThreadIsAlive();
if (SUCCEEDED(hr))
{
// This lets the debugger suspend / resume threads. This is only called when when the
// target is already synchronized. That means all the threads are already suspended. So
// setting the suspend bit here just means that the debugger's continue logic won't resume
// this thread when we do a Continue.
if ((state != THREAD_SUSPEND) && (state != THREAD_RUN))
{
ThrowHR(E_INVALIDARG);
}
IDacDbiInterface * pDAC = GetProcess()->GetDAC();
pDAC->SetDebugState(m_vmThreadToken, state);
m_debugState = state;
}
}
EX_CATCH_HRESULT(hr);
return hr;
}
HRESULT CordbThread::GetDebugState(CorDebugThreadState * pState)
{
PUBLIC_API_ENTRY(this);
FAIL_IF_NEUTERED(this);
ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
VALIDATE_POINTER_TO_OBJECT(pState, CorDebugThreadState *);
*pState = m_debugState;
return S_OK;
}
// Public implementation of ICorDebugThread::GetUserState
// Arguments:
// pState - out parameter; return the user state
//
// Return Value:
// Return S_OK if the operation is successful.
// Return E_INVALIDARG if the out parameter is NULL.
// Return other failure HRs returned by the call to the DDI.
HRESULT CordbThread::GetUserState(CorDebugUserState * pState)
{
PUBLIC_REENTRANT_API_ENTRY(this);
FAIL_IF_NEUTERED(this);
VALIDATE_POINTER_TO_OBJECT(pState, CorDebugUserState *);
ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
HRESULT hr = S_OK;
EX_TRY
{
if (pState == NULL)
{
ThrowHR(E_INVALIDARG);
}
*pState = GetUserState();
}
EX_CATCH_HRESULT(hr);
return hr;
}
//---------------------------------------------------------------------------------------
//
// Retrieve the user state of the current thread.
//
// Notes:
// This caches results between continues. The cache is cleared when the target continues or is flushed.
// See code:CordbThread::CleanupStack, code:CordbThread::MarkStackFramesDirty
//
CorDebugUserState CordbThread::GetUserState()
{
if (m_userState == kInvalidUserState)
{
IDacDbiInterface * pDAC = GetProcess()->GetDAC();
m_userState = pDAC->GetUserState(m_vmThreadToken);
}
return m_userState;
}
//---------------------------------------------------------------------------------------
//
// This routine finds and returns the current exception off of a thread.
//
// Arguments:
// ppExceptionObject - OUT: Space for storing the exception found on the thread as a value.
//
// Return Value:
// Normal HRESULT semantics.
//
HRESULT CordbThread::GetCurrentException(ICorDebugValue ** ppExceptionObject)
{
PUBLIC_API_ENTRY(this);
FAIL_IF_NEUTERED(this);
HRESULT hr = S_OK;
ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
VALIDATE_POINTER_TO_OBJECT(ppExceptionObject, ICorDebugValue **);
*ppExceptionObject = NULL;
EX_TRY
{
if (!HasException())
{
//
// Go to the LS and retrieve any exception object.
//
IDacDbiInterface * pDAC = GetProcess()->GetDAC();
VMPTR_OBJECTHANDLE vmObjHandle = pDAC->GetCurrentException(m_vmThreadToken);
if (vmObjHandle.IsNull())
{
hr = S_FALSE;
}
else
{
#if defined(_DEBUG)
// Since we know an exception is in progress on this thread, our assumption about the
// thread's current AppDomain should be correct
VMPTR_AppDomain vmAppDomain = pDAC->GetCurrentAppDomain(m_vmThreadToken);
_ASSERTE(GetAppDomain()->GetADToken() == vmAppDomain);
#endif // _DEBUG
m_vmExcepObjHandle = vmObjHandle;
}
}
if (hr == S_OK)
{
// We've believe this assert may fire in the wild.
// We've seen m_vmExcepObjHandle null in retail builds after stack overflow.
_ASSERTE(!m_vmExcepObjHandle.IsNull());
ICorDebugReferenceValue * pRefValue = NULL;
hr = CordbReferenceValue::BuildFromGCHandle(GetAppDomain(), m_vmExcepObjHandle, &pRefValue);
*ppExceptionObject = pRefValue;
}
}
EX_CATCH_HRESULT(hr);
return hr;
}
HRESULT CordbThread::ClearCurrentException()
{
PUBLIC_API_ENTRY(this);
FAIL_IF_NEUTERED(this);
ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
// This API is not implemented. For Continuable Exceptions, see InterceptCurrentException.
// @todo - should it return E_NOTIMPL?
return S_OK;
}
HRESULT CordbThread::CreateStepper(ICorDebugStepper ** ppStepper)
{
PUBLIC_API_ENTRY(this);
FAIL_IF_NEUTERED(this);
ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
VALIDATE_POINTER_TO_OBJECT(ppStepper, ICorDebugStepper **);
CordbStepper * pStepper = new (nothrow) CordbStepper(this, NULL);
if (pStepper == NULL)
{
return E_OUTOFMEMORY;
}
pStepper->ExternalAddRef();
*ppStepper = pStepper;
return S_OK;
}
//Returns true if current user state of a thread is USER_WAIT_SLEEP_JOIN
bool CordbThread::IsThreadWaitingOrSleeping()
{
CorDebugUserState userState = m_userState;
if (userState == kInvalidUserState)
{
//If m_userState is not ready, we'll read from DAC only part of it which
//is important for us now, bacuase we don't want possible side effects
//of reading USER_UNSAFE_POINT flag.
//We don't cache the value, because it's potentially incomplete.
IDacDbiInterface *pDAC = GetProcess()->GetDAC();
userState = pDAC->GetPartialUserState(m_vmThreadToken);
}
return (userState & USER_WAIT_SLEEP_JOIN) != 0;
}
//----------------------------------------------------------------------------
// check if the thread is dead
//
// Returns: true if the thread is dead.
//
bool CordbThread::IsThreadDead()
{
return GetProcess()->GetDAC()->IsThreadMarkedDead(m_vmThreadToken);
}
// Helper to return CORDBG_E_BAD_THREAD_STATE if IsThreadDead
//
// Notes:
// IsThreadDead queries the VM Thread's actual state, regardless of what ExitThread
// callbacks have or have not been sent / queued / dispatched.
HRESULT CordbThread::EnsureThreadIsAlive()
{
if (IsThreadDead())
{
return CORDBG_E_BAD_THREAD_STATE;
}
else
{
return S_OK;
}
}
// ----------------------------------------------------------------------------
// CordbThread::EnumerateChains
//
// Description:
// Create and return an ICDChainEnum for enumerating chains on the stack. Since chains have been
// deprecated in Arrowhead, this function returns E_NOTIMPL unless there is a shim.
//
// Arguments:
// * ppChains - out parameter; return the ICDChainEnum
//
// Return Value:
// Return S_OK on success.
// Return E_INVALIDARG if ppChains is NULL.
// Return CORDBG_E_OBJECT_NEUTERED if the CordbThread is neutered.
// Return E_NOTIMPL if there is no shim.
//
HRESULT CordbThread::EnumerateChains(ICorDebugChainEnum ** ppChains)
{
PUBLIC_API_ENTRY(this);
FAIL_IF_NEUTERED(this);
VALIDATE_POINTER_TO_OBJECT(ppChains, ICorDebugChainEnum **);
ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
HRESULT hr = S_OK;
EX_TRY
{
*ppChains = NULL;
if (GetProcess()->GetShim() != NULL)
{
hr = EnsureThreadIsAlive();
if (SUCCEEDED(hr))
{
// use the shim to create an ICDChainEnum
PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(GetProcess());
ShimStackWalk * pSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(this);
pSW->EnumerateChains(ppChains);
}
}
else
{
// This is the Arrowhead case, where ICDChain has been deprecated.
hr = E_NOTIMPL;
}
}
EX_CATCH_HRESULT(hr);
return hr;
}
// ----------------------------------------------------------------------------
// CordbThread::GetActiveChain
//
// Description:
// Retrieve the leaf chain on this thread. Since chains have been deprecated in Arrowhead,
// this function returns E_NOTIMPL unless there is a shim.
//
// Arguments:
// * ppChain - out parameter; return the leaf chain
//
// Return Value:
// Return S_OK on success.
// Return E_INVALIDARG if ppChain is NULL.
// Return CORDBG_E_OBJECT_NEUTERED if the CordbThread is neutered.
// Return E_NOTIMPL if there is no shim.
//