From 930ed9193c16b1aa305c8682be5ffc60ea544570 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 6 Jul 2026 16:31:54 -0400 Subject: [PATCH 1/4] Add ISOSDacInterface18 for GCInfo inspection APIs Add ISOSDacInterface18 with 4 COM methods exposing GC info data through the cDAC for the SOS !GCInfo command: - GetGCInfoHeader: header fields (version, code size, prolog, stack base register, GS cookie, PSP sym, generics context) - GetGCInfoInterruptibleRanges: code regions where GC can interrupt - GetGCInfoSafePoints: specific offsets with point-in-time liveness - GetGCInfoSlotLifetimes: unified slot lifetime ranges combining register and stack slots via SOSGCSlotLifetime with IsRegister IGCInfo contract changes: - Add GetHeader, GetSafePoints, GetSlotLifetimes to IGCInfo/IGCInfoDecoder - Remove GetStackBaseRegister/GetSizeOfStackParameterArea (use GetHeader) - Eagerly decode safe points in DecodeSafePoints (stored denormalized) - Single-pass chunk decoder for interruptible slot lifetimes - Safe-point-only methods emit per-safe-point lifetime entries - Implement x86 GCInfo support (GetHeader/GetSafePoints/GetSlotLifetimes) - Add GenericsContextKind.None as default value - Fix GSCookieValidRange: only set when GSCookie is present - Fix ARM thumb bit in EEJitManager/InterpreterJitManager relative offset Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/inc/sospriv.idl | 79 +++ src/coreclr/pal/prebuilt/inc/sospriv.h | 540 +++++++++++++++++- .../Contracts/IGCInfo.cs | 54 +- .../ExecutionManagerCore.EEJitManager.cs | 3 +- ...cutionManagerCore.InterpreterJitManager.cs | 3 +- .../Contracts/GCInfo/GCInfoDecoder.cs | 354 +++++++++++- .../Contracts/GCInfo/GCInfoX86_1.cs | 6 - .../Contracts/GCInfo/GCInfo_1.cs | 22 +- .../Contracts/GCInfo/IGCInfoDecoder.cs | 7 +- .../Contracts/GCInfo/X86/GCInfo.cs | 140 ++++- .../Contracts/StackWalk/GC/GcScanner.cs | 5 +- .../ISOSDacInterface.cs | 73 +++ 12 files changed, 1228 insertions(+), 58 deletions(-) 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..eb2ac2ec56885f 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 register (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..bb74f5dc4ed0fe 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,6 +510,45 @@ 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); @@ -530,6 +573,296 @@ public IReadOnlyList GetInterruptibleRanges() return _interruptibleRanges; } + public IReadOnlyList GetSafePoints() + { + EnsureDecodedTo(DecodePoints.InterruptibleRanges); + return _safePoints; + } + + public IReadOnlyList GetSlotLifetimes() + { + 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; IReadOnlyList IGCInfoDecoder.EnumerateLiveSlots( @@ -859,19 +1192,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 (uint i = 0; i < _safePoints.Count; i++) { - uint spOffset = (uint)_reader.ReadBits((int)numBitsPerOffset, ref scanOffset); - if (spOffset == normBreakOffset) + if (_safePoints[i] == codeOffset) return i; - if (spOffset > normBreakOffset) + 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..e634979cbf0055 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 @@ -31,12 +31,6 @@ IGCInfoHandle IGCInfo.DecodeInterpreterGCInfo(TargetPointer gcInfoAddress, uint 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(); 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..e91733d8ab65b5 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 @@ -421,30 +421,8 @@ 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 - // caller-popped (return 0); other methods report the argument size from the GC info - // header. Used by EECodeManager::GetStackParameterSize on x86. return Header.VarArgs ? 0u : Header.ArgCount * (uint)_target.PointerSize; } @@ -821,6 +799,124 @@ 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: 0, + CodeSize: MethodSize, + PrologSize: Header.PrologSize, + StackBaseRegister: Header.EbpFrame ? 5u : 0xFFFFFFFF, + SizeOfStackParameterArea: Header.ArgCount * (uint)_target.PointerSize, + 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)); + } + + Dictionary activeRegs = []; + foreach (int offset in SortedTransitionOffsets) + { + foreach (BaseGcTransition transition in Transitions[offset]) + { + if (transition is 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); + } + } + } + } + } + + foreach ((RegMask reg, uint beginOffset) in activeRegs) + lifetimes.Add(new GCSlotLifetime(true, RegMaskToRegNum(reg), 0, 0, 0, beginOffset, 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); +} From b399f34167ad853ec4a99d14adb57a4378151a76 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 6 Jul 2026 16:49:17 -0400 Subject: [PATCH 2/4] x86 GetSlotLifetimes: handle pushed pointer arg transitions Add tracking for GcTransitionPointer PUSH/POP/KILL actions and StackDepthTransition in x86 GetSlotLifetimes. Pushed GC pointer arguments now emit SP-relative stack slot lifetimes from their push offset to their pop/kill offset. Also handle register PUSH/POP actions for stack depth tracking (non-pointer register pushes affect the depth but don't create GC slot lifetimes). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Contracts/GCInfo/GCInfoDecoder.cs | 4 +- .../Contracts/GCInfo/GCInfoX86_1.cs | 9 ++ .../Contracts/GCInfo/X86/GCInfo.cs | 90 +++++++++++++++---- .../DumpTests/StackReferenceDumpTests.cs | 2 - 4 files changed, 86 insertions(+), 19 deletions(-) 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 bb74f5dc4ed0fe..d9344d5e8c0209 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 @@ -1195,10 +1195,10 @@ private uint FindSafePoint(uint codeOffset) // 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 - for (uint i = 0; i < _safePoints.Count; i++) + for (int i = 0; i < _safePoints.Count; i++) { if (_safePoints[i] == codeOffset) - return i; + 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 e634979cbf0055..231758b406b835 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 @@ -34,9 +34,18 @@ uint IGCInfo.GetCodeLength(IGCInfoHandle gcInfoHandle) uint IGCInfo.GetCalleePoppedArgumentsSize(IGCInfoHandle gcInfoHandle) => AssertCorrectHandle(gcInfoHandle).GetCalleePoppedArgumentsSize(); + GCInfoHeader IGCInfo.GetHeader(IGCInfoHandle gcInfoHandle) + => AssertCorrectHandle(gcInfoHandle).GetHeader(); + 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/X86/GCInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs index e91733d8ab65b5..2ade318cca3038 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; @@ -423,6 +425,9 @@ private static uint RegMaskToRegisterNumber(RegMask reg) uint IGCInfoDecoder.GetCalleePoppedArgumentsSize() { + // Mirrors native ::GetStackParameterSize(hdrInfo) in gc_unwind_x86.inl: varargs are + // caller-popped (return 0); other methods report the argument size from the GC info + // header. Used by EECodeManager::GetStackParameterSize on x86. return Header.VarArgs ? 0u : Header.ArgCount * (uint)_target.PointerSize; } @@ -813,11 +818,11 @@ GCInfoHeader IGCInfoDecoder.GetHeader() : null; return new GCInfoHeader( - Version: 0, + Version: _gcInfoVersion, CodeSize: MethodSize, PrologSize: Header.PrologSize, - StackBaseRegister: Header.EbpFrame ? 5u : 0xFFFFFFFF, - SizeOfStackParameterArea: Header.ArgCount * (uint)_target.PointerSize, + StackBaseRegister: (Header.EbpFrame || Header.DoubleAlign) ? 5u : 4u, // EBP=5, ESP=4 + SizeOfStackParameterArea: 0, // x86 doesn't encode an outgoing scratch area IsVarArg: Header.VarArgs, WantsReportOnlyLeaf: true, HasTailCalls: false, @@ -875,26 +880,75 @@ IReadOnlyList IGCInfoDecoder.GetSlotLifetimes() 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]) { - if (transition is GcTransitionRegister regTransition) + switch (transition) { - 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)) + case GcTransitionRegister regTransition: + if (regTransition.IsLive == Action.LIVE) { - uint gcFlags = regTransition.Iptr ? 0x1u : 0u; - lifetimes.Add(new GCSlotLifetime(true, RegMaskToRegNum(regTransition.Register), 0, 0, gcFlags, beginOffset, (uint)offset)); - activeRegs.Remove(regTransition.Register); + 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; } } } @@ -902,6 +956,12 @@ IReadOnlyList IGCInfoDecoder.GetSlotLifetimes() 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; } 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"); From badd3988db60ef03048445afb2f091431fd98217 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 6 Jul 2026 17:07:08 -0400 Subject: [PATCH 3/4] fix --- .../Contracts/GCInfo/GCInfoX86_1.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 231758b406b835..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,15 +28,15 @@ 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.GetCalleePoppedArgumentsSize(IGCInfoHandle gcInfoHandle) => AssertCorrectHandle(gcInfoHandle).GetCalleePoppedArgumentsSize(); - GCInfoHeader IGCInfo.GetHeader(IGCInfoHandle gcInfoHandle) - => AssertCorrectHandle(gcInfoHandle).GetHeader(); - IReadOnlyList IGCInfo.GetInterruptibleRanges(IGCInfoHandle gcInfoHandle) => AssertCorrectHandle(gcInfoHandle).GetInterruptibleRanges(); From 149c77bc0a4bf8c0b956dd0e04b742ef07bb0ece Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 6 Jul 2026 18:04:06 -0400 Subject: [PATCH 4/4] Address review feedback: fix docs, remove dead code, use helper - Fix BaseRegister XML doc: describe as stack base kind (0/1/2), not a CPU register number - Use RegMaskToRegisterNumber helper in x86 GetHeader instead of hard-coded register numbers - Remove unused GetStackBaseRegister/GetSizeOfStackParameterArea public methods from GcInfoDecoder (callers use GetHeader) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Contracts/IGCInfo.cs | 2 +- .../Contracts/GCInfo/GCInfoDecoder.cs | 12 ------------ .../Contracts/GCInfo/X86/GCInfo.cs | 2 +- 3 files changed, 2 insertions(+), 14 deletions(-) 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 eb2ac2ec56885f..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 @@ -41,7 +41,7 @@ public readonly record struct GCInfoHeader( /// 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 register (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). 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 d9344d5e8c0209..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 @@ -555,18 +555,6 @@ public uint GetCodeLength() return _codeLength; } - public uint GetStackBaseRegister() - { - EnsureDecodedTo(DecodePoints.ReversePInvoke); - return _stackBaseRegister; - } - - public uint GetSizeOfStackParameterArea() - { - EnsureDecodedTo(DecodePoints.ReversePInvoke); - return _fixedStackParameterScratchArea; - } - public IReadOnlyList GetInterruptibleRanges() { EnsureDecodedTo(DecodePoints.InterruptibleRanges); 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 2ade318cca3038..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 @@ -821,7 +821,7 @@ GCInfoHeader IGCInfoDecoder.GetHeader() Version: _gcInfoVersion, CodeSize: MethodSize, PrologSize: Header.PrologSize, - StackBaseRegister: (Header.EbpFrame || Header.DoubleAlign) ? 5u : 4u, // EBP=5, ESP=4 + 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,