Repository: dotnet/runtime
Pull request #115391: Fix incorrect codemanager implementation on x86
Author: login: tommcdon
Labels: name: area-Diagnostics-coreclr
Created: 2025-05-08T04:33:05Z | Merged: 2025-05-08T20:40:31Z

This addresses windows x86 debugger setip issues introduced by #114170


Diff:
diff --git a/src/coreclr/vm/codeman.h b/src/coreclr/vm/codeman.h
index a531ff1e42d878..9e2eabdeecc09a 100644
--- a/src/coreclr/vm/codeman.h
+++ b/src/coreclr/vm/codeman.h
@@ -2945,6 +2945,9 @@ class EECodeInfo
         *infoPtr = &m_hdrInfoBody;
         return m_hdrInfoTable;
     }
+
+    PTR_CBYTE DecodeGCHdrInfo(hdrInfo * infoPtr, DWORD relOffset);
+
 private:
     PTR_CBYTE   DecodeGCHdrInfoHelper(hdrInfo   ** infoPtr);
 #endif // TARGET_X86
diff --git a/src/coreclr/vm/eetwain.cpp b/src/coreclr/vm/eetwain.cpp
index d6832774c126a9..9290e3f15a9d93 100644
--- a/src/coreclr/vm/eetwain.cpp
+++ b/src/coreclr/vm/eetwain.cpp
@@ -69,14 +69,14 @@ void EECodeManager::FixContext( ContextType     ctxType,
     _ASSERTE((ctxType == FINALLY_CONTEXT) == (thrownObject == NULL));
 
     /* Extract the necessary information from the info block header */
-    hdrInfo *hdrInfoBody;
-    pCodeInfo->DecodeGCHdrInfo(&hdrInfoBody);
+    hdrInfo hdrInfoBody = { 0 };
+    pCodeInfo->DecodeGCHdrInfo(&hdrInfoBody, dwRelOffset);
 
 #ifdef  _DEBUG
     if (trFixContext) {
         minipal_log_print_info("FixContext [%s][%s] for %s.%s: ",
-               hdrInfoBody->ebpFrame?"ebp":"   ",
-               hdrInfoBody->interruptible?"int":"   ",
+               hdrInfoBody.ebpFrame?"ebp":"   ",
+               hdrInfoBody.interruptible?"int":"   ",
                "UnknownClass","UnknownMethod");
         minipal_log_flush_info();
     }
@@ -84,11 +84,11 @@ void EECodeManager::FixContext( ContextType     ctxType,
 
     /* make sure that we have an ebp stack frame */
 
-    _ASSERTE(hdrInfoBody->ebpFrame);
-    _ASSERTE(hdrInfoBody->handlers); // <TODO>@TODO : This will always be set. Remove it</TODO>
+    _ASSERTE(hdrInfoBody.ebpFrame);
+    _ASSERTE(hdrInfoBody.handlers); // <TODO>@TODO : This will always be set. Remove it</TODO>
 
     TADDR      baseSP;
-    GetHandlerFrameInfo(hdrInfoBody, ctx->Ebp,
+    GetHandlerFrameInfo(&hdrInfoBody, ctx->Ebp,
                                 ctxType == FILTER_CONTEXT ? ctx->Esp : IGNORE_VAL,
                                 ctxType == FILTER_CONTEXT ? (DWORD) IGNORE_VAL : nestingLevel,
                                 &baseSP,
@@ -102,7 +102,7 @@ void EECodeManager::FixContext( ContextType     ctxType,
     // EE will write Esp to **pShadowSP before jumping to handler
 
     PTR_TADDR pBaseSPslots =
-        GetFirstBaseSPslotPtr(ctx->Ebp, hdrInfoBody);
+        GetFirstBaseSPslotPtr(ctx->Ebp, &hdrInfoBody);
     *ppShadowSP = (size_t *)&pBaseSPslots[-(int) nestingLevel   ];
                    pBaseSPslots[-(int)(nestingLevel+1)] = 0; // Zero out the next slot
 
@@ -851,18 +851,18 @@ bool EECodeManager::IsGcSafe( EECodeInfo     *pCodeInfo,
         SUPPORTS_DAC;
     } CONTRACTL_END;
 
-    hdrInfo         *info;
+    hdrInfo         info = { 0 };
 
     /* Extract the necessary information from the info block header */
 
-    pCodeInfo->DecodeGCHdrInfo(&info);
+    pCodeInfo->DecodeGCHdrInfo(&info, dwRelOffset);
 
     /* workaround: prevent interruption within prolog/epilog */
 
-    if  (info->prologOffs != hdrInfo::NOT_IN_PROLOG || info->epilogOffs != hdrInfo::NOT_IN_EPILOG)
+    if  (info.prologOffs != hdrInfo::NOT_IN_PROLOG || info.epilogOffs != hdrInfo::NOT_IN_EPILOG)
         return false;
 
-    return (info->interruptible);
+    return (info.interruptible);
 }
 
 #endif // !USE_GC_INFO_DECODER
@@ -2254,34 +2254,43 @@ TADDR EECodeManager::GetAmbientSP(PREGDISPLAY     pContext,
 
     /* Extract the necessary information from the info block header */
 
-    hdrInfo *hdrInfoBody;
-    PTR_CBYTE table = pCodeInfo->DecodeGCHdrInfo(&hdrInfoBody);
+    PTR_CBYTE table = nullptr;
+    hdrInfo hdrInfoBody = { 0 };
+    hdrInfo *pCodeInfoBody = &hdrInfoBody;
+    if (pCodeInfo->GetRelOffset() == dwRelOffset)
+    {
+        table = pCodeInfo->DecodeGCHdrInfo(&pCodeInfoBody);
+    }
+    else
+    {
+        table = pCodeInfo->DecodeGCHdrInfo(&hdrInfoBody, dwRelOffset);
+    }
 
 #if defined(_DEBUG) && !defined(DACCESS_COMPILE)
     if (trFixContext)
     {
         minipal_log_print_info("GetAmbientSP [%s][%s] for %s.%s: ",
-               hdrInfoBody->ebpFrame?"ebp":"   ",
-               hdrInfoBody->interruptible?"int":"   ",
+               pCodeInfoBody->ebpFrame?"ebp":"   ",
+               pCodeInfoBody->interruptible?"int":"   ",
                "UnknownClass","UnknownMethod");
         minipal_log_flush_info();
     }
 #endif // _DEBUG && !DACCESS_COMPILE
 
-    if ((hdrInfoBody->prologOffs != hdrInfo::NOT_IN_PROLOG) ||
-        (hdrInfoBody->epilogOffs != hdrInfo::NOT_IN_EPILOG))
+    if ((pCodeInfoBody->prologOffs != hdrInfo::NOT_IN_PROLOG) ||
+        (pCodeInfoBody->epilogOffs != hdrInfo::NOT_IN_EPILOG))
     {
         return NULL;
     }
 
     /* make sure that we have an ebp stack frame */
 
-    if (hdrInfoBody->handlers)
+    if (pCodeInfoBody->handlers)
     {
-        _ASSERTE(hdrInfoBody->ebpFrame);
+        _ASSERTE(pCodeInfoBody->ebpFrame);
 
         TADDR      baseSP;
-        GetHandlerFrameInfo(hdrInfoBody,
+        GetHandlerFrameInfo(pCodeInfoBody,
                             GetRegdisplayFP(pContext),
                             (DWORD) IGNORE_VAL,
                             nestingLevel,
@@ -2294,24 +2303,24 @@ TADDR EECodeManager::GetAmbientSP(PREGDISPLAY     pContext,
 
     _ASSERTE(nestingLevel == 0);
 
-    if (hdrInfoBody->ebpFrame)
+    if (pCodeInfoBody->ebpFrame)
     {
-        return GetOutermostBaseFP(GetRegdisplayFP(pContext), hdrInfoBody);
+        return GetOutermostBaseFP(GetRegdisplayFP(pContext), pCodeInfoBody);
     }
 
     TADDR baseSP = GetRegdisplaySP(pContext);
-    if  (hdrInfoBody->interruptible)
+    if  (pCodeInfoBody->interruptible)
     {
-        baseSP += scanArgRegTableI(skipToArgReg(*hdrInfoBody, table),
+        baseSP += scanArgRegTableI(skipToArgReg(*pCodeInfoBody, table),
                                    dwRelOffset,
                                    dwRelOffset,
-                                   hdrInfoBody);
+                                   pCodeInfoBody);
     }
     else
     {
-        baseSP += scanArgRegTable(skipToArgReg(*hdrInfoBody, table),
+        baseSP += scanArgRegTable(skipToArgReg(*pCodeInfoBody, table),
                                   dwRelOffset,
-                                  hdrInfoBody);
+                                  pCodeInfoBody);
     }
 
     return baseSP;
diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp
index 5b00eab6389d96..8a76e18c157ff7 100644
--- a/src/coreclr/vm/jitinterface.cpp
+++ b/src/coreclr/vm/jitinterface.cpp
@@ -15301,6 +15301,15 @@ PTR_CBYTE EECodeInfo::DecodeGCHdrInfoHelper(hdrInfo ** infoPtr)
     return m_hdrInfoTable;
 }
 
+PTR_CBYTE EECodeInfo::DecodeGCHdrInfo(hdrInfo * infoPtr, DWORD relOffset)
+{
+    _ASSERTE(infoPtr != NULL);
+    GCInfoToken gcInfoToken = GetGCInfoToken();
+    DWORD hdrInfoSize = (DWORD)::DecodeGCHdrInfo(gcInfoToken, relOffset, infoPtr);
+    _ASSERTE(hdrInfoSize != 0);
+    return (PTR_CBYTE)gcInfoToken.Info + hdrInfoSize;
+}
+
 #endif // TARGET_X86
 
 #if defined(TARGET_AMD64)


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR fixes debugger setup issues on Windows x86 by addressing the incorrect implementation of the code manager.  
- Introduces a new DecodeGCHdrInfo method in jitinterface.cpp that uses a relative offset to compute the correct GC header info pointer.  
- Updates all affected call sites in eetwain.cpp to pass the proper structure (using a value instance and its address) and a dwRelOffset parameter.  
- Updates the codeman.h header with the new function prototype to support x86 changes.

### Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

| File                                 | Description                                                                  |
| ------------------------------------ | ---------------------------------------------------------------------------- |
| src/coreclr/vm/jitinterface.cpp      | Added the new DecodeGCHdrInfo method for proper GC header info decoding       |
| src/coreclr/vm/eetwain.cpp           | Modified calls to DecodeGCHdrInfo and adjusted usage of the hdrInfo structure  |
| src/coreclr/vm/codeman.h             | Added the function prototype for DecodeGCHdrInfo                            |


<details>
<summary>Comments suppressed due to low confidence (1)</summary>

**src/coreclr/vm/eetwain.cpp:90**
* Verify that the updated API for GetHandlerFrameInfo (which now accepts a pointer to hdrInfo) is consistently implemented across all call sites.
```
GetHandlerFrameInfo(&hdrInfoBody, ctx->Ebp,
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T04:33:48Z
user: login: filipnavara
body: Thanks! There's one code path that could possibly benefit from the cache optimization (`GetAmbientSPFromCrawlFrame` -> `GetAmbientSP` seems to use the same relOffset as the cache in `EECodeInfo`) but it's not worth the extra trouble, it is only used by debugger.

I'm sorry for breaking this but I assume there're no public tests that would alert me about this, right?
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-08T06:39:38Z
user: login: thaystg
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-08T13:33:55Z
user: login: thaystg
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-08T16:54:56Z

Review comments:
diff_hunk: @@ -14917,6 +14917,15 @@ PTR_CBYTE EECodeInfo::DecodeGCHdrInfoHelper(hdrInfo ** infoPtr)
     return m_hdrInfoTable;
 }
 
+PTR_CBYTE EECodeInfo::DecodeGCHdrInfo(hdrInfo * infoPtr, DWORD relOffset)
+{
+    _ASSERTE(infoPtr != NULL);
+    GCInfoToken gcInfoToken = GetGCInfoToken();
+    DWORD hdrInfoSize = (DWORD)::DecodeGCHdrInfo(gcInfoToken, relOffset, infoPtr);
+    _ASSERTE(hdrInfoSize != 0);
+    return (PTR_CBYTE)gcInfoToken.Info + hdrInfoSize;
path: src/coreclr/vm/jitinterface.cpp
user: login: Copilot
body: [nitpick] Consider using reinterpret_cast for the pointer cast (e.g. reinterpret_cast<PTR_CBYTE>(gcInfoToken.Info)) to improve type safety.
```suggestion
    return reinterpret_cast<PTR_CBYTE>(gcInfoToken.Info) + hdrInfoSize;
```
created_at: 2025-05-08T04:33:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -68,26 +68,26 @@ void EECodeManager::FixContext( ContextType     ctxType,
     _ASSERTE((ctxType == FINALLY_CONTEXT) == (thrownObject == NULL));
 
     /* Extract the necessary information from the info block header */
-    hdrInfo *hdrInfoBody;
-    pCodeInfo->DecodeGCHdrInfo(&hdrInfoBody);
+    hdrInfo hdrInfoBody = { 0 };
+    pCodeInfo->DecodeGCHdrInfo(&hdrInfoBody, dwRelOffset);
 
 #ifdef  _DEBUG
     if (trFixContext) {
         minipal_log_print_info("FixContext [%s][%s] for %s.%s: ",
-               hdrInfoBody->ebpFrame?"ebp":"   ",
-               hdrInfoBody->interruptible?"int":"   ",
+               hdrInfoBody.ebpFrame?"ebp":"   ",
+               hdrInfoBody.interruptible?"int":"   ",
                "UnknownClass","UnknownMethod");
         minipal_log_flush_info();
     }
 #endif
 
     /* make sure that we have an ebp stack frame */
 
-    _ASSERTE(hdrInfoBody->ebpFrame);
-    _ASSERTE(hdrInfoBody->handlers); // <TODO>@TODO : This will always be set. Remove it</TODO>
+    _ASSERTE(hdrInfoBody.ebpFrame);
+    _ASSERTE(hdrInfoBody.handlers); // <TODO>@TODO : This will always be set. Remove it</TODO>
path: src/coreclr/vm/eetwain.cpp
user: login: Copilot
body: [nitpick] Since the assertion on hdrInfoBody.handlers is marked as a TODO and appears redundant, consider removing it to clean up the code once confirmed.
```suggestion

```
created_at: 2025-05-08T04:33:48Z
author_association: CONTRIBUTOR

PR comments:
user: login: dotnet-policy-service[bot]
created_at: 2025-05-08T04:33:38Z
body: Tagging subscribers to this area: @tommcdon
See info in [area-owners.md](https://github.com/dotnet/runtime/blob/main/docs/area-owners.md) if you want to be subscribed.
<!-- Policy app identification https://img.shields.io/static/v1?label=PullRequestIssueManagement. -->
author_association: CONTRIBUTOR
user: login: tommcdon
created_at: 2025-05-08T16:40:39Z
body: > Thanks! There's one code path that could possibly benefit from the cache optimization (`GetAmbientSPFromCrawlFrame` -> `GetAmbientSP` seems to use the same relOffset as the cache in `EECodeInfo`) but it's not worth the extra trouble, it is only used by debugger.

I've pushed a commit to take advantage of the perf improvement when the relative offset matches the code manager's built-in offset.

> I'm sorry for breaking this but I assume there're no public tests that would alert me about this, right?

No worries.  You are correct - we have some internal Visual Studio testing that caught the issue.

author_association: MEMBER