Repository files navigation

armvm

armvm is an ARMv7 assembler and virtual machine that allows you to run 32-bit ARM assembly code on any host architecture. The project consists of two main components:

  1. ARM Assembler: Compiles ARM assembly source code into bytecode
  2. ARM VM: Executes ARM32 bytecode in a virtual environment with syscall support

Features

  • ARMv7 32-bit ARM Support: Full implementation of ARMv7 instruction set
  • Cross-Architecture: Run ARM32 code on x86, x86_64, ARM64, or any other architecture
  • Assembly-to-Bytecode Compiler: Converts ARM assembly directly to executable bytecode
  • Syscall Interface: Extensible system call mechanism for host function integration
  • Memory Management: Built-in stack and heap with configurable sizes
  • Zero Dependencies: Self-contained implementation in C

Assembly Syntax

The assembler supports ARM UAL (Unified Assembly Language) syntax with Apple/Xcode conventions, specifically targeting ARMv7 32-bit ARM architecture.

Supported Assembly Syntax

This assembler was designed to parse assembly code generated by Xcode's ARM32 compiler (using the -arch armv7 flag). The syntax follows ARM UAL with Apple Mach-O object file conventions.

Key Characteristics:

  • Architecture: ARMv7 (32-bit ARM)
  • Syntax Style: ARM UAL (Unified Assembly Language)
  • Object Format: Apple Mach-O conventions
  • Registers: r0-r15 (with sp=r13, lr=r14, pc=r15)
  • Instruction Width: 32-bit instructions
  • Endianness: Little-endian

Instruction Examples

@ Data processingmov r0, #10 @ Move immediateadd r0, r0, r1 @ Add registerssub r2, r0, r1 @ Subtract@ Conditional executioncmp r0, #9 @ Compareaddgt r0, #10 @ Add if greater thanmovls r0, #10 @ Move if lower or same@ Memory operationsldr r0,[sp, #4] @ Load from stackstr r1,[r0, #8] @ Store to memorypush {r0-r3, lr} @ Push multiple registerspop {r0-r3, pc} @ Popand return@ Shifts and rotatesmov r1, r1,lsl #2 @ Logical shift leftadd r0, r0, r1, lsr #1 @ Add with shifted operand@ Branchesb label @ Unconditional branchbl function @ Branch with link (call)bx lr @ Branch and exchange (return)@ PC-relative addressingldr r0, =constant @ Load constant (pseudo-instruction)adr r0, label @ Load address of label

Supported Directives

.ascii "text" @ String data (no null terminator).asciz "text" @ Null-terminated string.byte0x42 @ 8-bit data.short 0x1234 @ 16-bit data.long 0x12345678 @ 32-bit data.space 100 @ Reserve bytes.globl symbol @Globalsymbol export.set name, value @ Define constant.p2align 2 @ Align to power of 2.section __TEXT,__text @ Section directive@ Apple/Mach-O specific.subsections_via_symbols.build_version.indirect_symbol

Supported Instructions

Data Processing:and, eor, sub, rsb, add, adc, sbc, rsc, tst, teq, cmp, cmn, orr, mov, bic, mvn

Multiply:mul, mla, umull, umlal, smull, smlal

Memory Access:ldr, ldrb, ldrh, ldrsb, ldrsh, str, strb, strh, ldm, stm (Note: Byte/halfword/signed variants are parsed as suffixes to LDR/STR base instructions)

Branch:b, bl, bx

Shift Instructions:lsl, lsr, asr, ror (converted internally to MOV with shift operands)

Condition Codes:eq, ne, cs/hs, cc/lo, mi, pl, vs, vc, hi, ls, ge, lt, gt, le, al

Generating Compatible Assembly

Using Clang/LLVM

To generate ARMv7 assembly compatible with this assembler using Clang:

# Compile C to ARMv7 assembly
clang -S -arch armv7 -target armv7-apple-darwin -O2 input.c -o output.s
# For iOS devices (32-bit)
clang -S -arch armv7 -isysroot $(xcrun --sdk iphoneos --show-sdk-path) \
-miphoneos-version-min=10.0 -O2 input.c -o output.s

Using GCC ARM Cross-Compiler

# Using arm-none-eabi-gcc
arm-none-eabi-gcc -S -march=armv7-a -marm -O2 input.c -o output.s
# Using arm-linux-gnueabi-gcc
arm-linux-gnueabi-gcc -S -march=armv7-a -marm -O2 input.c -o output.s

Note: Assembly generated by GCC may need minor syntax adjustments:

  • GCC uses @ for comments; this assembler supports both @ and ;
  • Some GCC-specific directives may need to be removed or adapted
  • Apple/Mach-O specific directives (like .subsections_via_symbols) are optional

Using Xcode (Original Target)

In Xcode, configure your target with:

Build Settings:
- Architecture: armv7
- Valid Architectures: armv7
- Deployment Target: iOS 10.0 or earlier (32-bit support)
To generate assembly:
1. Select Product > Perform Action > Assemble [filename].c
2. Or use: clang -S -arch armv7 [source.c] -o [output.s]

Building

Using Make (Recommended)

The project includes a Makefile for easy compilation on Linux and Mac:

# Build the compiler and VM (creates armvm-compiler executable)
make
# Clean build artifacts
make clean
# Build and run tests
make test

Using Xcode

xcodebuild -project armvm.xcodeproj -scheme armvm

Manual Compilation

You can also build directly with GCC or Clang:

# Using GCCcd armvm
gcc -o armvm-compiler armvm.c compiler.c armcomp.c expr.c memory.c libpvm.c -lm
# Or with Clang
clang -o armvm-compiler armvm.c compiler.c armcomp.c expr.c memory.c libpvm.c -lm

Command-Line Usage

The armvm-compiler executable is an assembler that compiles ARM assembly files to bytecode:

# Compile assembly to bytecode
./armvm-compiler -o output.bin input.s
# Compile multiple files
./armvm-compiler -o program.bin file1.s file2.s file3.s

Output Format:

  • .bin file: Contains compiled bytecode with header
  • _d file: Debug symbols (e.g., output.bin_d)

The compiled bytecode includes:

  • Program header with magic number (0x4143524F / "ORCA" in little-endian)
  • Executable ARM32 instructions
  • Symbol table with global labels and their positions

Programmatic Usage

Lua-like API (recommended)

The preferred way to embed the ARM VM is through avm.h, whose interface is modelled after the Lua C API.

#include<stdio.h>#include<stdlib.h>#include<string.h>#include"avm.h"/* ---- Host functions (avm_CFunction signature) ---- *//* strlen(const char *s) -> int — r0 = pointer to string in VM memory */staticinthost_strlen(avm_State*S) {
avm_pushinteger(S, (int)strlen(avm_tostring(S, 1)));
return1; /* one return value placed in r0 */
}
/* puts(const char *s) — prints and returns void */staticinthost_puts(avm_State*S) {
puts(avm_tostring(S, 1));
return0;
}
intmain(void) {
/* 1. Create state with 64 KB stack and 64 KB heap */avm_State*S=avm_newstate(64*1024, 64*1024);
/* 2. Register host functions — must happen BEFORE avm_loadbuffer() * so the assembler can resolve "bl _strlen" / "bl _puts" */avm_register(S, "strlen", host_strlen);
avm_register(S, "puts", host_puts);
/* 3. Compile and load ARM assembly source */constchar*src=".globl _main\n""_main:\n"" bl _puts\n"/* prints the string whose address is in r0 */" bx lr\n";
if (avm_loadbuffer(S, src, strlen(src)) !=0) {
fprintf(stderr, "compilation failed\n");
avm_close(S);
return1;
}
/* 4. Set up arguments: r0 = VM-relative offset of the string "Hello!\n" *//* (normally you'd embed the string in the assembly; this just shows *//* that r0 is read with avm_tointeger and written with avm_pushinteger) *//* 5. Execute from the _main entry point */avm_call(S, S->entry_point);
/* 6. Read return value from r0 (register index 1) */intresult=avm_tointeger(S, 1);
/* 7. Destroy state */avm_close(S);
returnresult;
}

API Reference

FunctionDescription
avm_newstate(stack, heap)Allocate a new VM state
avm_close(S)Destroy state and free memory
avm_register(S, name, fn)Bind a C function to an assembly symbol
avm_loadbuffer(S, src, len)Compile & load ARM assembly source
avm_call(S, pc)Execute loaded code from given PC
avm_tointeger(S, idx)Read register idx (1=r0) as int
avm_touinteger(S, idx)
avm_tonumber(S, idx)Read register idx as float
avm_tostring(S, idx)Register value → pointer into VM memory
avm_toboolean(S, idx)Non-zero register → true
avm_pushinteger(S, n)Write int return value to r0
avm_pushnumber(S, n)Write float return value to r0
avm_pushboolean(S, b)Write boolean (0/1) to r0

Register indices in avm_to* / avm_push* are 1-indexed: index 1 maps to r0, index 2 maps to r1, etc., matching the ARM calling convention where r0 holds the first argument and the return value.

After avm_loadbuffer() succeeds, S->entry_point contains the byte offset of the _main label, ready to pass to avm_call().

Low-level API (backward-compatible)

The lower-level vm_create / vm_shutdown / execute interface is still available for applications that need more control.

#include"vm.h"// Implement syscall handlerstaticDWORDmy_syscall(LPVMvm, DWORDcall_id) {
switch (call_id) {
case1: return (DWORD)strlen((char*)(vm->memory+vm->r[0]));
case2: return (DWORD)puts((char*)(vm->memory+vm->r[0]));
default: return0;
}
}
intmain(void) {
// Register external function names (index must match case number above)strcpy(symbols[1], "strlen");
strcpy(symbols[2], "puts");
// Compile assembly source into a temporary fileFILE*fp=tmpfile();
compile_buffer(fp, NULL, NULL, source, &apple_asm_syntax);
// Read compiled bytecodefseek(fp, 0, SEEK_END);
DWORDsize= (DWORD)ftell(fp);
BYTE*program=malloc(size);
fseek(fp, 0, SEEK_SET);
fread(program, size, 1, fp);
fclose(fp);
// Create VM and executeLPVMvm=vm_create(my_syscall, 64*1024, 64*1024, program, size);
execute(vm, (DWORD)main_label);
DWORDresult=vm->r[0];
vm_shutdown(vm);
free(program);
return (int)result;
}

Architecture Details

  • Registers: 16 general-purpose registers (r0-r15)
    • r13: Stack Pointer (SP)
    • r14: Link Register (LR)
    • r15: Program Counter (PC)
  • CPSR Flags: N (Negative), Z (Zero), C (Carry), V (Overflow)
  • Memory Model: Separate stack and heap with configurable sizes
  • Instruction Format: 32-bit ARM instructions (not Thumb)
  • Default Sizes: 64KB stack, 64KB heap

Running Tests

The project includes a C-based test suite in the test/ directory:

# Run tests using Make (recommended)
make test# Run tests with Xcode (original Objective-C tests in armtest/)
xcodebuild test -project armvm.xcodeproj -scheme armtest
# Tests include:# - Basic instructions (MOV, ADD, SUB, etc.)# - Memory operations (LDR, STR, PUSH, POP)# - Conditional execution# - Shifts and rotates# - Branch instructions# - Complex programs (linked lists, string operations, etc.)

The C test suite runs 11 core tests covering essential ARM VM functionality.

Use Cases

Sandboxed Plugin / Mod Execution

Run untrusted user-supplied code — game mods, content scripts, user plugins — in a hard-isolated environment, similar to the Quake III Arena VM. The guest has no direct access to host memory or system calls beyond the narrow set of host functions you register, so a buggy or malicious plugin cannot corrupt or escape the host process.

Cross-Architecture Legacy Code

Execute 32-bit ARMv7 logic — old iOS app libraries, embedded firmware, legacy mobile SDKs — on any modern host (x86-64 servers, Apple Silicon Macs, RISC-V boards) without a full OS emulator. Think of it like a lightweight container for ARM32 binaries: bring the code, not the hardware.

Embedded Scripting Engine

Embed armvm as a scripting layer inside a C/C++ application. Authors write behaviour in ARM assembly (or compile C to ARM32 with Clang), register only the host functions they want to expose, and call into the VM at runtime — a fully deterministic, zero-dependency alternative to Lua or Wren for latency-sensitive applications.

Education & ARM Assembly Learning

Provide a safe, self-contained environment for learning ARMv7 assembly. Students can write, compile, and step through real ARM instructions on any laptop without needing physical hardware or a full QEMU image.

Embedded / IoT Firmware Testing

Compile embedded firmware written in C to ARM32 assembly, load it into armvm, stub out peripheral registers as host functions, and run automated tests on a CI server — no evaluation board required.

Reproducible Portable Bytecode Distribution

Ship an .bin bytecode blob that runs identically on every supported host. The compact "ORCA" binary format is self-describing and deterministic, making it straightforward to checksum, cache, or version alongside application assets.

Security Research & Fuzzing

Fuzz ARM binary code paths in a fully-isolated, in-process sandbox. The VM's configurable memory sizes and register-level introspection make it easy to inject inputs, catch crashes, and inspect state without spinning up a separate OS process.

UI-Managed VM Environments (paired with orion-ui)

Connect armvm instances to orion-ui to get a dedicated graphical interface for creating, running, inspecting, and debugging VMs. A visual register/memory inspector, step-by-step execution, and multi-VM management panel would make armvm suitable for teaching environments, game modding toolchains, and embedded development workflows.


Roadmap

The items below would expand armvm's utility across the use cases above.

Near-term

  • Thumb / Thumb-2 instruction set — decode and execute the 16/32-bit Thumb encoding so that GCC and modern Clang outputs work without -marm flags
  • VFP floating-point — basic single/double-precision arithmetic to support numerical code and C float/double values
  • Snapshot & restore — serialize full VM state (registers + memory) to a buffer so execution can be paused, migrated, or replayed
  • Step / breakpoint API — expose avm_step() and avm_breakpoint() in the high-level API to support debuggers and orion-ui integration

Medium-term

  • Multiple concurrent VM instances — thread-safe state so several VMs can run in parallel within the same host process
  • Inter-VM messaging — lightweight channels so cooperating VMs can exchange data without going through the host
  • Filesystem & network sandboxing helpers — optional host-function packs that give the VM controlled access to files and sockets, similar to WASI
  • orion-ui integration — a first-party adapter that exposes armvm's API to orion-ui for graphical VM management, register inspection, and live disassembly

Long-term

  • AArch64 (ARM64) guest support — execute 64-bit ARM code in the same embeddable VM model
  • JIT compilation back-end — translate hot bytecode paths to native host instructions for performance-critical workloads
  • WebAssembly target — compile the VM itself to WASM so ARM32 code can run in a browser with no native install
  • Profiling & tracing API — instruction-level counters and call-graph tracing for performance analysis of guest code

License

This project is licensed under the GNU General Public License v3.0. See the LICENSE file for complete details.

About

armv7 compiler (from asm) and virtual machine project

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

