Haxor VM

Haxor consists of compiler hcc, linker hld and virtual machine hvm. hcc translates asm-like code into tokens, hld links them into bytecode, while hvm runs it.

Man, why have you written that?

Writing own implementation of VM gives a lot of knowledge about computer's architecture. You hit into issues not known during day by day activity in high level languages you use. So... just to broaden horizons and for fun ;)

Usage

Compilation:

hcc program.hax

Linking:

hld -o program.hax.e program.hax.u
hld -s 4096 -o program.hax.e program.hax.u # custom stack size

Run:

hvm program.hax.e

License

Haxor is licensed under BSD 3-clause license. You can read it here.

Architecture

General information

  • Little Endian
  • Size of WORD is 64 bit
  • All registers are 64 bit
  • All numbers are signed
  • Memory works in flat model
  • Only integer arithmetic is supported
  • All memory cells are writable

OpCodes

OpCode is 64 bit integer. 0-7 bits are used to designate command, 8-63 are used to specify flags. Command can take 0, 1 or 2 operands. All operands are 64 bit addresses in memory. Const values are pushed into .data section by compiler and pointed by automatically generated labels.

[opcode (command + flags)] [operand1] [operand2]

vCPU

vCPU registers are mapped into lowest memory addresses between 0 and 1024 bytes.

  • ip - instruction pointer
  • sp - stack pointer
  • bp - base pointer
  • ar - arithmetic register I
  • dr - arithmetic register II
  • fr - flags register
  • sc - syscall register
  • op - currently processed opcode
  • r01-10 - general usage registres

Flags register

Flags register is 64 bit.

  • bit 0 - ZERO (A-B=0, so numbers are equal)
  • bit 1 - SIGN (result is negative)
  • rest is reserved for future use

Memory map

Language

Haxor uses primitive asm-like syntax. Each command goes into separate line. You can add comments in code, but they also need to be separate lines, beginning from # or rem. For two-argument commands destination goes into first argument while source into second one. In some commands you can dereference value by enclosing it in brackets (e.g. [sp]). Program starts from main label.

command A, B

Instructions

Arithmetic

add

Sums A and B, result goes to A. A or/and B can be dereferenced. OpCode: 0x01.

add A, B

sub

Subtracts B from A, result goes to A. A or/and B can be dereferenced. OpCode: 0x02.

sub A, B

div

Divides ar register by A. Result goes to register. Remainder goes to dr register. A can be dereferenced. OpCode: 0x03.

div A

mul

Multiplies ar register by A. Result goes to register. A can be dereferenced. OpCode: 0x04.

mul A

inc

Increments A by 1. A can be dereferenced. OpCode: 0x05.

inc A

dec

Decrements A by 1. A can be dereferenced. OpCode: 0x06.

dec A

cmp

Compares A with B by subtracting B from A and setting flags register bits. A or/and B can be dereferenced. OpCode: 0x07.

cmp A, B

Logical

and

Performs bitwise AND operation. A or/and B can be dereferenced. OpCode: 0x40.

and A, B

neg

Reverses the sign of number A. A can be dereferenced. OpCode: 0x41.

neg A

not

Performs bitwise NOT operation. A can be dereferenced. OpCode: 0x42.

not A

or

Performs bitwise OR operation. A or/and B can be dereferenced. OpCode: 0x43.

or A, B

xor

Performs bitwise XOR operation. A or/and B can be dereferenced. OpCode: 0x44.

xor A, B

Transfer

mov

Moves data from B to A. A or/and B can be dereferenced. OpCode: 0x60.

mov A, B

push

Places A on top of stack. OpCode: 0x61.

push A

pop

Removes element from top of the stack and into A. OpCode: 0x62.

pop A

Jumps

call

Places ip register on the stack and jumps to A. A can be dereferenced. OpCode: 0x20.

call A

ret

Pops ip from the stack, and jumps to it. OpCode: 0x2c.

ret

iret

Comes back from interrupt. OpCode: 0x2d.

iret

jmp

Performs unconditional jump to A. A can be dereferenced. OpCode: 0x21.

jmp A

je

Jumps to A if in cmp A is equal to B. A can be dereferenced. OpCode: 0x22.

je A

jg

Jumps to A if in cmp A is greater than B. A can be dereferenced. OpCode: 0x23.

jg A

jge

Jumps to A if in cmp A is greater or equal to B. A can be dereferenced. OpCode: 0x24.

jge A

jl

Jumps to A if in cmp A is less than B. A can be dereferenced. OpCode: 0x25.

jl A

jle

Jumps to A if in cmp A is less or equal to B. A can be dereferenced. OpCode: 0x26.

jle A

jne

Jumps to A if in cmp A is not equal to B. A can be dereferenced. OpCode: 0x27.

jne A

jng

Jumps to A if in cmp A is not greater than B. A can be dereferenced. OpCode: 0x28.

jng A

jnge

Jumps to A if in cmp A is not greater or equal to B. A can be dereferenced. OpCode: 0x29.

jnge A

jnl

Jumps to A if in cmp A is not less than B. A can be dereferenced. OpCode: 0x2a.

jnl A

jnle

Jumps to A if in cmp A is not less or equal to B. A can be dereferenced. OpCode: 0x2b.

jnle A

Various

lea

Pushes address of B into A. OpCode: 0x80.

lea A, B

nop

Does nothing. OpCode: 0x81.

nop

int

Generate software interrupt with ID specified by A. A can be dereferenced. OpCode: 0x85.

int A

syscall

Asks Haxor VM to do "system" call. OpCode: 0x86.

syscall

System calls

Using syscall command you can run some system calls provided by Haxor VM. System call number is passed via sc register, arguments go via stack in reversed order.

exit (01h)

Terminates VM process with specified exit code. Takes 1 argument:

  • exit code
push 100
mov sc, 01h
syscall

printf (02h)

Prints formatted text into file specified by descriptor. Takes 2 or more arguments:

  • file descriptor (1 for standard output, 2 for standard error)
  • format string
  • data depending on format string...
lea r01, format_text
push r01
push 1
mov sc, 02h

scanf (03h)

Converts data from file specified by descriptor. Remember that memory is not automatically allocated by this function. You need to prepare space before calling this function. Use length limits to avoid buffer overflow (e.g. %100s to take up to 100 characters from string). In case of string your buffer must have 1 element more for closing '0'. Takes 2 or more arguments:

  • file descriptor (0 for standard input)
  • format string
  • addresses in memory to put data into them...
section .data
dw scanfmt, "%100s", 0

section .bss
label name
resw 101

lea r01, name
push r01
lea r01, scanfmt
push r01
push 0
mov sc, 03h
syscall

random (04h)

Generates random integer from specified range. Arguments:

  • minimum (inclusive)
  • maximum (inclusive) Generated number is pushed onto stack.
mov sc, 04h
push 100
push 1
syscall