boot2

Playing with the boostrap
git clone https://git.ryansepassi.com/git/boot2.git
Log | Files | Refs | README

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