pokem.P1 (7486B)
1 ## pokem.M1 — raw-byte file patcher, P1. 2 ## 3 ## Runtime shape: pokem file offset hex_bytes 4 ## 5 ## Opens `file` with O_RDWR, advances the file position by `offset` 6 ## bytes via discard reads, then writes the ASCII-hex-decoded payload 7 ## bytes in place. Primary use case is patching ELF header fields 8 ## after assembly — e.g. bumping p_memsz to reserve a BSS region 9 ## without emitting literal zero bytes in the source. 10 ## 11 ## Arguments: 12 ## file path to the file to patch in place. 13 ## offset byte offset, decimal ASCII, unsigned. 14 ## hex_bytes 2*N ASCII hex nibbles (upper or lower case). Each pair 15 ## becomes one raw byte. Empty string is a no-op. 16 ## 17 ## Bootstrap-tool posture: no input validation and no syscall error 18 ## checking. Callers are expected to pass well-formed inputs, and the 19 ## tool is invoked by build scripts, not end users. Bad input produces 20 ## either a bogus patch, a crash, or a silently extended file; we trade 21 ## defensive code for auditability. 22 ## 23 ## Pipeline: 24 ## p1_main stash argv; decode offset (parse_u64) and 25 ## payload (parse_hex_bytes into patch_buf); 26 ## openat O_RDWR; advance via read into scratch_buf 27 ## until offset bytes consumed; write patch_buf; 28 ## close; return 0 (stub sys_exits with it). 29 ## parse_u64 leaf. Decimal ASCII -> u64 in a0. 30 ## parse_hex_bytes leaf. ASCII hex -> patch_buf; returns byte count 31 ## in a0. Uses a branchless nibble decode that is 32 ## correct for [0-9A-Fa-f] and garbage otherwise. 33 ## 34 ## P1 ABI: a0..a3 arg/return, t0..t2 caller-saved temps, s0..s3 35 ## callee-saved (unused here). Entry is the portable p1_main 36 ## (a0=argc, a1=argv). The backend-owned :_start stub captures argc/argv 37 ## from the native entry state and sys_exits p1_main's return value. 38 39 ## --- Constants & sizing ------------------------------------------------------ 40 41 ## patch_buf cap: 256 bytes. ELF-header patch fields are 4 or 8 bytes; 42 ## this leaves generous slack for longer fixups. 43 DEFINE POKEM_PATCH_CAP 0001000000000000 44 45 ## scratch_buf cap: 256 bytes. Used to absorb offset bytes before the 46 ## write. Each advance iteration reads up to this many to amortize 47 ## syscall cost on large offsets. 48 DEFINE POKEM_SCRATCH_CAP 0001000000000000 49 50 ## openat flags. O_RDWR = 2 so a single fd handles both the position- 51 ## advance reads and the in-place write. 52 DEFINE O_RDWR 0200000000000000 53 DEFINE AT_FDCWD 9CFFFFFFFFFFFFFF 54 55 DEFINE ZERO32 '0000000000000000000000000000000000000000000000000000000000000000' 56 DEFINE ZERO8 '0000000000000000' 57 58 ## --- Runtime shell: argv, open, advance, write, exit ------------------------- 59 60 :p1_main 61 enter_0 62 # a0 = argc, a1 = argv (pointer to argv[0]). Stash argv[1..3] into 63 # memory before anything clobbers a1. 64 # file_path = argv[1] 65 ld_t0,a1,8 66 la_a2 &file_path 67 st_t0,a2,0 68 # offset_str = argv[2] 69 ld_t0,a1,16 70 la_a2 &offset_str 71 st_t0,a2,0 72 # hex_str = argv[3] 73 ld_t0,a1,24 74 la_a2 &hex_str 75 st_t0,a2,0 76 77 # offset = parse_u64(offset_str) 78 la_a0 &offset_str 79 ld_a0,a0,0 80 la_br &parse_u64 81 call 82 la_a1 &offset_val 83 st_a0,a1,0 84 85 # patch_len = parse_hex_bytes(hex_str) — bytes land in patch_buf 86 la_a0 &hex_str 87 ld_a0,a0,0 88 la_br &parse_hex_bytes 89 call 90 la_a1 &patch_len 91 st_a0,a1,0 92 93 # fd = openat(AT_FDCWD, file_path, O_RDWR, 0) 94 la_a0 &file_path 95 ld_a2,a0,0 96 li_a0 sys_openat 97 li_a1 AT_FDCWD 98 li_a3 O_RDWR 99 li_t0 %0 %0 100 syscall 101 la_a1 &file_fd 102 st_a0,a1,0 103 104 # Advance the file position by offset bytes via discard reads into 105 # scratch_buf. Each iteration reads min(remaining, SCRATCH_CAP). 106 la_a0 &offset_val 107 ld_t0,a0,0 # t0 = remaining 108 109 :advance_loop 110 la_br &advance_done 111 beqz_t0 112 113 # chunk = min(remaining, SCRATCH_CAP) -> a3 114 mov_a3,t0 # default: chunk = remaining 115 li_t1 POKEM_SCRATCH_CAP 116 la_br &advance_do_read 117 bltu_t0,t1 # if remaining < cap, keep a3 = remaining 118 mov_a3,t1 # else chunk = cap 119 120 :advance_do_read 121 la_a0 &file_fd 122 ld_a1,a0,0 123 la_a2 &scratch_buf 124 li_a0 sys_read 125 syscall 126 127 # remaining -= n (short read with n == 0 loops forever; callers 128 # must pass offset <= file_size) 129 sub_t0,t0,a0 130 la_br &advance_loop 131 b 132 133 :advance_done 134 # Write patch_buf (patch_len bytes) at the current position. 135 la_a0 &patch_len 136 ld_t0,a0,0 # t0 = patch_len (total) 137 li_t1 %0 %0 # t1 = written 138 139 :write_loop 140 la_br &write_done 141 beq_t1,t0 142 143 # n = write(fd, patch_buf + written, patch_len - written) 144 la_a0 &file_fd 145 ld_a1,a0,0 146 la_a2 &patch_buf 147 add_a2,a2,t1 148 sub_a3,t0,t1 149 li_a0 sys_write 150 syscall 151 152 # written += n 153 add_t1,t1,a0 154 la_br &write_loop 155 b 156 157 :write_done 158 # close(fd); return 0 (backend :_start stub sys_exits with a0) 159 la_a0 &file_fd 160 ld_a1,a0,0 161 li_a0 sys_close 162 syscall 163 164 li_a0 %0 %0 165 eret 166 167 ## --- parse_u64(a0=str) -> a0=value -------------------------------------------- 168 ## Leaf. Decimal ASCII -> u64. Empty string returns 0. Non-digit bytes 169 ## produce garbage — caller's responsibility to pass a digit-only, 170 ## NUL-terminated string. 171 172 :parse_u64 173 mov_t0,a0 # cursor 174 li_t1 %0 %0 # result = 0 175 li_a3 %10 %0 # constant multiplier 176 177 :parse_u64_loop 178 lb_a1,t0,0 # c 179 la_br &parse_u64_done 180 beqz_a1 181 182 # result = result * 10 + (c - 48) 183 mul_t1,t1,a3 184 addi_a1,a1,neg48 185 add_t1,t1,a1 186 187 addi_t0,t0,1 188 la_br &parse_u64_loop 189 b 190 191 :parse_u64_done 192 mov_a0,t1 193 ret 194 195 ## --- parse_hex_bytes(a0=str) -> a0=byte_count --------------------------------- 196 ## Leaf. Walks the ASCII hex string two chars at a time into patch_buf. 197 ## Empty input returns 0. Odd length or non-hex characters produce 198 ## garbage bytes; no validation. 199 ## 200 ## Branchless nibble decode: for any c in [0-9A-Fa-f], 201 ## nibble = (c & 15) + 9 * (c >> 6) 202 ## Digits (48..57) have bit 6 clear, so `9 * (c>>6)` is 0 and 203 ## `c & 15` is the value. Letters (65..70 / 97..102) have bit 6 set, 204 ## so we add 9 to the low nibble (1..6) to land on 10..15. 205 206 :parse_hex_bytes 207 mov_t0,a0 # cursor 208 la_t1 &patch_buf # dst base 209 li_t2 %0 %0 # count 210 211 :phb_loop 212 lb_a0,t0,0 # c0 = *cursor 213 la_br &phb_done 214 beqz_a0 215 216 lb_a1,t0,1 # c1 = *(cursor+1) 217 li_a3 %9 %0 218 219 # hi = (c0 & 15) + 9 * (c0 >> 6) 220 andi_a2,a0,15 221 shri_a0,a0,6 222 mul_a0,a0,a3 223 add_a0,a2,a0 224 225 # lo = (c1 & 15) + 9 * (c1 >> 6) 226 andi_a2,a1,15 227 shri_a1,a1,6 228 mul_a1,a1,a3 229 add_a1,a2,a1 230 231 # byte = (hi << 4) | lo; patch_buf[count] = byte 232 shli_a0,a0,4 233 or_a0,a0,a1 234 add_a2,t1,t2 235 sb_a0,a2,0 236 237 addi_t2,t2,1 # count += 1 238 addi_t0,t0,2 # cursor += 2 239 la_br &phb_loop 240 b 241 242 :phb_done 243 mov_a0,t2 244 ret 245 246 ## --- BSS --------------------------------------------------------------------- 247 248 :file_path ZERO8 249 :offset_str ZERO8 250 :hex_str ZERO8 251 :offset_val ZERO8 252 :patch_len ZERO8 253 :file_fd ZERO8 254 255 ## scratch_buf (POKEM_SCRATCH_CAP bytes) — discard-read target. 256 :scratch_buf 257 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 258 259 ## patch_buf (POKEM_PATCH_CAP bytes) — decoded payload. 260 :patch_buf 261 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 262 263 :ELF_end