armvm

armvm is an ARMv7 assembler and virtual machine that allows you to run 32-bit ARM assembly code on any host architecture. The project consists of two main components:

  1. ARM Assembler: Compiles ARM assembly source code into bytecode
  2. ARM VM: Executes ARM32 bytecode in a virtual environment with syscall support

Features

  • ARMv7 32-bit ARM Support: Full implementation of ARMv7 instruction set
  • Cross-Architecture: Run ARM32 code on x86, x86_64, ARM64, or any other architecture
  • Assembly-to-Bytecode Compiler: Converts ARM assembly directly to executable bytecode
  • Syscall Interface: Extensible system call mechanism for host function integration
  • Memory Management: Built-in stack and heap with configurable sizes
  • Zero Dependencies: Self-contained implementation in C

Assembly Syntax

The assembler supports ARM UAL (Unified Assembly Language) syntax with Apple/Xcode conventions, specifically targeting ARMv7 32-bit ARM architecture.

Supported Assembly Syntax

This assembler was designed to parse assembly code generated by Xcode's ARM32 compiler (using the -arch armv7 flag). The syntax follows ARM UAL with Apple Mach-O object file conventions.

Key Characteristics:

  • Architecture: ARMv7 (32-bit ARM)
  • Syntax Style: ARM UAL (Unified Assembly Language)
  • Object Format: Apple Mach-O conventions
  • Registers: r0-r15 (with sp=r13, lr=r14, pc=r15)
  • Instruction Width: 32-bit instructions
  • Endianness: Little-endian

Instruction Examples

@ Data processingmov r0, #10 @ Move immediateadd r0, r0, r1 @ Add registerssub r2, r0, r1 @ Subtract@ Conditional executioncmp r0, #9 @ Compareaddgt r0, #10 @ Add if greater thanmovls r0, #10 @ Move if lower or same@ Memory operationsldr r0,[sp, #4] @ Load from stackstr r1,[r0, #8] @ Store to memorypush {r0-r3, lr} @ Push multiple registerspop {r0-r3, pc} @ Popand return@ Shifts and rotatesmov r1, r1,lsl #2 @ Logical shift leftadd r0, r0, r1, lsr #1 @ Add with shifted operand@ Branchesb label @ Unconditional branchbl function @ Branch with link (call)bx lr @ Branch and exchange (return)@ PC-relative addressingldr r0, =constant @ Load constant (pseudo-instruction)adr r0, label @ Load address of label

Supported Directives

.ascii "text" @ String data (no null terminator).asciz "text" @ Null-terminated string.byte0x42 @ 8-bit data.short 0x1234 @ 16-bit data.long 0x12345678 @ 32-bit data.space 100 @ Reserve bytes.globl symbol @Globalsymbol export.set name, value @ Define constant.p2align 2 @ Align to power of 2.section __TEXT,__text @ Section directive@ Apple/Mach-O specific.subsections_via_symbols.build_version.indirect_symbol

Supported Instructions

Data Processing:and, eor, sub, rsb, add, adc, sbc, rsc, tst, teq, cmp, cmn, orr, mov, bic, mvn

Multiply:mul, mla, umull, umlal, smull, smlal

Memory Access:ldr, ldrb, ldrh, ldrsb, ldrsh, str, strb, strh, ldm, stm (Note: Byte/halfword/signed variants are parsed as suffixes to LDR/STR base instructions)

Branch:b, bl, bx

Shift Instructions:lsl, lsr, asr, ror (converted internally to MOV with shift operands)

Condition Codes:eq, ne, cs/hs, cc/lo, mi, pl, vs, vc, hi, ls, ge, lt, gt, le, al

Generating Compatible Assembly

Using Clang/LLVM

To generate ARMv7 assembly compatible with this assembler using Clang:

# Compile C to ARMv7 assembly
clang -S -arch armv7 -target armv7-apple-darwin -O2 input.c -o output.s
# For iOS devices (32-bit)
clang -S -arch armv7 -isysroot $(xcrun --sdk iphoneos --show-sdk-path) \
-miphoneos-version-min=10.0 -O2 input.c -o output.s

Using GCC ARM Cross-Compiler

# Using arm-none-eabi-gcc
arm-none-eabi-gcc -S -march=armv7-a -marm -O2 input.c -o output.s
# Using arm-linux-gnueabi-gcc
arm-linux-gnueabi-gcc -S -march=armv7-a -marm -O2 input.c -o output.s

Note: Assembly generated by GCC may need minor syntax adjustments:

  • GCC uses @ for comments; this assembler supports both @ and ;
  • Some GCC-specific directives may need to be removed or adapted
  • Apple/Mach-O specific directives (like .subsections_via_symbols) are optional

Using Xcode (Original Target)

In Xcode, configure your target with:

Build Settings:
- Architecture: armv7
- Valid Architectures: armv7
- Deployment Target: iOS 10.0 or earlier (32-bit support)
To generate assembly:
1. Select Product > Perform Action > Assemble [filename].c
2. Or use: clang -S -arch armv7 [source.c] -o [output.s]

Building

Using Make (Recommended)

The project includes a Makefile for easy compilation on Linux and Mac:

# Build the compiler and VM (creates armvm-compiler executable)
make
# Clean build artifacts
make clean
# Build and run tests
make test

Using Xcode

xcodebuild -project armvm.xcodeproj -scheme armvm

Manual Compilation

You can also build directly with GCC or Clang:

# Using GCCcd armvm
gcc -o armvm-compiler armvm.c compiler.c armcomp.c expr.c memory.c libpvm.c -lm
# Or with Clang
clang -o armvm-compiler armvm.c compiler.c armcomp.c expr.c memory.c libpvm.c -lm

Command-Line Usage

The armvm-compiler executable is an assembler that compiles ARM assembly files to bytecode:

# Compile assembly to bytecode
./armvm-compiler -o output.bin input.s
# Compile multiple files
./armvm-compiler -o program.bin file1.s file2.s file3.s

Output Format:

  • .bin file: Contains compiled bytecode with header
  • _d file: Debug symbols (e.g., output.bin_d)

The compiled bytecode includes:

  • Program header with magic number (0x4143524F / "ORCA" in little-endian)
  • Executable ARM32 instructions
  • Symbol table with global labels and their positions

Programmatic Usage

Lua-like API (recommended)

The preferred way to embed the ARM VM is through avm.h, whose interface is modelled after the Lua C API.

#include<stdio.h>#include<stdlib.h>#include<string.h>#include"avm.h"/* ---- Host functions (avm_CFunction signature) ---- *//* strlen(const char *s) -> int — r0 = pointer to string in VM memory */staticinthost_strlen(avm_State*S) {
avm_pushinteger(S, (int)strlen(avm_tostring(S, 1)));
return1; /* one return value placed in r0 */
}
/* puts(const char *s) — prints and returns void */staticinthost_puts(avm_State*S) {
puts(avm_tostring(S, 1));
return0;
}
intmain(void) {
/* 1. Create state with 64 KB stack and 64 KB heap */avm_State*S=avm_newstate(64*1024, 64*1024);
/* 2. Register host functions — must happen BEFORE avm_loadbuffer() * so the assembler can resolve "bl _strlen" / "bl _puts" */avm_register(S, "strlen", host_strlen);
avm_register(S, "puts", host_puts);
/* 3. Compile and load ARM assembly source */constchar*src=".globl _main\n""_main:\n"" bl _puts\n"/* prints the string whose address is in r0 */" bx lr\n";
if (avm_loadbuffer(S, src, strlen(src)) !=0) {
fprintf(stderr, "compilation failed\n");
avm_close(S);
return1;
}
/* 4. Set up arguments: r0 = VM-relative offset of the string "Hello!\n" *//* (normally you'd embed the string in the assembly; this just shows *//* that r0 is read with avm_tointeger and written with avm_pushinteger) *//* 5. Execute from the _main entry point */avm_call(S, S->entry_point);
/* 6. Read return value from r0 (register index 1) */intresult=avm_tointeger(S, 1);
/* 7. Destroy state */avm_close(S);
returnresult;
}

API Reference

FunctionDescription
avm_newstate(stack, heap)Allocate a new VM state
avm_close(S)Destroy state and free memory
avm_register(S, name, fn)Bind a C function to an assembly symbol
avm_loadbuffer(S, src, len)Compile & load ARM assembly source
avm_call(S, pc)Execute loaded code from given PC
avm_tointeger(S, idx)Read register idx (1=r0) as int
avm_touinteger(S, idx)
avm_tonumber(S, idx)Read register idx as float
avm_tostring(S, idx)Register value → pointer into VM memory
avm_toboolean(S, idx)Non-zero register → true
avm_pushinteger(S, n)Write int return value to r0
avm_pushnumber(S, n)Write float return value to r0
avm_pushboolean(S, b)Write boolean (0/1) to r0

Register indices in avm_to* / avm_push* are 1-indexed: index 1 maps to r0, index 2 maps to r1, etc., matching the ARM calling convention where r0 holds the first argument and the return value.

After avm_loadbuffer() succeeds, S->entry_point contains the byte offset of the _main label, ready to pass to avm_call().

Low-level API (backward-compatible)

The lower-level vm_create / vm_shutdown / execute interface is still available for applications that need more control.

#include"vm.h"// Implement syscall handlerstaticDWORDmy_syscall(LPVMvm, DWORDcall_id) {
switch (call_id) {
case1: return (DWORD)strlen((char*)(vm->memory+vm->r[0]));
case2: return (DWORD)puts((char*)(vm->memory+vm->r[0]));
default: return0;
}
}
intmain(void) {
// Register external function names (index must match case number above)strcpy(symbols[1], "strlen");
strcpy(symbols[2], "puts");
// Compile assembly source into a temporary fileFILE*fp=tmpfile();
compile_buffer(fp, NULL, NULL, source, &apple_asm_syntax);
// Read compiled bytecodefseek(fp, 0, SEEK_END);
DWORDsize= (DWORD)ftell(fp);
BYTE*program=malloc(size);
fseek(fp, 0, SEEK_SET);
fread(program, size, 1, fp);
fclose(fp);
// Create VM and executeLPVMvm=vm_create(my_syscall, 64*1024, 64*1024, program, size);
execute(vm, (DWORD)main_label);
DWORDresult=vm->r[0];
vm_shutdown(vm);
free(program);
return (int)result;
}

Architecture Details

  • Registers: 16 general-purpose registers (r0-r15)
    • r13: Stack Pointer (SP)
    • r14: Link Register (LR)
    • r15: Program Counter (PC)
  • CPSR Flags: N (Negative), Z (Zero), C (Carry), V (Overflow)
  • Memory Model: Separate stack and heap with configurable sizes
  • Instruction Format: 32-bit ARM instructions (not Thumb)
  • Default Sizes: 64KB stack, 64KB heap

Running Tests

The project includes a C-based test suite in the test/ directory:

# Run tests using Make (recommended)
make test# Run tests with Xcode (original Objective-C tests in armtest/)
xcodebuild test -project armvm.xcodeproj -scheme armtest
# Tests include:# - Basic instructions (MOV, ADD, SUB, etc.)# - Memory operations (LDR, STR, PUSH, POP)# - Conditional execution# - Shifts and rotates# - Branch instructions# - Complex programs (linked lists, string operations, etc.)

The C test suite runs 11 core tests covering essential ARM VM functionality.

Use Cases

Sandboxed Plugin / Mod Execution

Run untrusted user-supplied code — game mods, content scripts, user plugins — in a hard-isolated environment, similar to the Quake III Arena VM. The guest has no direct access to host memory or system calls beyond the narrow set of host functions you register, so a buggy or malicious plugin cannot corrupt or escape the host process.

Cross-Architecture Legacy Code

Execute 32-bit ARMv7 logic — old iOS app libraries, embedded firmware, legacy mobile SDKs — on any modern host (x86-64 servers, Apple Silicon Macs, RISC-V boards) without a full OS emulator. Think of it like a lightweight container for ARM32 binaries: bring the code, not the hardware.

Embedded Scripting Engine

Embed armvm as a scripting layer inside a C/C++ application. Authors write behaviour in ARM assembly (or compile C to ARM32 with Clang), register only the host functions they want to expose, and call into the VM at runtime — a fully deterministic, zero-dependency alternative to Lua or Wren for latency-sensitive applications.

Education & ARM Assembly Learning

Provide a safe, self-contained environment for learning ARMv7 assembly. Students can write, compile, and step through real ARM instructions on any laptop without needing physical hardware or a full QEMU image.

Embedded / IoT Firmware Testing

Compile embedded firmware written in C to ARM32 assembly, load it into armvm, stub out peripheral registers as host functions, and run automated tests on a CI server — no evaluation board required.

Reproducible Portable Bytecode Distribution

Ship an .bin bytecode blob that runs identically on every supported host. The compact "ORCA" binary format is self-describing and deterministic, making it straightforward to checksum, cache, or version alongside application assets.

Security Research & Fuzzing

Fuzz ARM binary code paths in a fully-isolated, in-process sandbox. The VM's configurable memory sizes and register-level introspection make it easy to inject inputs, catch crashes, and inspect state without spinning up a separate OS process.

UI-Managed VM Environments (paired with orion-ui)

Connect armvm instances to orion-ui to get a dedicated graphical interface for creating, running, inspecting, and debugging VMs. A visual register/memory inspector, step-by-step execution, and multi-VM management panel would make armvm suitable for teaching environments, game modding toolchains, and embedded development workflows.


Roadmap

The items below would expand armvm's utility across the use cases above.

Near-term

  • Thumb / Thumb-2 instruction set — decode and execute the 16/32-bit Thumb encoding so that GCC and modern Clang outputs work without -marm flags
  • VFP floating-point — basic single/double-precision arithmetic to support numerical code and C float/double values
  • Snapshot & restore — serialize full VM state (registers + memory) to a buffer so execution can be paused, migrated, or replayed
  • Step / breakpoint API — expose avm_step() and avm_breakpoint() in the high-level API to support debuggers and orion-ui integration

Medium-term

  • Multiple concurrent VM instances — thread-safe state so several VMs can run in parallel within the same host process
  • Inter-VM messaging — lightweight channels so cooperating VMs can exchange data without going through the host
  • Filesystem & network sandboxing helpers — optional host-function packs that give the VM controlled access to files and sockets, similar to WASI
  • orion-ui integration — a first-party adapter that exposes armvm's API to orion-ui for graphical VM management, register inspection, and live disassembly

Long-term

  • AArch64 (ARM64) guest support — execute 64-bit ARM code in the same embeddable VM model
  • JIT compilation back-end — translate hot bytecode paths to native host instructions for performance-critical workloads
  • WebAssembly target — compile the VM itself to WASM so ARM32 code can run in a browser with no native install
  • Profiling & tracing API — instruction-level counters and call-graph tracing for performance analysis of guest code

License

This project is licensed under the GNU General Public License v3.0. See the LICENSE file for complete details.

About

armv7 compiler (from asm) and virtual machine project

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

armvm

armvm is an ARMv7 assembler and virtual machine that allows you to run 32-bit ARM assembly code on any host architecture. The project consists of two main components:

  1. ARM Assembler: Compiles ARM assembly source code into bytecode
  2. ARM VM: Executes ARM32 bytecode in a virtual environment with syscall support

Features

  • ARMv7 32-bit ARM Support: Full implementation of ARMv7 instruction set
  • Cross-Architecture: Run ARM32 code on x86, x86_64, ARM64, or any other architecture
  • Assembly-to-Bytecode Compiler: Converts ARM assembly directly to executable bytecode
  • Syscall Interface: Extensible system call mechanism for host function integration
  • Memory Management: Built-in stack and heap with configurable sizes
  • Zero Dependencies: Self-contained implementation in C

Assembly Syntax

The assembler supports ARM UAL (Unified Assembly Language) syntax with Apple/Xcode conventions, specifically targeting ARMv7 32-bit ARM architecture.

Supported Assembly Syntax

This assembler was designed to parse assembly code generated by Xcode's ARM32 compiler (using the -arch armv7 flag). The syntax follows ARM UAL with Apple Mach-O object file conventions.

Key Characteristics:

  • Architecture: ARMv7 (32-bit ARM)
  • Syntax Style: ARM UAL (Unified Assembly Language)
  • Object Format: Apple Mach-O conventions
  • Registers: r0-r15 (with sp=r13, lr=r14, pc=r15)
  • Instruction Width: 32-bit instructions
  • Endianness: Little-endian

Instruction Examples

@ Data processingmov r0, #10 @ Move immediateadd r0, r0, r1 @ Add registerssub r2, r0, r1 @ Subtract@ Conditional executioncmp r0, #9 @ Compareaddgt r0, #10 @ Add if greater thanmovls r0, #10 @ Move if lower or same@ Memory operationsldr r0,[sp, #4] @ Load from stackstr r1,[r0, #8] @ Store to memorypush {r0-r3, lr} @ Push multiple registerspop {r0-r3, pc} @ Popand return@ Shifts and rotatesmov r1, r1,lsl #2 @ Logical shift leftadd r0, r0, r1, lsr #1 @ Add with shifted operand@ Branchesb label @ Unconditional branchbl function @ Branch with link (call)bx lr @ Branch and exchange (return)@ PC-relative addressingldr r0, =constant @ Load constant (pseudo-instruction)adr r0, label @ Load address of label

Supported Directives

.ascii "text" @ String data (no null terminator).asciz "text" @ Null-terminated string.byte0x42 @ 8-bit data.short 0x1234 @ 16-bit data.long 0x12345678 @ 32-bit data.space 100 @ Reserve bytes.globl symbol @Globalsymbol export.set name, value @ Define constant.p2align 2 @ Align to power of 2.section __TEXT,__text @ Section directive@ Apple/Mach-O specific.subsections_via_symbols.build_version.indirect_symbol

Supported Instructions

Data Processing:and, eor, sub, rsb, add, adc, sbc, rsc, tst, teq, cmp, cmn, orr, mov, bic, mvn

Multiply:mul, mla, umull, umlal, smull, smlal

Memory Access:ldr, ldrb, ldrh, ldrsb, ldrsh, str, strb, strh, ldm, stm (Note: Byte/halfword/signed variants are parsed as suffixes to LDR/STR base instructions)

Branch:b, bl, bx

Shift Instructions:lsl, lsr, asr, ror (converted internally to MOV with shift operands)

Condition Codes:eq, ne, cs/hs, cc/lo, mi, pl, vs, vc, hi, ls, ge, lt, gt, le, al

Generating Compatible Assembly

Using Clang/LLVM

To generate ARMv7 assembly compatible with this assembler using Clang:

# Compile C to ARMv7 assembly
clang -S -arch armv7 -target armv7-apple-darwin -O2 input.c -o output.s
# For iOS devices (32-bit)
clang -S -arch armv7 -isysroot $(xcrun --sdk iphoneos --show-sdk-path) \
-miphoneos-version-min=10.0 -O2 input.c -o output.s

Using GCC ARM Cross-Compiler

# Using arm-none-eabi-gcc
arm-none-eabi-gcc -S -march=armv7-a -marm -O2 input.c -o output.s
# Using arm-linux-gnueabi-gcc
arm-linux-gnueabi-gcc -S -march=armv7-a -marm -O2 input.c -o output.s

Note: Assembly generated by GCC may need minor syntax adjustments:

  • GCC uses @ for comments; this assembler supports both @ and ;
  • Some GCC-specific directives may need to be removed or adapted
  • Apple/Mach-O specific directives (like .subsections_via_symbols) are optional

Using Xcode (Original Target)

In Xcode, configure your target with:

Build Settings:
- Architecture: armv7
- Valid Architectures: armv7
- Deployment Target: iOS 10.0 or earlier (32-bit support)
To generate assembly:
1. Select Product > Perform Action > Assemble [filename].c
2. Or use: clang -S -arch armv7 [source.c] -o [output.s]

Building

Using Make (Recommended)

The project includes a Makefile for easy compilation on Linux and Mac:

# Build the compiler and VM (creates armvm-compiler executable)
make
# Clean build artifacts
make clean
# Build and run tests
make test

Using Xcode

xcodebuild -project armvm.xcodeproj -scheme armvm

Manual Compilation

You can also build directly with GCC or Clang:

# Using GCCcd armvm
gcc -o armvm-compiler armvm.c compiler.c armcomp.c expr.c memory.c libpvm.c -lm
# Or with Clang
clang -o armvm-compiler armvm.c compiler.c armcomp.c expr.c memory.c libpvm.c -lm

Command-Line Usage

The armvm-compiler executable is an assembler that compiles ARM assembly files to bytecode:

# Compile assembly to bytecode
./armvm-compiler -o output.bin input.s
# Compile multiple files
./armvm-compiler -o program.bin file1.s file2.s file3.s

Output Format:

  • .bin file: Contains compiled bytecode with header
  • _d file: Debug symbols (e.g., output.bin_d)

The compiled bytecode includes:

  • Program header with magic number (0x4143524F / "ORCA" in little-endian)
  • Executable ARM32 instructions
  • Symbol table with global labels and their positions

Programmatic Usage

Lua-like API (recommended)

The preferred way to embed the ARM VM is through avm.h, whose interface is modelled after the Lua C API.

#include<stdio.h>#include<stdlib.h>#include<string.h>#include"avm.h"/* ---- Host functions (avm_CFunction signature) ---- *//* strlen(const char *s) -> int — r0 = pointer to string in VM memory */staticinthost_strlen(avm_State*S) {
avm_pushinteger(S, (int)strlen(avm_tostring(S, 1)));
return1; /* one return value placed in r0 */
}
/* puts(const char *s) — prints and returns void */staticinthost_puts(avm_State*S) {
puts(avm_tostring(S, 1));
return0;
}
intmain(void) {
/* 1. Create state with 64 KB stack and 64 KB heap */avm_State*S=avm_newstate(64*1024, 64*1024);
/* 2. Register host functions — must happen BEFORE avm_loadbuffer() * so the assembler can resolve "bl _strlen" / "bl _puts" */avm_register(S, "strlen", host_strlen);
avm_register(S, "puts", host_puts);
/* 3. Compile and load ARM assembly source */constchar*src=".globl _main\n""_main:\n"" bl _puts\n"/* prints the string whose address is in r0 */" bx lr\n";
if (avm_loadbuffer(S, src, strlen(src)) !=0) {
fprintf(stderr, "compilation failed\n");
avm_close(S);
return1;
}
/* 4. Set up arguments: r0 = VM-relative offset of the string "Hello!\n" *//* (normally you'd embed the string in the assembly; this just shows *//* that r0 is read with avm_tointeger and written with avm_pushinteger) *//* 5. Execute from the _main entry point */avm_call(S, S->entry_point);
/* 6. Read return value from r0 (register index 1) */intresult=avm_tointeger(S, 1);
/* 7. Destroy state */avm_close(S);
returnresult;
}

API Reference

FunctionDescription
avm_newstate(stack, heap)Allocate a new VM state
avm_close(S)Destroy state and free memory
avm_register(S, name, fn)Bind a C function to an assembly symbol
avm_loadbuffer(S, src, len)Compile & load ARM assembly source
avm_call(S, pc)Execute loaded code from given PC
avm_tointeger(S, idx)Read register idx (1=r0) as int
avm_touinteger(S, idx)
avm_tonumber(S, idx)Read register idx as float
avm_tostring(S, idx)Register value → pointer into VM memory
avm_toboolean(S, idx)Non-zero register → true
avm_pushinteger(S, n)Write int return value to r0
avm_pushnumber(S, n)Write float return value to r0
avm_pushboolean(S, b)Write boolean (0/1) to r0

Register indices in avm_to* / avm_push* are 1-indexed: index 1 maps to r0, index 2 maps to r1, etc., matching the ARM calling convention where r0 holds the first argument and the return value.

After avm_loadbuffer() succeeds, S->entry_point contains the byte offset of the _main label, ready to pass to avm_call().

Low-level API (backward-compatible)

The lower-level vm_create / vm_shutdown / execute interface is still available for applications that need more control.

#include"vm.h"// Implement syscall handlerstaticDWORDmy_syscall(LPVMvm, DWORDcall_id) {
switch (call_id) {
case1: return (DWORD)strlen((char*)(vm->memory+vm->r[0]));
case2: return (DWORD)puts((char*)(vm->memory+vm->r[0]));
default: return0;
}
}
intmain(void) {
// Register external function names (index must match case number above)strcpy(symbols[1], "strlen");
strcpy(symbols[2], "puts");
// Compile assembly source into a temporary fileFILE*fp=tmpfile();
compile_buffer(fp, NULL, NULL, source, &apple_asm_syntax);
// Read compiled bytecodefseek(fp, 0, SEEK_END);
DWORDsize= (DWORD)ftell(fp);
BYTE*program=malloc(size);
fseek(fp, 0, SEEK_SET);
fread(program, size, 1, fp);
fclose(fp);
// Create VM and executeLPVMvm=vm_create(my_syscall, 64*1024, 64*1024, program, size);
execute(vm, (DWORD)main_label);
DWORDresult=vm->r[0];
vm_shutdown(vm);
free(program);
return (int)result;
}

Architecture Details

  • Registers: 16 general-purpose registers (r0-r15)
    • r13: Stack Pointer (SP)
    • r14: Link Register (LR)
    • r15: Program Counter (PC)
  • CPSR Flags: N (Negative), Z (Zero), C (Carry), V (Overflow)
  • Memory Model: Separate stack and heap with configurable sizes
  • Instruction Format: 32-bit ARM instructions (not Thumb)
  • Default Sizes: 64KB stack, 64KB heap

Running Tests

The project includes a C-based test suite in the test/ directory:

# Run tests using Make (recommended)
make test# Run tests with Xcode (original Objective-C tests in armtest/)
xcodebuild test -project armvm.xcodeproj -scheme armtest
# Tests include:# - Basic instructions (MOV, ADD, SUB, etc.)# - Memory operations (LDR, STR, PUSH, POP)# - Conditional execution# - Shifts and rotates# - Branch instructions# - Complex programs (linked lists, string operations, etc.)

The C test suite runs 11 core tests covering essential ARM VM functionality.

Use Cases

Sandboxed Plugin / Mod Execution

Run untrusted user-supplied code — game mods, content scripts, user plugins — in a hard-isolated environment, similar to the Quake III Arena VM. The guest has no direct access to host memory or system calls beyond the narrow set of host functions you register, so a buggy or malicious plugin cannot corrupt or escape the host process.

Cross-Architecture Legacy Code

Execute 32-bit ARMv7 logic — old iOS app libraries, embedded firmware, legacy mobile SDKs — on any modern host (x86-64 servers, Apple Silicon Macs, RISC-V boards) without a full OS emulator. Think of it like a lightweight container for ARM32 binaries: bring the code, not the hardware.

Embedded Scripting Engine

Embed armvm as a scripting layer inside a C/C++ application. Authors write behaviour in ARM assembly (or compile C to ARM32 with Clang), register only the host functions they want to expose, and call into the VM at runtime — a fully deterministic, zero-dependency alternative to Lua or Wren for latency-sensitive applications.

Education & ARM Assembly Learning

Provide a safe, self-contained environment for learning ARMv7 assembly. Students can write, compile, and step through real ARM instructions on any laptop without needing physical hardware or a full QEMU image.

Embedded / IoT Firmware Testing

Compile embedded firmware written in C to ARM32 assembly, load it into armvm, stub out peripheral registers as host functions, and run automated tests on a CI server — no evaluation board required.

Reproducible Portable Bytecode Distribution

Ship an .bin bytecode blob that runs identically on every supported host. The compact "ORCA" binary format is self-describing and deterministic, making it straightforward to checksum, cache, or version alongside application assets.

Security Research & Fuzzing

Fuzz ARM binary code paths in a fully-isolated, in-process sandbox. The VM's configurable memory sizes and register-level introspection make it easy to inject inputs, catch crashes, and inspect state without spinning up a separate OS process.

UI-Managed VM Environments (paired with orion-ui)

Connect armvm instances to orion-ui to get a dedicated graphical interface for creating, running, inspecting, and debugging VMs. A visual register/memory inspector, step-by-step execution, and multi-VM management panel would make armvm suitable for teaching environments, game modding toolchains, and embedded development workflows.


Roadmap

The items below would expand armvm's utility across the use cases above.

Near-term

  • Thumb / Thumb-2 instruction set — decode and execute the 16/32-bit Thumb encoding so that GCC and modern Clang outputs work without -marm flags
  • VFP floating-point — basic single/double-precision arithmetic to support numerical code and C float/double values
  • Snapshot & restore — serialize full VM state (registers + memory) to a buffer so execution can be paused, migrated, or replayed
  • Step / breakpoint API — expose avm_step() and avm_breakpoint() in the high-level API to support debuggers and orion-ui integration

Medium-term

  • Multiple concurrent VM instances — thread-safe state so several VMs can run in parallel within the same host process
  • Inter-VM messaging — lightweight channels so cooperating VMs can exchange data without going through the host
  • Filesystem & network sandboxing helpers — optional host-function packs that give the VM controlled access to files and sockets, similar to WASI
  • orion-ui integration — a first-party adapter that exposes armvm's API to orion-ui for graphical VM management, register inspection, and live disassembly

Long-term

  • AArch64 (ARM64) guest support — execute 64-bit ARM code in the same embeddable VM model
  • JIT compilation back-end — translate hot bytecode paths to native host instructions for performance-critical workloads
  • WebAssembly target — compile the VM itself to WASM so ARM32 code can run in a browser with no native install
  • Profiling & tracing API — instruction-level counters and call-graph tracing for performance analysis of guest code

License

This project is licensed under the GNU General Public License v3.0. See the LICENSE file for complete details.

About

armv7 compiler (from asm) and virtual machine project

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

armvm

armvm is an ARMv7 assembler and virtual machine that allows you to run 32-bit ARM assembly code on any host architecture. The project consists of two main components:

  1. ARM Assembler: Compiles ARM assembly source code into bytecode
  2. ARM VM: Executes ARM32 bytecode in a virtual environment with syscall support

Features

  • ARMv7 32-bit ARM Support: Full implementation of ARMv7 instruction set
  • Cross-Architecture: Run ARM32 code on x86, x86_64, ARM64, or any other architecture
  • Assembly-to-Bytecode Compiler: Converts ARM assembly directly to executable bytecode
  • Syscall Interface: Extensible system call mechanism for host function integration
  • Memory Management: Built-in stack and heap with configurable sizes
  • Zero Dependencies: Self-contained implementation in C

Assembly Syntax

The assembler supports ARM UAL (Unified Assembly Language) syntax with Apple/Xcode conventions, specifically targeting ARMv7 32-bit ARM architecture.

Supported Assembly Syntax

This assembler was designed to parse assembly code generated by Xcode's ARM32 compiler (using the -arch armv7 flag). The syntax follows ARM UAL with Apple Mach-O object file conventions.

Key Characteristics:

  • Architecture: ARMv7 (32-bit ARM)
  • Syntax Style: ARM UAL (Unified Assembly Language)
  • Object Format: Apple Mach-O conventions
  • Registers: r0-r15 (with sp=r13, lr=r14, pc=r15)
  • Instruction Width: 32-bit instructions
  • Endianness: Little-endian

Instruction Examples

@ Data processingmov r0, #10 @ Move immediateadd r0, r0, r1 @ Add registerssub r2, r0, r1 @ Subtract@ Conditional executioncmp r0, #9 @ Compareaddgt r0, #10 @ Add if greater thanmovls r0, #10 @ Move if lower or same@ Memory operationsldr r0,[sp, #4] @ Load from stackstr r1,[r0, #8] @ Store to memorypush {r0-r3, lr} @ Push multiple registerspop {r0-r3, pc} @ Popand return@ Shifts and rotatesmov r1, r1,lsl #2 @ Logical shift leftadd r0, r0, r1, lsr #1 @ Add with shifted operand@ Branchesb label @ Unconditional branchbl function @ Branch with link (call)bx lr @ Branch and exchange (return)@ PC-relative addressingldr r0, =constant @ Load constant (pseudo-instruction)adr r0, label @ Load address of label

Supported Directives

.ascii "text" @ String data (no null terminator).asciz "text" @ Null-terminated string.byte0x42 @ 8-bit data.short 0x1234 @ 16-bit data.long 0x12345678 @ 32-bit data.space 100 @ Reserve bytes.globl symbol @Globalsymbol export.set name, value @ Define constant.p2align 2 @ Align to power of 2.section __TEXT,__text @ Section directive@ Apple/Mach-O specific.subsections_via_symbols.build_version.indirect_symbol

Supported Instructions

Data Processing:and, eor, sub, rsb, add, adc, sbc, rsc, tst, teq, cmp, cmn, orr, mov, bic, mvn

Multiply:mul, mla, umull, umlal, smull, smlal

Memory Access:ldr, ldrb, ldrh, ldrsb, ldrsh, str, strb, strh, ldm, stm (Note: Byte/halfword/signed variants are parsed as suffixes to LDR/STR base instructions)

Branch:b, bl, bx

Shift Instructions:lsl, lsr, asr, ror (converted internally to MOV with shift operands)

Condition Codes:eq, ne, cs/hs, cc/lo, mi, pl, vs, vc, hi, ls, ge, lt, gt, le, al

Generating Compatible Assembly

Using Clang/LLVM

To generate ARMv7 assembly compatible with this assembler using Clang:

# Compile C to ARMv7 assembly
clang -S -arch armv7 -target armv7-apple-darwin -O2 input.c -o output.s
# For iOS devices (32-bit)
clang -S -arch armv7 -isysroot $(xcrun --sdk iphoneos --show-sdk-path) \
-miphoneos-version-min=10.0 -O2 input.c -o output.s

Using GCC ARM Cross-Compiler

# Using arm-none-eabi-gcc
arm-none-eabi-gcc -S -march=armv7-a -marm -O2 input.c -o output.s
# Using arm-linux-gnueabi-gcc
arm-linux-gnueabi-gcc -S -march=armv7-a -marm -O2 input.c -o output.s

Note: Assembly generated by GCC may need minor syntax adjustments:

  • GCC uses @ for comments; this assembler supports both @ and ;
  • Some GCC-specific directives may need to be removed or adapted
  • Apple/Mach-O specific directives (like .subsections_via_symbols) are optional

Using Xcode (Original Target)

In Xcode, configure your target with:

Build Settings:
- Architecture: armv7
- Valid Architectures: armv7
- Deployment Target: iOS 10.0 or earlier (32-bit support)
To generate assembly:
1. Select Product > Perform Action > Assemble [filename].c
2. Or use: clang -S -arch armv7 [source.c] -o [output.s]

Building

Using Make (Recommended)

The project includes a Makefile for easy compilation on Linux and Mac:

# Build the compiler and VM (creates armvm-compiler executable)
make
# Clean build artifacts
make clean
# Build and run tests
make test

Using Xcode

xcodebuild -project armvm.xcodeproj -scheme armvm

Manual Compilation

You can also build directly with GCC or Clang:

# Using GCCcd armvm
gcc -o armvm-compiler armvm.c compiler.c armcomp.c expr.c memory.c libpvm.c -lm
# Or with Clang
clang -o armvm-compiler armvm.c compiler.c armcomp.c expr.c memory.c libpvm.c -lm

Command-Line Usage

The armvm-compiler executable is an assembler that compiles ARM assembly files to bytecode:

# Compile assembly to bytecode
./armvm-compiler -o output.bin input.s
# Compile multiple files
./armvm-compiler -o program.bin file1.s file2.s file3.s

Output Format:

  • .bin file: Contains compiled bytecode with header
  • _d file: Debug symbols (e.g., output.bin_d)

The compiled bytecode includes:

  • Program header with magic number (0x4143524F / "ORCA" in little-endian)
  • Executable ARM32 instructions
  • Symbol table with global labels and their positions

Programmatic Usage

Lua-like API (recommended)

The preferred way to embed the ARM VM is through avm.h, whose interface is modelled after the Lua C API.

#include<stdio.h>#include<stdlib.h>#include<string.h>#include"avm.h"/* ---- Host functions (avm_CFunction signature) ---- *//* strlen(const char *s) -> int — r0 = pointer to string in VM memory */staticinthost_strlen(avm_State*S) {
avm_pushinteger(S, (int)strlen(avm_tostring(S, 1)));
return1; /* one return value placed in r0 */
}
/* puts(const char *s) — prints and returns void */staticinthost_puts(avm_State*S) {
puts(avm_tostring(S, 1));
return0;
}
intmain(void) {
/* 1. Create state with 64 KB stack and 64 KB heap */avm_State*S=avm_newstate(64*1024, 64*1024);
/* 2. Register host functions — must happen BEFORE avm_loadbuffer() * so the assembler can resolve "bl _strlen" / "bl _puts" */avm_register(S, "strlen", host_strlen);
avm_register(S, "puts", host_puts);
/* 3. Compile and load ARM assembly source */constchar*src=".globl _main\n""_main:\n"" bl _puts\n"/* prints the string whose address is in r0 */" bx lr\n";
if (avm_loadbuffer(S, src, strlen(src)) !=0) {
fprintf(stderr, "compilation failed\n");
avm_close(S);
return1;
}
/* 4. Set up arguments: r0 = VM-relative offset of the string "Hello!\n" *//* (normally you'd embed the string in the assembly; this just shows *//* that r0 is read with avm_tointeger and written with avm_pushinteger) *//* 5. Execute from the _main entry point */avm_call(S, S->entry_point);
/* 6. Read return value from r0 (register index 1) */intresult=avm_tointeger(S, 1);
/* 7. Destroy state */avm_close(S);
returnresult;
}

API Reference

FunctionDescription
avm_newstate(stack, heap)Allocate a new VM state
avm_close(S)Destroy state and free memory
avm_register(S, name, fn)Bind a C function to an assembly symbol
avm_loadbuffer(S, src, len)Compile & load ARM assembly source
avm_call(S, pc)Execute loaded code from given PC
avm_tointeger(S, idx)Read register idx (1=r0) as int
avm_touinteger(S, idx)
avm_tonumber(S, idx)Read register idx as float
avm_tostring(S, idx)Register value → pointer into VM memory
avm_toboolean(S, idx)Non-zero register → true
avm_pushinteger(S, n)Write int return value to r0
avm_pushnumber(S, n)Write float return value to r0
avm_pushboolean(S, b)Write boolean (0/1) to r0

Register indices in avm_to* / avm_push* are 1-indexed: index 1 maps to r0, index 2 maps to r1, etc., matching the ARM calling convention where r0 holds the first argument and the return value.

After avm_loadbuffer() succeeds, S->entry_point contains the byte offset of the _main label, ready to pass to avm_call().

Low-level API (backward-compatible)

The lower-level vm_create / vm_shutdown / execute interface is still available for applications that need more control.

#include"vm.h"// Implement syscall handlerstaticDWORDmy_syscall(LPVMvm, DWORDcall_id) {
switch (call_id) {
case1: return (DWORD)strlen((char*)(vm->memory+vm->r[0]));
case2: return (DWORD)puts((char*)(vm->memory+vm->r[0]));
default: return0;
}
}
intmain(void) {
// Register external function names (index must match case number above)strcpy(symbols[1], "strlen");
strcpy(symbols[2], "puts");
// Compile assembly source into a temporary fileFILE*fp=tmpfile();
compile_buffer(fp, NULL, NULL, source, &apple_asm_syntax);
// Read compiled bytecodefseek(fp, 0, SEEK_END);
DWORDsize= (DWORD)ftell(fp);
BYTE*program=malloc(size);
fseek(fp, 0, SEEK_SET);
fread(program, size, 1, fp);
fclose(fp);
// Create VM and executeLPVMvm=vm_create(my_syscall, 64*1024, 64*1024, program, size);
execute(vm, (DWORD)main_label);
DWORDresult=vm->r[0];
vm_shutdown(vm);
free(program);
return (int)result;
}

Architecture Details

  • Registers: 16 general-purpose registers (r0-r15)
    • r13: Stack Pointer (SP)
    • r14: Link Register (LR)
    • r15: Program Counter (PC)
  • CPSR Flags: N (Negative), Z (Zero), C (Carry), V (Overflow)
  • Memory Model: Separate stack and heap with configurable sizes
  • Instruction Format: 32-bit ARM instructions (not Thumb)
  • Default Sizes: 64KB stack, 64KB heap

Running Tests

The project includes a C-based test suite in the test/ directory:

# Run tests using Make (recommended)
make test# Run tests with Xcode (original Objective-C tests in armtest/)
xcodebuild test -project armvm.xcodeproj -scheme armtest
# Tests include:# - Basic instructions (MOV, ADD, SUB, etc.)# - Memory operations (LDR, STR, PUSH, POP)# - Conditional execution# - Shifts and rotates# - Branch instructions# - Complex programs (linked lists, string operations, etc.)

The C test suite runs 11 core tests covering essential ARM VM functionality.

Use Cases

Sandboxed Plugin / Mod Execution

Run untrusted user-supplied code — game mods, content scripts, user plugins — in a hard-isolated environment, similar to the Quake III Arena VM. The guest has no direct access to host memory or system calls beyond the narrow set of host functions you register, so a buggy or malicious plugin cannot corrupt or escape the host process.

Cross-Architecture Legacy Code

Execute 32-bit ARMv7 logic — old iOS app libraries, embedded firmware, legacy mobile SDKs — on any modern host (x86-64 servers, Apple Silicon Macs, RISC-V boards) without a full OS emulator. Think of it like a lightweight container for ARM32 binaries: bring the code, not the hardware.

Embedded Scripting Engine

Embed armvm as a scripting layer inside a C/C++ application. Authors write behaviour in ARM assembly (or compile C to ARM32 with Clang), register only the host functions they want to expose, and call into the VM at runtime — a fully deterministic, zero-dependency alternative to Lua or Wren for latency-sensitive applications.

Education & ARM Assembly Learning

Provide a safe, self-contained environment for learning ARMv7 assembly. Students can write, compile, and step through real ARM instructions on any laptop without needing physical hardware or a full QEMU image.

Embedded / IoT Firmware Testing

Compile embedded firmware written in C to ARM32 assembly, load it into armvm, stub out peripheral registers as host functions, and run automated tests on a CI server — no evaluation board required.

Reproducible Portable Bytecode Distribution

Ship an .bin bytecode blob that runs identically on every supported host. The compact "ORCA" binary format is self-describing and deterministic, making it straightforward to checksum, cache, or version alongside application assets.

Security Research & Fuzzing

Fuzz ARM binary code paths in a fully-isolated, in-process sandbox. The VM's configurable memory sizes and register-level introspection make it easy to inject inputs, catch crashes, and inspect state without spinning up a separate OS process.

UI-Managed VM Environments (paired with orion-ui)

Connect armvm instances to orion-ui to get a dedicated graphical interface for creating, running, inspecting, and debugging VMs. A visual register/memory inspector, step-by-step execution, and multi-VM management panel would make armvm suitable for teaching environments, game modding toolchains, and embedded development workflows.


Roadmap

The items below would expand armvm's utility across the use cases above.

Near-term

  • Thumb / Thumb-2 instruction set — decode and execute the 16/32-bit Thumb encoding so that GCC and modern Clang outputs work without -marm flags
  • VFP floating-point — basic single/double-precision arithmetic to support numerical code and C float/double values
  • Snapshot & restore — serialize full VM state (registers + memory) to a buffer so execution can be paused, migrated, or replayed
  • Step / breakpoint API — expose avm_step() and avm_breakpoint() in the high-level API to support debuggers and orion-ui integration

Medium-term

  • Multiple concurrent VM instances — thread-safe state so several VMs can run in parallel within the same host process
  • Inter-VM messaging — lightweight channels so cooperating VMs can exchange data without going through the host
  • Filesystem & network sandboxing helpers — optional host-function packs that give the VM controlled access to files and sockets, similar to WASI
  • orion-ui integration — a first-party adapter that exposes armvm's API to orion-ui for graphical VM management, register inspection, and live disassembly

Long-term

  • AArch64 (ARM64) guest support — execute 64-bit ARM code in the same embeddable VM model
  • JIT compilation back-end — translate hot bytecode paths to native host instructions for performance-critical workloads
  • WebAssembly target — compile the VM itself to WASM so ARM32 code can run in a browser with no native install
  • Profiling & tracing API — instruction-level counters and call-graph tracing for performance analysis of guest code

License

This project is licensed under the GNU General Public License v3.0. See the LICENSE file for complete details.

About

armv7 compiler (from asm) and virtual machine project

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

armvm

armvm is an ARMv7 assembler and virtual machine that allows you to run 32-bit ARM assembly code on any host architecture. The project consists of two main components:

  1. ARM Assembler: Compiles ARM assembly source code into bytecode
  2. ARM VM: Executes ARM32 bytecode in a virtual environment with syscall support

Features

  • ARMv7 32-bit ARM Support: Full implementation of ARMv7 instruction set
  • Cross-Architecture: Run ARM32 code on x86, x86_64, ARM64, or any other architecture
  • Assembly-to-Bytecode Compiler: Converts ARM assembly directly to executable bytecode
  • Syscall Interface: Extensible system call mechanism for host function integration
  • Memory Management: Built-in stack and heap with configurable sizes
  • Zero Dependencies: Self-contained implementation in C

Assembly Syntax

The assembler supports ARM UAL (Unified Assembly Language) syntax with Apple/Xcode conventions, specifically targeting ARMv7 32-bit ARM architecture.

Supported Assembly Syntax

This assembler was designed to parse assembly code generated by Xcode's ARM32 compiler (using the -arch armv7 flag). The syntax follows ARM UAL with Apple Mach-O object file conventions.

Key Characteristics:

  • Architecture: ARMv7 (32-bit ARM)
  • Syntax Style: ARM UAL (Unified Assembly Language)
  • Object Format: Apple Mach-O conventions
  • Registers: r0-r15 (with sp=r13, lr=r14, pc=r15)
  • Instruction Width: 32-bit instructions
  • Endianness: Little-endian

Instruction Examples

@ Data processingmov r0, #10 @ Move immediateadd r0, r0, r1 @ Add registerssub r2, r0, r1 @ Subtract@ Conditional executioncmp r0, #9 @ Compareaddgt r0, #10 @ Add if greater thanmovls r0, #10 @ Move if lower or same@ Memory operationsldr r0,[sp, #4] @ Load from stackstr r1,[r0, #8] @ Store to memorypush {r0-r3, lr} @ Push multiple registerspop {r0-r3, pc} @ Popand return@ Shifts and rotatesmov r1, r1,lsl #2 @ Logical shift leftadd r0, r0, r1, lsr #1 @ Add with shifted operand@ Branchesb label @ Unconditional branchbl function @ Branch with link (call)bx lr @ Branch and exchange (return)@ PC-relative addressingldr r0, =constant @ Load constant (pseudo-instruction)adr r0, label @ Load address of label

Supported Directives

.ascii "text" @ String data (no null terminator).asciz "text" @ Null-terminated string.byte0x42 @ 8-bit data.short 0x1234 @ 16-bit data.long 0x12345678 @ 32-bit data.space 100 @ Reserve bytes.globl symbol @Globalsymbol export.set name, value @ Define constant.p2align 2 @ Align to power of 2.section __TEXT,__text @ Section directive@ Apple/Mach-O specific.subsections_via_symbols.build_version.indirect_symbol

Supported Instructions

Data Processing:and, eor, sub, rsb, add, adc, sbc, rsc, tst, teq, cmp, cmn, orr, mov, bic, mvn

Multiply:mul, mla, umull, umlal, smull, smlal

Memory Access:ldr, ldrb, ldrh, ldrsb, ldrsh, str, strb, strh, ldm, stm (Note: Byte/halfword/signed variants are parsed as suffixes to LDR/STR base instructions)

Branch:b, bl, bx

Shift Instructions:lsl, lsr, asr, ror (converted internally to MOV with shift operands)

Condition Codes:eq, ne, cs/hs, cc/lo, mi, pl, vs, vc, hi, ls, ge, lt, gt, le, al

Generating Compatible Assembly

Using Clang/LLVM

To generate ARMv7 assembly compatible with this assembler using Clang:

# Compile C to ARMv7 assembly
clang -S -arch armv7 -target armv7-apple-darwin -O2 input.c -o output.s
# For iOS devices (32-bit)
clang -S -arch armv7 -isysroot $(xcrun --sdk iphoneos --show-sdk-path) \
-miphoneos-version-min=10.0 -O2 input.c -o output.s

Using GCC ARM Cross-Compiler

# Using arm-none-eabi-gcc
arm-none-eabi-gcc -S -march=armv7-a -marm -O2 input.c -o output.s
# Using arm-linux-gnueabi-gcc
arm-linux-gnueabi-gcc -S -march=armv7-a -marm -O2 input.c -o output.s

Note: Assembly generated by GCC may need minor syntax adjustments:

  • GCC uses @ for comments; this assembler supports both @ and ;
  • Some GCC-specific directives may need to be removed or adapted
  • Apple/Mach-O specific directives (like .subsections_via_symbols) are optional

Using Xcode (Original Target)

In Xcode, configure your target with:

Build Settings:
- Architecture: armv7
- Valid Architectures: armv7
- Deployment Target: iOS 10.0 or earlier (32-bit support)
To generate assembly:
1. Select Product > Perform Action > Assemble [filename].c
2. Or use: clang -S -arch armv7 [source.c] -o [output.s]

Building

Using Make (Recommended)

The project includes a Makefile for easy compilation on Linux and Mac:

# Build the compiler and VM (creates armvm-compiler executable)
make
# Clean build artifacts
make clean
# Build and run tests
make test

Using Xcode

xcodebuild -project armvm.xcodeproj -scheme armvm

Manual Compilation

You can also build directly with GCC or Clang:

# Using GCCcd armvm
gcc -o armvm-compiler armvm.c compiler.c armcomp.c expr.c memory.c libpvm.c -lm
# Or with Clang
clang -o armvm-compiler armvm.c compiler.c armcomp.c expr.c memory.c libpvm.c -lm

Command-Line Usage

The armvm-compiler executable is an assembler that compiles ARM assembly files to bytecode:

# Compile assembly to bytecode
./armvm-compiler -o output.bin input.s
# Compile multiple files
./armvm-compiler -o program.bin file1.s file2.s file3.s

Output Format:

  • .bin file: Contains compiled bytecode with header
  • _d file: Debug symbols (e.g., output.bin_d)

The compiled bytecode includes:

  • Program header with magic number (0x4143524F / "ORCA" in little-endian)
  • Executable ARM32 instructions
  • Symbol table with global labels and their positions

Programmatic Usage

Lua-like API (recommended)

The preferred way to embed the ARM VM is through avm.h, whose interface is modelled after the Lua C API.

#include<stdio.h>#include<stdlib.h>#include<string.h>#include"avm.h"/* ---- Host functions (avm_CFunction signature) ---- *//* strlen(const char *s) -> int — r0 = pointer to string in VM memory */staticinthost_strlen(avm_State*S) {
avm_pushinteger(S, (int)strlen(avm_tostring(S, 1)));
return1; /* one return value placed in r0 */
}
/* puts(const char *s) — prints and returns void */staticinthost_puts(avm_State*S) {
puts(avm_tostring(S, 1));
return0;
}
intmain(void) {
/* 1. Create state with 64 KB stack and 64 KB heap */avm_State*S=avm_newstate(64*1024, 64*1024);
/* 2. Register host functions — must happen BEFORE avm_loadbuffer() * so the assembler can resolve "bl _strlen" / "bl _puts" */avm_register(S, "strlen", host_strlen);
avm_register(S, "puts", host_puts);
/* 3. Compile and load ARM assembly source */constchar*src=".globl _main\n""_main:\n"" bl _puts\n"/* prints the string whose address is in r0 */" bx lr\n";
if (avm_loadbuffer(S, src, strlen(src)) !=0) {
fprintf(stderr, "compilation failed\n");
avm_close(S);
return1;
}
/* 4. Set up arguments: r0 = VM-relative offset of the string "Hello!\n" *//* (normally you'd embed the string in the assembly; this just shows *//* that r0 is read with avm_tointeger and written with avm_pushinteger) *//* 5. Execute from the _main entry point */avm_call(S, S->entry_point);
/* 6. Read return value from r0 (register index 1) */intresult=avm_tointeger(S, 1);
/* 7. Destroy state */avm_close(S);
returnresult;
}

API Reference

FunctionDescription
avm_newstate(stack, heap)Allocate a new VM state
avm_close(S)Destroy state and free memory
avm_register(S, name, fn)Bind a C function to an assembly symbol
avm_loadbuffer(S, src, len)Compile & load ARM assembly source
avm_call(S, pc)Execute loaded code from given PC
avm_tointeger(S, idx)Read register idx (1=r0) as int
avm_touinteger(S, idx)
avm_tonumber(S, idx)Read register idx as float
avm_tostring(S, idx)Register value → pointer into VM memory
avm_toboolean(S, idx)Non-zero register → true
avm_pushinteger(S, n)Write int return value to r0
avm_pushnumber(S, n)Write float return value to r0
avm_pushboolean(S, b)Write boolean (0/1) to r0

Register indices in avm_to* / avm_push* are 1-indexed: index 1 maps to r0, index 2 maps to r1, etc., matching the ARM calling convention where r0 holds the first argument and the return value.

After avm_loadbuffer() succeeds, S->entry_point contains the byte offset of the _main label, ready to pass to avm_call().

Low-level API (backward-compatible)

The lower-level vm_create / vm_shutdown / execute interface is still available for applications that need more control.

#include"vm.h"// Implement syscall handlerstaticDWORDmy_syscall(LPVMvm, DWORDcall_id) {
switch (call_id) {
case1: return (DWORD)strlen((char*)(vm->memory+vm->r[0]));
case2: return (DWORD)puts((char*)(vm->memory+vm->r[0]));
default: return0;
}
}
intmain(void) {
// Register external function names (index must match case number above)strcpy(symbols[1], "strlen");
strcpy(symbols[2], "puts");
// Compile assembly source into a temporary fileFILE*fp=tmpfile();
compile_buffer(fp, NULL, NULL, source, &apple_asm_syntax);
// Read compiled bytecodefseek(fp, 0, SEEK_END);
DWORDsize= (DWORD)ftell(fp);
BYTE*program=malloc(size);
fseek(fp, 0, SEEK_SET);
fread(program, size, 1, fp);
fclose(fp);
// Create VM and executeLPVMvm=vm_create(my_syscall, 64*1024, 64*1024, program, size);
execute(vm, (DWORD)main_label);
DWORDresult=vm->r[0];
vm_shutdown(vm);
free(program);
return (int)result;
}

Architecture Details

  • Registers: 16 general-purpose registers (r0-r15)
    • r13: Stack Pointer (SP)
    • r14: Link Register (LR)
    • r15: Program Counter (PC)
  • CPSR Flags: N (Negative), Z (Zero), C (Carry), V (Overflow)
  • Memory Model: Separate stack and heap with configurable sizes
  • Instruction Format: 32-bit ARM instructions (not Thumb)
  • Default Sizes: 64KB stack, 64KB heap

Running Tests

The project includes a C-based test suite in the test/ directory:

# Run tests using Make (recommended)
make test# Run tests with Xcode (original Objective-C tests in armtest/)
xcodebuild test -project armvm.xcodeproj -scheme armtest
# Tests include:# - Basic instructions (MOV, ADD, SUB, etc.)# - Memory operations (LDR, STR, PUSH, POP)# - Conditional execution# - Shifts and rotates# - Branch instructions# - Complex programs (linked lists, string operations, etc.)

The C test suite runs 11 core tests covering essential ARM VM functionality.

Use Cases

Sandboxed Plugin / Mod Execution

Run untrusted user-supplied code — game mods, content scripts, user plugins — in a hard-isolated environment, similar to the Quake III Arena VM. The guest has no direct access to host memory or system calls beyond the narrow set of host functions you register, so a buggy or malicious plugin cannot corrupt or escape the host process.

Cross-Architecture Legacy Code

Execute 32-bit ARMv7 logic — old iOS app libraries, embedded firmware, legacy mobile SDKs — on any modern host (x86-64 servers, Apple Silicon Macs, RISC-V boards) without a full OS emulator. Think of it like a lightweight container for ARM32 binaries: bring the code, not the hardware.

Embedded Scripting Engine

Embed armvm as a scripting layer inside a C/C++ application. Authors write behaviour in ARM assembly (or compile C to ARM32 with Clang), register only the host functions they want to expose, and call into the VM at runtime — a fully deterministic, zero-dependency alternative to Lua or Wren for latency-sensitive applications.

Education & ARM Assembly Learning

Provide a safe, self-contained environment for learning ARMv7 assembly. Students can write, compile, and step through real ARM instructions on any laptop without needing physical hardware or a full QEMU image.

Embedded / IoT Firmware Testing

Compile embedded firmware written in C to ARM32 assembly, load it into armvm, stub out peripheral registers as host functions, and run automated tests on a CI server — no evaluation board required.

Reproducible Portable Bytecode Distribution

Ship an .bin bytecode blob that runs identically on every supported host. The compact "ORCA" binary format is self-describing and deterministic, making it straightforward to checksum, cache, or version alongside application assets.

Security Research & Fuzzing

Fuzz ARM binary code paths in a fully-isolated, in-process sandbox. The VM's configurable memory sizes and register-level introspection make it easy to inject inputs, catch crashes, and inspect state without spinning up a separate OS process.

UI-Managed VM Environments (paired with orion-ui)

Connect armvm instances to orion-ui to get a dedicated graphical interface for creating, running, inspecting, and debugging VMs. A visual register/memory inspector, step-by-step execution, and multi-VM management panel would make armvm suitable for teaching environments, game modding toolchains, and embedded development workflows.


Roadmap

The items below would expand armvm's utility across the use cases above.

Near-term

  • Thumb / Thumb-2 instruction set — decode and execute the 16/32-bit Thumb encoding so that GCC and modern Clang outputs work without -marm flags
  • VFP floating-point — basic single/double-precision arithmetic to support numerical code and C float/double values
  • Snapshot & restore — serialize full VM state (registers + memory) to a buffer so execution can be paused, migrated, or replayed
  • Step / breakpoint API — expose avm_step() and avm_breakpoint() in the high-level API to support debuggers and orion-ui integration

Medium-term

  • Multiple concurrent VM instances — thread-safe state so several VMs can run in parallel within the same host process
  • Inter-VM messaging — lightweight channels so cooperating VMs can exchange data without going through the host
  • Filesystem & network sandboxing helpers — optional host-function packs that give the VM controlled access to files and sockets, similar to WASI
  • orion-ui integration — a first-party adapter that exposes armvm's API to orion-ui for graphical VM management, register inspection, and live disassembly

Long-term

  • AArch64 (ARM64) guest support — execute 64-bit ARM code in the same embeddable VM model
  • JIT compilation back-end — translate hot bytecode paths to native host instructions for performance-critical workloads
  • WebAssembly target — compile the VM itself to WASM so ARM32 code can run in a browser with no native install
  • Profiling & tracing API — instruction-level counters and call-graph tracing for performance analysis of guest code

License

This project is licensed under the GNU General Public License v3.0. See the LICENSE file for complete details.

About

armv7 compiler (from asm) and virtual machine project

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

armvm

armvm is an ARMv7 assembler and virtual machine that allows you to run 32-bit ARM assembly code on any host architecture. The project consists of two main components:

  1. ARM Assembler: Compiles ARM assembly source code into bytecode
  2. ARM VM: Executes ARM32 bytecode in a virtual environment with syscall support

Features

  • ARMv7 32-bit ARM Support: Full implementation of ARMv7 instruction set
  • Cross-Architecture: Run ARM32 code on x86, x86_64, ARM64, or any other architecture
  • Assembly-to-Bytecode Compiler: Converts ARM assembly directly to executable bytecode
  • Syscall Interface: Extensible system call mechanism for host function integration
  • Memory Management: Built-in stack and heap with configurable sizes
  • Zero Dependencies: Self-contained implementation in C

Assembly Syntax

The assembler supports ARM UAL (Unified Assembly Language) syntax with Apple/Xcode conventions, specifically targeting ARMv7 32-bit ARM architecture.

Supported Assembly Syntax

This assembler was designed to parse assembly code generated by Xcode's ARM32 compiler (using the -arch armv7 flag). The syntax follows ARM UAL with Apple Mach-O object file conventions.

Key Characteristics:

  • Architecture: ARMv7 (32-bit ARM)
  • Syntax Style: ARM UAL (Unified Assembly Language)
  • Object Format: Apple Mach-O conventions
  • Registers: r0-r15 (with sp=r13, lr=r14, pc=r15)
  • Instruction Width: 32-bit instructions
  • Endianness: Little-endian

Instruction Examples

@ Data processingmov r0, #10 @ Move immediateadd r0, r0, r1 @ Add registerssub r2, r0, r1 @ Subtract@ Conditional executioncmp r0, #9 @ Compareaddgt r0, #10 @ Add if greater thanmovls r0, #10 @ Move if lower or same@ Memory operationsldr r0,[sp, #4] @ Load from stackstr r1,[r0, #8] @ Store to memorypush {r0-r3, lr} @ Push multiple registerspop {r0-r3, pc} @ Popand return@ Shifts and rotatesmov r1, r1,lsl #2 @ Logical shift leftadd r0, r0, r1, lsr #1 @ Add with shifted operand@ Branchesb label @ Unconditional branchbl function @ Branch with link (call)bx lr @ Branch and exchange (return)@ PC-relative addressingldr r0, =constant @ Load constant (pseudo-instruction)adr r0, label @ Load address of label

Supported Directives

.ascii "text" @ String data (no null terminator).asciz "text" @ Null-terminated string.byte0x42 @ 8-bit data.short 0x1234 @ 16-bit data.long 0x12345678 @ 32-bit data.space 100 @ Reserve bytes.globl symbol @Globalsymbol export.set name, value @ Define constant.p2align 2 @ Align to power of 2.section __TEXT,__text @ Section directive@ Apple/Mach-O specific.subsections_via_symbols.build_version.indirect_symbol

Supported Instructions

Data Processing:and, eor, sub, rsb, add, adc, sbc, rsc, tst, teq, cmp, cmn, orr, mov, bic, mvn

Multiply:mul, mla, umull, umlal, smull, smlal

Memory Access:ldr, ldrb, ldrh, ldrsb, ldrsh, str, strb, strh, ldm, stm (Note: Byte/halfword/signed variants are parsed as suffixes to LDR/STR base instructions)

Branch:b, bl, bx

Shift Instructions:lsl, lsr, asr, ror (converted internally to MOV with shift operands)

Condition Codes:eq, ne, cs/hs, cc/lo, mi, pl, vs, vc, hi, ls, ge, lt, gt, le, al

Generating Compatible Assembly

Using Clang/LLVM

To generate ARMv7 assembly compatible with this assembler using Clang:

# Compile C to ARMv7 assembly
clang -S -arch armv7 -target armv7-apple-darwin -O2 input.c -o output.s
# For iOS devices (32-bit)
clang -S -arch armv7 -isysroot $(xcrun --sdk iphoneos --show-sdk-path) \
-miphoneos-version-min=10.0 -O2 input.c -o output.s

Using GCC ARM Cross-Compiler

# Using arm-none-eabi-gcc
arm-none-eabi-gcc -S -march=armv7-a -marm -O2 input.c -o output.s
# Using arm-linux-gnueabi-gcc
arm-linux-gnueabi-gcc -S -march=armv7-a -marm -O2 input.c -o output.s

Note: Assembly generated by GCC may need minor syntax adjustments:

  • GCC uses @ for comments; this assembler supports both @ and ;
  • Some GCC-specific directives may need to be removed or adapted
  • Apple/Mach-O specific directives (like .subsections_via_symbols) are optional

Using Xcode (Original Target)

In Xcode, configure your target with:

Build Settings:
- Architecture: armv7
- Valid Architectures: armv7
- Deployment Target: iOS 10.0 or earlier (32-bit support)
To generate assembly:
1. Select Product > Perform Action > Assemble [filename].c
2. Or use: clang -S -arch armv7 [source.c] -o [output.s]

Building

Using Make (Recommended)

The project includes a Makefile for easy compilation on Linux and Mac:

# Build the compiler and VM (creates armvm-compiler executable)
make
# Clean build artifacts
make clean
# Build and run tests
make test

Using Xcode

xcodebuild -project armvm.xcodeproj -scheme armvm

Manual Compilation

You can also build directly with GCC or Clang:

# Using GCCcd armvm
gcc -o armvm-compiler armvm.c compiler.c armcomp.c expr.c memory.c libpvm.c -lm
# Or with Clang
clang -o armvm-compiler armvm.c compiler.c armcomp.c expr.c memory.c libpvm.c -lm

Command-Line Usage

The armvm-compiler executable is an assembler that compiles ARM assembly files to bytecode:

# Compile assembly to bytecode
./armvm-compiler -o output.bin input.s
# Compile multiple files
./armvm-compiler -o program.bin file1.s file2.s file3.s

Output Format:

  • .bin file: Contains compiled bytecode with header
  • _d file: Debug symbols (e.g., output.bin_d)

The compiled bytecode includes:

  • Program header with magic number (0x4143524F / "ORCA" in little-endian)
  • Executable ARM32 instructions
  • Symbol table with global labels and their positions

Programmatic Usage

Lua-like API (recommended)

The preferred way to embed the ARM VM is through avm.h, whose interface is modelled after the Lua C API.

#include<stdio.h>#include<stdlib.h>#include<string.h>#include"avm.h"/* ---- Host functions (avm_CFunction signature) ---- *//* strlen(const char *s) -> int — r0 = pointer to string in VM memory */staticinthost_strlen(avm_State*S) {
avm_pushinteger(S, (int)strlen(avm_tostring(S, 1)));
return1; /* one return value placed in r0 */
}
/* puts(const char *s) — prints and returns void */staticinthost_puts(avm_State*S) {
puts(avm_tostring(S, 1));
return0;
}
intmain(void) {
/* 1. Create state with 64 KB stack and 64 KB heap */avm_State*S=avm_newstate(64*1024, 64*1024);
/* 2. Register host functions — must happen BEFORE avm_loadbuffer() * so the assembler can resolve "bl _strlen" / "bl _puts" */avm_register(S, "strlen", host_strlen);
avm_register(S, "puts", host_puts);
/* 3. Compile and load ARM assembly source */constchar*src=".globl _main\n""_main:\n"" bl _puts\n"/* prints the string whose address is in r0 */" bx lr\n";
if (avm_loadbuffer(S, src, strlen(src)) !=0) {
fprintf(stderr, "compilation failed\n");
avm_close(S);
return1;
}
/* 4. Set up arguments: r0 = VM-relative offset of the string "Hello!\n" *//* (normally you'd embed the string in the assembly; this just shows *//* that r0 is read with avm_tointeger and written with avm_pushinteger) *//* 5. Execute from the _main entry point */avm_call(S, S->entry_point);
/* 6. Read return value from r0 (register index 1) */intresult=avm_tointeger(S, 1);
/* 7. Destroy state */avm_close(S);
returnresult;
}

API Reference

FunctionDescription
avm_newstate(stack, heap)Allocate a new VM state
avm_close(S)Destroy state and free memory
avm_register(S, name, fn)Bind a C function to an assembly symbol
avm_loadbuffer(S, src, len)Compile & load ARM assembly source
avm_call(S, pc)Execute loaded code from given PC
avm_tointeger(S, idx)Read register idx (1=r0) as int
avm_touinteger(S, idx)
avm_tonumber(S, idx)Read register idx as float
avm_tostring(S, idx)Register value → pointer into VM memory
avm_toboolean(S, idx)Non-zero register → true
avm_pushinteger(S, n)Write int return value to r0
avm_pushnumber(S, n)Write float return value to r0
avm_pushboolean(S, b)Write boolean (0/1) to r0

Register indices in avm_to* / avm_push* are 1-indexed: index 1 maps to r0, index 2 maps to r1, etc., matching the ARM calling convention where r0 holds the first argument and the return value.

After avm_loadbuffer() succeeds, S->entry_point contains the byte offset of the _main label, ready to pass to avm_call().

Low-level API (backward-compatible)

The lower-level vm_create / vm_shutdown / execute interface is still available for applications that need more control.

#include"vm.h"// Implement syscall handlerstaticDWORDmy_syscall(LPVMvm, DWORDcall_id) {
switch (call_id) {
case1: return (DWORD)strlen((char*)(vm->memory+vm->r[0]));
case2: return (DWORD)puts((char*)(vm->memory+vm->r[0]));
default: return0;
}
}
intmain(void) {
// Register external function names (index must match case number above)strcpy(symbols[1], "strlen");
strcpy(symbols[2], "puts");
// Compile assembly source into a temporary fileFILE*fp=tmpfile();
compile_buffer(fp, NULL, NULL, source, &apple_asm_syntax);
// Read compiled bytecodefseek(fp, 0, SEEK_END);
DWORDsize= (DWORD)ftell(fp);
BYTE*program=malloc(size);
fseek(fp, 0, SEEK_SET);
fread(program, size, 1, fp);
fclose(fp);
// Create VM and executeLPVMvm=vm_create(my_syscall, 64*1024, 64*1024, program, size);
execute(vm, (DWORD)main_label);
DWORDresult=vm->r[0];
vm_shutdown(vm);
free(program);
return (int)result;
}

Architecture Details

  • Registers: 16 general-purpose registers (r0-r15)
    • r13: Stack Pointer (SP)
    • r14: Link Register (LR)
    • r15: Program Counter (PC)
  • CPSR Flags: N (Negative), Z (Zero), C (Carry), V (Overflow)
  • Memory Model: Separate stack and heap with configurable sizes
  • Instruction Format: 32-bit ARM instructions (not Thumb)
  • Default Sizes: 64KB stack, 64KB heap

Running Tests

The project includes a C-based test suite in the test/ directory:

# Run tests using Make (recommended)
make test# Run tests with Xcode (original Objective-C tests in armtest/)
xcodebuild test -project armvm.xcodeproj -scheme armtest
# Tests include:# - Basic instructions (MOV, ADD, SUB, etc.)# - Memory operations (LDR, STR, PUSH, POP)# - Conditional execution# - Shifts and rotates# - Branch instructions# - Complex programs (linked lists, string operations, etc.)

The C test suite runs 11 core tests covering essential ARM VM functionality.

Use Cases

Sandboxed Plugin / Mod Execution

Run untrusted user-supplied code — game mods, content scripts, user plugins — in a hard-isolated environment, similar to the Quake III Arena VM. The guest has no direct access to host memory or system calls beyond the narrow set of host functions you register, so a buggy or malicious plugin cannot corrupt or escape the host process.

Cross-Architecture Legacy Code

Execute 32-bit ARMv7 logic — old iOS app libraries, embedded firmware, legacy mobile SDKs — on any modern host (x86-64 servers, Apple Silicon Macs, RISC-V boards) without a full OS emulator. Think of it like a lightweight container for ARM32 binaries: bring the code, not the hardware.

Embedded Scripting Engine

Embed armvm as a scripting layer inside a C/C++ application. Authors write behaviour in ARM assembly (or compile C to ARM32 with Clang), register only the host functions they want to expose, and call into the VM at runtime — a fully deterministic, zero-dependency alternative to Lua or Wren for latency-sensitive applications.

Education & ARM Assembly Learning

Provide a safe, self-contained environment for learning ARMv7 assembly. Students can write, compile, and step through real ARM instructions on any laptop without needing physical hardware or a full QEMU image.

Embedded / IoT Firmware Testing

Compile embedded firmware written in C to ARM32 assembly, load it into armvm, stub out peripheral registers as host functions, and run automated tests on a CI server — no evaluation board required.

Reproducible Portable Bytecode Distribution

Ship an .bin bytecode blob that runs identically on every supported host. The compact "ORCA" binary format is self-describing and deterministic, making it straightforward to checksum, cache, or version alongside application assets.

Security Research & Fuzzing

Fuzz ARM binary code paths in a fully-isolated, in-process sandbox. The VM's configurable memory sizes and register-level introspection make it easy to inject inputs, catch crashes, and inspect state without spinning up a separate OS process.

UI-Managed VM Environments (paired with orion-ui)

Connect armvm instances to orion-ui to get a dedicated graphical interface for creating, running, inspecting, and debugging VMs. A visual register/memory inspector, step-by-step execution, and multi-VM management panel would make armvm suitable for teaching environments, game modding toolchains, and embedded development workflows.


Roadmap

The items below would expand armvm's utility across the use cases above.

Near-term

  • Thumb / Thumb-2 instruction set — decode and execute the 16/32-bit Thumb encoding so that GCC and modern Clang outputs work without -marm flags
  • VFP floating-point — basic single/double-precision arithmetic to support numerical code and C float/double values
  • Snapshot & restore — serialize full VM state (registers + memory) to a buffer so execution can be paused, migrated, or replayed
  • Step / breakpoint API — expose avm_step() and avm_breakpoint() in the high-level API to support debuggers and orion-ui integration

Medium-term

  • Multiple concurrent VM instances — thread-safe state so several VMs can run in parallel within the same host process
  • Inter-VM messaging — lightweight channels so cooperating VMs can exchange data without going through the host
  • Filesystem & network sandboxing helpers — optional host-function packs that give the VM controlled access to files and sockets, similar to WASI
  • orion-ui integration — a first-party adapter that exposes armvm's API to orion-ui for graphical VM management, register inspection, and live disassembly

Long-term

  • AArch64 (ARM64) guest support — execute 64-bit ARM code in the same embeddable VM model
  • JIT compilation back-end — translate hot bytecode paths to native host instructions for performance-critical workloads
  • WebAssembly target — compile the VM itself to WASM so ARM32 code can run in a browser with no native install
  • Profiling & tracing API — instruction-level counters and call-graph tracing for performance analysis of guest code

License

This project is licensed under the GNU General Public License v3.0. See the LICENSE file for complete details.

About

armv7 compiler (from asm) and virtual machine project

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

armvm

armvm is an ARMv7 assembler and virtual machine that allows you to run 32-bit ARM assembly code on any host architecture. The project consists of two main components:

  1. ARM Assembler: Compiles ARM assembly source code into bytecode
  2. ARM VM: Executes ARM32 bytecode in a virtual environment with syscall support

Features

  • ARMv7 32-bit ARM Support: Full implementation of ARMv7 instruction set
  • Cross-Architecture: Run ARM32 code on x86, x86_64, ARM64, or any other architecture
  • Assembly-to-Bytecode Compiler: Converts ARM assembly directly to executable bytecode
  • Syscall Interface: Extensible system call mechanism for host function integration
  • Memory Management: Built-in stack and heap with configurable sizes
  • Zero Dependencies: Self-contained implementation in C

Assembly Syntax

The assembler supports ARM UAL (Unified Assembly Language) syntax with Apple/Xcode conventions, specifically targeting ARMv7 32-bit ARM architecture.

Supported Assembly Syntax

This assembler was designed to parse assembly code generated by Xcode's ARM32 compiler (using the -arch armv7 flag). The syntax follows ARM UAL with Apple Mach-O object file conventions.

Key Characteristics:

  • Architecture: ARMv7 (32-bit ARM)
  • Syntax Style: ARM UAL (Unified Assembly Language)
  • Object Format: Apple Mach-O conventions
  • Registers: r0-r15 (with sp=r13, lr=r14, pc=r15)
  • Instruction Width: 32-bit instructions
  • Endianness: Little-endian

Instruction Examples

@ Data processingmov r0, #10 @ Move immediateadd r0, r0, r1 @ Add registerssub r2, r0, r1 @ Subtract@ Conditional executioncmp r0, #9 @ Compareaddgt r0, #10 @ Add if greater thanmovls r0, #10 @ Move if lower or same@ Memory operationsldr r0,[sp, #4] @ Load from stackstr r1,[r0, #8] @ Store to memorypush {r0-r3, lr} @ Push multiple registerspop {r0-r3, pc} @ Popand return@ Shifts and rotatesmov r1, r1,lsl #2 @ Logical shift leftadd r0, r0, r1, lsr #1 @ Add with shifted operand@ Branchesb label @ Unconditional branchbl function @ Branch with link (call)bx lr @ Branch and exchange (return)@ PC-relative addressingldr r0, =constant @ Load constant (pseudo-instruction)adr r0, label @ Load address of label

Supported Directives

.ascii "text" @ String data (no null terminator).asciz "text" @ Null-terminated string.byte0x42 @ 8-bit data.short 0x1234 @ 16-bit data.long 0x12345678 @ 32-bit data.space 100 @ Reserve bytes.globl symbol @Globalsymbol export.set name, value @ Define constant.p2align 2 @ Align to power of 2.section __TEXT,__text @ Section directive@ Apple/Mach-O specific.subsections_via_symbols.build_version.indirect_symbol

Supported Instructions

Data Processing:and, eor, sub, rsb, add, adc, sbc, rsc, tst, teq, cmp, cmn, orr, mov, bic, mvn

Multiply:mul, mla, umull, umlal, smull, smlal

Memory Access:ldr, ldrb, ldrh, ldrsb, ldrsh, str, strb, strh, ldm, stm (Note: Byte/halfword/signed variants are parsed as suffixes to LDR/STR base instructions)

Branch:b, bl, bx

Shift Instructions:lsl, lsr, asr, ror (converted internally to MOV with shift operands)

Condition Codes:eq, ne, cs/hs, cc/lo, mi, pl, vs, vc, hi, ls, ge, lt, gt, le, al

Generating Compatible Assembly

Using Clang/LLVM

To generate ARMv7 assembly compatible with this assembler using Clang:

# Compile C to ARMv7 assembly
clang -S -arch armv7 -target armv7-apple-darwin -O2 input.c -o output.s
# For iOS devices (32-bit)
clang -S -arch armv7 -isysroot $(xcrun --sdk iphoneos --show-sdk-path) \
-miphoneos-version-min=10.0 -O2 input.c -o output.s

Using GCC ARM Cross-Compiler

# Using arm-none-eabi-gcc
arm-none-eabi-gcc -S -march=armv7-a -marm -O2 input.c -o output.s
# Using arm-linux-gnueabi-gcc
arm-linux-gnueabi-gcc -S -march=armv7-a -marm -O2 input.c -o output.s

Note: Assembly generated by GCC may need minor syntax adjustments:

  • GCC uses @ for comments; this assembler supports both @ and ;
  • Some GCC-specific directives may need to be removed or adapted
  • Apple/Mach-O specific directives (like .subsections_via_symbols) are optional

Using Xcode (Original Target)

In Xcode, configure your target with:

Build Settings:
- Architecture: armv7
- Valid Architectures: armv7
- Deployment Target: iOS 10.0 or earlier (32-bit support)
To generate assembly:
1. Select Product > Perform Action > Assemble [filename].c
2. Or use: clang -S -arch armv7 [source.c] -o [output.s]

Building

Using Make (Recommended)

The project includes a Makefile for easy compilation on Linux and Mac:

# Build the compiler and VM (creates armvm-compiler executable)
make
# Clean build artifacts
make clean
# Build and run tests
make test

Using Xcode

xcodebuild -project armvm.xcodeproj -scheme armvm

Manual Compilation

You can also build directly with GCC or Clang:

# Using GCCcd armvm
gcc -o armvm-compiler armvm.c compiler.c armcomp.c expr.c memory.c libpvm.c -lm
# Or with Clang
clang -o armvm-compiler armvm.c compiler.c armcomp.c expr.c memory.c libpvm.c -lm

Command-Line Usage

The armvm-compiler executable is an assembler that compiles ARM assembly files to bytecode:

# Compile assembly to bytecode
./armvm-compiler -o output.bin input.s
# Compile multiple files
./armvm-compiler -o program.bin file1.s file2.s file3.s

Output Format:

  • .bin file: Contains compiled bytecode with header
  • _d file: Debug symbols (e.g., output.bin_d)

The compiled bytecode includes:

  • Program header with magic number (0x4143524F / "ORCA" in little-endian)
  • Executable ARM32 instructions
  • Symbol table with global labels and their positions

Programmatic Usage

Lua-like API (recommended)

The preferred way to embed the ARM VM is through avm.h, whose interface is modelled after the Lua C API.

#include<stdio.h>#include<stdlib.h>#include<string.h>#include"avm.h"/* ---- Host functions (avm_CFunction signature) ---- *//* strlen(const char *s) -> int — r0 = pointer to string in VM memory */staticinthost_strlen(avm_State*S) {
avm_pushinteger(S, (int)strlen(avm_tostring(S, 1)));
return1; /* one return value placed in r0 */
}
/* puts(const char *s) — prints and returns void */staticinthost_puts(avm_State*S) {
puts(avm_tostring(S, 1));
return0;
}
intmain(void) {
/* 1. Create state with 64 KB stack and 64 KB heap */avm_State*S=avm_newstate(64*1024, 64*1024);
/* 2. Register host functions — must happen BEFORE avm_loadbuffer() * so the assembler can resolve "bl _strlen" / "bl _puts" */avm_register(S, "strlen", host_strlen);
avm_register(S, "puts", host_puts);
/* 3. Compile and load ARM assembly source */constchar*src=".globl _main\n""_main:\n"" bl _puts\n"/* prints the string whose address is in r0 */" bx lr\n";
if (avm_loadbuffer(S, src, strlen(src)) !=0) {
fprintf(stderr, "compilation failed\n");
avm_close(S);
return1;
}
/* 4. Set up arguments: r0 = VM-relative offset of the string "Hello!\n" *//* (normally you'd embed the string in the assembly; this just shows *//* that r0 is read with avm_tointeger and written with avm_pushinteger) *//* 5. Execute from the _main entry point */avm_call(S, S->entry_point);
/* 6. Read return value from r0 (register index 1) */intresult=avm_tointeger(S, 1);
/* 7. Destroy state */avm_close(S);
returnresult;
}

API Reference

FunctionDescription
avm_newstate(stack, heap)Allocate a new VM state
avm_close(S)Destroy state and free memory
avm_register(S, name, fn)Bind a C function to an assembly symbol
avm_loadbuffer(S, src, len)Compile & load ARM assembly source
avm_call(S, pc)Execute loaded code from given PC
avm_tointeger(S, idx)Read register idx (1=r0) as int
avm_touinteger(S, idx)
avm_tonumber(S, idx)Read register idx as float
avm_tostring(S, idx)Register value → pointer into VM memory
avm_toboolean(S, idx)Non-zero register → true
avm_pushinteger(S, n)Write int return value to r0
avm_pushnumber(S, n)Write float return value to r0
avm_pushboolean(S, b)Write boolean (0/1) to r0

Register indices in avm_to* / avm_push* are 1-indexed: index 1 maps to r0, index 2 maps to r1, etc., matching the ARM calling convention where r0 holds the first argument and the return value.

After avm_loadbuffer() succeeds, S->entry_point contains the byte offset of the _main label, ready to pass to avm_call().

Low-level API (backward-compatible)

The lower-level vm_create / vm_shutdown / execute interface is still available for applications that need more control.

#include"vm.h"// Implement syscall handlerstaticDWORDmy_syscall(LPVMvm, DWORDcall_id) {
switch (call_id) {
case1: return (DWORD)strlen((char*)(vm->memory+vm->r[0]));
case2: return (DWORD)puts((char*)(vm->memory+vm->r[0]));
default: return0;
}
}
intmain(void) {
// Register external function names (index must match case number above)strcpy(symbols[1], "strlen");
strcpy(symbols[2], "puts");
// Compile assembly source into a temporary fileFILE*fp=tmpfile();
compile_buffer(fp, NULL, NULL, source, &apple_asm_syntax);
// Read compiled bytecodefseek(fp, 0, SEEK_END);
DWORDsize= (DWORD)ftell(fp);
BYTE*program=malloc(size);
fseek(fp, 0, SEEK_SET);
fread(program, size, 1, fp);
fclose(fp);
// Create VM and executeLPVMvm=vm_create(my_syscall, 64*1024, 64*1024, program, size);
execute(vm, (DWORD)main_label);
DWORDresult=vm->r[0];
vm_shutdown(vm);
free(program);
return (int)result;
}

Architecture Details

  • Registers: 16 general-purpose registers (r0-r15)
    • r13: Stack Pointer (SP)
    • r14: Link Register (LR)
    • r15: Program Counter (PC)
  • CPSR Flags: N (Negative), Z (Zero), C (Carry), V (Overflow)
  • Memory Model: Separate stack and heap with configurable sizes
  • Instruction Format: 32-bit ARM instructions (not Thumb)
  • Default Sizes: 64KB stack, 64KB heap

Running Tests

The project includes a C-based test suite in the test/ directory:

# Run tests using Make (recommended)
make test# Run tests with Xcode (original Objective-C tests in armtest/)
xcodebuild test -project armvm.xcodeproj -scheme armtest
# Tests include:# - Basic instructions (MOV, ADD, SUB, etc.)# - Memory operations (LDR, STR, PUSH, POP)# - Conditional execution# - Shifts and rotates# - Branch instructions# - Complex programs (linked lists, string operations, etc.)

The C test suite runs 11 core tests covering essential ARM VM functionality.

Use Cases

Sandboxed Plugin / Mod Execution

Run untrusted user-supplied code — game mods, content scripts, user plugins — in a hard-isolated environment, similar to the Quake III Arena VM. The guest has no direct access to host memory or system calls beyond the narrow set of host functions you register, so a buggy or malicious plugin cannot corrupt or escape the host process.

Cross-Architecture Legacy Code

Execute 32-bit ARMv7 logic — old iOS app libraries, embedded firmware, legacy mobile SDKs — on any modern host (x86-64 servers, Apple Silicon Macs, RISC-V boards) without a full OS emulator. Think of it like a lightweight container for ARM32 binaries: bring the code, not the hardware.

Embedded Scripting Engine

Embed armvm as a scripting layer inside a C/C++ application. Authors write behaviour in ARM assembly (or compile C to ARM32 with Clang), register only the host functions they want to expose, and call into the VM at runtime — a fully deterministic, zero-dependency alternative to Lua or Wren for latency-sensitive applications.

Education & ARM Assembly Learning

Provide a safe, self-contained environment for learning ARMv7 assembly. Students can write, compile, and step through real ARM instructions on any laptop without needing physical hardware or a full QEMU image.

Embedded / IoT Firmware Testing

Compile embedded firmware written in C to ARM32 assembly, load it into armvm, stub out peripheral registers as host functions, and run automated tests on a CI server — no evaluation board required.

Reproducible Portable Bytecode Distribution

Ship an .bin bytecode blob that runs identically on every supported host. The compact "ORCA" binary format is self-describing and deterministic, making it straightforward to checksum, cache, or version alongside application assets.

Security Research & Fuzzing

Fuzz ARM binary code paths in a fully-isolated, in-process sandbox. The VM's configurable memory sizes and register-level introspection make it easy to inject inputs, catch crashes, and inspect state without spinning up a separate OS process.

UI-Managed VM Environments (paired with orion-ui)

Connect armvm instances to orion-ui to get a dedicated graphical interface for creating, running, inspecting, and debugging VMs. A visual register/memory inspector, step-by-step execution, and multi-VM management panel would make armvm suitable for teaching environments, game modding toolchains, and embedded development workflows.


Roadmap

The items below would expand armvm's utility across the use cases above.

Near-term

  • Thumb / Thumb-2 instruction set — decode and execute the 16/32-bit Thumb encoding so that GCC and modern Clang outputs work without -marm flags
  • VFP floating-point — basic single/double-precision arithmetic to support numerical code and C float/double values
  • Snapshot & restore — serialize full VM state (registers + memory) to a buffer so execution can be paused, migrated, or replayed
  • Step / breakpoint API — expose avm_step() and avm_breakpoint() in the high-level API to support debuggers and orion-ui integration

Medium-term

  • Multiple concurrent VM instances — thread-safe state so several VMs can run in parallel within the same host process
  • Inter-VM messaging — lightweight channels so cooperating VMs can exchange data without going through the host
  • Filesystem & network sandboxing helpers — optional host-function packs that give the VM controlled access to files and sockets, similar to WASI
  • orion-ui integration — a first-party adapter that exposes armvm's API to orion-ui for graphical VM management, register inspection, and live disassembly

Long-term

  • AArch64 (ARM64) guest support — execute 64-bit ARM code in the same embeddable VM model
  • JIT compilation back-end — translate hot bytecode paths to native host instructions for performance-critical workloads
  • WebAssembly target — compile the VM itself to WASM so ARM32 code can run in a browser with no native install
  • Profiling & tracing API — instruction-level counters and call-graph tracing for performance analysis of guest code

License

This project is licensed under the GNU General Public License v3.0. See the LICENSE file for complete details.

About

armv7 compiler (from asm) and virtual machine project

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

armvm

armvm is an ARMv7 assembler and virtual machine that allows you to run 32-bit ARM assembly code on any host architecture. The project consists of two main components:

  1. ARM Assembler: Compiles ARM assembly source code into bytecode
  2. ARM VM: Executes ARM32 bytecode in a virtual environment with syscall support

Features

  • ARMv7 32-bit ARM Support: Full implementation of ARMv7 instruction set
  • Cross-Architecture: Run ARM32 code on x86, x86_64, ARM64, or any other architecture
  • Assembly-to-Bytecode Compiler: Converts ARM assembly directly to executable bytecode
  • Syscall Interface: Extensible system call mechanism for host function integration
  • Memory Management: Built-in stack and heap with configurable sizes
  • Zero Dependencies: Self-contained implementation in C

Assembly Syntax

The assembler supports ARM UAL (Unified Assembly Language) syntax with Apple/Xcode conventions, specifically targeting ARMv7 32-bit ARM architecture.

Supported Assembly Syntax

This assembler was designed to parse assembly code generated by Xcode's ARM32 compiler (using the -arch armv7 flag). The syntax follows ARM UAL with Apple Mach-O object file conventions.

Key Characteristics:

  • Architecture: ARMv7 (32-bit ARM)
  • Syntax Style: ARM UAL (Unified Assembly Language)
  • Object Format: Apple Mach-O conventions
  • Registers: r0-r15 (with sp=r13, lr=r14, pc=r15)
  • Instruction Width: 32-bit instructions
  • Endianness: Little-endian

Instruction Examples

@ Data processingmov r0, #10 @ Move immediateadd r0, r0, r1 @ Add registerssub r2, r0, r1 @ Subtract@ Conditional executioncmp r0, #9 @ Compareaddgt r0, #10 @ Add if greater thanmovls r0, #10 @ Move if lower or same@ Memory operationsldr r0,[sp, #4] @ Load from stackstr r1,[r0, #8] @ Store to memorypush {r0-r3, lr} @ Push multiple registerspop {r0-r3, pc} @ Popand return@ Shifts and rotatesmov r1, r1,lsl #2 @ Logical shift leftadd r0, r0, r1, lsr #1 @ Add with shifted operand@ Branchesb label @ Unconditional branchbl function @ Branch with link (call)bx lr @ Branch and exchange (return)@ PC-relative addressingldr r0, =constant @ Load constant (pseudo-instruction)adr r0, label @ Load address of label

Supported Directives

.ascii "text" @ String data (no null terminator).asciz "text" @ Null-terminated string.byte0x42 @ 8-bit data.short 0x1234 @ 16-bit data.long 0x12345678 @ 32-bit data.space 100 @ Reserve bytes.globl symbol @Globalsymbol export.set name, value @ Define constant.p2align 2 @ Align to power of 2.section __TEXT,__text @ Section directive@ Apple/Mach-O specific.subsections_via_symbols.build_version.indirect_symbol

Supported Instructions

Data Processing:and, eor, sub, rsb, add, adc, sbc, rsc, tst, teq, cmp, cmn, orr, mov, bic, mvn

Multiply:mul, mla, umull, umlal, smull, smlal

Memory Access:ldr, ldrb, ldrh, ldrsb, ldrsh, str, strb, strh, ldm, stm (Note: Byte/halfword/signed variants are parsed as suffixes to LDR/STR base instructions)

Branch:b, bl, bx

Shift Instructions:lsl, lsr, asr, ror (converted internally to MOV with shift operands)

Condition Codes:eq, ne, cs/hs, cc/lo, mi, pl, vs, vc, hi, ls, ge, lt, gt, le, al

Generating Compatible Assembly

Using Clang/LLVM

To generate ARMv7 assembly compatible with this assembler using Clang:

# Compile C to ARMv7 assembly
clang -S -arch armv7 -target armv7-apple-darwin -O2 input.c -o output.s
# For iOS devices (32-bit)
clang -S -arch armv7 -isysroot $(xcrun --sdk iphoneos --show-sdk-path) \
-miphoneos-version-min=10.0 -O2 input.c -o output.s

Using GCC ARM Cross-Compiler

# Using arm-none-eabi-gcc
arm-none-eabi-gcc -S -march=armv7-a -marm -O2 input.c -o output.s
# Using arm-linux-gnueabi-gcc
arm-linux-gnueabi-gcc -S -march=armv7-a -marm -O2 input.c -o output.s

Note: Assembly generated by GCC may need minor syntax adjustments:

  • GCC uses @ for comments; this assembler supports both @ and ;
  • Some GCC-specific directives may need to be removed or adapted
  • Apple/Mach-O specific directives (like .subsections_via_symbols) are optional

Using Xcode (Original Target)

In Xcode, configure your target with:

Build Settings:
- Architecture: armv7
- Valid Architectures: armv7
- Deployment Target: iOS 10.0 or earlier (32-bit support)
To generate assembly:
1. Select Product > Perform Action > Assemble [filename].c
2. Or use: clang -S -arch armv7 [source.c] -o [output.s]

Building

Using Make (Recommended)

The project includes a Makefile for easy compilation on Linux and Mac:

# Build the compiler and VM (creates armvm-compiler executable)
make
# Clean build artifacts
make clean
# Build and run tests
make test

Using Xcode

xcodebuild -project armvm.xcodeproj -scheme armvm

Manual Compilation

You can also build directly with GCC or Clang:

# Using GCCcd armvm
gcc -o armvm-compiler armvm.c compiler.c armcomp.c expr.c memory.c libpvm.c -lm
# Or with Clang
clang -o armvm-compiler armvm.c compiler.c armcomp.c expr.c memory.c libpvm.c -lm

Command-Line Usage

The armvm-compiler executable is an assembler that compiles ARM assembly files to bytecode:

# Compile assembly to bytecode
./armvm-compiler -o output.bin input.s
# Compile multiple files
./armvm-compiler -o program.bin file1.s file2.s file3.s

Output Format:

  • .bin file: Contains compiled bytecode with header
  • _d file: Debug symbols (e.g., output.bin_d)

The compiled bytecode includes:

  • Program header with magic number (0x4143524F / "ORCA" in little-endian)
  • Executable ARM32 instructions
  • Symbol table with global labels and their positions

Programmatic Usage

Lua-like API (recommended)

The preferred way to embed the ARM VM is through avm.h, whose interface is modelled after the Lua C API.

#include<stdio.h>#include<stdlib.h>#include<string.h>#include"avm.h"/* ---- Host functions (avm_CFunction signature) ---- *//* strlen(const char *s) -> int — r0 = pointer to string in VM memory */staticinthost_strlen(avm_State*S) {
avm_pushinteger(S, (int)strlen(avm_tostring(S, 1)));
return1; /* one return value placed in r0 */
}
/* puts(const char *s) — prints and returns void */staticinthost_puts(avm_State*S) {
puts(avm_tostring(S, 1));
return0;
}
intmain(void) {
/* 1. Create state with 64 KB stack and 64 KB heap */avm_State*S=avm_newstate(64*1024, 64*1024);
/* 2. Register host functions — must happen BEFORE avm_loadbuffer() * so the assembler can resolve "bl _strlen" / "bl _puts" */avm_register(S, "strlen", host_strlen);
avm_register(S, "puts", host_puts);
/* 3. Compile and load ARM assembly source */constchar*src=".globl _main\n""_main:\n"" bl _puts\n"/* prints the string whose address is in r0 */" bx lr\n";
if (avm_loadbuffer(S, src, strlen(src)) !=0) {
fprintf(stderr, "compilation failed\n");
avm_close(S);
return1;
}
/* 4. Set up arguments: r0 = VM-relative offset of the string "Hello!\n" *//* (normally you'd embed the string in the assembly; this just shows *//* that r0 is read with avm_tointeger and written with avm_pushinteger) *//* 5. Execute from the _main entry point */avm_call(S, S->entry_point);
/* 6. Read return value from r0 (register index 1) */intresult=avm_tointeger(S, 1);
/* 7. Destroy state */avm_close(S);
returnresult;
}

API Reference

FunctionDescription
avm_newstate(stack, heap)Allocate a new VM state
avm_close(S)Destroy state and free memory
avm_register(S, name, fn)Bind a C function to an assembly symbol
avm_loadbuffer(S, src, len)Compile & load ARM assembly source
avm_call(S, pc)Execute loaded code from given PC
avm_tointeger(S, idx)Read register idx (1=r0) as int
avm_touinteger(S, idx)
avm_tonumber(S, idx)Read register idx as float
avm_tostring(S, idx)Register value → pointer into VM memory
avm_toboolean(S, idx)Non-zero register → true
avm_pushinteger(S, n)Write int return value to r0
avm_pushnumber(S, n)Write float return value to r0
avm_pushboolean(S, b)Write boolean (0/1) to r0

Register indices in avm_to* / avm_push* are 1-indexed: index 1 maps to r0, index 2 maps to r1, etc., matching the ARM calling convention where r0 holds the first argument and the return value.

After avm_loadbuffer() succeeds, S->entry_point contains the byte offset of the _main label, ready to pass to avm_call().

Low-level API (backward-compatible)

The lower-level vm_create / vm_shutdown / execute interface is still available for applications that need more control.

#include"vm.h"// Implement syscall handlerstaticDWORDmy_syscall(LPVMvm, DWORDcall_id) {
switch (call_id) {
case1: return (DWORD)strlen((char*)(vm->memory+vm->r[0]));
case2: return (DWORD)puts((char*)(vm->memory+vm->r[0]));
default: return0;
}
}
intmain(void) {
// Register external function names (index must match case number above)strcpy(symbols[1], "strlen");
strcpy(symbols[2], "puts");
// Compile assembly source into a temporary fileFILE*fp=tmpfile();
compile_buffer(fp, NULL, NULL, source, &apple_asm_syntax);
// Read compiled bytecodefseek(fp, 0, SEEK_END);
DWORDsize= (DWORD)ftell(fp);
BYTE*program=malloc(size);
fseek(fp, 0, SEEK_SET);
fread(program, size, 1, fp);
fclose(fp);
// Create VM and executeLPVMvm=vm_create(my_syscall, 64*1024, 64*1024, program, size);
execute(vm, (DWORD)main_label);
DWORDresult=vm->r[0];
vm_shutdown(vm);
free(program);
return (int)result;
}

Architecture Details

  • Registers: 16 general-purpose registers (r0-r15)
    • r13: Stack Pointer (SP)
    • r14: Link Register (LR)
    • r15: Program Counter (PC)
  • CPSR Flags: N (Negative), Z (Zero), C (Carry), V (Overflow)
  • Memory Model: Separate stack and heap with configurable sizes
  • Instruction Format: 32-bit ARM instructions (not Thumb)
  • Default Sizes: 64KB stack, 64KB heap

Running Tests

The project includes a C-based test suite in the test/ directory:

# Run tests using Make (recommended)
make test# Run tests with Xcode (original Objective-C tests in armtest/)
xcodebuild test -project armvm.xcodeproj -scheme armtest
# Tests include:# - Basic instructions (MOV, ADD, SUB, etc.)# - Memory operations (LDR, STR, PUSH, POP)# - Conditional execution# - Shifts and rotates# - Branch instructions# - Complex programs (linked lists, string operations, etc.)

The C test suite runs 11 core tests covering essential ARM VM functionality.

Use Cases

Sandboxed Plugin / Mod Execution

Run untrusted user-supplied code — game mods, content scripts, user plugins — in a hard-isolated environment, similar to the Quake III Arena VM. The guest has no direct access to host memory or system calls beyond the narrow set of host functions you register, so a buggy or malicious plugin cannot corrupt or escape the host process.

Cross-Architecture Legacy Code

Execute 32-bit ARMv7 logic — old iOS app libraries, embedded firmware, legacy mobile SDKs — on any modern host (x86-64 servers, Apple Silicon Macs, RISC-V boards) without a full OS emulator. Think of it like a lightweight container for ARM32 binaries: bring the code, not the hardware.

Embedded Scripting Engine

Embed armvm as a scripting layer inside a C/C++ application. Authors write behaviour in ARM assembly (or compile C to ARM32 with Clang), register only the host functions they want to expose, and call into the VM at runtime — a fully deterministic, zero-dependency alternative to Lua or Wren for latency-sensitive applications.

Education & ARM Assembly Learning

Provide a safe, self-contained environment for learning ARMv7 assembly. Students can write, compile, and step through real ARM instructions on any laptop without needing physical hardware or a full QEMU image.

Embedded / IoT Firmware Testing

Compile embedded firmware written in C to ARM32 assembly, load it into armvm, stub out peripheral registers as host functions, and run automated tests on a CI server — no evaluation board required.

Reproducible Portable Bytecode Distribution

Ship an .bin bytecode blob that runs identically on every supported host. The compact "ORCA" binary format is self-describing and deterministic, making it straightforward to checksum, cache, or version alongside application assets.

Security Research & Fuzzing

Fuzz ARM binary code paths in a fully-isolated, in-process sandbox. The VM's configurable memory sizes and register-level introspection make it easy to inject inputs, catch crashes, and inspect state without spinning up a separate OS process.

UI-Managed VM Environments (paired with orion-ui)

Connect armvm instances to orion-ui to get a dedicated graphical interface for creating, running, inspecting, and debugging VMs. A visual register/memory inspector, step-by-step execution, and multi-VM management panel would make armvm suitable for teaching environments, game modding toolchains, and embedded development workflows.


Roadmap

The items below would expand armvm's utility across the use cases above.

Near-term

  • Thumb / Thumb-2 instruction set — decode and execute the 16/32-bit Thumb encoding so that GCC and modern Clang outputs work without -marm flags
  • VFP floating-point — basic single/double-precision arithmetic to support numerical code and C float/double values
  • Snapshot & restore — serialize full VM state (registers + memory) to a buffer so execution can be paused, migrated, or replayed
  • Step / breakpoint API — expose avm_step() and avm_breakpoint() in the high-level API to support debuggers and orion-ui integration

Medium-term

  • Multiple concurrent VM instances — thread-safe state so several VMs can run in parallel within the same host process
  • Inter-VM messaging — lightweight channels so cooperating VMs can exchange data without going through the host
  • Filesystem & network sandboxing helpers — optional host-function packs that give the VM controlled access to files and sockets, similar to WASI
  • orion-ui integration — a first-party adapter that exposes armvm's API to orion-ui for graphical VM management, register inspection, and live disassembly

Long-term

  • AArch64 (ARM64) guest support — execute 64-bit ARM code in the same embeddable VM model
  • JIT compilation back-end — translate hot bytecode paths to native host instructions for performance-critical workloads
  • WebAssembly target — compile the VM itself to WASM so ARM32 code can run in a browser with no native install
  • Profiling & tracing API — instruction-level counters and call-graph tracing for performance analysis of guest code

License

This project is licensed under the GNU General Public License v3.0. See the LICENSE file for complete details.

About

armv7 compiler (from asm) and virtual machine project

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages