Skip to content

feat(vm): [Phase 1 & 2] Dalvik VM interpreter — Interpreter Basic & Core Instruction Set Support#2

Open
haeter525 wants to merge 12 commits intoev-flow:mainfrom
haeter525:android_emulator_enhance
Open

feat(vm): [Phase 1 & 2] Dalvik VM interpreter — Interpreter Basic & Core Instruction Set Support#2
haeter525 wants to merge 12 commits intoev-flow:mainfrom
haeter525:android_emulator_enhance

Conversation

@haeter525
Copy link
Copy Markdown
Member

@haeter525 haeter525 commented Apr 12, 2026

Key Changes

Phase 1 - Interpreter Basic

Execution Engine

  • engine.py: DalvikVM main loop with call-frame stack and invoke handling
  • errors.py: DalvikVM runtime error types
  • cmd_run.py: new dextrace run subcommand to run the Dalvik VM interpreter
  • _io.py: shared DEX/APK loader; handles APK with missing classes.dex and malformed DEX input

Register File

Call Stack & Frame

  • call_frame.py: per-call register snapshot/restore for callee isolation
  • state.py: VMState dataclass with pending_result lifecycle for invoke/move-result

Phase 2 - Core Instruction Set Support

  • handlers/: arithmetic, array, branch, compare, field, move, type_conv opcode handlers

How to use

Installation

git clone -b android_emulator_enhance https://github.com/haeter525/DexTrace.git
cd DexTrace
pip install -e .

Phase 1 — execute const/16 v0, #42 → return-void

$ dextrace run tests/fixtures/samples/p1_const_return.dex --entry 'Lp1;->main()V' --dump-regs
return: void
registers: v0=42
  • the register value v0 should be 42
  • the return value should be void

Phase 2 — execute recursive Fibonacci

$ dextrace run tests/fixtures/samples/p2_fib_recursive.dex --entry 'Lp2/Fib;->fib(I)I' --arg 10
return: 55
  • the return value should be 55

Tests

  • All tests pass: pytest tests
  • Phase 1 and Phase 2 commands above produce the expected output

haeter525 and others added 11 commits April 11, 2026 20:45
- vm/decoder.py: walk_method() static walk adapter over DalvikDisassembler
  DecoderTables type alias, MethodNotFound exception
- cli/cmd_trace.py: dextrace trace subcommand with JSON envelope output
  matching cmd_disasm schema (version/format/source/method/instructions)
  stderr-only errors, exit codes 0/1/3
- cli/main.py: wire trace subcommand
- tests/fixtures/samples/p1_const_return.dex: Lp1;->main()I fixture
  (const/16 v0, 42; return v0) built by tools/gen_p1_fixture.py
- tests/test_vm_trace_p1.py: 9 tests — unit + CLI integration

Verification: result['instructions'][0]['mnemonic'] == 'const/16'  ✓
59/59 tests pass, 0 regressions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
wrap_method() now catches ValueError/struct.error from DexResolver and
DalvikDisassembler and re-raises as DexParseError.
cmd_trace.py catches DexParseError and prints [ERROR] message, exits 3.

Before: python traceback to stderr, exit 1
After:  [ERROR] malformed DEX: File too small to contain a valid DEX header, exit 3

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
apk_reader.read_file() raises KeyError when the entry doesn't exist.
The old `if not dex_bytes` guard was dead code — the exception happens
before it. Fix: check list_entries() before reading.

Before: KeyError traceback to stderr, exit 1
After:  [ERROR] APK does not contain classes.dex, exit 3

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ISSUE-002: malformed DEX must raise DexParseError (not raw ValueError traceback)
ISSUE-003: APK without classes.dex must exit 3 with [ERROR] (not KeyError)

Found by /qa on 2026-04-11
Report: .gstack/qa-reports/qa-report-dextrace-2026-04-11.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add dextrace run subcommand: executes a method in the Dalvik VM
  interpreter with static analysis semantics (no JNI, no heap)
- Add vm/engine.py: step-budget interpreter loop with opcode dispatch
- Add vm/handlers/: arithmetic, branch, compare, field, move, array,
  type_conv instruction handlers
- Add vm/state.py, call_frame.py, register_file.py, errors.py, int_ops.py
- Add p2 fixture (Fib recursive DEX) + gen_p2_fixture.py
- Add Phase 2 tests: test_vm_run_p1, test_vm_run_p2, tests/vm/ unit suite

Review fixes (from /review):
- decoder.py: widen DexParseError catch to include IndexError, KeyError,
  OverflowError — real malformed DEX triggers opcode table KeyError
- cmd_trace.py: add is_file() guard to prevent IsADirectoryError traceback
- cmd_trace.py: stage BadZipFile catch, flags filter, resolve() path fix,
  REVIEW-001 regression test (non-ZIP APK → exit 3)
- decoder.py: remove unused DecoderTables type alias + dead imports

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extract load_dex_bytes() to shared cli/_io.py and use it from both
cmd_trace and cmd_run. dextrace run now accepts .apk files, extracts
classes.dex, and returns clean errors when the APK is malformed or
missing classes.dex. Previously gave "expected a .dex file, got: '.apk'".

Also fixes ISSUE-005: add is_file() guard to cmd_run so a directory
named *.dex gives exit 1 (user error) instead of exit 3 (parse error)
with an internal OSError traceback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ISSUE-004: APK files rejected with wrong error in dextrace run
ISSUE-005: directory-as-input gives wrong exit code (3 instead of 1)

Found by /qa on 2026-04-11

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- cmd_run.py: suppress too-many-return-statements/branches on run()
- move.py: suppress unused-argument on handle_nop (intentional no-op)
- register_file.py: suppress protected-access in snapshot() (same class)
- gen_p1/p2_fixture.py: rename HEADER_SIZE→header_size, _uleb128 out→buf
- test_handlers.py: move imports to module top-level, remove unused imports

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
gen_p1 and gen_p2 intentionally share boilerplate structure as
standalone tools building different DEX test fixtures.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@haeter525 haeter525 marked this pull request as draft April 12, 2026 04:17
@haeter525 haeter525 changed the title feat(vm): Dalvik VM interpreter — trace + run commands (Phase 1 & 2) feat(vm): [Phase 1 & 2] Dalvik VM interpreter — Interpreter Basic & Core Instruction Set Support Apr 12, 2026
- cmd_run.py: add --dump-regs flag to print non-zero register values
  after execution; add _print_registers() helper
- engine.py: save final VMState after run(); expose final_registers
  property for post-execution register inspection
- gen_p1_fixture.py: change method from ()I/return to ()V/return-void
  so Phase 1 demo exercises const/16 + return-void with --dump-regs
- Regenerate p1_const_return.dex with updated bytecode and proto
- Update all tests to use new entry sig Lp1;->main()V and void semantics

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@haeter525 haeter525 marked this pull request as ready for review April 12, 2026 06:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant