mirror of
https://github.com/StikDebug/StikJIT.git
synced 2026-10-15 20:10:31 +00:00
296 lines
9.6 KiB
JavaScript
296 lines
9.6 KiB
JavaScript
// 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_INFO;
|
|
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:(?<tid>[0-9a-f]+);/.exec(brkResponse);
|
|
tid = tmpMatch ? tmpMatch.groups['tid'] : null;
|
|
tmpMatch = /20:(?<reg>[0-9a-f]{16});/.exec(brkResponse);
|
|
pc = tmpMatch ? tmpMatch.groups['reg'] : null;
|
|
tmpMatch = /10:(?<reg>[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(?<sig>[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:(?<reg>[0-9a-f]{16});/.exec(brkResponse);
|
|
x0 = tmpMatch ? tmpMatch.groups['reg'] : null;
|
|
tmpMatch = /01:(?<reg>[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:(?<reg>[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}`);
|
|
}
|
|
*/
|