mirror of
https://github.com/StikDebug/StikDebug.git
synced 2026-10-16 04:20:30 +00:00
Fix Amethyst support (#311)
* Add download section to README Added download section with links and images for AltSource and StikDebug. * Update download link in README.md * Update FeatureFlags.swift * Update ScriptEditorView.swift * Implement Universal Script for Amethyst I don't know if melo has added support, but if so we could change the name and reassign * Disable Mini Tools feature flag
This commit is contained in:
parent
77b8810e6b
commit
cb5ff5c9f2
2 changed files with 268 additions and 64 deletions
|
|
@ -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:(?<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)})`);
|
||||
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) {
|
||||
// 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:(?<tid>[0-9a-f]+);/.exec(brkResponse).groups['tid']
|
||||
let pc = /20:(?<reg>[0-9a-f]{16});/.exec(brkResponse).groups['reg']
|
||||
let x0 = /00:(?<reg>[0-9a-f]{16});/.exec(brkResponse).groups['reg']
|
||||
|
||||
log(`tid = ${tid}, pc = ${littleEndianHexStringToNumber(pc).toString(16)}, x0 = ${littleEndianHexStringToNumber(x0).toString(16)}`)
|
||||
|
||||
pc = numberToLittleEndianHexString(littleEndianHexStringToNumber(pc) + BigInt(4))
|
||||
let x0Num = littleEndianHexStringToNumber(x0)
|
||||
|
||||
// pc+4
|
||||
let pcPlus4Response = send_command(`P20=${pc};thread:${tid};`)
|
||||
log(`pcPlus4Response = ${pcPlus4Response}`)
|
||||
|
||||
// apply for jit page of requested size
|
||||
let requestRXResponse = send_command(`_M${x0Num.toString(16)},rx`)
|
||||
log(`requestRXResponse = ${requestRXResponse}`)
|
||||
let jitPageAddress = BigInt(`0x${requestRXResponse}`)
|
||||
|
||||
// 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}`)
|
||||
}
|
||||
|
||||
// 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}`)
|
||||
function littleEndianHexToU32(hexStr) {
|
||||
return parseInt(hexStr.match(/../g).reverse().join(''), 16);
|
||||
}
|
||||
|
||||
attach()
|
||||
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}`);
|
||||
}
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ struct ScriptEditorView: View {
|
|||
}
|
||||
}
|
||||
.preferredColorScheme(preferredScheme)
|
||||
.tint(Color.white)
|
||||
.tint(colorScheme == .dark ? .white : .black)
|
||||
.toolbar(.hidden, for: .tabBar)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue