Merge pull request #1452 from akoeplinger/dictionary-duplicatekey
[platform/upstream/coreclr.git] / src / debug / di / shimlocaldatatarget.cpp
1 //
2 // Copyright (c) Microsoft. All rights reserved.
3 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
4 //
5 //*****************************************************************************
6
7 // 
8 // File: ShimLocalDataTarget.cpp
9 //
10 //*****************************************************************************
11 #include "stdafx.h"
12 #include "safewrap.h"
13
14 #include "check.h" 
15
16 #include <limits.h>
17
18 #include "shimpriv.h"
19 #include "shimdatatarget.h"
20
21
22 // The Shim's Live data-target is allowed to call OS APIs directly.
23 // see code:RSDebuggingInfo#UseDataTarget.
24 #undef ReadProcessMemory
25 #undef WriteProcessMemory
26
27
28 class ShimLocalDataTarget : public ShimDataTarget
29 {
30 public:
31     ShimLocalDataTarget(DWORD processId, HANDLE hProcess);
32
33     ~ShimLocalDataTarget();
34
35     virtual void Dispose();
36
37     //
38     // ICorDebugMutableDataTarget.
39     //
40
41     virtual HRESULT STDMETHODCALLTYPE GetPlatform( 
42         CorDebugPlatform *pPlatform);
43
44     virtual HRESULT STDMETHODCALLTYPE ReadVirtual( 
45         CORDB_ADDRESS address,
46         BYTE * pBuffer,
47         ULONG32 request,
48         ULONG32 *pcbRead);
49
50     virtual HRESULT STDMETHODCALLTYPE WriteVirtual( 
51         CORDB_ADDRESS address,
52         const BYTE * pBuffer,
53         ULONG32 request);
54
55     virtual HRESULT STDMETHODCALLTYPE GetThreadContext(
56         DWORD dwThreadID,
57         ULONG32 contextFlags,
58         ULONG32 contextSize,
59         BYTE * context);
60
61     virtual HRESULT STDMETHODCALLTYPE SetThreadContext(
62         DWORD dwThreadID,
63         ULONG32 contextSize,
64         const BYTE * context);
65
66     virtual HRESULT STDMETHODCALLTYPE ContinueStatusChanged(
67         DWORD dwThreadId,
68         CORDB_CONTINUE_STATUS dwContinueStatus);
69
70     virtual HRESULT STDMETHODCALLTYPE VirtualUnwind(
71         DWORD threadId, ULONG32 contextSize, PBYTE context);
72
73 private:
74     // Handle to the process. We own this.
75     HANDLE m_hProcess;
76 };
77
78
79 // Determines whether the target and host are running on compatible platforms.
80 // Arguments:
81 //     input: hTargetProcess - handle for the target process
82 // Return Value: TRUE iff both target and host are both Wow64 or neither is. 
83 // Note: throws
84 BOOL CompatibleHostAndTargetPlatforms(HANDLE hTargetProcess)
85 {
86 #if defined(FEATURE_PAL)    
87     return TRUE;
88 #else
89     // get the platform for the host process
90     BOOL fHostProcessIsWow64 = FALSE;
91     BOOL fSuccess = FALSE;            
92     HANDLE hHostProcess = GetCurrentProcess();
93
94     fSuccess = IsWow64Process(hHostProcess, &fHostProcessIsWow64);
95     CloseHandle(hHostProcess);
96     hHostProcess = NULL;
97
98     if (!fSuccess)
99     {
100         ThrowHR(HRESULT_FROM_GetLastError());
101     }
102     
103     //  get the platform for the target process
104     if (hTargetProcess == NULL)
105     {
106         ThrowHR(HRESULT_FROM_GetLastError());
107     }
108
109     BOOL fTargetProcessIsWow64 = FALSE;
110     fSuccess = IsWow64Process(hTargetProcess, &fTargetProcessIsWow64);
111
112     if (!fSuccess)
113     {
114         ThrowHR(HRESULT_FROM_GetLastError());
115     }
116
117     // We don't want to expose the IPC block if one process is x86 and
118     // the other is ia64 or amd64
119     if (fTargetProcessIsWow64 != fHostProcessIsWow64)
120     {
121         return FALSE;
122     }
123     else
124     {
125         return TRUE;
126     }
127 #endif
128 } // CompatibleHostAndTargetPlatforms
129
130 // Helper macro to check for failure conditions at the start of data-target methods.
131 #define ReturnFailureIfStateNotOk() \
132     if (m_hr != S_OK) \
133     { \
134         return m_hr; \
135     }
136
137 //---------------------------------------------------------------------------------------
138 //
139 // ctor for ShimLocalDataTarget. 
140 //
141 // Arguments:
142 //      processId - pid of live process.
143 //      hProcess - handle to kernel process object.
144 //
145 // Assumptions:
146 //    Shim takes ownership of handle hProcess.
147 //
148
149 ShimLocalDataTarget::ShimLocalDataTarget(DWORD processId, HANDLE hProcess)    
150 {
151     m_ref = 0;
152
153     m_processId = processId;
154     m_hProcess = hProcess;
155
156     m_hr = S_OK;
157
158     m_fpContinueStatusChanged = NULL;
159     m_pContinueStatusChangedUserData = NULL;
160 }
161
162 //---------------------------------------------------------------------------------------
163 //
164 // dctor for ShimLocalDataTarget. 
165 //
166 ShimLocalDataTarget::~ShimLocalDataTarget()
167 {
168     Dispose();
169 }
170
171
172 //---------------------------------------------------------------------------------------
173 //
174 // Dispose all resources and neuter the object.
175 //
176 //
177 //
178 // Notes:
179 //    Release all resources (such as the handle to the process we got in the ctor).
180 //    May be called multiple times.
181 //    All other non-trivial APIs (eg, not IUnknown) will fail after this.
182 //
183
184 void ShimLocalDataTarget::Dispose()
185 {
186     if (m_hProcess != NULL)
187     {
188         CloseHandle(m_hProcess);
189         m_hProcess = NULL;
190     }
191     m_hr = CORDBG_E_OBJECT_NEUTERED;
192 }
193
194 //---------------------------------------------------------------------------------------
195 //
196 // Construction method for data-target
197 //
198 // Arguments:
199 //      processId - (input) live OS process ID to build a data-target for.
200 //      ppDataTarget - (output) new data-target instance. This gets addreffed.
201 //
202 // Return Value:
203 //    S_OK on success.
204 //
205 // Assumptions:
206 //    pid must be for local, same architecture, process.
207 //    Caller must have security permissions for OpenProcess()
208 //    Caller must release *ppDataTarget.
209 //
210
211 HRESULT BuildPlatformSpecificDataTarget(MachineInfo machineInfo,
212                                         DWORD processId, 
213                                         ShimDataTarget ** ppDataTarget)
214 {
215     HRESULT hr = S_OK;
216     HANDLE hProcess = NULL;
217     ShimLocalDataTarget * pLocalDataTarget = NULL;
218     
219     *ppDataTarget = NULL;
220     
221     hProcess = OpenProcess(
222         PROCESS_DUP_HANDLE        |
223         PROCESS_QUERY_INFORMATION |
224         PROCESS_TERMINATE         |
225         PROCESS_VM_OPERATION      |
226         PROCESS_VM_READ           |
227         PROCESS_VM_WRITE          |
228         SYNCHRONIZE,
229         FALSE,
230         processId);
231
232     if (hProcess == NULL)
233     {
234         hr = HRESULT_FROM_GetLastError();
235         goto Label_Exit;
236     }
237
238     EX_TRY
239     {
240         if (!CompatibleHostAndTargetPlatforms(hProcess))
241         {
242             hr = CORDBG_E_UNCOMPATIBLE_PLATFORMS;
243             goto Label_Exit;
244         }
245     }
246     EX_CATCH_HRESULT(hr);
247     if (FAILED(hr))
248     {
249         goto Label_Exit;
250     }
251     pLocalDataTarget = new (nothrow) ShimLocalDataTarget(processId, hProcess);
252     if (pLocalDataTarget == NULL)
253     {
254         hr = E_OUTOFMEMORY;
255         goto Label_Exit;
256     }
257
258     // ShimLocalDataTarget now has ownership of Handle.
259     hProcess = NULL;
260
261     _ASSERTE(SUCCEEDED(hr));
262     *ppDataTarget = pLocalDataTarget;
263     pLocalDataTarget->AddRef(); // must addref out-parameters
264
265 Label_Exit:
266     if (FAILED(hr))
267     {
268         if (hProcess != NULL)
269         {
270             CloseHandle(hProcess);
271         }
272         delete pLocalDataTarget;
273     }    
274
275     return hr;
276 }
277
278 // impl of interface method ICorDebugDataTarget::GetPlatform
279 HRESULT STDMETHODCALLTYPE
280 ShimLocalDataTarget::GetPlatform( 
281         CorDebugPlatform *pPlatform)
282 {
283 #ifdef FEATURE_PAL
284 #error ShimLocalDataTarget is not implemented on PAL systems yet
285 #endif    
286     // Assume that we're running on Windows for now.
287 #if defined(DBG_TARGET_X86)
288     *pPlatform = CORDB_PLATFORM_WINDOWS_X86;
289 #elif defined(DBG_TARGET_AMD64)
290     *pPlatform = CORDB_PLATFORM_WINDOWS_AMD64;
291 #elif defined(DBG_TARGET_ARM)
292     *pPlatform = CORDB_PLATFORM_WINDOWS_ARM;
293 #elif defined(DBG_TARGET_ARM64)
294     *pPlatform = CORDB_PLATFORM_WINDOWS_ARM64;
295 #else
296 #error Unknown Processor.
297 #endif
298     return S_OK;
299 }
300
301 // impl of interface method ICorDebugDataTarget::ReadVirtual
302 HRESULT STDMETHODCALLTYPE
303 ShimLocalDataTarget::ReadVirtual( 
304     CORDB_ADDRESS address,
305     PBYTE pBuffer,
306     ULONG32 cbRequestSize,
307     ULONG32 *pcbRead)
308 {
309     ReturnFailureIfStateNotOk();
310
311
312     // ReadProcessMemory will fail if any part of the
313     // region to read does not have read access.  This
314     // routine attempts to read the largest valid prefix
315     // so it has to break up reads on page boundaries.
316
317     HRESULT hrStatus = S_OK;
318     ULONG32 totalDone = 0;
319     SIZE_T read;
320     ULONG32 readSize;
321
322     while (cbRequestSize > 0)
323     {
324         // Calculate bytes to read and don't let read cross
325         // a page boundary.
326         readSize = OS_PAGE_SIZE - (ULONG32)(address & (OS_PAGE_SIZE - 1));
327         readSize = min(cbRequestSize, readSize);
328
329         if (!ReadProcessMemory(m_hProcess, (PVOID)(ULONG_PTR)address,
330                                pBuffer, readSize, &read))
331         {
332             if (totalDone == 0)
333             {
334                 // If we haven't read anything indicate failure.
335                 hrStatus = HRESULT_FROM_GetLastError();
336             }
337             break;
338         }
339
340         totalDone += (ULONG32)read;
341         address += read;
342         pBuffer += read;
343         cbRequestSize -= (ULONG32)read;
344     }
345
346     *pcbRead = totalDone;
347     return hrStatus;
348 }
349
350 // impl of interface method ICorDebugMutableDataTarget::WriteVirtual
351 HRESULT STDMETHODCALLTYPE
352 ShimLocalDataTarget::WriteVirtual( 
353     CORDB_ADDRESS pAddress,
354     const BYTE * pBuffer,
355     ULONG32 cbRequestSize)
356 {
357     ReturnFailureIfStateNotOk();
358
359     SIZE_T cbWritten;
360     BOOL fWriteOk = WriteProcessMemory(m_hProcess, CORDB_ADDRESS_TO_PTR(pAddress), pBuffer, cbRequestSize, &cbWritten);
361     if (fWriteOk)
362     {
363         _ASSERTE(cbWritten == cbRequestSize);  // MSDN docs say this must always be true
364         return S_OK;
365     }
366     else
367     {
368         return HRESULT_FROM_GetLastError();
369     }
370 }
371
372 HRESULT STDMETHODCALLTYPE
373 ShimLocalDataTarget::GetThreadContext(
374     DWORD dwThreadID,
375     ULONG32 contextFlags,
376     ULONG32 contextSize,
377     BYTE * pContext)
378 {
379     ReturnFailureIfStateNotOk();
380     // @dbgtodo - Ideally we should cache the thread handles so that we don't need to 
381     // open and close the thread handles every time.
382
383     HRESULT hr = E_FAIL;
384
385     if (!CheckContextSizeForBuffer(contextSize, pContext))
386     {
387         return E_INVALIDARG;
388     }
389     
390     HandleHolder hThread = OpenThread(
391         THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_QUERY_INFORMATION , 
392         FALSE, // thread handle is not inheritable. 
393         dwThreadID);
394
395     if (hThread != NULL)
396     {
397         DT_CONTEXT * pCtx = reinterpret_cast<DT_CONTEXT *>(pContext);
398         pCtx->ContextFlags = contextFlags;
399
400         if (DbiGetThreadContext(hThread, pCtx))
401         {
402             hr = S_OK;
403         }
404     }
405
406     // hThread destructed automatically
407     return hr;
408 }
409
410 // impl of interface method ICorDebugMutableDataTarget::SetThreadContext
411 HRESULT STDMETHODCALLTYPE
412 ShimLocalDataTarget::SetThreadContext(
413     DWORD dwThreadID,
414     ULONG32 contextSize,
415     const BYTE * pContext)
416 {
417     ReturnFailureIfStateNotOk();
418     HRESULT hr = E_FAIL;
419
420     if (!CheckContextSizeForBuffer(contextSize, pContext))
421     {
422         return E_INVALIDARG;
423     }
424
425     
426     HandleHolder hThread = OpenThread(
427         THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_QUERY_INFORMATION, 
428         FALSE, // thread handle is not inheritable.
429         dwThreadID);
430
431     if (hThread != NULL)
432     {
433         if (DbiSetThreadContext(hThread, reinterpret_cast<const DT_CONTEXT *>(pContext)))
434         {
435             hr = S_OK;
436         }
437     }
438
439     // hThread destructed automatically
440     return hr;
441 }
442
443 // Public implementation of ICorDebugMutableDataTarget::ContinueStatusChanged
444 HRESULT STDMETHODCALLTYPE
445 ShimLocalDataTarget::ContinueStatusChanged(
446     DWORD dwThreadId,
447     CORDB_CONTINUE_STATUS dwContinueStatus)
448 {
449     ReturnFailureIfStateNotOk();
450     if (m_fpContinueStatusChanged != NULL)
451     {
452         return m_fpContinueStatusChanged(m_pContinueStatusChangedUserData, dwThreadId, dwContinueStatus);
453     }
454     return E_NOTIMPL;
455 }
456
457 //---------------------------------------------------------------------------------------
458 //
459 // Unwind the stack to the next frame.
460 //
461 // Return Value: 
462 //     context filled in with the next frame
463 //
464 HRESULT STDMETHODCALLTYPE 
465 ShimLocalDataTarget::VirtualUnwind(DWORD threadId, ULONG32 contextSize, PBYTE context)
466 {
467 #ifndef FEATURE_PAL
468     _ASSERTE(!"ShimLocalDataTarget::VirtualUnwind NOT IMPLEMENTED");
469 #endif 
470     return E_NOTIMPL;
471 }
472