feat(cli): disassemble emitted native instruction blocks
5 files changed, +51 −2
- cli/driver.ML modified +2 −1
- src/Encoding.thy modified +10 −0
- src/Export.thy modified +1 −1
- src/Frontend.thy modified +29 −0
- tests/cli.py modified +9 −0
cli/driver.ML modified
| @@ -36,8 +36,9 @@ fun main () = | ||
| 36 | 36 | | ["eval", source, x, y, t] => output (Remy.reference (read source) (chars x) (chars y) (chars t)) |
| 37 | 37 | | ["limits", depth, nodes, stack, source, destination] => |
| 38 | 38 | emit destination (Remy.configured (chars depth) (chars nodes) (chars stack) (read source)) |
| 39 | + | ["disassemble", binary] => output (Remy.disassemble (read binary)) | |
| 39 | 40 | | [source, destination] => emit destination (Remy.artifact (read source)) |
| 40 | - | _ => raise Fail "usage: remy limits depth nodes stack source binary | remy source binary | remy vectors source data | remy eval source x y t") | |
| 41 | + | _ => raise Fail "usage: remy disassemble binary | remy limits depth nodes stack source binary | remy source binary | remy vectors source data | remy eval source x y t") | |
| 41 | 42 | handle exn => |
| 42 | 43 | (TextIO.output (TextIO.stdErr, "remy: " ^ General.exnMessage exn ^ "\n"); |
| 43 | 44 | OS.Process.exit OS.Process.failure); |
src/Encoding.thy modified
| @@ -233,6 +233,16 @@ theorem recover_encode: | ||
| 233 | 233 | recover (length ops) (concat (map encode ops) @ tail) = Some (ops,tail)" |
| 234 | 234 | by (induction ops) (simp_all add: decode_encode) |
| 235 | 235 | |
| 236 | +function blocks :: "nat list \<Rightarrow> (nat list \<times> opcode) list option" where | |
| 237 | + "blocks bs = (if bs = [195] then Some [] else | |
| 238 | + Option.bind (decode bs) (\<lambda>(op,rest). | |
| 239 | + map_option (\<lambda>code. (take (length bs - length rest) bs,op) # code) (blocks rest)))" | |
| 240 | + by pat_completeness auto | |
| 241 | +termination | |
| 242 | + by (relation "measure length") (auto dest: decode_progress) | |
| 243 | + | |
| 244 | +declare blocks.simps [simp del] | |
| 245 | + | |
| 236 | 246 | definition executable :: "expression \<Rightarrow> nat list" where |
| 237 | 247 | "executable e = concat (map encode (program e)) @ [195]" |
| 238 | 248 | |
src/Export.thy modified
| @@ -6,6 +6,6 @@ definition ascii :: "integer \<Rightarrow> char" where "ascii = char_of" | ||
| 6 | 6 | |
| 7 | 7 | definition ordinal :: "char \<Rightarrow> integer" where "ordinal = of_char" |
| 8 | 8 | |
| 9 | -export_code configured artifact vectors reference ascii ordinal in SML module_name Remy file_prefix remy | |
| 9 | +export_code disassemble configured artifact vectors reference ascii ordinal in SML module_name Remy file_prefix remy | |
| 10 | 10 | |
| 11 | 11 | end |
src/Frontend.thy modified
| @@ -179,6 +179,35 @@ termination by (relation "measure id") (auto simp: div_less_dividend) | ||
| 179 | 179 | |
| 180 | 180 | definition newline :: string where "newline = [CHR 0x0A]" |
| 181 | 181 | |
| 182 | +fun regname :: "register \<Rightarrow> string" where | |
| 183 | + "regname Rax = ''rax''" | "regname Rcx = ''rcx''" | "regname Rbx = ''rbx''" | |
| 184 | +| "regname Rdx = ''rdx''" | "regname R8 = ''r8''" | "regname R9 = ''r9''" | |
| 185 | + | |
| 186 | +fun opname :: "operator \<Rightarrow> string" where | |
| 187 | + "opname Add = ''add''" | "opname Sub = ''sub''" | "opname Mul = ''mul''" | |
| 188 | +| "opname And = ''and''" | "opname Or = ''or''" | "opname Xor = ''xor''" | |
| 189 | +| "opname Shl = ''shl''" | "opname Shr = ''shr''" | "opname Less = ''less''" | |
| 190 | + | |
| 191 | +fun mnemonic :: "opcode \<Rightarrow> string" where | |
| 192 | + "mnemonic (Immediate v) = ''immediate rax '' @ decimal (unat v)" | |
| 193 | +| "mnemonic (Push r) = ''push '' @ regname r" | |
| 194 | +| "mnemonic (Pop r) = ''pop '' @ regname r" | |
| 195 | +| "mnemonic (Move dst src) = ''mov '' @ regname dst @ '' '' @ regname src" | |
| 196 | +| "mnemonic (Peek i) = ''peek rax stack['' @ decimal i @ '']''" | |
| 197 | +| "mnemonic (Arithmetic op) = ''arithmetic '' @ opname op" | |
| 198 | +| "mnemonic Conditional = ''conditional rax rcx rbx''" | |
| 199 | + | |
| 200 | +fun listing :: "nat \<Rightarrow> (nat list \<times> opcode) list \<Rightarrow> string" where | |
| 201 | + "listing offset [] = decimal offset @ '' ret'' @ newline" | |
| 202 | +| "listing offset ((bytes,op) # code) = decimal offset @ '' '' @ mnemonic op @ | |
| 203 | + '' ['' @ concat (map (\<lambda>b. decimal b @ '' '') bytes) @ '']'' @ newline @ | |
| 204 | + listing (offset + length bytes) code" | |
| 205 | + | |
| 206 | +definition disassemble :: "string \<Rightarrow> bool \<times> string" where | |
| 207 | + "disassemble bytes = (case blocks (map nat_of_char bytes) of | |
| 208 | + None \<Rightarrow> (False,''invalid native block stream'' @ newline) | |
| 209 | + | Some code \<Rightarrow> (True,listing 0 code))" | |
| 210 | + | |
| 182 | 211 | datatype problem = Unexpected nat | Exhausted nat |
| 183 | 212 | |
| 184 | 213 | datatype 'a outcome = Failed problem | Parsed 'a |
tests/cli.py modified
| @@ -46,6 +46,15 @@ def main(): | ||
| 46 | 46 | binary.write_bytes(b'preserve') |
| 47 | 47 | assert message in invoke('limits',depth,nodes,stack,source,binary,ok=False) |
| 48 | 48 | assert binary.read_bytes() == b'preserve' |
| 49 | + source.write_text('(+ x y)') | |
| 50 | + invoke(source,binary) | |
| 51 | + listing = invoke('disassemble',binary) | |
| 52 | + assert 'mov rax r9' in listing and 'arithmetic add' in listing and 'ret' in listing | |
| 53 | + for malformed in [b'',bytes([195,0]),bytes([72,199,192,0]),bytes([80])]: | |
| 54 | + binary.write_bytes(malformed) | |
| 55 | + assert 'invalid native' in invoke('disassemble',binary,ok=False) | |
| 56 | + binary.write_bytes(bytes([72,184,42,0,0,0,0,0,0,0,195])) | |
| 57 | + assert '10 ret' in invoke('disassemble',binary) | |
| 49 | 58 | source.write_text('é') |
| 50 | 59 | assert 'ASCII' in invoke(source,binary,ok=False) |
| 51 | 60 | for text in ['0x','0x10000000000000000','0xfg','0xffextra','-0x1']: |