Porting Guide
How to add support for new CPU architectures and binary formats to Rerius.
Repository: https://github.com/ECLS-Studio/rerius
Adding a New Architecture#
Step 1 - Decoder#
Create src/arch/<arch>_decode.c and include/arch/<arch>.h:
/* include/mips.h */
#ifndef DAX_MIPS_H
#define DAX_MIPS_H
#include <stdint.h>
typedef struct {
uint32_t raw;
uint64_t address;
char mnemonic[32];
char operands[256];
int length; /* always 4 for MIPS32, variable for MIPS16 */
} mips_insn_t;
int mips_decode(const uint8_t *buf, size_t len, uint64_t addr, mips_insn_t *insn);
void mips_reg_name(int r, char *out);
#endif
mips_decode() must:
- Populate all fields of mips_insn_t
- Return instruction byte length (> 0) on success, ≤ 0 on invalid
- Be safe on any input (no crashes on garbage bytes)
Step 2 - Add to dax_arch_t enum (include/core/dax.h)#
typedef enum {
ARCH_X86_64, ARCH_ARM64, ARCH_RISCV64,
ARCH_MIPS32, /* ← add here */
ARCH_UNKNOWN
} dax_arch_t;
Step 3 - Classification (src/analysis/analysis.c)#
dax_igrp_t dax_classify_mips(const char *mnem) {
if (!strcmp(mnem,"jal")||!strcmp(mnem,"jalr")) return IGRP_CALL;
if (!strcmp(mnem,"j") ||!strcmp(mnem,"jr")) return IGRP_BRANCH;
if (!strcmp(mnem,"jr") && /* ra */) return IGRP_RET;
/* ... */
return IGRP_UNKNOWN;
}
Step 4 - Disassembly output (src/arch/disasm.c)#
int dax_disasm_mips(dax_binary_t *bin, dax_opts_t *opts, FILE *out) {
/* same structure as dax_disasm_arm64 */
}
Step 5 - CFG builder (src/analysis/cfg.c)#
Add branch/call/ret classification to the CFG pass. The two-pass algorithm works the same for all architectures.
Step 6 - Function detection (src/analysis/analysis.c)#
Add prologue detection in dax_func_detect(). Look for the architecture's function entry patterns (stack frame setup, callee-saved register saves).
Step 7 - Wire up in loader.c and main.c#
/* loader.c - ELF machine type detection */
case EM_MIPS: bin->arch = ARCH_MIPS32; break;
/* main.c - dispatch to disassembler */
if (bin.arch == ARCH_MIPS32) dax_disasm_mips(&bin, &opts, stdout);
Step 8 - Add to setup.sh and build_js.sh#
There's no SRCS list in the top-level Makefile: it just delegates to setup.sh, which is where the actual source list lives:
# setup.sh, SRCS="..."
SRCS="... src/arch/mips_decode.c ..."
# build_js.sh LIB_SRCS
LIB_SRCS="... src/arch/mips_decode.c"
Step 9 - N-API disasmJson (js/src/rerius_napi.c)#
Add a branch to ndx_disasm_json():
} else if (bin->arch == ARCH_MIPS32) {
while (off < sz) {
mips_insn_t insn;
memset(&insn, 0, sizeof(insn));
int len = mips_decode(code+off, sz-off, base+off, &insn);
if (len <= 0) { off++; continue; }
/* ... build napi object ... */
off += (size_t)insn.length;
}
}
Step 10 - Tests and documentation#
- Add test binary or pattern to
js/test/basic.jsif available - Document in
docs/ARCHITECTURE.md - Add to format/arch table in
README.md
Adding a New Binary Format#
Example: WASM support#
Step 1 - Header file (include/wasm.h)#
Define the file magic and key structures:
#define WASM_MAGIC 0x6D736100U /* '\0asm' as LE uint32 */
#define WASM_VERSION 0x01000000U
typedef struct {
uint32_t magic;
uint32_t version;
} wasm_header_t;
Step 2 - Parser (src/formats/wasm.c)#
int dax_parse_wasm(dax_binary_t *bin) {
/* populate bin->sections[], bin->arch, bin->fmt, etc. */
bin->arch = ARCH_UNKNOWN; /* or add ARCH_WASM */
bin->os = DAX_PLAT_UNKNOWN;
return 0;
}
Must populate at minimum:
- bin->sections[] and bin->nsections
- bin->arch, bin->fmt, bin->os
- bin->entry, bin->base
- bin->sha256 (call dax_compute_sha256(bin) early)
Step 3 - Format enum (include/core/dax.h)#
typedef enum {
FMT_ELF32, FMT_ELF64, FMT_PE32, FMT_PE64, FMT_RAW,
FMT_WASM, /* ← add here */
FMT_UNKNOWN
} dax_fmt_t;
Step 4 - Detection in loader.c#
uint32_t magic32 = *(uint32_t *)bin->data;
if (magic32 == 0x464C457F) return dax_parse_elf(bin);
else if (magic16 == PE_DOS_MAGIC) return dax_parse_pe(bin);
else if (magic32 == MACHO_MAGIC_64_LE || ...) {
dax_compute_sha256(bin);
return dax_parse_macho(bin);
}
else if (magic32 == WASM_MAGIC) { /* ← add here */
dax_compute_sha256(bin);
return dax_parse_wasm(bin);
}
Step 5 - String representation (src/core/loader.c or src/cli/main.c)#
const char *dax_fmt_str(dax_fmt_t f) {
switch (f) {
case FMT_WASM: return "WASM";
/* ... */
}
}
Checklist#
When opening a PR for a new arch or format:
- [ ] Decoder handles all byte sequences without crashing (fuzz with AFL++)
- [ ]
dax_arch_str()/dax_fmt_str()updated - [ ]
dax_classify_<arch>()returns correct group for at least call/branch/ret - [ ]
disasmJsonN-API path added (or returns empty array with comment) - [ ]
setup.shSRCSupdated (not the top-levelMakefile: it has no source list of its own) - [ ]
build_js.shLIB_SRCSupdated - [ ]
README.mdformat/arch table updated - [ ]
ARCHITECTURE.mdmodule description added - [ ]
CHANGELOG.mdentry added - [ ]
js/test/basic.jspasses (all 27 existing tests unaffected)
Key Invariants to Preserve#
Any new code must not break:
- Zero external dependencies - no new
#includefor non-standard libraries - C99 strict - compiles with
clang -std=c99 -Wno-unusedand GCC 7+ - No segfaults on malformed input - all bounds checks before array/pointer access
sections().length > 0on any recognized binary - or return a clear errorfunctions().length >= 1on any binary with detectable code sections - use the section entry point as fallback
docs/PORTING.md · Rerius v1.0.0