Skip to content

SDK 22's audio update lock is read off UpdateImpl: the host locked +0x2d0 where SDK 22.2.8 locks +0x2d8, and Ocarina of Time's render thread crashed in the opening movie - #1345

Closed
nafields wants to merge 1 commit into
seedds:mainfrom
nafields:sdk22-audio-update-lock

Conversation

@nafields

@nafields nafields commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

What and why

Symptom: The Legend of Zelda: Ocarina of Time (Switch 2, 0400486024DEC000, update v131072, SDK 22.2.8) crashed on the audio render thread minutes into its opening movie:

  • CRASH sig=11 addr=0x20 at nn::audio::server::AddressInfo::GetReference (sdk+0x249874);
  • the stack was under CommandGenerator::GenerateDataSourceCommand and render_once;
  • the AddressInfo it read was all zeros, though the function had just read a non-null memory pool pointer from it.

Cause: the Span layout (SDK 19 and later) took AudioRenderSystem's update lock at a fixed +0x2d0.

  • SDK 22.2.8's UpdateImpl (sdk+0x238530) locks the SdkMutexType at +0x2d8: add x19, x0, #0x2d8; …; mov x0, x19; bl SdkMutexType::Lock.
  • Locked at +0x2d0, the render thread excluded nothing and wrote a lock word into the field before the mutex. The game's RequestUpdate then rewrote a voice while its commands were being generated.
  • update_lock already reads the offset off the SDK's own code for the older layouts, but it reads Update. From SDK 20, Update reads the system tick and calls UpdateImpl, which takes the lock.

Fix: update_lock now takes the function to read, and the Span layout reads UpdateImpl (hle_audren.c).

  • It keeps +0x2d0 when it finds no SdkMutexType::Lock there.
  • The start-up line names the offset it took: update lock SdkMutexType at +0x2d8.

Reference entry: The update lock read off UpdateImpl (SDK 22). It is linked from SDK 20's layout entry, and the symptom index is regenerated.

Checked

  • Tests:
    • make -C dm/host/tests GAME=0400C3F00006E000 run passes.
    • python3 -m unittest dm/tools/tests/test_docs.py passes.
    • The host builds.
  • Games run: The Legend of Zelda: Ocarina of Time (0400486024DEC000, v131072), on [WIP] The Legend of Zelda: Ocarina of Time (Switch 2, 0400486024DEC000, v0, SDK 22.2.8) runs on the Mac through the title movie into Kokiri Forest #1299's profile (this PR adds no profile).
    • Before the change, the second run through the opening crashed as above.
    • With it, the opening played through to Kokiri Forest, which ran with sound.
  • Static check: the prologue read the way update_lock reads it gives +0x2d0 for Mario Kart World's sdk (0400C3F00006E000, SDK 19.3.3), as before, and +0x2d8 for Ocarina's.
  • Devices and OS: M3 16 GB, macOS 26.6.2.
  • regress.py: not run. The change reaches only Span-layout games (SDK 19+), whose offset it now reads instead of assuming. On those whose UpdateImpl locks +0x2d0, as Mario Kart World's does, the value is the same as before.
  • Not checked:
    • lint_c.py tidy (no LLVM on this Mac);
    • Mario Kart World and other SDK 19–21 games at run time.

Docs

  • The finding is in its docs/reference/audio.md entry, and python3 dm/tools/docs_symptoms.py was run
  • No game files, keys or data extracted from a game in the change or the attachments

…x2d0 where SDK 22.2.8 locks +0x2d8, and Ocarina of Time's render thread crashed in the opening movie

The Legend of Zelda: Ocarina of Time (Switch 2, 0400486024DEC000, update v131072, SDK 22.2.8) crashed on the audio render thread minutes into its opening movie: CRASH sig=11 addr=0x20 at nn::audio::server::AddressInfo::GetReference (sdk+0x249874), under CommandGenerator::GenerateDataSourceCommand and render_once. The AddressInfo it read was all zeros, though the function had just read a non-null memory pool pointer from it.

