You are atop Flood Control Dam #3, which was once quite a tourist attraction. There are exits to the northeast and west, and a scramble down. The sluice gates on the dam are closed. Behind the dam is a wide reservoir. Water is pouring over the top of the abandoned dam. There is a control panel here, on which a large metal bolt is mounted. Above the bolt is a small green plastic bubble.
A guidebook entitled “Flood Control Dam #3” is on the ground.
There is a matchbook whose cover says “Visit beautiful FCD#3” here.
> take the matches
> read the guidebook
“Flood Control Dam #3 was constructed in 783 GUE with a grant of 37 million zorkmids from Lord Dimwit Flathead the Excessive. This impressive structure is composed of 370,000 cubic feet of concrete, is 256 feet tall and 193 feet wide.
The construction of FCD#3 took 112 days from ground breaking to dedication. It required a work force of 384 slaves, 34 slave drivers, and 12 engineers, 2345 bureaucrats, and nearly one million dead trees.
As you start your tour, notice the more interesting features of FCD#3. On your right…
> drop the guidebook
> go northeast
I knew that string would turn up again sooner or later.
Code for this episode is at https://github.com/ericlippert/flathead/tree/blog9
All right, we’ve decoded an instruction. Now that we have it, let’s print it.
There have been several different Z-machine disassemblers written over the years. I’m not going to attempt to exactly match the output of any of them, but I’ll stick to some of the conventions of the Inform assembly language, as that is mentioned in the Z-machine specification.
We print out an instruction in the same order that we disassembled it in: opcode, operands, store, branch, text.
The instruction names I have drawn from the Z-machine specification:
let opcode_name opcode ver = match opcode with | OP2_1 -> "je" | OP2_2 -> "jl" | OP2_3 -> "jg" | OP2_4 -> "dec_chk" | OP2_5 -> "inc_chk" ... and so on ...
Recall that variables are numbered 0, for the stack, 1 through 15 for locals, and 16 through 255 for globals. The Inform convention is to render stack as “sp”, locals as “local0” through “local14”, and globals as “g0” through “g239”. I am not super thrilled with these conventions but I can live with them.
let display_variable variable = match variable with | Stack -> "sp" | Local_variable Local local -> Printf.sprintf "local%d" (local - 1) | Global_variable Global global -> Printf.sprintf "g%02x" (global - 16)
We have a list of operands. I already have a method that takes a “to string” function and a list, and accumulates the result of converting every element in the list to a string. So let’s use it.
let display_operands () = let to_string operand = match operand with | Large large -> Printf.sprintf "%04x " large | Small small -> Printf.sprintf "%02x " small | Variable variable -> (display_variable variable) ^ " " in accumulate_strings to_string instr.operands
Note that we have a zero-argument function here, notated by making the method take the “unit” value: (). Remember, all functions are required take an argument. You might think of this as the tuple that has no elements. We don’t need real arguments because these functions will be nested inside a function that already has set up all the necessary variables.
This is a pretty naive approach; there are several instructions where we would like to have a more nuanced display. Calls, for example, have as their first operand a packed address of a routine; it would be nice to display that here as an unpacked address. Non-conditional jumps have as their operand a relative address to jump to; it would be nice to display that as an absolute address. And so on. But for the purposes of this blog, I’m just going to print these out naively.
A store we display as an arrow pointing to the storage.
let display_store () = match instr.store with | None -> "" | Some variable -> "->" ^ (display_variable variable)
Is it wrong to have a perverse enjoyment in making an arrow point to a quoted arrow? If so, then I don’t want to be right.
Branches are notated with a leading question mark. If the sense is “branch on false” then the question mark is followed by a twiddle, and then either true or false for “return true” and “return false”, or the address of the jump. Recall that we already decoded these into absolute addresses.
let display_branch () = match instr.branch with | None -> "" | Some (true, Return_false) -> "?false" | Some (false, Return_false) -> "?~false" | Some (true, Return_true) -> "?true" | Some (false, Return_true) -> "?~true" | Some (true, Branch_address Instruction address) -> Printf.sprintf "?%04x" address | Some (false, Branch_address Instruction address) -> Printf.sprintf "?~%04x" address
Maybe a little more duplicated code here than is strictly necessary, but again I’m going to not stress about it.
The text instructions are trivial:
let display_text () = match instr.text with | None -> "" | Some str -> str
Put it all together:
let display instr ver = [...helpers listed above...] let (Instruction start_addr) = instr.address in let name = opcode_name instr.opcode ver in let operands = display_operands() in let store = display_store() in let branch = display_branch() in let text = display_text() in Printf.sprintf "%04x: %s %s%s %s %s\n" start_addr name operands store branch text
And now at long last we can display an instruction. If only I knew where one was!
I happen to know that there is an instruction at 0x37d9; I’ll say how I know that next time.
let () = let story = Story.load "minizork.z3" in let instruction = Instruction.decode story (Instruction 0x37d9) in let text = Instruction.display instruction (Story.version story) in Printf.printf "%s\n" text
And sure enough we get
37d9: call 1d9b 3e88 ffff ->sp
The instruction here is a call, it takes three long constant operands, and it stores its result by pushing it onto the evaluation stack. There is no branch, and no text.
Next time on FAIC: how can we disassemble more instructions?