From e57a50a2b6fece325d96cd5ff94582e18d11acbb Mon Sep 17 00:00:00 2001 From: Xnick417x Date: Sun, 26 Jul 2026 17:44:07 +0000 Subject: [PATCH] Add Freeze Log debug toggle When the game stops presenting for 5 s, record what every guest thread is doing so a freeze can be diagnosed after the fact. Each dump captures per-thread state from /proc (name, run state, wchan, current syscall), every executable mapping including anonymous JIT regions, and the kgsl GPU counters. Dumps repeat every 15 s while stalled, capped at six, and are written to logs/freeze_.log so the existing log export picks them up. Writing to a file rather than logcat is deliberate: a guest process has several hundred mappings and logcat rate-limits and silently drops them. Off by default; toggle lives in Settings > Debug. --- .../feature/settings/debug/DebugFragment.kt | 5 + .../feature/settings/debug/DebugScreen.kt | 12 +++ app/src/main/res/values-b+es+419/strings.xml | 2 + app/src/main/res/values-da/strings.xml | 2 + app/src/main/res/values-de/strings.xml | 2 + app/src/main/res/values-es/strings.xml | 2 + app/src/main/res/values-fi/strings.xml | 2 + app/src/main/res/values-fr/strings.xml | 2 + app/src/main/res/values-hi/strings.xml | 2 + app/src/main/res/values-it/strings.xml | 2 + app/src/main/res/values-ja/strings.xml | 2 + app/src/main/res/values-ko/strings.xml | 2 + app/src/main/res/values-no/strings.xml | 2 + app/src/main/res/values-pl/strings.xml | 2 + app/src/main/res/values-pt-rBR/strings.xml | 2 + app/src/main/res/values-pt/strings.xml | 2 + app/src/main/res/values-ro/strings.xml | 2 + app/src/main/res/values-ru/strings.xml | 2 + app/src/main/res/values-sv/strings.xml | 2 + app/src/main/res/values-th/strings.xml | 2 + app/src/main/res/values-tr/strings.xml | 2 + app/src/main/res/values-uk/strings.xml | 2 + app/src/main/res/values-zh-rCN/strings.xml | 2 + app/src/main/res/values-zh-rTW/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + .../display/XServerDisplayActivity.java | 94 +++++++++++++++++++ .../xserver/extensions/PresentExtension.java | 2 + .../main/runtime/system/ProcessHelper.java | 94 +++++++++++++++++++ 28 files changed, 253 insertions(+) diff --git a/app/src/main/feature/settings/debug/DebugFragment.kt b/app/src/main/feature/settings/debug/DebugFragment.kt index 9d94a673b..f992e3a30 100644 --- a/app/src/main/feature/settings/debug/DebugFragment.kt +++ b/app/src/main/feature/settings/debug/DebugFragment.kt @@ -147,6 +147,10 @@ class DebugFragment : Fragment() { preferences.edit { putBoolean("hud_record_to_file", checked) } refresh() }, + onFreezeLogChanged = { checked -> + preferences.edit { putBoolean("enable_freeze_log", checked) } + refresh() + }, onVulkanValidationLayersChanged = { checked -> preferences.edit { putBoolean("enable_vulkan_validation_layers", checked) } refresh() @@ -214,6 +218,7 @@ class DebugFragment : Fragment() { inputLogs = preferences.getBoolean("enable_input_logs", false), downloadLogs = preferences.getBoolean("enable_download_logs", false), recordPerformanceToFile = preferences.getBoolean("hud_record_to_file", false), + freezeLog = preferences.getBoolean("enable_freeze_log", false), vulkanValidationLayers = preferences.getBoolean("enable_vulkan_validation_layers", false), wnHybridMode = com.winlator.cmod.feature.stores.steam.utils.PrefManager.wnHybridMode, logsSize = diff --git a/app/src/main/feature/settings/debug/DebugScreen.kt b/app/src/main/feature/settings/debug/DebugScreen.kt index 28062eba8..59b5c05d3 100644 --- a/app/src/main/feature/settings/debug/DebugScreen.kt +++ b/app/src/main/feature/settings/debug/DebugScreen.kt @@ -59,6 +59,7 @@ import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowBack +import androidx.compose.material.icons.outlined.AcUnit import androidx.compose.material.icons.outlined.BugReport import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.CloudDownload @@ -156,6 +157,7 @@ data class DebugState( val inputLogs: Boolean = false, val downloadLogs: Boolean = false, val recordPerformanceToFile: Boolean = false, + val freezeLog: Boolean = false, val vulkanValidationLayers: Boolean = false, val wnHybridMode: Boolean = false, val logsSize: String = "0 B", @@ -186,6 +188,7 @@ fun DebugScreen( onInputLogsChanged: (Boolean) -> Unit, onDownloadLogsChanged: (Boolean) -> Unit, onRecordPerformanceToFileChanged: (Boolean) -> Unit, + onFreezeLogChanged: (Boolean) -> Unit, onVulkanValidationLayersChanged: (Boolean) -> Unit, onWnHybridModeChanged: (Boolean) -> Unit, onShareLogs: () -> Unit, @@ -340,6 +343,15 @@ fun DebugScreen( onCheckedChange = onDownloadLogsChanged, ) + SettingsToggleCard( + title = stringResource(R.string.settings_debug_freeze_log), + subtitle = stringResource(R.string.settings_debug_freeze_log_description), + icon = Icons.Outlined.AcUnit, + checked = state.freezeLog, + onCheckedChange = onFreezeLogChanged, + accentColor = Color(0xFF7FC7FF), + ) + SettingsToggleCard( title = stringResource(R.string.settings_hud_record_to_file_title), subtitle = stringResource(R.string.settings_hud_record_to_file_summary), diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml index 8d898bcc1..7a5045a2c 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -1118,6 +1118,8 @@ Por ejemplo, META para la tecla META, \n Registra los eventos de entrada de gamepad, toque y teclado Registros de descarga Registra los eventos del resolutor de descargas y del respaldo de WinNative + Registro de bloqueos + Cuando el juego deja de dibujar, registra qué está haciendo cada hilo invitado Capas de validación de Vulkan Reinicia la pantalla para aplicar. Compartir registros diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 916881fa3..19efe055f 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -830,6 +830,8 @@ F.eks. META for META-tast, \n Log gamepad-, touch- og tastaturinputhændelser Download-logfiler Log download-resolver og WinNative-fallback-hændelser + Fryselog + Når spillet holder op med at tegne, registrér hvad hver gæstetråd laver Vulkan-valideringslag Genstart skærmen for at anvende. Del logfiler diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index e9ba67824..1d0930422 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -830,6 +830,8 @@ Z. B. META für Meta-Taste, \n Gamepad-, Touch- und Tastatur-Eingabeereignisse protokollieren Download-Protokolle Download-Resolver- und WinNative-Fallback-Ereignisse protokollieren + Freeze-Protokoll + Wenn das Spiel nicht mehr zeichnet, aufzeichnen, was jeder Gast-Thread tut Vulkan-Validierungsebenen Display neu starten, um die Änderung anzuwenden. Protokolle teilen diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index d7eda9e5a..8bac964ed 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -830,6 +830,8 @@ Ej. META para la tecla META, \n Registrar eventos de entrada de mando, táctil y teclado Registros de descarga Registrar eventos del resolvedor de descargas y respaldo de WinNative + Registro de bloqueos + Cuando el juego deja de dibujar, registra qué está haciendo cada hilo invitado Capas de validación de Vulkan Reinicia la pantalla para aplicar. Compartir registros diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 595bf6bb5..7b89d41fd 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -1118,6 +1118,8 @@ E.g. META for META-näppäin, \n Kirjaa peliohjaimen, kosketuksen ja näppäimistön syötetapahtumat Latauslokit Kirjaa latausten selvitys- ja WinNative-varajärjestelmätapahtumat + Jumiutumisloki + Kun peli lakkaa piirtämästä, tallenna mitä kukin vierassäie tekee Vulkan-validointikerrokset Käynnistä näyttö uudelleen ottaaksesi käyttöön. Jaa lokit diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 8132f47a6..c3117b775 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -830,6 +830,8 @@ Par ex. META pour la touche META, \n Journaliser les événements de saisie manette, tactile et clavier Journaux de téléchargement Journaliser les événements du résolveur de téléchargement et du repli WinNative + Journal de blocage + Quand le jeu cesse de dessiner, enregistrer ce que fait chaque thread invité Couches de validation Vulkan Redémarrez l\'affichage pour appliquer. Partager les journaux diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 241b8f65a..6575264b5 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -940,6 +940,8 @@ गेमपैड, टच और कीबोर्ड इनपुट इवेंट लॉग करें डाउनलोड लॉग डाउनलोड रिज़ॉल्वर और WinNative फ़ॉलबैक ईवेंट लॉग करें + फ़्रीज़ लॉग + जब गेम ड्रॉ करना बंद कर दे, तो रिकॉर्ड करें कि हर गेस्ट थ्रेड क्या कर रहा है Vulkan सत्यापन परतें लागू करने के लिए प्रदर्शन पुनः प्रारंभ करें. लॉग शेयर करें diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index ca71b1a8a..d5c57f9e7 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -830,6 +830,8 @@ Ad es. META per il tasto META, \n Registra gli eventi di input gamepad, touch e tastiera Log download Registra gli eventi del risolutore download e fallback WinNative + Registro blocchi + Quando il gioco smette di disegnare, registra cosa sta facendo ogni thread guest Livelli di validazione Vulkan Riavvia il display per applicare. Condividi log diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index a676775c1..9f848f18b 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -1118,6 +1118,8 @@ ゲームパッド、タッチ、キーボードの入力イベントを記録します ダウンロードログ ダウンロードリゾルバーと WinNative フォールバックのイベントを記録します + フリーズログ + ゲームの描画が停止したとき、各ゲストスレッドの状態を記録します Vulkan 検証レイヤー 適用するにはディスプレイを再起動してください。 ログを共有 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 163e90571..6ee121777 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -830,6 +830,8 @@ 게임패드, 터치 및 키보드 입력 이벤트를 기록 다운로드 로그 다운로드 리졸버 및 WinNative 폴백 이벤트를 기록 + 멈춤 로그 + 게임이 그리기를 멈추면 각 게스트 스레드의 상태를 기록합니다 Vulkan 검증 레이어 적용하려면 디스플레이를 다시 시작하세요. 로그 공유 diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml index a8e1d956c..d2ea1de91 100644 --- a/app/src/main/res/values-no/strings.xml +++ b/app/src/main/res/values-no/strings.xml @@ -1118,6 +1118,8 @@ F.eks. META for META-tast, \n Logg inndatahendelser fra spillkontroller, berøring og tastatur Nedlastingslogger Logg nedlastingsresolver- og WinNative-reservehendelser + Frysingslogg + Når spillet slutter å tegne, registrer hva hver gjestetråd gjør Vulkan-valideringslag Start skjermen på nytt for å ta i bruk. Del logger diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index cb31a3698..57f56979c 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -836,6 +836,8 @@ Np. META dla klawisza META, \n Loguj zdarzenia gamepada, dotyku i klawiatury Logi pobierania Loguj zdarzenia resolvera pobierania i fallbacku WinNative + Dziennik zawieszeń + Gdy gra przestaje rysować, zapisz co robi każdy wątek gościa Warstwy walidacji Vulkan Uruchom ponownie wyświetlanie, aby zastosować. Udostępnij logi diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 867da9ba0..6f6a51d5b 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -830,6 +830,8 @@ Ex. META para tecla META, \n Registrar eventos de entrada do gamepad, toque e teclado Logs de Download Registrar eventos do resolvedor de download e fallback do WinNative + Registro de travamentos + Quando o jogo para de desenhar, registrar o que cada thread convidada está fazendo Camadas de Validação Vulkan Reinicie a exibição para aplicar. Compartilhar Logs diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 42a3c3328..d4dcb98d4 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -1118,6 +1118,8 @@ Por ex. META para tecla META, \n Registar eventos de entrada de comando, toque e teclado Registos de Transferências Registar eventos do resolvedor de transferências e de recurso do WinNative + Registo de bloqueios + Quando o jogo deixa de desenhar, registar o que cada thread convidada está a fazer Camadas de Validação do Vulkan Reinicie o ecrã para aplicar. Partilhar Registos diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 39310269d..3ea26a181 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -830,6 +830,8 @@ De ex. META pentru tasta META, \n Inregistreaza evenimentele de intrare gamepad, tactile si tastatura Jurnale descarcari Inregistreaza evenimentele de rezolvare descarcari si alternativa WinNative + Jurnal de blocare + Când jocul nu mai desenează, înregistrează ce face fiecare fir invitat Straturi de validare Vulkan Repornește afișajul pentru aplicare. Partajeaza jurnalele diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 5bcfbf86f..b675b1b6f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -901,6 +901,8 @@ Записывать события ввода с геймпада, сенсорного экрана и клавиатуры. Журналы загрузок Записывать события загрузок и резервного копирования WinNative + Журнал зависаний + Когда игра перестаёт отрисовывать, записывать, чем занят каждый гостевой поток Слои валидации Vulkan Перезапустите дисплей, чтобы применить. Поделиться журналами diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index ba4f2a24c..ffeba7f7a 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -1118,6 +1118,8 @@ T.ex. META för META-tangent, \n Logga inmatningshändelser från gamepad, pekskärm och tangentbord Nedladdningsloggar Logga händelser för nedladdningsupplösare och WinNative-reserv + Frysningslogg + När spelet slutar rita, registrera vad varje gästtråd gör Vulkan-valideringslager Starta om skärmen för att tillämpa. Dela loggar diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 8f9a8605d..6d8d8c031 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -1118,6 +1118,8 @@ บันทึกเหตุการณ์อินพุตของเกมแพด สัมผัส และคีย์บอร์ด บันทึกการดาวน์โหลด บันทึกเหตุการณ์ตัวแก้ไขการดาวน์โหลดและ WinNative fallback + บันทึกการค้าง + เมื่อเกมหยุดวาดภาพ ให้บันทึกว่าแต่ละเธรดของเกสต์กำลังทำอะไร Vulkan Validation Layers รีสตาร์ทการแสดงผลเพื่อนำไปใช้ แชร์บันทึก diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 1926d1fef..63a6d7069 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -1118,6 +1118,8 @@ E.g. META için META tuşu, \n Oyun kumandası, dokunma ve klavye giriş olaylarını kaydet İndirme Günlükleri İndirme çözümleyici ve WinNative geri dönüş olaylarını kaydet + Donma Günlüğü + Oyun çizmeyi bıraktığında her konuk iş parçacığının ne yaptığını kaydet Vulkan Doğrulama Katmanları Uygulamak için ekranı yeniden başlatın. Günlükleri Paylaş diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 02934faa7..423f3e635 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -836,6 +836,8 @@ Журналювати події геймпада, дотиків та клавіатури Журнали завантажень Журналювати події засобу розв\'язування завантажень та резервного механізму WinNative + Журнал зависань + Коли гра припиняє малювати, записувати, що робить кожен гостьовий потік Рівні перевірки Vulkan Перезапустіть дисплей, щоб застосувати. Поділитися журналами diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 1ce451421..99ce206ab 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -830,6 +830,8 @@ 记录手柄、触摸和键盘输入事件 下载日志 记录下载解析器和 WinNative 回退事件 + 卡死日志 + 当游戏停止绘制时,记录每个客户机线程正在做什么 Vulkan 验证层 重启显示以应用。 分享日志 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index c78b2797b..6f7766e3c 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -830,6 +830,8 @@ 記錄手把、觸控和鍵盤輸入事件 下載日誌 記錄下載解析器和 WinNative 備援事件 + 當機記錄 + 當遊戲停止繪製時,記錄每個客體執行緒正在做什麼 Vulkan 驗證層 重新啟動顯示以套用。 分享日誌 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 448bfdf3a..dc4b12c92 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1157,6 +1157,8 @@ E.g. META for META key, \n Log gamepad, touch, and keyboard input events Download Logs Log download resolver and WinNative fallback events + Freeze Log + When the game stops drawing, record what every guest thread is doing Vulkan Validation Layers Restart display to apply. Share Logs diff --git a/app/src/main/runtime/display/XServerDisplayActivity.java b/app/src/main/runtime/display/XServerDisplayActivity.java index 59b41cf52..9ef52f9e1 100644 --- a/app/src/main/runtime/display/XServerDisplayActivity.java +++ b/app/src/main/runtime/display/XServerDisplayActivity.java @@ -335,6 +335,7 @@ public void run() { private int taskAffinityMaskWoW64 = 0; private final HashSet guestAffinityCheckedPids = new HashSet<>(); private volatile boolean serviceAffinityStarted = false; + private volatile boolean freezeWatchdogStarted = false; private static final String[] SERVICE_AFFINITY_PROCESSES = { "services.exe", "rpcss.exe", "svchost.exe", "winedevice.exe", "plugplay.exe", "conhost.exe", "start.exe" @@ -1820,6 +1821,7 @@ public void onUpdateWindowContent(Window window) { public void onMapWindow(Window window) { assignTaskAffinity(window); pinServiceAffinity(); + startFreezeWatchdog(); if ((effectiveShowFPS || controllerHudMode) && frameRating != null) { syncFrameRatingWithExistingWindows(); } @@ -8080,6 +8082,8 @@ private void extractGraphicsDriverFiles() { gamenativeMarker.delete(); } + logWrapperIdentity(rootDir); + // libgallium_wgl.dll is present only while Windows Zink is installed — use as marker. if (wineInfo != null && wineInfo.isArm64EC() && !GPUInformation.getRenderer(null, null).contains("Mali")) { @@ -11353,6 +11357,96 @@ else if (!className.isEmpty()) { // policy without ever re-sending — manual task manager changes stick. // Services go to the efficiency cores (lower half), shell/UI to the 64-bit // list, winhandler to all cores. + // Says which libvulkan_wrapper.so the container will actually load, so a stale + // or hand-copied .so can't be mistaken for the shipped one. + private void logWrapperIdentity(File rootDir) { + try { + File so = new File(rootDir, "usr/lib/libvulkan_wrapper.so"); + if (!so.isFile()) { + Log.w("WrapperIdentity", "MISSING " + so.getAbsolutePath()); + return; + } + int traces = 0; + byte[] needle = "WNTRACE".getBytes("UTF-8"); + try (java.io.FileInputStream in = new java.io.FileInputStream(so)) { + byte[] buf = new byte[1 << 20]; + int n, carry = 0; + byte[] prev = new byte[needle.length]; + while ((n = in.read(buf)) > 0) { + for (int i = 0; i <= n - needle.length; i++) { + boolean hit = true; + for (int j = 0; j < needle.length; j++) + if (buf[i + j] != needle[j]) { hit = false; break; } + if (hit) traces++; + } + } + } + Log.w("WrapperIdentity", "path=" + so.getAbsolutePath() + + " size=" + so.length() + + " mtime=" + so.lastModified() + + " WNTRACE=" + traces + + (traces >= 16 ? " (round3 instrumented)" + : traces >= 10 ? " (round2 instrumented)" + : traces > 0 ? " (older instrumented)" : " (STOCK - no tracing)")); + } catch (Exception e) { + Log.w("WrapperIdentity", "check failed: " + e); + } + } + + // Presents stop when the guest stalls; dump the kernel's view of every guest + // thread so we can see what it is actually blocked on. + private void startFreezeWatchdog() { + if (freezeWatchdogStarted) return; + if (!PreferenceManager.getDefaultSharedPreferences(this) + .getBoolean("enable_freeze_log", false)) return; + freezeWatchdogStarted = true; + final String freezeLogName = "freeze_" + + new java.text.SimpleDateFormat("yyyyMMdd_HHmmss", java.util.Locale.US) + .format(new java.util.Date()) + ".log"; + new Thread(() -> { + try { if (imageFs != null) logWrapperIdentity(imageFs.getRootDir()); } catch (Exception ignored) {} + final long stallNs = 5_000_000_000L; + final long repeatNs = 15_000_000_000L; + long lastDumpNs = 0; + int dumps = 0; + String kgslBase = ProcessHelper.readKgslCounters(); + Log.w("FreezeWatchdog", "kgsl baseline: " + kgslBase); + while (dumps < 6) { + try { Thread.sleep(1000); } catch (InterruptedException e) { return; } + long last = com.winlator.cmod.runtime.display.xserver.extensions.PresentExtension.lastPresentNanos; + if (last == 0) continue; + long now = System.nanoTime(); + long idleNs = now - last; + if (idleNs < stallNs) continue; + if (lastDumpNs != 0 && now - lastDumpNs < repeatNs) continue; + lastDumpNs = now; + dumps++; + String hdr = "No present for " + (idleNs / 1000000) + " ms (dump " + dumps + "/6)"; + Log.w("FreezeWatchdog", hdr + " — full dump in logs/" + freezeLogName); + StringBuilder rep = new StringBuilder(); + rep.append("\n========== ").append(hdr).append(" ==========\n") + .append("kgsl baseline: ").append(kgslBase).append('\n') + .append("kgsl now : ").append(ProcessHelper.readKgslCounters()).append('\n') + .append(ProcessHelper.dumpGuestThreadStates()) + .append(ProcessHelper.dumpGuestMaps()); + // logcat rate-limits and silently drops the game's several-hundred map + // lines, so the full dump goes to a file the log export already collects + try { + File ld = new File(getExternalFilesDir(null), "logs"); + if (ld.isDirectory() || ld.mkdirs()) { + try (java.io.FileWriter fw = new java.io.FileWriter(new File(ld, freezeLogName), true)) { + fw.write(rep.toString()); + } + } + } catch (Exception e) { + Log.w("FreezeWatchdog", "file dump failed: " + e); + } + for (String line : ProcessHelper.dumpGuestThreadStates().split("\n")) + Log.w("FreezeWatchdog", line); + } + }, "FreezeWatchdog").start(); + } + private void pinServiceAffinity() { if (serviceAffinityStarted || winHandler == null) return; serviceAffinityStarted = true; diff --git a/app/src/main/runtime/display/xserver/extensions/PresentExtension.java b/app/src/main/runtime/display/xserver/extensions/PresentExtension.java index 40b14ae2d..6d2812785 100644 --- a/app/src/main/runtime/display/xserver/extensions/PresentExtension.java +++ b/app/src/main/runtime/display/xserver/extensions/PresentExtension.java @@ -36,6 +36,7 @@ public class PresentExtension XResourceManager.OnResourceLifecycleListener, WindowManager.OnWindowModificationListener { public static final byte MAJOR_OPCODE = -103; + public static volatile long lastPresentNanos = 0; private static final int FAKE_INTERVAL = 1000000 / 60; private byte firstEventId = 0; private byte firstErrorId = 0; @@ -479,6 +480,7 @@ public void handleRequest(XClient client, XInputStream inputStream, XOutputStrea // necessary, import the sync_file as a Vulkan semaphore and chain it into the // submit instead of blocking here. PresentPixmapParams p = parsePresentPixmap(client, inputStream); + lastPresentNanos = System.nanoTime(); try (XLock lock = client.xServer.lock(XServer.Lockable.WINDOW_MANAGER, XServer.Lockable.PIXMAP_MANAGER)) { diff --git a/app/src/main/runtime/system/ProcessHelper.java b/app/src/main/runtime/system/ProcessHelper.java index 15ef604bc..863947339 100644 --- a/app/src/main/runtime/system/ProcessHelper.java +++ b/app/src/main/runtime/system/ProcessHelper.java @@ -605,6 +605,100 @@ public boolean accept(File proc, String filename) { return filteredPids; } + private static String readProcFile(String path) { + try (FileInputStream in = new FileInputStream(path)) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buf = new byte[4096]; + int n; + while ((n = in.read(buf)) > 0) out.write(buf, 0, n); + return out.toString("UTF-8").trim(); + } catch (Exception e) { + return ""; + } + } + + // GPU fault/reset bookkeeping from the kgsl driver. A reset counter that moves + // across a freeze means the GPU hung and the kernel recovered it. + // Probe fixed filenames: the kgsl dir is usually not listable by an app even though + // individual attributes inside it open fine. + private static final String[] KGSL_ATTRS = { + "reset_count", "gpu_reset_stat", "gpu_reset_count", "fault_count", "gpu_fault", + "hang_count", "recovery_count", "snapshot_crashdumper", "gpubusy", "temp", + "gpuclk", "gpu_busy_percentage", "gpu_model", "deviceid", + }; + + public static String readKgslCounters() { + StringBuilder sb = new StringBuilder(); + StringBuilder missing = new StringBuilder(); + for (String base : new String[] {"/sys/class/kgsl/kgsl-3d0", "/sys/devices/platform/soc/3d00000.qcom,kgsl-3d0/kgsl/kgsl-3d0"}) { + for (String n : KGSL_ATTRS) { + File f = new File(base, n); + if (!f.isFile()) continue; + String v = readProcFile(f.getAbsolutePath()); + if (v.isEmpty()) { if (missing.length() < 200) missing.append(n).append("? "); continue; } + if (v.length() > 120) v = v.substring(0, 120); + if (sb.length() > 0) sb.append(" "); + sb.append(n).append('=').append(v.replace('\n', '/')); + } + if (sb.length() > 0) break; + } + if (sb.length() == 0) + return "kgsl: no attributes readable (unreadable: " + missing + ")"; + return sb.toString(); + } + + // File-backed mappings for the biggest guest process, so faulting addresses seen + // in the wine log can be resolved to a library. + public static String dumpGuestMaps() { + StringBuilder sb = new StringBuilder("=== guest library maps ===\n"); + for (String pid : listRunningWineProcesses()) { + String maps = readProcFile("/proc/" + pid + "/maps"); + if (maps.isEmpty()) continue; + StringBuilder one = new StringBuilder(); + for (String line : maps.split("\n")) { + // every executable mapping, file-backed or anonymous (anon exec = JIT code) + String[] f = line.trim().split("\\s+", 6); + if (f.length < 2 || f[1].length() < 3 || f[1].charAt(2) != 'x') continue; + String path = f.length >= 6 ? f[5].trim() : ""; + one.append(" ").append(f[0]).append(' ').append(f[1]).append(" ") + .append(path.isEmpty() ? "[anon-exec]" : path).append('\n'); + } + if (one.length() > 0) sb.append("pid ").append(pid).append('\n').append(one); + } + return sb.toString(); + } + + // Kernel's own record of where each guest thread is parked. Readable because the + // guest runs under our uid. R=running S=sleeping D=uninterruptible. + public static String dumpGuestThreadStates() { + StringBuilder sb = new StringBuilder("=== guest thread states ===\n"); + for (String pid : listRunningWineProcesses()) { + String cmdline = readProcFile("/proc/" + pid + "/cmdline").replace('\0', ' ').trim(); + sb.append("pid ").append(pid).append(" ").append(cmdline).append('\n'); + File taskDir = new File("/proc/" + pid + "/task"); + String[] tids = taskDir.list(); + if (tids == null) continue; + Arrays.sort(tids); + for (String tid : tids) { + String base = "/proc/" + pid + "/task/" + tid + "/"; + String comm = readProcFile(base + "comm"); + String syscall = readProcFile(base + "syscall"); + String wchan = readProcFile(base + "wchan"); + String stat = readProcFile(base + "stat"); + String state = "?"; + int close = stat.lastIndexOf(')'); + if (close > 0 && close + 2 < stat.length()) state = stat.substring(close + 2, close + 3); + sb.append(" tid ").append(tid) + .append(" [").append(state).append("] ") + .append(comm.isEmpty() ? "-" : comm) + .append(" wchan=").append(wchan.isEmpty() ? "-" : wchan) + .append(" syscall=").append(syscall.isEmpty() ? "-" : syscall) + .append('\n'); + } + } + return sb.toString(); + } + public static ArrayList listRunningWineProcessDetails() { File proc = new File("/proc"); String[] allPids =