Add function-level code diff over caller-supplied views

code_diff compares two views of the same code in one coordinate space - an
on-disk image section against the live in-memory section, or one .text across
two snapshots - and reports the functions whose body changed. For each function
extent it func_hash()es the slice of each view and flags a mismatch: a patch, an
inline hook, or an unpacked/JIT-rewritten body. A thin handler over func_hash +
mem_sub, with no file I/O of its own - the caller owns reading the on-disk image.
The relocation limit (absolute-address immediates) is documented; two snapshots
at the same base diff exactly. Closes the non-starred reversing series.
This commit is contained in:
2026-06-16 20:21:36 +03:00
parent 35c5dc06ba
commit 50ed32b7dc
2 changed files with 74 additions and 0 deletions
+40
View File
@@ -173,3 +173,43 @@ uint64_t func_hash(mem_view_t fn) {
}
return h;
}
/* ---- function-level code diff -------------------------------------------- *
* For each function extent, mem_sub the SAME [start,end) out of both views and
* compare their func_hash (the position-independent, relocation-normalized
* fingerprint). A differing hash means a patched / hooked / rewritten body. The
* slices are zero-copy (mem_sub borrows the views' bytes; no byte is copied) and
* hashing reuses func_hash - no second decoder or hash here. Cold: a one-shot
* pass over the function table, not a hot loop. */
/* Does mem_sub yield exactly the requested extent? mem_sub clamps an out-of-view
* window to a zeroed view (data == NULL) or trims its size, so an extent that is
* fully present comes back with the same data and the full size - anything else
* is partially or wholly outside the view and must be skipped. */
static int sub_is_exact(mem_view_t sub, size_t want) {
return sub.data != NULL && sub.size == want;
}
int code_diff(mem_view_t a, mem_view_t b, const code_block* fns, int nfns,
uint32_t* changed, int max) __attribute__((cold));
int code_diff(mem_view_t a, mem_view_t b, const code_block* fns, int nfns,
uint32_t* changed, int max) {
if (!fns || nfns < 0) { return -1; }
int total = 0;
for (int i = 0; i < nfns; i++) {
if (fns[i].end <= fns[i].start) { continue; } /* empty/inverted ext */
const size_t len = (size_t)(fns[i].end - fns[i].start);
/* same [start,end) sliced out of both views (zero-copy borrow). */
const mem_view_t sa = mem_sub(a, a.base_va + fns[i].start, len);
const mem_view_t sb = mem_sub(b, b.base_va + fns[i].start, len);
if (!sub_is_exact(sa, len) || !sub_is_exact(sb, len)) { continue; }
if (func_hash(sa) != func_hash(sb)) {
if (changed && total < max) { changed[total] = fns[i].start; }
total++;
}
}
return total;
}