Repository navigation
libkernel: ccleste stalls after Vulkan device ready due to redirected stderr (fixed by #4536) #4664
Description
Resolved by merged #4536; original diagnosis corrected.
The stall was caused by the guest stderr-capture pipe feeding sceKernelDebugOutText back into itself, eventually blocking stderr writers during Vulkan construction. It was not an established guest-memory mirror/import lock cycle. The original knob table and raw stack scans did not prove that mechanism.
Local Windows 11 / Radeon 780M verification: ccleste on G09's saved bde332a59 runtime hangs before #4536; replacing only libkernel with that baseline plus the two merged #4536 commits creates the swapchain and keeps logging GPU submissions through 45 seconds with no APS5_* environment variables. AGC and the other libraries were identical in both arms. This is a check of #4536, not a full latest-main title run; RommPS was not rerun locally. See the resolution comment for actual output excerpts and limitations.
Original investigation (historical; causal claims withdrawn)
ccleste (PPSA59322, open-source MIT homebrew) deadlocks deterministically on main after Vulkan device ready: the VulkanDevice constructor stalls between the guest-memory mirror/import setup steps, wedging sceVideoOutOpen and the whole title — the "stops after Vulkan device ready with no swapchain" symptom from the 4th catalog run in #1578.
Reproduction (deterministic, every run)
Title: ccleste 1.00 (PPSA59322), built from https://github.com/darkxex/PS5_Celeste (MIT); catalog artifact sha256 96861b81...2fb43 verified against the catalog. eboot + sce_module/libc.prx unwrapped (ps5-payload-dev fSELF layout; two non-load segments zero-filled), relinked with the relinker built from the same commit:
relinker.exe --trap-sdk-syscall-stubs --windows run/eboot.elf out/eboot.exe -> exit 0, 352 NID references
Run against the libraries built from main at 9aa5fd290007d816bfed6970ee259f9baaa0e24e, then:
[src/VulkanDevice.cpp:1570 VulkanDevice] Vulkan device ready device=0xa412ab0 queue=0xa4d9d40 family=0
…then nothing, forever. Verified hung at 150 s and again at 600 s with flat private bytes (253–263 MB) and a shrinking working set (255→43 MB): a true deadlock, not slow progress, not a crash (every APS5 log line flushes per line).
Environment: Windows 11, AMD Radeon 780M (Vulkan 1.1), WinLibs GCC 15.2, Release.
What it is not
- The title's port flow is correct:
windowis non-null,Presentation window ... extent=1920x1080andVulkan surface createdare logged before the stall. - No thread is inside an AMD Vulkan-driver call at the stall; the constructing thread parks on AnyPS5's own sync objects, with guest addresses (0x2_00914000 region) and mirror-block structures on its stack.
- The guest's main thread is in
sceVideoOutOpenwaiting for the started promise thatVideoOutDriver's present thread only sets afterAgcDriverAttachWindow→VulkanDevicector returns (VideoOutDriver.cpp:555-565, ctor wait at:277) — so any ctor stall produces exactly this symptom with no error line.
Where it is
The silent window is the constructor's own setup between VulkanDevice.cpp:1571 and :1607 — the guest-memory host-import + image-mirror + parallel-refresh machinery (BufferPool, PipelineCache, TextureDetiler, GpuColorTransfer, PlaceholderImages/GpuMutex, Recorder::Activate).
Knob bisect on the same relinked title:
| environment | result |
|---|---|
| default | deadlock after Vulkan device ready |
APS5_NO_HOST_IMPORT=1 only |
deadlock |
APS5_NO_LEASE_MIRROR=1 only |
deadlock |
APS5_NO_SPARSE_HEAP_MIRROR=1 only |
deadlock |
APS5_NO_PARALLEL_MIRROR_REFRESH=1 only |
deadlock |
| all four together | Surface capabilities ... + Swapchain created swapchain=... images=2 |
So the cycle spans several guest-memory features interacting during construction, not one bad call site. The exact two-lock cycle is not yet named; stack captures of the parked threads are in the run logs below.
RommPS (PPSA76677) shows the same catalog symptom but was not re-run here; the artifact is catalog-verified and the same reproduction applies.
Evidence files
Full run log with commands and exit codes, the relink log, per-knob run outputs, and thread-stack captures: can be attached on request (the run receipt cites the baseline sha in its header).
AI-assisted: yes.
Activity
Joekrry commentedon Oct 12, 2026
Hi,
I traced the exact code path.
The four env knobs you bisect (APS5_NO_HOST_IMPORT, APS5_NO_LEASE_MIRROR, APS5_NO_SPARSE_HEAP_MIRROR, APS5_NO_PARALLEL_MIRROR_REFRESH) all gate the host-import/image-mirror machinery, and that only runs once a guest texture or buffer is bound in a draw.
In the constructor window you point to (VulkanDevice.cpp, between "Vulkan device ready" and swapchain creation), none of the code that actually runs there, BufferPool, PipelineCache, TextureDetiler, TextureCache, GpuColorTransfer, DescriptorCache, PlaceholderImages, touches guest memory, host imports, or mirrors at all. PlaceholderImages just creates 1x1 device-local images and clears them via a GPU submit/wait. And per your own repro, the guest's main thread is still parked in sceVideoOutOpen = no draw has happened yet.
I also checked GuestMemory::GpuMutex (correctly recursive) and RefreshPool's helper threads and no lock-ordering cycle there either.
The bisection table doesn't line up with code that can actually run in the window described, so I can't locate the deadlock.
Can you attach the actual run log and stack captures?
orishamir commentedon Oct 12, 2026
This is the stall #4536 fixes. It merged at 18:31Z (21c27e6);
main9aa5fd2 above is from before it.The thread stacks are in #4536: the title's log reader thread is in
sceKernelDebugOutText→fprintf→WriteFile, which does not return, and the thread constructing theVulkanDevicewaits for thestderrlock infprintf, called fromGraphics::PrepareImportWatch(VulkanDevice.cpp:1598; on Windows that write is the[write-watch] Windows direct host imports use comparisons ...line atGuestBufferMemory.cpp:524). ccleste and RommPS move their stderr into a pipe withdup2(pipe_w, 2)and forward what they read from it tosceKernelDebugOutText, which wrote to host stderr, i.e. back into the same pipe, until the pipe was full.On current
maincf4557e (with #4536), Windows 11, RTX 4080, WinLibs GCC 15.2.0 r7, Release, relinked with--trap-sdk-syscall-stubs, noAPS5_*variables set, 60 s each:Vulkan device ready,Surface capabilities ...,Swapchain created ... images=2; still running at 60 s; the frames saved every 300 presents show its title screen (logo, credits, "PS5 PORT", falling snow).The
[write-watch]line now appears once in each title's output, forwarded by the title's reader:[debug:0] [ccleste] [write-watch] Windows direct host imports use comparisons; separate shared aliases retain guest write tracking.AI-assisted: yes (Claude Code Opus 5.5 xhigh)
Mark #4664 as the stall #4536 fixed; ccleste Menus, RommPS Presents o…
[-]libSceAgcDriver: VulkanDevice constructor deadlocks in guest-memory mirror/import setup after 'Vulkan device ready' (ccleste PPSA59322, deterministic)[/-][+]libkernel: ccleste stalls after Vulkan device ready due to redirected stderr (fixed by #4536)[/+]Roojool commentedon Oct 12, 2026
Confirmed locally on Windows 11 / Radeon 780M. The original mirror/import lock-cycle diagnosis was incorrect; I withdraw it. The single-knob/all-knob outcomes did not establish that mechanism, and the original raw stack scans were not symbolic backtraces. Sorry for the misleading attribution.
I reused G09's runtime built from
bde332a59ce98ce8aa776b26a4ed04e53417946d, including its diagnostic AGC binary, and ran ccleste (PPSA59322) with noAPS5_*variables:Vulkan device ready; no surface-capability or swapchain line. Stopped by the test harness.libkernelafter applying the two already-merged fix(libkernel): keep sceKernelDebugOutText off a redirected guest stderr #4536 commits (2585eeb7e,fc0d46ba0, original authors preserved), leaving AGC and every other runtime library identical: surface capabilities and swapchain created; continued logging guest GPU submissions through the 45-second observation window. Stopped by the test harness.Actual output excerpts:
This is a before/after check of the existing merged fix on the saved runtime, not a full latest-main title run. I did not rerun RommPS. The initial attempt to substitute a much newer kernel into the older runtime failed at DLL loading and was excluded from the comparison.
The evidence agrees with @orishamir's explanation: the title's stderr-capture reader fed
sceKernelDebugOutTextback into its own pipe. #4536 breaks that loop by preserving host stderr. Thanks @Joekrry for questioning the constructor-path claim and @orishamir for the exact diagnosis and verification. Closing as already fixed by #4536; no separate G09 fix PR.AI-assisted: yes.