The only supported way to run QBDITrace is through qbditrace_runner.py. Do not load qbditrace.js directly with Frida—the agent requires configuration injected via script.post().
Terminal window
# Trace a function by offset in the main executable
python3tools/qbditrace/qbditrace_runner.py\
--targetcom.example.app\
--entry0xEE91EC\
--output/var/mobile/Documents/trace.bin
# Trace an exported symbol
python3tools/qbditrace/qbditrace_runner.py\
--targetcom.example.app\
--entry_CCCrypt\
--output/var/mobile/Documents/cccrypt.bin
# Trace a function inside a specific framework module
Explicit Frida device id (e.g. an Android serial). Required when several USB devices are attached — otherwise the first one wins, which may be the wrong phone
--android
off
Use Android default paths; dylib switches to /data/local/tmp/libqbditrace.so
Emit a full register snapshot every N instructions
--ring-bytes
0 (default 64 MiB)
Per-thread ring buffer size
--no-embed-code
off
Disable embedded code table; CFG/loop analysis in Tenet degrades without an external image
--pc-delta
off
Enable v5 PC-delta encoding: 2-byte signed deltas shrink INST headers by ≈60%
--fpr
off
Enable v7 FPR/NEON recording: FPR diffs per instruction + 512 B FPR snapshot per anchor
--no-compress
off
Disable default 1 MiB block zstd compression
--exclude-range
none
Exclude address ranges (format: START-END or START:SIZE, hex static offsets). Up to 8 ranges. Code inside excluded ranges runs natively with zero trace overhead
--snapshot-range
none
Opt-in memory snapshot of an absolute runtime range START END (decimal or 0x hex), captured once at configure time (v9 REC_MEMIMG). Repeatable, up to 8 ranges. Aim at the module’s writable data sections (__DATA/__bss) — do not dump rodata (Tenet’s --image fallback already covers it), heap, or stack. Tenet resolves bytes from the write/read logs first and uses the snapshot only as an explicitly-marked base layer
--sampling
off
Scout mode: bare PC stream only (no registers, memory, or sequence events). ≈80%+ size reduction while loop counts and back-edges remain exact. Implies --pc-delta and a large anchor interval. See Scout workflow below
Terminal window
# Snapshot the module's writable data segment before tracing
Write bytes to target process memory before tracing. Format: ADDR:HEXBYTES (absolute runtime) or +OFFSET:HEXBYTES (module-base offset). May be specified multiple times. Original bytes are restored after the trace completes. Use to clear cache flags, modify branch conditions, etc.
Terminal window
# Clear a 4-byte cache flag at module offset +0x1abc
Intercept pthread_create and record child threads whose start routine is inside the traced module. Each child runs inside its own QBDI VM via qbditrace_run_thread() and records into the same trace file, separated by THREAD markers
--threads-no-filter
off
Trace child threads regardless of whether the start routine is inside the target module
--java-entry fully.qualified.Class.method uses a Java method as the trace trigger instead of hooking a native entry. The agent hooks the Java method (via Java.perform); when the app calls it, the arguments are materialized and the native entry is invoked inside the VM. Java int/long become immediates, String becomes a UTF-8 buffer, and other objects are passed as jobject handles. Requires --android; mutually exclusive with --invoke.
Hook GCD dispatcher and synchronously re-invoke discovered block invoke pointers (each in its own trace file). Not a true multi-thread trace — prefer --trace-threads on Android for real multi-thread capture
--gcd-no-filter
off
Trace all blocks regardless of whether invoke is inside the target module
--gcd-max
0 (unlimited)
Maximum number of extra block executions to capture
No instructions recorded: verify runtime addresses include the ASLR slide and that the target runs on the VM-controlled thread.
Frida cannot connect: the Python bindings and device service must use matching versions.
Trace rejected by Tenet: check for a valid magic number, a supported format version with consistent layout flags (including rejecting versions older than v4), and a non-corrupt file with complete records.
Missing register diffs (REG_DIFF): the trace still opens and indexes normally, but register queries, taint propagation, and other analyses that depend on register state are degraded or unavailable.
Missing embedded code table (HAS_CODE): the trace still opens; disassembly-dependent analyses such as CFG and pattern recognition require an external image supplied with --image.
Missing asynchronous work: capture the worker entry separately, make the target path synchronous, or use --trace-threads (Android) / --trace-gcd (iOS).
Android app cannot write the output: apps cannot create files under /data/local/tmp (directory DAC denies the app uid). Write into the app data directory instead (/data/data/<package>/…) and pull with su. Likewise, an app cannot dlopen a .so from /data/local/tmp — bundle native libs inside the APK.
Several phones attached: pass --device-id with the explicit serial; otherwise the loader may pick the wrong USB device.
Crash near the target: the agent dumps the 2048-entry PC ring to <output>.pcring and flushes the instructions recorded before the fault, so the partial trace is still analyzable. qbditrace_dump_pcring can also be invoked manually.