// Universal JIT Script, last updated 2026-29-03 (YYYY-DD-MM) /* // 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 continuesWithSignal = true; 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)})`); if (continuesWithSignal) { 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) { 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 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; } 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 }; } } // 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; 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; } let jitPageAddress = x0; let prepareJITPageResponse = prepare_memory_region(jitPageAddress, x1); log(`prepareJITPageResponse = ${prepareJITPageResponse}`); let putX0Response = send_command(`P0=${numberToLittleEndianHexString(jitPageAddress)};thread:${tid};`); log(`putX0Response = ${putX0Response}`); } */