diff --git a/StikJIT/Amethyst.js b/StikJIT/Amethyst.js index b90c0c5..3e1da1f 100644 --- a/StikJIT/Amethyst.js +++ b/StikJIT/Amethyst.js @@ -1,89 +1,293 @@ +// Universal JIT Script, last updated 2025-10-10 +/* + // JIT "syscalls" + __attribute__((noinline,optnone,naked)) + void JIT26Detach(void) { + asm("mov x16, #0 \n" + "brk #0xf00d \n" + "ret"); + } + __attribute__((noinline,optnone,naked)) + void* JIT26PrepareRegion(void *addr, size_t len) { + asm("mov x16, #1 \n" + "brk #0xf00d \n" + "ret"); + } + __attribute__((noinline,optnone,naked)) +void BreakSendJITScript(char* script, size_t len) { + asm("mov x16, #2 \n" + "brk #0xf00d \n" + "ret"); +} + */ +const CMD_DETACH = 0; +const CMD_PREPARE_REGION = 1; +const CMD_NEW_BREAKPOINTS = 2; +const commands = { + [CMD_DETACH]: JIT26Detach, + [CMD_PREPARE_REGION]: JIT26PrepareRegion, + [CMD_NEW_BREAKPOINTS]: JIT26NewBreakpoints +}; +const legacyCommands = { + [0x68]: JIT26NewBreakpoints, + [0x69]: JIT26HandleBrk0x69, + [0xf00d]: JIT26HandleBrk0xf00d +}; + +// Log levels +//const LOG_NONE = 0; +const LOG_INFO = 1; +const LOG_VERBOSE = 2; +let logLevel = LOG_VERBOSE; +function log_verbose(msg) { + if (logLevel >= LOG_VERBOSE) { + log(msg); + } +} + +// To avoid having to re-parse these in each function, we save some registers here +let tid, x0, x1, x16, pc; +let detached = false; +let pid = get_pid(); +let attachResponse = send_command(`vAttach;${pid.toString(16)}`); + +log(`pid = ${pid}`); +log(`attach_response = ${attachResponse}`); + +let totalBreakpoints = 0; +while (!detached) { + totalBreakpoints++; + log(`Handling signal ${totalBreakpoints}`); + + let brkResponse = send_command(`c`); + log_verbose(`brkResponse = ${brkResponse}`); + + // extract tid, pc, x16 + let tmpMatch = /T[0-9a-f]+thread:(?[0-9a-f]+);/.exec(brkResponse); + tid = tmpMatch ? tmpMatch.groups['tid'] : null; + tmpMatch = /20:(?[0-9a-f]{16});/.exec(brkResponse); + pc = tmpMatch ? tmpMatch.groups['reg'] : null; + tmpMatch = /10:(?[0-9a-f]{16});/.exec(brkResponse); + x16 = tmpMatch ? tmpMatch.groups['reg'] : null; + if (!tid || !pc || !x16) { + log(`Failed to extract registers: tid=${tid}, pc=${pc}, x16=${x16}`); + continue; + } + pc = littleEndianHexStringToNumber(pc); + x16 = littleEndianHexStringToNumber(x16); + + let instructionResponse = send_command(`m${pc.toString(16)},4`); + log(`instruction at pc: ${instructionResponse}`); + let instrU32 = littleEndianHexToU32(instructionResponse); + + // check if this is a brk + if ((instrU32 & 0xFFE0001F)>>>0 != 0xD4200000) { + log(`Skipping: instruction was not a brk (was 0x${instrU32.toString(16)})`); + let signum = /^T(?[a-z0-9;]{2})/.exec(brkResponse); + signum = signum ? signum.groups['sig'] : null; + if (!signum) { + log(`Failed to extract signal number: ${signum}`); + continue; + } + log(`Continuing with signal 0x${signum}`); + send_command(`vCont;S${signum}:${tid}`); + continue; + } + + let brkImmediate = extractBrkImmediate(instrU32); + log(`BRK immediate: 0x${brkImmediate.toString(16)} (${brkImmediate})`); + if (legacyCommands[brkImmediate] != undefined) { + // when we find a valid brk immediate command, parse x0 and x1 + tmpMatch = /00:(?[0-9a-f]{16});/.exec(brkResponse); + x0 = tmpMatch ? tmpMatch.groups['reg'] : null; + tmpMatch = /01:(?[0-9a-f]{16});/.exec(brkResponse); + x1 = tmpMatch ? tmpMatch.groups['reg'] : null; + if (!x0 || !x1) { + log(`Failed to extract registers: x0=${x0}, x1=${x1}`); + continue; + } + x0 = littleEndianHexStringToNumber(x0); + x1 = littleEndianHexStringToNumber(x1); + + // jump over brk + let pcPlus4 = numberToLittleEndianHexString(pc + 4n); + let pcPlus4Response = send_command(`P20=${pcPlus4};thread:${tid};`); + log(`pcPlus4Response = ${pcPlus4Response}`); + + // dispatch brk-immediate command + const command = legacyCommands[brkImmediate]; + command(brkResponse); + } else { + log(`Skipping breakpoint: brk immediate 0x${brkImmediate.toString(16)} was not handled by this script. You could add it by evaluating legacyCommands[0x${brkImmediate.toString(16)}] = yourFunction;`); + continue; + } +} + +function JIT26Detach() { + let detachResponse = send_command(`D`); + log_verbose(`detachResponse = ${detachResponse}`); + detached = true; +} + +// brk 0x68 +function JIT26NewBreakpoints(brkResponse) { + let instructionResponse = send_command(`m${pc.toString(16)},4`); + log(`instruction at pc: ${instructionResponse}`); + let instrU32 = littleEndianHexToU32(instructionResponse); + let brkImmediate = extractBrkImmediate(instrU32); + + let memResponse = send_command(`m${x0.toString(16)},${x1}`); + + let scriptText = hexToAscii(memResponse); + log_verbose(`Script text: ${scriptText}`); + + const res = runScriptAndCapture(scriptText); + if (res.ok) { + log('Script succeeded:', res.value); + } else { + log('Script failed:', res.name, res.message); + log(res.stack); + } +} + +// brk 0x69 +function JIT26HandleBrk0x69(brkResponse) { + // in the old script we chose 0x69, so now we check here and return error + // if you wish to keep using this, you can set your own handler like `legacyCommands[0x69] = yourHandler;` using BreakSendJITScript + log(`Error: It seems you are using legacy breakpoint 0x69. Please set your legacy handler using \`legacyCommands[0x69] = yourHandler;\` or migrate to universal jitcalls to use this script. The function will now return 0xE0000069.`); + let putX0Response = send_command(`P0=E0000069;thread:${tid};`); + log(`putX0Response = ${putX0Response}`); +} + +// brk 0xf00d +function JIT26HandleBrk0xf00d(brkResponse) { + // dispatch command via x16 + const command = commands[x16]; + if (command === undefined) { + log(`Unknown command ${x16.toString(16)}`); + return; + } + log(`Invoking command ${x16.toString(16)}`); + command(brkResponse); +} + +function JIT26PrepareRegion(brkResponse) { + let instructionResponse = send_command(`m${pc.toString(16)},4`); + log(`instruction at pc: ${instructionResponse}`); + let instrU32 = littleEndianHexToU32(instructionResponse); + let brkImmediate = extractBrkImmediate(instrU32); + + if (x0 == 0n && x1 == 0n) { + return; + } + + let jitPageAddress = x0; + if (x0 == 0n) { + let requestRXResponse = send_command(`_M${x1.toString(16)},rx`); + log_verbose(`requestRXResponse = ${requestRXResponse}`); + + if (!requestRXResponse || requestRXResponse.length === 0) { + log(`Failed to allocate RX memory`); + return; + } + + jitPageAddress = BigInt(`0x${requestRXResponse}`); + log(`Allocated JIT page at address: 0x${jitPageAddress.toString(16)}`); + } + + let prepareJITPageResponse = prepare_memory_region(jitPageAddress, x1); + log(`prepareJITPageResponse = ${prepareJITPageResponse}`); + + let putX0Response = send_command(`P0=${numberToLittleEndianHexString(jitPageAddress)};thread:${tid};`); + log(`putX0Response = ${putX0Response}`); +} + +// utilities function littleEndianHexStringToNumber(hexStr) { - // Convert hex string to byte array const bytes = []; for (let i = 0; i < hexStr.length; i += 2) { bytes.push(parseInt(hexStr.substr(i, 2), 16)); } - - // Assemble number from first 5 bytes (little-endian) let num = 0n; for (let i = 4; i >= 0; i--) { num = (num << 8n) | BigInt(bytes[i]); } - - return num; // BigInt: 0x01016644fc + return num; } function numberToLittleEndianHexString(num) { - // Extract 5 bytes in little-endian order const bytes = []; for (let i = 0; i < 5; i++) { bytes.push(Number(num & 0xFFn)); num >>= 8n; } - - // Pad with 3 zero bytes to make 8 bytes while (bytes.length < 8) { bytes.push(0); } - - // Convert to hex string return bytes.map(b => b.toString(16).padStart(2, '0')).join(''); } -function attach() { - let pid = get_pid(); - log(`pid = ${pid}`) - - // attach - let attachResponse = send_command(`vAttach;${pid.toString(16)}`) - log(`attach_response = ${attachResponse}`) - - // wait for brk - let brkResponse = send_command(`c`) - log(`brkResponse = ${brkResponse}`) - - let tid = /T[0-9a-f]+thread:(?[0-9a-f]+);/.exec(brkResponse).groups['tid'] - let pc = /20:(?[0-9a-f]{16});/.exec(brkResponse).groups['reg'] - let x0 = /00:(?[0-9a-f]{16});/.exec(brkResponse).groups['reg'] +function littleEndianHexToU32(hexStr) { + return parseInt(hexStr.match(/../g).reverse().join(''), 16); +} - log(`tid = ${tid}, pc = ${littleEndianHexStringToNumber(pc).toString(16)}, x0 = ${littleEndianHexStringToNumber(x0).toString(16)}`) +function extractBrkImmediate(u32) { + return (u32 >> 5) & 0xFFFF; +} - pc = numberToLittleEndianHexString(littleEndianHexStringToNumber(pc) + BigInt(4)) - let x0Num = littleEndianHexStringToNumber(x0) +function hexToAscii(hexStr) { + let str = ''; + for (let i = 0; i < hexStr.length; i += 2) { + const byte = parseInt(hexStr.substr(i, 2), 16); + if (byte === 0) break; + str += String.fromCharCode(byte); + } + return str; +} - // pc+4 - let pcPlus4Response = send_command(`P20=${pc};thread:${tid};`) - log(`pcPlus4Response = ${pcPlus4Response}`) +function runScriptAndCapture(scriptText) { + try { + const value = eval(scriptText); + return { ok: true, value }; + } catch (err) { + return { + ok: false, + name: err && err.name, + message: err && err.message, + stack: err && err.stack + }; + } +} - // apply for jit page of requested size - let requestRXResponse = send_command(`_M${x0Num.toString(16)},rx`) - log(`requestRXResponse = ${requestRXResponse}`) - let jitPageAddress = BigInt(`0x${requestRXResponse}`) +// For making your own script / adding your own breakpoints. you can send this string to BreakSendJITScript and it'll add it for any subsequent breakpoints +// x0, x1, x16, pc and tid are global variables. If you need more registers, parse them like: +// tmpMatch = /02:(?[0-9a-f]{16});/.exec(brkResponse); // x2 +// let x2 = tmpMatch ? tmpMatch.groups['reg'] : null; +// if (!x2) { +// log(`Failed to extract registers: x2=${x2}`); +// return; +// } +// x2 = littleEndianHexStringToNumber(x2); +// +/* +commands[3] = wowBreakPoint; - // TODO fill the page - let a = 0; - try { - for(let i = 0n; i < x0Num; i += 16384n) { - let curPointer = jitPageAddress + i; - let response = send_command(`M${curPointer.toString(16)},1:69`) - if(a % 1000 == 0) { - log(`progress: ${a}/${Math.ceil(Number(x0Num / BigInt(16384)))}, response = ${response}`) - } - ++a; - } - log(`memory write completed. command executed: ${a}`) - }catch(e) { - log(`memory write failed at command ${a}`) +function wowBreakPoint(brekpoint) { + let instructionResponse = send_command(`m${pc.toString(16)},4`); + log(`instruction at pc: ${instructionResponse}`); + let instrU32 = littleEndianHexToU32(instructionResponse); + let brkImmediate = extractBrkImmediate(instrU32); + + if (x0 == 0n && x1 == 0n) { + return; } - // put page address in x0 - let putX0Response = send_command(`P0=${numberToLittleEndianHexString(jitPageAddress)};thread:${tid};`) - log(`putX0Response = ${putX0Response}`) - // detach - let detachResponse = send_command(`D`) - log(`detachResponse = ${detachResponse}`) -} + let jitPageAddress = x0; + let prepareJITPageResponse = prepare_memory_region(jitPageAddress, x1); + log(`prepareJITPageResponse = ${prepareJITPageResponse}`); -attach() \ No newline at end of file + let putX0Response = send_command(`P0=${numberToLittleEndianHexString(jitPageAddress)};thread:${tid};`); + log(`putX0Response = ${putX0Response}`); +} +*/ diff --git a/StikJIT/JSSupport/ScriptEditorView.swift b/StikJIT/JSSupport/ScriptEditorView.swift index 06cd282..f1ca2ac 100644 --- a/StikJIT/JSSupport/ScriptEditorView.swift +++ b/StikJIT/JSSupport/ScriptEditorView.swift @@ -58,7 +58,7 @@ struct ScriptEditorView: View { } } .preferredColorScheme(preferredScheme) - .tint(Color.white) + .tint(colorScheme == .dark ? .white : .black) .toolbar(.hidden, for: .tabBar) }