summaryrefslogtreecommitdiff
path: root/src/debug/daccess/fntableaccess.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/debug/daccess/fntableaccess.cpp')
-rw-r--r--src/debug/daccess/fntableaccess.cpp461
1 files changed, 461 insertions, 0 deletions
diff --git a/src/debug/daccess/fntableaccess.cpp b/src/debug/daccess/fntableaccess.cpp
new file mode 100644
index 0000000000..e7b96ec3e0
--- /dev/null
+++ b/src/debug/daccess/fntableaccess.cpp
@@ -0,0 +1,461 @@
+// 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: DebugSupport.cpp
+//
+// Support routines for debugging the CLR
+// ===========================================================================
+
+#include "stdafx.h"
+
+#ifndef _TARGET_X86_
+
+//
+//
+// @TODO: This is old code that should be easy to implement on top of the existing DAC support.
+// This code was originally written prior to DAC.
+//
+//
+
+#include <winwrap.h>
+#include <windows.h>
+#include <winnt.h>
+#include <clrnt.h>
+#include <stddef.h> // offsetof
+#include "nibblemapmacros.h"
+#include "stdmacros.h"
+
+#include "fntableaccess.h"
+
+#define move(dst, src) \
+{ \
+ if (!fpReadMemory(pUserContext, (LPCVOID)(src), &(dst), sizeof(dst), NULL)) \
+ { \
+ _ASSERTE(!"MSCORDBG ERROR: ReadProcessMemory failed!!"); \
+ return STATUS_UNSUCCESSFUL; \
+ } \
+}
+
+#define move_field(dst, src, cls, fld) \
+ move(dst, (SIZE_T)(src) + FIELD_OFFSET(cls, fld))
+
+static NTSTATUS OutOfProcessFindHeader(ReadMemoryFunction fpReadMemory,PVOID pUserContext, DWORD_PTR pMapIn, DWORD_PTR addr, DWORD_PTR &codeHead)
+{
+ codeHead = 0;
+
+ DWORD tmp; // must be a DWORD, not a DWORD_PTR
+ DWORD_PTR startPos = ADDR2POS(addr); // align to 128 byte buckets ( == index into the array of nibbles)
+ DWORD_PTR offset = ADDR2OFFS(addr); // this is the offset inside the bucket + 1
+ DWORD * pMap = (DWORD *) pMapIn; // make this a pointer type so our pointer math is correct w/o adding sizeof(DWORD) everywhere
+
+ _ASSERTE(offset == (offset & NIBBLE_MASK)); // the offset must fit in a nibble
+
+ pMap += (startPos >> LOG2_NIBBLES_PER_DWORD); // points to the proper DWORD of the map
+
+ //
+ // get DWORD and shift down our nibble
+ //
+ move(tmp, pMap);
+ tmp = tmp >> POS2SHIFTCOUNT(startPos);
+
+ // don't allow equality in the next check (tmp & NIBBLE_MASK == offset)
+ // there are code blocks that terminate with a call instruction
+ // (like call throwobject), i.e. their return address is
+ // right behind the code block. If the memory manager allocates
+ // heap blocks w/o gaps, we could find the next header in such
+ // cases. Therefore we exclude the first DWORD of the header
+ // from our search, but since we call this function for code
+ // anyway (which starts at the end of the header) this is not
+ // a problem.
+ if ((tmp & NIBBLE_MASK) && ((tmp & NIBBLE_MASK) < offset) )
+ {
+ codeHead = POSOFF2ADDR(startPos, tmp & NIBBLE_MASK) - sizeof(CodeHeader);
+ return STATUS_SUCCESS;
+ }
+
+ // is there a header in the remainder of the DWORD ?
+ tmp = tmp >> NIBBLE_SIZE;
+
+ if (tmp)
+ {
+ startPos--;
+ while (!(tmp & NIBBLE_MASK))
+ {
+ tmp = tmp >> NIBBLE_SIZE;
+ startPos--;
+ }
+
+ codeHead = POSOFF2ADDR(startPos, tmp & NIBBLE_MASK) - sizeof(CodeHeader);
+ return STATUS_SUCCESS;
+ }
+
+ // we skipped the remainder of the DWORD,
+ // so we must set startPos to the highest position of
+ // previous DWORD
+
+ startPos = ((startPos >> LOG2_NIBBLES_PER_DWORD) << LOG2_NIBBLES_PER_DWORD) - 1;
+
+ if ((INT_PTR)startPos < 0)
+ {
+ return STATUS_SUCCESS;
+ }
+
+ // skip "headerless" DWORDS
+
+ pMap--;
+ move(tmp, pMap);
+ while (!tmp)
+ {
+ startPos -= NIBBLES_PER_DWORD;
+ if ((INT_PTR)startPos < 0)
+ {
+ return STATUS_SUCCESS;
+ }
+ pMap--;
+ move (tmp, pMap);
+ }
+
+
+ while (!(tmp & NIBBLE_MASK))
+ {
+ tmp = tmp >> NIBBLE_SIZE;
+ startPos--;
+ }
+
+ codeHead = POSOFF2ADDR(startPos, tmp & NIBBLE_MASK) - sizeof(CodeHeader);
+ return STATUS_SUCCESS;
+}
+
+#define CODE_HEADER FakeRealCodeHeader
+#define ResolveCodeHeader(pHeader) \
+ if (pHeader) \
+ { \
+ DWORD_PTR tmp = pHeader; \
+ tmp += offsetof (FakeCodeHeader, pRealCodeHeader); \
+ move (tmp, tmp); \
+ pHeader = tmp; \
+ }
+
+static NTSTATUS OutOfProcessFunctionTableCallback_JIT(IN ReadMemoryFunction fpReadMemory,
+ IN PVOID pUserContext,
+ IN PVOID TableAddress,
+ OUT PULONG pnEntries,
+ OUT PT_RUNTIME_FUNCTION* ppFunctions)
+{
+ if (NULL == pnEntries) { return STATUS_INVALID_PARAMETER_3; }
+ if (NULL == ppFunctions) { return STATUS_INVALID_PARAMETER_4; }
+
+ DYNAMIC_FUNCTION_TABLE * pTable = (DYNAMIC_FUNCTION_TABLE *) TableAddress;
+
+ PVOID pvContext;
+ move(pvContext, &pTable->Context);
+
+ DWORD_PTR JitMan = (((DWORD_PTR)pvContext) & ~3);
+
+ DWORD_PTR MinAddress = (DWORD_PTR) &(pTable->MinimumAddress);
+ move(MinAddress, MinAddress);
+
+ *ppFunctions = 0;
+ *pnEntries = 0;
+
+ DWORD_PTR pHp = JitMan + (DWORD_PTR)offsetof(FakeEEJitManager, m_pCodeHeap);
+
+ move(pHp, pHp);
+
+ while (pHp)
+ {
+ FakeHeapList Hp;
+
+ move(Hp, pHp);
+
+ if (pHp == MinAddress)
+ {
+ DWORD_PTR pThisHeader;
+ DWORD_PTR hdrOffset;
+ DWORD_PTR hdrOffsetInitial;
+ DWORD nEntries;
+ DWORD index;
+ DWORD_PTR pUnwindInfo;
+ PT_RUNTIME_FUNCTION pFunctions;
+ LONG64 lSmallestOffset;
+
+ //
+ // walk the header map and count functions with unwind info
+ //
+ nEntries = 0;
+ hdrOffset = Hp.endAddress - Hp.mapBase;
+ lSmallestOffset = (LONG64)(Hp.startAddress - Hp.mapBase);
+
+ // Save the initial offset at which we start our enumeration (from the end to the beginning).
+ // The target process could be running when this function is called. New methods could be
+ // added after we have started our enumeration, but their code headers would be added after
+ // this initial offset. Methods could also be deleted, but the memory would still be there.
+ // It just wouldn't be marked as the beginning of a method, and we would collect fewer entries
+ // than we have anticipated.
+ hdrOffsetInitial = hdrOffset;
+
+ _ASSERTE(((LONG64)hdrOffset) >= lSmallestOffset);
+ OutOfProcessFindHeader(fpReadMemory, pUserContext, Hp.pHdrMap, hdrOffset, hdrOffset);
+
+ while (((LONG64)hdrOffset) >= lSmallestOffset) // MUST BE A SIGNED COMPARISON
+ {
+ pThisHeader = Hp.mapBase + hdrOffset;
+ ResolveCodeHeader(pThisHeader);
+
+ if (pThisHeader > FAKE_STUB_CODE_BLOCK_LAST)
+ {
+ DWORD nUnwindInfos;
+ move_field(nUnwindInfos, pThisHeader, CODE_HEADER, nUnwindInfos);
+
+ nEntries += nUnwindInfos;
+ }
+
+ _ASSERTE(((LONG64)hdrOffset) >= lSmallestOffset);
+ OutOfProcessFindHeader(fpReadMemory, pUserContext, Hp.pHdrMap, hdrOffset, hdrOffset);
+ }
+
+ pFunctions = (PT_RUNTIME_FUNCTION)ClrHeapAlloc(ClrGetProcessHeap(), HEAP_ZERO_MEMORY, S_SIZE_T(nEntries) * S_SIZE_T(sizeof(T_RUNTIME_FUNCTION)));
+ *ppFunctions = pFunctions;
+ *pnEntries = nEntries;
+
+ //
+ // walk the header map and copy the function tables
+ //
+
+ index = 0;
+ hdrOffset = hdrOffsetInitial;
+
+ _ASSERTE(((LONG64)hdrOffset) >= lSmallestOffset);
+ OutOfProcessFindHeader(fpReadMemory, pUserContext, Hp.pHdrMap, hdrOffset, hdrOffset);
+
+ while (((LONG64)hdrOffset) >= lSmallestOffset) // MUST BE A SIGNED COMPARISON
+ {
+ pThisHeader = Hp.mapBase + hdrOffset;
+ ResolveCodeHeader(pThisHeader);
+
+ if (pThisHeader > FAKE_STUB_CODE_BLOCK_LAST)
+ {
+ DWORD nUnwindInfos;
+ move_field(nUnwindInfos, pThisHeader, CODE_HEADER, nUnwindInfos);
+
+ if ((index + nUnwindInfos) > nEntries)
+ {
+ break;
+ }
+ for (DWORD iUnwindInfo = 0; iUnwindInfo < nUnwindInfos; iUnwindInfo++)
+ {
+ move(pFunctions[index], pThisHeader + offsetof(CODE_HEADER, unwindInfos[iUnwindInfo]));
+ index++;
+ }
+ }
+
+ _ASSERTE(((LONG64)hdrOffset) >= lSmallestOffset);
+ OutOfProcessFindHeader(fpReadMemory, pUserContext, Hp.pHdrMap, hdrOffset, hdrOffset);
+ }
+
+ // Return the final count.
+ *pnEntries = index;
+ break;
+ }
+
+ pHp = (DWORD_PTR)Hp.hpNext;
+ }
+
+ return STATUS_SUCCESS;
+}
+
+
+#ifdef DEBUGSUPPORT_STUBS_HAVE_UNWIND_INFO
+
+static NTSTATUS OutOfProcessFunctionTableCallback_Stub(IN ReadMemoryFunction fpReadMemory,
+ IN PVOID pUserContext,
+ IN PVOID TableAddress,
+ OUT PULONG pnEntries,
+ OUT PT_RUNTIME_FUNCTION* ppFunctions)
+{
+ if (NULL == pnEntries) { return STATUS_INVALID_PARAMETER_3; }
+ if (NULL == ppFunctions) { return STATUS_INVALID_PARAMETER_4; }
+
+ *ppFunctions = 0;
+ *pnEntries = 0;
+
+ PVOID pvContext;
+ move_field(pvContext, TableAddress, DYNAMIC_FUNCTION_TABLE, Context);
+
+ SIZE_T pStubHeapSegment = ((SIZE_T)pvContext & ~3);
+
+ FakeStubUnwindInfoHeapSegment stubHeapSegment;
+ move(stubHeapSegment, pStubHeapSegment);
+
+ UINT nEntries = 0;
+ UINT nEntriesAllocated = 0;
+ PT_RUNTIME_FUNCTION rgFunctions = NULL;
+
+ for (int pass = 1; pass <= 2; pass++)
+ {
+ // Use the same initial header for both passes. The process may still be running,
+ // and so new entries could be added at the beginning of the list. Using the initial header
+ // makes sure new entries are not picked up in the second pass. Entries could also be deleted,
+ // and there is a small time window here where we could read invalid memory. This just means
+ // that ReadProcessMemory() may fail. As long as we don't crash the host process (e.g. WER)
+ // we are fine.
+ SIZE_T pHeader = (SIZE_T)stubHeapSegment.pUnwindHeaderList;
+
+ while (pHeader)
+ {
+ FakeStubUnwindInfoHeader unwindInfoHeader;
+ move(unwindInfoHeader, pHeader);
+#if defined(_TARGET_AMD64_)
+ // Consistency checks to detect corrupted process state
+ if (unwindInfoHeader.FunctionEntry.BeginAddress > unwindInfoHeader.FunctionEntry.EndAddress ||
+ unwindInfoHeader.FunctionEntry.EndAddress > stubHeapSegment.cbSegment)
+ {
+ _ASSERTE(1 == pass);
+ return STATUS_UNSUCCESSFUL;
+ }
+
+ if ((SIZE_T)stubHeapSegment.pbBaseAddress + unwindInfoHeader.FunctionEntry.UnwindData !=
+ pHeader + FIELD_OFFSET(FakeStubUnwindInfoHeader, UnwindInfo))
+ {
+ _ASSERTE(1 == pass);
+ return STATUS_UNSUCCESSFUL;
+ }
+#elif defined(_TARGET_ARM_)
+
+ // Skip checking the corrupted process stateon ARM
+
+#elif defined(_TARGET_ARM64_)
+ // Compute the function length
+ ULONG64 functionLength = 0;
+ ULONG64 unwindData = unwindInfoHeader.FunctionEntry.UnwindData;
+ if (( unwindData & 3) != 0) {
+ // the unwindData contains the function length, retrieve it directly from unwindData
+ functionLength = (unwindInfoHeader.FunctionEntry.UnwindData >> 2) & 0x7ff;
+ } else {
+ // the unwindData is an RVA to the .xdata record which contains the function length
+ DWORD xdataHeader=0;
+ if ((SIZE_T)stubHeapSegment.pbBaseAddress + unwindData != pHeader + FIELD_OFFSET(FakeStubUnwindInfoHeader, UnwindInfo))
+ {
+ _ASSERTE(1 == pass);
+ return STATUS_UNSUCCESSFUL;
+ }
+ move(xdataHeader, stubHeapSegment.pbBaseAddress + unwindData);
+ functionLength = (xdataHeader & 0x3ffff) << 2;
+ }
+ if (unwindInfoHeader.FunctionEntry.BeginAddress + functionLength > stubHeapSegment.cbSegment)
+ {
+ _ASSERTE(1 == pass);
+ return STATUS_UNSUCCESSFUL;
+ }
+#else
+ PORTABILITY_ASSERT("OutOfProcessFunctionTableCallback_Stub");
+#endif
+ if (nEntriesAllocated)
+ {
+ if (nEntries >= nEntriesAllocated)
+ break;
+ rgFunctions[nEntries] = unwindInfoHeader.FunctionEntry;
+ }
+ nEntries++;
+
+ pHeader = (SIZE_T)unwindInfoHeader.pNext;
+ }
+
+ if (1 == pass)
+ {
+ if (!nEntries)
+ break;
+
+ _ASSERTE(!nEntriesAllocated);
+ nEntriesAllocated = nEntries;
+ rgFunctions = (PT_RUNTIME_FUNCTION)ClrHeapAlloc(ClrGetProcessHeap(), HEAP_ZERO_MEMORY, S_SIZE_T(nEntries) * S_SIZE_T(sizeof(T_RUNTIME_FUNCTION)));
+ nEntries = 0;
+ }
+ else
+ {
+ _ASSERTE(nEntriesAllocated >= nEntries);
+ }
+ }
+
+ *ppFunctions = rgFunctions;
+ *pnEntries = nEntries; // return the final count
+
+ return STATUS_SUCCESS;
+}
+
+#endif // DEBUGSUPPORT_STUBS_HAVE_UNWIND_INFO
+
+BOOL ReadMemory(PVOID pUserContext, LPCVOID lpBaseAddress, PVOID lpBuffer, SIZE_T nSize, SIZE_T* lpNumberOfBytesRead)
+{
+ HANDLE hProcess = (HANDLE)pUserContext;
+ return ReadProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, lpNumberOfBytesRead);
+}
+
+extern "C" NTSTATUS OutOfProcessFunctionTableCallback(IN HANDLE hProcess,
+ IN PVOID TableAddress,
+ OUT PULONG pnEntries,
+ OUT PT_RUNTIME_FUNCTION* ppFunctions)
+{
+ return OutOfProcessFunctionTableCallbackEx(&ReadMemory, hProcess, TableAddress, pnEntries, ppFunctions);
+}
+
+extern "C" NTSTATUS OutOfProcessFunctionTableCallbackEx(IN ReadMemoryFunction fpReadMemory,
+ IN PVOID pUserContext,
+ IN PVOID TableAddress,
+ OUT PULONG pnEntries,
+ OUT PT_RUNTIME_FUNCTION* ppFunctions)
+{
+ if (NULL == pnEntries) { return STATUS_INVALID_PARAMETER_3; }
+ if (NULL == ppFunctions) { return STATUS_INVALID_PARAMETER_4; }
+
+ DYNAMIC_FUNCTION_TABLE * pTable = (DYNAMIC_FUNCTION_TABLE *) TableAddress;
+ PVOID pvContext;
+
+ move(pvContext, &pTable->Context);
+
+ FakeEEDynamicFunctionTableType type = (FakeEEDynamicFunctionTableType)((SIZE_T)pvContext & 3);
+
+ switch (type)
+ {
+ case FAKEDYNFNTABLE_JIT:
+ return OutOfProcessFunctionTableCallback_JIT(
+ fpReadMemory,
+ pUserContext,
+ TableAddress,
+ pnEntries,
+ ppFunctions);
+
+#ifdef DEBUGSUPPORT_STUBS_HAVE_UNWIND_INFO
+ case FAKEDYNFNTABLE_STUB:
+ return OutOfProcessFunctionTableCallback_Stub(
+ fpReadMemory,
+ pUserContext,
+ TableAddress,
+ pnEntries,
+ ppFunctions);
+#endif // DEBUGSUPPORT_STUBS_HAVE_UNWIND_INFO
+ default:
+ break;
+ }
+
+ return STATUS_UNSUCCESSFUL;
+}
+
+#else
+
+extern "C" NTSTATUS OutOfProcessFunctionTableCallback()
+{
+ return STATUS_UNSUCCESSFUL;
+}
+
+extern "C" NTSTATUS OutOfProcessFunctionTableCallbackEx()
+{
+ return STATUS_UNSUCCESSFUL;
+}
+
+
+
+#endif // !_TARGET_X86_