Skip to content

Fix TryGetSymbol on iOS, tvOS and Mac Catalyst - #136361

Merged
steveisok merged 1 commit into
dotnet:mainfrom
steveisok:fix-trygetsymbol-apple-mobile
Oct 23, 2026
Merged

steveisok merged 1 commit into
dotnet:mainfrom
steveisok:fix-trygetsymbol-apple-mobile

Conversation

@steveisok

Copy link
Copy Markdown
Member

Summary

#135734 ("Make TryGetSymbol cross-os") moved TryGetSymbol into
src/coreclr/debug/dbgutil/symbol.cpp. The new dispatcher and the Mach-O and ELF
readers are compiled only under

#if !defined(HOST_WASM) && (defined(HOST_WINDOWS) || defined(HOST_OSX) || defined(__linux__))

and dbgutil/CMakeLists.txt adds the in-tree inc/llvm include directory
(for ELF.h) only for CLR_CMAKE_HOST_WIN32 OR CLR_CMAKE_HOST_OSX.

Apple mobile hosts (iOS, tvOS, Mac Catalyst) define HOST_APPLE, plus
HOST_IOS, HOST_TVOS or HOST_MACCATALYST, but not HOST_OSX
(eng/native/configurecompiler.cmake). On those hosts TryGetSymbol compiles
to return false. Before #135734, machoreader.cpp (with TryGetSymbol) was
built for every CLR_CMAKE_TARGET_APPLE.

Consequences for in-app debugging on Apple mobile, where the DBI and DAC run in
the app process:

  • The DAC's GetDacTableAddress can't find the DAC table export.
    CordbProcess::Init → CreateDacDbiInterface fails with
    CORDBG_E_MISSING_DEBUGGER_EXPORTS (0x80131C4F), so
    DebugActiveProcess fails and no debugger events are ever raised.
    Debugger.Break() is a no-op, and the debugger UI shows nothing. The HRESULT
    isn't surfaced to the user by the IDE.
  • The DBI and DAC lookups of DotNetRuntimeContractDescriptor
    (di/process.cpp, daccess.cpp, enummem.cpp) also fail, so the cDAC can't
    be located either.

Change

Use HOST_APPLE instead of HOST_OSX:

  • in the reader guards: symbol.cpp (×2), machoreader.cpp, elfreader.cpp;
  • for the inc/llvm include directory in dbgutil/CMakeLists.txt. Without
    it, elfreader.h's #include <ELF.h> (already chosen for HOST_APPLE)
    fails to compile once the guards are widened.

5 lines across 4 files; no behavior change for Windows, Linux or macOS hosts.

Alternative for reviewers: a broader guard such as
!defined(HOST_WASM) && (defined(HOST_WINDOWS) || defined(HOST_UNIX)) would also
cover non-Linux Unix hosts (for example FreeBSD and Haiku), which today also hit
the return false fallback for the contract-descriptor lookup. That would need
the include directory and elfreader.h's <ELF.h> vs <elf.h> selection
checked for those hosts. This PR keeps the minimal Apple-only change.

Validation

  • iOS Simulator (iossimulator-arm64, Xcode 27):
    • built with ./build.sh clr+clr.runtime+libs+packs -os iossimulator -arch arm64 -cross -c Release,
      with -cmakeargs "-DCMAKE_CXX_FLAGS=-Wno-error=#warnings". That argument is
      an unrelated local workaround for Xcode 27's libc++ #warning about the
      13.0 minimum under main's -Werror.
    • The DAC now contains the Mach-O reader (TryGetMachOSymbol).
  • With the locally built runtime pack, the in-app CoreCLR debugger (VS Code,
    .NET MAUI F5 flow, remote debugger host and target) stops at
    Debugger.Break and steps again
    . Before the fix, the attach failed
    silently, with 0x80131C4F from CordbProcess::Init confirmed by lldb on
    the in-app DBI.
  • The end-to-end debugger validation ran on a build whose source tree is identical to this commit; this exact commit was also rebuilt for iossimulator-arm64 to confirm it builds.
  • No unit test covers this native, host-configuration-dependent path.

dotnet#135734 only compiles the Mach-O/ELF symbol readers in dbgutil under
HOST_OSX. Apple mobile hosts define HOST_APPLE but not HOST_OSX, so
TryGetSymbol always returns false there. The DAC can't find its data table
(CreateDacDbiInterface fails with CORDBG_E_MISSING_DEBUGGER_EXPORTS), and the
cDAC can't find DotNetRuntimeContractDescriptor. In-app debugging silently
never attaches.

Use HOST_APPLE for the reader guards and for the inc/llvm include directory.

