Docs  /  Guides

Performance Guide

Repository: https://github.com/ECLS-Studio/rerius


Typical Analysis Times#

The table below is a real measurement (3 runs each, Analysis completed in N seconds self-reported by the tool), not a simulated or estimated figure, but it's from one x86-64 Linux container, not the ARM64 Android device the original draft of this doc claimed. Absolute numbers will differ by CPU, compiler optimization level, and binary; treat the relative shape (which passes cost more) as the more portable takeaway.

Test binary: /bin/ls from this environment: 142 KB, stripped x86-64 ELF, 162 functions detected. Build: gcc -std=c99 -O2 (see BUILDING.md for the full source list).

Analysis Flag Time
Disassembly only (default) ~20-30 ms
Standard full -x ~120 ms
+ Entropy -x -e ~120 ms
+ Recursive descent -x -R ~190 ms
+ Symbolic execution -x -P ~130 ms
+ Decompile -x -D ~155 ms
+ Emulate -x -I ~115 ms
Full everything -X ~760 ms

-X includes every pass (-P -Q -D -I -e -R -V --poly --aire --vm-trace --dsa on top of -x), which is why it's noticeably more than the sum of any two individual passes above: it's paying for all of them together, plus AIRE's cross-referencing pass over everything else's output. It also drops into the interactive shell afterward (see CLI_REFERENCE.md); the timing above was captured with stdin closed (< /dev/null) so the shell exits immediately on EOF.

These numbers are for a single small, non-obfuscated binary. Larger or heavily obfuscated binaries will scale differently, particularly for the modules noted as bottlenecks below (CFG block lookup, recursive-descent visited-set scan).


Performance Characteristics by Module#

Loader (loader.c)#

O(sections + symbols). Dominated by mmap/read: purely I/O bound. Scales linearly with file size.

Xref Builder (analysis.c)#

O(instructions). Single pass through all code sections. Very fast: typically under 2 ms even for large binaries.

CFG Builder (cfg.c)#

O(instructions x 2) for the two-pass algorithm. The pre-pass and main pass each walk instructions once. Block boundary lookup is a linear scan O(nblocks): this could be O(log n) with a sorted array, but nblocks is typically under 8192.

Entropy (entropy.c)#

O(sections x size / step). For a 1 MB .text section with a 64-byte step: roughly 15,000 window evaluations, each O(256). Fast in practice.

Recursive Descent (entropy.c)#

O(reachable_instructions x queue_size). BFS with a visited set: each instruction is decoded at most once. Dominates runtime for large binaries with many functions.

Symbolic Execution (symexec.c)#

O(function_instructions x pool_depth). The expression pool is bounded at 1024 nodes per function. Fast for small functions, slower for functions with many nested operations.

Emulator (emulate.c)#

O(steps). Hard limit of 8192 instructions per function. Page allocation is O(npages) on first write, bounded at 64 pages.


JS API Performance#

The N-API boundary adds negligible overhead even for large return values: arrays of structs are returned by value. The dominant cost is always the C-side analysis, not the JS/C boundary.

Repeated analysis on the same binary:

withBinary opens and closes the binary on every call. For a service that analyzes the same binary repeatedly, cache the loaded handle:

const cache = new Map();

function getCached(filePath) {
    const bin = rerius.load(filePath);
    const key = bin.sha256;
    if (cache.has(key)) { bin.close(); return cache.get(key); }
    cache.set(key, bin);
    return bin;
}

This avoids re-parsing ELF headers, re-loading symbols, and re-building xrefs on every request.


Memory Usage#

Struct sizes below are sizeof() measured directly (via a small probe compiled against include/core/dax.h), not estimated: the original draft of this table understated several of these by 2-20x, most notably the base struct.

Component Actual size Notes
Base dax_binary_t struct 174,592 bytes (~171 KB) Dominated by the embedded sections[128] array and other fixed-size fields: this is allocated once per binary regardless of file size
Raw file data (bin->data) = file size
Symbols array nsymbols × 1056 bytes
Xrefs array nxrefs × 24 bytes
Functions array nfunctions × 544 bytes
Blocks array nblocks × 240 bytes
Unicode strings nustrings × 528 bytes

Practical implication: because the base struct alone is ~171 KB, a service holding many dax_binary_t handles open concurrently (e.g. an unbounded cache in a long-running REST server) uses meaningfully more baseline memory per handle than the file size alone would suggest: budget for it if you're caching a large number of binaries.

For /bin/ls (142 KB, 8 symbols, 162 functions, 2928 blocks, 4293 xrefs) with -X (every pass): peak RSS measured via /proc/<pid>/status (VmHWM) in this environment was ~2 MB. Static struct sizes account for roughly 171 KB (base) + 142 KB (file) + 101 KB (xrefs) + 86 KB (functions) + 686 KB (blocks) ≈ 1.2 MB, so an additional ~0.8 MB comes from decoder/analysis scratch buffers, the C library, and allocator overhead not accounted for by the arrays above.


Profiling#

# Linux with perf (redirect stdin - -X drops into the interactive shell otherwise)
perf stat ./rerius -X /bin/ls < /dev/null

# Linux with Valgrind callgrind
valgrind --tool=callgrind ./rerius -x /bin/ls
callgrind_annotate callgrind.out.*

# Time individual modules
time ./rerius -x /bin/ls > /dev/null
time ./rerius -x -R /bin/ls > /dev/null

Known Bottlenecks#

Block boundary lookup in CFG: find_block_by_addr() is an O(n) linear scan. For binaries with more than 8000 blocks this becomes noticeable. A hash map or sorted array with binary search would reduce this to O(1) or O(log n).

Recursive descent visited set: rda_is_visited() is an O(n) linear scan over up to 65,536 entries. A hash set would reduce this to O(1).

Large .dynstr UTF-16LE scan: The unicode scanner skips known-bad sections, but for binaries with many rodata sections the scan is still O(section_size). Pre-filtering by section type reduces this significantly for most binaries.

These are tracked for improvement in future releases.

Edit this page on GitHub Source: docs/PERFORMANCE.md · Rerius v1.0.0
On this page
Performance Guide Typical Analysis Times Performance Characteristics by Module Loader (loader.c) Xref Builder (analysis.c) CFG Builder (cfg.c) Entropy (entropy.c) Recursive Descent (entropy.c) Symbolic Execution (symexec.c) Emulator (emulate.c) JS API Performance Memory Usage Profiling Known Bottlenecks
ESC
↑↓ navigate openesc close