diff --git a/src/coreclr/inc/sospriv.idl b/src/coreclr/inc/sospriv.idl
index f56be4132f1e47..dd32bf649ec868 100644
--- a/src/coreclr/inc/sospriv.idl
+++ b/src/coreclr/inc/sospriv.idl
@@ -657,3 +657,82 @@ interface ISOSDacInterface17 : IUnknown
[out] ISOSStressLogMsgEnum **ppEnum);
HRESULT GetStressLogMemoryRanges([out] ISOSMemoryEnum **ppEnum);
}
+
+cpp_quote("#ifndef _SOS_GCInfoData")
+cpp_quote("#define _SOS_GCInfoData")
+
+typedef struct _SOSCodeRange
+{
+ unsigned int BeginOffset;
+ unsigned int EndOffset;
+} SOSCodeRange;
+
+typedef struct _SOSGCInfoHeader
+{
+ ULONG SizeOf;
+
+ unsigned int GcInfoVersion;
+ unsigned int CodeSize;
+ unsigned int PrologSize;
+ unsigned int StackBaseRegister;
+ unsigned int SizeOfStackParameterArea;
+
+ BOOL IsVarArg;
+ BOOL WantsReportOnlyLeaf;
+ BOOL HasTailCalls;
+
+ BOOL GSCookieIsPresent;
+ int GSCookieStackSlot;
+ unsigned int GSCookieValidRangeStart;
+ unsigned int GSCookieValidRangeEnd;
+
+ BOOL PSPSymIsPresent;
+ int PSPSymStackSlot;
+
+ BOOL GenericsInstContextIsPresent;
+ int GenericsInstContextStackSlot;
+ unsigned int GenericsInstContextKind;
+} SOSGCInfoHeader;
+
+typedef struct _SOSGCSlotLifetime
+{
+ unsigned int BeginOffset;
+ unsigned int EndOffset;
+ int IsRegister;
+ unsigned int RegisterNumber;
+ int SpOffset;
+ unsigned int BaseRegister;
+ unsigned int GcFlags;
+} SOSGCSlotLifetime;
+
+cpp_quote("#endif //_SOS_GCInfoData")
+
+[
+ object,
+ local,
+ uuid(3dccf95b-bca2-40ee-8b83-d8d7574a1df0)
+]
+interface ISOSDacInterface18 : IUnknown
+{
+ HRESULT GetGCInfoHeader(
+ [in] CLRDATA_ADDRESS ip,
+ [in, out] SOSGCInfoHeader* header);
+
+ HRESULT GetGCInfoInterruptibleRanges(
+ [in] CLRDATA_ADDRESS ip,
+ [in] ULONG count,
+ [out, size_is(count), length_is(*pNeeded)] SOSCodeRange* ranges,
+ [out] ULONG* pNeeded);
+
+ HRESULT GetGCInfoSafePoints(
+ [in] CLRDATA_ADDRESS ip,
+ [in] ULONG count,
+ [out, size_is(count), length_is(*pNeeded)] unsigned int* offsets,
+ [out] ULONG* pNeeded);
+
+ HRESULT GetGCInfoSlotLifetimes(
+ [in] CLRDATA_ADDRESS ip,
+ [in] ULONG count,
+ [out, size_is(count), length_is(*pNeeded)] SOSGCSlotLifetime* lifetimes,
+ [out] ULONG* pNeeded);
+}
diff --git a/src/coreclr/pal/prebuilt/inc/sospriv.h b/src/coreclr/pal/prebuilt/inc/sospriv.h
index 764d1d424d0ba3..ef90037888260f 100644
--- a/src/coreclr/pal/prebuilt/inc/sospriv.h
+++ b/src/coreclr/pal/prebuilt/inc/sospriv.h
@@ -3766,6 +3766,545 @@ EXTERN_C const IID IID_ISOSDacInterface16;
#endif /* __ISOSDacInterface16_INTERFACE_DEFINED__ */
+#ifndef _SOS_StressLogData
+#define _SOS_StressLogData
+typedef struct _SOSStressLogData
+ {
+ unsigned int LoggedFacilities;
+ unsigned int Level;
+ unsigned int MaxSizePerThread;
+ unsigned int MaxSizeTotal;
+ int TotalChunks;
+ UINT64 TickFrequency;
+ UINT64 StartTimestamp;
+ UINT64 StartTime;
+ } SOSStressLogData;
+
+#endif // _SOS_StressLogData
+
+#ifndef _SOS_ThreadStressLogData
+#define _SOS_ThreadStressLogData
+typedef struct _SOSThreadStressLogData
+ {
+ CLRDATA_ADDRESS ThreadLogAddress;
+ UINT64 ThreadId;
+ } SOSThreadStressLogData;
+
+#endif // _SOS_ThreadStressLogData
+
+#ifndef _SOS_StressMsgData
+#define _SOS_StressMsgData
+typedef struct _SOSStressMsgData
+ {
+ unsigned int Facility;
+ CLRDATA_ADDRESS FormatString;
+ UINT64 Timestamp;
+ unsigned int ArgumentCount;
+ } SOSStressMsgData;
+
+#endif // _SOS_StressMsgData
+
+#ifndef __ISOSStressLogThreadEnum_INTERFACE_DEFINED__
+#define __ISOSStressLogThreadEnum_INTERFACE_DEFINED__
+
+/* interface ISOSStressLogThreadEnum */
+/* [uuid][local][object] */
+
+
+EXTERN_C const IID IID_ISOSStressLogThreadEnum;
+
+#if defined(__cplusplus) && !defined(CINTERFACE)
+
+ MIDL_INTERFACE("94a2bd3d-ab3d-43bf-81d8-3ae96b8e33cd")
+ ISOSStressLogThreadEnum : public ISOSEnum
+ {
+ public:
+ virtual HRESULT STDMETHODCALLTYPE Next(
+ unsigned int count,
+ SOSThreadStressLogData values[],
+ unsigned int *pFetched) = 0;
+
+ };
+
+
+#else /* C style interface */
+
+ typedef struct ISOSStressLogThreadEnumVtbl
+ {
+ BEGIN_INTERFACE
+
+ HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
+ ISOSStressLogThreadEnum * This,
+ /* [in] */ REFIID riid,
+ /* [annotation][iid_is][out] */
+ _COM_Outptr_ void **ppvObject);
+
+ ULONG ( STDMETHODCALLTYPE *AddRef )(
+ ISOSStressLogThreadEnum * This);
+
+ ULONG ( STDMETHODCALLTYPE *Release )(
+ ISOSStressLogThreadEnum * This);
+
+ HRESULT ( STDMETHODCALLTYPE *Skip )(
+ ISOSStressLogThreadEnum * This,
+ uint32_t count);
+
+ HRESULT ( STDMETHODCALLTYPE *Reset )(
+ ISOSStressLogThreadEnum * This);
+
+ HRESULT ( STDMETHODCALLTYPE *GetCount )(
+ ISOSStressLogThreadEnum * This,
+ uint32_t *pCount);
+
+ HRESULT ( STDMETHODCALLTYPE *Next )(
+ ISOSStressLogThreadEnum * This,
+ unsigned int count,
+ SOSThreadStressLogData values[],
+ unsigned int *pFetched);
+
+ END_INTERFACE
+ } ISOSStressLogThreadEnumVtbl;
+
+ interface ISOSStressLogThreadEnum
+ {
+ CONST_VTBL struct ISOSStressLogThreadEnumVtbl *lpVtbl;
+ };
+
+
+
+#ifdef COBJMACROS
+
+
+#define ISOSStressLogThreadEnum_QueryInterface(This,riid,ppvObject) \
+ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) )
+
+#define ISOSStressLogThreadEnum_AddRef(This) \
+ ( (This)->lpVtbl -> AddRef(This) )
+
+#define ISOSStressLogThreadEnum_Release(This) \
+ ( (This)->lpVtbl -> Release(This) )
+
+
+#define ISOSStressLogThreadEnum_Skip(This,count) \
+ ( (This)->lpVtbl -> Skip(This,count) )
+
+#define ISOSStressLogThreadEnum_Reset(This) \
+ ( (This)->lpVtbl -> Reset(This) )
+
+#define ISOSStressLogThreadEnum_GetCount(This,pCount) \
+ ( (This)->lpVtbl -> GetCount(This,pCount) )
+
+
+#define ISOSStressLogThreadEnum_Next(This,count,values,pFetched) \
+ ( (This)->lpVtbl -> Next(This,count,values,pFetched) )
+
+#endif /* COBJMACROS */
+
+
+#endif /* C style interface */
+
+
+
+
+#endif /* __ISOSStressLogThreadEnum_INTERFACE_DEFINED__ */
+
+
+#ifndef __ISOSStressLogMsgEnum_INTERFACE_DEFINED__
+#define __ISOSStressLogMsgEnum_INTERFACE_DEFINED__
+
+/* interface ISOSStressLogMsgEnum */
+/* [uuid][local][object] */
+
+
+EXTERN_C const IID IID_ISOSStressLogMsgEnum;
+
+#if defined(__cplusplus) && !defined(CINTERFACE)
+
+ MIDL_INTERFACE("437cb033-afe7-4c0f-a4a7-82c891bc049e")
+ ISOSStressLogMsgEnum : public ISOSEnum
+ {
+ public:
+ virtual HRESULT STDMETHODCALLTYPE Next(
+ unsigned int count,
+ SOSStressMsgData values[],
+ unsigned int *pFetched) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE GetArguments(
+ unsigned int messageIndex,
+ unsigned int argCount,
+ CLRDATA_ADDRESS args[],
+ unsigned int *pFetched) = 0;
+
+ };
+
+
+#else /* C style interface */
+
+ typedef struct ISOSStressLogMsgEnumVtbl
+ {
+ BEGIN_INTERFACE
+
+ HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
+ ISOSStressLogMsgEnum * This,
+ /* [in] */ REFIID riid,
+ /* [annotation][iid_is][out] */
+ _COM_Outptr_ void **ppvObject);
+
+ ULONG ( STDMETHODCALLTYPE *AddRef )(
+ ISOSStressLogMsgEnum * This);
+
+ ULONG ( STDMETHODCALLTYPE *Release )(
+ ISOSStressLogMsgEnum * This);
+
+ HRESULT ( STDMETHODCALLTYPE *Skip )(
+ ISOSStressLogMsgEnum * This,
+ uint32_t count);
+
+ HRESULT ( STDMETHODCALLTYPE *Reset )(
+ ISOSStressLogMsgEnum * This);
+
+ HRESULT ( STDMETHODCALLTYPE *GetCount )(
+ ISOSStressLogMsgEnum * This,
+ uint32_t *pCount);
+
+ HRESULT ( STDMETHODCALLTYPE *Next )(
+ ISOSStressLogMsgEnum * This,
+ unsigned int count,
+ SOSStressMsgData values[],
+ unsigned int *pFetched);
+
+ HRESULT ( STDMETHODCALLTYPE *GetArguments )(
+ ISOSStressLogMsgEnum * This,
+ unsigned int messageIndex,
+ unsigned int argCount,
+ CLRDATA_ADDRESS args[],
+ unsigned int *pFetched);
+
+ END_INTERFACE
+ } ISOSStressLogMsgEnumVtbl;
+
+ interface ISOSStressLogMsgEnum
+ {
+ CONST_VTBL struct ISOSStressLogMsgEnumVtbl *lpVtbl;
+ };
+
+
+
+#ifdef COBJMACROS
+
+
+#define ISOSStressLogMsgEnum_QueryInterface(This,riid,ppvObject) \
+ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) )
+
+#define ISOSStressLogMsgEnum_AddRef(This) \
+ ( (This)->lpVtbl -> AddRef(This) )
+
+#define ISOSStressLogMsgEnum_Release(This) \
+ ( (This)->lpVtbl -> Release(This) )
+
+
+#define ISOSStressLogMsgEnum_Skip(This,count) \
+ ( (This)->lpVtbl -> Skip(This,count) )
+
+#define ISOSStressLogMsgEnum_Reset(This) \
+ ( (This)->lpVtbl -> Reset(This) )
+
+#define ISOSStressLogMsgEnum_GetCount(This,pCount) \
+ ( (This)->lpVtbl -> GetCount(This,pCount) )
+
+
+#define ISOSStressLogMsgEnum_Next(This,count,values,pFetched) \
+ ( (This)->lpVtbl -> Next(This,count,values,pFetched) )
+
+#define ISOSStressLogMsgEnum_GetArguments(This,messageIndex,argCount,args,pFetched) \
+ ( (This)->lpVtbl -> GetArguments(This,messageIndex,argCount,args,pFetched) )
+
+#endif /* COBJMACROS */
+
+
+#endif /* C style interface */
+
+
+
+
+#endif /* __ISOSStressLogMsgEnum_INTERFACE_DEFINED__ */
+
+
+#ifndef __ISOSDacInterface17_INTERFACE_DEFINED__
+#define __ISOSDacInterface17_INTERFACE_DEFINED__
+
+/* interface ISOSDacInterface17 */
+/* [uuid][local][object] */
+
+
+EXTERN_C const IID IID_ISOSDacInterface17;
+
+#if defined(__cplusplus) && !defined(CINTERFACE)
+
+ MIDL_INTERFACE("2f4bb585-ed50-479e-bbe0-10a95a5da3bb")
+ ISOSDacInterface17 : public IUnknown
+ {
+ public:
+ virtual HRESULT STDMETHODCALLTYPE GetStressLogData(
+ SOSStressLogData *data) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE GetStressLogThreadEnumerator(
+ ISOSStressLogThreadEnum **ppEnum) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE GetStressLogMessageEnumerator(
+ CLRDATA_ADDRESS threadStressLogAddress,
+ ISOSStressLogMsgEnum **ppEnum) = 0;
+
+ };
+
+
+#else /* C style interface */
+
+ typedef struct ISOSDacInterface17Vtbl
+ {
+ BEGIN_INTERFACE
+
+ HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
+ ISOSDacInterface17 * This,
+ /* [in] */ REFIID riid,
+ /* [annotation][iid_is][out] */
+ _COM_Outptr_ void **ppvObject);
+
+ ULONG ( STDMETHODCALLTYPE *AddRef )(
+ ISOSDacInterface17 * This);
+
+ ULONG ( STDMETHODCALLTYPE *Release )(
+ ISOSDacInterface17 * This);
+
+ HRESULT ( STDMETHODCALLTYPE *GetStressLogData )(
+ ISOSDacInterface17 * This,
+ SOSStressLogData *data);
+
+ HRESULT ( STDMETHODCALLTYPE *GetStressLogThreadEnumerator )(
+ ISOSDacInterface17 * This,
+ ISOSStressLogThreadEnum **ppEnum);
+
+ HRESULT ( STDMETHODCALLTYPE *GetStressLogMessageEnumerator )(
+ ISOSDacInterface17 * This,
+ CLRDATA_ADDRESS threadStressLogAddress,
+ ISOSStressLogMsgEnum **ppEnum);
+
+ END_INTERFACE
+ } ISOSDacInterface17Vtbl;
+
+ interface ISOSDacInterface17
+ {
+ CONST_VTBL struct ISOSDacInterface17Vtbl *lpVtbl;
+ };
+
+
+
+#ifdef COBJMACROS
+
+
+#define ISOSDacInterface17_QueryInterface(This,riid,ppvObject) \
+ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) )
+
+#define ISOSDacInterface17_AddRef(This) \
+ ( (This)->lpVtbl -> AddRef(This) )
+
+#define ISOSDacInterface17_Release(This) \
+ ( (This)->lpVtbl -> Release(This) )
+
+
+#define ISOSDacInterface17_GetStressLogData(This,data) \
+ ( (This)->lpVtbl -> GetStressLogData(This,data) )
+
+#define ISOSDacInterface17_GetStressLogThreadEnumerator(This,ppEnum) \
+ ( (This)->lpVtbl -> GetStressLogThreadEnumerator(This,ppEnum) )
+
+#define ISOSDacInterface17_GetStressLogMessageEnumerator(This,threadStressLogAddress,ppEnum) \
+ ( (This)->lpVtbl -> GetStressLogMessageEnumerator(This,threadStressLogAddress,ppEnum) )
+
+#endif /* COBJMACROS */
+
+
+#endif /* C style interface */
+
+
+
+
+#endif /* __ISOSDacInterface17_INTERFACE_DEFINED__ */
+
+
+#ifndef _SOS_GCInfoData
+#define _SOS_GCInfoData
+typedef struct _SOSCodeRange
+ {
+ unsigned int BeginOffset;
+ unsigned int EndOffset;
+ } SOSCodeRange;
+
+typedef struct _SOSGCInfoHeader
+ {
+ ULONG SizeOf;
+ unsigned int GcInfoVersion;
+ unsigned int CodeSize;
+ unsigned int PrologSize;
+ unsigned int StackBaseRegister;
+ unsigned int SizeOfStackParameterArea;
+ BOOL IsVarArg;
+ BOOL WantsReportOnlyLeaf;
+ BOOL HasTailCalls;
+ BOOL GSCookieIsPresent;
+ int GSCookieStackSlot;
+ unsigned int GSCookieValidRangeStart;
+ unsigned int GSCookieValidRangeEnd;
+ BOOL PSPSymIsPresent;
+ int PSPSymStackSlot;
+ BOOL GenericsInstContextIsPresent;
+ int GenericsInstContextStackSlot;
+ unsigned int GenericsInstContextKind;
+ } SOSGCInfoHeader;
+
+typedef struct _SOSGCSlotLifetime
+ {
+ unsigned int BeginOffset;
+ unsigned int EndOffset;
+ int IsRegister;
+ unsigned int RegisterNumber;
+ int SpOffset;
+ unsigned int BaseRegister;
+ unsigned int GcFlags;
+ } SOSGCSlotLifetime;
+
+#endif // _SOS_GCInfoData
+
+#ifndef __ISOSDacInterface18_INTERFACE_DEFINED__
+#define __ISOSDacInterface18_INTERFACE_DEFINED__
+
+/* interface ISOSDacInterface18 */
+/* [uuid][local][object] */
+
+
+EXTERN_C const IID IID_ISOSDacInterface18;
+
+#if defined(__cplusplus) && !defined(CINTERFACE)
+
+ MIDL_INTERFACE("3dccf95b-bca2-40ee-8b83-d8d7574a1df0")
+ ISOSDacInterface18 : public IUnknown
+ {
+ public:
+ virtual HRESULT STDMETHODCALLTYPE GetGCInfoHeader(
+ CLRDATA_ADDRESS ip,
+ SOSGCInfoHeader *header) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE GetGCInfoInterruptibleRanges(
+ CLRDATA_ADDRESS ip,
+ ULONG count,
+ SOSCodeRange *ranges,
+ ULONG *pNeeded) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE GetGCInfoSafePoints(
+ CLRDATA_ADDRESS ip,
+ ULONG count,
+ unsigned int *offsets,
+ ULONG *pNeeded) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE GetGCInfoSlotLifetimes(
+ CLRDATA_ADDRESS ip,
+ ULONG count,
+ SOSGCSlotLifetime *lifetimes,
+ ULONG *pNeeded) = 0;
+
+ };
+
+
+#else /* C style interface */
+
+ typedef struct ISOSDacInterface18Vtbl
+ {
+ BEGIN_INTERFACE
+
+ HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
+ ISOSDacInterface18 * This,
+ /* [in] */ REFIID riid,
+ /* [annotation][iid_is][out] */
+ _COM_Outptr_ void **ppvObject);
+
+ ULONG ( STDMETHODCALLTYPE *AddRef )(
+ ISOSDacInterface18 * This);
+
+ ULONG ( STDMETHODCALLTYPE *Release )(
+ ISOSDacInterface18 * This);
+
+ HRESULT ( STDMETHODCALLTYPE *GetGCInfoHeader )(
+ ISOSDacInterface18 * This,
+ CLRDATA_ADDRESS ip,
+ SOSGCInfoHeader *header);
+
+ HRESULT ( STDMETHODCALLTYPE *GetGCInfoInterruptibleRanges )(
+ ISOSDacInterface18 * This,
+ CLRDATA_ADDRESS ip,
+ ULONG count,
+ SOSCodeRange *ranges,
+ ULONG *pNeeded);
+
+ HRESULT ( STDMETHODCALLTYPE *GetGCInfoSafePoints )(
+ ISOSDacInterface18 * This,
+ CLRDATA_ADDRESS ip,
+ ULONG count,
+ unsigned int *offsets,
+ ULONG *pNeeded);
+
+ HRESULT ( STDMETHODCALLTYPE *GetGCInfoSlotLifetimes )(
+ ISOSDacInterface18 * This,
+ CLRDATA_ADDRESS ip,
+ ULONG count,
+ SOSGCSlotLifetime *lifetimes,
+ ULONG *pNeeded);
+
+ END_INTERFACE
+ } ISOSDacInterface18Vtbl;
+
+ interface ISOSDacInterface18
+ {
+ CONST_VTBL struct ISOSDacInterface18Vtbl *lpVtbl;
+ };
+
+
+
+#ifdef COBJMACROS
+
+
+#define ISOSDacInterface18_QueryInterface(This,riid,ppvObject) \
+ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) )
+
+#define ISOSDacInterface18_AddRef(This) \
+ ( (This)->lpVtbl -> AddRef(This) )
+
+#define ISOSDacInterface18_Release(This) \
+ ( (This)->lpVtbl -> Release(This) )
+
+
+#define ISOSDacInterface18_GetGCInfoHeader(This,ip,header) \
+ ( (This)->lpVtbl -> GetGCInfoHeader(This,ip,header) )
+
+#define ISOSDacInterface18_GetGCInfoInterruptibleRanges(This,ip,count,ranges,pNeeded) \
+ ( (This)->lpVtbl -> GetGCInfoInterruptibleRanges(This,ip,count,ranges,pNeeded) )
+
+#define ISOSDacInterface18_GetGCInfoSafePoints(This,ip,count,offsets,pNeeded) \
+ ( (This)->lpVtbl -> GetGCInfoSafePoints(This,ip,count,offsets,pNeeded) )
+
+#define ISOSDacInterface18_GetGCInfoSlotLifetimes(This,ip,count,lifetimes,pNeeded) \
+ ( (This)->lpVtbl -> GetGCInfoSlotLifetimes(This,ip,count,lifetimes,pNeeded) )
+
+#endif /* COBJMACROS */
+
+
+#endif /* C style interface */
+
+
+
+
+#endif /* __ISOSDacInterface18_INTERFACE_DEFINED__ */
+
+
/* Additional Prototypes for ALL interfaces */
/* end of Additional Prototypes */
@@ -3776,4 +4315,3 @@ EXTERN_C const IID IID_ISOSDacInterface16;
#endif
-
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGCInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGCInfo.cs
index 74e085dc62508c..82bf283b2265e8 100644
--- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGCInfo.cs
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGCInfo.cs
@@ -8,6 +8,52 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts;
public interface IGCInfoHandle { }
+/// Stack offset of a special GC info slot (GS cookie, PSP sym, or generics inst context).
+public readonly record struct SpecialSlot(int SpOffset);
+
+/// Kind of the generics instantiation context.
+public enum GenericsContextKind
+{
+ None = 0,
+ MethodDesc = 1,
+ MethodHandle = 2,
+ This = 3,
+}
+
+/// Header fields decoded from the GC info stream for a method.
+public readonly record struct GCInfoHeader(
+ uint Version,
+ uint CodeSize,
+ uint PrologSize,
+ uint StackBaseRegister,
+ uint SizeOfStackParameterArea,
+ bool IsVarArg,
+ bool WantsReportOnlyLeaf,
+ bool HasTailCalls,
+ SpecialSlot? GSCookie,
+ uint GSCookieValidRangeStart,
+ uint GSCookieValidRangeEnd,
+ SpecialSlot? PSPSym,
+ SpecialSlot? GenericsInstContext,
+ GenericsContextKind GenericsInstContextKind);
+
+/// Unified lifetime of a GC slot (register or stack).
+/// True if the slot is a CPU register; false if it is a stack location.
+/// Register number (meaningful only when IsRegister is true).
+/// Stack offset from the base (meaningful only when IsRegister is false).
+/// Stack base kind: 0 = CALLER_SP_REL, 1 = SP_REL, 2 = FRAMEREG_REL (meaningful only when IsRegister is false).
+/// GC slot flags: 0x1 = interior pointer, 0x2 = pinned, 0x4 = untracked.
+/// Code offset where the slot becomes live.
+/// Code offset where the slot becomes dead (exclusive).
+public readonly record struct GCSlotLifetime(
+ bool IsRegister,
+ uint RegisterNumber,
+ int SpOffset,
+ uint BaseRegister,
+ uint GcFlags,
+ uint BeginOffset,
+ uint EndOffset);
+
///
/// Describes a code region where the GC can safely interrupt execution.
///
@@ -49,11 +95,15 @@ public interface IGCInfo : IContract
IGCInfoHandle DecodePlatformSpecificGCInfo(TargetPointer gcInfoAddress, uint gcVersion) => throw new NotImplementedException();
IGCInfoHandle DecodeInterpreterGCInfo(TargetPointer gcInfoAddress, uint gcVersion) => throw new NotImplementedException();
+ GCInfoHeader GetHeader(IGCInfoHandle handle) => throw new NotImplementedException();
+
uint GetCodeLength(IGCInfoHandle handle) => throw new NotImplementedException();
- uint GetStackBaseRegister(IGCInfoHandle handle) => throw new NotImplementedException();
- uint GetSizeOfStackParameterArea(IGCInfoHandle handle) => throw new NotImplementedException();
uint GetCalleePoppedArgumentsSize(IGCInfoHandle handle) => throw new NotImplementedException();
+
IReadOnlyList GetInterruptibleRanges(IGCInfoHandle handle) => throw new NotImplementedException();
+ IReadOnlyList GetSafePoints(IGCInfoHandle handle) => throw new NotImplementedException();
+ IReadOnlyList GetSlotLifetimes(IGCInfoHandle handle) => throw new NotImplementedException();
+
IReadOnlyList EnumerateLiveSlots(IGCInfoHandle handle, uint instructionOffset, GcSlotEnumerationOptions options) => throw new NotImplementedException();
}
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs
index c35521b4475eb5..ede524c4cf2f73 100644
--- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs
@@ -34,7 +34,8 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer
return false;
Debug.Assert(codeStart.Value <= jittedCodeAddress.Value);
- TargetNUInt relativeOffset = new TargetNUInt(jittedCodeAddress.Value - codeStart.Value);
+ TargetPointer instrPointer = CodePointerUtils.AddressFromCodePointer(jittedCodeAddress, Target);
+ TargetNUInt relativeOffset = new TargetNUInt(instrPointer.Value - codeStart.Value);
if (!GetRealCodeHeader(rangeSection, codeStart, out Data.RealCodeHeader? realCodeHeader))
return false;
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.InterpreterJitManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.InterpreterJitManager.cs
index 54cf0b7fc83860..84b5cc4ba18c2f 100644
--- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.InterpreterJitManager.cs
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.InterpreterJitManager.cs
@@ -33,7 +33,8 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer
return false;
Debug.Assert(codeStart.Value <= jittedCodeAddress.Value);
- TargetNUInt relativeOffset = new TargetNUInt(jittedCodeAddress.Value - codeStart.Value);
+ TargetPointer instrPointer = CodePointerUtils.AddressFromCodePointer(jittedCodeAddress, Target);
+ TargetNUInt relativeOffset = new TargetNUInt(instrPointer.Value - codeStart.Value);
if (!GetInterpreterRealCodeHeader(codeStart, out Data.InterpreterRealCodeHeader? realCodeHeader))
return false;
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoDecoder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoDecoder.cs
index 8dcded7b994d17..69d042eaf3af7d 100644
--- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoDecoder.cs
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoDecoder.cs
@@ -129,7 +129,7 @@ public static GcSlotDesc CreateStackSlot(int spOffset, GcStackSlotBase slotBase,
private uint _numSafePoints;
private uint _numInterruptibleRanges;
private List _interruptibleRanges = [];
- private int _safePointBitOffset;
+ private List _safePoints = [];
/* Slot Table Fields */
private uint _numRegisters;
@@ -332,11 +332,15 @@ private IEnumerable DecodeInterruptibleRanges()
private IEnumerable DecodeSafePoints()
{
- // Save the position of the safe point data for FindSafePoint
- _safePointBitOffset = _bitOffset;
- // skip over safe point data
uint numBitsPerOffset = CeilOfLog2(TTraits.NormalizeCodeOffset(_codeLength));
- _bitOffset += (int)(numBitsPerOffset * _numSafePoints);
+
+ _safePoints = new List((int)_numSafePoints);
+ for (uint i = 0; i < _numSafePoints; i++)
+ {
+ uint offset = TTraits.DenormalizeCodeOffset((uint)_reader.ReadBits((int)numBitsPerOffset, ref _bitOffset));
+ _safePoints.Add(offset);
+ }
+
yield break;
}
@@ -506,28 +510,345 @@ private void EnsureDecodedTo(DecodePoints point)
#endregion
#region Access Methods
+ public GCInfoHeader GetHeader()
+ {
+ EnsureDecodedTo(DecodePoints.ReversePInvoke);
+
+ GcInfoHeaderFlags genericsInstContextFlags = _headerFlags & GcInfoHeaderFlags.GC_INFO_HAS_GENERICS_INST_CONTEXT_MASK;
+ SpecialSlot? gsCookie = _gsCookieStackSlot != TTraits.NO_GS_COOKIE ? new SpecialSlot(_gsCookieStackSlot) : null;
+ SpecialSlot? pspSym = _pspSymStackSlot != TTraits.NO_PSP_SYM ? new SpecialSlot(_pspSymStackSlot) : null;
+ SpecialSlot? genericsInstContext = _genericsInstContextStackSlot != TTraits.NO_GENERICS_INST_CONTEXT ? new SpecialSlot(_genericsInstContextStackSlot) : null;
+
+ GenericsContextKind genericsContextKind = genericsInstContextFlags switch
+ {
+ GcInfoHeaderFlags.GC_INFO_HAS_GENERICS_INST_CONTEXT_MD => GenericsContextKind.MethodDesc,
+ GcInfoHeaderFlags.GC_INFO_HAS_GENERICS_INST_CONTEXT_MT => GenericsContextKind.MethodHandle,
+ GcInfoHeaderFlags.GC_INFO_HAS_GENERICS_INST_CONTEXT_THIS => GenericsContextKind.This,
+ _ => GenericsContextKind.None,
+ };
+
+ bool flag80Set = _headerFlags.HasFlag(GcInfoHeaderFlags.GC_INFO_WANTS_REPORT_ONLY_LEAF);
+ bool wantsReportOnlyLeaf = _arch == RuntimeInfoArchitecture.X64 && _gcVersion < 4 ? flag80Set : true;
+ bool hasTailCalls = _arch is RuntimeInfoArchitecture.Arm or RuntimeInfoArchitecture.Arm64 or RuntimeInfoArchitecture.LoongArch64 or RuntimeInfoArchitecture.RiscV64
+ && flag80Set;
+
+ return new GCInfoHeader(
+ Version: _gcVersion,
+ CodeSize: _codeLength,
+ PrologSize: _validRangeStart,
+ StackBaseRegister: _stackBaseRegister,
+ SizeOfStackParameterArea: _fixedStackParameterScratchArea,
+ IsVarArg: _headerFlags.HasFlag(GcInfoHeaderFlags.GC_INFO_IS_VARARG),
+ WantsReportOnlyLeaf: wantsReportOnlyLeaf,
+ HasTailCalls: hasTailCalls,
+ GSCookie: gsCookie,
+ GSCookieValidRangeStart: gsCookie.HasValue ? _validRangeStart : 0,
+ GSCookieValidRangeEnd: gsCookie.HasValue ? _validRangeEnd : 0,
+ PSPSym: pspSym,
+ GenericsInstContext: genericsInstContext,
+ GenericsInstContextKind: genericsContextKind);
+ }
+
public uint GetCodeLength()
{
EnsureDecodedTo(DecodePoints.CodeLength);
return _codeLength;
}
- public uint GetStackBaseRegister()
+ public IReadOnlyList GetInterruptibleRanges()
{
- EnsureDecodedTo(DecodePoints.ReversePInvoke);
- return _stackBaseRegister;
+ EnsureDecodedTo(DecodePoints.InterruptibleRanges);
+ return _interruptibleRanges;
}
- public uint GetSizeOfStackParameterArea()
+ public IReadOnlyList GetSafePoints()
{
- EnsureDecodedTo(DecodePoints.ReversePInvoke);
- return _fixedStackParameterScratchArea;
+ EnsureDecodedTo(DecodePoints.InterruptibleRanges);
+ return _safePoints;
}
- public IReadOnlyList GetInterruptibleRanges()
+ public IReadOnlyList GetSlotLifetimes()
{
- EnsureDecodedTo(DecodePoints.InterruptibleRanges);
- return _interruptibleRanges;
+ EnsureDecodedTo(DecodePoints.SlotTable);
+
+ List lifetimes = [];
+ uint numTracked = NumTrackedSlots;
+
+ // Untracked slots are always live for the entire method
+ for (uint slotIndex = numTracked; slotIndex < _numSlots; slotIndex++)
+ {
+ GcSlotDesc slot = _slots[(int)slotIndex];
+ uint gcFlags = (uint)slot.Flags & ((uint)GcSlotFlags.GC_SLOT_INTERIOR | (uint)GcSlotFlags.GC_SLOT_PINNED | (uint)GcSlotFlags.GC_SLOT_UNTRACKED);
+ lifetimes.Add(new GCSlotLifetime(slot.IsRegister, slot.RegisterNumber, slot.SpOffset, (uint)slot.Base, gcFlags, 0, _codeLength));
+ }
+
+ if (numTracked == 0)
+ return lifetimes;
+
+ // For methods with only safe points (not fully interruptible),
+ // decode tracked slot lifetimes from the per-safe-point bitmaps.
+ if (_numInterruptibleRanges == 0)
+ {
+ if (_numSafePoints == 0)
+ return lifetimes;
+
+ int safePointBitPos = _liveStateBitOffset;
+
+ // Read indirect live state table header
+ uint numBitsPerOffset = 0;
+ if (_reader.ReadBits(1, ref safePointBitPos) != 0)
+ {
+ numBitsPerOffset = (uint)_reader.DecodeVarLengthUnsigned(TTraits.POINTER_SIZE_ENCBASE, ref safePointBitPos) + 1;
+ }
+
+ // For each safe point, read the live slot bitmap.
+ // Safe points are independent snapshots. We emit lifetimes spanning
+ // from the first safe point where a slot is live to the last consecutive
+ // safe point where it remains live.
+ IReadOnlyList safePoints = GetSafePoints();
+ uint[] liveStart = new uint[numTracked];
+ bool[] prevLive = new bool[numTracked];
+
+ for (uint sp = 0; sp < _numSafePoints; sp++)
+ {
+ bool[] curLive = new bool[numTracked];
+ int spBitOffset;
+
+ if (numBitsPerOffset != 0)
+ {
+ // Indirect table: read offset pointer for this safe point
+ int offsetTablePos = safePointBitPos;
+ int spOffsetBit = offsetTablePos + (int)(sp * numBitsPerOffset);
+ uint liveStatesOffset = (uint)_reader.ReadBits((int)numBitsPerOffset, ref spOffsetBit);
+ int liveStatesStart = (int)(((uint)offsetTablePos + _numSafePoints * numBitsPerOffset + 7) & (~7u));
+ spBitOffset = (int)(liveStatesStart + liveStatesOffset);
+
+ if (_reader.ReadBits(1, ref spBitOffset) != 0)
+ {
+ // RLE encoded
+ bool fSkip = _reader.ReadBits(1, ref spBitOffset) == 0;
+ bool fReport = true;
+ uint readSlots = (uint)_reader.DecodeVarLengthUnsigned(
+ fSkip ? TTraits.LIVESTATE_RLE_SKIP_ENCBASE : TTraits.LIVESTATE_RLE_RUN_ENCBASE, ref spBitOffset);
+ fSkip = !fSkip;
+ while (readSlots < numTracked)
+ {
+ uint cnt = (uint)_reader.DecodeVarLengthUnsigned(
+ fSkip ? TTraits.LIVESTATE_RLE_SKIP_ENCBASE : TTraits.LIVESTATE_RLE_RUN_ENCBASE, ref spBitOffset) + 1;
+ if (fReport)
+ {
+ for (uint si = readSlots; si < readSlots + cnt; si++)
+ curLive[si] = true;
+ }
+ readSlots += cnt;
+ fSkip = !fSkip;
+ fReport = !fReport;
+ }
+ }
+ else
+ {
+ for (uint si = 0; si < numTracked; si++)
+ curLive[si] = _reader.ReadBits(1, ref spBitOffset) != 0;
+ }
+ }
+ else
+ {
+ // Direct bitmap: numTracked bits per safe point
+ spBitOffset = safePointBitPos + (int)(sp * numTracked);
+ for (uint si = 0; si < numTracked; si++)
+ curLive[si] = _reader.ReadBits(1, ref spBitOffset) != 0;
+ }
+
+ uint spOffset = safePoints[(int)sp];
+
+ for (uint si = 0; si < numTracked; si++)
+ {
+ if (curLive[si] && !prevLive[si])
+ {
+ // Slot became live at this safe point
+ liveStart[si] = spOffset;
+ }
+ else if (!curLive[si] && prevLive[si])
+ {
+ // Slot is dead -- emit lifetime ending after previous safe point
+ EmitSlotLifetime(si, liveStart[si], safePoints[(int)(sp - 1)] + 1, lifetimes);
+ }
+ }
+
+ Array.Copy(curLive, prevLive, numTracked);
+ }
+
+ // Close any slots still live at the last safe point
+ uint lastSpOffset = safePoints[(int)(_numSafePoints - 1)];
+ for (uint si = 0; si < numTracked; si++)
+ {
+ if (prevLive[si])
+ EmitSlotLifetime(si, liveStart[si], lastSpOffset + 1, lifetimes);
+ }
+
+ return lifetimes;
+ }
+
+ int bitOffset = _liveStateBitOffset;
+
+ // Skip indirect live state table header and safe point data
+ if (_numSafePoints > 0 && _reader.ReadBits(1, ref bitOffset) != 0)
+ {
+ _reader.DecodeVarLengthUnsigned(TTraits.POINTER_SIZE_ENCBASE, ref bitOffset);
+ }
+ bitOffset += (int)(_numSafePoints * numTracked);
+
+ // Compute total interruptible length
+ uint numInterruptibleLength = 0;
+ for (int i = 0; i < _interruptibleRanges.Count; i++)
+ {
+ uint normStart = TTraits.NormalizeCodeOffset(_interruptibleRanges[i].StartOffset);
+ uint normStop = TTraits.NormalizeCodeOffset(_interruptibleRanges[i].EndOffset);
+ numInterruptibleLength += normStop - normStart;
+ }
+
+ uint numChunks = (numInterruptibleLength + TTraits.NUM_NORM_CODE_OFFSETS_PER_CHUNK - 1) / TTraits.NUM_NORM_CODE_OFFSETS_PER_CHUNK;
+ uint numBitsPerPointer = (uint)_reader.DecodeVarLengthUnsigned(TTraits.POINTER_SIZE_ENCBASE, ref bitOffset);
+ if (numBitsPerPointer == 0)
+ return lifetimes;
+
+ int pointerTablePos = bitOffset;
+ int chunksStartPos = (int)(((uint)pointerTablePos + numChunks * numBitsPerPointer + 7) & (~7u));
+
+ // Track per-slot live state across chunks: slotIndex -> beginCodeOffset
+ Dictionary activeSlots = [];
+
+ for (uint chunk = 0; chunk < numChunks; chunk++)
+ {
+ bitOffset = pointerTablePos + (int)(chunk * numBitsPerPointer);
+ uint chunkPointer = (uint)_reader.ReadBits((int)numBitsPerPointer, ref bitOffset);
+ if (chunkPointer == 0)
+ continue;
+
+ int chunkPos = (int)(chunksStartPos + chunkPointer - 1);
+ bitOffset = chunkPos;
+
+ // Read couldBeLive bitvector
+ List couldBeLiveSlots = ReadCouldBeLiveSlots(ref bitOffset, numTracked);
+
+ int finalStateBitOffset = bitOffset;
+ int transitionBitOffset = bitOffset + couldBeLiveSlots.Count;
+
+ uint chunkStartNormOffset = chunk * TTraits.NUM_NORM_CODE_OFFSETS_PER_CHUNK;
+
+ for (int i = 0; i < couldBeLiveSlots.Count; i++)
+ {
+ uint slotIndex = couldBeLiveSlots[i];
+ uint finalState = (uint)_reader.ReadBits(1, ref finalStateBitOffset);
+
+ // Collect transitions within this chunk
+ List transitions = [];
+ while (_reader.ReadBits(1, ref transitionBitOffset) != 0)
+ {
+ uint transOffset = (uint)_reader.ReadBits(TTraits.NUM_NORM_CODE_OFFSETS_PER_CHUNK_LOG2, ref transitionBitOffset);
+ transitions.Add(transOffset);
+ }
+
+ // Initial state = finalState XOR (transitions.Count % 2)
+ uint currentState = finalState ^ (uint)(transitions.Count % 2);
+
+ // Walk transitions and track lifetime boundaries
+ uint prevNormOffset = chunkStartNormOffset;
+ foreach (uint transOffset in transitions)
+ {
+ uint absNormOffset = chunkStartNormOffset + transOffset;
+ if (currentState != 0 && !activeSlots.ContainsKey(slotIndex))
+ {
+ // Slot is live and we haven't recorded its start yet
+ activeSlots[slotIndex] = DenormInterruptibleOffset(prevNormOffset);
+ }
+ else if (currentState == 0 && activeSlots.TryGetValue(slotIndex, out uint beginOffset))
+ {
+ // Slot just died
+ EmitSlotLifetime(slotIndex, beginOffset, DenormInterruptibleOffset(absNormOffset), lifetimes);
+ activeSlots.Remove(slotIndex);
+ }
+ currentState ^= 1;
+ prevNormOffset = absNormOffset;
+ }
+
+ // End of chunk state
+ if (currentState != 0 && !activeSlots.ContainsKey(slotIndex))
+ activeSlots[slotIndex] = DenormInterruptibleOffset(prevNormOffset);
+ else if (currentState == 0 && activeSlots.TryGetValue(slotIndex, out uint begin))
+ {
+ EmitSlotLifetime(slotIndex, begin, DenormInterruptibleOffset(prevNormOffset), lifetimes);
+ activeSlots.Remove(slotIndex);
+ }
+ }
+ }
+
+ // Close any remaining open lifetimes
+ foreach ((uint slotIndex, uint beginOffset) in activeSlots)
+ EmitSlotLifetime(slotIndex, beginOffset, _codeLength, lifetimes);
+
+ return lifetimes;
+ }
+
+ private List ReadCouldBeLiveSlots(ref int bitOffset, uint numTracked)
+ {
+ List slots = [];
+ if (_reader.ReadBits(1, ref bitOffset) != 0)
+ {
+ // RLE encoded
+ bool fSkip = _reader.ReadBits(1, ref bitOffset) == 0;
+ bool fReport = true;
+ uint readSlots = (uint)_reader.DecodeVarLengthUnsigned(
+ fSkip ? TTraits.LIVESTATE_RLE_SKIP_ENCBASE : TTraits.LIVESTATE_RLE_RUN_ENCBASE, ref bitOffset);
+ fSkip = !fSkip;
+ while (readSlots < numTracked)
+ {
+ uint cnt = (uint)_reader.DecodeVarLengthUnsigned(
+ fSkip ? TTraits.LIVESTATE_RLE_SKIP_ENCBASE : TTraits.LIVESTATE_RLE_RUN_ENCBASE, ref bitOffset) + 1;
+ if (fReport)
+ {
+ for (uint j = 0; j < cnt; j++)
+ slots.Add(readSlots + j);
+ }
+ readSlots += cnt;
+ fSkip = !fSkip;
+ fReport = !fReport;
+ }
+ }
+ else
+ {
+ for (uint i = 0; i < numTracked; i++)
+ {
+ if (_reader.ReadBits(1, ref bitOffset) != 0)
+ slots.Add(i);
+ }
+ }
+ return slots;
+ }
+
+ private uint DenormInterruptibleOffset(uint normOffset)
+ {
+ uint accumulated = 0;
+ for (int i = 0; i < _interruptibleRanges.Count; i++)
+ {
+ uint normStart = TTraits.NormalizeCodeOffset(_interruptibleRanges[i].StartOffset);
+ uint normStop = TTraits.NormalizeCodeOffset(_interruptibleRanges[i].EndOffset);
+ uint rangeLen = normStop - normStart;
+ if (normOffset < accumulated + rangeLen)
+ {
+ uint delta = normOffset - accumulated;
+ return TTraits.DenormalizeCodeOffset(normStart + delta);
+ }
+ accumulated += rangeLen;
+ }
+ return _codeLength;
+ }
+
+ private void EmitSlotLifetime(uint slotIndex, uint beginOffset, uint endOffset, List lifetimes)
+ {
+ GcSlotDesc slot = _slots[(int)slotIndex];
+ uint gcFlags = (uint)slot.Flags & ((uint)GcSlotFlags.GC_SLOT_INTERIOR | (uint)GcSlotFlags.GC_SLOT_PINNED);
+ lifetimes.Add(new GCSlotLifetime(slot.IsRegister, slot.RegisterNumber, slot.SpOffset, (uint)slot.Base, gcFlags, beginOffset, endOffset));
}
public uint NumTrackedSlots => _numSlots - _numUntrackedSlots;
@@ -859,19 +1180,14 @@ private uint FindSafePoint(uint codeOffset)
{
EnsureDecodedTo(DecodePoints.InterruptibleRanges);
- uint normBreakOffset = TTraits.NormalizeCodeOffset(codeOffset);
- uint numBitsPerOffset = CeilOfLog2(TTraits.NormalizeCodeOffset(_codeLength));
-
// TODO(stackref): The native FindSafePoint uses binary search (NarrowSafePointSearch)
// when numSafePoints > 32. This is a performance optimization only — no correctness impact.
// Linear scan through safe point offsets from the saved position
- int scanOffset = _safePointBitOffset;
- for (uint i = 0; i < _numSafePoints; i++)
+ for (int i = 0; i < _safePoints.Count; i++)
{
- uint spOffset = (uint)_reader.ReadBits((int)numBitsPerOffset, ref scanOffset);
- if (spOffset == normBreakOffset)
- return i;
- if (spOffset > normBreakOffset)
+ if (_safePoints[i] == codeOffset)
+ return (uint)i;
+ if (_safePoints[i] > codeOffset)
break;
}
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoX86_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoX86_1.cs
index fa2d810c765fdd..d52b40c9299929 100644
--- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoX86_1.cs
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoX86_1.cs
@@ -28,21 +28,24 @@ IGCInfoHandle IGCInfo.DecodePlatformSpecificGCInfo(TargetPointer gcInfoAddress,
IGCInfoHandle IGCInfo.DecodeInterpreterGCInfo(TargetPointer gcInfoAddress, uint gcVersion)
=> new GcInfoDecoder(_target, gcInfoAddress, gcVersion);
+ GCInfoHeader IGCInfo.GetHeader(IGCInfoHandle gcInfoHandle)
+ => AssertCorrectHandle(gcInfoHandle).GetHeader();
+
uint IGCInfo.GetCodeLength(IGCInfoHandle gcInfoHandle)
=> AssertCorrectHandle(gcInfoHandle).GetCodeLength();
- uint IGCInfo.GetStackBaseRegister(IGCInfoHandle gcInfoHandle)
- => AssertCorrectHandle(gcInfoHandle).GetStackBaseRegister();
-
- uint IGCInfo.GetSizeOfStackParameterArea(IGCInfoHandle gcInfoHandle)
- => AssertCorrectHandle(gcInfoHandle).GetSizeOfStackParameterArea();
-
uint IGCInfo.GetCalleePoppedArgumentsSize(IGCInfoHandle gcInfoHandle)
=> AssertCorrectHandle(gcInfoHandle).GetCalleePoppedArgumentsSize();
IReadOnlyList IGCInfo.GetInterruptibleRanges(IGCInfoHandle gcInfoHandle)
=> AssertCorrectHandle(gcInfoHandle).GetInterruptibleRanges();
+ IReadOnlyList IGCInfo.GetSafePoints(IGCInfoHandle gcInfoHandle)
+ => AssertCorrectHandle(gcInfoHandle).GetSafePoints();
+
+ IReadOnlyList IGCInfo.GetSlotLifetimes(IGCInfoHandle gcInfoHandle)
+ => AssertCorrectHandle(gcInfoHandle).GetSlotLifetimes();
+
IReadOnlyList IGCInfo.EnumerateLiveSlots(IGCInfoHandle gcInfoHandle, uint instructionOffset, GcSlotEnumerationOptions options)
=> AssertCorrectHandle(gcInfoHandle).EnumerateLiveSlots(instructionOffset, options);
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfo_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfo_1.cs
index d13e0f6051e98d..9b02413462d043 100644
--- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfo_1.cs
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfo_1.cs
@@ -22,34 +22,40 @@ IGCInfoHandle IGCInfo.DecodePlatformSpecificGCInfo(TargetPointer gcInfoAddress,
IGCInfoHandle IGCInfo.DecodeInterpreterGCInfo(TargetPointer gcInfoAddress, uint gcVersion)
=> new GcInfoDecoder(_target, gcInfoAddress, gcVersion);
+ GCInfoHeader IGCInfo.GetHeader(IGCInfoHandle gcInfoHandle)
+ {
+ IGCInfoDecoder handle = AssertCorrectHandle(gcInfoHandle);
+ return handle.GetHeader();
+ }
+
uint IGCInfo.GetCodeLength(IGCInfoHandle gcInfoHandle)
{
IGCInfoDecoder handle = AssertCorrectHandle(gcInfoHandle);
return handle.GetCodeLength();
}
- uint IGCInfo.GetStackBaseRegister(IGCInfoHandle gcInfoHandle)
+ uint IGCInfo.GetCalleePoppedArgumentsSize(IGCInfoHandle gcInfoHandle)
{
IGCInfoDecoder handle = AssertCorrectHandle(gcInfoHandle);
- return handle.GetStackBaseRegister();
+ return handle.GetCalleePoppedArgumentsSize();
}
- uint IGCInfo.GetSizeOfStackParameterArea(IGCInfoHandle gcInfoHandle)
+ IReadOnlyList IGCInfo.GetInterruptibleRanges(IGCInfoHandle gcInfoHandle)
{
IGCInfoDecoder handle = AssertCorrectHandle(gcInfoHandle);
- return handle.GetSizeOfStackParameterArea();
+ return handle.GetInterruptibleRanges();
}
- uint IGCInfo.GetCalleePoppedArgumentsSize(IGCInfoHandle gcInfoHandle)
+ IReadOnlyList IGCInfo.GetSafePoints(IGCInfoHandle gcInfoHandle)
{
IGCInfoDecoder handle = AssertCorrectHandle(gcInfoHandle);
- return handle.GetCalleePoppedArgumentsSize();
+ return handle.GetSafePoints();
}
- IReadOnlyList IGCInfo.GetInterruptibleRanges(IGCInfoHandle gcInfoHandle)
+ IReadOnlyList IGCInfo.GetSlotLifetimes(IGCInfoHandle gcInfoHandle)
{
IGCInfoDecoder handle = AssertCorrectHandle(gcInfoHandle);
- return handle.GetInterruptibleRanges();
+ return handle.GetSlotLifetimes();
}
IReadOnlyList IGCInfo.EnumerateLiveSlots(IGCInfoHandle gcInfoHandle, uint instructionOffset, GcSlotEnumerationOptions options)
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/IGCInfoDecoder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/IGCInfoDecoder.cs
index 279992fb4cd0dc..e76107601f1633 100644
--- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/IGCInfoDecoder.cs
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/IGCInfoDecoder.cs
@@ -9,14 +9,17 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.GCInfoHelpers;
internal interface IGCInfoDecoder : IGCInfoHandle
{
+ GCInfoHeader GetHeader();
+
uint GetCodeLength();
- uint GetStackBaseRegister();
- uint GetSizeOfStackParameterArea();
// Default 0; mirrors native EECodeManager::GetStackParameterSize, which only
// returns non-zero on x86 (where managed code uses __stdcall, callee-popped args).
uint GetCalleePoppedArgumentsSize() => 0;
IReadOnlyList GetInterruptibleRanges();
+ IReadOnlyList GetSafePoints();
+ IReadOnlyList GetSlotLifetimes();
+
IReadOnlyList EnumerateLiveSlots(uint instructionOffset, GcSlotEnumerationOptions options);
}
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs
index 568469664ce059..7e78ab1040832d 100644
--- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs
@@ -36,6 +36,7 @@ public record X86GCInfo : IGCInfoDecoder
private readonly TargetPointer _gcInfoAddress;
private readonly uint _infoHdrSize;
+ private readonly uint _gcInfoVersion;
public uint RelativeOffset { get; set; }
public uint MethodSize { get; set; }
@@ -101,6 +102,7 @@ public X86GCInfo(Target target, TargetPointer gcInfoAddress, uint gcInfoVersion,
}
_target = target;
+ _gcInfoVersion = gcInfoVersion;
_gcInfoAddress = gcInfoAddress;
TargetPointer offset = gcInfoAddress;
@@ -421,25 +423,6 @@ private static uint RegMaskToRegisterNumber(RegMask reg)
uint IGCInfoDecoder.GetCodeLength() => MethodSize;
- uint IGCInfoDecoder.GetStackBaseRegister()
- {
- // x86 ModRM register encoding: ESP = 4, EBP = 5. EBP is the stack base for
- // EBP-frames and double-aligned frames; otherwise stack base is ESP.
- const uint REG_ESP = 4;
- const uint REG_EBP = 5;
- return (Header.EbpFrame || Header.DoubleAlign) ? REG_EBP : REG_ESP;
- }
-
- uint IGCInfoDecoder.GetSizeOfStackParameterArea()
- {
- // x86 GC info does not encode a separate outgoing-argument scratch area; the
- // per-offset transitions report pushed argument pointers directly at each offset.
- // Returning 0 disables the GcScanner's scratch-area filter on x86, which is the
- // correct behaviour: the live state at a given offset (call site or fully-interruptible
- // point) already excludes any args that have been popped by the time we resume there.
- return 0;
- }
-
uint IGCInfoDecoder.GetCalleePoppedArgumentsSize()
{
// Mirrors native ::GetStackParameterSize(hdrInfo) in gc_unwind_x86.inl: varargs are
@@ -821,6 +804,179 @@ private static IEnumerable EnumerateSingleRegs()
yield return RegMask.EDI;
// ESP is intentionally excluded -- it's never a live GC ref holder.
}
+
+ GCInfoHeader IGCInfoDecoder.GetHeader()
+ {
+ // x86 GCInfo tracks whether a generics context exists and its kind,
+ // but does not encode the stack slot offset in the header.
+ GenericsContextKind genericsKind = Header.GenericsContext
+ ? (Header.GenericsContextIsMethodDesc ? GenericsContextKind.MethodDesc : GenericsContextKind.MethodHandle)
+ : GenericsContextKind.None;
+
+ SpecialSlot? gsCookie = Header.GsCookieOffset != 0
+ ? new SpecialSlot((int)Header.GsCookieOffset)
+ : null;
+
+ return new GCInfoHeader(
+ Version: _gcInfoVersion,
+ CodeSize: MethodSize,
+ PrologSize: Header.PrologSize,
+ StackBaseRegister: RegMaskToRegisterNumber((Header.EbpFrame || Header.DoubleAlign) ? RegMask.EBP : RegMask.ESP),
+ SizeOfStackParameterArea: 0, // x86 doesn't encode an outgoing scratch area
+ IsVarArg: Header.VarArgs,
+ WantsReportOnlyLeaf: true,
+ HasTailCalls: false,
+ GSCookie: gsCookie,
+ GSCookieValidRangeStart: gsCookie.HasValue ? (uint)Header.PrologSize : 0,
+ GSCookieValidRangeEnd: gsCookie.HasValue ? MethodSize : 0,
+ PSPSym: null,
+ GenericsInstContext: null,
+ GenericsInstContextKind: genericsKind);
+ }
+
+ IReadOnlyList IGCInfoDecoder.GetSafePoints()
+ {
+ if (Header.Interruptible)
+ return [];
+
+ List safePoints = [];
+ foreach (int offset in SortedTransitionOffsets)
+ {
+ if ((uint)offset < Header.PrologSize)
+ continue;
+ foreach (BaseGcTransition transition in Transitions[offset])
+ {
+ if (transition is GcTransitionCall)
+ {
+ safePoints.Add((uint)offset);
+ break;
+ }
+ }
+ }
+ return safePoints;
+ }
+
+ IReadOnlyList IGCInfoDecoder.GetSlotLifetimes()
+ {
+ List lifetimes = [];
+
+ foreach (UntrackedSlot slot in UntrackedSlots)
+ {
+ uint gcFlags = (slot.LowBits & 0x1) != 0 ? 0x1u : 0u;
+ if ((slot.LowBits & 0x2) != 0)
+ gcFlags |= 0x2u;
+ gcFlags |= 0x4u;
+
+ uint baseReg = slot.IsEbpRelative ? 2u : 1u;
+ lifetimes.Add(new GCSlotLifetime(false, 0, slot.StackOffset, baseReg, gcFlags, 0, MethodSize));
+ }
+
+ foreach (VarPtrLifetime varPtr in VarPtrLifetimes)
+ {
+ uint gcFlags = (varPtr.LowBits & 0x1) != 0 ? 0x1u : 0u;
+ if ((varPtr.LowBits & 0x2) != 0)
+ gcFlags |= 0x2u;
+
+ lifetimes.Add(new GCSlotLifetime(false, 0, varPtr.StackOffset, 2u, gcFlags, varPtr.BeginOffset, varPtr.EndOffset));
+ }
+
+ // 3. Walk transitions for register lifetimes and pushed pointer arg lifetimes
+ Dictionary activeRegs = [];
+ int depthSlots = 0;
+ SortedDictionary activePushedPtrs = [];
+
+ foreach (int offset in SortedTransitionOffsets)
+ {
+ foreach (BaseGcTransition transition in Transitions[offset])
+ {
+ switch (transition)
+ {
+ case GcTransitionRegister regTransition:
+ if (regTransition.IsLive == Action.LIVE)
+ {
+ activeRegs.TryAdd(regTransition.Register, (uint)offset);
+ }
+ else if (regTransition.IsLive == Action.DEAD)
+ {
+ if (activeRegs.TryGetValue(regTransition.Register, out uint beginOffset))
+ {
+ uint gcFlags = regTransition.Iptr ? 0x1u : 0u;
+ lifetimes.Add(new GCSlotLifetime(true, RegMaskToRegNum(regTransition.Register), 0, 0, gcFlags, beginOffset, (uint)offset));
+ activeRegs.Remove(regTransition.Register);
+ }
+ }
+ else if (regTransition.IsLive == Action.PUSH)
+ {
+ depthSlots += regTransition.PushCountOrPopSize;
+ }
+ else if (regTransition.IsLive == Action.POP)
+ {
+ depthSlots -= regTransition.PushCountOrPopSize;
+ }
+ break;
+ case GcTransitionPointer ptrT:
+ switch (ptrT.Act)
+ {
+ case Action.PUSH:
+ if (ptrT.IsPtr)
+ activePushedPtrs[depthSlots] = ((uint)offset, ptrT.Iptr ? 0x1u : 0u);
+ depthSlots++;
+ break;
+ case Action.POP:
+ for (uint i = 0; i < ptrT.ArgOffset && depthSlots > 0; i++)
+ {
+ depthSlots--;
+ if (activePushedPtrs.TryGetValue(depthSlots, out var pushed))
+ {
+ int spOffset = depthSlots * (int)_target.PointerSize;
+ lifetimes.Add(new GCSlotLifetime(false, 0, spOffset, 1u, pushed.GcFlags, pushed.PushOffset, (uint)offset));
+ activePushedPtrs.Remove(depthSlots);
+ }
+ }
+ break;
+ case Action.KILL:
+ foreach ((int idx, (uint pushOff, uint gf)) in activePushedPtrs)
+ {
+ int spOffset = idx * (int)_target.PointerSize;
+ lifetimes.Add(new GCSlotLifetime(false, 0, spOffset, 1u, gf, pushOff, (uint)offset));
+ }
+ activePushedPtrs.Clear();
+ depthSlots = 0;
+ break;
+ }
+ break;
+ case StackDepthTransition stackT:
+ depthSlots += stackT.StackDepthChange;
+ if (depthSlots < 0) depthSlots = 0;
+ break;
+ }
+ }
+ }
+
+ foreach ((RegMask reg, uint beginOffset) in activeRegs)
+ lifetimes.Add(new GCSlotLifetime(true, RegMaskToRegNum(reg), 0, 0, 0, beginOffset, MethodSize));
+
+ foreach ((int idx, (uint pushOff, uint gf)) in activePushedPtrs)
+ {
+ int spOffset = idx * (int)_target.PointerSize;
+ lifetimes.Add(new GCSlotLifetime(false, 0, spOffset, 1u, gf, pushOff, MethodSize));
+ }
+
+ return lifetimes;
+ }
+
+ private static uint RegMaskToRegNum(RegMask reg) => reg switch
+ {
+ RegMask.EAX => 0,
+ RegMask.ECX => 1,
+ RegMask.EDX => 2,
+ RegMask.EBX => 3,
+ RegMask.ESP => 4,
+ RegMask.EBP => 5,
+ RegMask.ESI => 6,
+ RegMask.EDI => 7,
+ _ => 0,
+ };
}
///
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs
index d62d01e68bc1d4..935d898e6520d5 100644
--- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs
@@ -45,8 +45,9 @@ public void EnumGcRefsForManagedFrame(
IGCInfoHandle handle = _gcInfo.DecodePlatformSpecificGCInfo(gcInfoAddr, gcVersion);
- uint stackBaseRegister = _gcInfo.GetStackBaseRegister(handle);
- uint scratchAreaSize = _gcInfo.GetSizeOfStackParameterArea(handle);
+ GCInfoHeader header = _gcInfo.GetHeader(handle);
+ uint stackBaseRegister = header.StackBaseRegister;
+ uint scratchAreaSize = header.SizeOfStackParameterArea;
bool filterScratchStackSlots = !options.IsActiveFrame;
TargetPointer? callerSP = null;
uint offsetToUse = relOffsetOverride ?? (uint)relativeOffset.Value;
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs
index 6cafe023802c0e..dd2ce0fbbacb3c 100644
--- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs
@@ -1271,3 +1271,76 @@ int GetStressLogMemoryRanges(
DacComNullableByRef ppEnum);
}
+
+// GCInfo data structures for ISOSDacInterface18
+
+[StructLayout(LayoutKind.Sequential)]
+public struct SOSCodeRange
+{
+ public uint BeginOffset;
+ public uint EndOffset;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public struct SOSGCInfoHeader
+{
+ public uint SizeOf;
+ public uint GcInfoVersion;
+ public uint CodeSize;
+ public uint PrologSize;
+ public uint StackBaseRegister;
+ public uint SizeOfStackParameterArea;
+ public int IsVarArg;
+ public int WantsReportOnlyLeaf;
+ public int HasTailCalls;
+ public int GSCookieIsPresent;
+ public int GSCookieStackSlot;
+ public uint GSCookieValidRangeStart;
+ public uint GSCookieValidRangeEnd;
+ public int PSPSymIsPresent;
+ public int PSPSymStackSlot;
+ public int GenericsInstContextIsPresent;
+ public int GenericsInstContextStackSlot;
+ public uint GenericsInstContextKind;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public struct SOSGCSlotLifetime
+{
+ public uint BeginOffset;
+ public uint EndOffset;
+ public int IsRegister;
+ public uint RegisterNumber;
+ public int SpOffset;
+ public uint BaseRegister;
+ public uint GcFlags;
+}
+
+[GeneratedComInterface]
+[Guid("3dccf95b-bca2-40ee-8b83-d8d7574a1df0")]
+public unsafe partial interface ISOSDacInterface18
+{
+ [PreserveSig]
+ int GetGCInfoHeader(ClrDataAddress ip, SOSGCInfoHeader* header);
+
+ [PreserveSig]
+ int GetGCInfoInterruptibleRanges(
+ ClrDataAddress ip,
+ uint count,
+ [In, Out, MarshalUsing(CountElementName = nameof(count))] SOSCodeRange[]? ranges,
+ uint* pNeeded);
+
+ [PreserveSig]
+ int GetGCInfoSafePoints(
+ ClrDataAddress ip,
+ uint count,
+ [In, Out, MarshalUsing(CountElementName = nameof(count))] uint[]? offsets,
+ uint* pNeeded);
+
+ [PreserveSig]
+ int GetGCInfoSlotLifetimes(
+ ClrDataAddress ip,
+ uint count,
+ [In, Out, MarshalUsing(CountElementName = nameof(count))] SOSGCSlotLifetime[]? lifetimes,
+ uint* pNeeded);
+}
diff --git a/src/native/managed/cdac/tests/DumpTests/StackReferenceDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/StackReferenceDumpTests.cs
index b50da0526c7d4c..2f9452d782813f 100644
--- a/src/native/managed/cdac/tests/DumpTests/StackReferenceDumpTests.cs
+++ b/src/native/managed/cdac/tests/DumpTests/StackReferenceDumpTests.cs
@@ -108,7 +108,6 @@ public void GCRoots_RefsPointToValidObjects(TestConfiguration config)
[ConditionalTheory]
[MemberData(nameof(TestConfigurations))]
[SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")]
- [SkipOnArch("x86", "GCInfo decoder does not support x86")]
public void NestedException_InFlightExceptionsReportedAsRoots(TestConfiguration config)
{
InitializeDumpTest(config, "NestedException", "full");
@@ -160,7 +159,6 @@ public void NestedException_InFlightExceptionsReportedAsRoots(TestConfiguration
[ConditionalTheory]
[MemberData(nameof(TestConfigurations))]
[SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")]
- [SkipOnArch("x86", "GCInfo decoder does not support x86")]
public void GCProtect_GCFrameRootsAreReported(TestConfiguration config)
{
InitializeDumpTest(config, "GCProtect", "full");