macOS SOS-in-lldb: speed up ReadVirtual and fix arm64 SpecialDiagInfo - #5823

Merged
hoyosjs merged 7 commits into
dotnet:mainfrom
steveisok:steveisok/macos-arm64-sos-fixes
May 12, 2026
Merged

macOS SOS-in-lldb: speed up ReadVirtual and fix arm64 SpecialDiagInfo#5823
hoyosjs merged 7 commits into
dotnet:mainfrom
steveisok:steveisok/macos-arm64-sos-fixes

Conversation

@steveisok

@steveisoksteveisok commented May 2, 2026

Copy link
Copy Markdown
Member

Two independent fixes to SOS-in-lldb on macOS. They surfaced together
while debugging extremely slow / failing clrthreads -managedexception
on Apple Silicon, but each stands on its own.

1. Speed up LLDBServices::ReadVirtual with a cached section table — applies to both arm64 and x86_64 macOS

When lldb's process.ReadMemory can't satisfy a read, the plugin falls
back to reading from on-disk module sections. For MachO core dumps this
is the common path, not the rare one — .text/code segments aren't
in the dump, so the DAC's metadata reads almost all hit the fallback.

The previous implementation iterated numModules × numSections per
call. Measured on a typical .NET process: ~3500 entries × multiple SWIG
calls each = ~44 µs per fallback. With ~5M ReadVirtual calls during a
single clrthreads -managedexception, that's ~200 s of pure iteration.

This change builds a sorted SectionRange table on first use and
binary-searches it with std::upper_bound. The table is invalidated
when the target's module count changes, and through the existing
ClearCache() path. The fix is arch-agnostic — x86_64 macOS sees the
same speedup since the underlying lldb MachO core behavior is identical.

Measured impact (macOS arm64, SOS.LineNums)

ConfigBeforeAfter
singlefile.* (live debug)~35 s15–21 s
prebuilt.11 (dump load)118 s28 s (~4×)
prebuilt.{10,9,8} (dump load)~120 s each39–42 s each (~3×)
Total LineNums wall time~13 min~3.7 min (~3.5×)

Not yet measured on x86_64 macOS, but the same code path is exercised
and the same root cause applies.

2. Fix SpecialDiagInfo address for arm64 macOS — arm64 only

The legacy address 0x7fffffff10000000 is beyond Apple Silicon's
47-bit user-space VM limit. createdump still writes a segment at that
address into the core file, but lldb's MachO core reader refuses reads
above 0x7FFF_FFFFFFFF, so SOS reports

Special diagnostics info read failed

and falls through to the slower managed-side path (or fails outright on
older builds without that fallback). On x86_64 macOS this address
remains valid, so the legacy constant is preserved there — no behavior
change for Intel Macs.

This change uses 0x00007ffffff10000 on arm64 macOS — the same address
already used on Linux/non-Apple 64-bit — and probes the legacy x86_64
address as a fallback so dumps produced by older createdump on x86_64
Macs continue to be recognized. The managed SpecialDiagInfo.cs path
is mirrored.

Note: full coverage on arm64 macOS requires the matching createdump
change in dotnet/runtime to flow through. Until then, dumps from
old createdump remain unreadable on arm64 macOS (the data sits at an
address lldb won't return) — same behavior as today, no regression.


CI on Windows/Linux is unaffected — the section cache only kicks in
when lldb's primary read fails (rare on those platforms), and the
SpecialDiagInfo legacy fallback preserves the previous address on every
non-arm64-macOS configuration.

steveisokand others added 2 commits May 2, 2026 12:09
The legacy SpecialDiagInfo address 0x7fffffff10000000 is beyond Apple
Silicon's 47-bit user-space VM limit. While createdump writes this
address into the core file's segment list, lldb's MachO core reader
rejects reads above 0x7FFF_FFFFFFFF, so SOS reports
'Special diagnostics info read failed' and falls back to the slower
managed-side path.
Use 0x00007ffffff10000 on arm64 macOS (matching the Linux/non-Apple
64-bit address) and probe the legacy x86_64 address as a fallback so
older dumps continue to be recognized on platforms where the legacy
address is readable.
Mirrored on the managed side (SpecialDiagInfo.cs) for the
DataTarget-based path.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
LLDBServices::ReadVirtual falls back to reading directly from native
module sections when lldb's process.ReadMemory cannot satisfy a read.
For dumps that don't include code/data segments (notably MachO core
files on macOS, where the .text segments aren't in the dump) this is
hit on the vast majority of reads.
The previous fallback iterated numModules x numSections per call
(~3500 entries on a typical .NET process; ~44 microseconds per fallback)
making clrthreads -managedexception and similar DAC-heavy commands
take ~100s on macOS arm64.
Build a sorted SectionRange table on first use and binary-search it
with std::upper_bound. The cache is invalidated when the target's
module count changes, and through the existing ClearCache() path.
Measured on macOS arm64 SOS LineNums tests with prebuilt runtime
configs: ~118s -> ~28s for net11 (~4x), and total LineNums wall
time from ~13 minutes to ~3.7 minutes (~3.5x).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 2, 2026 16:15
@steveisok
steveisok requested a review from a team as a code ownerMay 2, 2026 16:15

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes SOS-in-lldb behavior on macOS arm64 by moving SpecialDiagInfo to a 47-bit-valid address (with legacy fallback) and by significantly reducing ReadVirtual fallback overhead via a cached, binary-searched section table.

Changes:

  • Update SpecialDiagInfo address on Apple Silicon macOS and add legacy-address probing for older dumps.
  • Add a cached SectionRange table to speed up LLDBServices::ReadVirtual section-backed fallback reads.
  • Mirror the SpecialDiagInfo address fallback behavior in the managed SpecialDiagInfo implementation.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

FileDescription
src/SOS/lldbplugin/services.hIntroduces section-range cache fields and helpers for faster section fallback reads.
src/SOS/lldbplugin/services.cppImplements legacy SpecialDiagInfo fallback and the section-range cache (sort + upper_bound) for ReadVirtual.
src/SOS/inc/specialdiaginfo.hAdjusts arm64 macOS SpecialDiagInfo address and defines legacy address constant.
src/Microsoft.Diagnostics.DebugServices.Implementation/SpecialDiagInfo.csAdds macOS arm64-valid address probing + legacy fallback in managed reader.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadsrc/SOS/lldbplugin/services.cpp
Comment threadsrc/SOS/lldbplugin/services.cpp
Comment threadsrc/SOS/inc/specialdiaginfo.h Outdated
@steveisoksteveisok changed the title macOS arm64 SOS-in-lldb: fix SpecialDiagInfo address and speed up ReadVirtualmacOS SOS-in-lldb: speed up ReadVirtual and fix arm64 SpecialDiagInfoMay 2, 2026
* Section cache: guard endAddr/endOffset against uint64 overflow when
building the SectionRange table and when computing the containment
check, so a pathologically large section can't wrap and produce
spurious cache hits.
* GetExceptionRecord: when the SpecialDiagInfo signature matches but
the exception record can't be read (Version too low,
ExceptionRecordAddress=0, or read fails), continue to the next
candidate address instead of returning eagerly. Cheap and avoids
pinning behavior to the first matching address.
* specialdiaginfo.h: fix cross-reference comment to point at the actual
reader (LLDBServices::GetLastEventInformation), not a non-existent
SOSReadDiagInfoHeader symbol.
* ExtensionCommands SpecialDiagInfoHeader: add GetCandidateAddresses
alongside the existing GetAddress so the OSX path probes the
47-bit-valid address first then falls back to the legacy x86_64
address. CommandFormatHelpers.DisplaySpecialInfo now iterates the
candidates so 'runtimes' / extension commands recognize Apple Silicon
dumps the same way the lldb plugin and managed reader already do.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
return SpecialDiagInfoAddressMacOS64;
// Try the arm64-valid address first (also valid on x86_64 macOS for newer
// createdump output); fall back to the legacy x86_64 address.
yield return SpecialDiagInfoAddressMacOSArm64;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow up change - lets remove the fallback. 8.0 already had this address.

{
Span<byte> headerBuffer = stackalloc byte[Unsafe.SizeOf<SpecialDiagInfoHeader>()];
if (_memoryService.ReadMemory(SpecialDiagInfoAddress, headerBuffer, out int bytesRead) && bytesRead == headerBuffer.Length)
Span<byte> exceptionRecordBuffer = stackalloc byte[Unsafe.SizeOf<EXCEPTION_RECORD64>()];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow up - encapsulate getting the SpecialDiagInfoHeader into a helper that this and HasDiagnosticInfo can use

Comment threadsrc/SOS/lldbplugin/services.cpp Outdated
@hoyosjs
hoyosjsforce-pushed the steveisok/macos-arm64-sos-fixes branch from 609e546 to 38d8dbaCompareMay 11, 2026 09:26
hoyosjsand others added 3 commits May 11, 2026 10:13
The flag was load-bearing only for the degenerate 'target with 0 modules
is a valid cached state' case. Module-count equality alone is sufficient
to detect cache freshness, and matches the previous behavior in every
real-world scenario.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@hoyosjs
hoyosjs enabled auto-merge (squash) May 11, 2026 23:28

@tommcdontommcdon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@hoyosjs
hoyosjs merged commit 9c40506 into dotnet:mainMay 12, 2026
19 checks passed
@hoyosjshoyosjs mentioned this pull request Jun 9, 2026
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 11, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@steveisok@tommcdon@hoyosjs
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

macOS SOS-in-lldb: speed up ReadVirtual and fix arm64 SpecialDiagInfo - #5823

Merged
hoyosjs merged 7 commits into
dotnet:mainfrom
steveisok:steveisok/macos-arm64-sos-fixes
May 12, 2026
Merged

macOS SOS-in-lldb: speed up ReadVirtual and fix arm64 SpecialDiagInfo#5823
hoyosjs merged 7 commits into
dotnet:mainfrom
steveisok:steveisok/macos-arm64-sos-fixes

Conversation

@steveisok

@steveisoksteveisok commented May 2, 2026

Copy link
Copy Markdown
Member

Two independent fixes to SOS-in-lldb on macOS. They surfaced together
while debugging extremely slow / failing clrthreads -managedexception
on Apple Silicon, but each stands on its own.

1. Speed up LLDBServices::ReadVirtual with a cached section table — applies to both arm64 and x86_64 macOS

When lldb's process.ReadMemory can't satisfy a read, the plugin falls
back to reading from on-disk module sections. For MachO core dumps this
is the common path, not the rare one — .text/code segments aren't
in the dump, so the DAC's metadata reads almost all hit the fallback.

The previous implementation iterated numModules × numSections per
call. Measured on a typical .NET process: ~3500 entries × multiple SWIG
calls each = ~44 µs per fallback. With ~5M ReadVirtual calls during a
single clrthreads -managedexception, that's ~200 s of pure iteration.

This change builds a sorted SectionRange table on first use and
binary-searches it with std::upper_bound. The table is invalidated
when the target's module count changes, and through the existing
ClearCache() path. The fix is arch-agnostic — x86_64 macOS sees the
same speedup since the underlying lldb MachO core behavior is identical.

Measured impact (macOS arm64, SOS.LineNums)

ConfigBeforeAfter
singlefile.* (live debug)~35 s15–21 s
prebuilt.11 (dump load)118 s28 s (~4×)
prebuilt.{10,9,8} (dump load)~120 s each39–42 s each (~3×)
Total LineNums wall time~13 min~3.7 min (~3.5×)

Not yet measured on x86_64 macOS, but the same code path is exercised
and the same root cause applies.

2. Fix SpecialDiagInfo address for arm64 macOS — arm64 only

The legacy address 0x7fffffff10000000 is beyond Apple Silicon's
47-bit user-space VM limit. createdump still writes a segment at that
address into the core file, but lldb's MachO core reader refuses reads
above 0x7FFF_FFFFFFFF, so SOS reports

Special diagnostics info read failed

and falls through to the slower managed-side path (or fails outright on
older builds without that fallback). On x86_64 macOS this address
remains valid, so the legacy constant is preserved there — no behavior
change for Intel Macs.

This change uses 0x00007ffffff10000 on arm64 macOS — the same address
already used on Linux/non-Apple 64-bit — and probes the legacy x86_64
address as a fallback so dumps produced by older createdump on x86_64
Macs continue to be recognized. The managed SpecialDiagInfo.cs path
is mirrored.

Note: full coverage on arm64 macOS requires the matching createdump
change in dotnet/runtime to flow through. Until then, dumps from
old createdump remain unreadable on arm64 macOS (the data sits at an
address lldb won't return) — same behavior as today, no regression.


CI on Windows/Linux is unaffected — the section cache only kicks in
when lldb's primary read fails (rare on those platforms), and the
SpecialDiagInfo legacy fallback preserves the previous address on every
non-arm64-macOS configuration.

steveisokand others added 2 commits May 2, 2026 12:09
The legacy SpecialDiagInfo address 0x7fffffff10000000 is beyond Apple
Silicon's 47-bit user-space VM limit. While createdump writes this
address into the core file's segment list, lldb's MachO core reader
rejects reads above 0x7FFF_FFFFFFFF, so SOS reports
'Special diagnostics info read failed' and falls back to the slower
managed-side path.
Use 0x00007ffffff10000 on arm64 macOS (matching the Linux/non-Apple
64-bit address) and probe the legacy x86_64 address as a fallback so
older dumps continue to be recognized on platforms where the legacy
address is readable.
Mirrored on the managed side (SpecialDiagInfo.cs) for the
DataTarget-based path.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
LLDBServices::ReadVirtual falls back to reading directly from native
module sections when lldb's process.ReadMemory cannot satisfy a read.
For dumps that don't include code/data segments (notably MachO core
files on macOS, where the .text segments aren't in the dump) this is
hit on the vast majority of reads.
The previous fallback iterated numModules x numSections per call
(~3500 entries on a typical .NET process; ~44 microseconds per fallback)
making clrthreads -managedexception and similar DAC-heavy commands
take ~100s on macOS arm64.
Build a sorted SectionRange table on first use and binary-search it
with std::upper_bound. The cache is invalidated when the target's
module count changes, and through the existing ClearCache() path.
Measured on macOS arm64 SOS LineNums tests with prebuilt runtime
configs: ~118s -> ~28s for net11 (~4x), and total LineNums wall
time from ~13 minutes to ~3.7 minutes (~3.5x).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 2, 2026 16:15
@steveisok
steveisok requested a review from a team as a code ownerMay 2, 2026 16:15

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes SOS-in-lldb behavior on macOS arm64 by moving SpecialDiagInfo to a 47-bit-valid address (with legacy fallback) and by significantly reducing ReadVirtual fallback overhead via a cached, binary-searched section table.

Changes:

  • Update SpecialDiagInfo address on Apple Silicon macOS and add legacy-address probing for older dumps.
  • Add a cached SectionRange table to speed up LLDBServices::ReadVirtual section-backed fallback reads.
  • Mirror the SpecialDiagInfo address fallback behavior in the managed SpecialDiagInfo implementation.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

FileDescription
src/SOS/lldbplugin/services.hIntroduces section-range cache fields and helpers for faster section fallback reads.
src/SOS/lldbplugin/services.cppImplements legacy SpecialDiagInfo fallback and the section-range cache (sort + upper_bound) for ReadVirtual.
src/SOS/inc/specialdiaginfo.hAdjusts arm64 macOS SpecialDiagInfo address and defines legacy address constant.
src/Microsoft.Diagnostics.DebugServices.Implementation/SpecialDiagInfo.csAdds macOS arm64-valid address probing + legacy fallback in managed reader.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadsrc/SOS/lldbplugin/services.cpp
Comment threadsrc/SOS/lldbplugin/services.cpp
Comment threadsrc/SOS/inc/specialdiaginfo.h Outdated
@steveisoksteveisok changed the title macOS arm64 SOS-in-lldb: fix SpecialDiagInfo address and speed up ReadVirtualmacOS SOS-in-lldb: speed up ReadVirtual and fix arm64 SpecialDiagInfoMay 2, 2026
* Section cache: guard endAddr/endOffset against uint64 overflow when
building the SectionRange table and when computing the containment
check, so a pathologically large section can't wrap and produce
spurious cache hits.
* GetExceptionRecord: when the SpecialDiagInfo signature matches but
the exception record can't be read (Version too low,
ExceptionRecordAddress=0, or read fails), continue to the next
candidate address instead of returning eagerly. Cheap and avoids
pinning behavior to the first matching address.
* specialdiaginfo.h: fix cross-reference comment to point at the actual
reader (LLDBServices::GetLastEventInformation), not a non-existent
SOSReadDiagInfoHeader symbol.
* ExtensionCommands SpecialDiagInfoHeader: add GetCandidateAddresses
alongside the existing GetAddress so the OSX path probes the
47-bit-valid address first then falls back to the legacy x86_64
address. CommandFormatHelpers.DisplaySpecialInfo now iterates the
candidates so 'runtimes' / extension commands recognize Apple Silicon
dumps the same way the lldb plugin and managed reader already do.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
return SpecialDiagInfoAddressMacOS64;
// Try the arm64-valid address first (also valid on x86_64 macOS for newer
// createdump output); fall back to the legacy x86_64 address.
yield return SpecialDiagInfoAddressMacOSArm64;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow up change - lets remove the fallback. 8.0 already had this address.

{
Span<byte> headerBuffer = stackalloc byte[Unsafe.SizeOf<SpecialDiagInfoHeader>()];
if (_memoryService.ReadMemory(SpecialDiagInfoAddress, headerBuffer, out int bytesRead) && bytesRead == headerBuffer.Length)
Span<byte> exceptionRecordBuffer = stackalloc byte[Unsafe.SizeOf<EXCEPTION_RECORD64>()];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow up - encapsulate getting the SpecialDiagInfoHeader into a helper that this and HasDiagnosticInfo can use

Comment threadsrc/SOS/lldbplugin/services.cpp Outdated
@hoyosjs
hoyosjsforce-pushed the steveisok/macos-arm64-sos-fixes branch from 609e546 to 38d8dbaCompareMay 11, 2026 09:26
hoyosjsand others added 3 commits May 11, 2026 10:13
The flag was load-bearing only for the degenerate 'target with 0 modules
is a valid cached state' case. Module-count equality alone is sufficient
to detect cache freshness, and matches the previous behavior in every
real-world scenario.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@hoyosjs
hoyosjs enabled auto-merge (squash) May 11, 2026 23:28

@tommcdontommcdon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@hoyosjs
hoyosjs merged commit 9c40506 into dotnet:mainMay 12, 2026
19 checks passed
@hoyosjshoyosjs mentioned this pull request Jun 9, 2026
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 11, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@steveisok@tommcdon@hoyosjs
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

macOS SOS-in-lldb: speed up ReadVirtual and fix arm64 SpecialDiagInfo - #5823

Merged
hoyosjs merged 7 commits into
dotnet:mainfrom
steveisok:steveisok/macos-arm64-sos-fixes
May 12, 2026
Merged

macOS SOS-in-lldb: speed up ReadVirtual and fix arm64 SpecialDiagInfo#5823
hoyosjs merged 7 commits into
dotnet:mainfrom
steveisok:steveisok/macos-arm64-sos-fixes

Conversation

@steveisok

@steveisoksteveisok commented May 2, 2026

Copy link
Copy Markdown
Member

Two independent fixes to SOS-in-lldb on macOS. They surfaced together
while debugging extremely slow / failing clrthreads -managedexception
on Apple Silicon, but each stands on its own.

1. Speed up LLDBServices::ReadVirtual with a cached section table — applies to both arm64 and x86_64 macOS

When lldb's process.ReadMemory can't satisfy a read, the plugin falls
back to reading from on-disk module sections. For MachO core dumps this
is the common path, not the rare one — .text/code segments aren't
in the dump, so the DAC's metadata reads almost all hit the fallback.

The previous implementation iterated numModules × numSections per
call. Measured on a typical .NET process: ~3500 entries × multiple SWIG
calls each = ~44 µs per fallback. With ~5M ReadVirtual calls during a
single clrthreads -managedexception, that's ~200 s of pure iteration.

This change builds a sorted SectionRange table on first use and
binary-searches it with std::upper_bound. The table is invalidated
when the target's module count changes, and through the existing
ClearCache() path. The fix is arch-agnostic — x86_64 macOS sees the
same speedup since the underlying lldb MachO core behavior is identical.

Measured impact (macOS arm64, SOS.LineNums)

ConfigBeforeAfter
singlefile.* (live debug)~35 s15–21 s
prebuilt.11 (dump load)118 s28 s (~4×)
prebuilt.{10,9,8} (dump load)~120 s each39–42 s each (~3×)
Total LineNums wall time~13 min~3.7 min (~3.5×)

Not yet measured on x86_64 macOS, but the same code path is exercised
and the same root cause applies.

2. Fix SpecialDiagInfo address for arm64 macOS — arm64 only

The legacy address 0x7fffffff10000000 is beyond Apple Silicon's
47-bit user-space VM limit. createdump still writes a segment at that
address into the core file, but lldb's MachO core reader refuses reads
above 0x7FFF_FFFFFFFF, so SOS reports

Special diagnostics info read failed

and falls through to the slower managed-side path (or fails outright on
older builds without that fallback). On x86_64 macOS this address
remains valid, so the legacy constant is preserved there — no behavior
change for Intel Macs.

This change uses 0x00007ffffff10000 on arm64 macOS — the same address
already used on Linux/non-Apple 64-bit — and probes the legacy x86_64
address as a fallback so dumps produced by older createdump on x86_64
Macs continue to be recognized. The managed SpecialDiagInfo.cs path
is mirrored.

Note: full coverage on arm64 macOS requires the matching createdump
change in dotnet/runtime to flow through. Until then, dumps from
old createdump remain unreadable on arm64 macOS (the data sits at an
address lldb won't return) — same behavior as today, no regression.


CI on Windows/Linux is unaffected — the section cache only kicks in
when lldb's primary read fails (rare on those platforms), and the
SpecialDiagInfo legacy fallback preserves the previous address on every
non-arm64-macOS configuration.

steveisokand others added 2 commits May 2, 2026 12:09
The legacy SpecialDiagInfo address 0x7fffffff10000000 is beyond Apple
Silicon's 47-bit user-space VM limit. While createdump writes this
address into the core file's segment list, lldb's MachO core reader
rejects reads above 0x7FFF_FFFFFFFF, so SOS reports
'Special diagnostics info read failed' and falls back to the slower
managed-side path.
Use 0x00007ffffff10000 on arm64 macOS (matching the Linux/non-Apple
64-bit address) and probe the legacy x86_64 address as a fallback so
older dumps continue to be recognized on platforms where the legacy
address is readable.
Mirrored on the managed side (SpecialDiagInfo.cs) for the
DataTarget-based path.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
LLDBServices::ReadVirtual falls back to reading directly from native
module sections when lldb's process.ReadMemory cannot satisfy a read.
For dumps that don't include code/data segments (notably MachO core
files on macOS, where the .text segments aren't in the dump) this is
hit on the vast majority of reads.
The previous fallback iterated numModules x numSections per call
(~3500 entries on a typical .NET process; ~44 microseconds per fallback)
making clrthreads -managedexception and similar DAC-heavy commands
take ~100s on macOS arm64.
Build a sorted SectionRange table on first use and binary-search it
with std::upper_bound. The cache is invalidated when the target's
module count changes, and through the existing ClearCache() path.
Measured on macOS arm64 SOS LineNums tests with prebuilt runtime
configs: ~118s -> ~28s for net11 (~4x), and total LineNums wall
time from ~13 minutes to ~3.7 minutes (~3.5x).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 2, 2026 16:15
@steveisok
steveisok requested a review from a team as a code ownerMay 2, 2026 16:15

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes SOS-in-lldb behavior on macOS arm64 by moving SpecialDiagInfo to a 47-bit-valid address (with legacy fallback) and by significantly reducing ReadVirtual fallback overhead via a cached, binary-searched section table.

Changes:

  • Update SpecialDiagInfo address on Apple Silicon macOS and add legacy-address probing for older dumps.
  • Add a cached SectionRange table to speed up LLDBServices::ReadVirtual section-backed fallback reads.
  • Mirror the SpecialDiagInfo address fallback behavior in the managed SpecialDiagInfo implementation.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

FileDescription
src/SOS/lldbplugin/services.hIntroduces section-range cache fields and helpers for faster section fallback reads.
src/SOS/lldbplugin/services.cppImplements legacy SpecialDiagInfo fallback and the section-range cache (sort + upper_bound) for ReadVirtual.
src/SOS/inc/specialdiaginfo.hAdjusts arm64 macOS SpecialDiagInfo address and defines legacy address constant.
src/Microsoft.Diagnostics.DebugServices.Implementation/SpecialDiagInfo.csAdds macOS arm64-valid address probing + legacy fallback in managed reader.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadsrc/SOS/lldbplugin/services.cpp
Comment threadsrc/SOS/lldbplugin/services.cpp
Comment threadsrc/SOS/inc/specialdiaginfo.h Outdated
@steveisoksteveisok changed the title macOS arm64 SOS-in-lldb: fix SpecialDiagInfo address and speed up ReadVirtualmacOS SOS-in-lldb: speed up ReadVirtual and fix arm64 SpecialDiagInfoMay 2, 2026
* Section cache: guard endAddr/endOffset against uint64 overflow when
building the SectionRange table and when computing the containment
check, so a pathologically large section can't wrap and produce
spurious cache hits.
* GetExceptionRecord: when the SpecialDiagInfo signature matches but
the exception record can't be read (Version too low,
ExceptionRecordAddress=0, or read fails), continue to the next
candidate address instead of returning eagerly. Cheap and avoids
pinning behavior to the first matching address.
* specialdiaginfo.h: fix cross-reference comment to point at the actual
reader (LLDBServices::GetLastEventInformation), not a non-existent
SOSReadDiagInfoHeader symbol.
* ExtensionCommands SpecialDiagInfoHeader: add GetCandidateAddresses
alongside the existing GetAddress so the OSX path probes the
47-bit-valid address first then falls back to the legacy x86_64
address. CommandFormatHelpers.DisplaySpecialInfo now iterates the
candidates so 'runtimes' / extension commands recognize Apple Silicon
dumps the same way the lldb plugin and managed reader already do.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
return SpecialDiagInfoAddressMacOS64;
// Try the arm64-valid address first (also valid on x86_64 macOS for newer
// createdump output); fall back to the legacy x86_64 address.
yield return SpecialDiagInfoAddressMacOSArm64;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow up change - lets remove the fallback. 8.0 already had this address.

{
Span<byte> headerBuffer = stackalloc byte[Unsafe.SizeOf<SpecialDiagInfoHeader>()];
if (_memoryService.ReadMemory(SpecialDiagInfoAddress, headerBuffer, out int bytesRead) && bytesRead == headerBuffer.Length)
Span<byte> exceptionRecordBuffer = stackalloc byte[Unsafe.SizeOf<EXCEPTION_RECORD64>()];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow up - encapsulate getting the SpecialDiagInfoHeader into a helper that this and HasDiagnosticInfo can use

Comment threadsrc/SOS/lldbplugin/services.cpp Outdated
@hoyosjs
hoyosjsforce-pushed the steveisok/macos-arm64-sos-fixes branch from 609e546 to 38d8dbaCompareMay 11, 2026 09:26
hoyosjsand others added 3 commits May 11, 2026 10:13
The flag was load-bearing only for the degenerate 'target with 0 modules
is a valid cached state' case. Module-count equality alone is sufficient
to detect cache freshness, and matches the previous behavior in every
real-world scenario.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@hoyosjs
hoyosjs enabled auto-merge (squash) May 11, 2026 23:28

@tommcdontommcdon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@hoyosjs
hoyosjs merged commit 9c40506 into dotnet:mainMay 12, 2026
19 checks passed
@hoyosjshoyosjs mentioned this pull request Jun 9, 2026
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 11, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@steveisok@tommcdon@hoyosjs
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

macOS SOS-in-lldb: speed up ReadVirtual and fix arm64 SpecialDiagInfo - #5823

Merged
hoyosjs merged 7 commits into
dotnet:mainfrom
steveisok:steveisok/macos-arm64-sos-fixes
May 12, 2026
Merged

macOS SOS-in-lldb: speed up ReadVirtual and fix arm64 SpecialDiagInfo#5823
hoyosjs merged 7 commits into
dotnet:mainfrom
steveisok:steveisok/macos-arm64-sos-fixes

Conversation

@steveisok

@steveisoksteveisok commented May 2, 2026

Copy link
Copy Markdown
Member

Two independent fixes to SOS-in-lldb on macOS. They surfaced together
while debugging extremely slow / failing clrthreads -managedexception
on Apple Silicon, but each stands on its own.

1. Speed up LLDBServices::ReadVirtual with a cached section table — applies to both arm64 and x86_64 macOS

When lldb's process.ReadMemory can't satisfy a read, the plugin falls
back to reading from on-disk module sections. For MachO core dumps this
is the common path, not the rare one — .text/code segments aren't
in the dump, so the DAC's metadata reads almost all hit the fallback.

The previous implementation iterated numModules × numSections per
call. Measured on a typical .NET process: ~3500 entries × multiple SWIG
calls each = ~44 µs per fallback. With ~5M ReadVirtual calls during a
single clrthreads -managedexception, that's ~200 s of pure iteration.

This change builds a sorted SectionRange table on first use and
binary-searches it with std::upper_bound. The table is invalidated
when the target's module count changes, and through the existing
ClearCache() path. The fix is arch-agnostic — x86_64 macOS sees the
same speedup since the underlying lldb MachO core behavior is identical.

Measured impact (macOS arm64, SOS.LineNums)

ConfigBeforeAfter
singlefile.* (live debug)~35 s15–21 s
prebuilt.11 (dump load)118 s28 s (~4×)
prebuilt.{10,9,8} (dump load)~120 s each39–42 s each (~3×)
Total LineNums wall time~13 min~3.7 min (~3.5×)

Not yet measured on x86_64 macOS, but the same code path is exercised
and the same root cause applies.

2. Fix SpecialDiagInfo address for arm64 macOS — arm64 only

The legacy address 0x7fffffff10000000 is beyond Apple Silicon's
47-bit user-space VM limit. createdump still writes a segment at that
address into the core file, but lldb's MachO core reader refuses reads
above 0x7FFF_FFFFFFFF, so SOS reports

Special diagnostics info read failed

and falls through to the slower managed-side path (or fails outright on
older builds without that fallback). On x86_64 macOS this address
remains valid, so the legacy constant is preserved there — no behavior
change for Intel Macs.

This change uses 0x00007ffffff10000 on arm64 macOS — the same address
already used on Linux/non-Apple 64-bit — and probes the legacy x86_64
address as a fallback so dumps produced by older createdump on x86_64
Macs continue to be recognized. The managed SpecialDiagInfo.cs path
is mirrored.

Note: full coverage on arm64 macOS requires the matching createdump
change in dotnet/runtime to flow through. Until then, dumps from
old createdump remain unreadable on arm64 macOS (the data sits at an
address lldb won't return) — same behavior as today, no regression.


CI on Windows/Linux is unaffected — the section cache only kicks in
when lldb's primary read fails (rare on those platforms), and the
SpecialDiagInfo legacy fallback preserves the previous address on every
non-arm64-macOS configuration.

steveisokand others added 2 commits May 2, 2026 12:09
The legacy SpecialDiagInfo address 0x7fffffff10000000 is beyond Apple
Silicon's 47-bit user-space VM limit. While createdump writes this
address into the core file's segment list, lldb's MachO core reader
rejects reads above 0x7FFF_FFFFFFFF, so SOS reports
'Special diagnostics info read failed' and falls back to the slower
managed-side path.
Use 0x00007ffffff10000 on arm64 macOS (matching the Linux/non-Apple
64-bit address) and probe the legacy x86_64 address as a fallback so
older dumps continue to be recognized on platforms where the legacy
address is readable.
Mirrored on the managed side (SpecialDiagInfo.cs) for the
DataTarget-based path.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
LLDBServices::ReadVirtual falls back to reading directly from native
module sections when lldb's process.ReadMemory cannot satisfy a read.
For dumps that don't include code/data segments (notably MachO core
files on macOS, where the .text segments aren't in the dump) this is
hit on the vast majority of reads.
The previous fallback iterated numModules x numSections per call
(~3500 entries on a typical .NET process; ~44 microseconds per fallback)
making clrthreads -managedexception and similar DAC-heavy commands
take ~100s on macOS arm64.
Build a sorted SectionRange table on first use and binary-search it
with std::upper_bound. The cache is invalidated when the target's
module count changes, and through the existing ClearCache() path.
Measured on macOS arm64 SOS LineNums tests with prebuilt runtime
configs: ~118s -> ~28s for net11 (~4x), and total LineNums wall
time from ~13 minutes to ~3.7 minutes (~3.5x).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 2, 2026 16:15
@steveisok
steveisok requested a review from a team as a code ownerMay 2, 2026 16:15

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes SOS-in-lldb behavior on macOS arm64 by moving SpecialDiagInfo to a 47-bit-valid address (with legacy fallback) and by significantly reducing ReadVirtual fallback overhead via a cached, binary-searched section table.

Changes:

  • Update SpecialDiagInfo address on Apple Silicon macOS and add legacy-address probing for older dumps.
  • Add a cached SectionRange table to speed up LLDBServices::ReadVirtual section-backed fallback reads.
  • Mirror the SpecialDiagInfo address fallback behavior in the managed SpecialDiagInfo implementation.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

FileDescription
src/SOS/lldbplugin/services.hIntroduces section-range cache fields and helpers for faster section fallback reads.
src/SOS/lldbplugin/services.cppImplements legacy SpecialDiagInfo fallback and the section-range cache (sort + upper_bound) for ReadVirtual.
src/SOS/inc/specialdiaginfo.hAdjusts arm64 macOS SpecialDiagInfo address and defines legacy address constant.
src/Microsoft.Diagnostics.DebugServices.Implementation/SpecialDiagInfo.csAdds macOS arm64-valid address probing + legacy fallback in managed reader.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadsrc/SOS/lldbplugin/services.cpp
Comment threadsrc/SOS/lldbplugin/services.cpp
Comment threadsrc/SOS/inc/specialdiaginfo.h Outdated
@steveisoksteveisok changed the title macOS arm64 SOS-in-lldb: fix SpecialDiagInfo address and speed up ReadVirtualmacOS SOS-in-lldb: speed up ReadVirtual and fix arm64 SpecialDiagInfoMay 2, 2026
* Section cache: guard endAddr/endOffset against uint64 overflow when
building the SectionRange table and when computing the containment
check, so a pathologically large section can't wrap and produce
spurious cache hits.
* GetExceptionRecord: when the SpecialDiagInfo signature matches but
the exception record can't be read (Version too low,
ExceptionRecordAddress=0, or read fails), continue to the next
candidate address instead of returning eagerly. Cheap and avoids
pinning behavior to the first matching address.
* specialdiaginfo.h: fix cross-reference comment to point at the actual
reader (LLDBServices::GetLastEventInformation), not a non-existent
SOSReadDiagInfoHeader symbol.
* ExtensionCommands SpecialDiagInfoHeader: add GetCandidateAddresses
alongside the existing GetAddress so the OSX path probes the
47-bit-valid address first then falls back to the legacy x86_64
address. CommandFormatHelpers.DisplaySpecialInfo now iterates the
candidates so 'runtimes' / extension commands recognize Apple Silicon
dumps the same way the lldb plugin and managed reader already do.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
return SpecialDiagInfoAddressMacOS64;
// Try the arm64-valid address first (also valid on x86_64 macOS for newer
// createdump output); fall back to the legacy x86_64 address.
yield return SpecialDiagInfoAddressMacOSArm64;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow up change - lets remove the fallback. 8.0 already had this address.

{
Span<byte> headerBuffer = stackalloc byte[Unsafe.SizeOf<SpecialDiagInfoHeader>()];
if (_memoryService.ReadMemory(SpecialDiagInfoAddress, headerBuffer, out int bytesRead) && bytesRead == headerBuffer.Length)
Span<byte> exceptionRecordBuffer = stackalloc byte[Unsafe.SizeOf<EXCEPTION_RECORD64>()];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow up - encapsulate getting the SpecialDiagInfoHeader into a helper that this and HasDiagnosticInfo can use

Comment threadsrc/SOS/lldbplugin/services.cpp Outdated
@hoyosjs
hoyosjsforce-pushed the steveisok/macos-arm64-sos-fixes branch from 609e546 to 38d8dbaCompareMay 11, 2026 09:26
hoyosjsand others added 3 commits May 11, 2026 10:13
The flag was load-bearing only for the degenerate 'target with 0 modules
is a valid cached state' case. Module-count equality alone is sufficient
to detect cache freshness, and matches the previous behavior in every
real-world scenario.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@hoyosjs
hoyosjs enabled auto-merge (squash) May 11, 2026 23:28

@tommcdontommcdon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@hoyosjs
hoyosjs merged commit 9c40506 into dotnet:mainMay 12, 2026
19 checks passed
@hoyosjshoyosjs mentioned this pull request Jun 9, 2026
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 11, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@steveisok@tommcdon@hoyosjs
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

macOS SOS-in-lldb: speed up ReadVirtual and fix arm64 SpecialDiagInfo - #5823

Merged
hoyosjs merged 7 commits into
dotnet:mainfrom
steveisok:steveisok/macos-arm64-sos-fixes
May 12, 2026
Merged

macOS SOS-in-lldb: speed up ReadVirtual and fix arm64 SpecialDiagInfo#5823
hoyosjs merged 7 commits into
dotnet:mainfrom
steveisok:steveisok/macos-arm64-sos-fixes

Conversation

@steveisok

@steveisoksteveisok commented May 2, 2026

Copy link
Copy Markdown
Member

Two independent fixes to SOS-in-lldb on macOS. They surfaced together
while debugging extremely slow / failing clrthreads -managedexception
on Apple Silicon, but each stands on its own.

1. Speed up LLDBServices::ReadVirtual with a cached section table — applies to both arm64 and x86_64 macOS

When lldb's process.ReadMemory can't satisfy a read, the plugin falls
back to reading from on-disk module sections. For MachO core dumps this
is the common path, not the rare one — .text/code segments aren't
in the dump, so the DAC's metadata reads almost all hit the fallback.

The previous implementation iterated numModules × numSections per
call. Measured on a typical .NET process: ~3500 entries × multiple SWIG
calls each = ~44 µs per fallback. With ~5M ReadVirtual calls during a
single clrthreads -managedexception, that's ~200 s of pure iteration.

This change builds a sorted SectionRange table on first use and
binary-searches it with std::upper_bound. The table is invalidated
when the target's module count changes, and through the existing
ClearCache() path. The fix is arch-agnostic — x86_64 macOS sees the
same speedup since the underlying lldb MachO core behavior is identical.

Measured impact (macOS arm64, SOS.LineNums)

ConfigBeforeAfter
singlefile.* (live debug)~35 s15–21 s
prebuilt.11 (dump load)118 s28 s (~4×)
prebuilt.{10,9,8} (dump load)~120 s each39–42 s each (~3×)
Total LineNums wall time~13 min~3.7 min (~3.5×)

Not yet measured on x86_64 macOS, but the same code path is exercised
and the same root cause applies.

2. Fix SpecialDiagInfo address for arm64 macOS — arm64 only

The legacy address 0x7fffffff10000000 is beyond Apple Silicon's
47-bit user-space VM limit. createdump still writes a segment at that
address into the core file, but lldb's MachO core reader refuses reads
above 0x7FFF_FFFFFFFF, so SOS reports

Special diagnostics info read failed

and falls through to the slower managed-side path (or fails outright on
older builds without that fallback). On x86_64 macOS this address
remains valid, so the legacy constant is preserved there — no behavior
change for Intel Macs.

This change uses 0x00007ffffff10000 on arm64 macOS — the same address
already used on Linux/non-Apple 64-bit — and probes the legacy x86_64
address as a fallback so dumps produced by older createdump on x86_64
Macs continue to be recognized. The managed SpecialDiagInfo.cs path
is mirrored.

Note: full coverage on arm64 macOS requires the matching createdump
change in dotnet/runtime to flow through. Until then, dumps from
old createdump remain unreadable on arm64 macOS (the data sits at an
address lldb won't return) — same behavior as today, no regression.


CI on Windows/Linux is unaffected — the section cache only kicks in
when lldb's primary read fails (rare on those platforms), and the
SpecialDiagInfo legacy fallback preserves the previous address on every
non-arm64-macOS configuration.

steveisokand others added 2 commits May 2, 2026 12:09
The legacy SpecialDiagInfo address 0x7fffffff10000000 is beyond Apple
Silicon's 47-bit user-space VM limit. While createdump writes this
address into the core file's segment list, lldb's MachO core reader
rejects reads above 0x7FFF_FFFFFFFF, so SOS reports
'Special diagnostics info read failed' and falls back to the slower
managed-side path.
Use 0x00007ffffff10000 on arm64 macOS (matching the Linux/non-Apple
64-bit address) and probe the legacy x86_64 address as a fallback so
older dumps continue to be recognized on platforms where the legacy
address is readable.
Mirrored on the managed side (SpecialDiagInfo.cs) for the
DataTarget-based path.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
LLDBServices::ReadVirtual falls back to reading directly from native
module sections when lldb's process.ReadMemory cannot satisfy a read.
For dumps that don't include code/data segments (notably MachO core
files on macOS, where the .text segments aren't in the dump) this is
hit on the vast majority of reads.
The previous fallback iterated numModules x numSections per call
(~3500 entries on a typical .NET process; ~44 microseconds per fallback)
making clrthreads -managedexception and similar DAC-heavy commands
take ~100s on macOS arm64.
Build a sorted SectionRange table on first use and binary-search it
with std::upper_bound. The cache is invalidated when the target's
module count changes, and through the existing ClearCache() path.
Measured on macOS arm64 SOS LineNums tests with prebuilt runtime
configs: ~118s -> ~28s for net11 (~4x), and total LineNums wall
time from ~13 minutes to ~3.7 minutes (~3.5x).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 2, 2026 16:15
@steveisok
steveisok requested a review from a team as a code ownerMay 2, 2026 16:15

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes SOS-in-lldb behavior on macOS arm64 by moving SpecialDiagInfo to a 47-bit-valid address (with legacy fallback) and by significantly reducing ReadVirtual fallback overhead via a cached, binary-searched section table.

Changes:

  • Update SpecialDiagInfo address on Apple Silicon macOS and add legacy-address probing for older dumps.
  • Add a cached SectionRange table to speed up LLDBServices::ReadVirtual section-backed fallback reads.
  • Mirror the SpecialDiagInfo address fallback behavior in the managed SpecialDiagInfo implementation.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

FileDescription
src/SOS/lldbplugin/services.hIntroduces section-range cache fields and helpers for faster section fallback reads.
src/SOS/lldbplugin/services.cppImplements legacy SpecialDiagInfo fallback and the section-range cache (sort + upper_bound) for ReadVirtual.
src/SOS/inc/specialdiaginfo.hAdjusts arm64 macOS SpecialDiagInfo address and defines legacy address constant.
src/Microsoft.Diagnostics.DebugServices.Implementation/SpecialDiagInfo.csAdds macOS arm64-valid address probing + legacy fallback in managed reader.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadsrc/SOS/lldbplugin/services.cpp
Comment threadsrc/SOS/lldbplugin/services.cpp
Comment threadsrc/SOS/inc/specialdiaginfo.h Outdated
@steveisoksteveisok changed the title macOS arm64 SOS-in-lldb: fix SpecialDiagInfo address and speed up ReadVirtualmacOS SOS-in-lldb: speed up ReadVirtual and fix arm64 SpecialDiagInfoMay 2, 2026
* Section cache: guard endAddr/endOffset against uint64 overflow when
building the SectionRange table and when computing the containment
check, so a pathologically large section can't wrap and produce
spurious cache hits.
* GetExceptionRecord: when the SpecialDiagInfo signature matches but
the exception record can't be read (Version too low,
ExceptionRecordAddress=0, or read fails), continue to the next
candidate address instead of returning eagerly. Cheap and avoids
pinning behavior to the first matching address.
* specialdiaginfo.h: fix cross-reference comment to point at the actual
reader (LLDBServices::GetLastEventInformation), not a non-existent
SOSReadDiagInfoHeader symbol.
* ExtensionCommands SpecialDiagInfoHeader: add GetCandidateAddresses
alongside the existing GetAddress so the OSX path probes the
47-bit-valid address first then falls back to the legacy x86_64
address. CommandFormatHelpers.DisplaySpecialInfo now iterates the
candidates so 'runtimes' / extension commands recognize Apple Silicon
dumps the same way the lldb plugin and managed reader already do.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
return SpecialDiagInfoAddressMacOS64;
// Try the arm64-valid address first (also valid on x86_64 macOS for newer
// createdump output); fall back to the legacy x86_64 address.
yield return SpecialDiagInfoAddressMacOSArm64;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow up change - lets remove the fallback. 8.0 already had this address.

{
Span<byte> headerBuffer = stackalloc byte[Unsafe.SizeOf<SpecialDiagInfoHeader>()];
if (_memoryService.ReadMemory(SpecialDiagInfoAddress, headerBuffer, out int bytesRead) && bytesRead == headerBuffer.Length)
Span<byte> exceptionRecordBuffer = stackalloc byte[Unsafe.SizeOf<EXCEPTION_RECORD64>()];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow up - encapsulate getting the SpecialDiagInfoHeader into a helper that this and HasDiagnosticInfo can use

Comment threadsrc/SOS/lldbplugin/services.cpp Outdated
@hoyosjs
hoyosjsforce-pushed the steveisok/macos-arm64-sos-fixes branch from 609e546 to 38d8dbaCompareMay 11, 2026 09:26
hoyosjsand others added 3 commits May 11, 2026 10:13
The flag was load-bearing only for the degenerate 'target with 0 modules
is a valid cached state' case. Module-count equality alone is sufficient
to detect cache freshness, and matches the previous behavior in every
real-world scenario.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@hoyosjs
hoyosjs enabled auto-merge (squash) May 11, 2026 23:28

@tommcdontommcdon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@hoyosjs
hoyosjs merged commit 9c40506 into dotnet:mainMay 12, 2026
19 checks passed
@hoyosjshoyosjs mentioned this pull request Jun 9, 2026
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 11, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@steveisok@tommcdon@hoyosjs
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

macOS SOS-in-lldb: speed up ReadVirtual and fix arm64 SpecialDiagInfo - #5823

Merged
hoyosjs merged 7 commits into
dotnet:mainfrom
steveisok:steveisok/macos-arm64-sos-fixes
May 12, 2026
Merged

macOS SOS-in-lldb: speed up ReadVirtual and fix arm64 SpecialDiagInfo#5823
hoyosjs merged 7 commits into
dotnet:mainfrom
steveisok:steveisok/macos-arm64-sos-fixes

Conversation

@steveisok

@steveisoksteveisok commented May 2, 2026

Copy link
Copy Markdown
Member

Two independent fixes to SOS-in-lldb on macOS. They surfaced together
while debugging extremely slow / failing clrthreads -managedexception
on Apple Silicon, but each stands on its own.

1. Speed up LLDBServices::ReadVirtual with a cached section table — applies to both arm64 and x86_64 macOS

When lldb's process.ReadMemory can't satisfy a read, the plugin falls
back to reading from on-disk module sections. For MachO core dumps this
is the common path, not the rare one — .text/code segments aren't
in the dump, so the DAC's metadata reads almost all hit the fallback.

The previous implementation iterated numModules × numSections per
call. Measured on a typical .NET process: ~3500 entries × multiple SWIG
calls each = ~44 µs per fallback. With ~5M ReadVirtual calls during a
single clrthreads -managedexception, that's ~200 s of pure iteration.

This change builds a sorted SectionRange table on first use and
binary-searches it with std::upper_bound. The table is invalidated
when the target's module count changes, and through the existing
ClearCache() path. The fix is arch-agnostic — x86_64 macOS sees the
same speedup since the underlying lldb MachO core behavior is identical.

Measured impact (macOS arm64, SOS.LineNums)

ConfigBeforeAfter
singlefile.* (live debug)~35 s15–21 s
prebuilt.11 (dump load)118 s28 s (~4×)
prebuilt.{10,9,8} (dump load)~120 s each39–42 s each (~3×)
Total LineNums wall time~13 min~3.7 min (~3.5×)

Not yet measured on x86_64 macOS, but the same code path is exercised
and the same root cause applies.

2. Fix SpecialDiagInfo address for arm64 macOS — arm64 only

The legacy address 0x7fffffff10000000 is beyond Apple Silicon's
47-bit user-space VM limit. createdump still writes a segment at that
address into the core file, but lldb's MachO core reader refuses reads
above 0x7FFF_FFFFFFFF, so SOS reports

Special diagnostics info read failed

and falls through to the slower managed-side path (or fails outright on
older builds without that fallback). On x86_64 macOS this address
remains valid, so the legacy constant is preserved there — no behavior
change for Intel Macs.

This change uses 0x00007ffffff10000 on arm64 macOS — the same address
already used on Linux/non-Apple 64-bit — and probes the legacy x86_64
address as a fallback so dumps produced by older createdump on x86_64
Macs continue to be recognized. The managed SpecialDiagInfo.cs path
is mirrored.

Note: full coverage on arm64 macOS requires the matching createdump
change in dotnet/runtime to flow through. Until then, dumps from
old createdump remain unreadable on arm64 macOS (the data sits at an
address lldb won't return) — same behavior as today, no regression.


CI on Windows/Linux is unaffected — the section cache only kicks in
when lldb's primary read fails (rare on those platforms), and the
SpecialDiagInfo legacy fallback preserves the previous address on every
non-arm64-macOS configuration.

steveisokand others added 2 commits May 2, 2026 12:09
The legacy SpecialDiagInfo address 0x7fffffff10000000 is beyond Apple
Silicon's 47-bit user-space VM limit. While createdump writes this
address into the core file's segment list, lldb's MachO core reader
rejects reads above 0x7FFF_FFFFFFFF, so SOS reports
'Special diagnostics info read failed' and falls back to the slower
managed-side path.
Use 0x00007ffffff10000 on arm64 macOS (matching the Linux/non-Apple
64-bit address) and probe the legacy x86_64 address as a fallback so
older dumps continue to be recognized on platforms where the legacy
address is readable.
Mirrored on the managed side (SpecialDiagInfo.cs) for the
DataTarget-based path.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
LLDBServices::ReadVirtual falls back to reading directly from native
module sections when lldb's process.ReadMemory cannot satisfy a read.
For dumps that don't include code/data segments (notably MachO core
files on macOS, where the .text segments aren't in the dump) this is
hit on the vast majority of reads.
The previous fallback iterated numModules x numSections per call
(~3500 entries on a typical .NET process; ~44 microseconds per fallback)
making clrthreads -managedexception and similar DAC-heavy commands
take ~100s on macOS arm64.
Build a sorted SectionRange table on first use and binary-search it
with std::upper_bound. The cache is invalidated when the target's
module count changes, and through the existing ClearCache() path.
Measured on macOS arm64 SOS LineNums tests with prebuilt runtime
configs: ~118s -> ~28s for net11 (~4x), and total LineNums wall
time from ~13 minutes to ~3.7 minutes (~3.5x).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 2, 2026 16:15
@steveisok
steveisok requested a review from a team as a code ownerMay 2, 2026 16:15

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes SOS-in-lldb behavior on macOS arm64 by moving SpecialDiagInfo to a 47-bit-valid address (with legacy fallback) and by significantly reducing ReadVirtual fallback overhead via a cached, binary-searched section table.

Changes:

  • Update SpecialDiagInfo address on Apple Silicon macOS and add legacy-address probing for older dumps.
  • Add a cached SectionRange table to speed up LLDBServices::ReadVirtual section-backed fallback reads.
  • Mirror the SpecialDiagInfo address fallback behavior in the managed SpecialDiagInfo implementation.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

FileDescription
src/SOS/lldbplugin/services.hIntroduces section-range cache fields and helpers for faster section fallback reads.
src/SOS/lldbplugin/services.cppImplements legacy SpecialDiagInfo fallback and the section-range cache (sort + upper_bound) for ReadVirtual.
src/SOS/inc/specialdiaginfo.hAdjusts arm64 macOS SpecialDiagInfo address and defines legacy address constant.
src/Microsoft.Diagnostics.DebugServices.Implementation/SpecialDiagInfo.csAdds macOS arm64-valid address probing + legacy fallback in managed reader.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadsrc/SOS/lldbplugin/services.cpp
Comment threadsrc/SOS/lldbplugin/services.cpp
Comment threadsrc/SOS/inc/specialdiaginfo.h Outdated
@steveisoksteveisok changed the title macOS arm64 SOS-in-lldb: fix SpecialDiagInfo address and speed up ReadVirtualmacOS SOS-in-lldb: speed up ReadVirtual and fix arm64 SpecialDiagInfoMay 2, 2026
* Section cache: guard endAddr/endOffset against uint64 overflow when
building the SectionRange table and when computing the containment
check, so a pathologically large section can't wrap and produce
spurious cache hits.
* GetExceptionRecord: when the SpecialDiagInfo signature matches but
the exception record can't be read (Version too low,
ExceptionRecordAddress=0, or read fails), continue to the next
candidate address instead of returning eagerly. Cheap and avoids
pinning behavior to the first matching address.
* specialdiaginfo.h: fix cross-reference comment to point at the actual
reader (LLDBServices::GetLastEventInformation), not a non-existent
SOSReadDiagInfoHeader symbol.
* ExtensionCommands SpecialDiagInfoHeader: add GetCandidateAddresses
alongside the existing GetAddress so the OSX path probes the
47-bit-valid address first then falls back to the legacy x86_64
address. CommandFormatHelpers.DisplaySpecialInfo now iterates the
candidates so 'runtimes' / extension commands recognize Apple Silicon
dumps the same way the lldb plugin and managed reader already do.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
return SpecialDiagInfoAddressMacOS64;
// Try the arm64-valid address first (also valid on x86_64 macOS for newer
// createdump output); fall back to the legacy x86_64 address.
yield return SpecialDiagInfoAddressMacOSArm64;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow up change - lets remove the fallback. 8.0 already had this address.

{
Span<byte> headerBuffer = stackalloc byte[Unsafe.SizeOf<SpecialDiagInfoHeader>()];
if (_memoryService.ReadMemory(SpecialDiagInfoAddress, headerBuffer, out int bytesRead) && bytesRead == headerBuffer.Length)
Span<byte> exceptionRecordBuffer = stackalloc byte[Unsafe.SizeOf<EXCEPTION_RECORD64>()];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow up - encapsulate getting the SpecialDiagInfoHeader into a helper that this and HasDiagnosticInfo can use

Comment threadsrc/SOS/lldbplugin/services.cpp Outdated
@hoyosjs
hoyosjsforce-pushed the steveisok/macos-arm64-sos-fixes branch from 609e546 to 38d8dbaCompareMay 11, 2026 09:26
hoyosjsand others added 3 commits May 11, 2026 10:13
The flag was load-bearing only for the degenerate 'target with 0 modules
is a valid cached state' case. Module-count equality alone is sufficient
to detect cache freshness, and matches the previous behavior in every
real-world scenario.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@hoyosjs
hoyosjs enabled auto-merge (squash) May 11, 2026 23:28

@tommcdontommcdon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@hoyosjs
hoyosjs merged commit 9c40506 into dotnet:mainMay 12, 2026
19 checks passed
@hoyosjshoyosjs mentioned this pull request Jun 9, 2026
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 11, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@steveisok@tommcdon@hoyosjs
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

macOS SOS-in-lldb: speed up ReadVirtual and fix arm64 SpecialDiagInfo - #5823

Merged
hoyosjs merged 7 commits into
dotnet:mainfrom
steveisok:steveisok/macos-arm64-sos-fixes
May 12, 2026
Merged

macOS SOS-in-lldb: speed up ReadVirtual and fix arm64 SpecialDiagInfo#5823
hoyosjs merged 7 commits into
dotnet:mainfrom
steveisok:steveisok/macos-arm64-sos-fixes

Conversation

@steveisok

@steveisoksteveisok commented May 2, 2026

Copy link
Copy Markdown
Member

Two independent fixes to SOS-in-lldb on macOS. They surfaced together
while debugging extremely slow / failing clrthreads -managedexception
on Apple Silicon, but each stands on its own.

1. Speed up LLDBServices::ReadVirtual with a cached section table — applies to both arm64 and x86_64 macOS

When lldb's process.ReadMemory can't satisfy a read, the plugin falls
back to reading from on-disk module sections. For MachO core dumps this
is the common path, not the rare one — .text/code segments aren't
in the dump, so the DAC's metadata reads almost all hit the fallback.

The previous implementation iterated numModules × numSections per
call. Measured on a typical .NET process: ~3500 entries × multiple SWIG
calls each = ~44 µs per fallback. With ~5M ReadVirtual calls during a
single clrthreads -managedexception, that's ~200 s of pure iteration.

This change builds a sorted SectionRange table on first use and
binary-searches it with std::upper_bound. The table is invalidated
when the target's module count changes, and through the existing
ClearCache() path. The fix is arch-agnostic — x86_64 macOS sees the
same speedup since the underlying lldb MachO core behavior is identical.

Measured impact (macOS arm64, SOS.LineNums)

ConfigBeforeAfter
singlefile.* (live debug)~35 s15–21 s
prebuilt.11 (dump load)118 s28 s (~4×)
prebuilt.{10,9,8} (dump load)~120 s each39–42 s each (~3×)
Total LineNums wall time~13 min~3.7 min (~3.5×)

Not yet measured on x86_64 macOS, but the same code path is exercised
and the same root cause applies.

2. Fix SpecialDiagInfo address for arm64 macOS — arm64 only

The legacy address 0x7fffffff10000000 is beyond Apple Silicon's
47-bit user-space VM limit. createdump still writes a segment at that
address into the core file, but lldb's MachO core reader refuses reads
above 0x7FFF_FFFFFFFF, so SOS reports

Special diagnostics info read failed

and falls through to the slower managed-side path (or fails outright on
older builds without that fallback). On x86_64 macOS this address
remains valid, so the legacy constant is preserved there — no behavior
change for Intel Macs.

This change uses 0x00007ffffff10000 on arm64 macOS — the same address
already used on Linux/non-Apple 64-bit — and probes the legacy x86_64
address as a fallback so dumps produced by older createdump on x86_64
Macs continue to be recognized. The managed SpecialDiagInfo.cs path
is mirrored.

Note: full coverage on arm64 macOS requires the matching createdump
change in dotnet/runtime to flow through. Until then, dumps from
old createdump remain unreadable on arm64 macOS (the data sits at an
address lldb won't return) — same behavior as today, no regression.


CI on Windows/Linux is unaffected — the section cache only kicks in
when lldb's primary read fails (rare on those platforms), and the
SpecialDiagInfo legacy fallback preserves the previous address on every
non-arm64-macOS configuration.

steveisokand others added 2 commits May 2, 2026 12:09
The legacy SpecialDiagInfo address 0x7fffffff10000000 is beyond Apple
Silicon's 47-bit user-space VM limit. While createdump writes this
address into the core file's segment list, lldb's MachO core reader
rejects reads above 0x7FFF_FFFFFFFF, so SOS reports
'Special diagnostics info read failed' and falls back to the slower
managed-side path.
Use 0x00007ffffff10000 on arm64 macOS (matching the Linux/non-Apple
64-bit address) and probe the legacy x86_64 address as a fallback so
older dumps continue to be recognized on platforms where the legacy
address is readable.
Mirrored on the managed side (SpecialDiagInfo.cs) for the
DataTarget-based path.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
LLDBServices::ReadVirtual falls back to reading directly from native
module sections when lldb's process.ReadMemory cannot satisfy a read.
For dumps that don't include code/data segments (notably MachO core
files on macOS, where the .text segments aren't in the dump) this is
hit on the vast majority of reads.
The previous fallback iterated numModules x numSections per call
(~3500 entries on a typical .NET process; ~44 microseconds per fallback)
making clrthreads -managedexception and similar DAC-heavy commands
take ~100s on macOS arm64.
Build a sorted SectionRange table on first use and binary-search it
with std::upper_bound. The cache is invalidated when the target's
module count changes, and through the existing ClearCache() path.
Measured on macOS arm64 SOS LineNums tests with prebuilt runtime
configs: ~118s -> ~28s for net11 (~4x), and total LineNums wall
time from ~13 minutes to ~3.7 minutes (~3.5x).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 2, 2026 16:15
@steveisok
steveisok requested a review from a team as a code ownerMay 2, 2026 16:15

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes SOS-in-lldb behavior on macOS arm64 by moving SpecialDiagInfo to a 47-bit-valid address (with legacy fallback) and by significantly reducing ReadVirtual fallback overhead via a cached, binary-searched section table.

Changes:

  • Update SpecialDiagInfo address on Apple Silicon macOS and add legacy-address probing for older dumps.
  • Add a cached SectionRange table to speed up LLDBServices::ReadVirtual section-backed fallback reads.
  • Mirror the SpecialDiagInfo address fallback behavior in the managed SpecialDiagInfo implementation.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

FileDescription
src/SOS/lldbplugin/services.hIntroduces section-range cache fields and helpers for faster section fallback reads.
src/SOS/lldbplugin/services.cppImplements legacy SpecialDiagInfo fallback and the section-range cache (sort + upper_bound) for ReadVirtual.
src/SOS/inc/specialdiaginfo.hAdjusts arm64 macOS SpecialDiagInfo address and defines legacy address constant.
src/Microsoft.Diagnostics.DebugServices.Implementation/SpecialDiagInfo.csAdds macOS arm64-valid address probing + legacy fallback in managed reader.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadsrc/SOS/lldbplugin/services.cpp
Comment threadsrc/SOS/lldbplugin/services.cpp
Comment threadsrc/SOS/inc/specialdiaginfo.h Outdated
@steveisoksteveisok changed the title macOS arm64 SOS-in-lldb: fix SpecialDiagInfo address and speed up ReadVirtualmacOS SOS-in-lldb: speed up ReadVirtual and fix arm64 SpecialDiagInfoMay 2, 2026
* Section cache: guard endAddr/endOffset against uint64 overflow when
building the SectionRange table and when computing the containment
check, so a pathologically large section can't wrap and produce
spurious cache hits.
* GetExceptionRecord: when the SpecialDiagInfo signature matches but
the exception record can't be read (Version too low,
ExceptionRecordAddress=0, or read fails), continue to the next
candidate address instead of returning eagerly. Cheap and avoids
pinning behavior to the first matching address.
* specialdiaginfo.h: fix cross-reference comment to point at the actual
reader (LLDBServices::GetLastEventInformation), not a non-existent
SOSReadDiagInfoHeader symbol.
* ExtensionCommands SpecialDiagInfoHeader: add GetCandidateAddresses
alongside the existing GetAddress so the OSX path probes the
47-bit-valid address first then falls back to the legacy x86_64
address. CommandFormatHelpers.DisplaySpecialInfo now iterates the
candidates so 'runtimes' / extension commands recognize Apple Silicon
dumps the same way the lldb plugin and managed reader already do.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
return SpecialDiagInfoAddressMacOS64;
// Try the arm64-valid address first (also valid on x86_64 macOS for newer
// createdump output); fall back to the legacy x86_64 address.
yield return SpecialDiagInfoAddressMacOSArm64;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow up change - lets remove the fallback. 8.0 already had this address.

{
Span<byte> headerBuffer = stackalloc byte[Unsafe.SizeOf<SpecialDiagInfoHeader>()];
if (_memoryService.ReadMemory(SpecialDiagInfoAddress, headerBuffer, out int bytesRead) && bytesRead == headerBuffer.Length)
Span<byte> exceptionRecordBuffer = stackalloc byte[Unsafe.SizeOf<EXCEPTION_RECORD64>()];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow up - encapsulate getting the SpecialDiagInfoHeader into a helper that this and HasDiagnosticInfo can use

Comment threadsrc/SOS/lldbplugin/services.cpp Outdated
@hoyosjs
hoyosjsforce-pushed the steveisok/macos-arm64-sos-fixes branch from 609e546 to 38d8dbaCompareMay 11, 2026 09:26
hoyosjsand others added 3 commits May 11, 2026 10:13
The flag was load-bearing only for the degenerate 'target with 0 modules
is a valid cached state' case. Module-count equality alone is sufficient
to detect cache freshness, and matches the previous behavior in every
real-world scenario.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@hoyosjs
hoyosjs enabled auto-merge (squash) May 11, 2026 23:28

@tommcdontommcdon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@hoyosjs
hoyosjs merged commit 9c40506 into dotnet:mainMay 12, 2026
19 checks passed
@hoyosjshoyosjs mentioned this pull request Jun 9, 2026
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 11, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@steveisok@tommcdon@hoyosjs
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

macOS SOS-in-lldb: speed up ReadVirtual and fix arm64 SpecialDiagInfo - #5823

Merged
hoyosjs merged 7 commits into
dotnet:mainfrom
steveisok:steveisok/macos-arm64-sos-fixes
May 12, 2026
Merged

macOS SOS-in-lldb: speed up ReadVirtual and fix arm64 SpecialDiagInfo#5823
hoyosjs merged 7 commits into
dotnet:mainfrom
steveisok:steveisok/macos-arm64-sos-fixes

Conversation

@steveisok

@steveisoksteveisok commented May 2, 2026

Copy link
Copy Markdown
Member

Two independent fixes to SOS-in-lldb on macOS. They surfaced together
while debugging extremely slow / failing clrthreads -managedexception
on Apple Silicon, but each stands on its own.

1. Speed up LLDBServices::ReadVirtual with a cached section table — applies to both arm64 and x86_64 macOS

When lldb's process.ReadMemory can't satisfy a read, the plugin falls
back to reading from on-disk module sections. For MachO core dumps this
is the common path, not the rare one — .text/code segments aren't
in the dump, so the DAC's metadata reads almost all hit the fallback.

The previous implementation iterated numModules × numSections per
call. Measured on a typical .NET process: ~3500 entries × multiple SWIG
calls each = ~44 µs per fallback. With ~5M ReadVirtual calls during a
single clrthreads -managedexception, that's ~200 s of pure iteration.

This change builds a sorted SectionRange table on first use and
binary-searches it with std::upper_bound. The table is invalidated
when the target's module count changes, and through the existing
ClearCache() path. The fix is arch-agnostic — x86_64 macOS sees the
same speedup since the underlying lldb MachO core behavior is identical.

Measured impact (macOS arm64, SOS.LineNums)

ConfigBeforeAfter
singlefile.* (live debug)~35 s15–21 s
prebuilt.11 (dump load)118 s28 s (~4×)
prebuilt.{10,9,8} (dump load)~120 s each39–42 s each (~3×)
Total LineNums wall time~13 min~3.7 min (~3.5×)

Not yet measured on x86_64 macOS, but the same code path is exercised
and the same root cause applies.

2. Fix SpecialDiagInfo address for arm64 macOS — arm64 only

The legacy address 0x7fffffff10000000 is beyond Apple Silicon's
47-bit user-space VM limit. createdump still writes a segment at that
address into the core file, but lldb's MachO core reader refuses reads
above 0x7FFF_FFFFFFFF, so SOS reports

Special diagnostics info read failed

and falls through to the slower managed-side path (or fails outright on
older builds without that fallback). On x86_64 macOS this address
remains valid, so the legacy constant is preserved there — no behavior
change for Intel Macs.

This change uses 0x00007ffffff10000 on arm64 macOS — the same address
already used on Linux/non-Apple 64-bit — and probes the legacy x86_64
address as a fallback so dumps produced by older createdump on x86_64
Macs continue to be recognized. The managed SpecialDiagInfo.cs path
is mirrored.

Note: full coverage on arm64 macOS requires the matching createdump
change in dotnet/runtime to flow through. Until then, dumps from
old createdump remain unreadable on arm64 macOS (the data sits at an
address lldb won't return) — same behavior as today, no regression.


CI on Windows/Linux is unaffected — the section cache only kicks in
when lldb's primary read fails (rare on those platforms), and the
SpecialDiagInfo legacy fallback preserves the previous address on every
non-arm64-macOS configuration.

steveisokand others added 2 commits May 2, 2026 12:09
The legacy SpecialDiagInfo address 0x7fffffff10000000 is beyond Apple
Silicon's 47-bit user-space VM limit. While createdump writes this
address into the core file's segment list, lldb's MachO core reader
rejects reads above 0x7FFF_FFFFFFFF, so SOS reports
'Special diagnostics info read failed' and falls back to the slower
managed-side path.
Use 0x00007ffffff10000 on arm64 macOS (matching the Linux/non-Apple
64-bit address) and probe the legacy x86_64 address as a fallback so
older dumps continue to be recognized on platforms where the legacy
address is readable.
Mirrored on the managed side (SpecialDiagInfo.cs) for the
DataTarget-based path.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
LLDBServices::ReadVirtual falls back to reading directly from native
module sections when lldb's process.ReadMemory cannot satisfy a read.
For dumps that don't include code/data segments (notably MachO core
files on macOS, where the .text segments aren't in the dump) this is
hit on the vast majority of reads.
The previous fallback iterated numModules x numSections per call
(~3500 entries on a typical .NET process; ~44 microseconds per fallback)
making clrthreads -managedexception and similar DAC-heavy commands
take ~100s on macOS arm64.
Build a sorted SectionRange table on first use and binary-search it
with std::upper_bound. The cache is invalidated when the target's
module count changes, and through the existing ClearCache() path.
Measured on macOS arm64 SOS LineNums tests with prebuilt runtime
configs: ~118s -> ~28s for net11 (~4x), and total LineNums wall
time from ~13 minutes to ~3.7 minutes (~3.5x).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 2, 2026 16:15
@steveisok
steveisok requested a review from a team as a code ownerMay 2, 2026 16:15

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes SOS-in-lldb behavior on macOS arm64 by moving SpecialDiagInfo to a 47-bit-valid address (with legacy fallback) and by significantly reducing ReadVirtual fallback overhead via a cached, binary-searched section table.

Changes:

  • Update SpecialDiagInfo address on Apple Silicon macOS and add legacy-address probing for older dumps.
  • Add a cached SectionRange table to speed up LLDBServices::ReadVirtual section-backed fallback reads.
  • Mirror the SpecialDiagInfo address fallback behavior in the managed SpecialDiagInfo implementation.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

FileDescription
src/SOS/lldbplugin/services.hIntroduces section-range cache fields and helpers for faster section fallback reads.
src/SOS/lldbplugin/services.cppImplements legacy SpecialDiagInfo fallback and the section-range cache (sort + upper_bound) for ReadVirtual.
src/SOS/inc/specialdiaginfo.hAdjusts arm64 macOS SpecialDiagInfo address and defines legacy address constant.
src/Microsoft.Diagnostics.DebugServices.Implementation/SpecialDiagInfo.csAdds macOS arm64-valid address probing + legacy fallback in managed reader.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadsrc/SOS/lldbplugin/services.cpp
Comment threadsrc/SOS/lldbplugin/services.cpp
Comment threadsrc/SOS/inc/specialdiaginfo.h Outdated
@steveisoksteveisok changed the title macOS arm64 SOS-in-lldb: fix SpecialDiagInfo address and speed up ReadVirtualmacOS SOS-in-lldb: speed up ReadVirtual and fix arm64 SpecialDiagInfoMay 2, 2026
* Section cache: guard endAddr/endOffset against uint64 overflow when
building the SectionRange table and when computing the containment
check, so a pathologically large section can't wrap and produce
spurious cache hits.
* GetExceptionRecord: when the SpecialDiagInfo signature matches but
the exception record can't be read (Version too low,
ExceptionRecordAddress=0, or read fails), continue to the next
candidate address instead of returning eagerly. Cheap and avoids
pinning behavior to the first matching address.
* specialdiaginfo.h: fix cross-reference comment to point at the actual
reader (LLDBServices::GetLastEventInformation), not a non-existent
SOSReadDiagInfoHeader symbol.
* ExtensionCommands SpecialDiagInfoHeader: add GetCandidateAddresses
alongside the existing GetAddress so the OSX path probes the
47-bit-valid address first then falls back to the legacy x86_64
address. CommandFormatHelpers.DisplaySpecialInfo now iterates the
candidates so 'runtimes' / extension commands recognize Apple Silicon
dumps the same way the lldb plugin and managed reader already do.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
return SpecialDiagInfoAddressMacOS64;
// Try the arm64-valid address first (also valid on x86_64 macOS for newer
// createdump output); fall back to the legacy x86_64 address.
yield return SpecialDiagInfoAddressMacOSArm64;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow up change - lets remove the fallback. 8.0 already had this address.

{
Span<byte> headerBuffer = stackalloc byte[Unsafe.SizeOf<SpecialDiagInfoHeader>()];
if (_memoryService.ReadMemory(SpecialDiagInfoAddress, headerBuffer, out int bytesRead) && bytesRead == headerBuffer.Length)
Span<byte> exceptionRecordBuffer = stackalloc byte[Unsafe.SizeOf<EXCEPTION_RECORD64>()];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow up - encapsulate getting the SpecialDiagInfoHeader into a helper that this and HasDiagnosticInfo can use

Comment threadsrc/SOS/lldbplugin/services.cpp Outdated
@hoyosjs
hoyosjsforce-pushed the steveisok/macos-arm64-sos-fixes branch from 609e546 to 38d8dbaCompareMay 11, 2026 09:26
hoyosjsand others added 3 commits May 11, 2026 10:13
The flag was load-bearing only for the degenerate 'target with 0 modules
is a valid cached state' case. Module-count equality alone is sufficient
to detect cache freshness, and matches the previous behavior in every
real-world scenario.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@hoyosjs
hoyosjs enabled auto-merge (squash) May 11, 2026 23:28

@tommcdontommcdon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@hoyosjs
hoyosjs merged commit 9c40506 into dotnet:mainMay 12, 2026
19 checks passed
@hoyosjshoyosjs mentioned this pull request Jun 9, 2026
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 11, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@steveisok@tommcdon@hoyosjs