Compare commits

...
11 Commits
Author SHA1 Message Date
pancake 5d77df7091 Release 1.6.2 2026-11-06 01:26:23 +01:00
pancake 722793f084 Fix -Wsign-compare in decompiler output truncation check 2026-11-05 12:51:12 +01:00
pancake 0b53e148fb Request trycatch script output in tests 2026-10-20 08:31:28 +02:00
pancake a93ca6767d Release 1.6.0 2026-10-19 17:29:47 +02:00
pancake e3f5b7e560 Use r2 6.2.4 in the ci 2026-10-19 17:29:14 +02:00
pancake 47279d355c Pin the actions 2026-10-19 17:28:11 +02:00
Abhi 3b08970ce8 Fix bindist triplet naming on runners with older r2pm
The 1.5.2 release shipped r2hermes-6.2.2---.zip for unix targets:
the bindist job pins radare2 6.2.2 whose r2pm lacks R2PM_OS/ARCH/BITS.
Fall back to uname (darwin/linux, x86/arm) so all targets get proper
triplet names.
2026-10-18 11:18:58 +05:30
pancake 0566ca00a8 Last minute fix for a missing string flag 2026-10-08 09:57:37 +02:00
pancake 4bfde1f33f Add -A to sync with r2* usage 2026-10-08 09:54:39 +02:00
pancake 6a243fbc1e Fix slp address location, honor io.va, baddr and construction instruction 2026-10-08 09:46:53 +02:00
pancake e073f29cb3 Register as r2hermes pkg 2026-10-06 16:55:28 +02:00
17 changed files with 499 additions and 88 deletions

No files matched your search

