A "virtual CPU" based on neoned71's Virtual-CPU with a basic assembler included. The goal of this project is simply to create an environment for experimentation.
Maybe best consider this codebase more like the mix of a virtual CPU with some additional features normally implemented by kernels.
| # | Title | Description | Details |
|---|---|---|---|
| 1 | Return Address Protection | Return addresses are stored on a dedicated stack to prevent corruption, thus even if the stack is corrupted a function can always return to the caller. | Details |
| 2 | Zero Trust Stack | (In progress) | Details |
Make sure you have Bazelisk installed.
Run all tests to make sure things will work as expected.
bazelisk test --test_output=all //...Compile the assembler and the virtual CPU:
bazelisk build //...You can compile and run the examples to see how things work.
- Use the assembler to create an executable. For example:
./bazel-bin/vasm ./docs/examples/print_hello.asm ./print_hello.o- Run the executable using the virtual CPU. For example:
./bazel-bin/vcpu -m 0x1000 ./print_hello.oWhere we specify how much memory (in bytes) to allocate to the CPU using the -m option.
| Register | Read-Only | Description |
|---|---|---|
| IP | Yes | Instruction pointer |
| SP | No | Stack pointer |
| BP | No | Base pointer |
| RP | Yes | Return address pointer. It works very similar to the stack pointer, but it points to the Return Address Stack. The CPU updates both the Return Address Stack and the value of the register automatically when executing call and ret instructions. |
| FR | No | The CPU sets the value of the register to a number greater than 0 in case of a critical error/exception. The user code must reset the value of the register to 0 as soon as the error/exception is handled. |
| EH | Yes | The address of the error handler function. See the documentation of the seh and jf instructions for more information. |
| FLR | No | Flags register |
| R0..R15 | No | General purpose registers R0, R1, R2 up to R15. |
| Instruction | Syntax | Description |
|---|---|---|
| halt | halt <r> |
Terminate execution and return with the error code stored in the register <r>. Example: halt %R0. |
| seh | seh <l> |
Sets the global error handler to be the function pointed to by the label <l>. Example: seh error_handler. |
| push | push <i> |
Push immediate value <i> onto the stack. Example: push $0x12. |
| pushr | pushr <r> |
Push the value of register <r> onto the stack. Example: push %R0. |
| popr | popr <r> |
Pop a value from the top of the stack into register <r>. Example: popr %R0. |
| jmp | jmp <l> |
Jump to instruction at the address pointed to by the label <l>. Example: jmp hello. |
| call | call <l> |
Call the function pointed to by label <l>. Example: call print_hello. |
| ret | ret |
Return from function to caller. Example: ret. |
| str | str <rs>, <rd> |
Store the value of register <rs> at the memory address pointed to by register <rd>. Example: str %R0, %R1. |
| ldr | ldr <rd> <rs> |
Load value at memory address pointed to by register <rs> into register <rd>. Example: ldr %R1, %R10. |
| mov | mov <r>, <i> |
Set the value of register <r> to the immediate value <i>. Example: mov %R0, $255. |
| movr | mov <rd>, <rs> |
Set the value of register <rd> to the value of register <rs>. Example: movr %R0, %R1. |
| add | add <r>, <i> |
Add the immediate value <i> to the value of the register <r>. Example: add %R0, $32. |
| addr | addr <rd>, <rs> |
Add the value of register <rs> to the value of register <rd>. Example: addr %R0, %R1. |
| sub | sub <r>, <i> |
Subtract the immediate value <i> from the value of register <r>. Example: sub %R0, $1. |
| subr | subr <rd>, <rs> |
Subtract the value of register <rs> from the value of register <rd>. Example: subr %R0, %R1. |
| mul | mul <r>, <i> |
Multiple the value of register <r> by the immediate value <i>. Example: mul %R0, $5. |
| mulr | mul <rd>, <rs> |
Multiple the value of register <rd> by the value of register <rs>. Example: mulr %R0, %R1. |
| div | div <r>, <i> |
Divide the value of register <r> by the immediate value <i>. Example: div %R0, $5. In case of division by zero, if an error handler was set using the seh instruction, execution will continue by immediately jumping to the error handler. If an error handler was not set, the execution will continue with the next instruction. In both cases, when an error is encountered, the value of the Fault Register (FR) will be set to reflect the nature of the error. |
| divr | div <rd>, <rs> |
Divide the value of register <rd> by the value of register <rs>. Example: divr %R0, %R1. In case of division by zero, if an error handler was set using the seh instruction, execution will continue by immediately jumping to the error handler. If an error handler was not set, the execution will continue with the next instruction. In both cases, when an error is encountered, the value of the Fault Register (FR) will be set to reflect the nature of the error. |
| cmp | cmp <r>, <i> |
Compare the value of register <r> to the immediate value <i>. Example: cmp %R0, $0xffff. |
| cmpr | cmpr <rd>, <rs> |
Compare the value of register <rd> to the value of register <rs>. Example: cmpr %R0, %R1. |
| jeq | jeq <l> |
Jump to address marked by label <l> if the two values compared by cmp or cmpr were equal. Example: jeq values_equal. |
| jlt | jlt <l> |
Jump to address marked by label <l> if the value of <r> or <rd> was less than <i> or <rs>. Example: jlt value_less. |
| jgt | jgt <l> |
Jump to address marked by label <l> if the value of <r> or <rd> was greater than <i> or <rs>. Example: jgt value_greater. |
| jf | jf <l> |
Jump to address marked by label <l> if the Fault Register (FR) is set. Example: jf handle_error. |
| putc | putc <r> |
Print the byte value located at the memory address pointed to by register <r> to the screen as a character. Example: putc %$0. |
print |
Print the value at the top of the stack to the console. This instruction is used for test/debugging purposes only. Example: print. |