Skip to content

The controller applet's stand-in runs on the big host stack: Drag x Drive opened it from a game thread with too little stack for its log lines (SIGBUS in fprintf under focus_sync) - #1943

Closed
FifteenPen wants to merge 1 commit into
seedds:mainfrom
FifteenPen:pr/ctrl-applet-bigstack

Conversation

@FifteenPen

@FifteenPen FifteenPen commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

What and why

Drag x Drive (040041601D83A000, v196608, SDK 20) opened the controller applet (nn::hid::ShowControllerSupport) from one of its game threads and the host crashed in the stand-in's own log line, then hung in the crash handler at 100% CPU. This happens after completing a time trial, when the game opens the screen to show the result/offer to retry/offer to return the user to the main menu:

CRASH sig=10 addr=0x7ccdfcbff0 pc=__sfvwrite+0xc lr=__vfprintf+0x1e44 sp=0x7ccdfcc050
bt: __xvprintf fprintf focus_sync+0xc0 h_hid_ShowCtrl+0x20 main+0x2bcee0 main+0x2bcd20 main+0x523a50 main+0x161a484 sdk+0x149bd0 sdk+0x150718 dm_thread_enter

The fault is 0x60 bytes below sp: the case of Host calls on small guest stacks. Host hooks run on the calling guest thread's stack, and the controller applet's stand-in logs twice before it answers (DM_APPLET's oe library applet line, then focus_sync's focus state). fprintf needs more than the game's thread had left.

The change registers both ShowControllerSupport overloads through HLE_FORCE_BIG, as the file system hooks are, so the stand-in runs on the per-thread host stack. t_hid now links hle_bigstack.c with the same guest-address-space stub t_fs uses (every test stack is a host stack, so the take-over runs on it directly).

The other applet stand-ins (DM_APPLET: error, shop, Mii editor, strap guide, user selector, software keyboard) log the same way and have the same exposure on a small stack; only the controller applet was seen to overflow, so only it is moved here.

Docs: the reference entry Host calls on small guest stacks gains this case (symptom, cause, fix, found on), and the symptom index its line.

Checked

  • Tests, on this branch (main cfbdd93 + this change), Apple silicon:
    • make -C dm/host/tests GAME=010015100B514000 run: passes (t_hid included).
    • python3 -m unittest dm/tools/tests/test_docs.py dm/tools/tests/test_env_switches.py, python3 dm/tools/docs_symptoms.py.
    • dm/tools/lint_c.py tidy (LLVM 23.1.4) on dm/host/hle_hid.c: 0 file/check pairs above the baseline.
  • Games run: Drag x Drive (040041601D83A000, v196608), a Mac app of main e82888e with nn::hid::EnableUnintendedHomeButtonInputProtection is answered by the host and logged: Drag x Drive no longer crashes at sdk+0x63ce10 as player 1's Joy-Con 2 joins #1921 and NVN: account for four-byte direct indexed draws using control memory #1609 (the game needs both to reach play), a pair of Joy-Con 2s, docked:
    • the crash above once, about 3 minutes into a session, on a screen loading the players' names;
    • with this change, a 6-minute session through the same screen: no crash. On that screen the game opened the strap-guide applet that time, not the controller applet, so the change's own path was not taken again in play.
  • Devices and OS: Apple M4 Pro (20-core GPU, 24 GB), macOS 27.2.
  • regress.py: host only, hle_hid.c's two applet take-overs; they bind only where a game imports ShowControllerSupport, and run the same code on another stack. regress.py check was not run.
  • Not checked: iOS.

Docs

  • The finding is in its game note or its docs/reference/<subsystem>.md entry, and python3 dm/tools/docs_symptoms.py was run (the symptom index)
  • A chain tool changed: not applicable (host only)
  • No game files, keys or data extracted from a game in the change or the attachments

…rive opened it from a game thread with too little stack for its log lines (SIGBUS in fprintf under focus_sync)

