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 | // shimprivate.h |
6 | // |
7 | |
8 | // |
9 | // private header for RS shim which bridges from V2 to V3. |
10 | //***************************************************************************** |
11 | |
12 | #ifndef SHIMPRIV_H |
13 | #define SHIMPRIV_H |
14 | |
15 | #include "helpers.h" |
16 | |
17 | #include "shimdatatarget.h" |
18 | |
19 | #include <shash.h> |
20 | |
21 | // Forward declarations |
22 | class CordbWin32EventThread; |
23 | class Cordb; |
24 | |
25 | class ShimStackWalk; |
26 | class ShimChain; |
27 | class ShimChainEnum; |
28 | class ShimFrameEnum; |
29 | |
30 | // This struct specifies that it's a hash table of ShimStackWalk * using ICorDebugThread as the key. |
31 | struct ShimStackWalkHashTableTraits : public PtrSHashTraits<ShimStackWalk, ICorDebugThread *> {}; |
32 | typedef SHash<ShimStackWalkHashTableTraits> ShimStackWalkHashTable; |
33 | |
34 | |
35 | //--------------------------------------------------------------------------------------- |
36 | // |
37 | // Simple struct for storing a void *. This is to be used with a SHash hash table. |
38 | // |
39 | |
40 | struct DuplicateCreationEventEntry |
41 | { |
42 | public: |
43 | DuplicateCreationEventEntry(void * pKey) : m_pKey(pKey) {}; |
44 | |
45 | // These functions must be defined for DuplicateCreationEventsHashTableTraits. |
46 | void * GetKey() {return m_pKey;}; |
47 | static UINT32 Hash(void * pKey) {return (UINT32)(size_t)pKey;}; |
48 | |
49 | private: |
50 | void * m_pKey; |
51 | }; |
52 | |
53 | // This struct specifies that it's a hash table of DuplicateCreationEventEntry * using a void * as the key. |
54 | // The void * is expected to be an ICDProcess/ICDAppDomain/ICDThread/ICDAssembly/ICDThread interface pointer. |
55 | struct DuplicateCreationEventsHashTableTraits : public PtrSHashTraits<DuplicateCreationEventEntry, void *> {}; |
56 | typedef SHash<DuplicateCreationEventsHashTableTraits> DuplicateCreationEventsHashTable; |
57 | |
58 | // |
59 | // Callback that shim provides, which then queues up the events. |
60 | // |
61 | class ShimProxyCallback : |
62 | public ICorDebugManagedCallback, |
63 | public ICorDebugManagedCallback2, |
64 | public ICorDebugManagedCallback3, |
65 | public ICorDebugManagedCallback4 |
66 | { |
67 | ShimProcess * m_pShim; // weak reference |
68 | LONG m_cRef; |
69 | |
70 | public: |
71 | ShimProxyCallback(ShimProcess * pShim); |
72 | virtual ~ShimProxyCallback() {} |
73 | |
74 | // Implement IUnknown |
75 | ULONG STDMETHODCALLTYPE AddRef(); |
76 | ULONG STDMETHODCALLTYPE Release(); |
77 | COM_METHOD QueryInterface(REFIID riid, void **ppInterface); |
78 | |
79 | // |
80 | // Implementation of ICorDebugManagedCallback |
81 | // |
82 | |
83 | COM_METHOD Breakpoint( ICorDebugAppDomain *pAppDomain, |
84 | ICorDebugThread *pThread, |
85 | ICorDebugBreakpoint *pBreakpoint); |
86 | |
87 | COM_METHOD StepComplete( ICorDebugAppDomain *pAppDomain, |
88 | ICorDebugThread *pThread, |
89 | ICorDebugStepper *pStepper, |
90 | CorDebugStepReason reason); |
91 | |
92 | COM_METHOD Break( ICorDebugAppDomain *pAppDomain, |
93 | ICorDebugThread *thread); |
94 | |
95 | COM_METHOD Exception( ICorDebugAppDomain *pAppDomain, |
96 | ICorDebugThread *pThread, |
97 | BOOL unhandled); |
98 | |
99 | COM_METHOD EvalComplete( ICorDebugAppDomain *pAppDomain, |
100 | ICorDebugThread *pThread, |
101 | ICorDebugEval *pEval); |
102 | |
103 | COM_METHOD EvalException( ICorDebugAppDomain *pAppDomain, |
104 | ICorDebugThread *pThread, |
105 | ICorDebugEval *pEval); |
106 | |
107 | COM_METHOD CreateProcess( ICorDebugProcess *pProcess); |
108 | void QueueCreateProcess( ICorDebugProcess *pProcess); |
109 | |
110 | COM_METHOD ExitProcess( ICorDebugProcess *pProcess); |
111 | |
112 | COM_METHOD CreateThread( ICorDebugAppDomain *pAppDomain, ICorDebugThread *thread); |
113 | |
114 | |
115 | COM_METHOD ExitThread( ICorDebugAppDomain *pAppDomain, ICorDebugThread *thread); |
116 | |
117 | COM_METHOD LoadModule( ICorDebugAppDomain *pAppDomain, ICorDebugModule *pModule); |
118 | |
119 | void FakeLoadModule(ICorDebugAppDomain *pAppDomain, ICorDebugModule *pModule); |
120 | |
121 | COM_METHOD UnloadModule( ICorDebugAppDomain *pAppDomain, ICorDebugModule *pModule); |
122 | |
123 | COM_METHOD LoadClass( ICorDebugAppDomain *pAppDomain, ICorDebugClass *c); |
124 | |
125 | COM_METHOD UnloadClass( ICorDebugAppDomain *pAppDomain, ICorDebugClass *c); |
126 | |
127 | COM_METHOD DebuggerError( ICorDebugProcess *pProcess, HRESULT errorHR, DWORD errorCode); |
128 | |
129 | COM_METHOD LogMessage( ICorDebugAppDomain *pAppDomain, |
130 | ICorDebugThread *pThread, |
131 | LONG lLevel, |
132 | __in LPWSTR pLogSwitchName, |
133 | __in LPWSTR pMessage); |
134 | |
135 | COM_METHOD LogSwitch( ICorDebugAppDomain *pAppDomain, |
136 | ICorDebugThread *pThread, |
137 | LONG lLevel, |
138 | ULONG ulReason, |
139 | __in LPWSTR pLogSwitchName, |
140 | __in LPWSTR pParentName); |
141 | |
142 | COM_METHOD CreateAppDomain(ICorDebugProcess *pProcess, |
143 | ICorDebugAppDomain *pAppDomain); |
144 | |
145 | COM_METHOD ExitAppDomain(ICorDebugProcess *pProcess, |
146 | ICorDebugAppDomain *pAppDomain); |
147 | |
148 | COM_METHOD LoadAssembly(ICorDebugAppDomain *pAppDomain, |
149 | ICorDebugAssembly *pAssembly); |
150 | |
151 | COM_METHOD UnloadAssembly(ICorDebugAppDomain *pAppDomain, |
152 | ICorDebugAssembly *pAssembly); |
153 | |
154 | COM_METHOD ControlCTrap(ICorDebugProcess *pProcess); |
155 | |
156 | COM_METHOD NameChange(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread); |
157 | |
158 | |
159 | COM_METHOD UpdateModuleSymbols( ICorDebugAppDomain *pAppDomain, |
160 | ICorDebugModule *pModule, |
161 | IStream *pSymbolStream); |
162 | |
163 | COM_METHOD EditAndContinueRemap( ICorDebugAppDomain *pAppDomain, |
164 | ICorDebugThread *pThread, |
165 | ICorDebugFunction *pFunction, |
166 | BOOL fAccurate); |
167 | |
168 | COM_METHOD BreakpointSetError( ICorDebugAppDomain *pAppDomain, |
169 | ICorDebugThread *pThread, |
170 | ICorDebugBreakpoint *pBreakpoint, |
171 | DWORD dwError); |
172 | |
173 | /// |
174 | /// Implementation of ICorDebugManagedCallback2 |
175 | /// |
176 | COM_METHOD FunctionRemapOpportunity( ICorDebugAppDomain *pAppDomain, |
177 | ICorDebugThread *pThread, |
178 | ICorDebugFunction *pOldFunction, |
179 | ICorDebugFunction *pNewFunction, |
180 | ULONG32 oldILOffset); |
181 | |
182 | COM_METHOD CreateConnection(ICorDebugProcess *pProcess, CONNID dwConnectionId, __in LPWSTR pConnName); |
183 | |
184 | COM_METHOD ChangeConnection(ICorDebugProcess *pProcess, CONNID dwConnectionId ); |
185 | |
186 | |
187 | COM_METHOD DestroyConnection(ICorDebugProcess *pProcess, CONNID dwConnectionId); |
188 | |
189 | COM_METHOD Exception(ICorDebugAppDomain *pAppDomain, |
190 | ICorDebugThread *pThread, |
191 | ICorDebugFrame *pFrame, |
192 | ULONG32 nOffset, |
193 | CorDebugExceptionCallbackType dwEventType, |
194 | DWORD dwFlags ); |
195 | |
196 | COM_METHOD ExceptionUnwind(ICorDebugAppDomain *pAppDomain, |
197 | ICorDebugThread *pThread, |
198 | CorDebugExceptionUnwindCallbackType dwEventType, |
199 | DWORD dwFlags); |
200 | |
201 | COM_METHOD FunctionRemapComplete( ICorDebugAppDomain *pAppDomain, |
202 | ICorDebugThread *pThread, |
203 | ICorDebugFunction *pFunction); |
204 | |
205 | COM_METHOD MDANotification(ICorDebugController * pController, ICorDebugThread *pThread, ICorDebugMDA * pMDA); |
206 | |
207 | /// |
208 | /// Implementation of ICorDebugManagedCallback3 |
209 | /// |
210 | |
211 | // Implementation of ICorDebugManagedCallback3::CustomNotification |
212 | COM_METHOD CustomNotification(ICorDebugThread * pThread, ICorDebugAppDomain * pAppDomain); |
213 | |
214 | /// |
215 | /// Implementation of ICorDebugManagedCallback4 |
216 | /// |
217 | |
218 | // Implementation of ICorDebugManagedCallback4::BeforeGarbageCollection |
219 | COM_METHOD ShimProxyCallback::BeforeGarbageCollection(ICorDebugProcess* pProcess); |
220 | |
221 | // Implementation of ICorDebugManagedCallback4::AfterGarbageCollection |
222 | COM_METHOD ShimProxyCallback::AfterGarbageCollection(ICorDebugProcess* pProcess); |
223 | |
224 | // Implementation of ICorDebugManagedCallback4::DataBreakpoint |
225 | COM_METHOD ShimProxyCallback::DataBreakpoint(ICorDebugProcess* pProcess, ICorDebugThread* pThread, BYTE* pContext, ULONG32 contextSize); |
226 | }; |
227 | |
228 | |
229 | // |
230 | // Base class for event queue. These are nested into a singly linked list. |
231 | // Shim maintains event queue |
232 | // |
233 | class ManagedEvent |
234 | { |
235 | public: |
236 | // Need virtual dtor since this is a base class. |
237 | virtual ~ManagedEvent(); |
238 | |
239 | #ifdef _DEBUG |
240 | // For debugging, get a pointer value that can identify the type of this event. |
241 | void * GetDebugCookie(); |
242 | #endif |
243 | |
244 | // We'll have a lot of derived classes of ManagedEvent, and so encapsulating the arguments |
245 | // for the Dispatch() function lets us juggle them around easily without hitting every signature. |
246 | class DispatchArgs |
247 | { |
248 | public: |
249 | DispatchArgs(ICorDebugManagedCallback * pCallback1, ICorDebugManagedCallback2 * pCallback2, ICorDebugManagedCallback3 * pCallback3, ICorDebugManagedCallback4 * pCallback4); |
250 | |
251 | ICorDebugManagedCallback * GetCallback1(); |
252 | ICorDebugManagedCallback2 * GetCallback2(); |
253 | ICorDebugManagedCallback3 * GetCallback3(); |
254 | ICorDebugManagedCallback4 * GetCallback4(); |
255 | |
256 | |
257 | protected: |
258 | ICorDebugManagedCallback * m_pCallback1; |
259 | ICorDebugManagedCallback2 * m_pCallback2; |
260 | ICorDebugManagedCallback3 * m_pCallback3; |
261 | ICorDebugManagedCallback4 * m_pCallback4; |
262 | }; |
263 | |
264 | // Returns: value of callback from end-user |
265 | virtual HRESULT Dispatch(DispatchArgs args) = 0; |
266 | |
267 | |
268 | // Returns 0 if none. |
269 | DWORD GetOSTid(); |
270 | |
271 | protected: |
272 | // Ctor for events with thread-affinity |
273 | ManagedEvent(ICorDebugThread * pThread); |
274 | |
275 | // Ctor for events without thread affinity. |
276 | ManagedEvent(); |
277 | |
278 | friend class ManagedEventQueue; |
279 | ManagedEvent * m_pNext; |
280 | |
281 | DWORD m_dwThreadId; |
282 | }; |
283 | |
284 | // |
285 | // Queue of managed events. |
286 | // Shim can use this to collect managed debug events, queue them, and then drain the event |
287 | // queue when a sync-complete occurs. |
288 | // Event queue gets initialized with a lock and will lock internally. |
289 | class ManagedEventQueue |
290 | { |
291 | public: |
292 | ManagedEventQueue(); |
293 | |
294 | |
295 | void Init(RSLock * pLock); |
296 | |
297 | // Remove event from the top. Caller then takes ownership of Event and will call Delete on it. |
298 | // Caller checks IsEmpty() first. |
299 | ManagedEvent * Dequeue(); |
300 | |
301 | // Queue owns the event and will delete it (unless it's dequeued first). |
302 | void QueueEvent(ManagedEvent * pEvent); |
303 | |
304 | // Test if event queue is empty |
305 | bool IsEmpty(); |
306 | |
307 | // Empty event queue and delete all objects |
308 | void DeleteAll(); |
309 | |
310 | // Nothrows |
311 | BOOL HasQueuedCallbacks(ICorDebugThread * pThread); |
312 | |
313 | // Save the current queue and start with a new empty queue |
314 | void SuspendQueue(); |
315 | |
316 | // Restore the saved queue onto the end of the current queue |
317 | void RestoreSuspendedQueue(); |
318 | |
319 | protected: |
320 | // The lock to be used for synchronizing all access to the queue |
321 | RSLock * m_pLock; |
322 | |
323 | // If empty, First + Last are both NULL. |
324 | // Else first points to the head of the queue; and Last points to the end of the queue. |
325 | ManagedEvent * m_pFirstEvent; |
326 | ManagedEvent * m_pLastEvent; |
327 | |
328 | }; |
329 | |
330 | |
331 | //--------------------------------------------------------------------------------------- |
332 | // |
333 | // Shim's layer on top of a process. |
334 | // |
335 | // Notes: |
336 | // This contains a V3 ICorDebugProcess, and provides V2 ICDProcess functionality. |
337 | // |
338 | class ShimProcess |
339 | { |
340 | // Delete via Ref count semantics. |
341 | ~ShimProcess(); |
342 | public: |
343 | // Initialize ref count is 0. |
344 | ShimProcess(); |
345 | |
346 | // Lifetime semantics handled by reference counting. |
347 | void AddRef(); |
348 | void Release(); |
349 | |
350 | // Release all resources. Can be called multiple times. |
351 | void Dispose(); |
352 | |
353 | // Initialization phases. |
354 | // 1. allocate new ShimProcess(). This lets us spin up a Win32 EventThread, which can then |
355 | // be used to |
356 | // 2. Call ShimProcess::CreateProcess/DebugActiveProcess. This will call CreateAndStartWin32ET to |
357 | // craete the w32et. |
358 | // 3. Create OS-debugging pipeline. This establishes the physical OS process and gets us a pid/handle |
359 | // 4. pShim->InitializeDataTarget - this creates a reader/writer abstraction around the OS process. |
360 | // 5. pShim->SetProcess() - this connects the Shim to the ICDProcess object. |
361 | HRESULT InitializeDataTarget(const ProcessDescriptor * pProcessDescriptor); |
362 | void SetProcess(ICorDebugProcess * pProcess); |
363 | |
364 | //----------------------------------------------------------- |
365 | // Creation |
366 | //----------------------------------------------------------- |
367 | |
368 | static HRESULT CreateProcess( |
369 | Cordb * pCordb, |
370 | ICorDebugRemoteTarget * pRemoteTarget, |
371 | LPCWSTR programName, |
372 | __in_z LPWSTR programArgs, |
373 | LPSECURITY_ATTRIBUTES lpProcessAttributes, |
374 | LPSECURITY_ATTRIBUTES lpThreadAttributes, |
375 | BOOL bInheritHandles, |
376 | DWORD dwCreationFlags, |
377 | PVOID lpEnvironment, |
378 | LPCWSTR lpCurrentDirectory, |
379 | LPSTARTUPINFOW lpStartupInfo, |
380 | LPPROCESS_INFORMATION lpProcessInformation, |
381 | CorDebugCreateProcessFlags corDebugFlags |
382 | ); |
383 | |
384 | static HRESULT DebugActiveProcess( |
385 | Cordb * pCordb, |
386 | ICorDebugRemoteTarget * pRemoteTarget, |
387 | const ProcessDescriptor * pProcessDescriptor, |
388 | BOOL win32Attach |
389 | |
390 | ); |
391 | |
392 | // Locates the DAC module adjacent to DBI |
393 | static HMODULE GetDacModule(); |
394 | |
395 | // |
396 | // Functions used by CordbProcess |
397 | // |
398 | |
399 | // Determine if the calling thread is the win32 event thread. |
400 | bool IsWin32EventThread(); |
401 | |
402 | |
403 | // Expose the W32ET thread to the CordbProcess so that it can emulate V2 behavior |
404 | CordbWin32EventThread * GetWin32EventThread(); |
405 | |
406 | // Accessor wrapper to mark whether we're interop-debugging. |
407 | void SetIsInteropDebugging(bool fIsInteropDebugging); |
408 | |
409 | // Handle a debug event. |
410 | HRESULT HandleWin32DebugEvent(const DEBUG_EVENT * pEvent); |
411 | |
412 | ManagedEventQueue * GetManagedEventQueue(); |
413 | |
414 | ManagedEvent * DequeueManagedEvent(); |
415 | |
416 | ShimProxyCallback * GetShimCallback(); |
417 | |
418 | // Begin Queing the fake attach events. |
419 | void BeginQueueFakeAttachEvents(); |
420 | |
421 | // Queue fake attach events if needed |
422 | void QueueFakeAttachEventsIfNeeded(bool fRealCreateProcessEvent); |
423 | |
424 | // Actually do the work to queue the fake attach events. |
425 | void QueueFakeAttachEvents(); |
426 | |
427 | // Helper to queue fake assembly and mdule events |
428 | void QueueFakeAssemblyAndModuleEvent(ICorDebugAssembly * pAssembly); |
429 | |
430 | // Queue fake thread-create events on attach. Order via native threads. |
431 | HRESULT QueueFakeThreadAttachEventsNativeOrder(); |
432 | |
433 | // Queue fake thread-create events on attach. No ordering. |
434 | HRESULT QueueFakeThreadAttachEventsNoOrder(); |
435 | |
436 | bool IsThreadSuspendedOrHijacked(ICorDebugThread * pThread); |
437 | |
438 | // Expose m_attached to CordbProcess. |
439 | bool GetAttached(); |
440 | |
441 | // We need to know whether we are in the CreateProcess callback to be able to |
442 | // return the v2.0 hresults from code:CordbProcess::SetDesiredNGENCompilerFlags |
443 | // when we are using the shim. |
444 | // |
445 | // Expose m_fInCreateProcess |
446 | bool GetInCreateProcess(); |
447 | void SetInCreateProcess(bool value); |
448 | |
449 | // We need to know whether we are in the FakeLoadModule callback to be able to |
450 | // return the v2.0 hresults from code:CordbModule::SetJITCompilerFlags when |
451 | // we are using the shim. |
452 | // |
453 | // Expose m_fInLoadModule |
454 | bool GetInLoadModule(); |
455 | void SetInLoadModule(bool value); |
456 | |
457 | // When we get a continue, we need to clear the flags indicating we're still in a callback |
458 | void NotifyOnContinue (); |
459 | |
460 | // The RS calls this function when the stack is about to be changed in any way, e.g. continue, SetIP, |
461 | // etc. |
462 | void NotifyOnStackInvalidate(); |
463 | |
464 | // Helpers to filter HRs to emulate V2 error codes. |
465 | HRESULT FilterSetNgenHresult(HRESULT hr); |
466 | HRESULT FilterSetJitFlagsHresult(HRESULT hr); |
467 | |
468 | //............................................................. |
469 | |
470 | |
471 | // Lookup or create a ShimStackWalk for the specified thread. ShimStackWalk and ICorDebugThread has |
472 | // a 1:1 relationship. |
473 | ShimStackWalk * LookupOrCreateShimStackWalk(ICorDebugThread * pThread); |
474 | |
475 | // Clear all ShimStackWalks and flush all the caches. |
476 | void ClearAllShimStackWalk(); |
477 | |
478 | // Get the corresponding ICDProcess object. |
479 | ICorDebugProcess * GetProcess(); |
480 | |
481 | // Get the data target to access the debuggee. |
482 | ICorDebugMutableDataTarget * GetDataTarget(); |
483 | |
484 | // Get the native event pipeline |
485 | INativeEventPipeline * GetNativePipeline(); |
486 | |
487 | // Are we interop-debugging? |
488 | bool IsInteropDebugging(); |
489 | |
490 | |
491 | // Finish all the necessary initialization work and queue up any necessary fake attach events before |
492 | // dispatching an event. |
493 | void PreDispatchEvent(bool fRealCreateProcessEvent = false); |
494 | |
495 | // Look for a CLR in the process and if found, return it's instance ID |
496 | HRESULT FindLoadedCLR(CORDB_ADDRESS * pClrInstanceId); |
497 | |
498 | // Retrieve the IP address and the port number of the debugger proxy. |
499 | MachineInfo GetMachineInfo(); |
500 | |
501 | // Add an entry in the duplicate creation event hash table for the specified key. |
502 | void AddDuplicateCreationEvent(void * pKey); |
503 | |
504 | // Check if a duplicate creation event entry exists for the specified key. If so, remove it. |
505 | bool RemoveDuplicateCreationEventIfPresent(void * pKey); |
506 | |
507 | void SetMarkAttachPendingEvent(); |
508 | |
509 | void SetTerminatingEvent(); |
510 | |
511 | RSLock * GetShimLock(); |
512 | |
513 | protected: |
514 | |
515 | // Reference count. |
516 | LONG m_ref; |
517 | |
518 | // |
519 | // Helper functions |
520 | // |
521 | HRESULT CreateAndStartWin32ET(Cordb * pCordb); |
522 | |
523 | // |
524 | // Synchronization events to ensure that AttachPending bit is marked before DebugActiveProcess |
525 | // returns or debugger is detaching |
526 | // |
527 | HANDLE m_markAttachPendingEvent; |
528 | HANDLE m_terminatingEvent; |
529 | |
530 | // Finds the base address of [core]clr.dll |
531 | CORDB_ADDRESS GetCLRInstanceBaseAddress(); |
532 | |
533 | // |
534 | // Event Queues |
535 | // |
536 | |
537 | // Shim maintains event queue to emulate V2 semantics. |
538 | // In V2, IcorDebug internally queued debug events and dispatched them |
539 | // once the debuggee was synchronized. In V3, ICorDebug dispatches events immediately. |
540 | // The event queue is moved into the shim to build V2 semantics of V3 behavior. |
541 | ManagedEventQueue m_eventQueue; |
542 | |
543 | // Lock to protect Shim data structures. This is currently a small lock that |
544 | // protects leaf-level structures, but it may grow to protect larger things. |
545 | RSLock m_ShimLock; |
546 | |
547 | // Serializes ShimProcess:Dispose() with other ShimProcess functions. For now, this |
548 | // cannot be the same as m_ShimLock. See LL_SHIM_PROCESS_DISPOSE_LOCK for more |
549 | // information |
550 | RSLock m_ShimProcessDisposeLock; |
551 | |
552 | // Sticky bit to do lazy-initialization on the first managed event. |
553 | bool m_fFirstManagedEvent; |
554 | |
555 | RSExtSmartPtr<ShimProxyCallback> m_pShimCallback; |
556 | |
557 | |
558 | // This is for emulating V2 Attach. Initialized to false, and then set to true if we ened to send fake attach events. |
559 | // Reset to false once the events are sent. See code:ShimProcess::QueueFakeAttachEventsIfNeeded |
560 | bool m_fNeedFakeAttachEvents; |
561 | |
562 | // True if the process was created from an attach (DebugActiveProcess); False if it was launched (CreateProcess) |
563 | // This is used to send an Attach IPC event, and also used to provide more specific error codes. |
564 | bool m_attached; |
565 | |
566 | // True iff we are in the shim's CreateProcess callback. This is used to determine which hresult to |
567 | // return from code:CordbProcess::SetDesiredNGENCompilerFlags so we correctly emulate the behavior of v2.0. |
568 | // This is set at the beginning of the callback and cleared in code:CordbProcess::ContinueInternal. |
569 | bool m_fInCreateProcess; |
570 | |
571 | // True iff we are in the shim's FakeLoadModule callback. This is used to determine which hresult to |
572 | // return from code:CordbModule::SetJITCompilerFlags so we correctly emulate the behavior of v2.0. |
573 | // This is set at the beginning of the callback and cleared in code:CordbProcess::ContinueInternal. |
574 | bool m_fInLoadModule; |
575 | // |
576 | // Data |
577 | // |
578 | |
579 | // Pointer to CordbProcess. |
580 | // @dbgtodo shim: We'd like this to eventually go through public interfaces (ICorDebugProcess) |
581 | IProcessShimHooks * m_pProcess; // Reference is kept by m_pIProcess; |
582 | RSExtSmartPtr<ICorDebugProcess> m_pIProcess; |
583 | |
584 | // Win32EvenThread, which is the thread that uses the native debug API. |
585 | CordbWin32EventThread * m_pWin32EventThread; |
586 | |
587 | // Actual data-target. Since we're shimming V2 scenarios, and V3 is always |
588 | // live-debugging, this is always a live data-target. |
589 | RSExtSmartPtr<ShimDataTarget> m_pLiveDataTarget; |
590 | |
591 | |
592 | // If true, the shim is emulating interop-debugging |
593 | // If false, the shim is emulating managed-only debugging. |
594 | // Both managed and native debugging have the same underlying pipeline (built |
595 | // on native-debug events). So the only difference is how they handle those events. |
596 | bool m_fIsInteropDebugging; |
597 | |
598 | // true iff Dispose() was called. Consult this and do your work under m_ShimProcessDisposeLock |
599 | // to serialize yourself against a call to Dispose(). This protects your work |
600 | // from the user doing a Debugger Detach in the middle. |
601 | bool m_fIsDisposed; |
602 | |
603 | //............................................................................. |
604 | // |
605 | // Members used for handling native events when managed-only debugging. |
606 | // |
607 | //............................................................................. |
608 | |
609 | // Default handler for native events when managed-only debugging. |
610 | void DefaultEventHandler(const DEBUG_EVENT * pEvent, DWORD * pdwContinueStatus); |
611 | |
612 | // Given a debug event, track the file handles. |
613 | void TrackFileHandleForDebugEvent(const DEBUG_EVENT * pEvent); |
614 | |
615 | // Have we gotten the loader breakpoint yet? |
616 | // A Debugger needs to do special work to skip the loader breakpoint, |
617 | // and that's also when it should dispatch the faked managed attach events. |
618 | bool m_loaderBPReceived; |
619 | |
620 | // Raw callback for ContinueStatusChanged from Data-target. |
621 | static HRESULT ContinueStatusChanged(void * pUserData, DWORD dwThreadId, CORDB_CONTINUE_STATUS dwContinueStatus); |
622 | |
623 | // Real worker to update ContinueStatusChangedData |
624 | HRESULT ContinueStatusChangedWorker(DWORD dwThreadId, CORDB_CONTINUE_STATUS dwContinueStatus); |
625 | |
626 | struct ContinueStatusChangedData |
627 | { |
628 | void Clear(); |
629 | bool IsSet(); |
630 | // Tid of Thread changed |
631 | DWORD m_dwThreadId; |
632 | |
633 | // New continue status. |
634 | CORDB_CONTINUE_STATUS m_status; |
635 | } m_ContinueStatusChangedData; |
636 | |
637 | // the hash table of ShimStackWalks |
638 | ShimStackWalkHashTable * m_pShimStackWalkHashTable; |
639 | |
640 | // the hash table of duplicate creation events |
641 | DuplicateCreationEventsHashTable * m_pDupeEventsHashTable; |
642 | |
643 | MachineInfo m_machineInfo; |
644 | }; |
645 | |
646 | |
647 | //--------------------------------------------------------------------------------------- |
648 | // |
649 | // This is the container class of ShimChains, ICorDebugFrames, ShimChainEnums, and ShimFrameEnums. |
650 | // It has a 1:1 relationship with ICorDebugThreads. Upon creation, this class walks the entire stack and |
651 | // caches all the stack frames and chains. The enumerators are created on demand. |
652 | // |
653 | |
654 | class ShimStackWalk |
655 | { |
656 | public: |
657 | ShimStackWalk(ShimProcess * pProcess, ICorDebugThread * pThread); |
658 | ~ShimStackWalk(); |
659 | |
660 | // These functions do not adjust the reference count. |
661 | ICorDebugThread * GetThread(); |
662 | ShimChain * GetChain(UINT32 index); |
663 | ICorDebugFrame * GetFrame(UINT32 index); |
664 | |
665 | // Get the number of frames and chains. |
666 | ULONG GetChainCount(); |
667 | ULONG GetFrameCount(); |
668 | |
669 | RSLock * GetShimLock(); |
670 | |
671 | // Add ICDChainEnum and ICDFrameEnum. |
672 | void AddChainEnum(ShimChainEnum * pChainEnum); |
673 | void AddFrameEnum(ShimFrameEnum * pFrameEnum); |
674 | |
675 | // The next two functions are for ShimStackWalkHashTableTraits. |
676 | ICorDebugThread * GetKey(); |
677 | static UINT32 Hash(ICorDebugThread * pThread); |
678 | |
679 | // Check if the specified frame is the leaf frame according to the V2 definition. |
680 | BOOL IsLeafFrame(ICorDebugFrame * pFrame); |
681 | |
682 | // Check if the two specified frames are the same. This function checks the SPs, frame address, etc. |
683 | // instead of just checking for pointer equality. |
684 | BOOL IsSameFrame(ICorDebugFrame * pLeft, ICorDebugFrame * pRight); |
685 | |
686 | // The following functions are entry point into the ShimStackWalk. They are called by the RS. |
687 | void EnumerateChains(ICorDebugChainEnum ** ppChainEnum); |
688 | |
689 | void GetActiveChain(ICorDebugChain ** ppChain); |
690 | void GetActiveFrame(ICorDebugFrame ** ppFrame); |
691 | void GetActiveRegisterSet(ICorDebugRegisterSet ** ppRegisterSet); |
692 | |
693 | void GetChainForFrame(ICorDebugFrame * pFrame, ICorDebugChain ** ppChain); |
694 | void GetCallerForFrame(ICorDebugFrame * pFrame, ICorDebugFrame ** ppCallerFrame); |
695 | void GetCalleeForFrame(ICorDebugFrame * pFrame, ICorDebugFrame ** ppCalleeFrame); |
696 | |
697 | private: |
698 | //--------------------------------------------------------------------------------------- |
699 | // |
700 | // This is a helper class used to store the information of a chain during a stackwalk. A chain is marked |
701 | // by the CONTEXT on the leaf boundary and a FramePointer on the root boundary. Also, notice that we |
702 | // are keeping two CONTEXTs. This is because some chain types may cancel a previous unmanaged chain. |
703 | // For example, a CHAIN_FUNC_EVAL chain cancels any CHAIN_ENTER_UNMANAGED chain immediately preceding |
704 | // it. In this case, the leaf boundary of the CHAIN_FUNC_EVAL chain is marked by the CONTEXT of the |
705 | // previous CHAIN_ENTER_MANAGED, not the previous CHAIN_ENTER_UNMANAGED. |
706 | // |
707 | |
708 | struct ChainInfo |
709 | { |
710 | public: |
711 | ChainInfo() : m_rootFP(LEAF_MOST_FRAME), m_reason(CHAIN_NONE), m_fNeedEnterManagedChain(FALSE), m_fLeafNativeContextIsValid(FALSE) {} |
712 | |
713 | void CancelUMChain() { m_reason = CHAIN_NONE; } |
714 | BOOL IsTrackingUMChain() { return (m_reason == CHAIN_ENTER_UNMANAGED); } |
715 | |
716 | DT_CONTEXT m_leafNativeContext; |
717 | DT_CONTEXT m_leafManagedContext; |
718 | FramePointer m_rootFP; |
719 | CorDebugChainReason m_reason; |
720 | bool m_fNeedEnterManagedChain; |
721 | bool m_fLeafNativeContextIsValid; |
722 | }; |
723 | |
724 | //--------------------------------------------------------------------------------------- |
725 | // |
726 | // This is a helper class used to store information during a stackwalk. Conceptually it is a simplified |
727 | // version of FrameInfo used on the LS in V2. |
728 | // |
729 | |
730 | struct StackWalkInfo |
731 | { |
732 | public: |
733 | StackWalkInfo(); |
734 | ~StackWalkInfo(); |
735 | |
736 | // Reset all the per-frame information. |
737 | void ResetForNextFrame(); |
738 | |
739 | // During the stackwalk, we need to find out whether we should process the next stack frame or the |
740 | // next internal frame. These functions help us determine whether we have exhausted one or both |
741 | // types of frames. The stackwalk is finished when both types are exhausted. |
742 | bool ExhaustedAllFrames(); |
743 | bool ExhaustedAllStackFrames(); |
744 | bool ExhaustedAllInternalFrames(); |
745 | |
746 | // Simple helper function to get the current internal frame. |
747 | ICorDebugInternalFrame2 * GetCurrentInternalFrame(); |
748 | |
749 | // Check whether we are processing the first frame. |
750 | BOOL IsLeafFrame(); |
751 | |
752 | // Check whether we are skipping frames because of a child frame. |
753 | BOOL IsSkippingFrame(); |
754 | |
755 | // Indicates whether we are dealing with a converted frame. |
756 | // See code:CordbThread::ConvertFrameForILMethodWithoutMetadata. |
757 | BOOL HasConvertedFrame(); |
758 | |
759 | // Store the child frame we are currently trying to find the parent frame for. |
760 | // If this is NULL, then we are not skipping frames. |
761 | RSExtSmartPtr<ICorDebugNativeFrame2> m_pChildFrame; |
762 | |
763 | // Store the converted frame, if any. |
764 | RSExtSmartPtr<ICorDebugInternalFrame2> m_pConvertedInternalFrame2; |
765 | |
766 | // Store the array of internal frames. This is an array of RSExtSmartPtrs, and so each element |
767 | // is protected, and we only need to call Clear() to release each element and free all the memory. |
768 | RSExtPtrArray<ICorDebugInternalFrame2> m_ppInternalFrame2; |
769 | |
770 | UINT32 m_cChain; // number of chains |
771 | UINT32 m_cFrame; // number of frames |
772 | UINT32 m_firstFrameInChain; // the index of the first frame in the current chain |
773 | UINT32 m_cInternalFrames; // number of internal frames |
774 | UINT32 m_curInternalFrame; // the index of the current internal frame being processed |
775 | |
776 | CorDebugInternalFrameType m_internalFrameType; |
777 | |
778 | bool m_fExhaustedAllStackFrames; |
779 | |
780 | // Indicate whether we are processing an internal frame or a stack frame. |
781 | bool m_fProcessingInternalFrame; |
782 | |
783 | // Indicate whether we should skip the current chain because it's a chain derived from a leaf frame |
784 | // of type TYPE_INTERNAL. This is the behaviour in V2. |
785 | // See code:DebuggerWalkStackProc. |
786 | bool m_fSkipChain; |
787 | |
788 | // Indicate whether the current frame is the first frame we process. |
789 | bool m_fLeafFrame; |
790 | |
791 | // Indicate whether we are processing a converted frame. |
792 | bool m_fHasConvertedFrame; |
793 | }; |
794 | |
795 | // A ShimStackWalk is deleted when a process is continued, or when the stack is changed in any way |
796 | // (e.g. SetIP, EnC, etc.). |
797 | void Populate(); |
798 | void Clear(); |
799 | |
800 | // Get a FramePointer to mark the root boundary of a chain. |
801 | FramePointer GetFramePointerForChain(DT_CONTEXT * pContext); |
802 | FramePointer GetFramePointerForChain(ICorDebugInternalFrame2 * pInternalFrame2); |
803 | |
804 | CorDebugInternalFrameType GetInternalFrameType(ICorDebugInternalFrame2 * pFrame2); |
805 | |
806 | // Append a frame to the array. |
807 | void AppendFrame(ICorDebugFrame * pFrame, StackWalkInfo * pStackWalkInfo); |
808 | void AppendFrame(ICorDebugInternalFrame2 * pInternalFrame2, StackWalkInfo * pStackWalkInfo); |
809 | |
810 | // Append a chain to the array. |
811 | void AppendChainWorker(StackWalkInfo * pStackWalkInfo, |
812 | DT_CONTEXT * pLeafContext, |
813 | FramePointer fpRoot, |
814 | CorDebugChainReason chainReason, |
815 | BOOL fIsManagedChain); |
816 | void AppendChain(ChainInfo * pChainInfo, StackWalkInfo * pStackWalkInfo); |
817 | |
818 | // Save information on the ChainInfo regarding the current chain. |
819 | void SaveChainContext(ICorDebugStackWalk * pSW, ChainInfo * pChainInfo, DT_CONTEXT * pContext); |
820 | |
821 | // Check what we are process next, a internal frame or a stack frame. |
822 | BOOL CheckInternalFrame(ICorDebugFrame * pNextStackFrame, |
823 | StackWalkInfo * pStackWalkInfo, |
824 | ICorDebugThread3 * pThread3, |
825 | ICorDebugStackWalk * pSW); |
826 | |
827 | // Convert an ICDInternalFrame to another ICDInternalFrame due to IL methods without metadata. |
828 | // See code:CordbThread::ConvertFrameForILMethodWithoutMetadata. |
829 | BOOL ConvertInternalFrameToDynamicMethod(StackWalkInfo * pStackWalkInfo); |
830 | |
831 | // Convert an ICDNativeFrame to an ICDInternalFrame due to IL methods without metadata. |
832 | // See code:CordbThread::ConvertFrameForILMethodWithoutMetadata. |
833 | BOOL ConvertStackFrameToDynamicMethod(ICorDebugFrame * pFrame, StackWalkInfo * pStackWalkInfo); |
834 | |
835 | // Process an unmanaged chain. |
836 | BOOL ShouldTrackUMChain(StackWalkInfo * pswInfo); |
837 | void TrackUMChain(ChainInfo * pChainInfo, StackWalkInfo * pStackWalkInfo); |
838 | |
839 | // Check whether the internal frame is a newly exposed type in Arrowhead. If so, then the shim should |
840 | // not expose it. |
841 | BOOL IsV3FrameType(CorDebugInternalFrameType type); |
842 | |
843 | // Check whether the specified frame represents a dynamic method. |
844 | BOOL IsILFrameWithoutMetadata(ICorDebugFrame * pFrame); |
845 | |
846 | CDynArray<ShimChain *> m_stackChains; // growable ordered array of chains and frames |
847 | CDynArray<ICorDebugFrame *> m_stackFrames; |
848 | |
849 | ShimChainEnum * m_pChainEnumList; // linked list of ShimChainEnum and ShimFrameEnum |
850 | ShimFrameEnum * m_pFrameEnumList; |
851 | |
852 | // the thread on which we are doing a stackwalk, i.e. the "owning" thread |
853 | RSExtSmartPtr<ShimProcess> m_pProcess; |
854 | RSExtSmartPtr<ICorDebugThread> m_pThread; |
855 | }; |
856 | |
857 | |
858 | //--------------------------------------------------------------------------------------- |
859 | // |
860 | // This class implements the deprecated ICDChain interface. |
861 | // |
862 | |
863 | class ShimChain : public ICorDebugChain |
864 | { |
865 | public: |
866 | ShimChain(ShimStackWalk * pSW, |
867 | DT_CONTEXT * pContext, |
868 | FramePointer fpRoot, |
869 | UINT32 chainIndex, |
870 | UINT32 frameStartIndex, |
871 | UINT32 frameEndIndex, |
872 | CorDebugChainReason chainReason, |
873 | BOOL fIsManaged, |
874 | RSLock * pShimLock); |
875 | virtual ~ShimChain(); |
876 | |
877 | void Neuter(); |
878 | BOOL IsNeutered(); |
879 | |
880 | // |
881 | // IUnknown |
882 | // |
883 | |
884 | ULONG STDMETHODCALLTYPE AddRef(); |
885 | ULONG STDMETHODCALLTYPE Release(); |
886 | COM_METHOD QueryInterface(REFIID riid, void ** ppInterface); |
887 | |
888 | // |
889 | // ICorDebugChain |
890 | // |
891 | |
892 | COM_METHOD GetThread(ICorDebugThread ** ppThread); |
893 | COM_METHOD GetStackRange(CORDB_ADDRESS * pStart, CORDB_ADDRESS * pEnd); |
894 | COM_METHOD GetContext(ICorDebugContext ** ppContext); |
895 | COM_METHOD GetCaller(ICorDebugChain ** ppChain); |
896 | COM_METHOD GetCallee(ICorDebugChain ** ppChain); |
897 | COM_METHOD GetPrevious(ICorDebugChain ** ppChain); |
898 | COM_METHOD GetNext(ICorDebugChain ** ppChain); |
899 | COM_METHOD IsManaged(BOOL * pManaged); |
900 | COM_METHOD EnumerateFrames(ICorDebugFrameEnum ** ppFrames); |
901 | COM_METHOD GetActiveFrame(ICorDebugFrame ** ppFrame); |
902 | COM_METHOD GetRegisterSet(ICorDebugRegisterSet ** ppRegisters); |
903 | COM_METHOD GetReason(CorDebugChainReason * pReason); |
904 | |
905 | // |
906 | // accessors |
907 | // |
908 | |
909 | // Get the owning ShimStackWalk. |
910 | ShimStackWalk * GetShimStackWalk(); |
911 | |
912 | // Get the first and last index of the frame owned by this chain. This class itself doesn't store the |
913 | // frames. Rather, the frames are stored on the ShimStackWalk. This class just stores the indices. |
914 | // Note that the indices are [firstIndex, lastIndex), i.e. the last index is exclusive. |
915 | UINT32 GetFirstFrameIndex(); |
916 | UINT32 GetLastFrameIndex(); |
917 | |
918 | private: |
919 | // A chain describes a stack range within the stack. This includes a CONTEXT at the start (leafmost) |
920 | // end of the chain, and a frame pointer where the chain ends (rootmost). This stack range is exposed |
921 | // publicly via ICDChain::GetStackRange(), and can be used to stitch managed and native stack frames |
922 | // together into a unified stack. |
923 | DT_CONTEXT m_context; // the leaf end of the chain |
924 | FramePointer m_fpRoot; // the root end of the chain |
925 | |
926 | ShimStackWalk * m_pStackWalk; // the owning ShimStackWalk |
927 | Volatile<ULONG> m_refCount; |
928 | |
929 | // The 0-based index of this chain in the ShimStackWalk's chain array (m_pStackWalk->m_stackChains). |
930 | UINT32 m_chainIndex; |
931 | |
932 | // The 0-based index of the first frame owned by this chain in the ShimStackWalk's frame array |
933 | // (m_pStackWalk->m_stackFrames). See code::ShimChain::GetFirstFrameIndex(). |
934 | UINT32 m_frameStartIndex; |
935 | |
936 | // The 0-based index of the last frame owned by this chain in the ShimStackWalk's frame array |
937 | // (m_pStackWalk->m_stackFrames). This index is exlusive. See code::ShimChain::GetLastFrameIndex(). |
938 | UINT32 m_frameEndIndex; |
939 | |
940 | CorDebugChainReason m_chainReason; |
941 | BOOL m_fIsManaged; // indicates whether this chain contains managed frames |
942 | BOOL m_fIsNeutered; |
943 | |
944 | RSLock * m_pShimLock; // shim lock from ShimProcess to protect neuteredness checks |
945 | }; |
946 | |
947 | |
948 | //--------------------------------------------------------------------------------------- |
949 | // |
950 | // This class implements the deprecated ICDChainEnum interface. |
951 | // |
952 | |
953 | class ShimChainEnum : public ICorDebugChainEnum |
954 | { |
955 | public: |
956 | ShimChainEnum(ShimStackWalk * pSW, RSLock * pShimLock); |
957 | virtual ~ShimChainEnum(); |
958 | |
959 | void Neuter(); |
960 | BOOL IsNeutered(); |
961 | |
962 | // |
963 | // IUnknown |
964 | // |
965 | |
966 | ULONG STDMETHODCALLTYPE AddRef(); |
967 | ULONG STDMETHODCALLTYPE Release(); |
968 | COM_METHOD QueryInterface(REFIID riid, void ** ppInterface); |
969 | |
970 | // |
971 | // ICorDebugEnum |
972 | // |
973 | |
974 | COM_METHOD Skip(ULONG celt); |
975 | COM_METHOD Reset(); |
976 | COM_METHOD Clone(ICorDebugEnum ** ppEnum); |
977 | COM_METHOD GetCount(ULONG * pcChains); |
978 | |
979 | // |
980 | // ICorDebugChainEnum |
981 | // |
982 | |
983 | COM_METHOD Next(ULONG cChains, ICorDebugChain * rgpChains[], ULONG * pcChainsFetched); |
984 | |
985 | // |
986 | // accessors |
987 | // |
988 | |
989 | // used to link ShimChainEnums in a list |
990 | ShimChainEnum * GetNext(); |
991 | void SetNext(ShimChainEnum * pNext); |
992 | |
993 | private: |
994 | ShimStackWalk * m_pStackWalk; // the owning ShimStackWalk |
995 | |
996 | // This points to the next ShimChainEnum in the linked list of ShimChainEnums to be cleaned up. |
997 | // The head of the list is on the ShimStackWalk (m_pStackWalk->m_pChainEnumList). |
998 | ShimChainEnum * m_pNext; |
999 | |
1000 | UINT32 m_currentChainIndex; // the index of the current ShimChain being enumerated |
1001 | Volatile<ULONG> m_refCount; |
1002 | BOOL m_fIsNeutered; |
1003 | |
1004 | RSLock * m_pShimLock; // shim lock from ShimProcess to protect neuteredness checks |
1005 | }; |
1006 | |
1007 | |
1008 | //--------------------------------------------------------------------------------------- |
1009 | // |
1010 | // This class implements the deprecated ICDFrameEnum interface. |
1011 | // |
1012 | |
1013 | class ShimFrameEnum : public ICorDebugFrameEnum |
1014 | { |
1015 | public: |
1016 | ShimFrameEnum(ShimStackWalk * pSW, ShimChain * pChain, UINT32 frameStartIndex, UINT32 frameEndIndex, RSLock * pShimLock); |
1017 | virtual ~ShimFrameEnum(); |
1018 | |
1019 | void Neuter(); |
1020 | BOOL IsNeutered(); |
1021 | |
1022 | // |
1023 | // IUnknown |
1024 | // |
1025 | |
1026 | ULONG STDMETHODCALLTYPE AddRef(); |
1027 | ULONG STDMETHODCALLTYPE Release(); |
1028 | COM_METHOD QueryInterface(REFIID riid, void ** ppInterface); |
1029 | |
1030 | // |
1031 | // ICorDebugEnum |
1032 | // |
1033 | |
1034 | COM_METHOD Skip(ULONG celt); |
1035 | COM_METHOD Reset(); |
1036 | COM_METHOD Clone(ICorDebugEnum ** ppEnum); |
1037 | COM_METHOD GetCount(ULONG * pcFrames); |
1038 | |
1039 | // |
1040 | // ICorDebugFrameEnum |
1041 | // |
1042 | |
1043 | COM_METHOD Next(ULONG cFrames, ICorDebugFrame * rgpFrames[], ULONG * pcFramesFetched); |
1044 | |
1045 | // |
1046 | // accessors |
1047 | // |
1048 | |
1049 | // used to link ShimChainEnums in a list |
1050 | ShimFrameEnum * GetNext(); |
1051 | void SetNext(ShimFrameEnum * pNext); |
1052 | |
1053 | private: |
1054 | ShimStackWalk * m_pStackWalk; // the owning ShimStackWalk |
1055 | ShimChain * m_pChain; // the owning ShimChain |
1056 | RSLock * m_pShimLock; // shim lock from ShimProcess to protect neuteredness checks |
1057 | |
1058 | // This points to the next ShimFrameEnum in the linked list of ShimFrameEnums to be cleaned up. |
1059 | // The head of the list is on the ShimStackWalk (m_pStackWalk->m_pFrameEnumList). |
1060 | ShimFrameEnum * m_pNext; |
1061 | |
1062 | UINT32 m_currentFrameIndex; // the current ICDFrame being enumerated |
1063 | UINT32 m_endFrameIndex; // the last index (exclusive) of the frame owned by the chain; |
1064 | // see code:ShimChain::GetLastFrameIndex |
1065 | Volatile<ULONG> m_refCount; |
1066 | BOOL m_fIsNeutered; |
1067 | }; |
1068 | |
1069 | |
1070 | #endif // SHIMPRIV_H |
1071 | |
1072 | |