The Span layout (SDK 19 and later) took AudioRenderSystem's update lock at a fixed +0x2d0. SDK 22.2.8's UpdateImpl (sdk+0x238530) locks the SdkMutexType at +0x2d8 (add x19, x0, #0x2d8; ...; mov x0, x19; bl SdkMutexType::Lock). Locked at +0x2d0, the render thread excluded nothing and wrote a lock word into the field before the mutex, so the game's RequestUpdate rewrote a voice while its commands were being generated. update_lock already reads the offset off the SDK's own code for the older layouts, but it reads Update, and from SDK 20 Update reads the system tick and calls UpdateImpl, which takes the lock.

update_lock now takes the function to read, and the Span layout reads UpdateImpl. It keeps +0x2d0 when it finds no SdkMutexType::Lock there, so nothing changes for a game whose SDK has no such prologue. The start-up line names the offset it took: "update lock SdkMutexType at +0x2d8".

Checked: Ocarina of Time's opening crashed on the second run through it before the change; with it, the opening played through to Kokiri Forest, which ran with sound (M3 16 GB, macOS 26.6.2, on seedds#1299's profile). The same prologue read statically gives +0x2d0 for Mario Kart World's sdk (SDK 19.3.3), as before, and +0x2d8 for Ocarina's. Host tests pass; docs test passes.
@github-actions github-actions Bot added host The Horizon host: SVCs, services, threads, memory (dm/host) docs Documentation labels Nov 5, 2026
fpedrolucas95 pushed a commit to fpedrolucas95/nxnative that referenced this pull request Nov 6, 2026
…ad off UpdateImpl: the host locked +0x2d0 where SDK 22.2.8 locks +0x2d8, and Ocarina of Time's render thread crashed in the opening movie
seedds added a commit that referenced this pull request Nov 6, 2026
…ed it to +0x2d8, and Ocarina of Time's render thread crashed under a lock that excluded nothing (PR #1345)

From SDK 19 Update reads the system tick and calls UpdateImpl, which takes the lock, so the Span layout took a fixed +0x2d0. SDK 22.2.5 and 22.2.8 lock +0x2d8: the render held no lock in common with the game's RequestUpdate, which rewrote a voice while its commands were generated (SIGSEGV in AddressInfo::GetReference under GenerateDataSourceCommand).

update_lock now reads Update, then UpdateImpl, for every SDK, and a miss in both is a red line instead of a silent guess. Statically on the sdk builds on hand: before SDK 19 Update's lock as before, 19.3 to 21.4 +0x2d0 as before, 22.2 +0x2d8. t_audren_layout tests the scan. The reference corrects "inlined in 19 and 20" and adds 18.3's +0x208.

Co-authored-by: nafields <2613057+nafields@users.noreply.github.com>
@seedds

seedds commented Nov 6, 2026

Copy link
Copy Markdown
Owner

Landed as 5219060, with your diagnosis and your fix for the offset, and you are credited as co-author. Thank you: the bug was real, and SDK 22.2.8's UpdateImpl, SendCommandToDsp, Start, Stop and FinalizeImpl all use +0x2d8.

What changed from this pull request:

  • update_lock reads Update, then UpdateImpl, for every SDK, so there is no gen20 branch in the lookup.
  • A miss in both now logs a [RED] line instead of quietly keeping the fixed offset.
  • t_audren_layout tests the scan.
  • The docs say the lock moved to UpdateImpl from SDK 19, not 20, and add 18.3's +0x208.

Checked statically on the sdk builds on hand: SDK 19.3 to 21.4 still read +0x2d0, and 22.2.5 and 22.2.8 read +0x2d8. No game has run this exact form yet, because Ocarina's profile is still in #1299. Your run used the form in this pull request. If you run it on top of #1299, the start-up line should say update lock SdkMutexType at +0x2d8.

#1444 carries the old form of these lines and will need a rebase.

@seedds seedds closed this Nov 6, 2026
gnosticgeek pushed a commit to gnosticgeek/nxforge that referenced this pull request Nov 6, 2026
… layers, seedds#1345 and seedds#1373 landed)

Upstream's c83c48f makes tex_fit's views from layer counts, which covers the cube read as a 2D array, so NXForge's own version of that (f17b3e7) is reverted before this merge. hle_audren.c and its notes take upstream's landed seedds#1345. The failed command buffer report keeps both seedds#1382's segment names (cbn_close) and upstream's shader keys (cbs_feedback), and seedds#1332's gpu_lost_check. The present keeps NXForge's sidebar inset on upstream's present_fit. The translator binaries, lock and chain header are upstream's until the translator is rebuilt on the Mac.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qteK29T18U8p1c9LsVY8g
@Romen-Adama-Dev

Copy link
Copy Markdown
Contributor

Confirmed on an Apple M3 Pro (18 GB), macOS 27.0.1, update v131072, with #1299 merged onto main 865cc35f: the start-up line reads update lock SdkMutexType at +0x2d8, the opening and about 20 minutes in Kokiri Forest played with sound, and the audio render thread did not crash (it had not crashed for me without this change either, over about an hour of runs, so this is a no-regression report rather than a reproduction of the crash).

gnosticgeek pushed a commit to gnosticgeek/nxforge that referenced this pull request Nov 6, 2026
) replaces the pull request heads

