summaryrefslogtreecommitdiff
path: root/src/debug/di/windowspipeline.cpp
blob: 6e58f0be15932ea21db5bff63cce87496bc02b66 (plain)
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
// 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: WindowsPipeline.cpp
// 

//
// Implements the native-pipeline on Windows OS.
//*****************************************************************************

#include "stdafx.h"
#include "nativepipeline.h"

#include <Tlhelp32.h>

#include "holder.h"


DWORD GetProcessId(const DEBUG_EVENT * pEvent)
{
    return pEvent->dwProcessId;
}
DWORD GetThreadId(const DEBUG_EVENT * pEvent)
{
    return pEvent->dwThreadId;
}

// Get exception event
BOOL IsExceptionEvent(const DEBUG_EVENT * pEvent, BOOL * pfFirstChance, const EXCEPTION_RECORD ** ppRecord)
{
    if (pEvent->dwDebugEventCode != EXCEPTION_DEBUG_EVENT)
    {
        *pfFirstChance = FALSE;
        *ppRecord = NULL;
        return FALSE;
    }
    *pfFirstChance = pEvent->u.Exception.dwFirstChance;
    *ppRecord = &(pEvent->u.Exception.ExceptionRecord);
    return TRUE;
}


//---------------------------------------------------------------------------------------
// Class serves as a connector to win32 native-debugging API.
class WindowsNativePipeline : 
    public INativeEventPipeline
{
public:
    WindowsNativePipeline()
    {
        // Default value for Win32.
        m_fKillOnExit = true;
        m_dwProcessId = 0;
    }

    // Call to free up the pipeline.
    virtual void Delete();

    virtual BOOL DebugSetProcessKillOnExit(bool fKillOnExit);

    // Create
    virtual HRESULT CreateProcessUnderDebugger(
        MachineInfo machineInfo,
        LPCWSTR lpApplicationName,
        LPCWSTR lpCommandLine,
        LPSECURITY_ATTRIBUTES lpProcessAttributes,
        LPSECURITY_ATTRIBUTES lpThreadAttributes,
        BOOL bInheritHandles,
        DWORD dwCreationFlags,
        LPVOID lpEnvironment,
        LPCWSTR lpCurrentDirectory,
        LPSTARTUPINFOW lpStartupInfo,
        LPPROCESS_INFORMATION lpProcessInformation);

    // Attach
    virtual HRESULT DebugActiveProcess(MachineInfo machineInfo, const ProcessDescriptor& processDescriptor);

    // Detach
    virtual HRESULT DebugActiveProcessStop(DWORD processId);

    virtual BOOL WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess);

    virtual BOOL ContinueDebugEvent(
      DWORD dwProcessId,
      DWORD dwThreadId,
      DWORD dwContinueStatus
    );

    // Return a handle for the debuggee process.
    virtual HANDLE GetProcessHandle();

    // Terminate the debuggee process.
    virtual BOOL TerminateProcess(UINT32 exitCode);

    // Resume any suspended threads
    virtual HRESULT EnsureThreadsRunning();

protected:
    void UpdateDebugSetProcessKillOnExit();

    HRESULT IsRemoteDebuggerPresent(DWORD processId, BOOL* pfDebuggerPresent);

    // Cached value from DebugSetProcessKillOnExit.
    // This is thread-local, and impacts all debuggees on the thread.
    bool m_fKillOnExit;

    DWORD m_dwProcessId;
};

// Allocate and return a pipeline object for this platform
INativeEventPipeline * NewPipelineForThisPlatform()
{
    return new (nothrow) WindowsNativePipeline();
}

// Call to free up the pipeline.
void WindowsNativePipeline::Delete()
{
    delete this;
}


// set whether to kill outstanding debuggees when the debugger exits.
BOOL WindowsNativePipeline::DebugSetProcessKillOnExit(bool fKillOnExit)
{
    // Can't call kernel32!DebugSetProcessKillOnExit until after the event thread
    // has spawned a debuggee. So cache the value now and call it later.
    // This bit is enforced in code:WindowsNativePipeline::UpdateDebugSetProcessKillOnExit
    m_fKillOnExit = fKillOnExit;
    return TRUE;
}

// Enforces the bit set in code:WindowsNativePipeline::DebugSetProcessKillOnExit
void WindowsNativePipeline::UpdateDebugSetProcessKillOnExit()
{
#if !defined(FEATURE_CORESYSTEM)
    // Late bind to DebugSetProcessKillOnExit - WinXP and above only
    HModuleHolder hKernel32;
    hKernel32 = WszLoadLibrary(W("kernel32"));
    SIMPLIFYING_ASSUMPTION(hKernel32 != NULL);
    if (hKernel32 == NULL)
        return;

    typedef BOOL (*DebugSetProcessKillOnExitSig) (BOOL);
    DebugSetProcessKillOnExitSig pDebugSetProcessKillOnExit = 
        reinterpret_cast<DebugSetProcessKillOnExitSig>(GetProcAddress(hKernel32, "DebugSetProcessKillOnExit"));

    // If the API doesn't exist (eg. Win2k) - there isn't anything we can do, just
    // silently ignore the request.
    if (pDebugSetProcessKillOnExit == NULL)
        return;

    BOOL ret = pDebugSetProcessKillOnExit(m_fKillOnExit);

    // Not a good failure path here. 
    // 1) This shouldn't fail.
    // 2) Even if it does, this is likely called after the debuggee
    // has already been created, and if this API fails, most scenarios will
    // be unaffected, so we don't want to fail the overall debugging session. 
    SIMPLIFYING_ASSUMPTION(ret);

#else
	// The API doesn't exit on CoreSystem, just return
	return;
#endif
}

