1+ // Universal JIT Script, last updated 2025-10-10
2+ /*
3+ // JIT "syscalls"
4+ __attribute__((noinline,optnone,naked))
5+ void JIT26Detach(void) {
6+ asm("mov x16, #0 \n"
7+ "brk #0xf00d \n"
8+ "ret");
9+ }
10+ __attribute__((noinline,optnone,naked))
11+ void* JIT26PrepareRegion(void *addr, size_t len) {
12+ asm("mov x16, #1 \n"
13+ "brk #0xf00d \n"
14+ "ret");
15+ }
116
17+ __attribute__((noinline,optnone,naked))
18+ void BreakSendJITScript(char* script, size_t len) {
19+ asm("mov x16, #2 \n"
20+ "brk #0xf00d \n"
21+ "ret");
22+ }
23+ */
24+ const CMD_DETACH = 0 ;
25+ const CMD_PREPARE_REGION = 1 ;
26+ const CMD_NEW_BREAKPOINTS = 2 ;
27+ const commands = {
28+ [ CMD_DETACH ] : JIT26Detach ,
29+ [ CMD_PREPARE_REGION ] : JIT26PrepareRegion ,
30+ [ CMD_NEW_BREAKPOINTS ] : JIT26NewBreakpoints
31+ } ;
32+ const legacyCommands = {
33+ [ 0x68 ] : JIT26NewBreakpoints ,
34+ [ 0x69 ] : JIT26HandleBrk0x69 ,
35+ [ 0xf00d ] : JIT26HandleBrk0xf00d
36+ } ;
37+
38+ // Log levels
39+ //const LOG_NONE = 0;
40+ const LOG_INFO = 1 ;
41+ const LOG_VERBOSE = 2 ;
42+ let logLevel = LOG_VERBOSE ;
43+ function log_verbose ( msg ) {
44+ if ( logLevel >= LOG_VERBOSE ) {
45+ log ( msg ) ;
46+ }
47+ }
48+
49+ // To avoid having to re-parse these in each function, we save some registers here
50+ let tid , x0 , x1 , x16 , pc ;
51+ let detached = false ;
52+ let pid = get_pid ( ) ;
53+ let attachResponse = send_command ( `vAttach;${ pid . toString ( 16 ) } ` ) ;
54+
55+ log ( `pid = ${ pid } ` ) ;
56+ log ( `attach_response = ${ attachResponse } ` ) ;
57+
58+ let totalBreakpoints = 0 ;
59+ while ( ! detached ) {
60+ totalBreakpoints ++ ;
61+ log ( `Handling signal ${ totalBreakpoints } ` ) ;
62+
63+ let brkResponse = send_command ( `c` ) ;
64+ log_verbose ( `brkResponse = ${ brkResponse } ` ) ;
65+
66+ // extract tid, pc, x16
67+ let tmpMatch = / T [ 0 - 9 a - f ] + t h r e a d : (?< tid > [ 0 - 9 a - f ] + ) ; / . exec ( brkResponse ) ;
68+ tid = tmpMatch ? tmpMatch . groups [ 'tid' ] : null ;
69+ tmpMatch = / 2 0 : (?< reg > [ 0 - 9 a - f ] { 16 } ) ; / . exec ( brkResponse ) ;
70+ pc = tmpMatch ? tmpMatch . groups [ 'reg' ] : null ;
71+ tmpMatch = / 1 0 : (?< reg > [ 0 - 9 a - f ] { 16 } ) ; / . exec ( brkResponse ) ;
72+ x16 = tmpMatch ? tmpMatch . groups [ 'reg' ] : null ;
73+ if ( ! tid || ! pc || ! x16 ) {
74+ log ( `Failed to extract registers: tid=${ tid } , pc=${ pc } , x16=${ x16 } ` ) ;
75+ continue ;
76+ }
77+ pc = littleEndianHexStringToNumber ( pc ) ;
78+ x16 = littleEndianHexStringToNumber ( x16 ) ;
79+
80+ let instructionResponse = send_command ( `m${ pc . toString ( 16 ) } ,4` ) ;
81+ log ( `instruction at pc: ${ instructionResponse } ` ) ;
82+ let instrU32 = littleEndianHexToU32 ( instructionResponse ) ;
83+
84+ // check if this is a brk
85+ if ( ( instrU32 & 0xFFE0001F ) >>> 0 != 0xD4200000 ) {
86+ log ( `Skipping: instruction was not a brk (was 0x${ instrU32 . toString ( 16 ) } )` ) ;
87+ let signum = / ^ T (?< sig > [ a - z 0 - 9 ; ] { 2 } ) / . exec ( brkResponse ) ;
88+ signum = signum ? signum . groups [ 'sig' ] : null ;
89+ if ( ! signum ) {
90+ log ( `Failed to extract signal number: ${ signum } ` ) ;
91+ continue ;
92+ }
93+ log ( `Continuing with signal 0x${ signum } ` ) ;
94+ send_command ( `vCont;S${ signum } :${ tid } ` ) ;
95+ continue ;
96+ }
97+
98+ let brkImmediate = extractBrkImmediate ( instrU32 ) ;
99+ log ( `BRK immediate: 0x${ brkImmediate . toString ( 16 ) } (${ brkImmediate } )` ) ;
100+ if ( legacyCommands [ brkImmediate ] != undefined ) {
101+ // when we find a valid brk immediate command, parse x0 and x1
102+ tmpMatch = / 0 0 : (?< reg > [ 0 - 9 a - f ] { 16 } ) ; / . exec ( brkResponse ) ;
103+ x0 = tmpMatch ? tmpMatch . groups [ 'reg' ] : null ;
104+ tmpMatch = / 0 1 : (?< reg > [ 0 - 9 a - f ] { 16 } ) ; / . exec ( brkResponse ) ;
105+ x1 = tmpMatch ? tmpMatch . groups [ 'reg' ] : null ;
106+ if ( ! x0 || ! x1 ) {
107+ log ( `Failed to extract registers: x0=${ x0 } , x1=${ x1 } ` ) ;
108+ continue ;
109+ }
110+ x0 = littleEndianHexStringToNumber ( x0 ) ;
111+ x1 = littleEndianHexStringToNumber ( x1 ) ;
112+
113+ // jump over brk
114+ let pcPlus4 = numberToLittleEndianHexString ( pc + 4n ) ;
115+ let pcPlus4Response = send_command ( `P20=${ pcPlus4 } ;thread:${ tid } ;` ) ;
116+ log ( `pcPlus4Response = ${ pcPlus4Response } ` ) ;
117+
118+ // dispatch brk-immediate command
119+ const command = legacyCommands [ brkImmediate ] ;
120+ command ( brkResponse ) ;
121+ } else {
122+ 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;` ) ;
123+ continue ;
124+ }
125+ }
126+
127+ function JIT26Detach ( ) {
128+ let detachResponse = send_command ( `D` ) ;
129+ log_verbose ( `detachResponse = ${ detachResponse } ` ) ;
130+ detached = true ;
131+ }
132+
133+ // brk 0x68
134+ function JIT26NewBreakpoints ( brkResponse ) {
135+ let instructionResponse = send_command ( `m${ pc . toString ( 16 ) } ,4` ) ;
136+ log ( `instruction at pc: ${ instructionResponse } ` ) ;
137+ let instrU32 = littleEndianHexToU32 ( instructionResponse ) ;
138+ let brkImmediate = extractBrkImmediate ( instrU32 ) ;
139+
140+ let memResponse = send_command ( `m${ x0 . toString ( 16 ) } ,${ x1 } ` ) ;
141+
142+ let scriptText = hexToAscii ( memResponse ) ;
143+ log_verbose ( `Script text: ${ scriptText } ` ) ;
144+
145+ const res = runScriptAndCapture ( scriptText ) ;
146+ if ( res . ok ) {
147+ log ( 'Script succeeded:' , res . value ) ;
148+ } else {
149+ log ( 'Script failed:' , res . name , res . message ) ;
150+ log ( res . stack ) ;
151+ }
152+ }
153+
154+ // brk 0x69
155+ function JIT26HandleBrk0x69 ( brkResponse ) {
156+ // in the old script we chose 0x69, so now we check here and return error
157+ // if you wish to keep using this, you can set your own handler like `legacyCommands[0x69] = yourHandler;` using BreakSendJITScript
158+ 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.` ) ;
159+ let putX0Response = send_command ( `P0=E0000069;thread:${ tid } ;` ) ;
160+ log ( `putX0Response = ${ putX0Response } ` ) ;
161+ }
162+
163+ // brk 0xf00d
164+ function JIT26HandleBrk0xf00d ( brkResponse ) {
165+ // dispatch command via x16
166+ const command = commands [ x16 ] ;
167+ if ( command === undefined ) {
168+ log ( `Unknown command ${ x16 . toString ( 16 ) } ` ) ;
169+ return ;
170+ }
171+ log ( `Invoking command ${ x16 . toString ( 16 ) } ` ) ;
172+ command ( brkResponse ) ;
173+ }
174+
175+ function JIT26PrepareRegion ( brkResponse ) {
176+ let instructionResponse = send_command ( `m${ pc . toString ( 16 ) } ,4` ) ;
177+ log ( `instruction at pc: ${ instructionResponse } ` ) ;
178+ let instrU32 = littleEndianHexToU32 ( instructionResponse ) ;
179+ let brkImmediate = extractBrkImmediate ( instrU32 ) ;
180+
181+ if ( x0 == 0n && x1 == 0n ) {
182+ return ;
183+ }
184+
185+ let jitPageAddress = x0 ;
186+ if ( x0 == 0n ) {
187+ let requestRXResponse = send_command ( `_M${ x1 . toString ( 16 ) } ,rx` ) ;
188+ log_verbose ( `requestRXResponse = ${ requestRXResponse } ` ) ;
189+
190+ if ( ! requestRXResponse || requestRXResponse . length === 0 ) {
191+ log ( `Failed to allocate RX memory` ) ;
192+ return ;
193+ }
194+
195+ jitPageAddress = BigInt ( `0x${ requestRXResponse } ` ) ;
196+ log ( `Allocated JIT page at address: 0x${ jitPageAddress . toString ( 16 ) } ` ) ;
197+ }
198+
199+ let prepareJITPageResponse = prepare_memory_region ( jitPageAddress , x1 ) ;
200+ log ( `prepareJITPageResponse = ${ prepareJITPageResponse } ` ) ;
201+
202+ let putX0Response = send_command ( `P0=${ numberToLittleEndianHexString ( jitPageAddress ) } ;thread:${ tid } ;` ) ;
203+ log ( `putX0Response = ${ putX0Response } ` ) ;
204+ }
205+
206+ // utilities
2207function littleEndianHexStringToNumber ( hexStr ) {
3- // Convert hex string to byte array
4208 const bytes = [ ] ;
5209 for ( let i = 0 ; i < hexStr . length ; i += 2 ) {
6210 bytes . push ( parseInt ( hexStr . substr ( i , 2 ) , 16 ) ) ;
7211 }
8-
9- // Assemble number from first 5 bytes (little-endian)
10212 let num = 0n ;
11213 for ( let i = 4 ; i >= 0 ; i -- ) {
12214 num = ( num << 8n ) | BigInt ( bytes [ i ] ) ;
13215 }
14-
15- return num ; // BigInt: 0x01016644fc
216+ return num ;
16217}
17218
18219function numberToLittleEndianHexString ( num ) {
19- // Extract 5 bytes in little-endian order
20220 const bytes = [ ] ;
21221 for ( let i = 0 ; i < 5 ; i ++ ) {
22222 bytes . push ( Number ( num & 0xFFn ) ) ;
23223 num >>= 8n ;
24224 }
25-
26- // Pad with 3 zero bytes to make 8 bytes
27225 while ( bytes . length < 8 ) {
28226 bytes . push ( 0 ) ;
29227 }
30-
31- // Convert to hex string
32228 return bytes . map ( b => b . toString ( 16 ) . padStart ( 2 , '0' ) ) . join ( '' ) ;
33229}
34230
35- function attach ( ) {
36- let pid = get_pid ( ) ;
37- log ( `pid = ${ pid } ` )
38-
39- // attach
40- let attachResponse = send_command ( `vAttach;${ pid . toString ( 16 ) } ` )
41- log ( `attach_response = ${ attachResponse } ` )
42-
43- // wait for brk
44- let brkResponse = send_command ( `c` )
45- log ( `brkResponse = ${ brkResponse } ` )
46-
47- let tid = / T [ 0 - 9 a - f ] + t h r e a d : (?< tid > [ 0 - 9 a - f ] + ) ; / . exec ( brkResponse ) . groups [ 'tid' ]
48- let pc = / 2 0 : (?< reg > [ 0 - 9 a - f ] { 16 } ) ; / . exec ( brkResponse ) . groups [ 'reg' ]
49- let x0 = / 0 0 : (?< reg > [ 0 - 9 a - f ] { 16 } ) ; / . exec ( brkResponse ) . groups [ 'reg' ]
231+ function littleEndianHexToU32 ( hexStr ) {
232+ return parseInt ( hexStr . match ( / ../ g) . reverse ( ) . join ( '' ) , 16 ) ;
233+ }
50234
51- log ( `tid = ${ tid } , pc = ${ littleEndianHexStringToNumber ( pc ) . toString ( 16 ) } , x0 = ${ littleEndianHexStringToNumber ( x0 ) . toString ( 16 ) } ` )
235+ function extractBrkImmediate ( u32 ) {
236+ return ( u32 >> 5 ) & 0xFFFF ;
237+ }
52238
53- pc = numberToLittleEndianHexString ( littleEndianHexStringToNumber ( pc ) + BigInt ( 4 ) )
54- let x0Num = littleEndianHexStringToNumber ( x0 )
239+ function hexToAscii ( hexStr ) {
240+ let str = '' ;
241+ for ( let i = 0 ; i < hexStr . length ; i += 2 ) {
242+ const byte = parseInt ( hexStr . substr ( i , 2 ) , 16 ) ;
243+ if ( byte === 0 ) break ;
244+ str += String . fromCharCode ( byte ) ;
245+ }
246+ return str ;
247+ }
55248
56- // pc+4
57- let pcPlus4Response = send_command ( `P20=${ pc } ;thread:${ tid } ;` )
58- log ( `pcPlus4Response = ${ pcPlus4Response } ` )
249+ function runScriptAndCapture ( scriptText ) {
250+ try {
251+ const value = eval ( scriptText ) ;
252+ return { ok : true , value } ;
253+ } catch ( err ) {
254+ return {
255+ ok : false ,
256+ name : err && err . name ,
257+ message : err && err . message ,
258+ stack : err && err . stack
259+ } ;
260+ }
261+ }
59262
60- // apply for jit page of requested size
61- let requestRXResponse = send_command ( `_M${ x0Num . toString ( 16 ) } ,rx` )
62- log ( `requestRXResponse = ${ requestRXResponse } ` )
63- let jitPageAddress = BigInt ( `0x${ requestRXResponse } ` )
263+ // 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
264+ // x0, x1, x16, pc and tid are global variables. If you need more registers, parse them like:
265+ // tmpMatch = /02:(?<reg>[0-9a-f]{16});/.exec(brkResponse); // x2
266+ // let x2 = tmpMatch ? tmpMatch.groups['reg'] : null;
267+ // if (!x2) {
268+ // log(`Failed to extract registers: x2=${x2}`);
269+ // return;
270+ // }
271+ // x2 = littleEndianHexStringToNumber(x2);
272+ //
273+ /*
274+ commands[3] = wowBreakPoint;
64275
65- // TODO fill the page
66- let a = 0 ;
67- try {
68- for ( let i = 0n ; i < x0Num ; i += 16384n ) {
69- let curPointer = jitPageAddress + i ;
70- let response = send_command ( `M${ curPointer . toString ( 16 ) } ,1:69` )
71- if ( a % 1000 == 0 ) {
72- log ( `progress: ${ a } /${ Math . ceil ( Number ( x0Num / BigInt ( 16384 ) ) ) } , response = ${ response } ` )
73- }
74- ++ a ;
75- }
76- log ( `memory write completed. command executed: ${ a } ` )
77- } catch ( e ) {
78- log ( `memory write failed at command ${ a } ` )
276+ function wowBreakPoint(brekpoint) {
277+ let instructionResponse = send_command(`m${pc.toString(16)},4`);
278+ log(`instruction at pc: ${instructionResponse}`);
279+ let instrU32 = littleEndianHexToU32(instructionResponse);
280+ let brkImmediate = extractBrkImmediate(instrU32);
281+
282+ if (x0 == 0n && x1 == 0n) {
283+ return;
79284 }
80285
81- // put page address in x0
82- let putX0Response = send_command ( `P0=${ numberToLittleEndianHexString ( jitPageAddress ) } ;thread:${ tid } ;` )
83- log ( `putX0Response = ${ putX0Response } ` )
84- // detach
85- let detachResponse = send_command ( `D` )
86- log ( `detachResponse = ${ detachResponse } ` )
87- }
286+ let jitPageAddress = x0;
287+ let prepareJITPageResponse = prepare_memory_region(jitPageAddress, x1);
288+ log(`prepareJITPageResponse = ${prepareJITPageResponse}`);
88289
89- attach ( )
290+ let putX0Response = send_command(`P0=${numberToLittleEndianHexString(jitPageAddress)};thread:${tid};`);
291+ log(`putX0Response = ${putX0Response}`);
292+ }
293+ */
0 commit comments