Master the fundamental concepts of instruction set architecture through this focused micro-challenge.
You have read the whole brief, and the concepts above stay free on every task. Writing and running the code needs a plan.
Three hints are available for this task, revealed one at a time inside the code workspace so you can struggle productively before seeing them.
Every task includes starter code, theory, and hidden tests so you can implement and verify locally in the browser.
How it worksA disassembler reverses assembly: bytes in, mnemonics out. A minimal x86 decoder reads one opcode byte, optionally consumes ModR/M and immediates, then prints the instruction.
cLoading…
Start with a table for common one-byte opcodes:
0x90: NOP0xC3: RET0x01 /r: ADD r/m, rFor example, the byte stream 90 90 C3 disassembles to two NOPs followed by RET.
Extend ModR/M parsing to distinguish ADD EAX, EBX from ADD [RBX], EAX.
For this exercise, you will write a loop that advances ip correctly for each instruction length. This task asks you to never lose sync with the byte stream, because one wrong instruction boundary corrupts every following decode in a real debugger.
Keep the relevant datasheet, ISA manual, or architecture textbook chapter open while you implement. When your output disagrees with the reference trace on the same program, the bug is usually a mis-decoded opcode, a stale register read, or a flag bit left unchanged after arithmetic.
For this exercise, you will use those habits while implementing the requirement in the starter code. Microarchitectural product names change across CPU generations, but the control ideas (fetch, bypass, cache lines, vector lanes) stay stable enough to debug from first principles.
Write a mini x86-64 disassembler. It reads a stream of machine-code bytes and prints one assembly line per instruction. The hard part of x86 is that instructions have different lengths. The decoder has to work out each instruction's length from its prefix, opcode and ModR/M byte, or every instruction after it comes out as garbage.
Hex bytes separated by whitespace (any number per line). The first byte is at address 0.
Registers are numbered 0–7: eax ecx edx ebx esp ebp esi edi (32-bit) / rax … rdi (64-bit). An optional REX.W prefix 48 makes the operation 64-bit.
| Opcode | Instruction | Length after the opcode |
|---|---|---|
90, c3 | nop, ret | 0 |
50+r / 58+r | push r64 / pop r64 | 0 |
01 09 21 29 31 39 89 | add or and sub xor cmp mov r/m, reg | ModR/M (+ disp) |
03 2b 8b | add sub mov reg, r/m | ModR/M (+ disp) |
83 / 81 | ALU op chosen by ModR/M.reg (0 add, 1 or, 2 adc, 3 sbb, 4 and, 5 sub, 6 xor, 7 cmp) r/m, imm | ModR/M (+ disp) + imm8 / imm32 |
b8+r | mov r32, imm32 | 4 |
eb / 74 / 75 | jmp / je / jne rel8 | 1 |
e8 | call rel32 | 4 |
ModR/M = mod(2) reg(3) rm(3). mod=11: rm is a register. mod=00: [rm]. mod=01: [rm+disp8]. mod=10: [rm+disp32]. Memory operands always use 64-bit register names and print a signed displacement: [rbp-8], [rbx+16], [rax]. (The SIB and RIP-relative forms, rm=100 or mod=00 rm=101, do not occur in the tests.) Immediates and displacements are little-endian and signed. Jump and call targets are address of next instruction + rel.
cLoading…
AAAA is the 4-digit hex address. The instruction's bytes are lower-case hex, followed by two spaces and the text.", ". Immediates are signed decimal, and targets are 0xAAAA.83/81 with a memory operand, write DWORD PTR/QWORD PTR before the brackets, e.g. add DWORD PTR [rbx+8], 1.(bad) and decoding continues with the next byte. If the stream ends inside an instruction, print the remaining bytes with the text (truncated).Finally, print N instructions, M bytes. (bad) and (truncated) count as instructions.
Input:
cLoading…
Output:
cLoading…
ip += len.Hidden tests cover 83/81 with register and memory operands, disp32, backward and forward jumps, call, an unknown opcode in the middle of the stream, and a truncated final instruction.