From 25a5b3f026641faeb03c5f46c75ecb590ecc0f35 Mon Sep 17 00:00:00 2001 From: pancake Date: Wed, 21 Oct 2026 12:46:56 +0200 Subject: [PATCH] Decode Record fields in snapshot value graphs --- doc/learn.md | 26 ++++++++++++ src/lib/dart_pool_modern.c | 58 +++++++++++++++++++++++++- test/db/cmd/record-values-android | 23 ++++++++++ test/db/cmd/record-values-malformed | 14 +++++++ test/db/cmd/record-values-synthetic | 34 +++++++++++++++ test/db/cmd/record-values-uncompressed | 20 +++++++++ 6 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 test/db/cmd/record-values-android create mode 100644 test/db/cmd/record-values-malformed create mode 100644 test/db/cmd/record-values-synthetic create mode 100644 test/db/cmd/record-values-uncompressed diff --git a/doc/learn.md b/doc/learn.md index 3e28844..3e3c19c 100644 --- a/doc/learn.md +++ b/doc/learn.md @@ -2001,3 +2001,29 @@ On iOS AuthPass, `PP+0x68` holds ref 168520 in the Code cluster starting at regression checks index zero, a discarded record, the following main record, and an unresolved deferred record. These targets use the existing table payload-address convention described above. + +## Record inspection decodes the shape and field references + +Record fills contain `ReadUnsigned (shape)` followed by one reference per +field. `RecordShape::NumFieldsBitField` in the VM is the low 16 bits; the upper +bits identify the registered array of named field names. Allocation records +separately store each Record's field count. Retain those counts and check each +fill against its own allocation instead of accepting only matching cluster +minimum, maximum, and total counts. + +The value graph now decodes Record fields into the same bounded edges used by +other objects. JSON includes `shape`, `field_names_index`, `length`, and +`fields`; text output prints the shape, length, and child values. Labels remain +ordinal (`field_0`, etc.); the shape's named-field table index is preserved +rather than treated as a reference or used to invent names. Empty Records +report length zero. Cycles, depth limits, and the 128-child display limit use +the existing graph handling. An unreadable or inconsistent Record fill stays +`complete: false`. + +In `t/libapp_1.so`, ref 146422 has shape 2 and fields referring to base object +996 and instance 164557; that instance exposes the string `DOUBLE_QUOTE`. +The public Android mafia fixture has the equivalent Record at ref 158051, +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. diff --git a/src/lib/dart_pool_modern.c b/src/lib/dart_pool_modern.c index 869b43c..e0e8907 100644 --- a/src/lib/dart_pool_modern.c +++ b/src/lib/dart_pool_modern.c @@ -760,7 +760,7 @@ static bool modern_skip_alloc(ClusterStream *s, const ModernCidCache *cids, int return false; } meta->count = count; - if (kind == MODERN_ALLOC_VARIABLE && count) { + if ((kind == MODERN_ALLOC_VARIABLE || kind == MODERN_ALLOC_RECORD) && count) { meta->alloc_items = (ut64 *)calloc ((size_t)count, sizeof (ut64)); if (!meta->alloc_items) { return false; @@ -1303,6 +1303,9 @@ static bool modern_skip_fill_record(ClusterStream *s, const ModernClusterMeta *m return false; } ut64 fields = shape & 0xffffULL; + if (meta->alloc_items && fields != meta->alloc_items[i]) { + return false; + } if (meta->alloc_items_count == meta->count && fields > meta->alloc_items_max) { return false; } @@ -2982,10 +2985,12 @@ typedef struct { bool edges_truncated; bool has_type_arguments; bool has_scalar; + bool has_shape; ut64 ref; ut64 count; ut64 type_arguments_ref; ut64 scalar; + ut64 shape; int cid; const char *kind; char *name; @@ -3092,6 +3097,46 @@ static bool modern_value_read_array(ModernValueGraph *graph, ModernValueNode *no return false; } +static bool modern_value_read_record(ModernValueGraph *graph, ModernValueNode *node, const ModernClusterMeta *meta) { + ClusterStream s = { + .ctx = graph->ctx, + .cursor = meta->fill_offset, + .end = meta->fill_end, +}; + const ut64 target = node->ref - meta->start_ref; + for (ut64 i = 0; i < meta->count; i++) { + ut64 shape = 0; + if (!cs_read_unsigned (&s, &shape)) { + return false; + } + const ut64 fields = shape & 0xffffULL; + if (!meta->alloc_items || fields != meta->alloc_items[i]) { + return false; + } + if (i == target) { + node->has_shape = true; + node->shape = shape; + if (!modern_value_edges_alloc (node, fields)) { + return false; + } + } + for (ut64 j = 0; j < fields; j++) { + ut64 ref = 0; + if (!cs_read_ref_id (&s, &ref)) { + return false; + } + if (i == target && j < node->edge_count) { + node->edges[j].index = j; + node->edges[j].ref = ref; + } + } + if (i == target) { + return true; + } + } + return false; +} + static bool modern_value_read_refs(ModernValueGraph *graph, ModernValueNode *node, const ModernClusterMeta *meta) { const ModernFillSpec spec = modern_get_fill_spec (&graph->cids, meta->cid); ClusterStream s = { @@ -3255,6 +3300,8 @@ static ModernValueNode *modern_value_graph_node(ModernValueGraph *graph, ut64 re } if (!strcmp (node->kind, "array")) { node->complete = modern_value_read_array (graph, node, meta); + } else if (meta->fill_kind == MODERN_FILL_RECORD) { + node->complete = modern_value_read_record (graph, node, meta); } else if (meta->fill_kind == MODERN_FILL_REFS) { node->complete = modern_value_read_refs (graph, node, meta); } else if (!strcmp (node->kind, "instance")) { @@ -3358,7 +3405,11 @@ static void modern_value_json_node(PJ *pj, ModernValueGraph *graph, ut64 ref, in if (node->has_type_arguments) { pj_kn (pj, "type_arguments_ref", node->type_arguments_ref); } - if (node->count || node->edges) { + if (node->has_shape) { + pj_kn (pj, "shape", node->shape); + pj_kn (pj, "field_names_index", node->shape >> 16); + } + if (node->count || node->edges || node->has_shape) { pj_kn (pj, "length", node->count); pj_kb (pj, "values_truncated", node->edges_truncated); } @@ -3425,6 +3476,9 @@ static void modern_value_text_node(RStrBuf *sb, ModernValueGraph *graph, ut64 re if (node->has_scalar) { r_strbuf_appendf (sb, " value=%" PFMT64d, (st64)node->scalar); } + if (node->has_shape) { + r_strbuf_appendf (sb, " shape=%" PRIu64 " length=%" PRIu64, (uint64_t)node->shape, (uint64_t)node->count); + } r_strbuf_appendf (sb, " complete=%s\n", r_str_bool (node->complete)); if (modern_value_path_contains (path, path_count, ref) || depth >= MODERN_VALUE_MAX_DEPTH) { return; diff --git a/test/db/cmd/record-values-android b/test/db/cmd/record-values-android new file mode 100644 index 0000000..040b111 --- /dev/null +++ b/test/db/cmd/record-values-android @@ -0,0 +1,23 @@ +NAME=record-values-android +FILE=bins/android/mafia/libapp.so +CMDS=<