Virtual hardware¶
CPU¶
Ducky VM can have multiple CPUs, each with multiple cores. Each core is a 32-bit microprocessor, with 32 32-bit registers, connected to main memory. It is equiped with MMU, and its own instruction cache.
CPU core can work in privileged and unprivileged modes, allowing use of several protected instructions in privileged mode.
Registers¶
- 32 32-bit registers - registers
r0
tor28
are general purpose registers -r30
is reserved, and used as a stack pointer register,SP
- contains address of the last push’ed value on stack -r29
is reserved, and used as a frame pointer register,FP
- contains content ofSP
in time of the lastcall
orint
instruction -r31
is reserved, and used as a instruction pointer register,IP
- contains address of next instruction to be executedflags
register
IP
and flags
registers are protected, and cannot be modified by standard means (push flags; <modify flags>; pop flags
) when CPU is in user mode
Flags register¶
Mask | Flags | Usage |
0x00 | privileged |
If set, CPU runs in privileged mode, and usage of protected instructions is allowed |
0x01 | hwint_allowed |
If set, HW interrupts can be delivered to this core |
0x04 | e |
Set if the last two compared registers were equal |
0x08 | z |
Set if the last arithmetic operation produced zero |
0x10 | o |
Set if the last arithmetic operation overflown |
0x20 | s |
Set if the last arithmetic operation produced negative result |
Instruction set¶
CPU supports multiple instruction set. The default one, ducky, is the main workhorse, suited for general coding, but other instruction sets can exist, e.g. coprocessor may use its own instruction set for its operations.
Design principles¶
- load and store operations are performed by dedicated instructions
- all memory-register transfers work with 32-bit operands, 16-bit and 8-bit operands are handled by special instructions when necessary
- all memory-register transfers work with addresses that are aligned to the size of their operands (1 byte alignment - so no alignment at all - for 8-bit operands)
- in most cases, destination operand is the first one. Exceptions are instructions that work with IO ports.
- when content of a register is changed by an instruction, several flags can be modified subsequently. E.g. when new value of register is zero,
z
flag is set.
Notes on documentation¶
rN
refers to generaly any register, fromr0
up tor28
- special registers are refered to by their common names (e.g.SP
).rA
,rB
refer to the first and the second instruction operand respectively and stand for any register.<value>
means immediate, absolute value. This covers both integers, specified as base 10 or base 16 integers, both positive and negative, and labels and addresses, specified as&label
- when instruction accepts more than one operand type, it is documented using
|
character, e.g.(rA|<value>)
means either register or immediate value
Memory¶
Memory model¶
- the full addressable memory is 4 GB, but it is quite unrealistic expectation. I usually stick to 24-bits for addresses, which leads to 16MB of main memory
- memory is organized into pages of 256 bytes - each page can be restricted for read, write and execute operations
Memory layout¶
Interrupt Vector table¶
Interrupt vector table (IVT), located in main memory, is by default located at address 0x00000000
. IVT
address can be set per CPU core. IVT
is 256 bytes long, providing enough space for 64 entries. Typically, lower 32 entries are reserved for hardware interrupts, provided by devices, and upper 32 entries leads to software routines that provide additional functionality for binaries. Each entry has the same layout:
IP - 32 bits |
SP - 32 bits |
When CPU is interrupted - by hardware (device generates interrupt) or software (program executes int
instruction) interrupt - corresponding entry is located in IVT
, using interrupt ID as an index.
Stack¶
- standard LIFO data structure
- grows from higher addresses to lower
- there is no pre-allocated stack, every bit of code needs to prepare its own if it intends to use instructions that operate with stack
- when push’ing value to stack,
SP
is decreased by 4 (size of general register), and then value is stored on this address- each
IVT
provides its own stack pointer