Both ShowControllerSupport overloads register through HLE_FORCE_BIG, as the file system hooks do (memory.md, Host calls on small guest stacks). t_hid links hle_bigstack.c with a guest-address-space stub, as t_fs does.
@github-actions github-actions Bot added host The Horizon host: SVCs, services, threads, memory (dm/host) docs Documentation labels Nov 9, 2026
seedds added a commit that referenced this pull request Nov 11, 2026
… controller applet's stand-in runs there: Drag x Drive (040041601D83A000, v196608) opened the controller applet from a game thread with too little stack left for the stand-in's log lines, SIGBUS in fprintf under focus_sync / h_hid_ShowCtrl 0x60 bytes below sp, then a hang in the crash handler (the PR author's report). Formatting one line takes about 2.7 KB of stack (fprintf to stderr, measured on macOS 27.2) and a hook runs on the stack of the game thread that calls it, so every hook that logs was exposed, as the file system, add-on content and audio hooks were before they moved one by one. LOG is now one call, dm_log (dm.h): the host's definition (hle_bigstack.c) formats the line through dm_bigstack_enter, on the host stack when the caller is on a guest stack and directly anywhere else, the variable arguments read from where the caller left them; programs that do not link hle_bigstack.c (unit tests) get dm.h's weak definition, which writes on the caller's stack. A hook needs HLE_FORCE_BIG only for its own stack use: both ShowControllerSupport overloads take it (the PR's change), since with too few players the front end's prompt runs on the game's thread. t_hid and t_hid_tsan link hle_bigstack.c (the PR left t_hid_tsan out, which no longer linked), and t_audio_stats captures dm_log where it captured fprintf. Not covered: direct fprintf(stderr) calls in a hook (the thread dumps of hle_thread.c, a debug line of hle_svc.c). Checked: t_bigstack (new case: a hook logs with 1 KB of guest stack left; SIGBUS with the line written on the guest stack), make run in dm/host/tests and dm/gfx/tests, the tsan, tsan-threads and memory-tsan targets, test_docs. OCTOPATH TRAVELER II on the Mac (M4 Pro), one full regress route on this host: 59.8 fps, no red line, clean exit (main: 59.5 fps); the regress comparison itself was not completed (the window was covered, and other jobs ran). Not run: Drag x Drive (no files here), iOS (PR #1943)

Co-authored-by: FifteenPen <354289956+FifteenPen@users.noreply.github.com>
@seedds

seedds commented Nov 11, 2026

Copy link
Copy Markdown
Owner

Landed on main as 2d3fb83, in a wider form, with you credited as co-author. Thank you for the report and the diagnosis: the backtrace and the fault address 0x60 below sp were exactly what was needed.

What changed against this PR:

  • Every hook's log line is covered, not two hooks. One log line takes about 2.7 KB of stack (measured), so any hook that logs was exposed on a thread with less left, including the other applet stand-ins you named (your retest hit the strap guide on that screen). LOG is now one call, dm_log, and the host's definition formats the line through dm_bigstack_enter: on the host stack when the caller is on a guest stack, directly anywhere else.
  • Both ShowControllerSupport overloads keep HLE_FORCE_BIG, as you wrote them: with too few players the front end's prompt also runs on the game's thread.
  • t_hid_tsan links hle_bigstack.c too. The PR added it to t_hid only, so make tsan (which CI runs) no longer linked.
  • A test for the path: t_bigstack logs from a hook with 1 KB of guest stack left; it dies with SIGBUS when the line is written on the guest stack.
  • The finding is its own reference entry, "Log lines on small guest stacks" in docs/reference/memory.md.

Not run here: Drag x Drive (no files of it on this Mac). Could you retest on main, through the screen after a time trial, and say whether the crash is gone? A log from a session where the controller applet opens (oe library applet: controller support) would confirm the fixed path was taken.

@seedds seedds closed this Nov 11, 2026
seedds added a commit that referenced this pull request Nov 11, 2026
…ost's are: MLOG (nvn_mtl.m) wrote with fprintf directly, about 2.7 KB of stack a line, and the nvnCommandBuffer* recording calls run on the game thread's stack (every other NVN function goes through nvn_big_entry), so a line logged from one on a thread with little stack left would fault as the controller applet's did (2d3fb83). MLOG is now dm_log, as LOG is. No recording call was found to log on a small stack: this closes an exposure, not a reproduced crash. The reference entry (Log lines on small guest stacks) is corrected: the direct writes it named as not covered run on host stacks (an SVC, so the OutputDebugString line, on the thread's gate stack, entry.S; the crash handler and its thread dumps on its alternate stack; probes, haptics and save sync on host threads or behind HLE_FORCE_BIG). Open: dm_backtrace writes directly and nn::diag's abort hooks call it on the game's stack, so an abort on a thread with little stack left can fault before its backtrace. Checked: the Mac host builds without a format warning, make run in dm/gfx/tests (the graphics layer without hle_bigstack.c, so dm.h's weak definition), test_docs. Not run: a game, regress.py, iOS (PR #1943)
@FifteenPen

Copy link
Copy Markdown
Contributor Author

I will do that test in an hour!

@FifteenPen

Copy link
Copy Markdown
Contributor Author

Waiting for shaders to build now.

@FifteenPen

FifteenPen commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor Author

Retested on main be23843 (with #1944's profile on top): Drag x Drive (040041601D83A000, v196608), a Mac app from its NUP, a pair of Joy-Con 2s, docked, M4 Pro, macOS 27.2.

  • Through the screen after a time trial: no crash. The host exited with 0, with no red lines, in every session (three). All good.

  • I was playing with the Joy-Con 2s and never had the controller applet appear, no matter what I did: oe library applet: controller support is in none of the logs, so that path was not taken. The applet the game did open was the strap guide, a few seconds after boot; its log line now goes through dm_log, and it returned normally.

Log, the session through the time trial (the applet, Home-protection and exit lines)
launcher: Drag x Drive (040041601D83A000), host Drag x Drive main-be23843f0.app/Contents/MacOS/dmhost
[dm] oe library applet: controller strap guide
[dm] oe focus state → 2 (library applet open)
[dm] oe focus state → 1 (library applet closed)
[dm] hid: EnableUnintendedHomeButtonInputProtection npad 0 on
[dm] hid: EnableUnintendedHomeButtonInputProtection npad 0 off
[dm] hid: EnableUnintendedHomeButtonInputProtection npad 0 on
CRASH lines: 0
launcher: the host exited with 0

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.

2 participants