// Create an process under the debugger.
HRESULT WindowsNativePipeline::CreateProcessUnderDebugger(
    MachineInfo machineInfo,
    LPCWSTR lpApplicationName,
    LPCWSTR lpCommandLine,
    LPSECURITY_ATTRIBUTES lpProcessAttributes,
    LPSECURITY_ATTRIBUTES lpThreadAttributes,
    BOOL bInheritHandles,
    DWORD dwCreationFlags,
    LPVOID lpEnvironment,
    LPCWSTR lpCurrentDirectory,
    LPSTARTUPINFOW lpStartupInfo,
    LPPROCESS_INFORMATION lpProcessInformation)
{
    // This is always doing Native-debugging at the OS-level.
    dwCreationFlags |= (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS);

    BOOL ret = ::WszCreateProcess(
          lpApplicationName,
          lpCommandLine,
          lpProcessAttributes,
          lpThreadAttributes,
          bInheritHandles,
          dwCreationFlags,
          lpEnvironment,
          lpCurrentDirectory,
          lpStartupInfo,
          lpProcessInformation);
    if (!ret)
    {
        return HRESULT_FROM_GetLastError();
    }

    m_dwProcessId = lpProcessInformation->dwProcessId;
    UpdateDebugSetProcessKillOnExit();
    return S_OK;    
}

// Attach the debugger to this process.
HRESULT WindowsNativePipeline::DebugActiveProcess(MachineInfo machineInfo, const ProcessDescriptor& processDescriptor)
{
    HRESULT hr = E_FAIL;
    BOOL ret = ::DebugActiveProcess(processDescriptor.m_Pid);

    if (ret)
    {
        hr = S_OK;
        m_dwProcessId = processDescriptor.m_Pid;
        UpdateDebugSetProcessKillOnExit();
    }
    else
    {
        hr = HRESULT_FROM_GetLastError();

        // There are at least two scenarios in which DebugActiveProcess() returns E_INVALIDARG: 
        //     1) if the specified process doesn't exist, or
        //     2) if the specified process already has a debugger atttached
        // We need to distinguish these two cases in order to return the correct HR.
        if (hr == E_INVALIDARG)
        {
            // Check whether a debugger is known to be already attached.
            // Note that this API won't work on some OSes, in which case we err on the side of returning E_INVALIDARG
            // even though a debugger may be attached.  Another approach could be to assume that if
            // OpenProcess succeeded, then DebugActiveProcess must only have failed because a debugger is
            // attached.  But I think it's better to only return the specific error code if we know for sure
            // the case is true.
            BOOL fIsDebuggerPresent = FALSE;
            if (SUCCEEDED(IsRemoteDebuggerPresent(processDescriptor.m_Pid, &fIsDebuggerPresent)))
            {
                if (fIsDebuggerPresent)
                {
                    hr = CORDBG_E_DEBUGGER_ALREADY_ATTACHED;
                }
            }
        }
    }

    return hr;
}

// Determine (if possible) whether a debugger is attached to the target process
HRESULT WindowsNativePipeline::IsRemoteDebuggerPresent(DWORD processId, BOOL* pfDebuggerPresent)
{
#if !defined(FEATURE_CORESYSTEM)

    // Get a process handle for the process ID.
    HandleHolder hProc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, processId);
    if (hProc == NULL)
        return HRESULT_FROM_GetLastError();

    // Delay-bind to CheckRemoteDebuggerPresent - WinXP SP1 and above only
    HModuleHolder hKernel32;
    hKernel32 = WszLoadLibrary(W("kernel32"));
    if (hKernel32 == NULL)
        return HRESULT_FROM_GetLastError();

    typedef BOOL (*CheckRemoteDebuggerPresentSig) (HANDLE, PBOOL);
    CheckRemoteDebuggerPresentSig pCheckRemoteDebuggerPresent = 
        reinterpret_cast<CheckRemoteDebuggerPresentSig>(GetProcAddress(hKernel32, "CheckRemoteDebuggerPresent"));
    if (pCheckRemoteDebuggerPresent == NULL)
        return HRESULT_FROM_GetLastError();

    // API exists - call it
    if (!pCheckRemoteDebuggerPresent(hProc, pfDebuggerPresent))
        return HRESULT_FROM_GetLastError();

    return S_OK;
#else

	//CoreSystem doesn't have this API
	return E_FAIL;
#endif
}