-3
View File
@@ -4,8 +4,5 @@ updates:
directory: "/"
schedule:
interval: "weekly"
commit-message:
prefix: "##build "
prefix-development: "##build "
labels:
- buildsystem
+18 -18
View File
@@ -1,7 +1,7 @@
name: CI
env:
R2V: 6.2.2
R2V: 6.2.6
permissions:
contents: write
@@ -19,14 +19,14 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Install build deps
run: sudo apt-get update -y && sudo apt-get install -y build-essential autoconf automake libtool pkg-config
- name: Install radare2
id: r2
uses: radareorg/github-actions@v3
uses: radareorg/github-actions@4ae27a8b29a049ac0fd5119f6eda37ecfc64ead8 # v3
with:
version: ${{ env.R2V }}
- name: Build library
@@ -57,7 +57,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
@@ -66,7 +66,7 @@ jobs:
- name: Install radare2
id: r2
uses: radareorg/github-actions@v3
uses: radareorg/github-actions@4ae27a8b29a049ac0fd5119f6eda37ecfc64ead8 # v3
with:
version: ${{ env.R2V }}
@@ -74,7 +74,7 @@ jobs:
run: make -C dist/debian SUDO=
- name: Upload Debian package artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: debian-package
path: dist/debian/*.deb
@@ -86,10 +86,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup Python
uses: actions/setup-python@v6
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: 3.12.x
@@ -100,7 +100,7 @@ jobs:
- name: Install radare2
id: r2
uses: radareorg/github-actions@v3
uses: radareorg/github-actions@4ae27a8b29a049ac0fd5119f6eda37ecfc64ead8 # v3
with:
version: ${{ env.R2V }}
@@ -140,7 +140,7 @@ jobs:
Compress-Archive -Path bindist\* -DestinationPath "r2hermes-$env:R2V-windows-x86-64.zip" -Force
- name: Upload Windows package artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: windows-package
path: r2hermes-*.zip
@@ -155,10 +155,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install radare2
uses: radareorg/github-actions@v3
uses: radareorg/github-actions@4ae27a8b29a049ac0fd5119f6eda37ecfc64ead8 # v3
with:
version: ${{ env.R2V }}
@@ -166,7 +166,7 @@ jobs:
run: make bindist
- name: Upload r2pm binary artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: bindist-${{ matrix.os }}
path: r2hermes-*.zip
@@ -180,7 +180,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
@@ -192,26 +192,26 @@ jobs:
ls -l libhbc*
- name: Download Debian package artifact
uses: actions/download-artifact@v8
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: debian-package
path: release-artifacts
- name: Download Windows package artifact
uses: actions/download-artifact@v8
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: windows-package
path: release-artifacts/windows
- name: Download r2pm binary artifacts
uses: actions/download-artifact@v8
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
pattern: bindist-*
merge-multiple: true
path: release-artifacts/bindist
- name: Create GitHub Release
uses: softprops/[email protected].1
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
with:
tag_name: ${{ needs.build.outputs.tag }}
files: |
+2 -2
View File
@@ -14,7 +14,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
@@ -23,7 +23,7 @@ jobs:
- name: Install radare2
id: r2
uses: radareorg/github-actions@v3
uses: radareorg/github-actions@4ae27a8b29a049ac0fd5119f6eda37ecfc64ead8 # v3
with:
version: ${{ env.R2V }}
+2 -2
View File
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Checkout TinyCC repository
@@ -49,7 +49,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Install build dependencies
+4 -4
View File
@@ -14,10 +14,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup Python
uses: actions/setup-python@v6
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: 3.12.x
@@ -28,7 +28,7 @@ jobs:
- name: Install radare2
id: r2
uses: radareorg/github-actions@v3
uses: radareorg/github-actions@4ae27a8b29a049ac0fd5119f6eda37ecfc64ead8 # v3
with:
version: ${{ env.R2V }}
@@ -60,7 +60,7 @@ jobs:
Compress-Archive -Path install\* -DestinationPath r2hermes-windows-x64.zip -Force
- name: Upload artifacts
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: windows-package
path: r2hermes-windows-x64.zip
+1
View File
@@ -1,3 +1,4 @@
.pmai/
build/
bin/
third_party/
+27 -1
View File
@@ -119,7 +119,33 @@ user-install user-uninstall:
$(MAKE) -C src/r2 $@
# Binary package for r2pm -bi: plugins/ and bin/ for this r2 version and platform
BINDIST_ZIP=r2hermes-$(shell r2 -qv)-$(shell r2pm -H R2PM_OS)-$(shell r2pm -H R2PM_ARCH)-$(shell r2pm -H R2PM_BITS).zip
# Binary package for r2pm -bi: plugins/ and bin/ for this r2 version and platform
# R2PM_OS/ARCH/BITS require a recent r2pm; fall back to uname when missing
R2PM_OS_SH=$(shell r2pm -H R2PM_OS 2>/dev/null)
R2PM_ARCH_SH=$(shell r2pm -H R2PM_ARCH 2>/dev/null)
R2PM_BITS_SH=$(shell r2pm -H R2PM_BITS 2>/dev/null)
ifeq ($(R2PM_OS_SH),)
ifeq ($(shell uname -s),Darwin)
R2PM_OS_SH=darwin
else
R2PM_OS_SH=linux
endif
endif
ifeq ($(R2PM_ARCH_SH),)
ifeq ($(shell uname -m),x86_64)
R2PM_ARCH_SH=x86
else ifeq ($(shell uname -m),aarch64)
R2PM_ARCH_SH=arm
else ifeq ($(shell uname -m),arm64)
R2PM_ARCH_SH=arm
else
R2PM_ARCH_SH=$(shell uname -m)
endif
endif
ifeq ($(R2PM_BITS_SH),)
R2PM_BITS_SH=64
endif
BINDIST_ZIP=r2hermes-$(shell r2 -qv)-$(R2PM_OS_SH)-$(R2PM_ARCH_SH)-$(R2PM_BITS_SH).zip
bindist: all
$(MAKE) -C src/r2
+18
View File
@@ -81,12 +81,30 @@ Usage: pd:h[subcommand]
[0x00000000]> r2hermes-?
Usage: r2hermes[-arg] # see also pd:h for decompilation
r2hermes-h - help message (same as r2hermes-?, see pd:h? too)
r2hermes-A - Import symbols and entrypoints
r2hermes-AA - Also import strings and SLP literal comments/xrefs
r2hermes-AAA - Also analyze bytecode references
r2hermes-AAAA - Also perform full analysis (aaa)
r2hermes-E[jq] - List direct eval instruction sites (j=JSON, q=addresses only)
r2hermes-H - Show file information and hash status
r2hermes-L[?] - SLP literal cache: list/scan/reset/format/toggle
r2hermes-S[jr?] - emit SBOM from SLP literals (j=CycloneDX JSON, r=raw input)
```
The analysis levels are cumulative, like `r2flutter-A` and `r2unity-A`.
Use `r2hermes-A` to import symbols and entrypoint flags, and `r2hermes-AA` to
also import string flags and metadata, plus SLP literal flags, comments, and
constructor references. `r2hermes-AAA` adds reference analysis over each
function's bytecode range; `r2hermes-AAAA` additionally runs `aaa` to analyze
functions and control flow. Addresses follow `io.va`, and the commands preserve
the current seek and flagspace. Use `r2hermes-A?` for analysis help.
`r2hermes-L` lists the constructor instruction addresses after `xrefs=`.
Object literal addresses point to their serialized values; their keys or shapes
can be shared by objects with different values. Pools can also share values, so
`r2hermes-Lx <addr>` lists every matching entry. Use the constructor addresses
on each row to navigate to that specific object. Text output follows `io.va`.
### Examples
```bash
+1 -1
View File
@@ -40,7 +40,7 @@ typedef struct {
u32 num_items;
u32 primary_id; /* array_id (array) or keys/shape id (object) */
u32 secondary_id; /* values_id for objects, 0 for arrays */
u32 paddr; /* absolute file offset of the literal data in the SLP pool (0 if unknown) */
u32 paddr; /* absolute file offset of array data or object values (0 if unknown) */
char *formatted; /* cached JS literal text; NULL if not yet formatted */
u32 *xref_addrs; /* instruction addresses that construct this literal */
u32 xref_count;
+1 -1
View File
@@ -1,7 +1,7 @@
project(
'r2hermes',
'c',
version: '1.5.2',
version: '1.6.2',
license: 'MIT',
default_options: [
'c_std=c11',
+1 -1
View File
@@ -2962,7 +2962,7 @@ Result _hbc_output_code(HermesDecompiler *state, DecompiledFunctionBody *functio
for (HbcTokenString *si_iter = R_VEC_START_ITER (&function_body->statements); si_iter != R_VEC_END_ITER (&function_body->statements); si_iter++) {
size_t si = (size_t) (si_iter - R_VEC_START_ITER (&function_body->statements));
/* Stop when budget exhausted or earlier phase (pass2) flagged it. */
if (state->output_truncated || (state->options.max_output_bytes > 0 && r_strbuf_length (out) >= state->options.max_output_bytes)) {
if (state->output_truncated || (state->options.max_output_bytes > 0 && r_strbuf_length (out) >= (size_t)state->options.max_output_bytes)) {
state->output_truncated = true;
if (!state->truncation_marker_emitted) {
state->truncation_marker_emitted = true;
+5 -14
View File
@@ -133,9 +133,8 @@ static HBCLiteralEntry *cache_find(HBCLiteralCache *c, HBCLiteralKind kind, u32
return NULL;
}
/* Compute paddr for an entry based on its kind/primary_id. For v97+ objects,
* primary_id is a shape index — resolve via the shape table. */
static u32 entry_paddr(HBC *hbc, HBCLiteralKind kind, u32 primary_id) {
/* Anchor objects at their values, not their shared keys/shape. */
static u32 entry_paddr(HBC *hbc, HBCLiteralKind kind, u32 primary_id, u32 secondary_id) {
const HBCReader *r = &hbc->reader;
if (kind == HBC_LIT_ARRAY) {
if (!r->arrays) {
@@ -146,18 +145,10 @@ static u32 entry_paddr(HBC *hbc, HBCLiteralKind kind, u32 primary_id) {
}
return r->arrays_paddr + primary_id;
}
/* object */
if (r->header.version >= 97 && r->object_shapes && primary_id < r->object_shape_count) {
u32 key_off = r->object_shapes[primary_id].key_buffer_offset;
if (r->object_keys && key_off < r->header.objKeyBufferSize) {
return r->object_keys_paddr + key_off;
}
if (!r->object_values || secondary_id >= r->header.objValueBufferSize) {
return 0;
}
if (!r->object_keys || primary_id >= r->header.objKeyBufferSize) {
return 0;
}
return r->object_keys_paddr + primary_id;
return r->object_values_paddr + secondary_id;
}
static Result cache_get_or_create(HBC *hbc, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id, bool format, HBCLiteralEntry **out_entry) {
@@ -182,7 +173,7 @@ static Result cache_get_or_create(HBC *hbc, HBCLiteralKind kind, u32 num_items,
e->num_items = num_items;
e->primary_id = primary_id;
e->secondary_id = secondary_id;
e->paddr = entry_paddr (hbc, kind, primary_id);
e->paddr = entry_paddr (hbc, kind, primary_id, secondary_id);
if (format) {
char *txt = NULL;
Result fr = format_raw_impl (&hbc->reader, kind, num_items, primary_id, secondary_id, &txt);
+153 -39
View File
@@ -741,7 +741,8 @@ static void register_r2_artifacts(RCore *core, const HBCLiteralEntry *e) {
if (!e->paddr) {
return;
}
ut64 vaddr = HBC_VADDR_BASE + (ut64)e->paddr;
ut64 base = r_config_get_b (core->config, "io.va")? HBC_VADDR_BASE: 0;
ut64 vaddr = base + e->paddr;
char flag_name[64];
snprintf (flag_name, sizeof (flag_name), "%s0x%x", lit_kind_prefix (e->kind), e->paddr);
r_flag_set (core->flags, flag_name, vaddr, 1);
@@ -749,7 +750,7 @@ static void register_r2_artifacts(RCore *core, const HBCLiteralEntry *e) {
r_meta_set_string (core->anal, R_META_TYPE_COMMENT, vaddr, e->formatted);
}
for (u32 i = 0; i < e->xref_count; i++) {
ut64 from = HBC_VADDR_BASE + (ut64)e->xref_addrs[i];
ut64 from = base + e->xref_addrs[i];
r_anal_xrefs_set (core->anal, from, vaddr, R_ANAL_REF_TYPE_DATA);
}
}
@@ -812,9 +813,10 @@ static void cmd_lit_list_quiet(HbcContext *ctx, RCore *core) {
if (r.code != RESULT_SUCCESS) {
R_LOG_ERROR ("%s", safe_errmsg (r.error_message));
} else {
ut64 base = r_config_get_b (core->config, "io.va")? HBC_VADDR_BASE: 0;
for (u32 i = 0; i < n; i++) {
const HBCLiteralEntry *e = &arr[i];
const ut64 addr = (ut64)HBC_VADDR_BASE + e->paddr;
const ut64 addr = base + e->paddr;
const char *fmt = e->formatted? e->formatted: "";
r_cons_printf (core->cons, "0x%" PFMT64x " %s\n", addr, fmt);
}
@@ -845,18 +847,16 @@ static void cmd_lit_list_xrefs(HbcContext *ctx, RCore *core, const char *args) {
ut64 want = r_num_get (core->num, args);
ut64 base = va? (ut64)HBC_VADDR_BASE: 0;
ut64 target_paddr = (want >= base)? (want - base): want;
const HBCLiteralEntry *match = NULL;
bool found = false;
for (u32 i = 0; i < n; i++) {
if (arr[i].paddr == target_paddr) {
match = &arr[i];
break;
print_xref_line (core, &arr[i], va);
found = true;
}
}
if (!match) {
if (!found) {
R_LOG_ERROR ("no literal at that address");
return;
}
print_xref_line (core, match, va);
return;
}
for (u32 i = 0; i < n; i++) {
@@ -904,19 +904,23 @@ static void cmd_lit_list(HbcContext *ctx, RCore *core, bool as_json) {
}
r_cons_printf (core->cons, "literals: %u\n", n);
bool va = r_config_get_b (core->config, "io.va");
ut64 base = va? HBC_VADDR_BASE: 0;
for (u32 i = 0; i < n; i++) {
const HBCLiteralEntry *e = &arr[i];
const char *pavastr = va? "vaddr": "paddr";
r_cons_printf (core->cons,
"%-6s n=%-4u id=(%u,%u) %s=0x%08" PFMT64x " xrefs=%u %s\n",
"%-6s n=%-4u id=(%u,%u) %s=0x%08" PFMT64x " xrefs=%u",
kind_label (e->kind),
e->num_items,
e->primary_id,
e->secondary_id,
pavastr,
(ut64)HBC_VADDR_BASE + e->paddr,
e->xref_count,
e->formatted? e->formatted: "");
base + e->paddr,
e->xref_count);
for (u32 j = 0; j < e->xref_count; j++) {
r_cons_printf (core->cons, " 0x%" PFMT64x, base + e->xref_addrs[j]);
}
r_cons_printf (core->cons, " %s\n", e->formatted? e->formatted: "");
}
}
@@ -957,18 +961,16 @@ static void print_r2_for_entry(RCore *core, const HBCLiteralEntry *e, bool va) {
r_cons_printf (core->cons, "f %s0x%x 1 @ 0x%" PFMT64x "\n", prefix, e->paddr, vaddr);
/* embed the formatted text as a comment at the literal vaddr */
if (e->formatted && *e->formatted) {
char *clean = r_str_sanitize_r2 (e->formatted);
if (clean) {
r_str_replace_char (clean, '\n', ' ');
r_str_replace_char (clean, '\r', ' ');
r_cons_printf (core->cons, "\" /* %s */ CC %s @ 0x%" PFMT64x "\n", prefix, clean, vaddr);
free (clean);
char *b64 = r_base64_encode_dyn ((const ut8 *)e->formatted, -1);
if (b64) {
r_cons_printf (core->cons, "CCu base64:%s @ 0x%" PFMT64x "\n", b64, vaddr);
free (b64);
}
}
/* xref from each call site to the literal */
for (u32 j = 0; j < e->xref_count; j++) {
ut64 from = base + (ut64)e->xref_addrs[j];
r_cons_printf (core->cons, "axd 0x%" PFMT64x " 0x%" PFMT64x "\n", from, vaddr);
r_cons_printf (core->cons, "axd 0x%" PFMT64x " 0x%" PFMT64x "\n", vaddr, from);
}
}
@@ -990,18 +992,16 @@ static void cmd_lit_print_r2(HbcContext *ctx, RCore *core, const char *args) {
ut64 want = r_num_get (core->num, args);
ut64 base = va? (ut64)HBC_VADDR_BASE: 0;
ut64 target_paddr = (want >= base)? (want - base): want;
const HBCLiteralEntry *match = NULL;
bool found = false;
for (u32 i = 0; i < n; i++) {
if (arr[i].paddr == target_paddr) {
match = &arr[i];
break;
print_r2_for_entry (core, &arr[i], va);
found = true;
}
}
if (!match) {
if (!found) {
R_LOG_ERROR ("no literal at that address");
return;
}
print_r2_for_entry (core, match, va);
return;
}
for (u32 i = 0; i < n; i++) {
@@ -1058,25 +1058,28 @@ static void cmd_lit_toggle_inline(RCore *core) {
/* Show help */
static const char r2hermes_helpmsg[] =
"Usage: r2hermes-L[subcmd]\n"
" r2hermes-L List cached literals (auto-scans code if cache is empty)\n"
" r2hermes-L List cached literals and constructor addresses (auto-scans if empty)\n"
" r2hermes-Lj List as JSON (auto-scans if needed)\n"
" r2hermes-Lq List as `<vaddr> <formatted>` per line (one per literal)\n"
" r2hermes-Lx [addr] List as `<vaddr> <xref1> <xref2> ...` (one per literal)\n"
" With addr: print xrefs for that single literal only\n"
" r2hermes-Lq List as `<addr> <formatted>` per line (addresses use io.va)\n"
" r2hermes-Lx [addr] List as `<addr> <xref1> <xref2> ...` (addresses use io.va)\n"
" With addr: print all literals sharing that pool address\n"
" r2hermes-Lp[ao] Scan SLP pool (default: arrays; a=arrays, o=objects)\n"
" r2hermes-LR Reset literal cache (does not remove r2 flags/comments)\n"
" r2hermes-Lr [addr] Print r2 oneliners (f/CC/axd) to register flag+comment+xrefs\n"
" With addr: print oneliners for that single literal only\n"
" With addr: print all literals sharing that pool address\n"
" r2hermes-Lg <k> <n> <primary> [<sec>]\n"
" Format a literal from raw params (k=a|o)\n"
" r2hermes-Li Toggle inline literal comments in disasm\n"
" r2hermes-L? This help\n"
"\nDeprecated alias: pd:hL[subcmd]\n";
" r2hermes-L? This help\n";
static void r2hermes_help(RCore *core) {
const char msg[] =
"Usage: r2hermes[-arg] # see also pd:h for decompilation\n"
"r2hermes-h - help message (same as r2hermes-?, see pd:h? too)\n"
"r2hermes-A - Import symbols and entrypoints\n"
"r2hermes-AA - Also import strings and SLP literal comments/xrefs\n"
"r2hermes-AAA - Also analyze bytecode references\n"
"r2hermes-AAAA - Also perform full analysis (aaa)\n"
"r2hermes-E[jq] - List direct eval instruction sites (j=JSON, q=addresses only)\n"
"r2hermes-H - Show file information and hash status\n"
"r2hermes-L[?] - SLP literal cache: list/scan/reset/format/toggle\n"
@@ -1099,7 +1102,6 @@ static void cmd_help(RCore *core) {
" pd:ho [id] - Decompile with offsets (addresses) per statement\n"
" pd:hoa - Decompile all with offsets\n"
" pd:hs [id] - List source-line information\n"
" pd:hL[?] - Deprecated alias for r2hermes-L[?]\n"
" pd:h? - Show this help\n"
"\nNote: r2 comments (CC command) are automatically inlined in decompiler output.\n");
}
@@ -1151,10 +1153,125 @@ static void cmd_literals(HbcContext *ctx, RCore *core, const char *arg) {
#include "sbom.inc.c"
#include "version.inc.c"
static void analysis_xref(RCore *core, ut64 from, ut64 target, RAnalRefType type, bool va) {
if (target == UT64_MAX || target < HBC_VADDR_BASE) {
return;
}
if (!va) {
target -= HBC_VADDR_BASE;
}
if (r_io_is_valid_offset (core->io, target, 0)) {
r_anal_xrefs_set (core->anal, from, target, type);
}
}
/* Decode complete functions, including tails shorter than the maximum opcode
* size that the generic aar scan skips. Decode at vaddrs so absolute function
* and string operands from the architecture plugin share one address space. */
static void analyze_function_refs(RCore *core, HBC *hbc, u32 id, bool va) {
HBCFunc fi;
const u8 *code = NULL;
u32 size = 0;
if (hbc_get_function_info (hbc, id, &fi).code != RESULT_SUCCESS ||
hbc_get_function_bytecode (hbc, id, &code, &size).code != RESULT_SUCCESS || !code) {
return;
}
for (u32 pc = 0; pc < size && !r_cons_is_breaked (core->cons);) {
const ut64 vaddr = HBC_VADDR_BASE + fi.offset + pc;
const ut64 from = va? vaddr: (ut64)fi.offset + pc;
RAnalOp op = { 0 };
const int len = r_anal_op (core->anal, &op, vaddr, code + pc, R_MIN (size - pc, INT_MAX), R_ARCH_OP_MASK_BASIC);
if (len < 1 || (u32)len > size - pc) {
r_anal_op_fini (&op);
break;
}
analysis_xref (core, from, op.ptr, R_ANAL_REF_TYPE_DATA, va);
if (op.type == R_ANAL_OP_TYPE_CALL || op.type == R_ANAL_OP_TYPE_CCALL) {
analysis_xref (core, from, op.jump, R_ANAL_REF_TYPE_CALL, va);
} else if (op.type == R_ANAL_OP_TYPE_JMP || op.type == R_ANAL_OP_TYPE_CJMP) {
analysis_xref (core, from, op.jump, R_ANAL_REF_TYPE_CODE, va);
}
r_anal_op_fini (&op);
pc += len;
}
}
static void cmd_analyze(RCore *core, HbcContext *ctx, const char *arg) {
unsigned level = 0;
while (arg[level] == 'A') {
level++;
}
const char *rest = r_str_trim_head_ro (arg + level);
if (!strcmp (rest, "?")) {
r_cons_print (core->cons,
"Usage: r2hermes-A[AAA]\n"
" r2hermes-A Import symbols and entrypoints\n"
" r2hermes-AA Also import strings and SLP literal flags/comments/xrefs\n"
" r2hermes-AAA Also analyze references in every bytecode function\n"
" r2hermes-AAAA Also perform full analysis (aaa)\n"
"Levels are cumulative; addresses follow io.va.\n");
return;
}
if (level > 4 || *rest) {
R_LOG_ERROR ("Invalid analysis level. Use r2hermes-A? for help");
return;
}
HBC *hbc = NULL;
Result res = ensure_hbc_loaded (ctx, core, &hbc);
if (res.code != RESULT_SUCCESS) {
R_LOG_ERROR ("%s", safe_errmsg (res.error_message));
return;
}
const ut64 addr = core->addr;
const bool va = r_config_get_b (core->config, "io.va");
if (!r_flag_space_push (core->flags, R_FLAGS_FS_SYMBOLS)) {
return;
}
if (!r_core_bin_info (core, R_CORE_BIN_ACC_ENTRIES, NULL, R_MODE_SET, va, NULL, NULL) ||
r_core_cmd0 (core, ".is*") != 0) {
R_LOG_ERROR ("Failed to import HBC symbols and entrypoints");
goto done;
}
if (level >= 2) {
if (!r_core_bin_info (core, R_CORE_BIN_ACC_STRINGS, NULL, R_MODE_SET, va, NULL, NULL)) {
R_LOG_ERROR ("Failed to import HBC strings");
goto done;
}
res = hbc_literals_scan_code (hbc, NULL);
if (res.code != RESULT_SUCCESS) {
R_LOG_ERROR ("Literal scan failed: %s", safe_errmsg (res.error_message));
goto done;
}
r_flag_space_set (core->flags, R_FLAGS_FS_SYMBOLS);
register_all_artifacts (core, hbc);
}
if (level >= 3) {
const u32 count = hbc_function_count (hbc);
r_cons_break_push (core->cons, NULL, NULL);
for (u32 i = 0; i < count && !r_cons_is_breaked (core->cons); i++) {
analyze_function_refs (core, hbc, i, va);
}
const bool interrupted = r_cons_is_breaked (core->cons);
r_cons_break_pop (core->cons);
if (interrupted) {
goto done;
}
}
if (level >= 4) {
r_core_cmd0 (core, "aaa");
}
done:
r_flag_space_pop (core->flags);
r_core_seek (core, addr, true);
}
static void cmd_r2hermes(RCore *core, HbcContext *ctx, const char *arg) {
arg = r_str_trim_head_ro (arg);
if (*arg == '-') {
switch (arg[1]) {
case 'A':
cmd_analyze (core, ctx, arg + 1);
break;
case 'E':
cmd_list_eval_functions (ctx, core, arg + 2);
break;
@@ -1227,10 +1344,6 @@ static void cmd_pdh(RCore *core, HbcContext *ctx, const char *arg) {
}
break;
}
case 'L': /* pd:hL* — deprecated alias for r2hermes-L* */
R_LOG_WARN ("pd:hL is deprecated; use r2hermes-L instead");
cmd_literals (ctx, core, arg + 1);
break;
case 's': /* pd:hs [id] */
cmd_source_lines (ctx, core, arg + 1);
break;
@@ -1399,6 +1512,7 @@ R_API RLibStruct radare_plugin = {
.type = R_LIB_TYPE_CORE,
.data = (void *)&r_core_plugin_r2hermes,
.version = R2_VERSION,
.abiversion = R2_ABIVERSION
.abiversion = R2_ABIVERSION,
.pkgname = "r2hermes"
};
#endif
+161
View File
@@ -0,0 +1,161 @@
NAME=r2hermes analysis help
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
r2hermes-?~r2hermes-A
r2hermes-A?
EOF
EXPECT=<<EOF
r2hermes-A - Import symbols and entrypoints
r2hermes-AA - Also import strings and SLP literal comments/xrefs
r2hermes-AAA - Also analyze bytecode references
r2hermes-AAAA - Also perform full analysis (aaa)
Usage: r2hermes-A[AAA]
r2hermes-A Import symbols and entrypoints
r2hermes-AA Also import strings and SLP literal flags/comments/xrefs
r2hermes-AAA Also analyze references in every bytecode function
r2hermes-AAAA Also perform full analysis (aaa)
Levels are cumulative; addresses follow io.va.
EOF
RUN
NAME=r2hermes-A imports symbols and preserves session state
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
f-*
fs scratch
s 0x100000b4
r2hermes-A
?v $$
fs~scratch[1]
fs *
f~global,entry,str.
EOF
EXPECT=<<EOF
0x100000b4
*
0x100000b0 1 entry0
0x100000b0 10 sym.global_0x000000b0
EOF
RUN
NAME=r2hermes-AA adds strings
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
f-*
r2hermes-AA
f~global,entry,str.
EOF
EXPECT=<<EOF
0x1000009c 6 str.global
0x100000a2 11 str.print_123__
0x100000b0 1 entry0
0x100000b0 10 sym.global_0x000000b0
EOF
RUN
NAME=r2hermes-AA imports literals again from an existing cache
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
r2hermes-Lq~^0x10037b28
ax-*
f- lit.*
CC- @ 0x10037b28
r2hermes-AA
f~lit.obj.0x37b28
CC. @ 0x10037b28
axtq @ 0x10037b28
r2hermes-AA
axtq @ 0x10037b28
EOF
EXPECT=<<EOF
0x10037b28 {trace: 0, info: 1, warn: 2, error: 3}
0x10037b28 1 lit.obj.0x37b28
{trace: 0, info: 1, warn: 2, error: 3}
0x1003e9f9
0x1003e9f9
EOF
RUN
NAME=r2hermes-AA imports literal physical addresses
FILE=bins/hbc/index.android.bundle
ARGS=-e io.va=false
CMDS=<<EOF
f-*
r2hermes-AA
f~lit.obj.0x37b28
CC. @ 0x37b28
axtq @ 0x37b28
EOF
EXPECT=<<EOF
0x00037b28 1 lit.obj.0x37b28
{trace: 0, info: 1, warn: 2, error: 3}
0x3e9f9
EOF
RUN
NAME=r2hermes-AAA finds short function references and remains additive
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
ax-*
r2hermes-AA
axtq @ 0x100000a2
r2hermes-AAA
axtq @ 0x100000a2
r2hermes-AAA
axtq @ 0x100000a2
EOF
EXPECT=<<EOF
0x100000b0
0x100000b0
EOF
RUN
NAME=r2hermes-AAA uses physical addresses for code and data
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e io.va=false
CMDS=<<EOF
ax-*
r2hermes-AAA
axtq @ 0xa2
axtq @ 0x100000a2
f~global,entry,str.print
EOF
EXPECT=<<EOF
0xb0
0x0000009c 6 str.global
0x000000a2 11 str.print_123__
0x000000b0 1 entry0
0x000000b0 10 sym.global_0x000000b0
EOF
RUN
NAME=r2hermes-AAAA analyzes function control flow
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e log.level=0
CMDS=<<EOF
r2hermes-AAAA
aflj~{[0].addr}
aflj~{[0].size}
EOF
EXPECT=<<EOF
268435632
10
EOF
RUN
NAME=r2hermes analysis rejects invalid levels before importing
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
f-*
r2hermes-AAAAA
r2hermes-AAj
r2hermes-AA extra
f
EOF
EXPECT=
EXPECT_ERR=<<EOF
ERROR: Invalid analysis level. Use r2hermes-A? for help
ERROR: Invalid analysis level. Use r2hermes-A? for help
ERROR: Invalid analysis level. Use r2hermes-A? for help
EOF
RUN
+1 -1
View File
@@ -146,7 +146,7 @@ RUN
NAME=hbc exception handlers are exposed through bin metadata
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
iw~try.0
iw*~try.0
EOF
EXPECT=<<EOF
f try.0.1003e6dc.from=0x1003e703
+59
View File
@@ -0,0 +1,59 @@
NAME=hbc literal addresses and constructor refs
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
r2hermes-L~trace:
r2hermes-Lx 0x10037b3c
r2hermes-Lr 0x10037b3c~^axd
ax-*
.r2hermes-Lr 0x10037b3c
axtq @ 0x10037b3c
EOF
EXPECT=<<EOF
object n=4 id=(0,0) vaddr=0x10037b28 xrefs=1 0x1003e9f9 {trace: 0, info: 1, warn: 2, error: 3}
0x10037b3c 0x1003eb09 0x1003ebe7
0x10037b3c 0x100c2cfb
axd 0x10037b3c 0x1003eb09
axd 0x10037b3c 0x1003ebe7
axd 0x10037b3c 0x100c2cfb
0x1003eb09
0x1003ebe7
0x100c2cfb
EOF
RUN
NAME=hbc literal physical addresses and refs
FILE=bins/hbc/index.android.bundle
ARGS=-e io.va=false
CMDS=<<EOF
r2hermes-L~trace:
r2hermes-Lq~^0x37b28
r2hermes-Lx 0x37b3c
r2hermes-Lr 0x37b3c~^axd
axtq @ 0x37b3c
EOF
EXPECT=<<EOF
object n=4 id=(0,0) paddr=0x00037b28 xrefs=1 0x3e9f9 {trace: 0, info: 1, warn: 2, error: 3}
0x37b28 {trace: 0, info: 1, warn: 2, error: 3}
0x37b3c 0x3eb09 0x3ebe7
0x37b3c 0xc2cfb
axd 0x37b3c 0x3eb09
axd 0x37b3c 0x3ebe7
axd 0x37b3c 0xc2cfb
0x3eb09
0x3ebe7
0xc2cfb
EOF
RUN
NAME=hbc literal help drops deprecated alias
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
r2hermes-L?~pd:hL
pd:h?~pd:hL
pd:hL?
EOF
EXPECT=
EXPECT_ERR=<<EOF
ERROR: Unknown subcommand. Use pd:h? for help
EOF
RUN
+45 -1
View File
@@ -209,9 +209,53 @@ static int test_decode_rejects_bad_overflow_string_index(void) {
return 0;
}
static int test_literal_addresses(void) {
u8 keys[] = { 0x71, 1, 0, 0, 0 };
u8 values[] = { 0x71, 20, 0, 0, 0, 0x71, 36, 0, 0, 0 };
ShapeTableEntry shape = { .key_buffer_offset = 0, .prop_count = 1 };
for (u32 version = 96; version <= 97; version++) {
HBC hbc = { 0 };
RVecHBCLiteralEntry_init (&hbc.lit_cache.entries);
hbc.reader.header.version = version;
hbc.reader.header.arrayBufferSize = sizeof (values);
hbc.reader.header.objKeyBufferSize = sizeof (keys);
hbc.reader.header.objValueBufferSize = sizeof (values);
hbc.reader.arrays = values;
hbc.reader.arrays_paddr = 0x100;
hbc.reader.object_keys = keys;
hbc.reader.object_keys_paddr = 0x200;
hbc.reader.object_values = values;
hbc.reader.object_values_paddr = version >= 97? 0x100: 0x300;
hbc.reader.object_shapes = &shape;
hbc.reader.object_shape_count = 1;
u32 num_items = version >= 97? 0: 1;
CHECK (hbc_literals_register (&hbc, HBC_LIT_OBJECT, num_items, 0, 0, 0x400).code == RESULT_SUCCESS);
CHECK (hbc_literals_register (&hbc, HBC_LIT_OBJECT, num_items, 0, 5, 0x410).code == RESULT_SUCCESS);
CHECK (hbc_literals_register (&hbc, HBC_LIT_OBJECT, num_items, 0, 5, 0x420).code == RESULT_SUCCESS);
CHECK (hbc_literals_register (&hbc, HBC_LIT_ARRAY, 1, 5, 0, 0x430).code == RESULT_SUCCESS);
CHECK (hbc_literals_register (&hbc, HBC_LIT_OBJECT, num_items, 0, sizeof (values), 0x440).code == RESULT_SUCCESS);
const HBCLiteralEntry *entries = NULL;
u32 count = 0;
CHECK (hbc_literals_list (&hbc, &entries, &count).code == RESULT_SUCCESS);
CHECK (count == 4);
CHECK (entries[0].paddr == hbc.reader.object_values_paddr);
CHECK (entries[1].paddr == hbc.reader.object_values_paddr + 5);
CHECK (entries[2].paddr == 0x105);
CHECK (entries[3].paddr == 0);
CHECK (!strcmp (entries[0].formatted, "{1: 20}"));
CHECK (!strcmp (entries[1].formatted, "{1: 36}"));
CHECK (!strcmp (entries[2].formatted, "[36]"));
CHECK (entries[1].xref_count == 2);
CHECK (entries[1].xref_addrs[0] == 0x410);
CHECK (entries[1].xref_addrs[1] == 0x420);
hbc_literals_reset (&hbc);
}
return 0;
}
int main(int argc, char **argv) {
const char *root = argc > 1? argv[1]: ".";
if (test_small_debug_info (root) || test_empty_debug_info (root) || test_exception_handlers (root) || test_opcode_operand_meanings () || test_function_bytecode_bounds (root) || test_decode_rejects_bad_overflow_string_index ()) {
if (test_small_debug_info (root) || test_empty_debug_info (root) || test_exception_handlers (root) || test_opcode_operand_meanings () || test_function_bytecode_bounds (root) || test_decode_rejects_bad_overflow_string_index () || test_literal_addresses ()) {
return 1;
}
return 0;