Scan complete instruction ranges for standalone xrefs

This commit is contained in:
pancake committed 2026-10-21 12:47:42 +02:00
1 parent 25a5b3f026
commit 441d614937
6 files changed
+122 -50

No files matched your search

+25
View File
@@ -2027,3 +2027,28 @@ whose second field is an `XmlAttributeType` instance. Synthetic compressed and
uncompressed snapshots cover nested Records, named shapes, empty Records,
cycles, truncation, and a malformed field count. Existing iOS graph tests
continue to cover arrays, maps, and sets; those fixtures have no Record cluster.
## Standalone xrefs scan full instruction-table payloads
The `-x` ObjectPool-use scanner used to ignore entries without retained Code
metadata, stop after 4096 functions by default, and inspect at most 96
instructions per function. This missed real instructions in the discarded-Code
prefix and PP references late in a function or instruction table.
The scanner now visits every instruction-table entry with an address and scans
to the next distinct entry address. The last payload ends at the instructions
image size recorded in its header, checked against its IO mapping. Instruction
sizes must fit inside that range. Table extraction is unlimited even when
`-l` caps emitted xrefs, so an output limit cannot remove the boundary needed
for a scanned function. PP-use buckets use a hash lookup to keep the expanded
scan practical. Instruction addresses identify visited use sites directly,
avoiding collisions from the previous XOR of address and PP offset.
Android w0rdle now includes the string load at `0x1b675c`, instruction 389 of
IT entry 808 (`Base64Codec.normalize`), and the load at `0x3f4908` in entry
10906. iOS Runner includes `0x929d0`, instruction 107 of entry 2642
(`TextPainter.layout`), and `0xea070` in entry 4235 (`Layer.dispose`).
Regressions also mark the Android table entries as lacking Code metadata and
place matching PP loads in the last function and outside the image to check
coverage and bounds. This expands instruction coverage; operand decoding
continues to use the existing standalone scanner.
+38 -50
View File
@@ -33,7 +33,7 @@ typedef struct {
typedef struct {
DartCtx *ctx;
RList *buckets;
HtUP *buckets;
RList *xrefs;
ut64 *count;
ut64 limit;
@@ -498,30 +498,20 @@ static const char *xref_pool_flag_kind(const char *name, const char **dst_type,
return NULL;
}
static bool xref_pool_use_seen(HtUP *seen, ut64 at, ut64 pp_off) {
ut64 key = at ^ (pp_off << 8) ^ (pp_off >> 8);
if (ht_up_find (seen, key, NULL)) {
static bool xref_pool_use_seen(HtUP *seen, ut64 at) {
// Each instruction contributes at most one PP use. Key by its address so
// distinct sites cannot collide through a lossy address/offset combination.
if (ht_up_find (seen, at, NULL)) {
return true;
}
ht_up_insert (seen, key, (void *)1);
ht_up_insert (seen, at, (void *)1);
return false;
}
#define DART_XREF_MAX_OPS 96
// `end_addr` is the next entry's address, or 0 when unknown. Without it the
// scan disassembles a fixed window that runs past short functions into their
// neighbours, crediting those neighbours' pool references to this entry.
// `end_addr` is the next entry's address, or the instructions-image end for
// the last entry. Scan the whole payload without crossing into its neighbour.
static void collect_pool_uses_from_entry(DartCtx *ctx, const DartInstructionTableEntry *entry, ut64 end_addr, RList *uses, HtUP *seen) {
if (!ctx || !ctx->core || !entry || !entry->address || !uses || !seen) {
return;
}
ut64 nops = DART_XREF_MAX_OPS;
if (end_addr > entry->address) {
const ut64 avail = (end_addr - entry->address) / 4;
nops = R_MIN (avail, (ut64)DART_XREF_MAX_OPS);
}
if (!nops) {
if (!ctx || !ctx->core || !entry || !entry->address || end_addr <= entry->address || !uses || !seen) {
return;
}
// Decode the function's instructions one at a time via r_core_anal_op and
@@ -531,9 +521,9 @@ static void collect_pool_uses_from_entry(DartCtx *ctx, const DartInstructionTabl
bool reg_valid[31] = { false };
ut64 reg_off[31] = { 0 };
ut64 at = entry->address;
for (ut64 i = 0; i < nops; i++) {
while (at < end_addr) {
RAnalOp *op = r_core_anal_op (ctx->core, at, R_ARCH_OP_MASK_BASIC | R_ARCH_OP_MASK_DISASM);
if (!op || op->size <= 0 || R_STR_ISEMPTY (op->mnemonic)) {
if (!op || op->size <= 0 || (ut64)op->size > end_addr - at || R_STR_ISEMPTY (op->mnemonic)) {
r_anal_op_free (op);
break;
}
@@ -541,7 +531,7 @@ static void collect_pool_uses_from_entry(DartCtx *ctx, const DartInstructionTabl
ut64 pp_off = 0;
bool have_pp = xref_parse_pp_offset (m, &pp_off) ||
xref_parse_mem_pp_offset (m, reg_valid, reg_off, &pp_off);
if (have_pp && !xref_pool_use_seen (seen, at, pp_off)) {
if (have_pp && !xref_pool_use_seen (seen, at)) {
DartPoolUseInfo *ui = R_NEW0 (DartPoolUseInfo);
ui->at = at;
ui->fn_index = entry->index;
@@ -565,35 +555,40 @@ static void collect_pool_uses_from_entry(DartCtx *ctx, const DartInstructionTabl
}
}
static RList *collect_pool_uses(DartCtx *ctx, RVecDartInstructionTableEntry *entries, ut64 limit) {
static ut64 xref_instructions_end(DartCtx *ctx) {
ut64 size = 0;
if (find_snapshots (ctx) != 0 || !ctx->iso_instr || !read_u64_at (ctx, ctx->iso_instr, &size) || size < DART_IMAGE_ALIGNMENT || size > UT64_MAX - ctx->iso_instr) {
return 0;
}
const ut64 end = ctx->iso_instr + size;
RIOMap *map = r_io_map_get_at (ctx->core->io, ctx->iso_instr);
return map && end - 1 <= r_io_map_to (map)? end: 0;
}
static RList *collect_pool_uses(DartCtx *ctx, RVecDartInstructionTableEntry *entries) {
RList *uses = r_list_newf (dart_pool_use_info_free);
if (!ctx || !entries || RVecDartInstructionTableEntry_length (entries) == 0) {
return uses;
}
HtUP *seen = ht_up_new0 ();
ut64 scanned = 0;
ut64 scan_limit = limit > 0? limit: 4096;
const ut64 image_end = xref_instructions_end (ctx);
const ut64 nentries = RVecDartInstructionTableEntry_length (entries);
for (ut64 i = 0; i < nentries; i++) {
const DartInstructionTableEntry *entry = RVecDartInstructionTableEntry_at (entries, i);
if (!entry || !entry->has_code || !entry->address) {
if (!entry || !entry->address) {
continue;
}
if (scan_limit > 0 && scanned >= scan_limit) {
break;
}
// Entries are emitted in ascending address order, so the next one with
// code bounds this one.
ut64 end_addr = 0;
// a distinct address bounds this one, including discarded-Code entries.
ut64 end_addr = image_end;
for (ut64 k = i + 1; k < nentries; k++) {
const DartInstructionTableEntry *nx = RVecDartInstructionTableEntry_at (entries, k);
if (nx && nx->address > entry->address) {
end_addr = nx->address;
end_addr = image_end? R_MIN (image_end, nx->address): nx->address;
break;
}
}
collect_pool_uses_from_entry (ctx, entry, end_addr, uses, seen);
scanned++;
}
ht_up_free (seen);
return uses;
@@ -653,17 +648,6 @@ static bool resolve_pool_entry_xref(DartCtx *ctx, DartRecoveryModel *model, RLis
return false;
}
static DartPpUseBucket *xref_bucket_by_pp(RList *buckets, ut64 pp_off) {
RListIter *it;
DartPpUseBucket *bucket;
r_list_foreach (buckets, it, bucket) {
if (bucket && bucket->pp_off == pp_off) {
return bucket;
}
}
return NULL;
}
static void xref_serialized_pool_string_cb(const ModernPoolStrRef *info, void *user) {
DartSerializedPoolXrefCtx *ux = (DartSerializedPoolXrefCtx *)user;
if (!info || !ux || !ux->buckets || !ux->xrefs || !ux->count || xref_limit_reached (*ux->count, ux->limit)) {
@@ -672,7 +656,7 @@ static void xref_serialized_pool_string_cb(const ModernPoolStrRef *info, void *u
if (!info->string_addr || R_STR_ISEMPTY (info->string_value)) {
return;
}
DartPpUseBucket *bucket = xref_bucket_by_pp (ux->buckets, info->pp_offset);
DartPpUseBucket *bucket = ht_up_find (ux->buckets, info->pp_offset, NULL);
if (!bucket || !bucket->uses) {
return;
}
@@ -701,13 +685,13 @@ static bool collect_serialized_object_pool_xrefs(DartCtx *ctx, RList *uses, RLis
if (!use) {
continue;
}
DartPpUseBucket *bucket = xref_bucket_by_pp (buckets, use->pp_off);
DartPpUseBucket *bucket = ht_up_find (wanted, use->pp_off, NULL);
if (!bucket) {
bucket = R_NEW0 (DartPpUseBucket);
bucket->pp_off = use->pp_off;
bucket->uses = r_list_new ();
r_list_append (buckets, bucket);
ht_up_insert (wanted, use->pp_off, (void *)1);
ht_up_insert (wanted, use->pp_off, bucket);
}
r_list_append (bucket->uses, use);
}
@@ -715,7 +699,7 @@ static bool collect_serialized_object_pool_xrefs(DartCtx *ctx, RList *uses, RLis
DartVerLayout *layout_owned = ctx->layout? NULL: dart_ctx_init_layout (ctx, &layout_tmp);
DartSerializedPoolXrefCtx ux = {
.ctx = ctx,
.buckets = buckets,
.buckets = wanted,
.xrefs = xrefs,
.count = count,
.limit = limit,
@@ -752,7 +736,7 @@ static void collect_object_pool_xrefs(DartCtx *ctx, DartRecoveryModel *model, RL
if (!ctx || !ctx->core || !model || !model->it_entries || !xrefs || xref_limit_reached (*count, limit)) {
return;
}
RList *uses = collect_pool_uses (ctx, model->it_entries, limit);
RList *uses = collect_pool_uses (ctx, model->it_entries);
(void)collect_serialized_object_pool_xrefs (ctx, uses, xrefs, count, limit);
ut64 pool_base = xref_object_pool_base (ctx);
if (!pool_base) {
@@ -784,7 +768,11 @@ static RList *dart_pool_extract_xrefs(DartCtx *ctx) {
const ut64 limit = ctx->dump_fns_limit > 0? (ut64)ctx->dump_fns_limit: 0;
ut64 count = 0;
DartRecoveryModel model = { 0 };
dart_recovery_model_load (ctx, &model, DART_RECOVERY_STRINGS | DART_RECOVERY_CLASSES | DART_RECOVERY_CLASS_FIELDS | DART_RECOVERY_IT);
dart_recovery_model_load (ctx, &model, DART_RECOVERY_STRINGS | DART_RECOVERY_CLASSES | DART_RECOVERY_CLASS_FIELDS);
// Even limited output needs the full table to bound every scanned payload.
DartCtx work = *ctx;
work.dump_fns_limit = 0;
model.it_entries = dart_pool_extract_instruction_table (&work);
RList *classes = model.classes;
RVecDartInstructionTableEntry *it_entries = model.it_entries;
if (classes) {
+14
View File
@@ -0,0 +1,14 @@
NAME=xrefs-discarded-code-android
FILE=bins/android/w0rdle/libapp.so
ARGS=-NN -e scr.color=0 -e bin.relocs.apply=true -e io.cache=true
CMDS=<<EOF
L ../src/r2/core_flutter.so
L ../src/r2/core_flutter.dylib
# Mark all table rows as lacking retained Code metadata; instructions remain.
wv4 11017 @ 0x12c998
r2flutter -q -x~0x1b675c~0xc7b
EOF
EXPECT=<<EOF
code code.string 0x1b675c -> 0xc7b
EOF
RUN
+15
View File
@@ -0,0 +1,15 @@
NAME=xrefs-full-coverage-android
FILE=bins/android/w0rdle/libapp.so
ARGS=-NN -e scr.color=0 -e bin.relocs.apply=true
CMDS=<<EOF
L ../src/r2/core_flutter.so
L ../src/r2/core_flutter.dylib
# Instruction 389 in IT entry 808, followed by IT entry 10906.
r2flutter -q -x~0x1b675c~0xc7b
r2flutter -q -x~0x3f4908~0x23a0b
EOF
EXPECT=<<EOF
code code.string 0x1b675c -> 0xc7b
code code.string 0x3f4908 -> 0x23a0b
EOF
RUN
+15
View File
@@ -0,0 +1,15 @@
NAME=xrefs-full-coverage-ios
FILE=bins/ios/Runner.app/Frameworks/App.framework/App
ARGS=-NN -e scr.color=0 -e bin.relocs.apply=true
CMDS=<<EOF
L ../src/r2/core_flutter.so
L ../src/r2/core_flutter.dylib
# Instruction 107 in IT entry 2642, followed by IT entry 4235.
r2flutter -q -x~0x929d0~0x2a1600
r2flutter -q -x~0xea070~0x2a1600
EOF
EXPECT=<<EOF
code code.string 0x929d0 -> 0x2a1600
code code.string 0xea070 -> 0x2a1600
EOF
RUN
+15
View File
@@ -0,0 +1,15 @@
NAME=xrefs-instructions-end
FILE=bins/android/w0rdle/libapp.so
ARGS=-NN -e scr.color=0 -e bin.relocs.apply=true -e io.cache=true
CMDS=<<EOF
L ../src/r2/core_flutter.so
L ../src/r2/core_flutter.dylib
# Load the same PP string in the last function and just outside the image.
wx 602f40f9 @ 0x3f8814
wx 602f40f9 @ 0x3f8870
r2flutter -q -x~0x3f8814,0x3f8870~0x23a0b
EOF
EXPECT=<<EOF
code code.string 0x3f8814 -> 0x23a0b
EOF
RUN