// Detach
HRESULT WindowsNativePipeline::DebugActiveProcessStop(DWORD processId)
{
#if !defined(FEATURE_CORESYSTEM)
    // Late-bind to DebugActiveProcessStop since it's WinXP and above only
    HModuleHolder hKernel32;
    hKernel32 = WszLoadLibrary(W("kernel32"));
    if (hKernel32 == NULL)
        return HRESULT_FROM_GetLastError();

    typedef BOOL (*DebugActiveProcessStopSig) (DWORD);
    DebugActiveProcessStopSig pDebugActiveProcessStop = 
        reinterpret_cast<DebugActiveProcessStopSig>(GetProcAddress(hKernel32, "DebugActiveProcessStop"));

    // Win2K will fail here - can't find DebugActiveProcessStop
    if (pDebugActiveProcessStop == NULL)
        return HRESULT_FROM_GetLastError();

    // Ok, the API exists, call it
    if (!pDebugActiveProcessStop(processId))
    {
        // Detach itself failed
        return HRESULT_FROM_GetLastError();
    }
#else
	// The API exists, call it
    if (!::DebugActiveProcessStop(processId))
    {
        // Detach itself failed
        return HRESULT_FROM_GetLastError();
    }
#endif
    return S_OK;
}

BOOL WindowsNativePipeline::WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess)
{
    return ::WaitForDebugEvent(pEvent, dwTimeout);
}

BOOL WindowsNativePipeline::ContinueDebugEvent(
  DWORD dwProcessId,
  DWORD dwThreadId,
  DWORD dwContinueStatus
)
{
    return ::ContinueDebugEvent(dwProcessId, dwThreadId, dwContinueStatus);
}

// Return a handle for the debuggee process.
HANDLE WindowsNativePipeline::GetProcessHandle()
{
    _ASSERTE(m_dwProcessId != 0);

    return ::OpenProcess(PROCESS_DUP_HANDLE        |
                         PROCESS_QUERY_INFORMATION |
                         PROCESS_TERMINATE         |
                         PROCESS_VM_OPERATION      |
                         PROCESS_VM_READ           |
                         PROCESS_VM_WRITE          |
                         SYNCHRONIZE,
                         FALSE,
                         m_dwProcessId);
}

// Terminate the debuggee process.
BOOL WindowsNativePipeline::TerminateProcess(UINT32 exitCode)
{
    _ASSERTE(m_dwProcessId != 0);

    // Get a process handle for the process ID.
    HandleHolder hProc = OpenProcess(PROCESS_TERMINATE, FALSE, m_dwProcessId);

    if (hProc == NULL)
    {
        return FALSE;
    }

    return ::TerminateProcess(hProc, exitCode);
}

// Resume any suspended threads (but just once)
HRESULT WindowsNativePipeline::EnsureThreadsRunning()
{
#ifdef FEATURE_CORESYSTEM
	_ASSERTE("NYI");
	return E_FAIL;
#else
    _ASSERTE(m_dwProcessId != 0);

    // Take a snapshot of all running threads (similar to ShimProcess::QueueFakeThreadAttachEventsNativeOrder)
    // Alternately we could return thread creation/exit in WaitForDebugEvent.  But we expect this to be used 
    // very rarely, so no need to complicate more common codepaths.
    HANDLE hThreadSnap = INVALID_HANDLE_VALUE; 
    THREADENTRY32 te32; 

    hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); 
    if (hThreadSnap == INVALID_HANDLE_VALUE) 
        return HRESULT_FROM_GetLastError();

    // HandleHolder doesn't deal with INVALID_HANDLE_VALUE, so we only assign if we have a legal value.
    HandleHolder hSnapshotHolder(hThreadSnap);

    // Fill in the size of the structure before using it. 
    te32.dwSize = sizeof(THREADENTRY32); 

    // Retrieve information about the first thread, and exit if unsuccessful
    if (!Thread32First(hThreadSnap, &te32)) 
        return HRESULT_FROM_GetLastError();

    // Now walk the thread list of the system and attempt to resume any that are part of this process
    // Ignore errors - this is a best effort (but ASSERT in CHK builds since we don't expect errors
    // in practice - we expect the process to be frozen at a debug event, so no races etc.)

    HRESULT hr = S_FALSE;   // no thread was resumed
    do 
    { 
        if (te32.th32OwnerProcessID == m_dwProcessId)
        {
            HandleHolder hThread = ::OpenThread(THREAD_SUSPEND_RESUME, FALSE, te32.th32ThreadID);
            _ASSERTE(hThread != NULL);
            if (hThread != NULL)
            {
                // Resume each thread exactly once (if they were suspended multiple times, 
                // then EnsureThreadsRunning would need to be called multiple times until it
                // returned S_FALSE.
                DWORD prevCount = ::ResumeThread(hThread);
                _ASSERTE(prevCount >= 0);
                if (prevCount >= 1)
                    hr = S_OK;      // some thread was resumed
            }
        }
    } while(Thread32Next(hThreadSnap, &te32)); 

    return hr;
#endif
}