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
6//
7// File: DebugSupport.cpp
8//
9// Support routines for debugging the CLR
10// ===========================================================================
11
12#include "stdafx.h"
13
14#ifndef FEATURE_PAL
15#ifndef _TARGET_X86_
16
17//
18//
19// @TODO: This is old code that should be easy to implement on top of the existing DAC support.
20// This code was originally written prior to DAC.
21//
22//
23
24#include <winwrap.h>
25#include <windows.h>
26#include <winnt.h>
27#include <clrnt.h>
28#include <stddef.h> // offsetof
29#include "nibblemapmacros.h"
30#include "stdmacros.h"
31
32#include "fntableaccess.h"
33
34#define move(dst, src) \
35{ \
36 if (!fpReadMemory(pUserContext, (LPCVOID)(src), &(dst), sizeof(dst), NULL)) \
37 { \
38 _ASSERTE(!"MSCORDBG ERROR: ReadProcessMemory failed!!"); \
39 return STATUS_UNSUCCESSFUL; \
40 } \
41}
42
43#define move_field(dst, src, cls, fld) \
44 move(dst, (SIZE_T)(src) + FIELD_OFFSET(cls, fld))
45
46static NTSTATUS OutOfProcessFindHeader(ReadMemoryFunction fpReadMemory,PVOID pUserContext, DWORD_PTR pMapIn, DWORD_PTR addr, DWORD_PTR &codeHead)
47{
48 codeHead = 0;
49
50 DWORD tmp; // must be a DWORD, not a DWORD_PTR
51 DWORD_PTR startPos = ADDR2POS(addr); // align to 128 byte buckets ( == index into the array of nibbles)
52 DWORD_PTR offset = ADDR2OFFS(addr); // this is the offset inside the bucket + 1
53 DWORD * pMap = (DWORD *) pMapIn; // make this a pointer type so our pointer math is correct w/o adding sizeof(DWORD) everywhere
54
55 _ASSERTE(offset == (offset & NIBBLE_MASK)); // the offset must fit in a nibble
56
57 pMap += (startPos >> LOG2_NIBBLES_PER_DWORD); // points to the proper DWORD of the map
58
59 //
60 // get DWORD and shift down our nibble
61 //
62 move(tmp, pMap);
63 tmp = tmp >> POS2SHIFTCOUNT(startPos);
64
65 // don't allow equality in the next check (tmp & NIBBLE_MASK == offset)
66 // there are code blocks that terminate with a call instruction
67 // (like call throwobject), i.e. their return address is
68 // right behind the code block. If the memory manager allocates
69 // heap blocks w/o gaps, we could find the next header in such
70 // cases. Therefore we exclude the first DWORD of the header
71 // from our search, but since we call this function for code
72 // anyway (which starts at the end of the header) this is not
73 // a problem.
74 if ((tmp & NIBBLE_MASK) && ((tmp & NIBBLE_MASK) < offset) )
75 {
76 codeHead = POSOFF2ADDR(startPos, tmp & NIBBLE_MASK) - sizeof(CodeHeader);
77 return STATUS_SUCCESS;
78 }
79
80 // is there a header in the remainder of the DWORD ?
81 tmp = tmp >> NIBBLE_SIZE;
82
83 if (tmp)
84 {
85 startPos--;
86 while (!(tmp & NIBBLE_MASK))
87 {
88 tmp = tmp >> NIBBLE_SIZE;
89 startPos--;
90 }
91
92 codeHead = POSOFF2ADDR(startPos, tmp & NIBBLE_MASK) - sizeof(CodeHeader);
93 return STATUS_SUCCESS;
94 }
95
96 // we skipped the remainder of the DWORD,
97 // so we must set startPos to the highest position of
98 // previous DWORD
99
100 startPos = ((startPos >> LOG2_NIBBLES_PER_DWORD) << LOG2_NIBBLES_PER_DWORD) - 1;
101
102 if ((INT_PTR)startPos < 0)
103 {
104 return STATUS_SUCCESS;
105 }
106
107 // skip "headerless" DWORDS
108
109 pMap--;
110 move(tmp, pMap);
111 while (!tmp)
112 {
113 startPos -= NIBBLES_PER_DWORD;
114 if ((INT_PTR)startPos < 0)
115 {
116 return STATUS_SUCCESS;
117 }
118 pMap--;
119 move (tmp, pMap);
120 }
121
122
123 while (!(tmp & NIBBLE_MASK))
124 {
125 tmp = tmp >> NIBBLE_SIZE;
126 startPos--;
127 }
128
129 codeHead = POSOFF2ADDR(startPos, tmp & NIBBLE_MASK) - sizeof(CodeHeader);
130 return STATUS_SUCCESS;
131}
132
133#define CODE_HEADER FakeRealCodeHeader
134#define ResolveCodeHeader(pHeader) \
135 if (pHeader) \
136 { \
137 DWORD_PTR tmp = pHeader; \
138 tmp += offsetof (FakeCodeHeader, pRealCodeHeader); \
139 move (tmp, tmp); \
140 pHeader = tmp; \
141 }
142
143static NTSTATUS OutOfProcessFunctionTableCallback_JIT(IN ReadMemoryFunction fpReadMemory,
144 IN PVOID pUserContext,
145 IN PVOID TableAddress,
146 OUT PULONG pnEntries,
147 OUT PT_RUNTIME_FUNCTION* ppFunctions)
148{
149 if (NULL == pnEntries) { return STATUS_INVALID_PARAMETER_3; }
150 if (NULL == ppFunctions) { return STATUS_INVALID_PARAMETER_4; }
151
152 DYNAMIC_FUNCTION_TABLE * pTable = (DYNAMIC_FUNCTION_TABLE *) TableAddress;
153
154 PVOID pvContext;
155 move(pvContext, &pTable->Context);
156
157 DWORD_PTR JitMan = (((DWORD_PTR)pvContext) & ~3);
158
159 DWORD_PTR MinAddress = (DWORD_PTR) &(pTable->MinimumAddress);
160 move(MinAddress, MinAddress);
161
162 *ppFunctions = 0;
163 *pnEntries = 0;
164
165 DWORD_PTR pHp = JitMan + (DWORD_PTR)offsetof(FakeEEJitManager, m_pCodeHeap);
166
167 move(pHp, pHp);
168
169 while (pHp)
170 {
171 FakeHeapList Hp;
172
173 move(Hp, pHp);
174
175 if (pHp == MinAddress)
176 {
177 DWORD_PTR pThisHeader;
178 DWORD_PTR hdrOffset;
179 DWORD_PTR hdrOffsetInitial;
180 DWORD nEntries;
181 DWORD index;
182 PT_RUNTIME_FUNCTION pFunctions;
183 LONG64 lSmallestOffset;
184
185 //
186 // walk the header map and count functions with unwind info
187 //
188 nEntries = 0;
189 hdrOffset = Hp.endAddress - Hp.mapBase;
190 lSmallestOffset = (LONG64)(Hp.startAddress - Hp.mapBase);
191
192 // Save the initial offset at which we start our enumeration (from the end to the beginning).
193 // The target process could be running when this function is called. New methods could be
194 // added after we have started our enumeration, but their code headers would be added after
195 // this initial offset. Methods could also be deleted, but the memory would still be there.
196 // It just wouldn't be marked as the beginning of a method, and we would collect fewer entries
197 // than we have anticipated.
198 hdrOffsetInitial = hdrOffset;
199
200 _ASSERTE(((LONG64)hdrOffset) >= lSmallestOffset);
201 OutOfProcessFindHeader(fpReadMemory, pUserContext, Hp.pHdrMap, hdrOffset, hdrOffset);
202
203 while (((LONG64)hdrOffset) >= lSmallestOffset) // MUST BE A SIGNED COMPARISON
204 {
205 pThisHeader = Hp.mapBase + hdrOffset;
206 ResolveCodeHeader(pThisHeader);
207
208 if (pThisHeader > FAKE_STUB_CODE_BLOCK_LAST)
209 {
210 DWORD nUnwindInfos;
211 move_field(nUnwindInfos, pThisHeader, CODE_HEADER, nUnwindInfos);
212
213 nEntries += nUnwindInfos;
214 }
215
216 _ASSERTE(((LONG64)hdrOffset) >= lSmallestOffset);
217 OutOfProcessFindHeader(fpReadMemory, pUserContext, Hp.pHdrMap, hdrOffset, hdrOffset);
218 }
219
220 pFunctions = (PT_RUNTIME_FUNCTION)ClrHeapAlloc(ClrGetProcessHeap(), HEAP_ZERO_MEMORY, S_SIZE_T(nEntries) * S_SIZE_T(sizeof(T_RUNTIME_FUNCTION)));
221 *ppFunctions = pFunctions;
222 *pnEntries = nEntries;
223
224 //
225 // walk the header map and copy the function tables
226 //
227
228 index = 0;
229 hdrOffset = hdrOffsetInitial;
230
231 _ASSERTE(((LONG64)hdrOffset) >= lSmallestOffset);
232 OutOfProcessFindHeader(fpReadMemory, pUserContext, Hp.pHdrMap, hdrOffset, hdrOffset);
233
234 while (((LONG64)hdrOffset) >= lSmallestOffset) // MUST BE A SIGNED COMPARISON
235 {
236 pThisHeader = Hp.mapBase + hdrOffset;
237 ResolveCodeHeader(pThisHeader);
238
239 if (pThisHeader > FAKE_STUB_CODE_BLOCK_LAST)
240 {
241 DWORD nUnwindInfos;
242 move_field(nUnwindInfos, pThisHeader, CODE_HEADER, nUnwindInfos);
243
244 if ((index + nUnwindInfos) > nEntries)
245 {
246 break;
247 }
248 for (DWORD iUnwindInfo = 0; iUnwindInfo < nUnwindInfos; iUnwindInfo++)
249 {
250 move(pFunctions[index], pThisHeader + offsetof(CODE_HEADER, unwindInfos[iUnwindInfo]));
251 index++;
252 }
253 }
254
255 _ASSERTE(((LONG64)hdrOffset) >= lSmallestOffset);
256 OutOfProcessFindHeader(fpReadMemory, pUserContext, Hp.pHdrMap, hdrOffset, hdrOffset);
257 }
258
259 // Return the final count.
260 *pnEntries = index;
261 break;
262 }
263
264 pHp = (DWORD_PTR)Hp.hpNext;
265 }
266
267 return STATUS_SUCCESS;
268}
269
270
271#ifdef DEBUGSUPPORT_STUBS_HAVE_UNWIND_INFO
272
273static NTSTATUS OutOfProcessFunctionTableCallback_Stub(IN ReadMemoryFunction fpReadMemory,
274 IN PVOID pUserContext,
275 IN PVOID TableAddress,
276 OUT PULONG pnEntries,
277 OUT PT_RUNTIME_FUNCTION* ppFunctions)
278{
279 if (NULL == pnEntries) { return STATUS_INVALID_PARAMETER_3; }
280 if (NULL == ppFunctions) { return STATUS_INVALID_PARAMETER_4; }
281
282 *ppFunctions = 0;
283 *pnEntries = 0;
284
285 PVOID pvContext;
286 move_field(pvContext, TableAddress, DYNAMIC_FUNCTION_TABLE, Context);
287
288 SIZE_T pStubHeapSegment = ((SIZE_T)pvContext & ~3);
289
290 FakeStubUnwindInfoHeapSegment stubHeapSegment;
291 move(stubHeapSegment, pStubHeapSegment);
292
293 UINT nEntries = 0;
294 UINT nEntriesAllocated = 0;
295 PT_RUNTIME_FUNCTION rgFunctions = NULL;
296
297 for (int pass = 1; pass <= 2; pass++)
298 {
299 // Use the same initial header for both passes. The process may still be running,
300 // and so new entries could be added at the beginning of the list. Using the initial header
301 // makes sure new entries are not picked up in the second pass. Entries could also be deleted,
302 // and there is a small time window here where we could read invalid memory. This just means
303 // that ReadProcessMemory() may fail. As long as we don't crash the host process (e.g. WER)
304 // we are fine.
305 SIZE_T pHeader = (SIZE_T)stubHeapSegment.pUnwindHeaderList;
306
307 while (pHeader)
308 {
309 FakeStubUnwindInfoHeader unwindInfoHeader;
310 move(unwindInfoHeader, pHeader);
311#if defined(_TARGET_AMD64_)
312 // Consistency checks to detect corrupted process state
313 if (unwindInfoHeader.FunctionEntry.BeginAddress > unwindInfoHeader.FunctionEntry.EndAddress ||
314 unwindInfoHeader.FunctionEntry.EndAddress > stubHeapSegment.cbSegment)
315 {
316 _ASSERTE(1 == pass);
317 return STATUS_UNSUCCESSFUL;
318 }
319
320 if ((SIZE_T)stubHeapSegment.pbBaseAddress + unwindInfoHeader.FunctionEntry.UnwindData !=
321 pHeader + FIELD_OFFSET(FakeStubUnwindInfoHeader, UnwindInfo))
322 {
323 _ASSERTE(1 == pass);
324 return STATUS_UNSUCCESSFUL;
325 }
326#elif defined(_TARGET_ARM_)
327
328 // Skip checking the corrupted process stateon ARM
329
330#elif defined(_TARGET_ARM64_)
331 // Compute the function length
332 ULONG64 functionLength = 0;
333 ULONG64 unwindData = unwindInfoHeader.FunctionEntry.UnwindData;
334 if (( unwindData & 3) != 0) {
335 // the unwindData contains the function length, retrieve it directly from unwindData
336 functionLength = (unwindInfoHeader.FunctionEntry.UnwindData >> 2) & 0x7ff;
337 } else {
338 // the unwindData is an RVA to the .xdata record which contains the function length
339 DWORD xdataHeader=0;
340 if ((SIZE_T)stubHeapSegment.pbBaseAddress + unwindData != pHeader + FIELD_OFFSET(FakeStubUnwindInfoHeader, UnwindInfo))
341 {
342 _ASSERTE(1 == pass);
343 return STATUS_UNSUCCESSFUL;
344 }
345 move(xdataHeader, stubHeapSegment.pbBaseAddress + unwindData);
346 functionLength = (xdataHeader & 0x3ffff) << 2;
347 }
348 if (unwindInfoHeader.FunctionEntry.BeginAddress + functionLength > stubHeapSegment.cbSegment)
349 {
350 _ASSERTE(1 == pass);
351 return STATUS_UNSUCCESSFUL;
352 }
353#else
354 PORTABILITY_ASSERT("OutOfProcessFunctionTableCallback_Stub");
355#endif
356 if (nEntriesAllocated)
357 {
358 if (nEntries >= nEntriesAllocated)
359 break;
360 rgFunctions[nEntries] = unwindInfoHeader.FunctionEntry;
361 }
362 nEntries++;
363
364 pHeader = (SIZE_T)unwindInfoHeader.pNext;
365 }
366
367 if (1 == pass)
368 {
369 if (!nEntries)
370 break;
371
372 _ASSERTE(!nEntriesAllocated);
373 nEntriesAllocated = nEntries;
374 rgFunctions = (PT_RUNTIME_FUNCTION)ClrHeapAlloc(ClrGetProcessHeap(), HEAP_ZERO_MEMORY, S_SIZE_T(nEntries) * S_SIZE_T(sizeof(T_RUNTIME_FUNCTION)));
375 nEntries = 0;
376 }
377 else
378 {
379 _ASSERTE(nEntriesAllocated >= nEntries);
380 }
381 }
382
383 *ppFunctions = rgFunctions;
384 *pnEntries = nEntries; // return the final count
385
386 return STATUS_SUCCESS;
387}
388
389#endif // DEBUGSUPPORT_STUBS_HAVE_UNWIND_INFO
390
391
392BOOL ReadMemory(PVOID pUserContext, LPCVOID lpBaseAddress, PVOID lpBuffer, SIZE_T nSize, SIZE_T* lpNumberOfBytesRead)
393{
394 HANDLE hProcess = (HANDLE)pUserContext;
395 return ReadProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, lpNumberOfBytesRead);
396}
397
398extern "C" NTSTATUS OutOfProcessFunctionTableCallback(IN HANDLE hProcess,
399 IN PVOID TableAddress,
400 OUT PULONG pnEntries,
401 OUT PT_RUNTIME_FUNCTION* ppFunctions)
402{
403 return OutOfProcessFunctionTableCallbackEx(&ReadMemory, hProcess, TableAddress, pnEntries, ppFunctions);
404}
405
406extern "C" NTSTATUS OutOfProcessFunctionTableCallbackEx(IN ReadMemoryFunction fpReadMemory,
407 IN PVOID pUserContext,
408 IN PVOID TableAddress,
409 OUT PULONG pnEntries,
410 OUT PT_RUNTIME_FUNCTION* ppFunctions)
411{
412 if (NULL == pnEntries) { return STATUS_INVALID_PARAMETER_3; }
413 if (NULL == ppFunctions) { return STATUS_INVALID_PARAMETER_4; }
414
415 DYNAMIC_FUNCTION_TABLE * pTable = (DYNAMIC_FUNCTION_TABLE *) TableAddress;
416 PVOID pvContext;
417
418 move(pvContext, &pTable->Context);
419
420 FakeEEDynamicFunctionTableType type = (FakeEEDynamicFunctionTableType)((SIZE_T)pvContext & 3);
421
422 switch (type)
423 {
424 case FAKEDYNFNTABLE_JIT:
425 return OutOfProcessFunctionTableCallback_JIT(
426 fpReadMemory,
427 pUserContext,
428 TableAddress,
429 pnEntries,
430 ppFunctions);
431
432#ifdef DEBUGSUPPORT_STUBS_HAVE_UNWIND_INFO
433 case FAKEDYNFNTABLE_STUB:
434 return OutOfProcessFunctionTableCallback_Stub(
435 fpReadMemory,
436 pUserContext,
437 TableAddress,
438 pnEntries,
439 ppFunctions);
440#endif // DEBUGSUPPORT_STUBS_HAVE_UNWIND_INFO
441 default:
442 break;
443 }
444
445 return STATUS_UNSUCCESSFUL;
446}
447
448#else
449
450extern "C" NTSTATUS OutOfProcessFunctionTableCallback()
451{
452 return STATUS_UNSUCCESSFUL;
453}
454
455extern "C" NTSTATUS OutOfProcessFunctionTableCallbackEx()
456{
457 return STATUS_UNSUCCESSFUL;
458}
459
460#endif // !_TARGET_X86_
461#endif // !FEATURE_PAL
462