Upstream landed Ocarina of Time in main (chain adec7d81d970: "holds 60 fps in
Kokiri Forest, 4 GB less") and closed the pull requests it folded in, with
reworded and partly different code. Mixing a pull request head with its landed
version hunk by hunk silently broke things (two GlobalToStorage tables, a
duplicate AF2ip, two mtk_pso definitions), so the rule here is one version per
feature:

- Pull requests upstream closed (seedds#1299, seedds#1300, seedds#1312, seedds#1343, seedds#1345, seedds#1353,
  seedds#1354, seedds#1359, seedds#1360, seedds#1373, seedds#1428, seedds#1430, seedds#1434): their hunks are taken out
  of our side before the three-way merge, so upstream's landed version stands
  alone. Gone with them: bindless pool table patching (reg_bump), the ghost
  table's per-submission hold, the growing raw pool table and its write-log
  sizing, seedds#1428's pool_buf spares (upstream's pool_table_take is the same
  idea), the ixc-based indirect mesh draw (upstream uses mdx), and their tests
  (t_pool_table, t_mesh_indirect, t_gmem_raw, finalized_bindless_pool,
  test_bnsh_nvn2, test_ndve).
- Open pull requests that upstream's landing supersedes: seedds#1394 (upstream's
  mtl_pool_addressed) and seedds#1393 pool_skip_retired (upstream's budgeted pool
  walk; the profile does not use it) are dropped with their tests and docs.
  seedds#1368's mesh pipeline prewarm is dropped too: upstream kept the background
  build in nvn_geometry.inc, and the prewarm's mtk_build / mtk_claim /
  g_warm_mps went with it.
- Kept, as upstream does not have them: NXForge's own features (GPU watchdog,
  present filters and anti-aliasing, aniso floor, quick settings, pipeline
  archive compaction, upload breakdown, memory report lines) and the still
  open pull requests seedds#1304, seedds#1305, seedds#1308, seedds#1320, seedds#1331, seedds#1332, seedds#1355, seedds#1356,
  seedds#1369, seedds#1376, seedds#1377, seedds#1379, seedds#1382, seedds#1390, seedds#1391, seedds#1392, seedds#1414, seedds#1420,
  seedds#1436, seedds#1439, seedds#1446, seedds#1453.
- Combined where both sides changed the same lines: GW_N 1 << 16 with seedds#1320's
  wide-write intervals (GW_PAGES = SBW_WPAGES, 128 slots), the CPU upload log
  with upstream's linear upload route, arch_open_end before the compaction
  limit, cbn_prog in geo_gs_one (seedds#1382).
- Translator: upstream's sources and binaries, plus seedds#1414's
  InstEmitAmpereMem diff and the LDSM SubgroupBarrier in
  InstEmitAmpereTensor.cs. The binaries and dmsh_chain.lock are upstream's
  until the Mac rebuilds them (dotnet publish, tool_lock --update-lock,
  dmsh_chain --header); dmsh_chain.h is regenerated for now.
- RED_ONCE keys: upstream took 153 and 154, so the present anti-aliasing lines
  move to 156 and 157 and seedds#1420's packaged-entry line to 158 (array 159).
- Host: hle_net.c and t_net_eventfd.c are upstream's (its eventfd poll waits
  out the whole timeout, which our 5d7920c fixed another way).
- Kept ours: dm/tools/romfs_image.py and its test, so the 26 GB RomFS image is
  not packed again.

dm/tools tests on Linux: same failures as on 875449e (Linux-only quirks),
plus test_nlg4 and test_dmsh_pool_arrays, which fail on upstream main too.
Not yet built on the Mac.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qteK29T18U8p1c9LsVY8g
Know-It-All-Brother pushed a commit to Know-It-All-Brother/nxnative that referenced this pull request Nov 9, 2026
…ed it to +0x2d8, and Ocarina of Time's render thread crashed under a lock that excluded nothing (PR seedds#1345)

From SDK 19 Update reads the system tick and calls UpdateImpl, which takes the lock, so the Span layout took a fixed +0x2d0. SDK 22.2.5 and 22.2.8 lock +0x2d8: the render held no lock in common with the game's RequestUpdate, which rewrote a voice while its commands were generated (SIGSEGV in AddressInfo::GetReference under GenerateDataSourceCommand).

update_lock now reads Update, then UpdateImpl, for every SDK, and a miss in both is a red line instead of a silent guess. Statically on the sdk builds on hand: before SDK 19 Update's lock as before, 19.3 to 21.4 +0x2d0 as before, 22.2 +0x2d8. t_audren_layout tests the scan. The reference corrects "inlined in 19 and 20" and adds 18.3's +0x208.

Co-authored-by: nafields <2613057+nafields@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation host The Horizon host: SVCs, services, threads, memory (dm/host)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants