An educational desktop CPU debugger inspired by QtSPIM, written in Python and PyQt6. Unicorn executes instructions, Capstone disassembles them, and pyelftools reads ELF executables; pefile reads PE and macholib supplies Mach-O structures. The bundled BSD SPIM r764 assembler supplies full SPIM syntax; the original Keystone frontend remains available as a subset backend. Guest CPU execution always uses Unicorn.
- MIPS32 in little- and big-endian configurations; x86 32-bit/x86-64; ARM32 A32/Thumb/Thumb-2 and ARM64 A64; PowerPC32/64 in both byte orders; Motorola 68000/020/030/040/060 (FPU on 020/030/040).
- Run, Pause, backend Step, Reset, and Run to Cursor in a dedicated Qt worker.
- Address breakpoints: toggle, enable/disable, clear, continue and repeated hits.
- Register/disassembly tables with PC, changed values, symbols and source lines.
- Assembly editor with line numbers, syntax highlighting, breakpoint gutter, execution/error highlighting, Open, Save and Save As.
- Static MIPS32/x86/ARM32/PowerPC32/m68k ELF32 and x86-64/ARM64/PowerPC64 ELF64 loading with PT_LOAD permissions, zero-filled BSS, entry
points, symbols and GP initialization from
_gpor PT_MIPS_REGINFO. - x86/ARM and legacy PowerPC PE32/PE32+ loading with headers, section permissions, zero-filled tails, exports and optional HIGHLOW/DIR64 and ARM MOV32 base relocations.
- Thin/universal ARM/PowerPC Mach-O loading with segment permissions, BSS and symbols.
- Reset restores registers, mapped bytes and map topology without disk reads.
- SPIM console syscalls, interactive input, float/double I/O, heap allocation and exit.
- File services 13–16 with binary reads/writes, guest descriptors and a program directory.
- Syscalls in branch delay slots, including waiting for input and branch-likely annulment.
- Memory/stack windows with address navigation, SP following, bytes, words and ASCII.
- GUI register and memory editing, including live disassembly after code edits.
- FPU raw bits, IEEE singles/doubles, FR=0 pairs/FR=1 registers and FCSR inspection/editing.
- Standalone delay-slot breakpoints, saved branch continuation and likely annulment.
- SPIM terminal MMIO with polled I/O and interrupts, CP0 Count/Compare timer,
guest exception handlers,
mfc0/mtc0anderet/rfe. - Optional bundled SPIM startup/standard exception handler.
- Dockable panels with saved layouts.
Failed assembly or executable loading preserves the previously loaded machine. Editing source clears stale source markers until it has been assembled again.
Requires Python 3.12+ and a graphical desktop. Targets macOS (Apple Silicon/Intel), Ubuntu 26.04 Desktop and Windows 10/11 x64. See platform setup and verification for exact dependencies, Windows build instructions, CI coverage and remaining native desktop checks. Use Python 3.12 when native wheels for a newer Python are unavailable.
python3.12 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
python -m pip install -e '.[dev]'
python scripts/build_spim_assembler.py # requires C++, flex and bison
unicorn-uiAlternatively, use uv venv --python 3.12 and uv pip install -e '.[dev]'.
PyQt6 distribution is subject to its GPL/commercial licensing terms.
Run python scripts/build_spim_assembler.py once after installing Python dependencies.
It builds little- and big-endian assembly adapters from the bundled SPIM sources.
A C++ compiler, flex and bison are required; macOS Command Line Tools supply them.
For an installed package, unicorn-ui-build-spim runs the same build.
The adapters use a portable assembly-only frontend. POSIX console code, timers
and the original SPIM interpreter are not linked. Windows uses MinGW-w64 GCC
and .exe helpers; see the platform guide. CXX or --cxx selects GCC/Clang.
Missing adapters produce a setup diagnostic and preserve the loaded program.
The toolbar also offers Keystone subset, which needs no SPIM adapter.
The PyPI Keystone 0.9.2 source package can install without a native library. Install the Homebrew library and link it to the active environment's binding:
brew install keystone
.venv/bin/python scripts/configure_keystone.pyThe helper only creates a link inside the active Python environment. On other
platforms the PyPI wheel normally bundles the native library. A separately
installed library can be configured with --library /path/to/libkeystone.so.
If assembly cannot initialize Keystone, the app reports a setup error and keeps
its existing loaded program available. Do not replace it with OpenStack Keystone.
- Choose File → Open Assembly and select
examples/addition.sorexamples/data-and-branches.s, or create/edit a source in the Source tab. - Select byte order in the toolbar. Opening source automatically assembles it in the currently selected byte order; press F7 after edits or byte-order changes.
- Press F10 to step or F5 to run. The addition example produces
$t2 = 15. - Double-click a disassembly row, press F9, or click an executable source line's gutter to toggle a breakpoint. Source markers refer to the loaded source version, so reassemble after editing before placing source breakpoints.
- Use View → Breakpoints to enable/disable breakpoints. Reset retains persistent breakpoints; loading a new program clears them.
SPIM assembler is the default. It uses the actual r764 grammar, directives,
expressions, pseudo-instruction expansion, small-data allocation and $gp
optimizations, including .ktext, .kdata, .extern, .comm, .lcomm, repeated
data, absolute/symbolic loads/stores, comparison/rotation/arithmetic pseudos,
and the instruction vocabulary accepted by that SPIM revision. It is SPIM syntax;
GNU and MARS-only directives are not added. SPIM's scanner treats adjacent decimal
numbers such as 1,2 as a float, so use spaces in integer lists: .word 1, 2, 3.
Use break 0 for a guest breakpoint exception. Standard data begins at
0x10010000, with the first 64 KiB reserved for $gp small data.
SPIM bare assembler uses SPIM's bare-machine assembly configuration: no
pseudo-instructions or $gp allocation; data starts at 0x10000000.
Keystone subset retains the previous Python frontend: .text, .data,
.globl/.global, .byte, .half, .word, .float, .double, .ascii,
.asciiz, .space, .align, .set noreorder, simple bounded expressions,
li, fixed two-word la, move, b, and nop. Its data starts at 0x10000000.
Unsupported forms produce diagnostics. Keystone native library setup applies to this mode and both x86 and ARM modes.
Use explicit branch delay slots, for example:
b done
nop # executes before the branch reaches done
li $t0, 99 # skipped
done:
move $s0, $t2Branch delay slots are explicit in both modes. Place an instruction after every
branch/jump and avoid branches inside slots, which MIPS defines as unpredictable.
By default, entry is main, then _start, then the text base; reaching the end
of user text completes source execution. Enable Run → SPIM startup and standard
exception handler before assembling to load the bundled exceptions.s, start
at __start, call main and exit when it returns. Leave this option off for a
custom .ktext 0x80000180 handler. Source markers cover expanded instructions.
Open View → Memory or View → Stack. Enter a hexadecimal/decimal address
and press Go / Refresh. Each bounded window shows 256 bytes, endian-aware
32-bit words and ASCII; ?? marks holes. Choose 8, 16 or 32 bytes per row. Follow SP keeps the stack window
centered near SP. Device inspection never consumes terminal input.
Double-click a register value or memory byte while stopped. Register values accept
hexadecimal or signed/unsigned decimal; bytes accept hexadecimal. $zero and FIR
are read-only. MIPS PC must be word aligned; x86 EIP/RIP are byte addressed. Byte edits require mapped RAM, can patch
text, and refresh its disassembly. Edits are disabled while running or waiting for
syscall input. Reset restores the original register/memory/code baseline.
View → Floating-point registers shows raw bits, singles and doubles. FR=0
uses even/odd low/high pairs; FR=1 presents independent 64-bit registers. Edit
any supported cell directly, including NaN/infinity. View → CP0 / FPU control
shows Status, Cause, EPC, BadVAddr, Count, Compare, Config, FCSR and FIR. The FCSR
tooltip decodes rounding mode, flags, enables, causes and FCC0–FCC7.
Try examples/fpu-inspection.s.
Enable Run → Terminal MMIO. Registers at 0xFFFF0000, +4, +8, +12
provide receiver control/data and transmitter control/data. Bit 0 means ready;
bit 1 in each control enables its interrupt. Reading receiver data consumes one
byte. Writing ready transmitter data emits its low byte to the Program Console.
Byte/halfword/word accesses at these register addresses are supported. Transmitter
readiness returns after ten instruction hooks; this is a deterministic device
model, not a physical timing model. The separate Send to MMIO field can queue
UTF-8 text while running. Try examples/mmio-echo.s.
A mapped executable handler at 0x80000180 receives overflow, traps, guest break,
reserved instructions, address/bus errors, FPU exceptions and enabled interrupts.
Cause records ExcCode and BD; EPC records the branch PC for a slot fault and the
faulting PC otherwise; BadVAddr records bad addresses. EXL prevents recursive
handler delivery. mfc0/mtc0 access the SPIM CP0 registers; eret clears EXL and
returns to EPC. Receiver/transmitter interrupts use IP3/IP2. CP0 Count ticks at
100 Hz during execution; matching Compare asserts IP7, and writing Compare clears
it. Masks, IE and EXL control delivery. This educational environment has a flat
virtual memory map; it does not implement a hardware MMU/TLB or a Linux kernel.
Try examples/guest-exceptions.s. The bundled standard handler follows SPIM's
own behavior, including its documented limitation when skipping delay-slot faults;
custom handlers should inspect BD when advancing EPC.
Choose x86 32-bit or x86-64 in the architecture toolbar, then open an x86
source file or press F7. File → Load x86 32-bit/64-bit demo provides runnable
examples. Try examples/x86-addition.s, examples/x86-64-addition.s and
examples/x86-stack.s. Intel instructions use Keystone; x86 assembly runs in a
bounded subprocess so a native assembler failure cannot terminate the GUI.
The x86 frontend supports labels, forward/backward branches and Keystone's branch
relaxation, .text, .data, .globl/.global, .intel_syntax noprefix, matching
.code32/.code64, and data directives .byte, .word/.short (16 bits),
.long/.int (32 bits), .quad (64 bits), .ascii, .asciz/.asciiz,
.space/.zero, and .align (power of two). Use # or ; for comments.
Text contains instructions and labels; data directives belong in .data.
This is a documented Intel frontend, not full NASM/GAS source compatibility.
Run/Pause/Step/Reset, breakpoints, Run to Cursor, source mapping, editable CPU/RAM,
flags, disassembly, and stack inspection work in both modes. Steps execute one
x86 instruction-count boundary; REP string operations can remain at the same PC
while making progress. Branches have no delay slots. HLT completes a bare program,
with PC left at the halt instruction. Source images also complete at text end.
Code edits re-decode variable instruction boundaries; Reset restores original code
and disassembly. Breakpoints remain absolute addresses and hit only instruction
starts that the CPU actually reaches.
Registers include GPRs, EIP/RIP and 32-bit EFLAGS. View → Control / vector registers shows MXCSR and 128-bit XMM registers (8 in x86, 16 in x86-64), with raw editing. EFLAGS/MXCSR tooltips explain their fields. Memory/stack windows accept 64-bit addresses and show architecture-sized words; x86-64 RSP starts above 4 GiB. The MIPS floating-pair dock, SPIM assembler options, standard handler and terminal MMIO are disabled for an x86 machine.
x86 currently emulates a flat bare CPU, without Linux/Windows guest services,
a Windows runtime loader, real-mode/16-bit execution, or OS exception handlers.
int 0x80, syscall and sysenter stop with an explicit OS-environment diagnostic;
other interrupts/exceptions stop with their native CPU diagnostic. Static ELF
loading does not imply that an executable requiring OS services can run to completion.
Choose ARM32 (A32) or ARM64 (A64) in the architecture toolbar, then press
F7 to assemble source. File → Load ARM32/ARM64 demo provides addition examples;
examples/arm32-addition.s and examples/arm64-addition.s produce R0/X0 = 15.
Both modes use little endian. A32/A64 instructions are four bytes; Thumb supports
two- and four-byte instructions through the Thumb / Thumb-2 toolbar option. Run, Step,
breakpoints, Run to Cursor, register/memory edits, and Reset use the shared debugger.
ARM64 has a 64-bit memory/stack view and a 16-byte-aligned initial stack above 4 GiB.
ARM32 displays R0–R12, SP, LR, PC and CPSR. ARM64 displays X0–X30, SP, PC and NZCV; W-register instructions follow Unicorn's zero-extension semantics. Flags tooltips show N/Z/C/V. The Control / vector registers dock presents raw 128-bit Q registers (16 in ARM32, 32 in ARM64), floating-point status/control and FP access controls. SIMD/VFP access starts enabled. ARM32 starts in supervisor A32 state; ARM64 uses Unicorn's initial EL1 state. These are flat bare CPUs without process startup, an MMU setup, OS services, SPIM syscalls or guest exception handlers.
Source accepts Keystone ARM instruction syntax, labels, forward/backward branches,
.text, .data, .globl/.global, data .align exponents, .byte, .short/
.hword, .word/.long/.int (four bytes), .quad/.xword (eight bytes),
.ascii/.asciz/.asciiz, and .space/.zero. ARM32 also accepts .arm and
.syntax unified. # denotes an immediate; use //, @ or ; for comments.
Text contains instructions and labels, with data directives in .data.
Use explicit MOV/MOVW/MOVT or ADR instructions instead of ldr reg, =value
literal-pool pseudos. Full GNU assembler directives/macros are not implemented.
Source execution completes at the end of its text. ARM64 hlt #imm is also a
completion hook. svc stops with an OS-environment diagnostic when its condition
is satisfied; a false A32 condition skips it. Jazelle and big-endian ARM are unsupported. CPSR T edits and guest BX/BLX
interworking switch between A32 and Thumb, with canonical even PC/breakpoint
addresses and mode-aware disassembly. ELF32/EM_ARM and ELF64/EM_AARCH64 load
automatically through Open ELF. ARM32 accepts EABI5 or unspecified ABI flags;
Thumb entry bit 0 selects Thumb. ELF mapping symbols $a/$t/$d identify A32,
Thumb and inline data. Observed guest state changes refine disassembly when
metadata is absent. BE8 flags are rejected.
For Thumb source, select Thumb / Thumb-2 and try examples/thumb-addition.s.
.thumb, .thumb_func (without an argument), .code 16 and .syntax unified
are accepted. A32 accepts .arm/.code 32. A source file uses one selected state;
changing states within one source is not implemented. Thumb code addresses in data
expressions become tagged function pointers; debugger symbols remain even.
Try examples/thumb-addition.elf and break at entry + 4 to inspect R0 = 15.
False IT conditions follow Unicorn; a taken svc reports the missing OS runtime.
Reset restores CPSR, bytes and original disassembly.
Try the independent static examples/arm32-addition.elf or
examples/arm64-addition.elf. Set a breakpoint at entry + 8 and Run to inspect
R0/X0 = 15. These ELF fixtures loop there and require no OS runtime.
Choose PowerPC32 or PowerPC64, in big- or little-endian mode, in the
architecture selector and assemble with F7. File → Load PowerPC32/64 demo provides addition
examples; examples/powerpc32-addition.s and powerpc64-addition.s produce R3 = 15.
Both modes use four-byte instructions and the shared Run/Step/breakpoint/edit/reset
workflow. The stack pointer is R1 and initially 16-byte aligned. PowerPC64 supports
64-bit register values and memory addresses above 4 GiB.
The register table displays R0–R31, PC, LR, CTR, CR and XER. CR tooltips explain its eight condition fields; XER shows carry/overflow flags. The Control / floating-point registers dock provides MSR, FPSCR and 32 raw 64-bit FPRs, with IEEE double values in tooltips. Floating-point execution starts enabled. Hex edits and Reset support all exposed registers and mapped RAM.
Source uses Keystone PowerPC syntax. Both r3/f1/cr0 and numeric register
operands are accepted. Labels, forward branches, .text/.data, globals, data
alignment and the shared integer/string/space directives are supported. .word
is four bytes, .short two, .quad eight; .float/.double accept numeric
literals. Instructions, data and label metadata use the selected byte order. # and ; begin
comments. Full GNU macros/directives and relocation suffixes are not implemented.
Source execution completes at the end of text; sc reports a missing OS environment.
Open ELF recognizes either byte order for ELF32/EM_PPC and ELF64/EM_PPC64 static ET_EXEC
images. PPC64 ELFv1 entry descriptors resolve to code and initialize R2 (TOC) and
R11 (environment). Function symbols in .opd resolve to code addresses for the
debugger. Direct-entry PPC64 images, including ELFv2, initialize R12 to the global
entry; their code must establish any needed TOC. ABI-unspecified files use .opd
when the entry lies there, otherwise a direct code entry. ELF segment permissions,
BSS and reset follow the common loader. Try powerpc32-addition.elf,
powerpc64-addition.elf or powerpc64-elfv1.elf in examples, with a breakpoint at
entry + 8 to inspect R3 = 15. Regenerate them with
python scripts/generate_powerpc_examples.py.
PowerPC runs a flat bare CPU with Unicorn's virtual TLB. MSR edits/state changes that enable address translation, switch endian or clear PPC64's 64-bit state are rejected. Both byte orders use Unicorn's supported big-endian constructor; MSR LE selects little-endian instruction fetch/data access. Keystone assembles PPC code in big endian, then the frontend reverses each four-byte instruction for LE images; label metadata is decoded separately. This also supports PPC32 LE despite Keystone's constructor limitation. CPU execution and memory bytes remain native to Unicorn. VLE ELF sections are rejected; AltiVec/VSX register presentation, OS/exception handlers, dynamic linking and complete processor-model compatibility are not implemented. CPU instruction support follows Unicorn's models; prefixed eight-byte Power ISA instructions are outside this frontend.
Choose Motorola 68000 in the architecture selector and assemble with F7.
File → Load Motorola 68000 demo produces D0 = 15. Try
examples/m68000-addition.s or m68000-stack.s. Unicorn uses its original 68000
CPU model and Capstone uses the matching decoder. Instructions are big endian,
word aligned, and variable length (up to ten bytes). Run, Step, breakpoints, Run to
Cursor, register/memory editing, source markers and Reset share the debugger.
Keystone has no m68k assembler. Source assembly uses a GNU m68k cross-assembler
and linker, discovered on PATH or through standard Homebrew locations. Install
brew install m68k-elf-binutils on macOS. Other platforms need GNU m68k-elf-as/
m68k-elf-ld or m68k-linux-gnu-as/m68k-linux-gnu-ld. Set
UNICORN_UI_M68K_TOOL_PREFIX to an alternative executable prefix, such as
/opt/m68k/bin/m68k-elf-. Missing tools produce an actionable diagnostic; raw CPU
and ELF loading work without them. Python wheel installation does not bundle GNU
tools. Source assembly tests require this toolchain.
GNU Motorola/Sun syntax accepts bare or %-prefixed registers, # immediates,
moveq #5,d0, addi.l #10,d0, (a0) and GNU address forms. .text, .data,
.bss, .rodata, globals, integer/string/space/alignment directives and native
labels/relocations are supported. .word is two bytes; .long four. |, ; or
// begin comments in this frontend; a leading # line is also a comment. Full
vasm/Devpac dialect compatibility is not supplied. File inclusion and macro/repeat
expansion directives are rejected. Data links at 0x00100000, text at 0x00400000.
The assembler enforces the selected CPU (-m68000, -m68020, etc.).
The 020/030/040 targets enable native 68881-style floating-point assembly.
Native processes have time limits and POSIX CPU/file limits; source and segments
are bounded to 1 MiB. Failed assembly preserves the active session.
The register table presents D0–D7, A0–A7, PC and 16-bit SR. A7 is the active stack
pointer; USP is available in Control / FPU / stack registers. SR tooltips show
X/N/Z/V/C, supervisor state and interrupt priority. Native MOVE SR probes preserve
lazy condition flags when reading snapshots. SR edits use Unicorn's status setter;
user/supervisor stack switching is supported, with both stacks initialized in the
configured debugger stack. Reserved SR bits and debugger trace-mode edits are
rejected. Original 68000 has no FPU, so its FPU/vector panes stay disabled.
TRAP #n reports a missing OS runtime. STOP #sr applies its native SR/privilege
behavior and then reports CPU halted; no following instruction executes. Other
CPU exceptions stop with diagnostics, without routing through a guest vector table.
Source completes at the end of its text. Open ELF recognizes big-endian
ELF32/EM_68K static executables. EF_M68K_M68000 selects 68000; unflagged
classic ELF defaults to 68020. GNU does not distinguish 020/030/040/060 in these
ELF flags: select a later CPU before Open ELF to override the model. ColdFire,
CPU32 flags, LE m68k, dynamic linking and m68k PE/Mach-O are not implemented. The flat software memory map does not reproduce a physical 24-bit bus,
board devices or an Amiga/Atari/Mac OS environment.
examples/m68000-addition.elf is an independent bare-code fixture. Break at
entry + 8 to inspect D0 = 15. Regenerate examples with
python scripts/generate_m68k_examples.py.
Choose Motorola 68020 + FPU, 68030 + FPU, or 68040 + FPU for later instructions, full indexed addressing, and Unicorn's native floating-point engine. Motorola 68060 (integer) uses the matching CPU/decoder but disables FPU source assembly: the installed Unicorn 2.1.4 backend raises line-F exceptions for basic FPU instructions on that model. A separate 68010 model is unavailable in Unicorn. These targets are backend emulations, not complete board or OS implementations.
The Control / FPU / stack registers pane adds SFC, DFC, CACR and (on FPU targets)
FP0–FP7, FPCR and FPSR. Double-click a hex value to edit it. FP registers retain all
80 bits: 16 sign/exponent bits followed by 64 significand bits; memory uses Motorola's
12-byte extended format with two padding bytes. Hover to see a decimal approximation
or FPCR precision/rounding and FPSR condition flags. For example, extended 1.0 is
0x3FFF8000000000000000, and 15.0 is 0x4002F000000000000000. Native FMOVEM probes
preserve guest flags, FP status, GPRs, PC and memory, and do not trigger breakpoints.
Reset restores the complete FPU context along with memory.
Try examples/m68020-fpu.s or examples/m68020-fpu.elf: D0 and FP0 finish at 15,
and result holds an extended value. Normal native FPU arithmetic, comparisons,
conditional branches and memory transfers work. FPIAR is omitted because Unicorn
returns zero and ignores its writes; complete FP exception delivery, all original
68881/68882 instructions, per-chip FPU fidelity and OS trap emulation are not
promised. These limitations follow the installed backend, rather than a Python
reimplementation of the CPU. See the upstream
68k translator
for its FPIAR and floating-point exception limitations.
Choose File → Open ELF. Byte order and entry come from the file, not the
assembly toolbar. The ELF must be static ET_EXEC: ELF32/EM_MIPS,
ELF32/EM_386, ELF64/EM_X86_64, ELF32/EM_ARM, ELF64/EM_AARCH64,
ELF32/EM_PPC, ELF64/EM_PPC64 or ELF32/EM_68K.
x86 and ARM require little endian; PowerPC accepts both byte orders; m68k is big endian. MIPS accepts the
classic MIPS I/II and MIPS32/r2 flag families. MIPS64, MIPS16, microMIPS,
PIE/shared objects, dynamic linking and runtime relocations are rejected.
Non-overlapping segment bytes may share a page; page permissions are combined.
ELF images do not receive synthetic text/data mappings or an automatic end-of-
file stop; use breakpoints/steps to inspect them. The configured stack is added.
Try examples/addition-little.elf or examples/addition-big.elf. These deliberately
small hand-built fixtures (see tests/elf_fixture.py) have main at 0x00020004
and done at 0x00020010. Set a breakpoint at done, then Run to inspect
$t2 = 15. The ELF loops at done until paused; it does not require an OS or
syscall service. Symbol names appear in the disassembly table.
Choose File → Open PE… to load an x86 or ARM .exe, .dll, .sys or .efi image.
Machine headers select x86, x86-64, A32 ARM, Thumb/ARMNT, ARM64 or legacy
little-endian PowerPC32 automatically. Headers and sections map at the
preferred image base with section permissions and zero-filled virtual tails.
The debugger uses the PE entry point and displays export names. Its configured
stack is added; PE stack-reserve fields do not create a Windows process environment.
DLLs without an entry point use the first file-backed executable section for
inspection, with a warning. Images without executable code are rejected.
Try examples/bare-pe-x86-32.exe or examples/bare-pe-x86-64.exe, then Run:
EAX/RAX becomes 15 and HLT stops execution. These are Unicorn-only fixtures,
not programs intended for native Windows execution. Regenerate them with
python scripts/generate_pe_examples.py.
Imports, delay imports, TLS callbacks, DLL initialization and .NET execution are not emulated. Such images can load for inspection, with warnings in the console and program tooltip; calling an unresolved import will not execute a Windows API. Only export symbols are loaded, without PDB/COFF debug information. Certificate and overlay bytes are not mapped, and signatures are not verified.
The Python loader also supports an alternate, 64 KiB-aligned base through
load_pe(path, load_base=...). Rebasing requires a valid relocation table and
supports HIGHLOW for PE32, DIR64 for PE32+, ARM MOV32A and Thumb MOV32T
MOVW/MOVT pairs. PowerPC PE also supports HIGH, LOW and paired HIGHADJ
relocations for split address immediates. Other relocation types are rejected.
The GUI uses the preferred base. The loader validates headers, section ranges,
alignment, directories and entry bytes before replacing the active machine.
Choose File → Open Mach-O… to load an ARM32, ARM64, PowerPC32 or PowerPC64 image. ARM is little endian;
PowerPC follows the Mach-O header byte order (legacy Mac images normally use BE).
Thin and 32/64-bit universal containers are supported. Universal files select an
64-bit slice when available, preferring ARM on equal word widths.
load_macho(path, bits=32, family="ppc") selects a PowerPC32 slice; family="arm"
selects ARM through the Python API. Segment linked addresses, R/W/X permissions, zero-filled tails and
nlist symbol names are retained. __PAGEZERO remains unmapped. Entry comes from
LC_MAIN or LC_THREAD/LC_UNIXTHREAD; ARM thread metadata selects A32/Thumb
state and PowerPC thread metadata supplies SRR0 entry, without
restoring a native process stack. Mach-O Thumb function descriptors guide decoding.
Executable/preload images require an entry; dylibs/bundles without one can load for inspection at their first executable segment, with a warning. Relocatable objects, unsupported CPUs, big-endian ARM and SG_HIGHVM segments are rejected. Mach-O loads at its linked base, without ASLR sliding, binding/rebasing, chained fixups, dyld, TLS, constructors, Darwin syscalls or Apple runtime initialization. Affected images display loader warnings. ARM64e loads for inspection with a warning; pointer authentication/Apple ABI initialization is not supplied. Code signatures are range-checked but not verified. DWARF/dSYM and export tries are not consumed.
Try examples/bare-thumb.macho, examples/bare-arm32.macho,
examples/bare-arm64.macho, or examples/bare-arm-universal.macho.
Break at entry + 4 for Thumb, or entry + 8 for A32/A64, to inspect R0/X0 = 15.
Matching bare-*.exe examples exercise ARM PE loading. These are Unicorn-only
fixtures; do not run them as native host programs. Regenerate all ARM executable
examples with python scripts/generate_arm_examples.py.
Open PE supports legacy Windows NT POWERPC/POWERPCFP PE32 images in little
endian. PE32+ PowerPC is not a standardized supported target. Entry can point to
code or a file-backed eight-byte descriptor holding absolute code/TOC addresses;
the loader resolves descriptor entry and initializes R2, including optional rebasing.
Exports remain faithful to their file addresses, including data/descriptor exports.
Windows imports and startup remain unimplemented.
Try examples/bare-powerpc32.exe or powerpc32-pe-descriptor.exe, and break at
entry + 8 to inspect R3 = 15. Mach-O examples include bare-powerpc32-big.macho,
bare-powerpc64-big.macho, their -little.macho variants, and
bare-powerpc-universal.macho. powerpc32-little.elf and powerpc64-little.elf
exercise LE ELF loading. File → Load PowerPC32/64 little-endian demo runs source
examples directly. These are Unicorn-only fixtures, without a native OS runtime.
Regenerate them with python scripts/generate_powerpc_examples.py.
| Action | Shortcut / interaction |
|---|---|
| Run / Continue | F5 |
| Pause | F6 |
| Backend step | F10 |
| Reset machine | Ctrl/Cmd+Shift+F5 |
| Assemble and load source | F7 |
| Toggle breakpoint | F9 / double-click disassembly / source gutter |
| Run to Cursor | Ctrl/Cmd+F10 / disassembly context menu |
| New / Open / Save source | Standard platform shortcuts |
| Copy address / instruction | Disassembly context menu |
A breakpoint stops before execution. Continue bypasses that particular stop once and can hit the same address again on the next loop iteration. Run to Cursor creates a temporary target, cleared on any stop, explicit pause, fault or reset. The diagnostics panel shows debugger messages; the separate Program Console shows guest stdout and accepts input when a syscall requests it.
Open examples/spim-console.s, then Run. Enter an integer in the Program Console
and press Enter or Send, then enter your name. The example allocates heap memory,
stores/prints the integer, reads/prints a string and exits with status zero.
examples/spim-floats.s demonstrates floating-point input/output. Both examples
work in either byte order. View → Program Console reveals the console when hidden;
an input request also brings it forward automatically.
$v0 |
Service | Arguments / result |
|---|---|---|
| 1 | Print signed integer | $a0 |
| 2 | Print float | $f12 (8 fractional digits, like SPIM) |
| 3 | Print double | Paired $f12/$f13 (18 significant digits) |
| 4 | Print NUL-terminated string | Pointer in $a0 |
| 5 | Read signed decimal integer | Result in $v0 |
| 6 | Read float | Result in $f0 |
| 7 | Read double | Result in paired $f0/$f1 |
| 8 | Read string | Buffer $a0, capacity $a1 including trailing NUL |
| 9 | Allocate heap bytes (sbrk) |
Size $a0, previous break returned in $v0 |
| 10 | Exit | Exit status 0 |
| 11 | Print character | Low byte of $a0 |
| 12 | Read character | Next input byte returned in $v0 |
| 13 | Open file | Filename $a0, flags $a1; descriptor in $v0 |
| 14 | Read bytes | Descriptor $a0, buffer $a1, count $a2; count/EOF/error in $v0 |
| 15 | Write bytes | Descriptor $a0, buffer $a1, count $a2; count/error in $v0 |
| 16 | Close file | Descriptor $a0; 0 on success, -1 on error |
| 17 | Exit with status | Signed $a0 |
PC stays at the syscall while input is pending. Valid input completes it exactly once and advances PC once. Run resumes automatically; input requested by Step completes only that syscall and remains paused. Pause while waiting cancels automatic resume. Invalid numeric input keeps the request active so it can be corrected. Reset/new load cancels old requests, clears console/stdin/exit state, and restores the initial heap. Late input from an old request cannot affect a new run.
Input is line-based in the GUI and receives a newline on Enter. String input
copies at most capacity - 1 bytes, includes a newline if it fits, and always
adds NUL. Capacity <= 0 does nothing; capacity 1 writes only NUL without waiting.
Unused input bytes remain buffered for subsequent reads, including read-character.
Numeric input consumes a whole line and must be valid decimal text; unlike SPIM's
permissive C conversions, invalid prefixes/overflow are reported for correction.
Strings/characters are byte streams rendered as UTF-8 (invalid bytes use replacement
characters). Enter alone supplies a newline to read-character. Input is in a
separate field and is not echoed into guest stdout.
The initial heap is page-aligned beyond static writable segments and at least
0x10010000. Allocations round up to four bytes, map zero-initialized RW pages
as needed, and have a 16 MiB total limit. Negative sizes and collisions are
diagnostics. String transfers/input buffers are capped at 1 MiB. The GUI input
field accepts up to 64 Ki characters; its transcript retains up to 1 Mi characters
and 2,000 blocks. The worker batches output rather than emitting per-character signals.
Floating-point services use Unicorn's real FPU words; doubles require the default
paired-register FR=0 mode, verified using guest loads/stores in both byte orders.
The optional Floating-point registers dock shows raw 32-bit words with change
highlighting. .float/.double accept decimal floating-point literals and
align naturally to four/eight bytes. Full SPIM floating-point pseudos are not implemented.
Open examples/spim-files.s and choose File → Program Directory… before Run.
The example creates demo-output.txt, writes it, reopens/reads it and sends the
bytes to the Program Console. It puts file services in branch delay slots.
Relative filenames default to the opened source/ELF file's directory, or the application working directory for untitled assembly and the built-in demo. Program Directory overrides that default for the app session. The active path appears in the console. Paths must resolve within it; parent escapes and symlinks leading outside fail. Only regular files are opened.
Portable MARS open flags are 0 = read, 1 = create/truncate/write, and
9 = create/append/write. $a2 mode is ignored. Other flag words return -1;
SPIM's host-specific native flag combinations are not portable across OSes.
Guest descriptors start at 3 and are independent of host descriptor numbers.
The limit is 64 open files. File errors, invalid descriptors/counts return -1
(0xFFFFFFFF). Invalid guest buffers fault before file I/O. Read returns a byte
count or 0 at EOF, with no added NUL; Write returns the actual byte count and
preserves embedded NUL bytes. Transfers are capped at 1 MiB.
Descriptor 0 reads console stdin; 1 and 2 write to the Program Console (stdout/stderr are merged). Reserved console descriptors cannot be closed. End input signals EOF: buffered bytes drain first, then fd-0 reads return 0, character reads return -1, numeric reads return zero and string reads produce an empty NUL-terminated string. Reset clears EOF and buffered input.
Exit, natural completion, faults, Reset, new program load, directory changes and application shutdown close owned file handles. Reset does not undo disk writes. File contents persist; descriptors and emulated machine/input state are reset.
Try examples/spim-delay-input.s. Normal branches execute their syscall slot
whether taken or not. A false branch-likely annuls it, including all syscall
side effects and input requests. Jumps, register jumps, link instructions and
floating-point branches retain Unicorn's target/condition/link behavior.
While a delay-slot syscall waits for input, PC points at the syscall and link
registers already reflect the executed branch. Input resumes at the saved native
continuation, not at syscall PC + 4, without executing the branch again. Step
normally executes branch and slot together. A standalone slot breakpoint stops
after the branch's condition and link effects but before slot effects. Continue
or Step executes the slot once and installs the saved continuation. An annulled
likely slot does not hit its breakpoint. Branch conditions are not reevaluated
after edits/input. Original bytes and scratch state are restored before every
visible stop, syscall and input request.
- CPU execution and floating-point arithmetic follow Unicorn's architecture models; SPIM parser compatibility does not guarantee every SPIM CPU extension behaves identically. Branches inside delay slots remain architecturally unpredictable.
- MARS services beyond 17, host-specific file open flags, non-regular files, Linux syscalls, a hardware MMU/TLB and full operating-system emulation are unsupported.
- MIPS double console services retain SPIM's FR=0 pair convention. FR=1 is supported for FPU inspection/editing, not double syscalls.
- Configurable raw-file loading, watchpoints, conditional breakpoints and step-over/out remain future work. Platform setup, automated compatibility checks and native desktop validation status are described in the platform guide.
- ELF/PE/Mach-O files and mapped images are limited to 64 MiB; source to 1 MiB. The SPIM adapter caps each segment at 4 MiB; the Keystone subset caps sections at 1 MiB. Disassembly displays up to 1 MiB per executable segment. Images overlapping segment bytes, the configured stack, or an enabled MMIO mapping are rejected.
- Reset stores full in-memory baseline copies; host file writes persist.
unicorn-ui-demo # headless real-Unicorn LE and BE addition demo
python -m pytest # core and offscreen GUI integration tests
ruff check .
unicorn-ui --smoke-test # native window startup and cooperative shutdownTests cover breakpoint continuation/loops, explicit delay-slot constraints, ELF segment/entry/symbol/BSS validation, assembly/pseudos/data/source diagnostics, SPIM I/O/FPU/heap/exit/error/reset semantics, real binary file I/O in temporary directories, native delay-slot continuation/annulment, and the complete desktop open/assemble/step/run/reset/input and failed-load workflows. Additional tests exercise SPIM grammar and pseudos, endian data/relocations, GUI edits and code refresh, IEEE displays, delay-slot breakpoints, CP0 handler return/BD/EPC, MMIO interrupts, output ordering and side-effect-free memory inspection.
See SPIM analysis and architecture.
The x86 regression suite covers short/near branch relaxation, source spans, calls/returns, stack operations, breakpoint loops/continuation, EFLAGS and SSE register widths, ELF32/ELF64 high-address mapping/BSS/reset, OS-service diagnostics, native assembly isolation, and switching architectures through the GUI.
The PE regression suite covers PE32/PE32+ execution, exports, BSS, permissions, rebasing, malformed images, low-alignment sections, certificates, reset, GUI architecture selection and preserving the session after a failed load.
The ARM regression suite covers A32/A64 stepping, conditional execution, flags, branches/calls/returns, stack operations, SIMD/VFP access, register/memory editing, static ELF and BSS/reset, Thumb/IT/interworking, source locations and GUI switching. Additional tests cover ARM PE MOV32 relocations, thin/universal Mach-O entry/symbol/ segment validation, malformed candidates and preserving sessions after failed loads.
PowerPC tests cover both word widths, branches/LR/CTR/CR, endian data and label relocations, FPR arithmetic/edit/reset, high addresses, ELFv1 descriptors and symbols, direct ELF entries, MSR constraints and GUI mode switching/failed loads.
PowerPC format tests also cover LE floating memory operations, PE descriptors and HIGH/LOW/HIGHADJ rebasing, Mach-O symbols/thread state/universal selection in both byte orders, GUI automatic mode selection and preserving sessions on failed loads.
Motorola 68000 tests cover GNU assembly/diagnostics, variable instruction spans, branch flags, BSR/RTS, SR/USP stack switching, partial register writes, endian load/store, STOP privilege/halt behavior, traps, static ELF/BSS/reset and GUI edits.