diff --git a/docs/design/datacontracts/ExecutionManager.md b/docs/design/datacontracts/ExecutionManager.md index 8504456e1d749e..9075ee2548b643 100644 --- a/docs/design/datacontracts/ExecutionManager.md +++ b/docs/design/datacontracts/ExecutionManager.md @@ -23,6 +23,10 @@ struct CodeBlockHandle TargetPointer GetMethodDesc(CodeBlockHandle codeInfoHandle); // Get the instruction pointer address of the start of the code block TargetCodePointer GetStartAddress(CodeBlockHandle codeInfoHandle); + // Gets the unwind info of the code block at the specified code pointer + TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle, TargetCodePointer ip); + // Gets the base address the UnwindInfo of codeInfoHandle is relative to. + TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle); ``` ## Version 1 @@ -53,6 +57,8 @@ Data descriptors used: | `CodeHeapListNode` | `MapBase` | Start of the map - start address rounded down based on OS page size | | `CodeHeapListNode` | `HeaderMap` | Bit array used to find the start of methods - relative to `MapBase` | | `RealCodeHeader` | `MethodDesc` | Pointer to the corresponding `MethodDesc` | +| `RealCodeHeader` | `NumUnwindInfos` | Number of Unwind Infos | +| `RealCodeHeader` | `UnwindInfos` | Start address of Unwind Infos | | `Module` | `ReadyToRunInfo` | Pointer to the `ReadyToRunInfo` for the module | | `ReadyToRunInfo` | `CompositeInfo` | Pointer to composite R2R info - or itself for non-composite | | `ReadyToRunInfo` | `NumRuntimeFunctions` | Number of `RuntimeFunctions` | @@ -214,7 +220,7 @@ class CodeBlock } ``` -The remaining contract APIs extract fields of the `CodeBlock`: +The `GetMethodDesc` and `GetStartAddress` APIs extract fields of the `CodeBlock`: ```csharp TargetPointer IExecutionManager.GetMethodDesc(CodeBlockHandle codeInfoHandle) @@ -230,6 +236,15 @@ The remaining contract APIs extract fields of the `CodeBlock`: } ``` +`GetUnwindInfo` gets the Windows style unwind data in the form of `RUNTIME_FUNCTION` which has a platform dependent implementation. The ExecutionManager delegates to the JitManager implementations as the unwind infos (`RUNTIME_FUNCTION`) are stored differently on jitted and R2R code. + +* For jitted code (`EEJitManager`) a list of sorted `RUNTIME_FUNCTION` are stored on the `RealCodeHeader` which is accessed in the same was as `GetMethodInfo` described above. The correct `RUNTIME_FUNCTION` is found by binary searching the list based on IP. + +* For R2R code (`ReadyToRunJitManager`), a list of sorted `RUNTIME_FUNCTION` are stored on the module's `ReadyToRunInfo`. This is accessed as described above for `GetMethodInfo`. Again, the relevant `RUNTIME_FUNCTION` is found by binary searching the list based on IP. + +Unwind info (`RUNTIME_FUNCTION`) use relative addressing. For managed code, these values are relative to the start of the code's containing range in the RangeSectionMap (described below). This could be the beginning of a `CodeHeap` for jitted code or the base address of the loaded image for ReadyToRun code. +`GetUnwindInfoBaseAddress` finds this base address for a given `CodeBlockHandle`. + ### RangeSectionMap The range section map logically partitions the entire 32-bit or 64-bit addressable space into chunks. diff --git a/docs/design/datacontracts/StackWalk.md b/docs/design/datacontracts/StackWalk.md new file mode 100644 index 00000000000000..d76aece42731f9 --- /dev/null +++ b/docs/design/datacontracts/StackWalk.md @@ -0,0 +1,246 @@ +# Contract StackWalk + +This contract encapsulates support for walking the stack of managed threads. + +## APIs of contract + +```csharp +public interface IStackDataFrameHandle { }; +``` + +```csharp +// Creates a stack walk and returns a handle +IEnumerable CreateStackWalk(ThreadData threadData); + +// Gets the thread context at the given stack dataframe. +byte[] GetRawContext(IStackDataFrameHandle stackDataFrameHandle); +// Gets the Frame address at the given stack dataframe. Returns TargetPointer.Null if the current dataframe does not have a valid Frame. +TargetPointer GetFrameAddress(IStackDataFrameHandle stackDataFrameHandle); +``` + +## Version 1 +To create a full walk of the managed stack, two types of 'stacks' must be read. + +1. True call frames on the thread's stack +2. Capital "F" Frames (referred to as Frames as opposed to frames) which are used by the runtime for book keeping purposes. + +Capital "F" Frames are pushed and popped to a singly-linked list on the runtime's Thread object and are accessible using the [IThread](./Thread.md) contract. These capital "F" Frames are allocated within a functions call frame, meaning they also live on the stack. A subset of Frame types store extra data allowing us to recover a portion of the context from when they were created For our purposes, these are relevant because they mark every transition where managed code calls native code. For more information about Frames see: [BOTR Stack Walking](https://github.com/dotnet/runtime/blob/44b7251f94772c69c2efb9daa7b69979d7ddd001/docs/design/coreclr/botr/stackwalking.md). + +Unwinding call frames on the stack usually requires an OS specific implementation. However, in our particular circumstance of unwinding only **managed function** call frames, the runtime uses Windows style unwind logic/codes for all platforms (this isn't true for NativeAOT). Therefore we can delegate to the existing native unwinding code located in `src/coreclr/unwinder/`. For more information on the Windows unwinding algorithm and unwind codes see the following docs: + +* [Windows x64](https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64) +* [Windows ARM64](https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling) + +This contract depends on the following descriptors: + +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| `Frame` | `Next` | Pointer to next from on linked list | +| `InlinedCallFrame` | `CallSiteSP` | SP saved in Frame | +| `InlinedCallFrame` | `CallerReturnAddress` | Return address saved in Frame | +| `InlinedCallFrame` | `CalleeSavedFP` | FP saved in Frame | +| `SoftwareExceptionFrame` | `TargetContext` | Context object saved in Frame | +| `SoftwareExceptionFrame` | `ReturnAddress` | Return address saved in Frame | + +Global variables used: +| Global Name | Type | Purpose | +| --- | --- | --- | +| For each FrameType ``, `##Identifier` | FrameIdentifier enum value | Identifier used to determine concrete type of Frames | + +Contracts used: +| Contract Name | +| --- | +| `ExecutionManager` | +| `Thread` | + + +### Stackwalk Algorithm +The intuition for walking a managed stack is relatively simply: unwind managed portions of the stack until we hit native code then use capital "F" Frames as checkpoints to get into new sections of managed code. Because Frames are added at each point before managed code (higher SP value) calls native code (lower SP values), we are guaranteed that a Frame exists at the top (lower SP value) of each managed call frame run. + +In reality, the actual algorithm is a little more complex fow two reasons. It requires pausing to return the current context and Frame at certain points and it checks for "skipped Frames" which can occur if an capital "F" Frame is allocated in a managed stack frame (e.g. an inlined P/Invoke call). + +1. Setup + 1. Set the current context `currContext` to be the thread's context. Fetched as part of the [ICorDebugDataTarget](https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/debugging/icordebugdatatarget-getthreadcontext-method) COM interface. + 2. Create a stack of the thread's capital "F" Frames `frameStack`. +2. **Return the current context**. +3. While the `currContext` is in managed code or `frameStack` is not empty: + 1. If `currContext` is native code, pop the top Frame from `frameStack` update the context using the popped Frame. **Return the updated context** and **go to step 3**. + 2. If `frameStack` is not empty, check for skipped Frames. Peek `frameStack` to find a Frame `frame`. Compare the address of `frame` (allocated on the stack) with the caller of the current context's stack pointer (found by unwinding current context one iteration). + If the address of the `frame` is less than the caller's stack pointer, **return the current context**, pop the top Frame from `frameStack`, and **go to step 3**. + 3. Unwind `currContext` using the Windows style unwinder. **Return the current context**. + + +#### Simple Example + +In this example we walk through the algorithm without instances of skipped Frames. + +Given the following call stack and capital "F" Frames linked list, we can apply the above algorithm. + + + + + + + + + +
Call Stack (growing down) Capital "F" Frames Linked List
+ +``` +Managed Call: ----------- + + | Native | <- 's SP + - | | + |-----------| <- 's SP + | | + | Managed | + | | + |-----------| <- 's SP + | | + | Native | + + | | + | StackBase | +``` + + +``` +SoftwareExceptionFrame + (Context = ) + + || + \/ + + NULL TERMINATOR +``` + +
+ +1. (1) Set `currContext` to the thread context ``. Create a stack of Frames `frameStack`. +2. (2) Return the `currContext` which has the threads context. +3. (3) `currContext` is in unmanaged code (native) however, because `frameStack` is not empty, we begin processing the context. +4. (3.1) Since `currContext` is unmanaged. We pop the SoftwareExceptionFrame from `frameStack` and use it to update `currContext`. The SoftwareExceptionFrame is holding context `` which we set `currContext` to. Return the current context and go back to step 3. +5. (3) Now `currContext` is in managed code as shown by ``'s SP. Therefore, we begin to process the context. +6. (3.1) Since `currContext` is managed, skip step 3.1. +7. (3.2) Since `frameStack` is empty, we do not check for skipped Frames. +8. (3.3) Unwind `currContext` a single iteration to `` and return the current context. +9. (3) `currContext` is now at unmanaged (native) code and `frameStack` is empty. Therefore we are done. + +The following C# code could yield a stack similar to the example above: +```csharp +void foo() +{ + // Call native code or function that calls down to native. + Console.ReadLine(); + // Capture stack trace while inside native code. +} +``` + +#### Skipped Frame Example +The skipped Frame check is important when managed code calls managed code through an unmanaged boundary. +This occurs when calling a function marked with `[UnmanagedCallersOnly]` as an unmanaged delegate from a managed caller. +In this case, if we ignored the skipped Frame check we would miss the unmanaged boundary. + +Given the following call stack and capital "F" Frames linked list, we can apply the above algorithm. + + + + + + + + + +
Call Stack (growing down) Capital "F" Frames Linked List
+ +``` +Unmanaged Call: -X-X-X-X-X- +Managed Call: ----------- +InlinedCallFrame location: [ICF] + + | Managed | <- 's SP + - | | + | | + |-X-X-X-X-X-| <- 's SP + | [ICF] | + | Managed | + | | + |-----------| <- 's SP + | | + | Native | + + | | + | StackBase | +``` + + +``` +InlinedCallFrame + (Context = ) + + || + \/ + + NULL TERMINATOR +``` + +
+ +1. (1) Set `currContext` to the thread context `
`. Create a stack of Frames `frameStack`. +2. (2) Return the `currContext` which has the threads context. +3. (3) Since `currContext` is in managed code, we begin to process the context. +4. (3.1) Since `currContext` is managed, skip step 3.1. +5. (3.2) Check for skipped Frames. Copy `currContext` into `parentContext` and unwind `parentContext` once using the Windows style unwinder. As seen from the call stack, unwinding `currContext=` will yield ``. We peek the top of `frameStack` and find an InlinedCallFrame (shown in call stack above as `[ICF]`). Since `parentContext`'s SP is greater than the address of `[ICF]` there are no skipped Frames. +6. (3.3) Unwind `currContext` a single iteration to `` and return the current context. +7. (3) Since `currContext` is still in managed code, we continue processing the context. +8. (3.1) Since `currContext` is managed, skip step 3.1. +9. (3.2) Check for skipped Frames. Copy `currContext` into `parentContext` and unwind `parentContext` once using the Windows style unwinder. As seen from the call stack, unwinding `currContext=` will yield ``. We peek the top of `frameStack` and find an InlinedCallFrame (shown in call stack above as `[ICF]`). This time the the address of `[ICF]` is less than `parentContext`'s SP. Therefore we return the current context then pop the InlinedCallFrame from `frameStack` which is now empty and return to step 3. +10. (3) Since `currContext` is still in managed code, we continue processing the context. +11. (3.1) Since `currContext` is managed, skip step 3.1. +12. (3.2) Since `frameStack` is empty, we do not check for skipped Frames. +13. (3.3) Unwind `currContext` a single iteration to `` and return the current context. +14. (3) `currContext` is now at unmanaged (native) code and `frameStack` is empty. Therefore we are done. + +The following C# code could yield a stack similar to the example above: +```csharp +void foo() +{ + var fptr = (delegate* unmanaged)&bar; + fptr(); +} + +[UnmanagedCallersOnly] +private static void bar() +{ + // Do something + // Capture stack trace while in here +} +``` + +### APIs + +The majority of the contract's complexity is the stack walking algorithm (detailed above) implemented as part of `CreateStackWalk`. +The `IEnumerable` return value is computed lazily. + +```csharp +IEnumerable CreateStackWalk(ThreadData threadData); +``` + +The rest of the APIs convey state about the stack walk at a given point which fall out of the stack walking algorithm relatively simply. + +`GetRawContext` Retrieves the raw Windows style thread context of the current frame as a byte array. The size and shape of the context is platform dependent. + +* On Windows the context is defined directly in Windows header `winnt.h`. See [CONTEXT structure](https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-context) for more info. +* On non-Windows platform the context's are defined in `src/coreclr/pal/inc/pal.h` and should mimic the Windows structure. + +This context is not guaranteed to be complete. Not all capital "F" Frames store the entire context, some only store the IP/SP/FP. Therefore, at points where the context is based on these Frames it will be incomplete. +```csharp +byte[] GetRawContext(IStackDataFrameHandle stackDataFrameHandle); +``` + + +`GetFrameAddress` gets the address of the current capital "F" Frame. This is only valid if the `IStackDataFrameHandle` is at a point where the context is based on a capital "F" Frame. For example, it is not valid when when the current context was created by using the stack frame unwinder. +If the Frame is not valid, returns `TargetPointer.Null`. + +```csharp +TargetPointer GetFrameAddress(IStackDataFrameHandle stackDataFrameHandle); +``` + diff --git a/eng/native/functions.cmake b/eng/native/functions.cmake index 581dbd28fb2bad..ba5689e01727e4 100644 --- a/eng/native/functions.cmake +++ b/eng/native/functions.cmake @@ -554,11 +554,11 @@ function(install_static_library targetName destination component) endif() endfunction() -# install_clr(TARGETS targetName [targetName2 ...] [DESTINATIONS destination [destination2 ...]] [COMPONENT componentName]) +# install_clr(TARGETS targetName [targetName2 ...] [DESTINATIONS destination [destination2 ...]] [COMPONENT componentName] [INSTALL_ALL_ARTIFACTS]) function(install_clr) set(multiValueArgs TARGETS DESTINATIONS) set(singleValueArgs COMPONENT) - set(options "") + set(options INSTALL_ALL_ARTIFACTS) cmake_parse_arguments(INSTALL_CLR "${options}" "${singleValueArgs}" "${multiValueArgs}" ${ARGV}) if ("${INSTALL_CLR_TARGETS}" STREQUAL "") @@ -594,9 +594,14 @@ function(install_clr) endif() foreach(destination ${destinations}) - # We don't need to install the export libraries for our DLLs - # since they won't be directly linked against. - install(PROGRAMS $ DESTINATION ${destination} COMPONENT ${INSTALL_CLR_COMPONENT}) + # Install the export libraries for static libraries. + if (${INSTALL_CLR_INSTALL_ALL_ARTIFACTS}) + install(TARGETS ${targetName} DESTINATION ${destination} COMPONENT ${INSTALL_CLR_COMPONENT}) + else() + # We don't need to install the export libraries for our DLLs + # since they won't be directly linked against. + install(PROGRAMS $ DESTINATION ${destination} COMPONENT ${INSTALL_CLR_COMPONENT}) + endif() if (NOT "${symbolFile}" STREQUAL "") install_symbol_file(${symbolFile} ${destination} COMPONENT ${INSTALL_CLR_COMPONENT}) endif() diff --git a/eng/pipelines/runtime-official.yml b/eng/pipelines/runtime-official.yml index 03c4308e13aebf..7b3afe0f222315 100644 --- a/eng/pipelines/runtime-official.yml +++ b/eng/pipelines/runtime-official.yml @@ -367,7 +367,7 @@ extends: - windows_x64 jobParameters: templatePath: 'templates-official' - buildArgs: -s tools+libs -pack -c $(_BuildConfig) /p:TestAssemblies=false /p:TestPackages=true + buildArgs: -s tools.illink+libs -pack -c $(_BuildConfig) /p:TestAssemblies=false /p:TestPackages=true nameSuffix: Libraries_WithPackages isOfficialBuild: ${{ variables.isOfficialBuild }} postBuildSteps: diff --git a/eng/pipelines/runtime.yml b/eng/pipelines/runtime.yml index 8fe7d9bfaa0853..f627b38bce8bf3 100644 --- a/eng/pipelines/runtime.yml +++ b/eng/pipelines/runtime.yml @@ -1244,7 +1244,7 @@ extends: platforms: - windows_x64 jobParameters: - buildArgs: -test -s tools+libs+libs.tests -pack -c $(_BuildConfig) /p:TestAssemblies=false /p:TestPackages=true + buildArgs: -test -s tools.illink+libs+libs.tests -pack -c $(_BuildConfig) /p:TestAssemblies=false /p:TestPackages=true nameSuffix: Libraries_WithPackages timeoutInMinutes: 150 condition: >- diff --git a/src/coreclr/debug/daccess/cdac.cpp b/src/coreclr/debug/daccess/cdac.cpp index cf129318ae9618..01e92782aeef4d 100644 --- a/src/coreclr/debug/daccess/cdac.cpp +++ b/src/coreclr/debug/daccess/cdac.cpp @@ -25,7 +25,15 @@ namespace iter++; path.Truncate(iter); path.Append(CDAC_LIB_NAME); + +#ifdef HOST_WINDOWS + // LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR tells the native windows loader to load dependencies + // from the same directory as cdacreader.dll. Once the native portions of the cDAC + // are statically linked, this won't be required. + *phCDAC = CLRLoadLibraryEx(path.GetUnicode(), NULL, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR); +#else // !HOST_WINDOWS *phCDAC = CLRLoadLibrary(path.GetUnicode()); +#endif // HOST_WINDOWS if (*phCDAC == NULL) return false; @@ -41,6 +49,26 @@ namespace return S_OK; } + + int ReadThreadContext(uint32_t threadId, uint32_t contextFlags, uint32_t contextBufferSize, uint8_t* contextBuffer, void* context) + { + ICorDebugDataTarget* target = reinterpret_cast(context); + HRESULT hr = target->GetThreadContext(threadId, contextFlags, contextBufferSize, contextBuffer); + if (FAILED(hr)) + return hr; + + return S_OK; + } + + int GetPlatform(uint32_t* platform, void* context) + { + ICorDebugDataTarget* target = reinterpret_cast(context); + HRESULT hr = target->GetPlatform((CorDebugPlatform*)platform); + if (FAILED(hr)) + return hr; + + return S_OK; + } } CDAC CDAC::Create(uint64_t descriptorAddr, ICorDebugDataTarget* target, IUnknown* legacyImpl) @@ -53,7 +81,7 @@ CDAC CDAC::Create(uint64_t descriptorAddr, ICorDebugDataTarget* target, IUnknown _ASSERTE(init != nullptr); intptr_t handle; - if (init(descriptorAddr, &ReadFromTargetCallback, target, &handle) != 0) + if (init(descriptorAddr, &ReadFromTargetCallback, &ReadThreadContext, &GetPlatform, target, &handle) != 0) { ::FreeLibrary(cdacLib); return {}; diff --git a/src/coreclr/debug/runtimeinfo/contracts.jsonc b/src/coreclr/debug/runtimeinfo/contracts.jsonc index f732119e507cad..8d0ecf9f679ff2 100644 --- a/src/coreclr/debug/runtimeinfo/contracts.jsonc +++ b/src/coreclr/debug/runtimeinfo/contracts.jsonc @@ -20,6 +20,7 @@ "PrecodeStubs": 1, "ReJIT": 1, "RuntimeTypeSystem": 1, - "Thread": 1, - "StressLog": 2 + "StackWalk": 1, + "StressLog": 2, + "Thread": 1 } diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index eaba5d699ba15a..be0889c9f48385 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -573,6 +573,10 @@ CDAC_TYPE_END(RangeSection) CDAC_TYPE_BEGIN(RealCodeHeader) CDAC_TYPE_INDETERMINATE(RealCodeHeader) CDAC_TYPE_FIELD(RealCodeHeader, /*pointer*/, MethodDesc, offsetof(RealCodeHeader, phdrMDesc)) +#ifdef FEATURE_EH_FUNCLETS +CDAC_TYPE_FIELD(RealCodeHeader, /*uint32*/, NumUnwindInfos, offsetof(RealCodeHeader, nUnwindInfos)) +CDAC_TYPE_FIELD(RealCodeHeader, /* T_RUNTIME_FUNCTION */, UnwindInfos, offsetof(RealCodeHeader, unwindInfos)) +#endif // FEATURE_EH_FUNCLETS CDAC_TYPE_END(RealCodeHeader) CDAC_TYPE_BEGIN(CodeHeapListNode) @@ -622,6 +626,26 @@ CDAC_TYPE_FIELD(GCCoverageInfo, /*pointer*/, SavedCode, offsetof(GCCoverageInfo, CDAC_TYPE_END(GCCoverageInfo) #endif // HAVE_GCCOVER +CDAC_TYPE_BEGIN(Frame) +CDAC_TYPE_INDETERMINATE(Frame) +CDAC_TYPE_FIELD(Frame, /*pointer*/, Next, cdac_data::Next) +CDAC_TYPE_END(Frame) + +CDAC_TYPE_BEGIN(InlinedCallFrame) +CDAC_TYPE_SIZE(sizeof(InlinedCallFrame)) +CDAC_TYPE_FIELD(InlinedCallFrame, /*pointer*/, CallSiteSP, offsetof(InlinedCallFrame, m_pCallSiteSP)) +CDAC_TYPE_FIELD(InlinedCallFrame, /*pointer*/, CallerReturnAddress, offsetof(InlinedCallFrame, m_pCallerReturnAddress)) +CDAC_TYPE_FIELD(InlinedCallFrame, /*pointer*/, CalleeSavedFP, offsetof(InlinedCallFrame, m_pCalleeSavedFP)) +CDAC_TYPE_END(InlinedCallFrame) + +#ifdef FEATURE_EH_FUNCLETS +CDAC_TYPE_BEGIN(SoftwareExceptionFrame) +CDAC_TYPE_SIZE(sizeof(SoftwareExceptionFrame)) +CDAC_TYPE_FIELD(SoftwareExceptionFrame, /*T_CONTEXT*/, TargetContext, cdac_data::TargetContext) +CDAC_TYPE_FIELD(SoftwareExceptionFrame, /*pointer*/, ReturnAddress, cdac_data::ReturnAddress) +CDAC_TYPE_END(SoftwareExceptionFrame) +#endif // FEATURE_EH_FUNCLETS + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() @@ -629,6 +653,14 @@ CDAC_GLOBAL_POINTER(AppDomain, &AppDomain::m_pTheAppDomain) CDAC_GLOBAL_POINTER(ThreadStore, &ThreadStore::s_pThreadStore) CDAC_GLOBAL_POINTER(FinalizerThread, &::g_pFinalizerThread) CDAC_GLOBAL_POINTER(GCThread, &::g_pSuspensionThread) + +// Add FrameIdentifier for all defined Frame types. Used to differentiate Frame objects. +#define FRAME_TYPE_NAME(frameType) \ + CDAC_GLOBAL_POINTER(frameType##Identifier, FrameIdentifier::frameType) + + #include "frames.h" +#undef FRAME_TYPE_NAME + CDAC_GLOBAL(MethodDescTokenRemainderBitCount, uint8, METHOD_TOKEN_REMAINDER_BIT_COUNT) #if FEATURE_EH_FUNCLETS CDAC_GLOBAL(FeatureEHFunclets, uint8, 1) diff --git a/src/coreclr/unwinder/CMakeLists.txt b/src/coreclr/unwinder/CMakeLists.txt index c63712c500e695..8d8ddc7c154edb 100644 --- a/src/coreclr/unwinder/CMakeLists.txt +++ b/src/coreclr/unwinder/CMakeLists.txt @@ -1,19 +1,18 @@ -include_directories(BEFORE ${VM_DIR}) -include_directories(BEFORE ${VM_DIR}/${ARCH_SOURCES_DIR}) -include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(BEFORE ${CLR_DIR}/unwinder) -include_directories(${CLR_DIR}/debug/ee) -include_directories(${CLR_DIR}/gc) -include_directories(${CLR_DIR}/gcdump) -include_directories(${CLR_DIR}/debug/daccess) +# helper to add set of include directories to unwinder targets +macro(add_unwinder_include_directories TARGET) + target_include_directories(${TARGET} BEFORE PRIVATE ${VM_DIR}) + target_include_directories(${TARGET} BEFORE PRIVATE ${VM_DIR}/${ARCH_SOURCES_DIR}) + target_include_directories(${TARGET} BEFORE PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_include_directories(${TARGET} BEFORE PRIVATE ${CLR_DIR}/unwinder) + target_include_directories(${TARGET} PRIVATE ${CLR_DIR}/debug/ee) + target_include_directories(${TARGET} PRIVATE ${CLR_DIR}/gc) + target_include_directories(${TARGET} PRIVATE ${CLR_DIR}/gcdump) + target_include_directories(${TARGET} PRIVATE ${CLR_DIR}/debug/daccess) + target_include_directories(${TARGET} PRIVATE ${ARCH_SOURCES_DIR}) +endmacro() set(UNWINDER_SOURCES baseunwinder.cpp -) - -# Include platform specific unwinder for applicable (native and cross-target) builds. -include_directories(${ARCH_SOURCES_DIR}) -list(APPEND UNWINDER_SOURCES ${ARCH_SOURCES_DIR}/unwinder.cpp ) @@ -21,11 +20,62 @@ convert_to_absolute_path(UNWINDER_SOURCES ${UNWINDER_SOURCES}) if(CLR_CMAKE_HOST_UNIX) add_library_clr(unwinder_wks OBJECT ${UNWINDER_SOURCES}) + add_unwinder_include_directories(unwinder_wks) add_dependencies(unwinder_wks eventing_headers) endif(CLR_CMAKE_HOST_UNIX) add_library_clr(unwinder_dac ${UNWINDER_SOURCES}) +add_unwinder_include_directories(unwinder_dac) add_dependencies(unwinder_dac eventing_headers) set_target_properties(unwinder_dac PROPERTIES DAC_COMPONENT TRUE) target_compile_definitions(unwinder_dac PRIVATE FEATURE_NO_HOST) +# Helper function for platform specific cDAC uwninder builds. +function(create_platform_unwinder) + set(oneValueArgs TARGET ARCH) + set(multiValueArgs DESTINATIONS) + cmake_parse_arguments(TARGETDETAILS "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(TARGETDETAILS_ARCH STREQUAL "x64") + set(ARCH_SOURCES_DIR amd64) + elseif((TARGETDETAILS_ARCH STREQUAL "arm") OR (TARGETDETAILS_ARCH STREQUAL "armel")) + set(ARCH_SOURCES_DIR arm) + elseif(TARGETDETAILS_ARCH STREQUAL "x86") + set(ARCH_SOURCES_DIR i386) + elseif(TARGETDETAILS_ARCH STREQUAL "arm64") + set(ARCH_SOURCES_DIR arm64) + else() + clr_unknown_arch() + endif() + + set(UNWINDER_SOURCES + baseunwinder.cpp + ${ARCH_SOURCES_DIR}/unwinder.cpp + ) + + convert_to_absolute_path(UNWINDER_SOURCES ${UNWINDER_SOURCES}) + + add_library_clr(${TARGETDETAILS_TARGET} + SHARED + ${UNWINDER_SOURCES} + ) + + add_unwinder_include_directories(${TARGETDETAILS_TARGET}) + + target_link_libraries(${TARGETDETAILS_TARGET} PRIVATE ${STATIC_MT_CRT_LIB} ${STATIC_MT_VCRT_LIB}) + + # add the install targets + install_clr(TARGETS ${TARGETDETAILS_TARGET} DESTINATIONS ${TARGETDETAILS_DESTINATIONS} COMPONENT debug INSTALL_ALL_ARTIFACTS) + + # Set the target to be built for the specified OS and ARCH + set_target_definitions_to_custom_os_and_arch(TARGET ${TARGETDETAILS_TARGET} OS win ARCH ${TARGETDETAILS_ARCH}) + + target_compile_definitions(${TARGETDETAILS_TARGET} PRIVATE FEATURE_NO_HOST FEATURE_CDAC_UNWINDER) +endfunction() + +# TODO: Support building cDAC unwinders on other platforms +# https://github.com/dotnet/runtime/issues/112272#issue-2838611496 +if(CLR_CMAKE_TARGET_WIN32 AND CLR_CMAKE_TARGET_ARCH_AMD64) + create_platform_unwinder(TARGET unwinder_cdac_amd64 ARCH x64 DESTINATIONS cdaclibs) + create_platform_unwinder(TARGET unwinder_cdac_arm64 ARCH arm64 DESTINATIONS cdaclibs) +endif(CLR_CMAKE_TARGET_WIN32 AND CLR_CMAKE_TARGET_ARCH_AMD64) diff --git a/src/coreclr/unwinder/amd64/unwinder.cpp b/src/coreclr/unwinder/amd64/unwinder.cpp index cf4b96ff0b6f8d..57acbd30ab06e8 100644 --- a/src/coreclr/unwinder/amd64/unwinder.cpp +++ b/src/coreclr/unwinder/amd64/unwinder.cpp @@ -8,6 +8,7 @@ typedef DPTR(M128A) PTR_M128A; +#ifndef FEATURE_CDAC_UNWINDER //--------------------------------------------------------------------------------------- // // Read 64 bit unsigned value from the specified address. When the unwinder is built @@ -51,12 +52,29 @@ static M128A MemoryRead128(PM128A addr) { return *dac_cast((TADDR)addr); } +#else +// Read 64 bit unsigned value from the specified addres when the unwinder is build +// for the cDAC. This triggers a callback to the cDAC host to read the memory from +// the target process. +static ULONG64 MemoryRead64(PULONG64 addr) +{ + ULONG64 value; + t_pCallbacks->readFromTarget((uint64_t)addr, &value, sizeof(value), t_pCallbacks->callbackContext); + return value; +} -#ifdef DACCESS_COMPILE - -// Report failure in the unwinder if the condition is FALSE -#define UNWINDER_ASSERT(Condition) if (!(Condition)) DacError(CORDBG_E_TARGET_INCONSISTENT) +// Read 128 bit value from the specified addres when the unwinder is build +// for the cDAC. This triggers a callback to the cDAC host to read the memory from +// the target process. +static M128A MemoryRead128(PM128A addr) +{ + M128A value; + t_pCallbacks->readFromTarget((uint64_t)addr, &value, sizeof(value), t_pCallbacks->callbackContext); + return value; +} +#endif // FEATURE_CDAC_UNWINDER +#if defined(DACCESS_COMPILE) || defined(FEATURE_CDAC_UNWINDER) //--------------------------------------------------------------------------------------- // // The InstructionBuffer class abstracts accessing assembler instructions in the function @@ -71,6 +89,19 @@ class InstructionBuffer UCHAR m_buffer[32]; // Load the instructions from the target process being debugged +#ifdef FEATURE_CDAC_UNWINDER + HRESULT Load() + { + HRESULT hr = t_pCallbacks->readFromTarget(m_address, m_buffer, sizeof(m_buffer), t_pCallbacks->callbackContext); + if (SUCCEEDED(hr)) + { + // TODO: Implement breakpoint patching for cDAC + // https://github.com/dotnet/runtime/issues/112273#issue-2838620747 + } + + return hr; + } +#else // FEATURE_CDAC_UNWINDER HRESULT Load() { HRESULT hr = DacReadAll(TO_TADDR(m_address), m_buffer, sizeof(m_buffer), false); @@ -85,6 +116,7 @@ class InstructionBuffer return hr; } +#endif // FEATURE_CDAC_UNWINDER public: @@ -129,7 +161,7 @@ class InstructionBuffer } // Get the byte at the given index from the current position - // Invoke DacError if the index is out of the buffer + // Assert that the index is within the buffer UCHAR operator[](int index) { int realIndex = m_offset + index; @@ -137,7 +169,9 @@ class InstructionBuffer return m_buffer[realIndex]; } }; +#endif // DACCESS_COMPILE || FEATURE_CDAC_UNWINDER +#ifdef DACCESS_COMPILE //--------------------------------------------------------------------------------------- // // Given the target address of an UNWIND_INFO structure, this function retrieves all the memory used for @@ -217,50 +251,57 @@ BOOL DacUnwindStackFrame(CONTEXT * pContext, KNONVOLATILE_CONTEXT_POINTERS* pCon return res; } -//--------------------------------------------------------------------------------------- -// -// Unwind the given CONTEXT to the caller CONTEXT. The given CONTEXT will be overwritten. -// -// Arguments: -// pContext - in-out parameter storing the specified CONTEXT on entry and the unwound CONTEXT on exit -// -// Return Value: -// TRUE if the unwinding is successful -// +#elif defined(FEATURE_CDAC_UNWINDER) -BOOL OOPStackUnwinderAMD64::Unwind(CONTEXT * pContext) +BOOL amd64Unwind(void* pContext, ReadFromTarget readFromTarget, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo, UnwinderFail unwinderFail, void* callbackContext) { - HRESULT hr = E_FAIL; + CDACCallbacks callbacks { readFromTarget, getAllocatedBuffer, getStackWalkInfo, unwinderFail, callbackContext }; + t_pCallbacks = &callbacks; + BOOL res = OOPStackUnwinderAMD64::Unwind((CONTEXT*) pContext); + t_pCallbacks = nullptr; - ULONG64 uControlPC = (DWORD64)dac_cast(::GetIP(pContext)); + return res; +} - // get the module base - ULONG64 uImageBase; - hr = GetModuleBase(uControlPC, &uImageBase); - if (FAILED(hr)) +UNWIND_INFO * OOPStackUnwinderAMD64::GetUnwindInfo(TADDR taUnwindInfo) +{ + UNWIND_INFO unwindInfo; + if(t_pCallbacks->readFromTarget((uint64_t)taUnwindInfo, &unwindInfo, sizeof(unwindInfo), t_pCallbacks->callbackContext) != S_OK) { - return FALSE; + return NULL; } - // get the function entry - IMAGE_RUNTIME_FUNCTION_ENTRY functionEntry; - hr = GetFunctionEntry(uControlPC, &functionEntry, sizeof(functionEntry)); - if (FAILED(hr)) + DWORD cbUnwindInfo = offsetof(UNWIND_INFO, UnwindCode) + + unwindInfo.CountOfUnwindCodes * sizeof(UNWIND_CODE); + + // Check if there is a chained unwind info. If so, it has an extra RUNTIME_FUNCTION tagged to the end. + if ((unwindInfo.Flags & UNW_FLAG_CHAININFO) != 0) { - return FALSE; + // If there is an odd number of UNWIND_CODE, we need to adjust for alignment. + if ((unwindInfo.CountOfUnwindCodes & 1) != 0) + { + cbUnwindInfo += sizeof(UNWIND_CODE); + } + cbUnwindInfo += sizeof(T_RUNTIME_FUNCTION); } - // call VirtualUnwind() to do the real work - ULONG64 EstablisherFrame; - hr = VirtualUnwind(0, uImageBase, uControlPC, &functionEntry, pContext, NULL, &EstablisherFrame, NULL, NULL); + // Allocate a buffer for the unwind info from cDAC callback. + // This buffer will be freed by the cDAC host once unwinding is done. + UNWIND_INFO* pUnwindInfo; + if(t_pCallbacks->getAllocatedBuffer(cbUnwindInfo, (void**)&pUnwindInfo, t_pCallbacks->callbackContext) != S_OK) + { + return NULL; + } - return (hr == S_OK); -} + if(t_pCallbacks->readFromTarget(taUnwindInfo, pUnwindInfo, cbUnwindInfo, t_pCallbacks->callbackContext) != S_OK) + { + return NULL; + } -#else // DACCESS_COMPILE + return pUnwindInfo; +} -// Report failure in the unwinder if the condition is FALSE -#define UNWINDER_ASSERT _ASSERTE +#else // !DACCESS_COMPILE && !FEATURE_CDAC_UNWINDER // For unwinding of the jitted code on non-Windows platforms, the Instruction buffer is // just a plain pointer to the instruction data. @@ -344,13 +385,57 @@ PEXCEPTION_ROUTINE RtlVirtualUnwind_Unsafe( ContextPointers, &handlerRoutine); - _ASSERTE(SUCCEEDED(res)); + UNWINDER_ASSERT(SUCCEEDED(res)); return handlerRoutine; } +#endif // !DACCESS_COMPILE && !FEATURE_CDAC_UNWINDER -#endif // DACCESS_COMPILE +//--------------------------------------------------------------------------------------- +// +// Unwind the given CONTEXT to the caller CONTEXT. The given CONTEXT will be overwritten. +// +// Arguments: +// pContext - in-out parameter storing the specified CONTEXT on entry and the unwound CONTEXT on exit +// +// Return Value: +// TRUE if the unwinding is successful +// + +BOOL OOPStackUnwinderAMD64::Unwind(CONTEXT * pContext) +{ + HRESULT hr = E_FAIL; + + ULONG64 uControlPC = +#ifndef FEATURE_CDAC_UNWINDER + (DWORD64)dac_cast(::GetIP(pContext)); +#else // FEATURE_CDAC_UNWINDER + pContext->Rip; +#endif // FEATURE_CDAC_UNWINDER + + // get the module base + ULONG64 uImageBase; + hr = GetModuleBase(uControlPC, &uImageBase); + if (FAILED(hr)) + { + return FALSE; + } + + // get the function entry + IMAGE_RUNTIME_FUNCTION_ENTRY functionEntry; + hr = GetFunctionEntry(uControlPC, &functionEntry, sizeof(functionEntry)); + if (FAILED(hr)) + { + return FALSE; + } + + // call VirtualUnwind() to do the real work + ULONG64 EstablisherFrame; + hr = VirtualUnwind(0, uImageBase, uControlPC, &functionEntry, pContext, NULL, &EstablisherFrame, NULL, NULL); + + return (hr == S_OK); +} // // diff --git a/src/coreclr/unwinder/amd64/unwinder.h b/src/coreclr/unwinder/amd64/unwinder.h index 1c714224f32eb5..f7bc18e5cebc2e 100644 --- a/src/coreclr/unwinder/amd64/unwinder.h +++ b/src/coreclr/unwinder/amd64/unwinder.h @@ -8,6 +8,14 @@ #include "baseunwinder.h" +#ifdef FEATURE_CDAC_UNWINDER +EXTERN_C __declspec(dllexport) BOOL amd64Unwind(void* pContext, + ReadFromTarget readFromTarget, + GetAllocatedBuffer getAllocatedBuffer, + GetStackWalkInfo getStackWalkInfo, + UnwinderFail unwinderFail, + void* callbackContext); +#endif // FEATURE_CDAC_UNWINDER //--------------------------------------------------------------------------------------- // diff --git a/src/coreclr/unwinder/arm64/unwinder.cpp b/src/coreclr/unwinder/arm64/unwinder.cpp index c7d04a70255fa1..ed4238c98a6bf6 100644 --- a/src/coreclr/unwinder/arm64/unwinder.cpp +++ b/src/coreclr/unwinder/arm64/unwinder.cpp @@ -4,7 +4,9 @@ // #include "stdafx.h" +#ifndef FEATURE_CDAC_UNWINDER #include "utilcode.h" +#endif // FEATURE_CDAC_UNWINDER #include "crosscomp.h" #include "unwinder.h" @@ -164,13 +166,25 @@ typedef struct _ARM64_VFP_STATE // Macros for accessing memory. These can be overridden if other code // (in particular the debugger) needs to use them. -#if !defined(DEBUGGER_UNWIND) +#if !defined(DEBUGGER_UNWIND) && !defined(FEATURE_CDAC_UNWINDER) #define MEMORY_READ_BYTE(params, addr) (*dac_cast(addr)) -#define MEMORY_READ_WORD(params, addr) (*dac_cast(addr)) +#define MEMORY_READ_WORD(params, addr) (*dac_cast(addr)) #define MEMORY_READ_DWORD(params, addr) (*dac_cast(addr)) #define MEMORY_READ_QWORD(params, addr) (*dac_cast(addr)) +#elif defined(FEATURE_CDAC_UNWINDER) +template +T cdacRead(uint64_t addr) +{ + T t; + t_pCallbacks->readFromTarget(addr, &t, sizeof(t), t_pCallbacks->callbackContext); + return t; +} +#define MEMORY_READ_BYTE(params, addr) (cdacRead(addr)) +#define MEMORY_READ_WORD(params, addr) (cdacRead(addr)) +#define MEMORY_READ_DWORD(params, addr) (cdacRead(addr)) +#define MEMORY_READ_QWORD(params, addr) (cdacRead(addr)) #endif // @@ -823,7 +837,7 @@ RtlpExpandCompactToFull ( // !sav_predec_done can't even happen. // - _ASSERTE(sav_predec_done); + UNWINDER_ASSERT(sav_predec_done); DBG_OP("save_lrpair\t(%s, %i)\n", int_reg_names[intreg], sav_slot * 8); emit_save_lrpair(&op_buffer, intreg, sav_slot * 8); @@ -1063,7 +1077,7 @@ Return Value: --*/ { - _ASSERTE(UnwindCode <= 0xFF); + UNWINDER_ASSERT(UnwindCode <= 0xFF); if (UnwindCode < 0xC0) { if (ARGUMENT_PRESENT(ScopeSize)) { @@ -1621,7 +1635,7 @@ Return Value: case 0xeb: // MSFT_OP_EC_CONTEXT: // NOTE: for .NET, the arm64ec context restoring is not implemented - _ASSERTE(FALSE); + UNWINDER_ASSERT(FALSE); return STATUS_UNSUCCESSFUL; case 0xec: // MSFT_OP_CLEAR_UNWOUND_TO_CALL @@ -2335,7 +2349,7 @@ Return Value: } // - // pac (11111100): function has pointer authentication + // pac (11111100): function has pointer authentication // else if (CurCode == 0xfc) { @@ -2574,7 +2588,7 @@ Return Value: UNREFERENCED_PARAMETER(HandlerType); - _ASSERTE((UnwindFlags & ~RTL_VIRTUAL_UNWIND_VALID_FLAGS_ARM64) == 0); + UNWINDER_ASSERT((UnwindFlags & ~RTL_VIRTUAL_UNWIND_VALID_FLAGS_ARM64) == 0); if (FunctionEntry == NULL) { @@ -2648,7 +2662,7 @@ Return Value: FunctionEntry = (PRUNTIME_FUNCTION)(ImageBase + FunctionEntry->UnwindData - 3); UnwindType = (FunctionEntry->UnwindData & 3); - _ASSERTE(UnwindType != 3); + UNWINDER_ASSERT(UnwindType != 3); ControlPcRva = FunctionEntry->BeginAddress; @@ -2759,6 +2773,7 @@ BOOL OOPStackUnwinderArm64::Unwind(T_CONTEXT * pContext) return TRUE; } +#ifdef DACCESS_COMPILE BOOL DacUnwindStackFrame(T_CONTEXT *pContext, T_KNONVOLATILE_CONTEXT_POINTERS* pContextPointers) { OOPStackUnwinderArm64 unwinder; @@ -2774,6 +2789,18 @@ BOOL DacUnwindStackFrame(T_CONTEXT *pContext, T_KNONVOLATILE_CONTEXT_POINTERS* p return res; } +#elif defined(FEATURE_CDAC_UNWINDER) +BOOL arm64Unwind(void* pContext, ReadFromTarget readFromTarget, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo, UnwinderFail unwinderFail, void* callbackContext) +{ + CDACCallbacks callbacks { readFromTarget, getAllocatedBuffer, getStackWalkInfo, unwinderFail, callbackContext }; + t_pCallbacks = &callbacks; + OOPStackUnwinderArm64 unwinder; + BOOL res = unwinder.Unwind((T_CONTEXT*) pContext); + t_pCallbacks = nullptr; + + return res; +} +#endif // FEATURE_CDAC_UNWINDER #if defined(HOST_UNIX) diff --git a/src/coreclr/unwinder/arm64/unwinder.h b/src/coreclr/unwinder/arm64/unwinder.h index aa03c5a59fe9f1..98f0a61b3137b1 100644 --- a/src/coreclr/unwinder/arm64/unwinder.h +++ b/src/coreclr/unwinder/arm64/unwinder.h @@ -8,6 +8,13 @@ #include "baseunwinder.h" +#ifdef FEATURE_CDAC_UNWINDER +EXTERN_C __declspec(dllexport) BOOL arm64Unwind(void* pContext, ReadFromTarget readFromTarget, + GetAllocatedBuffer getAllocatedBuffer, + GetStackWalkInfo getStackWalkInfo, + UnwinderFail unwinderFail, + void* callbackContext); +#endif // FEATURE_CDAC_UNWINDER //--------------------------------------------------------------------------------------- // diff --git a/src/coreclr/unwinder/baseunwinder.cpp b/src/coreclr/unwinder/baseunwinder.cpp index b00c2aa114835e..1af3255845acdf 100644 --- a/src/coreclr/unwinder/baseunwinder.cpp +++ b/src/coreclr/unwinder/baseunwinder.cpp @@ -6,9 +6,15 @@ #include "stdafx.h" #include "baseunwinder.h" +#ifndef FEATURE_CDAC_UNWINDER EXTERN_C void GetRuntimeStackWalkInfo(IN ULONG64 ControlPc, OUT UINT_PTR* pModuleBase, OUT UINT_PTR* pFuncEntry); +#endif // FEATURE_CDAC_UNWINDER + +#ifdef FEATURE_CDAC_UNWINDER +thread_local CDACCallbacks* t_pCallbacks; +#endif // FEATURE_CDAC_UNWINDER //--------------------------------------------------------------------------------------- // @@ -27,7 +33,11 @@ EXTERN_C void GetRuntimeStackWalkInfo(IN ULONG64 ControlPc, HRESULT OOPStackUnwinder::GetModuleBase( DWORD64 address, _Out_ PDWORD64 pdwBase) { +#ifndef FEATURE_CDAC_UNWINDER GetRuntimeStackWalkInfo(address, reinterpret_cast(pdwBase), NULL); +#else // FEATURE_CDAC_UNWINDER + t_pCallbacks->getStackWalkInfo(address, reinterpret_cast(pdwBase), NULL, t_pCallbacks->callbackContext); +#endif // FEATURE_CDAC_UNWINDER return ((*pdwBase == 0) ? E_FAIL : S_OK); } @@ -56,6 +66,7 @@ HRESULT OOPStackUnwinder::GetFunctionEntry( DWORD64 addres } PVOID pFuncEntry = NULL; +#ifndef FEATURE_CDAC_UNWINDER GetRuntimeStackWalkInfo(address, NULL, reinterpret_cast(&pFuncEntry)); if (pFuncEntry == NULL) { @@ -64,4 +75,17 @@ HRESULT OOPStackUnwinder::GetFunctionEntry( DWORD64 addres memcpy(pBuffer, pFuncEntry, cbBuffer); return S_OK; +#else // FEATURE_CDAC_UNWINDER + t_pCallbacks->getStackWalkInfo(address, NULL, reinterpret_cast(&pFuncEntry), t_pCallbacks->callbackContext); + if (pFuncEntry == NULL) + { + return E_FAIL; + } + if (t_pCallbacks->readFromTarget((DWORD64)pFuncEntry, pBuffer, cbBuffer, t_pCallbacks->callbackContext) != S_OK) + { + return E_FAIL; + } + + return S_OK; +#endif } diff --git a/src/coreclr/unwinder/baseunwinder.h b/src/coreclr/unwinder/baseunwinder.h index 241dc8a7ddfb30..f620bae944744b 100644 --- a/src/coreclr/unwinder/baseunwinder.h +++ b/src/coreclr/unwinder/baseunwinder.h @@ -6,6 +6,46 @@ #ifndef __unwinder_h__ #define __unwinder_h__ +#ifdef FEATURE_CDAC_UNWINDER +using ReadFromTarget = LONG (*)(ULONG64 addr, PVOID pBuffer, LONG bufferSize, PVOID callbackContext); +using GetAllocatedBuffer = LONG (*)(LONG bufferSize, PVOID* ppBuffer, PVOID callbackContext); +using GetStackWalkInfo = VOID (*)(ULONG64 controlPC, UINT_PTR* pUnwindInfoBase, UINT_PTR* pFuncEntry, PVOID callbackContext); +using UnwinderFail = VOID (*)(); + +class CDACCallbacks +{ +public: + CDACCallbacks(ReadFromTarget readFromTarget, + GetAllocatedBuffer getAllocatedBuffer, + GetStackWalkInfo getStackWalkInfo, + UnwinderFail unwinderFail, + void* callbackContext) + : readFromTarget(readFromTarget), + getAllocatedBuffer(getAllocatedBuffer), + getStackWalkInfo(getStackWalkInfo), + unwinderFail(unwinderFail), + callbackContext(callbackContext) + { } + + ReadFromTarget readFromTarget; + GetAllocatedBuffer getAllocatedBuffer; + GetStackWalkInfo getStackWalkInfo; + UnwinderFail unwinderFail; + void* callbackContext; +}; + +// thread_local used to access cDAC callbacks outside of unwinder. +extern thread_local CDACCallbacks* t_pCallbacks; +#endif // FEATURE_CDAC_UNWINDER + +// Report failure in the unwinder if the condition is FALSE +#if defined(FEATURE_CDAC_UNWINDER) +#define UNWINDER_ASSERT(Condition) if (!(Condition)) t_pCallbacks->unwinderFail() +#elif defined(DACCESS_COMPILE) +#define UNWINDER_ASSERT(Condition) if (!(Condition)) DacError(CORDBG_E_TARGET_INCONSISTENT) +#else // !DACCESS_COMPILE AND !FEATURE_CDAC_UNWINDER +#define UNWINDER_ASSERT _ASSERTE +#endif //--------------------------------------------------------------------------------------- // @@ -14,7 +54,7 @@ // are actually borrowed from dbghelp.dll. (StackWalk64() is built on top of these classes.) We have ripped // out everything we don't need such as symbol lookup and various state, and keep just enough code to support // VirtualUnwind(). The managed debugging infrastructure can't call RtlVirtualUnwind() because it doesn't -// work from out-of-processr +// work from out-of-processor // // Notes: // To see what we have changed in the borrowed source, you can diff the original version and our version. diff --git a/src/coreclr/unwinder/stdafx.h b/src/coreclr/unwinder/stdafx.h index 8decdc68562bd4..e0dc4fe44b3813 100644 --- a/src/coreclr/unwinder/stdafx.h +++ b/src/coreclr/unwinder/stdafx.h @@ -10,10 +10,17 @@ #define USE_COM_CONTEXT_DEF +#ifndef FEATURE_CDAC_UNWINDER #include - #include #include +#else // FEATURE_CDAC_UNWINDER +#include +#include +#include +#include +#endif // FEATURE_CDAC_UNWINDER + #ifdef DACCESS_COMPILE #include #include diff --git a/src/coreclr/vm/frames.h b/src/coreclr/vm/frames.h index bc5a203ce14c63..be519af3b9b239 100644 --- a/src/coreclr/vm/frames.h +++ b/src/coreclr/vm/frames.h @@ -226,7 +226,7 @@ enum class FrameIdentifier : TADDR { None = 0, #define FRAME_TYPE_NAME(frameType) frameType, -#include "FrameTypes.h" +#include "FrameTypes.h" CountPlusOne }; @@ -628,8 +628,15 @@ class Frame void PopIfChained(); #endif // TARGET_UNIX && !DACCESS_COMPILE + + friend struct ::cdac_data; }; +template<> +struct cdac_data +{ + static constexpr size_t Next = offsetof(Frame, m_Next); +}; //----------------------------------------------------------------------------- // This frame provides a context for a code location at which @@ -1062,8 +1069,15 @@ class SoftwareExceptionFrame : public Frame } void UpdateRegDisplay_Impl(const PREGDISPLAY, bool updateFloats = false); -}; + friend struct ::cdac_data; +}; +template<> +struct cdac_data +{ + static constexpr size_t TargetContext = offsetof(SoftwareExceptionFrame, m_Context); + static constexpr size_t ReturnAddress = offsetof(SoftwareExceptionFrame, m_ReturnAddress); +}; #endif // FEATURE_EH_FUNCLETS //----------------------------------------------------------------------- diff --git a/src/libraries/externals.csproj b/src/libraries/externals.csproj index 480419db34f86f..402a312ab4cec8 100644 --- a/src/libraries/externals.csproj +++ b/src/libraries/externals.csproj @@ -92,6 +92,8 @@ TODO: [cdac] Remove once cdacreader is added to shipping shared framework --> + + diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs index 0e8a580bcb4c24..42e0d38c545249 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -59,4 +59,8 @@ public abstract class ContractRegistry /// Gets an instance of the ReJIT contract for the target. /// public abstract IReJIT ReJIT { get; } + /// + /// Gets an instance of the StackWalk contract for the target. + /// + public abstract IStackWalk StackWalk { get; } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs index ecb174e222f374..a977c79cab0c3a 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs @@ -18,6 +18,8 @@ public interface IExecutionManager : IContract CodeBlockHandle? GetCodeBlockHandle(TargetCodePointer ip) => throw new NotImplementedException(); TargetPointer GetMethodDesc(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); TargetCodePointer GetStartAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); + TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle, TargetCodePointer ip) => throw new NotImplementedException(); + TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); } public readonly struct ExecutionManager : IExecutionManager diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs new file mode 100644 index 00000000000000..4fed8a815f9fe5 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +public interface IStackDataFrameHandle { }; + +public interface IStackWalk : IContract +{ + static string IContract.Name => nameof(StackWalk); + + public virtual IEnumerable CreateStackWalk(ThreadData threadData) => throw new NotImplementedException(); + public virtual byte[] GetRawContext(IStackDataFrameHandle stackDataFrameHandle) => throw new NotImplementedException(); + public virtual TargetPointer GetFrameAddress(IStackDataFrameHandle stackDataFrameHandle) => throw new NotImplementedException(); +} + +public struct StackWalk : IStackWalk +{ + // Everything throws NotImplementedException +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs index 09a8a5996843aa..b1cb058483ca01 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -89,4 +89,8 @@ public enum DataType MethodImpl, NativeCodeSlot, GCCoverageInfo, + + Frame, + InlinedCallFrame, + SoftwareExceptionFrame, } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs index b46d6ca92cd088..266e0b2b0ba7f1 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs @@ -18,6 +18,27 @@ namespace Microsoft.Diagnostics.DataContractReader; /// public abstract class Target { + /// + /// CorDebugPlatform represents the platform of the target. + /// + public enum CorDebugPlatform : int + { + CORDB_PLATFORM_WINDOWS_X86 = 0, + CORDB_PLATFORM_WINDOWS_AMD64 = 1, + CORDB_PLATFORM_WINDOWS_IA64 = 2, + CORDB_PLATFORM_MAC_PPC = 3, + CORDB_PLATFORM_MAC_X86 = 4, + CORDB_PLATFORM_WINDOWS_ARM = 5, + CORDB_PLATFORM_MAC_AMD64 = 6, + CORDB_PLATFORM_WINDOWS_ARM64 = 7, + CORDB_PLATFORM_POSIX_AMD64 = 8, + CORDB_PLATFORM_POSIX_X86 = 9, + CORDB_PLATFORM_POSIX_ARM = 10, + CORDB_PLATFORM_POSIX_ARM64 = 11, + CORDB_PLATFORM_POSIX_LOONGARCH64 = 12, + CORDB_PLATFORM_POSIX_RISCV64 = 13, + } + /// /// Pointer size of the target /// @@ -27,6 +48,20 @@ public abstract class Target /// public abstract bool IsLittleEndian { get; } + /// + /// Platform of the target + /// + public abstract CorDebugPlatform Platform { get; } + + /// + /// Fills a buffer with the context of the given thread + /// + /// The identifier of the thread whose context is to be retrieved. The identifier is defined by the operating system. + /// A bitwise combination of platform-dependent flags that indicate which portions of the context should be read. + /// Buffer to be filled with thread context. + /// true if successful, false otherwise + public abstract bool TryGetThreadContext(ulong threadId, uint contextFlags, Span buffer); + /// /// Reads a well-known global pointer value from the target process /// diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs index 467fff50928167..42982babbd0dc6 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs @@ -13,9 +13,11 @@ internal partial class ExecutionManagerCore : IExecutionManager private sealed class EEJitManager : JitManager { private readonly INibbleMap _nibbleMap; + private readonly RuntimeFunctionLookup _runtimeFunctions; public EEJitManager(Target target, INibbleMap nibbleMap) : base(target) { _nibbleMap = nibbleMap; + _runtimeFunctions = RuntimeFunctionLookup.Create(target); } public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info) @@ -28,25 +30,61 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer if (rangeSection.Data == null) throw new ArgumentException(nameof(rangeSection)); - TargetPointer start = FindMethodCode(rangeSection, jittedCodeAddress); - if (start == TargetPointer.Null) + TargetPointer codeStart = FindMethodCode(rangeSection, jittedCodeAddress); + if (codeStart == TargetPointer.Null) return false; - Debug.Assert(start.Value <= jittedCodeAddress.Value); - TargetNUInt relativeOffset = new TargetNUInt(jittedCodeAddress.Value - start.Value); - // See EEJitManager::GetCodeHeaderFromStartAddress in vm/codeman.h - int codeHeaderOffset = Target.PointerSize; - TargetPointer codeHeaderIndirect = new TargetPointer(start - (ulong)codeHeaderOffset); - if (RangeSection.IsStubCodeBlock(Target, codeHeaderIndirect)) - { + Debug.Assert(codeStart.Value <= jittedCodeAddress.Value); + TargetNUInt relativeOffset = new TargetNUInt(jittedCodeAddress.Value - codeStart.Value); + + if (!GetRealCodeHeader(rangeSection, codeStart, out Data.RealCodeHeader? realCodeHeader)) return false; - } - TargetPointer codeHeaderAddress = Target.ReadPointer(codeHeaderIndirect); - Data.RealCodeHeader realCodeHeader = Target.ProcessedData.GetOrAdd(codeHeaderAddress); - info = new CodeBlock(start.Value, realCodeHeader.MethodDesc, relativeOffset, rangeSection.Data!.JitManager); + + info = new CodeBlock(codeStart.Value, realCodeHeader.MethodDesc, relativeOffset, rangeSection.Data!.JitManager); return true; } + public override TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress) + { + // TODO: This only works with funclets enabled. See runtime definition of RealCodeHeader for more info. + if (rangeSection.IsRangeList) + return TargetPointer.Null; + if (rangeSection.Data == null) + throw new ArgumentException(nameof(rangeSection)); + + TargetPointer codeStart = FindMethodCode(rangeSection, jittedCodeAddress); + if (codeStart == TargetPointer.Null) + return TargetPointer.Null; + Debug.Assert(codeStart.Value <= jittedCodeAddress.Value); + + if (!GetRealCodeHeader(rangeSection, codeStart, out Data.RealCodeHeader? realCodeHeader)) + return TargetPointer.Null; + + if (realCodeHeader.NumUnwindInfos is not uint numUnwindInfos) + { + throw new InvalidOperationException("Unable to get NumUnwindInfos"); + } + if (realCodeHeader.UnwindInfos is not TargetPointer unwindInfos) + { + throw new InvalidOperationException("Unable to get NumUnwindInfos"); + } + + if (numUnwindInfos == 0) + { + return TargetPointer.Null; + } + + // Find the relative address that we are looking for + TargetPointer addr = CodePointerUtils.AddressFromCodePointer(jittedCodeAddress, Target); + TargetPointer imageBase = rangeSection.Data.RangeBegin; + TargetPointer relativeAddr = addr - imageBase; + + if (!_runtimeFunctions.TryGetRuntimeFunctionIndexForAddress(unwindInfos, numUnwindInfos, relativeAddr, out uint index)) + return TargetPointer.Null; + + return _runtimeFunctions.GetRuntimeFunctionAddress(unwindInfos, index); + } + private TargetPointer FindMethodCode(RangeSection rangeSection, TargetCodePointer jittedCodeAddress) { // EEJitManager::FindMethodCode @@ -59,5 +97,30 @@ private TargetPointer FindMethodCode(RangeSection rangeSection, TargetCodePointe Data.CodeHeapListNode heapListNode = Target.ProcessedData.GetOrAdd(heapListAddress); return _nibbleMap.FindMethodCode(heapListNode, jittedCodeAddress); } + + private bool GetRealCodeHeader(RangeSection rangeSection, TargetPointer codeStart, [NotNullWhen(true)] out Data.RealCodeHeader? realCodeHeader) + { + realCodeHeader = null; + // EEJitManager::JitCodeToMethodInfo + if (rangeSection.IsRangeList) + return false; + + if (rangeSection.Data == null) + throw new ArgumentException(nameof(rangeSection)); + + if (codeStart == TargetPointer.Null) + return false; + + // See EEJitManager::GetCodeHeaderFromStartAddress in vm/codeman.h + int codeHeaderOffset = Target.PointerSize; + TargetPointer codeHeaderIndirect = new TargetPointer(codeStart - (ulong)codeHeaderOffset); + if (RangeSection.IsStubCodeBlock(Target, codeHeaderIndirect)) + { + return false; + } + TargetPointer codeHeaderAddress = Target.ReadPointer(codeHeaderIndirect); + realCodeHeader = Target.ProcessedData.GetOrAdd(codeHeaderAddress); + return true; + } } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs index 386ad015d91093..7b35e56f542d15 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs @@ -94,6 +94,34 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer return true; } + public override TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress) + { + // ReadyToRunJitManager::JitCodeToMethodInfo + if (rangeSection.Data == null) + throw new ArgumentException(nameof(rangeSection)); + + Debug.Assert(rangeSection.Data.R2RModule != TargetPointer.Null); + + Data.Module r2rModule = Target.ProcessedData.GetOrAdd(rangeSection.Data.R2RModule); + Debug.Assert(r2rModule.ReadyToRunInfo != TargetPointer.Null); + Data.ReadyToRunInfo r2rInfo = Target.ProcessedData.GetOrAdd(r2rModule.ReadyToRunInfo); + + // Check if address is in a thunk + if (IsStubCodeBlockThunk(rangeSection.Data, r2rInfo, jittedCodeAddress)) + return TargetPointer.Null; + + // Find the relative address that we are looking for + TargetPointer addr = CodePointerUtils.AddressFromCodePointer(jittedCodeAddress, Target); + TargetPointer imageBase = rangeSection.Data.RangeBegin; + TargetPointer relativeAddr = addr - imageBase; + + uint index; + if (!_runtimeFunctions.TryGetRuntimeFunctionIndexForAddress(r2rInfo.RuntimeFunctions, r2rInfo.NumRuntimeFunctions, relativeAddr, out index)) + return TargetPointer.Null; + + return _runtimeFunctions.GetRuntimeFunctionAddress(r2rInfo.RuntimeFunctions, index); + } + private bool IsStubCodeBlockThunk(Data.RangeSection rangeSection, Data.ReadyToRunInfo r2rInfo, TargetCodePointer jittedCodeAddress) { if (r2rInfo.DelayLoadMethodCallThunks == TargetPointer.Null) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs index 25c52223fd5931..d8e1e501641711 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs @@ -65,6 +65,7 @@ protected JitManager(Target target) } public abstract bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info); + public abstract TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress); } private sealed class RangeSection @@ -178,4 +179,30 @@ TargetCodePointer IExecutionManager.GetStartAddress(CodeBlockHandle codeInfoHand return info.StartAddress; } + + TargetPointer IExecutionManager.GetUnwindInfo(CodeBlockHandle codeInfoHandle, TargetCodePointer ip) + { + if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out CodeBlock? info)) + throw new InvalidOperationException($"{nameof(CodeBlock)} not found for {codeInfoHandle.Address}"); + + RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, ip); + if (range.Data == null) + return TargetPointer.Null; + + JitManager jitManager = GetJitManager(range.Data); + + return jitManager.GetUnwindInfo(range, ip); + } + + TargetPointer IExecutionManager.GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle) + { + if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out CodeBlock? info)) + throw new InvalidOperationException($"{nameof(CodeBlock)} not found for {codeInfoHandle.Address}"); + + RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, new TargetCodePointer(codeInfoHandle.Address)); + if (range.Data == null) + throw new InvalidOperationException($"{nameof(RangeSection)} not found for {codeInfoHandle.Address}"); + + return range.Data.RangeBegin; + } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs index ce9f6ce9ce5972..670b75dbe27b13 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs @@ -17,4 +17,6 @@ internal ExecutionManager_1(Target target, Data.RangeSectionMap topRangeSectionM public CodeBlockHandle? GetCodeBlockHandle(TargetCodePointer ip) => _executionManagerCore.GetCodeBlockHandle(ip); public TargetPointer GetMethodDesc(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetMethodDesc(codeInfoHandle); public TargetCodePointer GetStartAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetStartAddress(codeInfoHandle); + public TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle, TargetCodePointer ip) => _executionManagerCore.GetUnwindInfo(codeInfoHandle, ip); + public TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetUnwindInfoBaseAddress(codeInfoHandle); } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs index a935ca00f0a32b..7378685ce67396 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs @@ -17,4 +17,6 @@ internal ExecutionManager_2(Target target, Data.RangeSectionMap topRangeSectionM public CodeBlockHandle? GetCodeBlockHandle(TargetCodePointer ip) => _executionManagerCore.GetCodeBlockHandle(ip); public TargetPointer GetMethodDesc(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetMethodDesc(codeInfoHandle); public TargetCodePointer GetStartAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetStartAddress(codeInfoHandle); + public TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle, TargetCodePointer ip) => _executionManagerCore.GetUnwindInfo(codeInfoHandle, ip); + public TargetPointer GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetUnwindInfoBaseAddress(codeInfoHandle); } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs index e8dbbf6d220909..597cc6db205124 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs @@ -54,24 +54,27 @@ bool Compare(uint index) bool Match(uint index) { - // Entries are terminated by a sentinel value of -1, so we can index one past the end safely. - // Read as a runtime function, its begin address is 0xffffffff (always > relative address). - // See RuntimeFunctionsTableNode.GetData in RuntimeFunctionsTableNode.cs - Data.RuntimeFunction nextFunc = GetRuntimeFunction(runtimeFunctions, index + 1); - if (relativeAddress >= nextFunc.BeginAddress) - return false; + // If there is a next Unwind Info, check if the address is in the next Unwind Info. + if (index < numRuntimeFunctions - 1) + { + Data.RuntimeFunction nextFunc = GetRuntimeFunction(runtimeFunctions, index + 1); + if (relativeAddress >= nextFunc.BeginAddress) + return false; + } Data.RuntimeFunction func = GetRuntimeFunction(runtimeFunctions, index); - if (relativeAddress >= func.BeginAddress) - return true; - - return false; + return relativeAddress >= func.BeginAddress; } } + public TargetPointer GetRuntimeFunctionAddress(TargetPointer runtimeFunctions, uint index) + { + return runtimeFunctions + (index * _runtimeFunctionSize); + } + public Data.RuntimeFunction GetRuntimeFunction(TargetPointer runtimeFunctions, uint index) { - TargetPointer addr = runtimeFunctions + (ulong)(index * _runtimeFunctionSize); + TargetPointer addr = GetRuntimeFunctionAddress(runtimeFunctions, index); return _target.ProcessedData.GetOrAdd(addr); } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64Context.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64Context.cs new file mode 100644 index 00000000000000..a56fc168d9b73b --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64Context.cs @@ -0,0 +1,241 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +/// +/// AMD64-specific thread context. +/// +[StructLayout(LayoutKind.Explicit, Pack = 1)] +internal struct AMD64Context : IPlatformContext +{ + [Flags] + public enum ContextFlagsValues : uint + { + CONTEXT_AMD = 0x00100000, + CONTEXT_CONTROL = CONTEXT_AMD | 0x1, + CONTEXT_INTEGER = CONTEXT_AMD | 0x2, + CONTEXT_SEGMENTS = CONTEXT_AMD | 0x4, + CONTEXT_FLOATING_POINT = CONTEXT_AMD | 0x8, + CONTEXT_DEBUG_REGISTERS = CONTEXT_AMD | 0x10, + CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT, + CONTEXT_ALL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS, + CONTEXT_XSTATE = CONTEXT_AMD | 0x40, + CONTEXT_KERNEL_CET = CONTEXT_AMD | 0x80, + } + + public readonly uint Size => 0x4d0; + public readonly uint DefaultContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; + + public TargetPointer StackPointer + { + readonly get => new(Rsp); + set => Rsp = value.Value; + } + public TargetPointer InstructionPointer + { + readonly get => new(Rip); + set => Rip = value.Value; + } + public TargetPointer FramePointer + { + readonly get => new(Rbp); + set => Rbp = value.Value; + } + + public void Unwind(Target target) + { + Unwinder.AMD64Unwind(ref this, target); + } + + [FieldOffset(0x0)] + public ulong P1Home; + + [FieldOffset(0x8)] + public ulong P2Home; + + [FieldOffset(0x10)] + public ulong P3Home; + + [FieldOffset(0x18)] + public ulong P4Home; + + [FieldOffset(0x20)] + public ulong P5Home; + + [FieldOffset(0x28)] + public ulong P6Home; + + [FieldOffset(0x30)] + public uint ContextFlags; + + [FieldOffset(0x34)] + public uint MxCsr; + + #region Segment registers + + [Register(RegisterType.Segments)] + [FieldOffset(0x38)] + public ushort Cs; + + [Register(RegisterType.Segments)] + [FieldOffset(0x3a)] + public ushort Ds; + + [Register(RegisterType.Segments)] + [FieldOffset(0x3c)] + public ushort Es; + + [Register(RegisterType.Segments)] + [FieldOffset(0x3e)] + public ushort Fs; + + [Register(RegisterType.Segments)] + [FieldOffset(0x40)] + public ushort Gs; + + [Register(RegisterType.Segments)] + [FieldOffset(0x42)] + public ushort Ss; + + #endregion + + [Register(RegisterType.General)] + [FieldOffset(0x44)] + public int EFlags; + + #region Debug registers + + [Register(RegisterType.Debug)] + [FieldOffset(0x48)] + public ulong Dr0; + + [Register(RegisterType.Debug)] + [FieldOffset(0x50)] + public ulong Dr1; + + [Register(RegisterType.Debug)] + [FieldOffset(0x58)] + public ulong Dr2; + + [Register(RegisterType.Debug)] + [FieldOffset(0x60)] + public ulong Dr3; + + [Register(RegisterType.Debug)] + [FieldOffset(0x68)] + public ulong Dr6; + + [Register(RegisterType.Debug)] + [FieldOffset(0x70)] + public ulong Dr7; + + #endregion + + #region General and control registers + + [Register(RegisterType.General)] + [FieldOffset(0x78)] + public ulong Rax; + + [Register(RegisterType.General)] + [FieldOffset(0x80)] + public ulong Rcx; + + [Register(RegisterType.General)] + [FieldOffset(0x88)] + public ulong Rdx; + + [Register(RegisterType.General)] + [FieldOffset(0x90)] + public ulong Rbx; + + [Register(RegisterType.Control | RegisterType.StackPointer)] + [FieldOffset(0x98)] + public ulong Rsp; + + [Register(RegisterType.Control | RegisterType.FramePointer)] + [FieldOffset(0xa0)] + public ulong Rbp; + + [Register(RegisterType.General)] + [FieldOffset(0xa8)] + public ulong Rsi; + + [Register(RegisterType.General)] + [FieldOffset(0xb0)] + public ulong Rdi; + + [Register(RegisterType.General)] + [FieldOffset(0xb8)] + public ulong R8; + + [Register(RegisterType.General)] + [FieldOffset(0xc0)] + public ulong R9; + + [Register(RegisterType.General)] + [FieldOffset(0xc8)] + public ulong R10; + + [Register(RegisterType.General)] + [FieldOffset(0xd0)] + public ulong R11; + + [Register(RegisterType.General)] + [FieldOffset(0xd8)] + public ulong R12; + + [Register(RegisterType.General)] + [FieldOffset(0xe0)] + public ulong R13; + + [Register(RegisterType.General)] + [FieldOffset(0xe8)] + public ulong R14; + + [Register(RegisterType.General)] + [FieldOffset(0xf0)] + public ulong R15; + + [Register(RegisterType.Control | RegisterType.ProgramCounter)] + [FieldOffset(0xf8)] + public ulong Rip; + + #endregion + + #region Floating point registers + + // [Register(RegisterType.FloatPoint)] + // [FieldOffset(0x100)] + // public XmmSaveArea FltSave; + + // [Register(RegisterType.FloatPoint)] + // [FieldOffset(0x300)] + // public VectorRegisterArea VectorRegisters; + + #endregion + + [Register(RegisterType.Debug)] + [FieldOffset(0x4a8)] + public ulong DebugControl; + + [Register(RegisterType.Debug)] + [FieldOffset(0x4b0)] + public ulong LastBranchToRip; + + [Register(RegisterType.Debug)] + [FieldOffset(0x4b8)] + public ulong LastBranchFromRip; + + [Register(RegisterType.Debug)] + [FieldOffset(0x4c0)] + public ulong LastExceptionToRip; + + [Register(RegisterType.Debug)] + [FieldOffset(0x4c8)] + public ulong LastExceptionFromRip; +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs new file mode 100644 index 00000000000000..6be29853aa6f47 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs @@ -0,0 +1,242 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +/// +/// ARM64-specific thread context. +/// +[StructLayout(LayoutKind.Explicit, Pack = 1)] +internal struct ARM64Context : IPlatformContext +{ + [Flags] + public enum ContextFlagsValues : uint + { + CONTEXT_ARM64 = 0x00400000, + CONTEXT_CONTROL = CONTEXT_ARM64 | 0x1, + CONTEXT_INTEGER = CONTEXT_ARM64 | 0x2, + CONTEXT_FLOATING_POINT = CONTEXT_ARM64 | 0x4, + CONTEXT_DEBUG_REGISTERS = CONTEXT_ARM64 | 0x8, + CONTEXT_X18 = CONTEXT_ARM64 | 0x10, + CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT, + CONTEXT_ALL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS | CONTEXT_X18, + } + + public readonly uint Size => 0x390; + + public readonly uint DefaultContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; + + public TargetPointer StackPointer + { + readonly get => new(Sp); + set => Sp = value.Value; + } + public TargetPointer InstructionPointer + { + readonly get => new(Pc); + set => Pc = value.Value; + } + public TargetPointer FramePointer + { + readonly get => new(Fp); + set => Fp = value.Value; + } + + public void Unwind(Target target) + { + Unwinder.ARM64Unwind(ref this, target); + } + + // Control flags + + [FieldOffset(0x0)] + public uint ContextFlags; + + #region General registers + + [Register(RegisterType.General)] + [FieldOffset(0x4)] + public uint Cpsr; + + [Register(RegisterType.General)] + [FieldOffset(0x8)] + public ulong X0; + + [Register(RegisterType.General)] + [FieldOffset(0x10)] + public ulong X1; + + [Register(RegisterType.General)] + [FieldOffset(0x18)] + public ulong X2; + + [Register(RegisterType.General)] + [FieldOffset(0x20)] + public ulong X3; + + [Register(RegisterType.General)] + [FieldOffset(0x28)] + public ulong X4; + + [Register(RegisterType.General)] + [FieldOffset(0x30)] + public ulong X5; + + [Register(RegisterType.General)] + [FieldOffset(0x38)] + public ulong X6; + + [Register(RegisterType.General)] + [FieldOffset(0x40)] + public ulong X7; + + [Register(RegisterType.General)] + [FieldOffset(0x48)] + public ulong X8; + + [Register(RegisterType.General)] + [FieldOffset(0x50)] + public ulong X9; + + [Register(RegisterType.General)] + [FieldOffset(0x58)] + public ulong X10; + + [Register(RegisterType.General)] + [FieldOffset(0x60)] + public ulong X11; + + [Register(RegisterType.General)] + [FieldOffset(0x68)] + public ulong X12; + + [Register(RegisterType.General)] + [FieldOffset(0x70)] + public ulong X13; + + [Register(RegisterType.General)] + [FieldOffset(0x78)] + public ulong X14; + + [Register(RegisterType.General)] + [FieldOffset(0x80)] + public ulong X15; + + [Register(RegisterType.General)] + [FieldOffset(0x88)] + public ulong X16; + + [Register(RegisterType.General)] + [FieldOffset(0x90)] + public ulong X17; + + [Register(RegisterType.General)] + [FieldOffset(0x98)] + public ulong X18; + + [Register(RegisterType.General)] + [FieldOffset(0xa0)] + public ulong X19; + + [Register(RegisterType.General)] + [FieldOffset(0xa8)] + public ulong X20; + + [Register(RegisterType.General)] + [FieldOffset(0xb0)] + public ulong X21; + + [Register(RegisterType.General)] + [FieldOffset(0xb8)] + public ulong X22; + + [Register(RegisterType.General)] + [FieldOffset(0xc0)] + public ulong X23; + + [Register(RegisterType.General)] + [FieldOffset(0xc8)] + public ulong X24; + + [Register(RegisterType.General)] + [FieldOffset(0xd0)] + public ulong X25; + + [Register(RegisterType.General)] + [FieldOffset(0xd8)] + public ulong X26; + + [Register(RegisterType.General)] + [FieldOffset(0xe0)] + public ulong X27; + + [Register(RegisterType.General)] + [FieldOffset(0xe8)] + public ulong X28; + + #endregion + + #region Control Registers + + [Register(RegisterType.Control | RegisterType.FramePointer)] + [FieldOffset(0xf0)] + public ulong Fp; + + [Register(RegisterType.Control)] + [FieldOffset(0xf8)] + public ulong Lr; + + [Register(RegisterType.Control | RegisterType.StackPointer)] + [FieldOffset(0x100)] + public ulong Sp; + + [Register(RegisterType.Control | RegisterType.ProgramCounter)] + [FieldOffset(0x108)] + public ulong Pc; + + #endregion + + #region Floating Point/NEON Registers + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x110)] + public unsafe fixed ulong V[32 * 2]; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x310)] + public uint Fpcr; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x314)] + public uint Fpsr; + + #endregion + + #region Debug Registers + +#pragma warning disable CA1823 // Avoid unused private fields. See https://github.com/dotnet/roslyn/issues/29224 + private const int ARM64_MAX_BREAKPOINTS = 8; + private const int ARM64_MAX_WATCHPOINTS = 2; +#pragma warning restore CA1823 // Avoid unused private fields + + [Register(RegisterType.Debug)] + [FieldOffset(0x318)] + public unsafe fixed uint Bcr[ARM64_MAX_BREAKPOINTS]; + + [Register(RegisterType.Debug)] + [FieldOffset(0x338)] + public unsafe fixed ulong Bvr[ARM64_MAX_BREAKPOINTS]; + + [Register(RegisterType.Debug)] + [FieldOffset(0x378)] + public unsafe fixed uint Wcr[ARM64_MAX_WATCHPOINTS]; + + [Register(RegisterType.Debug)] + [FieldOffset(0x380)] + public unsafe fixed ulong Wvr[ARM64_MAX_WATCHPOINTS]; + + #endregion +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ContextHolder.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ContextHolder.cs new file mode 100644 index 00000000000000..b5d194d0824fc2 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ContextHolder.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +public class CotnextHolder : IPlatformAgnosticContext where T : unmanaged, IPlatformContext +{ + public T Context; + + public uint Size => Context.Size; + public uint DefaultContextFlags => Context.DefaultContextFlags; + + public TargetPointer StackPointer { get => Context.StackPointer; set => Context.StackPointer = value; } + public TargetPointer InstructionPointer { get => Context.InstructionPointer; set => Context.InstructionPointer = value; } + public TargetPointer FramePointer { get => Context.FramePointer; set => Context.FramePointer = value; } + + public unsafe void ReadFromAddress(Target target, TargetPointer address) + { + Span buffer = new byte[Size]; + target.ReadBuffer(address, buffer); + FillFromBuffer(buffer); + } + public unsafe void FillFromBuffer(Span buffer) + { + Span structSpan = new(ref Context); + Span byteSpan = MemoryMarshal.Cast(structSpan); + if (buffer.Length > sizeof(T)) + { + buffer.Slice(0, sizeof(T)).CopyTo(byteSpan); + } + else + { + buffer.CopyTo(byteSpan); + } + } + public unsafe byte[] GetBytes() + { + Span structSpan = MemoryMarshal.CreateSpan(ref Context, 1); + Span byteSpan = MemoryMarshal.AsBytes(structSpan); + return byteSpan.ToArray(); + } + public IPlatformAgnosticContext Clone() => new CotnextHolder() { Context = Context }; + public void Clear() => Context = default; + public void Unwind(Target target) => Context.Unwind(target); +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IContext.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IContext.cs new file mode 100644 index 00000000000000..7a8c3bf1ebacb3 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IContext.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +public interface IPlatformContext +{ + public abstract uint Size { get; } + public abstract uint DefaultContextFlags { get; } + + public TargetPointer StackPointer { get; set; } + public TargetPointer InstructionPointer { get; set; } + public TargetPointer FramePointer { get; set; } + public abstract void Unwind(Target target); +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs new file mode 100644 index 00000000000000..7cb0db6ea79a54 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +public interface IPlatformAgnosticContext +{ + public abstract uint Size { get; } + public abstract uint DefaultContextFlags { get; } + + public TargetPointer StackPointer { get; set; } + public TargetPointer InstructionPointer { get; set; } + public TargetPointer FramePointer { get; set; } + + public abstract void Clear(); + public abstract void ReadFromAddress(Target target, TargetPointer address); + public abstract void FillFromBuffer(Span buffer); + public abstract byte[] GetBytes(); + public abstract IPlatformAgnosticContext Clone(); + public abstract void Unwind(Target target); + + public static IPlatformAgnosticContext GetContextForPlatform(Target target) + { + switch (target.Platform) + { + case Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_AMD64: + case Target.CorDebugPlatform.CORDB_PLATFORM_POSIX_AMD64: + case Target.CorDebugPlatform.CORDB_PLATFORM_MAC_AMD64: + return new CotnextHolder(); + case Target.CorDebugPlatform.CORDB_PLATFORM_POSIX_ARM64: + case Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_ARM64: + return new CotnextHolder(); + default: + throw new InvalidOperationException($"Unsupported platform {target.Platform}"); + } + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/RegisterAttribute.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/RegisterAttribute.cs new file mode 100644 index 00000000000000..1ae0c32bf7ffa4 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/RegisterAttribute.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +[Flags] +public enum RegisterType : byte +{ + General = 0x01, + Control = 0x02, + Segments = 0x03, + FloatingPoint = 0x04, + Debug = 0x05, + TypeMask = 0x0f, + + ProgramCounter = 0x10, + StackPointer = 0x20, + FramePointer = 0x40, +} + + +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] +public sealed class RegisterAttribute : Attribute +{ + /// + /// Gets or sets optional name override + /// + public string? Name { get; set; } + + /// + /// Gets register type and flags + /// + public RegisterType RegisterType { get; } + + public RegisterAttribute(RegisterType registerType) + { + RegisterType = registerType; + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Unwinder.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Unwinder.cs new file mode 100644 index 00000000000000..b977d1d263a731 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Unwinder.cs @@ -0,0 +1,168 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; +using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; +internal static unsafe partial class Unwinder +{ + [LibraryImport("unwinder_cdac_arm64", EntryPoint = "arm64Unwind")] + private static partial int ARM64Unwind( + ref ARM64Context context, + delegate* unmanaged readFromTarget, + delegate* unmanaged getAllocatedBuffer, + delegate* unmanaged getStackWalkInfo, + delegate* unmanaged unwinderFail, + void* callbackContext); + + public static int ARM64Unwind( + ref ARM64Context context, + Target target) + { + using CallbackContext callbackContext = new(target); + + GCHandle handle = GCHandle.Alloc(callbackContext); + int ret = ARM64Unwind( + ref context, + &ReadFromTarget, + &GetAllocatedBuffer, + &GetStackWalkInfo, + &UnwinderFail, + GCHandle.ToIntPtr(handle).ToPointer()); + handle.Free(); + + return ret; + } + + [LibraryImport("unwinder_cdac_amd64", EntryPoint = "amd64Unwind")] + private static partial int AMD64Unwind( + ref AMD64Context context, + delegate* unmanaged readFromTarget, + delegate* unmanaged getAllocatedBuffer, + delegate* unmanaged getStackWalkInfo, + delegate* unmanaged unwinderFail, + void* callbackContext); + + public static int AMD64Unwind( + ref AMD64Context context, + Target target) + { + using CallbackContext callbackContext = new(target); + + GCHandle handle = GCHandle.Alloc(callbackContext); + int ret = AMD64Unwind( + ref context, + &ReadFromTarget, + &GetAllocatedBuffer, + &GetStackWalkInfo, + &UnwinderFail, + GCHandle.ToIntPtr(handle).ToPointer()); + handle.Free(); + + return ret; + } + + /// + /// Used to inject target into unwinder callbacks and track memory allocated for native unwinder. + /// + private sealed class CallbackContext(Target target) : IDisposable + { + private bool disposed; + public Target Target { get; } = target; + public List AllocatedRegions { get; } = []; + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (disposed) return; + + if (disposing) + { + foreach (IntPtr ptr in AllocatedRegions) + { + NativeMemory.Free(ptr.ToPointer()); + } + } + disposed = true; + } + } + + // ReadFromTarget allows the unwinder to read memory from the target process + // into an allocated buffer. This buffer is either allocated by the unwinder + // with its lifetime managed by the unwinder or allocated through GetAllocatedBuffer. + // In the latter case, the unwinder can only use the buffer for the duration of the + // unwind call. Once the call is over the cDAC will free all allocated buffers. + [UnmanagedCallersOnly] + private static unsafe int ReadFromTarget(ulong address, void* pBuffer, int bufferSize, void* context) + { + if (GCHandle.FromIntPtr((IntPtr)context).Target is not CallbackContext callbackContext) + { + return -1; + } + Span span = new Span(pBuffer, bufferSize); + callbackContext.Target.ReadBuffer(address, span); + return 0; + } + + // GetAllocatedBuffer allows the unwinder to allocate a buffer that will be freed + // once the unwinder call is complete. + // Freeing is handeled in the Dispose method of CallbackContext. + [UnmanagedCallersOnly] + private static unsafe int GetAllocatedBuffer(int bufferSize, void** ppBuffer, void* context) + { + if (GCHandle.FromIntPtr((IntPtr)context).Target is not CallbackContext callbackContext) + { + return -1; + } + *ppBuffer = NativeMemory.Alloc((nuint)bufferSize); + callbackContext.AllocatedRegions.Add((IntPtr)(*ppBuffer)); + return 0; + } + + // cDAC version of GetRuntimeStackWalkInfo defined in codeman.cpp + // To maintain the same signature as the original function, this returns void. + // If the unwindInfoBase or funcEntry can not be found, both will be 0. + [UnmanagedCallersOnly] + private static unsafe void GetStackWalkInfo(ulong controlPC, void* pUnwindInfoBase, void* pFuncEntry, void* context) + { + if ((nuint)pUnwindInfoBase != 0) *(nuint*)pUnwindInfoBase = 0; + if ((nuint)pFuncEntry != 0) *(nuint*)pFuncEntry = 0; + + if (GCHandle.FromIntPtr((IntPtr)context).Target is not CallbackContext callbackContext) + { + return; + } + + IExecutionManager eman = callbackContext.Target.Contracts.ExecutionManager; + try + { + if (eman.GetCodeBlockHandle(controlPC) is CodeBlockHandle cbh) + { + TargetPointer methodDesc = eman.GetMethodDesc(cbh); + TargetPointer unwindInfoBase = eman.GetUnwindInfoBaseAddress(cbh); + TargetPointer unwindInfo = eman.GetUnwindInfo(cbh, controlPC); + if ((nuint)pUnwindInfoBase != 0) *(nuint*)pUnwindInfoBase = (nuint)unwindInfoBase.Value; + if ((nuint)pFuncEntry != 0) *(nuint*)pFuncEntry = (nuint)unwindInfo.Value; + } + } + catch (System.Exception ex) + { + Console.WriteLine($"GetStackWalkInfo failed: {ex}"); + } + } + + [UnmanagedCallersOnly] + private static void UnwinderFail() + { + Debug.Fail("Native unwinder assertion failure."); + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameIterator.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameIterator.cs new file mode 100644 index 00000000000000..3ef20699a973c5 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameIterator.cs @@ -0,0 +1,97 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal sealed class FrameIterator +{ + private static readonly DataType[] SupportedFrameTypes = + [ + DataType.InlinedCallFrame, + DataType.SoftwareExceptionFrame + ]; + + private readonly Target target; + private readonly TargetPointer terminator; + private TargetPointer currentFramePointer; + + internal Data.Frame CurrentFrame => target.ProcessedData.GetOrAdd(currentFramePointer); + + public TargetPointer CurrentFrameAddress => currentFramePointer; + + public FrameIterator(Target target, ThreadData threadData) + { + this.target = target; + terminator = new TargetPointer(target.PointerSize == 8 ? ulong.MaxValue : uint.MaxValue); + currentFramePointer = threadData.Frame; + } + + public bool IsValid() + { + return currentFramePointer != terminator; + } + + public bool Next() + { + if (currentFramePointer == terminator) + return false; + + currentFramePointer = CurrentFrame.Next; + return true; + } + + public bool TryUpdateContext(IPlatformAgnosticContext context) + { + switch (GetFrameType(CurrentFrame)) + { + case DataType.InlinedCallFrame: + Data.InlinedCallFrame inlinedCallFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); + context.Clear(); + context.InstructionPointer = inlinedCallFrame.CallerReturnAddress; + context.StackPointer = inlinedCallFrame.CallSiteSP; + context.FramePointer = inlinedCallFrame.CalleeSavedFP; + return true; + case DataType.SoftwareExceptionFrame: + Data.SoftwareExceptionFrame softwareExceptionFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); + context.ReadFromAddress(target, softwareExceptionFrame.TargetContext); + return true; + default: + return false; + } + } + + public bool IsInlineCallFrameWithActiveCall() + { + if (GetFrameType(CurrentFrame) != DataType.InlinedCallFrame) + { + return false; + } + Data.InlinedCallFrame inlinedCallFrame = target.ProcessedData.GetOrAdd(currentFramePointer); + return inlinedCallFrame.CallerReturnAddress != 0; + } + + private DataType GetFrameType(Data.Frame frame) + { + foreach (DataType frameType in SupportedFrameTypes) + { + TargetPointer typeVptr; + try + { + // not all Frames are in all builds, so we need to catch the exception + typeVptr = target.ReadGlobalPointer(frameType.ToString() + "Identifier"); + if (frame.VPtr == typeVptr) + { + return frameType; + } + } + catch (InvalidOperationException) + { + } + } + + return DataType.Unknown; + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalkFactory.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalkFactory.cs new file mode 100644 index 00000000000000..3a05fdb48ddc15 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalkFactory.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +public sealed class StackWalkFactory : IContractFactory +{ + IStackWalk IContractFactory.CreateContract(Target target, int version) + { + return version switch + { + 1 => new StackWalk_1(target), + _ => default(StackWalk), + }; + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs new file mode 100644 index 00000000000000..24f0c9c19e5908 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs @@ -0,0 +1,194 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics; +using System.Collections.Generic; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal readonly struct StackWalk_1 : IStackWalk +{ + private readonly Target _target; + + internal StackWalk_1(Target target) + { + _target = target; + } + + public enum StackWalkState + { + SW_COMPLETE, + SW_ERROR, + + // The current Context is managed + SW_FRAMELESS, + + // The current Context is unmanaged. + // The next update will use a Frame to get a managed context + // When SW_FRAME, the FrameAddress is valid + SW_FRAME, + SW_SKIPPED_FRAME, + } + + private record StackDataFrameHandle( + IPlatformAgnosticContext Context, + StackWalkState State, + TargetPointer FrameAddress) : IStackDataFrameHandle + { } + + private class StackWalkData(IPlatformAgnosticContext context, StackWalkState state, FrameIterator frameIter) + { + public IPlatformAgnosticContext Context { get; set; } = context; + public StackWalkState State { get; set; } = state; + public FrameIterator FrameIter { get; set; } = frameIter; + + public StackDataFrameHandle ToDataFrame() => new(Context.Clone(), State, FrameIter.CurrentFrameAddress); + } + + IEnumerable IStackWalk.CreateStackWalk(ThreadData threadData) + { + IPlatformAgnosticContext context = IPlatformAgnosticContext.GetContextForPlatform(_target); + FillContextFromThread(context, threadData); + StackWalkState state = IsManaged(context.InstructionPointer, out _) ? StackWalkState.SW_FRAMELESS : StackWalkState.SW_FRAME; + StackWalkData stackWalkData = new(context, state, new(_target, threadData)); + + yield return stackWalkData.ToDataFrame(); + + while (Next(stackWalkData)) + { + yield return stackWalkData.ToDataFrame(); + } + } + + private bool Next(StackWalkData handle) + { + switch (handle.State) + { + case StackWalkState.SW_FRAMELESS: + try + { + handle.Context.Unwind(_target); + } + catch + { + handle.State = StackWalkState.SW_ERROR; + throw; + } + break; + case StackWalkState.SW_SKIPPED_FRAME: + handle.FrameIter.Next(); + break; + case StackWalkState.SW_FRAME: + handle.FrameIter.TryUpdateContext(handle.Context); + if (!handle.FrameIter.IsInlineCallFrameWithActiveCall()) + { + handle.FrameIter.Next(); + } + break; + case StackWalkState.SW_ERROR: + case StackWalkState.SW_COMPLETE: + return false; + } + UpdateState(handle); + + return handle.State is not (StackWalkState.SW_ERROR or StackWalkState.SW_COMPLETE); + } + + private void UpdateState(StackWalkData handle) + { + // If we are complete or in a bad state, no updating is required. + if (handle.State is StackWalkState.SW_ERROR or StackWalkState.SW_COMPLETE) + { + return; + } + + bool isManaged = IsManaged(handle.Context.InstructionPointer, out _); + bool validFrame = handle.FrameIter.IsValid(); + + if (isManaged) + { + handle.State = StackWalkState.SW_FRAMELESS; + if (CheckForSkippedFrames(handle)) + { + handle.State = StackWalkState.SW_SKIPPED_FRAME; + return; + } + } + else + { + handle.State = validFrame ? StackWalkState.SW_FRAME : StackWalkState.SW_COMPLETE; + } + } + + private bool CheckForSkippedFrames(StackWalkData handle) + { + // ensure we can find the caller context + Debug.Assert(IsManaged(handle.Context.InstructionPointer, out _)); + + // if there are no more Frames, vacuously false + if (!handle.FrameIter.IsValid()) + { + return false; + } + + // get the caller context + IPlatformAgnosticContext parentContext = handle.Context.Clone(); + parentContext.Unwind(_target); + + return handle.FrameIter.CurrentFrameAddress.Value < parentContext.StackPointer.Value; + } + + byte[] IStackWalk.GetRawContext(IStackDataFrameHandle stackDataFrameHandle) + { + StackDataFrameHandle handle = AssertCorrectHandle(stackDataFrameHandle); + return handle.Context.GetBytes(); + } + + TargetPointer IStackWalk.GetFrameAddress(IStackDataFrameHandle stackDataFrameHandle) + { + StackDataFrameHandle handle = AssertCorrectHandle(stackDataFrameHandle); + if (handle.State is StackWalkState.SW_FRAME or StackWalkState.SW_SKIPPED_FRAME) + { + return handle.FrameAddress; + } + return TargetPointer.Null; + } + + private bool IsManaged(TargetPointer ip, [NotNullWhen(true)] out CodeBlockHandle? codeBlockHandle) + { + IExecutionManager eman = _target.Contracts.ExecutionManager; + TargetCodePointer codePointer = CodePointerUtils.CodePointerFromAddress(ip, _target); + if (eman.GetCodeBlockHandle(codePointer) is CodeBlockHandle cbh && cbh.Address != TargetPointer.Null) + { + codeBlockHandle = cbh; + return true; + } + codeBlockHandle = default; + return false; + } + + private unsafe void FillContextFromThread(IPlatformAgnosticContext context, ThreadData threadData) + { + byte[] bytes = new byte[context.Size]; + Span buffer = new Span(bytes); + if (!_target.TryGetThreadContext(threadData.OSId.Value, context.DefaultContextFlags, buffer)) + { + throw new InvalidOperationException($"GetThreadContext failed for thread {threadData.OSId.Value}"); + } + + context.FillFromBuffer(buffer); + } + + private static StackDataFrameHandle AssertCorrectHandle(IStackDataFrameHandle stackDataFrameHandle) + { + if (stackDataFrameHandle is not StackDataFrameHandle handle) + { + throw new ArgumentException("Invalid stack data frame handle", nameof(stackDataFrameHandle)); + } + + return handle; + } +}; diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/Frame.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/Frame.cs new file mode 100644 index 00000000000000..b6939f9d70b3bc --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/Frame.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class Frame : IData +{ + static Frame IData.Create(Target target, TargetPointer address) + => new Frame(target, address); + + public Frame(Target target, TargetPointer address) + { + Address = address; + Target.TypeInfo type = target.GetTypeInfo(DataType.Frame); + Next = target.ReadPointer(address + (ulong)type.Fields[nameof(Next)].Offset); + VPtr = target.ReadPointer(address); + } + + public TargetPointer Address { get; init; } + public TargetPointer VPtr { get; init; } + public TargetPointer Next { get; init; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/InlinedCallFrame.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/InlinedCallFrame.cs new file mode 100644 index 00000000000000..78af563e607985 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/InlinedCallFrame.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal class InlinedCallFrame : IData +{ + static InlinedCallFrame IData.Create(Target target, TargetPointer address) + => new InlinedCallFrame(target, address); + + public InlinedCallFrame(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.InlinedCallFrame); + CallSiteSP = target.ReadPointer(address + (ulong)type.Fields[nameof(CallSiteSP)].Offset); + CallerReturnAddress = target.ReadPointer(address + (ulong)type.Fields[nameof(CallerReturnAddress)].Offset); + CalleeSavedFP = target.ReadPointer(address + (ulong)type.Fields[nameof(CalleeSavedFP)].Offset); + Address = address; + } + + public TargetPointer Address { get;} + public TargetPointer CallSiteSP { get; } + public TargetPointer CallerReturnAddress { get; } + public TargetPointer CalleeSavedFP { get; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/SoftwareExceptionFrame.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/SoftwareExceptionFrame.cs new file mode 100644 index 00000000000000..e318aa2638f58f --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/SoftwareExceptionFrame.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal class SoftwareExceptionFrame : IData +{ + static SoftwareExceptionFrame IData.Create(Target target, TargetPointer address) + => new SoftwareExceptionFrame(target, address); + + public SoftwareExceptionFrame(Target target, TargetPointer address) + { + // TypeInfo will only exist if FEATURE_EH_FUNCLETS is enabled. + // If it doesn't exist, then this type of frame is not present in target. + Target.TypeInfo type = target.GetTypeInfo(DataType.SoftwareExceptionFrame); + Address = address; + TargetContext = address + (ulong)type.Fields[nameof(TargetContext)].Offset; + ReturnAddress = target.ReadPointer(address + (ulong)type.Fields[nameof(ReturnAddress)].Offset); + } + + public TargetPointer Address { get; } + public TargetPointer TargetContext { get; } + public TargetPointer ReturnAddress { get; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs index 22a33b4820e326..84ffab173b5f6c 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs @@ -12,7 +12,17 @@ public RealCodeHeader(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.RealCodeHeader); MethodDesc = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodDesc)].Offset); + + // Only available if FEATURE_EH_FUNCLETS is enabled. + if (type.Fields.ContainsKey(nameof(NumUnwindInfos))) + NumUnwindInfos = target.Read(address + (ulong)type.Fields[nameof(NumUnwindInfos)].Offset); + + // Only available if FEATURE_EH_FUNCLETS is enabled. + if (type.Fields.ContainsKey(nameof(UnwindInfos))) + UnwindInfos = address + (ulong)type.Fields[nameof(UnwindInfos)].Offset; } public TargetPointer MethodDesc { get; init; } + public uint? NumUnwindInfos { get; init; } + public TargetPointer? UnwindInfos { get; init; } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RuntimeFunction.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RuntimeFunction.cs index f87da0de3f07f9..f614803e0efabc 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RuntimeFunction.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RuntimeFunction.cs @@ -19,7 +19,7 @@ public RuntimeFunction(Target target, TargetPointer address) EndAddress = target.Read(address + (ulong)type.Fields[nameof(EndAddress)].Offset); UnwindData = target.Read(address + (ulong)type.Fields[nameof(UnwindData)].Offset); - } + } public uint BeginAddress { get; } public uint? EndAddress { get; } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs index 7655335bec0f9b..d49ceb0eeaacd2 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs @@ -37,6 +37,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG [typeof(IPlatformMetadata)] = new PlatformMetadataFactory(), [typeof(IPrecodeStubs)] = new PrecodeStubsFactory(), [typeof(IReJIT)] = new ReJITFactory(), + [typeof(IStackWalk)] = new StackWalkFactory(), }; configureFactories?.Invoke(_factories); } @@ -53,6 +54,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG public override IPlatformMetadata PlatformMetadata => GetContract(); public override IPrecodeStubs PrecodeStubs => GetContract(); public override IReJIT ReJIT => GetContract(); + public override IStackWalk StackWalk => GetContract(); private TContract GetContract() where TContract : IContract { diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs index 8bcc78fdd31975..e2885d60f74b05 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs @@ -43,18 +43,32 @@ private readonly struct Configuration public override DataCache ProcessedData { get; } public delegate int ReadFromTargetDelegate(ulong address, Span bufferToFill); + public delegate int GetTargetThreadContextDelegate(uint threadId, uint contextFlags, uint contextSize, Span bufferToFill); + public delegate int GetTargetPlatformDelegate(out int platform); /// /// Create a new target instance from a contract descriptor embedded in the target memory. /// /// The offset of the contract descriptor in the target memory /// A callback to read memory blocks at a given address from the target + /// A callback to fetch a thread's context + /// A callback to fetch the target's platform /// The target object. /// If a target instance could be created, true; otherwise, false. - public static bool TryCreate(ulong contractDescriptor, ReadFromTargetDelegate readFromTarget, out ContractDescriptorTarget? target) + public static bool TryCreate( + ulong contractDescriptor, + ReadFromTargetDelegate readFromTarget, + GetTargetThreadContextDelegate getThreadContext, + GetTargetPlatformDelegate getTargetPlatform, + out ContractDescriptorTarget? target) { - Reader reader = new Reader(readFromTarget); - if (TryReadContractDescriptor(contractDescriptor, reader, out Configuration config, out ContractDescriptorParser.ContractDescriptor? descriptor, out TargetPointer[] pointerData)) + Reader reader = new Reader(readFromTarget, getThreadContext, getTargetPlatform); + if (TryReadContractDescriptor( + contractDescriptor, + reader, + out Configuration config, + out ContractDescriptorParser.ContractDescriptor? descriptor, + out TargetPointer[] pointerData)) { target = new ContractDescriptorTarget(config, descriptor!, pointerData, reader); return true; @@ -70,12 +84,25 @@ public static bool TryCreate(ulong contractDescriptor, ReadFromTargetDelegate re /// The contract descriptor to use for this target /// The values for any global pointers specified in the contract descriptor. /// A callback to read memory blocks at a given address from the target + /// A callback to fetch a thread's context + /// A callback to fetch the target's platform /// Whether the target is little-endian /// The size of a pointer in bytes in the target process. /// The target object. - public static ContractDescriptorTarget Create(ContractDescriptorParser.ContractDescriptor contractDescriptor, TargetPointer[] globalPointerValues, ReadFromTargetDelegate readFromTarget, bool isLittleEndian, int pointerSize) + public static ContractDescriptorTarget Create( + ContractDescriptorParser.ContractDescriptor contractDescriptor, + TargetPointer[] globalPointerValues, + ReadFromTargetDelegate readFromTarget, + GetTargetThreadContextDelegate getThreadContext, + GetTargetPlatformDelegate getTargetPlatform, + bool isLittleEndian, + int pointerSize) { - return new ContractDescriptorTarget(new Configuration { IsLittleEndian = isLittleEndian, PointerSize = pointerSize }, contractDescriptor, globalPointerValues, new Reader(readFromTarget)); + return new ContractDescriptorTarget( + new Configuration { IsLittleEndian = isLittleEndian, PointerSize = pointerSize }, + contractDescriptor, + globalPointerValues, + new Reader(readFromTarget, getThreadContext, getTargetPlatform)); } private ContractDescriptorTarget(Configuration config, ContractDescriptorParser.ContractDescriptor descriptor, TargetPointer[] pointerData, Reader reader) @@ -236,6 +263,21 @@ private static DataType GetDataType(string type) public override int PointerSize => _config.PointerSize; public override bool IsLittleEndian => _config.IsLittleEndian; + public override CorDebugPlatform Platform + { + get + { + _reader.GetTargetPlatform(out int platform); + return (CorDebugPlatform)platform; + } + } + + public override bool TryGetThreadContext(ulong threadId, uint contextFlags, Span buffer) + { + // Underlying API only supports 32-bit thread IDs, mask off top 32 bits + int hr = _reader.GetThreadContext((uint)(threadId & uint.MaxValue), contextFlags, (uint)buffer.Length, buffer); + return hr == 0; + } /// /// Read a value from the target in target endianness @@ -550,7 +592,10 @@ public void Clear() } } - private readonly struct Reader(ReadFromTargetDelegate readFromTarget) + private readonly struct Reader( + ReadFromTargetDelegate readFromTarget, + GetTargetThreadContextDelegate getThreadContext, + GetTargetPlatformDelegate getTargetPlatform) { public int ReadFromTarget(ulong address, Span buffer) { @@ -559,5 +604,15 @@ public int ReadFromTarget(ulong address, Span buffer) public int ReadFromTarget(ulong address, byte* buffer, uint bytesToRead) => readFromTarget(address, new Span(buffer, checked((int)bytesToRead))); + + public int GetTargetPlatform(out int platform) + { + return getTargetPlatform(out platform); + } + + public int GetThreadContext(uint threadId, uint contextFlags, uint contextSize, Span buffer) + { + return getThreadContext(threadId, contextFlags, contextSize, buffer); + } } } diff --git a/src/native/managed/cdacreader/inc/cdac_reader.h b/src/native/managed/cdacreader/inc/cdac_reader.h index 8fe07b4d013e46..9bf5ddb409090e 100644 --- a/src/native/managed/cdacreader/inc/cdac_reader.h +++ b/src/native/managed/cdacreader/inc/cdac_reader.h @@ -12,9 +12,17 @@ extern "C" // Initialize the cDAC reader // descriptor: the address of the descriptor in the target process // read_from_target: a callback that reads memory from the target process -// read_context: a context pointer that will be passed to read_from_target +// read_thread_context: a callback that reads the context of a thread in the target process +// get_platform: a callback that reads the platform of the target process +// read_context: a context pointer that will be passed to callbacks // handle: returned opaque the handle to the reader. This should be passed to other functions in this API. -int cdac_reader_init(uint64_t descriptor, int(*read_from_target)(uint64_t, uint8_t*, uint32_t, void*), void* read_context, /*out*/ intptr_t* handle); +int cdac_reader_init( + uint64_t descriptor, + int(*read_from_target)(uint64_t, uint8_t*, uint32_t, void*), + int(*read_thread_context)(uint32_t, uint32_t, uint32_t, uint8_t*, void*), + int(*get_platform)(uint32_t*, void*), + void* read_context, + /*out*/ intptr_t* handle); // Free the cDAC reader // handle: handle to the reader diff --git a/src/native/managed/cdacreader/src/Entrypoints.cs b/src/native/managed/cdacreader/src/Entrypoints.cs index cc24447ef3f133..015247efc0a9c4 100644 --- a/src/native/managed/cdacreader/src/Entrypoints.cs +++ b/src/native/managed/cdacreader/src/Entrypoints.cs @@ -12,16 +12,39 @@ internal static class Entrypoints private const string CDAC = "cdac_reader_"; [UnmanagedCallersOnly(EntryPoint = $"{CDAC}init")] - private static unsafe int Init(ulong descriptor, delegate* unmanaged readFromTarget, void* readContext, IntPtr* handle) + private static unsafe int Init( + ulong descriptor, + delegate* unmanaged readFromTarget, + delegate* unmanaged readThreadContext, + delegate* unmanaged getPlatform, + void* readContext, + IntPtr* handle) { // TODO: [cdac] Better error code/details - if (!ContractDescriptorTarget.TryCreate(descriptor, (address, buffer) => + if (!ContractDescriptorTarget.TryCreate( + descriptor, + (address, buffer) => { fixed (byte* bufferPtr = buffer) { return readFromTarget(address, bufferPtr, (uint)buffer.Length, readContext); } - }, out ContractDescriptorTarget? target)) + }, + (threadId, contextFlags, contextSize, buffer) => + { + fixed (byte* bufferPtr = buffer) + { + return readThreadContext(threadId, contextFlags, contextSize, bufferPtr, readContext); + } + }, + (out int platform) => + { + fixed (int* platformPtr = &platform) + { + return getPlatform(platformPtr, readContext); + } + }, + out ContractDescriptorTarget? target)) return -1; GCHandle gcHandle = GCHandle.Alloc(target); diff --git a/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs b/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs index 0e543c87fccdff..c351796c33c8c3 100644 --- a/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs +++ b/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs @@ -2,8 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; namespace Microsoft.Diagnostics.DataContractReader.Legacy; @@ -15,16 +19,61 @@ internal sealed unsafe partial class ClrDataStackWalk : IXCLRDataStackWalk private readonly Target _target; private readonly IXCLRDataStackWalk? _legacyImpl; + private readonly IEnumerator _dataFrames; + public ClrDataStackWalk(TargetPointer threadAddr, uint flags, Target target, IXCLRDataStackWalk? legacyImpl) { _threadAddr = threadAddr; _flags = flags; _target = target; _legacyImpl = legacyImpl; + + ThreadData threadData = _target.Contracts.Thread.GetThreadData(_threadAddr); + _dataFrames = _target.Contracts.StackWalk.CreateStackWalk(threadData).GetEnumerator(); + + // IEnumerator begins before the first element. + // Call MoveNext() to set _dataFrames.Current to the first element. + _dataFrames.MoveNext(); } int IXCLRDataStackWalk.GetContext(uint contextFlags, uint contextBufSize, uint* contextSize, [MarshalUsing(CountElementName = "contextBufSize"), Out] byte[] contextBuf) - => _legacyImpl is not null ? _legacyImpl.GetContext(contextFlags, contextBufSize, contextSize, contextBuf) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + + IStackWalk sw = _target.Contracts.StackWalk; + IStackDataFrameHandle dataFrame = _dataFrames.Current; + byte[] context = sw.GetRawContext(dataFrame); + if (context.Length > contextBufSize) + hr = HResults.E_INVALIDARG; + + if (contextSize is not null) + { + *contextSize = (uint)context.Length; + } + + context.CopyTo(contextBuf); + +#if DEBUG + if (_legacyImpl is not null) + { + byte[] localContextBuf = new byte[contextBufSize]; + int hrLocal = _legacyImpl.GetContext(contextFlags, contextBufSize, null, localContextBuf); + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + + IPlatformAgnosticContext contextStruct = IPlatformAgnosticContext.GetContextForPlatform(_target); + IPlatformAgnosticContext localContextStruct = IPlatformAgnosticContext.GetContextForPlatform(_target); + contextStruct.FillFromBuffer(contextBuf); + localContextStruct.FillFromBuffer(localContextBuf); + + Debug.Assert(contextStruct.InstructionPointer == localContextStruct.InstructionPointer, $"cDAC IP: {contextStruct.InstructionPointer:x}, DAC IP: {localContextStruct.InstructionPointer:x}"); + Debug.Assert(contextStruct.StackPointer == localContextStruct.StackPointer, $"cDAC SP: {contextStruct.StackPointer:x}, DAC SP: {localContextStruct.StackPointer:x}"); + Debug.Assert(contextStruct.FramePointer == localContextStruct.FramePointer, $"cDAC FP: {contextStruct.FramePointer:x}, DAC FP: {localContextStruct.FramePointer:x}"); + } +#endif + + return hr; + } + int IXCLRDataStackWalk.GetFrame(void** frame) => _legacyImpl is not null ? _legacyImpl.GetFrame(frame) : HResults.E_NOTIMPL; int IXCLRDataStackWalk.GetFrameType(uint* simpleType, uint* detailedType) @@ -32,9 +81,69 @@ int IXCLRDataStackWalk.GetFrameType(uint* simpleType, uint* detailedType) int IXCLRDataStackWalk.GetStackSizeSkipped(ulong* stackSizeSkipped) => _legacyImpl is not null ? _legacyImpl.GetStackSizeSkipped(stackSizeSkipped) : HResults.E_NOTIMPL; int IXCLRDataStackWalk.Next() - => _legacyImpl is not null ? _legacyImpl.Next() : HResults.E_NOTIMPL; + { + int hr; + try + { + hr = _dataFrames.MoveNext() ? HResults.S_OK : HResults.S_FALSE; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyImpl is not null) + { + int hrLocal = _legacyImpl.Next(); + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + } +#endif + + return hr; + } int IXCLRDataStackWalk.Request(uint reqCode, uint inBufferSize, byte* inBuffer, uint outBufferSize, byte* outBuffer) - => _legacyImpl is not null ? _legacyImpl.Request(reqCode, inBufferSize, inBuffer, outBufferSize, outBuffer) : HResults.E_NOTIMPL; + { + const uint DACSTACKPRIV_REQUEST_FRAME_DATA = 0xf0000000; + + int hr = HResults.S_OK; + + switch (reqCode) + { + case DACSTACKPRIV_REQUEST_FRAME_DATA: + if (outBufferSize < sizeof(ulong)) + hr = HResults.E_INVALIDARG; + + IStackWalk sw = _target.Contracts.StackWalk; + IStackDataFrameHandle frameData = _dataFrames.Current; + TargetPointer frameAddr = sw.GetFrameAddress(frameData); + *(ulong*)outBuffer = frameAddr.Value; + hr = HResults.S_OK; + break; + default: + hr = HResults.E_NOTIMPL; + break; + } + +#if DEBUG + if (_legacyImpl is not null) + { + int hrLocal; + byte[] localOutBuffer = new byte[outBufferSize]; + fixed (byte* localOutBufferPtr = localOutBuffer) + { + hrLocal = _legacyImpl.Request(reqCode, inBufferSize, inBuffer, outBufferSize, localOutBufferPtr); + } + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + + for (int i = 0; i < outBufferSize; i++) + { + Debug.Assert(localOutBuffer[i] == outBuffer[i], $"cDAC: {outBuffer[i]:x}, DAC: {localOutBuffer[i]:x}"); + } + } +#endif + return hr; + } int IXCLRDataStackWalk.SetContext(uint contextSize, [In, MarshalUsing(CountElementName = "contextSize")] byte[] context) => _legacyImpl is not null ? _legacyImpl.SetContext(contextSize, context) : HResults.E_NOTIMPL; int IXCLRDataStackWalk.SetContext2(uint flags, uint contextSize, [In, MarshalUsing(CountElementName = "contextSize")] byte[] context) diff --git a/src/native/managed/cdacreader/src/cdacreader.csproj b/src/native/managed/cdacreader/src/cdacreader.csproj index 28249236574d8a..f2e86840cd13fd 100644 --- a/src/native/managed/cdacreader/src/cdacreader.csproj +++ b/src/native/managed/cdacreader/src/cdacreader.csproj @@ -31,6 +31,15 @@ + + + + + + + + + diff --git a/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs b/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs index e22d8653368ebf..ff941454038e43 100644 --- a/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs +++ b/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs @@ -168,6 +168,13 @@ public bool TryCreateTarget([NotNullWhen(true)] out ContractDescriptorTarget? ta throw new InvalidOperationException("Context already created"); ulong contractDescriptorAddress = CreateDescriptorFragments(); MockMemorySpace.ReadContext context = GetReadContext(); - return ContractDescriptorTarget.TryCreate(contractDescriptorAddress, context.ReadFromTarget, out target); + ContractDescriptorTarget.GetTargetPlatformDelegate getTargetPlatform = (out int platform) => + { + platform = TargetTestHelpers.Arch.Is64Bit ? + (int)Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_AMD64 : + (int)Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_X86; + return 0; + }; + return ContractDescriptorTarget.TryCreate(contractDescriptorAddress, context.ReadFromTarget, null, getTargetPlatform, out target); } } diff --git a/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTests.cs b/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTests.cs index 5687961764cd9f..c64b8af35ea783 100644 --- a/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTests.cs +++ b/src/native/managed/cdacreader/tests/ExecutionManager/ExecutionManagerTests.cs @@ -134,6 +134,40 @@ public void GetCodeBlockHandle_OneRangeZeroMethod(int version, MockTarget.Archit Assert.Null(eeInfo); } + [Theory] + [MemberData(nameof(StdArchAllVersions))] + public void GetUnwindInfoBaseAddress_OneRangeOneMethod(int version, MockTarget.Architecture arch) + { + const ulong codeRangeStart = 0x0a0a_0000u; // arbitrary + const uint codeRangeSize = 0xc000u; // arbitrary + const uint methodSize = 0x450; // arbitrary + + TargetPointer jitManagerAddress = new (0x000b_ff00); // arbitrary + + MockDescriptors.ExecutionManager emBuilder = new(version, arch, MockDescriptors.ExecutionManager.DefaultAllocationRange); + var jittedCode = emBuilder.AllocateJittedCodeRange(codeRangeStart, codeRangeSize); + + TargetCodePointer methodStart = emBuilder.AddJittedMethod(jittedCode, methodSize, 0x0101_aaa0); + + NibbleMapTestBuilderBase nibBuilder = emBuilder.CreateNibbleMap(codeRangeStart, codeRangeSize); + nibBuilder.AllocateCodeChunk(methodStart, methodSize); + + TargetPointer codeHeapListNodeAddress = emBuilder.AddCodeHeapListNode(TargetPointer.Null, codeRangeStart, codeRangeStart + codeRangeSize, codeRangeStart, nibBuilder.NibbleMapFragment.Address); + TargetPointer rangeSectionAddress = emBuilder.AddRangeSection(jittedCode, jitManagerAddress: jitManagerAddress, codeHeapListNodeAddress: codeHeapListNodeAddress); + TargetPointer rangeSectionFragmentAddress = emBuilder.AddRangeSectionFragment(jittedCode, rangeSectionAddress); + + var target = CreateTarget(emBuilder); + + var em = target.Contracts.ExecutionManager; + Assert.NotNull(em); + + // Get CodeBlockHandle + var eeInfo = em.GetCodeBlockHandle(methodStart); + Assert.NotNull(eeInfo); + TargetPointer actualBaseAddress = em.GetUnwindInfoBaseAddress(eeInfo.Value); + Assert.Equal(new TargetPointer(actualBaseAddress), actualBaseAddress); + } + [Theory] [MemberData(nameof(StdArchAllVersions))] public void GetCodeBlockHandle_R2R_NoRuntimeFunctionMatch(int version, MockTarget.Architecture arch) @@ -328,6 +362,40 @@ public void GetMethodDesc_R2R_HotColdBlock(int version, MockTarget.Architecture } } + [Theory] + [MemberData(nameof(StdArchAllVersions))] + public void GetUnwindInfoBaseAddress_R2R_ManyRuntimeFunction(int version, MockTarget.Architecture arch) + { + const ulong codeRangeStart = 0x0a0a_0000u; // arbitrary + const uint codeRangeSize = 0xc000u; // arbitrary + TargetPointer jitManagerAddress = new(0x000b_ff00); // arbitrary + + MockDescriptors.ExecutionManager emBuilder = new(version, arch, MockDescriptors.ExecutionManager.DefaultAllocationRange); + var jittedCode = emBuilder.AllocateJittedCodeRange(codeRangeStart, codeRangeSize); + + uint runtimeFunction = 0x100; + + TargetPointer r2rInfo = emBuilder.AddReadyToRunInfo([runtimeFunction], []); + MockDescriptors.HashMap hashMapBuilder = new(emBuilder.Builder); + hashMapBuilder.PopulatePtrMap( + r2rInfo + (uint)emBuilder.Types[DataType.ReadyToRunInfo].Fields[nameof(Data.ReadyToRunInfo.EntryPointToMethodDescMap)].Offset, + [(jittedCode.RangeStart + runtimeFunction, new TargetPointer(0x0101_aaa0))]); + + TargetPointer r2rModule = emBuilder.AddReadyToRunModule(r2rInfo); + TargetPointer rangeSectionAddress = emBuilder.AddReadyToRunRangeSection(jittedCode, jitManagerAddress, r2rModule); + _ = emBuilder.AddRangeSectionFragment(jittedCode, rangeSectionAddress); + + Target target = CreateTarget(emBuilder); + + IExecutionManager em = target.Contracts.ExecutionManager; + Assert.NotNull(em); + + var handle = em.GetCodeBlockHandle(codeRangeStart + runtimeFunction); + Assert.NotNull(handle); + TargetPointer actualBaseAddress = em.GetUnwindInfoBaseAddress(handle.Value); + Assert.Equal(new TargetPointer(codeRangeStart), actualBaseAddress); + } + public static IEnumerable StdArchAllVersions() { const int highestVersion = 2; diff --git a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs index 007574ca7d7431..92f0a924f2e2dc 100644 --- a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs @@ -29,6 +29,7 @@ public TestPlaceholderTarget(MockTarget.Architecture arch, ReadFromTargetDelegat { IsLittleEndian = arch.IsLittleEndian; PointerSize = arch.Is64Bit ? 8 : 4; + Platform = Target.CorDebugPlatform.CORDB_PLATFORM_MAC_AMD64; _contractRegistry = new Mock().Object; _dataCache = new DefaultDataCache(this); _typeInfoCache = types ?? []; @@ -43,6 +44,7 @@ internal void SetContracts(ContractRegistry contracts) public override int PointerSize { get; } public override bool IsLittleEndian { get; } + public override CorDebugPlatform Platform { get; } public override bool IsAlignedToPointerSize(TargetPointer pointer) { @@ -226,6 +228,8 @@ public override Target.TypeInfo GetTypeInfo(DataType dataType) throw new NotImplementedException(); } + public override bool TryGetThreadContext(ulong threadId, uint contextFlags, Span bufferToFill) => throw new NotImplementedException(); + public override Target.IDataCache ProcessedData => _dataCache; public override ContractRegistry Contracts => _contractRegistry; diff --git a/src/tests/Common/Directory.Build.targets b/src/tests/Common/Directory.Build.targets index ca3cd3786b9713..3a7cf329b59df5 100644 --- a/src/tests/Common/Directory.Build.targets +++ b/src/tests/Common/Directory.Build.targets @@ -122,6 +122,10 @@ True + + True + + diff --git a/src/tools/StressLogAnalyzer/src/Program.cs b/src/tools/StressLogAnalyzer/src/Program.cs index bd5e3b48e580ad..e475d0547b2a14 100644 --- a/src/tools/StressLogAnalyzer/src/Program.cs +++ b/src/tools/StressLogAnalyzer/src/Program.cs @@ -492,6 +492,8 @@ ContractDescriptorTarget CreateTarget() => ContractDescriptorTarget.Create( GetDescriptor(contractVersion), [TargetPointer.Null, new TargetPointer(header->memoryBase + (nuint)((byte*)&header->moduleTable - (byte*)header))], (address, buffer) => ReadFromMemoryMappedLog(address, buffer, header), + (threadId, contextFlags, contextSize, bufferToFill) => throw new NotImplementedException("StressLogAnalyzer does not provide GetTargetThreadContext implementation"), + (out platform) => throw new NotImplementedException("StressLogAnalyzer does not provide GetTargetPlatform implementation"), true, nuint.Size); }