Validated on iossimulator-arm64: Debugger.Break stops and stepping works again.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 4 pipeline(s).
12 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.

@steveisok
steveisok requested a review from a team October 23, 2026 12:00
@steveisok

Copy link
Copy Markdown
Member Author

/ba-g Unrelated known issues

@steveisok
steveisok merged commit 856a1e3 into dotnet:main Oct 23, 2026
104 of 106 checks passed
steveisok added a commit that referenced this pull request Oct 23, 2026
…rames (#136362)

Independent of #136361 in code, but end-to-end testing on iOS needs
#136361, otherwise in-app debugging never attaches.

## Summary

With the cDAC as the in-app `IDacDbiInterface` on CoreCLR iOS, where
Debug
app code runs on the interpreter, the debugger never reported
`Debugger.Break`. Two cDAC divergences from the native DAC cause this,
one per
commit.

### 1. Map entry point to code start when finding a native code version

`CodeVersions_1.GetSpecificNativeCodeVersion` compared the MethodDesc
native
code slot with the code-block start. For interpreted methods the slot
holds an
`InterpreterPrecode` and the code block starts at the bytecode, so the
two
never matched.
- `GetNativeCodeVersionForIP` returned an invalid handle.
- `DebugInfo_1.GetMethodVarInfo` threw "No NativeCodeVersion found for
native
  code …" (`0x80131509`).
- `GetNativeCodeSequencePointsAndVarInfo` failed, the DBI abandoned the
frame,
  and the stop was dropped.

The fix maps the default and explicit versions' native code through
`IExecutionManager.GetDiagnosticCodeStartFromEntryPoint` before
comparing.
That's the helper #135430 added for `GetMethodVarInfo`; it covers
interpreter
precodes and portable entry points. It matches the native DAC's

`GetInterpreterCodeFromEntryPointIfPresent(NativeCodeVersion::GetNativeCode())`
pattern. #135430 alone isn't enough, because the throw happens before
its
offset mapping.

Tests and docs:
-
`CodeVersionsTests.GetNativeCodeVersion_Interpreted_MapsPrecodeToBytecode`
  (4 architectures).
- The mock `AddCodeBlock` gets a default identity
  `GetDiagnosticCodeStartFromEntryPoint`.
- `CodeVersions.md` pseudo-code updated.

### 2. Compute caller SP for interpreted frames like the native code
manager

`StackWalk_1.CallerStackPointer` backs `GetRuntimeFramePointer` on
caller-SP architectures such as ARM64, and `GetFuncletRootId`
(`fpParentOrSelf`). It always ran the OS unwinder, which has no unwind
info for
bytecode, so `fp`/`fpParentOrSelf` came back as the frame's own SP.

For interpreter code it now uses the new
`FrameHelpers.InterpreterCallerStackPointer`, which returns:
- the parent `InterpMethodContextFrame`, including the zeroed root frame
  (`ip == NULL`) of an interpreter activation, or
- the owning `InterpreterFrame` when there is no parent.

This is the SP that native `VirtualUnwindInterpreterCallFrame`
(eetwain.cpp)
gives the caller context. The walker's own unwind path (`Next`) is
unchanged.

Tests and docs:
-
`StackWalkTests.CallerStackPointer_InterpretedFrame_IsParentInterpMethodContextFrame`
and
`StackWalkTests.CallerStackPointer_InterpretedFrameWithRootParent_IsRootFrameAddress`.
- `StackWalk.md` updated.

## Validation

- Unit tests (`Microsoft.Diagnostics.DataContractReader.Tests`):
- **3,406/3,406** pass after commit 1, and **3,408/3,408** after commit
2.
- Each new test fails without its own commit's product change and passes
    with it.
- `Microsoft.Diagnostics.DataContractReader.Usage.Tests`: 4/4 after each
    commit.
- **iOS Simulator** (iossimulator-arm64, interpreted Debug app, VS Code
.NET MAUI
  F5 flow, `DOTNET_ENABLE_CDAC=1`, with #136361 applied):
- `Debugger.Break` stops, and stepping, Locals and caller inspection all
    work.
- A diagnosis-only trace compared each cDAC result with the legacy DAC
for
    the same calls during the whole workflow:
- `GetNativeCodeSequencePointsAndVarInfo`: **8/8 calls match** in
HRESULT,
      variable count and sequence-point count;
- `GetStackWalkCurrentFrameInfo`: **0 mismatches in 975 compared frame
      fields** (including `fp`, `fpParentOrSelf`, method token and
MethodDesc, native start and offset, and context) across 288 frames,
      covering every frame token in the session.
- Without these commits, the same scenario never reports
`Debugger.Break`.

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 12.0-preview1 milestone Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants