mirror of
https://github.com/StikDebug/StikDebug.git
synced 2026-10-16 04:20:30 +00:00
Fix keep-alive crash on non-JIT apps (e.g. Roblox)
Keep-alive ran the universal JIT breakpoint-handler script against every app on
TXM devices. That script is meant for apps that use the brk #0xf00d JIT protocol
(emulators). Against a large, multi-threaded, non-JIT app like Roblox it chases
every signal the app raises — logging verbosely each iteration — which:
- grows RunJSViewModel.logs (a @Published array rendered in a SwiftUI List)
without bound until StikDebug runs out of memory and crashes, and
- mishandles the app's many threads (single-thread vCont), wedging it.
Two fixes:
- keepAliveScript now returns a script only for user-assigned or name-matched
(known JIT) apps. Everything else is held with a plain debugger attach, which
keeps the app alive in the background without interfering with its threads or
signals. This restores the proven plain-hold path for apps like Roblox.
- appendLog caps the log buffer (with amortized trimming) so a long-running or
spinning hold script can never exhaust memory. The user can still stop such a
hold via should_continue().
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EpCxAimrBo9RA8v6BxSEXT
This commit is contained in:
parent
e4f02100ee
commit
67794a0557
2 changed files with 30 additions and 31 deletions
|
|
@ -69,7 +69,7 @@ final class RunJSViewModel: ObservableObject, Identifiable, @unchecked Sendable
|
|||
|
||||
let logFunction: @convention(block) (String) -> Void = { logStr in
|
||||
DispatchQueue.main.async {
|
||||
self.logs.append(logStr)
|
||||
self.appendLog(logStr)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -111,13 +111,27 @@ final class RunJSViewModel: ObservableObject, Identifiable, @unchecked Sendable
|
|||
|
||||
DispatchQueue.main.async {
|
||||
if let exception = self.context?.exception {
|
||||
self.logs.append(exception.debugDescription)
|
||||
self.appendLog(exception.debugDescription)
|
||||
}
|
||||
self.logs.append("Script Execution Completed")
|
||||
self.logs.append("You are safe to close this window.")
|
||||
self.appendLog("Script Execution Completed")
|
||||
self.appendLog("You are safe to close this window.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Appends a log line, keeping the buffer bounded. A hold-mode script (e.g.
|
||||
/// universal.js) can run indefinitely and log on every iteration; without a
|
||||
/// cap the `@Published` array — and the SwiftUI list bound to it — grows
|
||||
/// without bound and eventually exhausts memory. Must run on the main thread.
|
||||
private func appendLog(_ entry: String) {
|
||||
logs.append(entry)
|
||||
if logs.count > Self.maxLogEntries + Self.logTrimSlack {
|
||||
logs.removeFirst(logs.count - Self.maxLogEntries)
|
||||
}
|
||||
}
|
||||
|
||||
private static let maxLogEntries = 1000
|
||||
private static let logTrimSlack = 256
|
||||
|
||||
private func captureScreenshot(named preferredName: String?) -> String {
|
||||
if executionInterrupted {
|
||||
raiseException("Script execution is interrupted by StikDebug.")
|
||||
|
|
|
|||
|
|
@ -72,15 +72,17 @@ enum ScriptStore {
|
|||
assignedScript(for: bundleID, fileManager: fileManager) ?? autoScript(for: bundleID, fileManager: fileManager)
|
||||
}
|
||||
|
||||
/// The JIT script to run when holding an app alive in the background.
|
||||
/// The JIT script to run while holding an app alive in the background, or
|
||||
/// `nil` to hold the app with a plain debugger attach.
|
||||
///
|
||||
/// On a TXM device (iOS 26+) JIT is serviced by handling `brk #0xf00d`
|
||||
/// syscalls, so an app that is merely attached — with no script servicing
|
||||
/// those traps — hangs the moment it requests executable memory. That is
|
||||
/// why keep-alive "didn't work for any app": only apps whose name/bundle
|
||||
/// matched an assigned or auto script got a callback. Fall back to the
|
||||
/// bundled universal script so *any* app has its JIT traps serviced (and,
|
||||
/// for non-JIT apps, its debug connection actively held) while alive.
|
||||
/// Only apps with a user-assigned or name-matched (known JIT app) script get
|
||||
/// a script. Those apps request executable memory via `brk #0xf00d` syscalls
|
||||
/// that the script must service, so without it they hang. Apps that do *not*
|
||||
/// use JIT — e.g. Roblox and most games — are deliberately held with a plain
|
||||
/// attach instead: running the JIT breakpoint handler against them makes it
|
||||
/// chase every signal the app raises across all of its threads, which floods
|
||||
/// the log (and can crash a large multi-threaded app or StikDebug itself). A
|
||||
/// plain attach keeps them alive in the background without interfering.
|
||||
///
|
||||
/// Returns `nil` on non-TXM devices, where JIT comes from `CS_DEBUGGED`
|
||||
/// alone and a plain attach is enough to hold the app.
|
||||
|
|
@ -89,24 +91,7 @@ enum ScriptStore {
|
|||
return nil
|
||||
}
|
||||
|
||||
if let preferred = preferredScript(for: bundleID, fileManager: fileManager) {
|
||||
return preferred
|
||||
}
|
||||
|
||||
return bundledScript(resourceName: "universal", fileName: "universal.js")
|
||||
}
|
||||
|
||||
/// Reads a bundled script straight from the app bundle (not the user's
|
||||
/// scripts directory). Used for the keep-alive fallback so the current
|
||||
/// shipped script — which supports `should_continue()` cancellation — is
|
||||
/// always used, even for users whose documents copy predates it.
|
||||
private static func bundledScript(resourceName: String, fileName: String) -> (data: Data, name: String)? {
|
||||
guard let bundleURL = Bundle.main.url(forResource: resourceName, withExtension: "js"),
|
||||
let data = try? Data(contentsOf: bundleURL) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (data, fileName)
|
||||
return preferredScript(for: bundleID, fileManager: fileManager)
|
||||
}
|
||||
|
||||
static func favoriteAppName(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue