Skip to content

Instantly share code, notes, and snippets.

@akkartik
Last active July 25, 2018 04:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save akkartik/8b0952b2ed5b5fb14cea54211e05c537 to your computer and use it in GitHub Desktop.
Save akkartik/8b0952b2ed5b5fb14cea54211e05c537 to your computer and use it in GitHub Desktop.
SubX online help, 2018-07-20

$ subx

Welcome to SubX, a better way to program in machine code.
SubX uses a subset of the x86 instruction set. SubX programs will run without modification on Linux computers.
It provides a better experience and better error messages than programming directly in machine code, but you have to stick to the instructions it supports.

== Ways to invoke subx
- Run tests:
    subx test
- See this message:
    subx --help
- Convert a textual SubX program into a standard ELF binary that you can run on your computer:
    subx translate <input 'source' file> <output ELF binary>
- Run a SubX binary using SubX itself (for better error messages):
    subx run <ELF binary>

To start learning how to write SubX programs, run:
  subx help

$ subx help

help on what?
Available top-level topics:
  usage
  registers
  syntax
  opcodes

$ subx help registers

SubX currently supports eight 32-bit integer registers: R0 to R7.
R4 contains the top of the stack.
There's also a register for the address of the currently executing instruction. It is modified by jumps.
Various instructions modify one or more of three 1-bit 'flag' registers, as a side-effect:
- the sign flag (SF): usually set if an arithmetic result is negative, or reset if not.
- the zero flag (ZF): usually set if a result is zero, or reset if not.
- the overflow flag (OF): usually set if an arithmetic result overflows.
The flag bits are read by conditional jumps.
We don't support non-integer (floating-point) registers yet.

$ subx help syntax

SubX programs consist of segments, each segment in turn consisting of lines.
Line-endings are significant; each line should contain a single instruction, macro or directive.
Comments start with the '#' character. It should be at the start of a word (start of line, or following a space).
Each segment starts with a header line: a '==' delimiter followed by the starting address for the segment.
The starting address for a segment has some finicky requirements. But just start with a round number, and `subx` will try to guide you to a valid configuration.
A good rule of thumb is to try to start the first segment at the default address of 0x08048000, and to start each subsequent segment at least 0x1000 (most common page size) bytes after the last.
If a segment occupies than 0x1000 bytes you'll need to push subsequent segments further down.
Currently only the first segment contains executable code (because it gets annoying to have to change addresses in later segments every time an earlier one changes length; one of those finicky requirements).
Check out some examples in this directory (ex*.subx)
Programming in machine code can be annoying, but let's see if we can make it nice enough to be able to write a compiler in it.

$ subx help opcodes

Opcodes currently supported by SubX:
  01: add r32 to rm32
  03: add rm32 to r32
  05: add imm32 to R0 (EAX)
  09: rm32 = bitwise OR of r32 with rm32
  0b: r32 = bitwise OR of r32 with rm32
  0d: R0 = bitwise OR of imm32 with R0 (EAX)
  21: rm32 = bitwise AND of r32 with rm32
  23: r32 = bitwise AND of r32 with rm32
  25: R0 = bitwise AND of imm32 with R0 (EAX)
  29: subtract r32 from rm32
  2b: subtract rm32 from r32
  2d: subtract imm32 from R0 (EAX)
  31: rm32 = bitwise XOR of r32 with rm32
  33: r32 = bitwise XOR of r32 with rm32
  35: R0 = bitwise XOR of imm32 with R0 (EAX)
  39: set SF if rm32 < r32
  3b: set SF if rm32 > r32
  3d: subtract imm32 from R0 (EAX)
  50: push R0 (EAX) to stack
  51: push R1 (ECX) to stack
  52: push R2 (EDX) to stack
  53: push R3 (EBX) to stack
  54: push R4 (ESP) to stack
  55: push R5 (EBP) to stack
  56: push R6 (ESI) to stack
  57: push R7 (EDI) to stack
  58: pop top of stack to R0 (EAX)
  59: pop top of stack to R1 (ECX)
  5a: pop top of stack to R2 (EDX)
  5b: pop top of stack to R3 (EBX)
  5c: pop top of stack to R4 (ESP)
  5d: pop top of stack to R5 (EBP)
  5e: pop top of stack to R6 (ESI)
  5f: pop top of stack to R7 (EDI)
  68: push imm32 to stack
  74: jump disp8 bytes away if ZF is set
  75: jump disp8 bytes away if ZF is not set
  7c: jump disp8 bytes away if lesser (SF != OF)
  7d: jump disp8 bytes away if greater or equal (SF == OF)
  7e: jump disp8 bytes away if lesser or equal (ZF is set or SF != OF)
  7f: jump disp8 bytes away if greater (ZF is unset, SF == OF)
  81: combine rm32 with imm32 based on subop
  87: swap the contents of r32 and rm32
  89: copy r32 to rm32
  8b: copy rm32 to r32
  8f: pop top of stack to rm32
  b8: copy imm32 to R0 (EAX)
  b9: copy imm32 to R1 (ECX)
  ba: copy imm32 to R2 (EDX)
  bb: copy imm32 to R3 (EBX)
  bc: copy imm32 to R4 (ESP)
  bd: copy imm32 to R5 (EBP)
  be: copy imm32 to R6 (ESI)
  bf: copy imm32 to R7 (EDI)
  c3: return from most recent unfinished call
  c7: copy imm32 to rm32
  cd: software interrupt (0x80 only)
  e8: call disp32
  e9: jump disp16 bytes away
  eb: jump disp8 bytes away
  f4: halt
  f7: bitwise complement of rm32
  ff: jump/push/call rm32 based on subop
Coming soon: `subx help operands` for details on words like 'r32' and 'disp8'.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment