mirror of
https://github.com/StikDebug/StikJIT.git
synced 2026-10-15 12:02:15 +00:00
Support for legacy script (#1)
This commit is contained in:
parent
5bdecfcafa
commit
2320badda8
8 changed files with 128 additions and 13 deletions
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
|
@ -50,7 +50,7 @@ jobs:
|
|||
run: zip -ry StikJIT.xcframework.zip StikJIT.xcframework
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: StikJIT.xcframework
|
||||
path: StikJIT.xcframework.zip
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
An iOS XCFramework that enables JIT for another process over the device's RSD tunnel.
|
||||
|
||||
JIT cannot be enabled in-process, since a process that attaches a debugger to itself deadlocks. StikJIT runs in a separate process, attaches a debugserver to the target by PID, enables JIT for it, then detaches. It is self-contained: the idevice FFI and `universal.js` are bundled inside. The target must have the `get-task-allow` entitlement.
|
||||
JIT cannot be enabled in-process, since a process that attaches a debugger to itself deadlocks. StikJIT runs in a separate process, attaches a debugserver to the target by PID, enables JIT for it, then detaches. It is self-contained: the idevice FFI and its bundled JIT scripts (`universal.js`, `legacy.js`) are bundled inside. The target must have the `get-task-allow` entitlement.
|
||||
|
||||
## Use
|
||||
|
||||
|
|
@ -20,7 +20,7 @@ try StikJIT.enableJIT(
|
|||
)
|
||||
```
|
||||
|
||||
It blocks until done and throws `StikJITError` on failure. Pass `configuration:` to override the tunnel endpoint (defaults to `10.7.0.1:49152`).
|
||||
It blocks until done and throws `StikJITError` on failure. Pass `configuration:` to override the tunnel endpoint (defaults to `10.7.0.1:49152`). Pass `script:` to select the bundled JS used to drive the JIT-enabling exchange on devices with TXM — `.universal` (default) or `.legacy` based on your app's needs.
|
||||
|
||||
## Build
|
||||
|
||||
|
|
@ -35,4 +35,4 @@ xcodebuild -create-xcframework \
|
|||
|
||||
## License
|
||||
|
||||
StikJIT is licensed under the MPL-2.0 (see [`LICENSE`](LICENSE)). It uses StikDebug as a reference, with the bundled [idevice](https://github.com/jkcoxson/idevice) and universal.js retaining their own licenses.
|
||||
StikJIT is licensed under the MPL-2.0 (see [`LICENSE`](LICENSE)). It uses StikDebug as a reference, with the bundled [idevice](https://github.com/jkcoxson/idevice), universal.js, and legacy.js retaining their own licenses.
|
||||
|
|
|
|||
98
Resources/legacy.js
Normal file
98
Resources/legacy.js
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
function littleEndianHexStringToNumber(hexStr) {
|
||||
const bytes = [];
|
||||
for (let i = 0; i < hexStr.length; i += 2) {
|
||||
bytes.push(parseInt(hexStr.substr(i, 2), 16));
|
||||
}
|
||||
let num = 0n;
|
||||
for (let i = 4; i >= 0; i--) {
|
||||
num = (num << 8n) | BigInt(bytes[i]);
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
function numberToLittleEndianHexString(num) {
|
||||
const bytes = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
bytes.push(Number(num & 0xFFn));
|
||||
num >>= 8n;
|
||||
}
|
||||
while (bytes.length < 8) {
|
||||
bytes.push(0);
|
||||
}
|
||||
return bytes.map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
function littleEndianHexToU32(hexStr) {
|
||||
return parseInt(hexStr.match(/../g).reverse().join(''), 16);
|
||||
}
|
||||
|
||||
function extractBrkImmediate(u32) {
|
||||
return (u32 >> 5) & 0xFFFF;
|
||||
}
|
||||
|
||||
function attach(breakpointcount) {
|
||||
let pid = get_pid();
|
||||
log(`pid = ${pid}`);
|
||||
let attachResponse = send_command(`vAttach;${pid.toString(16)}`);
|
||||
log(`attach_response = ${attachResponse}`);
|
||||
|
||||
let validBreakpoints = 0;
|
||||
let totalBreakpoints = 0;
|
||||
|
||||
while (validBreakpoints < breakpointcount) {
|
||||
totalBreakpoints++;
|
||||
log(`Handling breakpoint ${totalBreakpoints} (looking for valid breakpoint ${validBreakpoints + 1}/${breakpointcount})`);
|
||||
|
||||
let brkResponse = send_command(`c`);
|
||||
log(`brkResponse = ${brkResponse}`);
|
||||
|
||||
let tidMatch = /T[0-9a-f]+thread:(?<tid>[0-9a-f]+);/.exec(brkResponse);
|
||||
let tid = tidMatch ? tidMatch.groups['tid'] : null;
|
||||
let pcMatch = /20:(?<reg>[0-9a-f]{16});/.exec(brkResponse);
|
||||
let pc = pcMatch ? pcMatch.groups['reg'] : null;
|
||||
let x0Match = /00:(?<reg>[0-9a-f]{16});/.exec(brkResponse);
|
||||
let x0 = x0Match ? x0Match.groups['reg'] : null;
|
||||
let x1Match = /01:(?<reg>[0-9a-f]{16});/.exec(brkResponse);
|
||||
let x1 = x1Match ? x1Match.groups['reg'] : null;
|
||||
|
||||
if (!tid || !pc || !x0 || !x1) {
|
||||
log(`Failed to extract registers: tid=${tid}, pc=${pc}, x0=${x0}, x1=${x1}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const pcNum = littleEndianHexStringToNumber(pc);
|
||||
const x0Num = littleEndianHexStringToNumber(x0);
|
||||
const x1Num = littleEndianHexStringToNumber(x1);
|
||||
log(`tid = ${tid}, pc = ${pcNum.toString(16)}, x0 = ${x0Num.toString(16)}, x1 = ${x1Num.toString(16)}`);
|
||||
|
||||
let instructionResponse = send_command(`m${pcNum.toString(16)},4`);
|
||||
log(`instruction at pc: ${instructionResponse}`);
|
||||
let instrU32 = littleEndianHexToU32(instructionResponse);
|
||||
let brkImmediate = extractBrkImmediate(instrU32);
|
||||
log(`BRK immediate: 0x${brkImmediate.toString(16)} (${brkImmediate})`);
|
||||
|
||||
if (brkImmediate !== 0x69) {
|
||||
log(`Skipping breakpoint: brk immediate was not 0x69 (was 0x${brkImmediate.toString(16)})`);
|
||||
continue;
|
||||
}
|
||||
|
||||
log(`BRK immediate matches expected value 0x69 - processing valid breakpoint ${validBreakpoints + 1}/${breakpointcount}`);
|
||||
|
||||
log(`Allocated JIT page at address: 0x${x0Num.toString(16)}`);
|
||||
|
||||
let prepareJITPageResponse = prepare_memory_region(x0Num, x1Num);
|
||||
log(`prepareJITPageResponse = ${prepareJITPageResponse}`);
|
||||
|
||||
let pcPlus4 = numberToLittleEndianHexString(pcNum + 4n);
|
||||
let pcPlus4Response = send_command(`P20=${pcPlus4};thread:${tid};`);
|
||||
log(`pcPlus4Response = ${pcPlus4Response}`);
|
||||
|
||||
validBreakpoints++;
|
||||
log(`Completed valid breakpoint ${validBreakpoints}/${breakpointcount}`);
|
||||
}
|
||||
|
||||
let detachResponse = send_command(`D`);
|
||||
log(`detachResponse = ${detachResponse}`);
|
||||
}
|
||||
|
||||
attach(1);
|
||||
|
|
@ -2,9 +2,9 @@ import Foundation
|
|||
|
||||
enum BundledScript {
|
||||
|
||||
static func universalJS() throws -> String {
|
||||
static func source(for script: StikJIT.Script) throws -> String {
|
||||
let bundle = Bundle(for: BundleToken.self)
|
||||
guard let url = bundle.url(forResource: "universal", withExtension: "js"),
|
||||
guard let url = bundle.url(forResource: script.resourceName, withExtension: "js"),
|
||||
let source = try? String(contentsOf: url, encoding: .utf8),
|
||||
!source.isEmpty else {
|
||||
throw StikJITError.scriptUnavailable
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ final class JITSession {
|
|||
self.configuration = configuration
|
||||
}
|
||||
|
||||
func enableJIT(targetPID: Int32, progress: @escaping (String) -> Void) throws {
|
||||
func enableJIT(targetPID: Int32, script: StikJIT.Script, progress: @escaping (String) -> Void) throws {
|
||||
let tunnel = try makeTunnel()
|
||||
defer { tunnel.free() }
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ final class JITSession {
|
|||
debug_proxy_set_ack_mode(debugProxy, 0)
|
||||
|
||||
if ProcessInfo.processInfo.hasTXM {
|
||||
try ScriptRunner(targetPID: targetPID, debugProxy: debugProxy, progress: progress).run()
|
||||
try ScriptRunner(targetPID: targetPID, debugProxy: debugProxy, script: script, progress: progress).run()
|
||||
} else {
|
||||
try attachWithoutScript(targetPID: targetPID, debugProxy: debugProxy, progress: progress)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,17 +8,19 @@ final class ScriptRunner {
|
|||
|
||||
private let targetPID: Int32
|
||||
private let debugProxy: OpaquePointer
|
||||
private let script: StikJIT.Script
|
||||
private let progress: (String) -> Void
|
||||
private var context: JSContext?
|
||||
|
||||
init(targetPID: Int32, debugProxy: OpaquePointer, progress: @escaping (String) -> Void) {
|
||||
init(targetPID: Int32, debugProxy: OpaquePointer, script: StikJIT.Script, progress: @escaping (String) -> Void) {
|
||||
self.targetPID = targetPID
|
||||
self.debugProxy = debugProxy
|
||||
self.script = script
|
||||
self.progress = progress
|
||||
}
|
||||
|
||||
func run() throws {
|
||||
let source = try BundledScript.universalJS()
|
||||
let source = try BundledScript.source(for: script)
|
||||
|
||||
guard let context = JSContext() else { throw StikJITError.scriptUnavailable }
|
||||
self.context = context
|
||||
|
|
@ -45,7 +47,7 @@ final class ScriptRunner {
|
|||
context.setObject(prepare, forKeyedSubscript: "prepare_memory_region" as NSString)
|
||||
context.setObject(log, forKeyedSubscript: "log" as NSString)
|
||||
|
||||
progress("Running universal.js against pid \(targetPID)…")
|
||||
progress("Running \(script.resourceName).js against pid \(targetPID)…")
|
||||
context.evaluateScript(source)
|
||||
progress("JIT script finished (region blessed, detached).")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,20 @@ import Foundation
|
|||
|
||||
public enum StikJIT {
|
||||
|
||||
public enum Script: Sendable {
|
||||
|
||||
case universal
|
||||
|
||||
case legacy
|
||||
|
||||
var resourceName: String {
|
||||
switch self {
|
||||
case .universal: return "universal"
|
||||
case .legacy: return "legacy"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct Configuration: Sendable {
|
||||
|
||||
public var deviceAddress: String
|
||||
|
|
@ -19,8 +33,9 @@ public enum StikJIT {
|
|||
public static func enableJIT(targetPID: Int32,
|
||||
pairingFile: URL,
|
||||
configuration: Configuration = .default,
|
||||
script: Script = .universal,
|
||||
progress: @escaping (String) -> Void = { _ in }) throws {
|
||||
let session = JITSession(pairingFilePath: pairingFile.path, configuration: configuration)
|
||||
try session.enableJIT(targetPID: targetPID, progress: progress)
|
||||
try session.enableJIT(targetPID: targetPID, script: script, progress: progress)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ public enum StikJITError: Error, LocalizedError {
|
|||
case .pairingFile(let detail):
|
||||
return "Pairing file error: \(detail)."
|
||||
case .scriptUnavailable:
|
||||
return "The bundled universal.js could not be loaded from the StikJIT framework."
|
||||
return "The bundled JIT script could not be loaded from the StikJIT framework."
|
||||
case .debugProxyUnavailable:
|
||||
return "Failed to establish a debugserver connection to the target process."
|
||||
case .device(let code, let subCode, let message):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue