Guide · RISC-V

From machine code to assembly

We turn an eight-word dump into a readable routine: decode, turn offsets into labels, recognize pseudo-instructions and work out what it does.

Reviewed on against the site’s tools.

When you debug a compiled program, inspect a memory dump or mark an exam, you often only have hexadecimal words. Going from there to readable assembly takes more than decoding each word on its own: you have to rebuild the labels and recognize the pseudo-instructions. We do it with an eight-instruction routine written for this guide; every word was generated and checked with the site’s decoder.

Step 1: decode every word

We start from eight consecutive words beginning at address 0x00. Applying the method from the decoding guide to each of them gives:

AddressWordFormatDecoded
0x0000000293Iaddi t0, zero, 0
0x0400050A63Bbeq a0, zero, 20
0x0800157313Iandi t1, a0, 1
0x0C006282B3Radd t0, t0, t1
0x1000155513Isrli a0, a0, 1
0x14FF1FF06FJjal zero, -16
0x1800028513Iaddi a0, t0, 0
0x1C00008067Ijalr zero, 0(ra)

This is already correct assembly, but it is hard to read: branches appear as numeric offsets and some instructions use “odd” registers, such as adding zero or jumping while saving the return address into zero.

Step 2: turn offsets into labels

The offsets of beq and jal are relative to the address of the instruction itself. The beq at 0x04 jumps +20, i.e. to 0x04 + 20 = 0x18. The jal at 0x14 jumps −16, to 0x14 − 16 = 0x04. Those two addresses deserve a name: we call 0x04 loop (the target of the backward jump) and 0x18 done.

Step 3: recognize the pseudo-instructions

Pseudo-instructions are shorthands the assembler turns into real instructions. When reading machine code you go the other way:

  • addi rd, zero, imm is li rd, imm, and addi rd, rs, 0 is mv rd, rs.
  • beq rs, zero, target is beqz rs, target.
  • jal zero, target is j target: a jump that saves no return address.
  • jalr zero, 0(ra) is ret: it goes back to the address saved in ra.
AddressBase instructionReadable form
0x00addi t0, zero, 0li t0, 0
0x04beq a0, zero, 20 → 0x18beqz a0, done
0x08andi t1, a0, 1andi t1, a0, 1
0x0Cadd t0, t0, t1add t0, t0, t1
0x10srli a0, a0, 1srli a0, a0, 1
0x14jal zero, -16 → 0x04j loop
0x18addi a0, t0, 0mv a0, t0
0x1Cjalr zero, 0(ra)ret

Step 4: understand what it does

popcount:            # a0 = input number
  li   t0, 0         # t0 = count of ones
loop:
  beqz a0, done      # no bits left: finish
  andi t1, a0, 1     # t1 = lowest bit of a0
  add  t0, t0, t1    # add that bit to the count
  srli a0, a0, 1     # drop the lowest bit
  j    loop
done:
  mv   a0, t0        # return the count in a0
  ret

With names and comments the intent becomes obvious. On each iteration, andi t1, a0, 1 isolates the lowest bit, it is added to the counter and srli shifts a0 right. When a0 reaches 0 there are no ones left. The routine counts how many bits of a0 are 1 (the population count) and returns the result in a0, following the calling convention: argument and result in a0, return address in ra.

Step 5: check it by running it

To run it you need a program that calls it. This one loads 13 (0b1101) into a0, calls the routine with jal and stops:

li   a0, 13
jal  ra, popcount
j    end
popcount:
  li   t0, 0
loop:
  beqz a0, done
  andi t1, a0, 1
  add  t0, t0, t1
  srli a0, a0, 1
  j    loop
done:
  mv   a0, t0
  ret
end:
  nop

In the debugger the program executes 28 instructions and ends with a0 = 3, because 1101 has three ones. Try other values, for example 255 (eight ones) or 0 (the loop body never runs).

Summary of the procedure

  1. Decode every word and note its address.
  2. Compute each branch target (address + offset) and give it a label.
  3. Replace base instructions with their pseudo-instructions where they fit.
  4. Look for structure: loops (backward branches), conditions (forward branches) and calls (jal/ret).
  5. Check your reading by running the code with known values.

Practise what you read

Follow a loop

What is a0 when the loop exits? How many instructions run in total (counting every iteration)?

li   a0, 1
li   t0, 4
loop:
  add  a0, a0, a0
  addi t0, t0, -1
  bnez t0, loop
Open in the tool

Call a function with jal and return with ret

The triple routine takes a number in a0 and returns the result in a0. What are a0 and ra at the end?

li   a0, 6
jal  ra, triple
j    end
triple:
  add  a1, a0, a0
  add  a0, a1, a0
  ret
end:
  nop
Open in the tool

References