21 Commits
Author SHA1 Message Date
pancake 02b0b84fa6 Release 0.6.0 2026-11-06 14:03:14 +01:00
pancake 0ee5e01498 Give duplicate Dart method names unique r2 flags and function names
r2 flag names are unique, so flagging a second function with a repeated
Dart name (e.g. Native._anon_closure) moved the flag away from the first
one, and current r2's af then named it after the Mach-O sym.func.* flag.
Suffix repeats with their address, use the same names for afs, and rename
analyzed functions after the af pass.

Also skip var recovery, call-following and jump tables in the signature
af pass (AuthPass -AAA 47s -> 29s) and raise R2R_TIMEOUT to 60s.
2026-11-06 13:54:07 +01:00
pancake cc338995cf Update mafia -z goldens for VM base-object pool strings 2026-11-06 13:37:57 +01:00
pancake 8a9e54be29 Support RVec fcn->bbs on R2_ABIVERSION >= 168 2026-11-05 12:57:12 +01:00
pancake 441d614937 Scan complete instruction ranges for standalone xrefs 2026-10-21 12:47:42 +02:00
pancake 25a5b3f026 Decode Record fields in snapshot value graphs 2026-10-21 12:46:56 +02:00
pancake 3bfdd5601a Resolve direct ObjectPool Code targets 2026-10-21 12:20:58 +02:00
pancake 84a25ea38c Resolve ObjectPool Field names and owners 2026-10-21 12:12:31 +02:00
pancake 2bf613618f Propagate ObjectPool collection strings into analysis 2026-10-21 10:52:18 +02:00
pancake a2fd82106e Include ObjectPool instance strings in -z output 2026-10-21 10:39:11 +02:00
pancake bd6f596279 Fix ObjectPool string xrefs through instance fields 2026-10-21 09:32:21 +02:00
pancake 7be0c178fb Release 0.5.1 2026-10-16 03:04:52 +02:00
pancake 3552d7485c Update class metadata golden for quoted pf output 2026-10-16 03:04:40 +02:00
pancake 878a588adf Release 0.5.0 2026-10-16 00:26:17 +02:00
pancake d0215dbfc2 Use r2-6.2.4 2026-10-16 00:26:09 +02:00
pancake 3cef0ce07a Fix signed 64-bit varint decoding
cs_read_tagged64 biased the final byte by -64 instead of -192, adding
128 << shift to every Mint/Smi value, which inflated field offsets and
decoded constants.
2026-10-09 16:05:25 +02:00
pancake 7d019ea748 Resolve class method entry points
Map each Function's code index (a raw InstructionTable slot, including
discarded-Code slots) to its entry address. Class-derived names only
replace placeholder names when merged into the function list, so
InstructionTable names stay authoritative for deduplicated code.
2026-10-09 16:05:23 +02:00
pancake f7c6cc5b3d Extract class metadata from Dart 2.14-3.3 snapshots
modern_can_extract_classes only accepted OBJECT_HEADER (3.4+) snapshots,
so CID_SHIFT1 apps such as Dart 2.19 fell back to the string heuristic
after the legacy parser failed. Gate on modern_supported instead.
2026-10-09 16:05:20 +02:00
pancake 24e3502737 Complete code xrefs for Dart 2.19 2026-10-06 17:50:40 +02:00
pancake 78719df43e Fix Dart 2.19 ObjectPool xrefs after Type fill parsing 2026-10-06 17:30:45 +02:00
pancake 3fe3c11bac Register the r2flutter pkg 2026-10-06 16:55:12 +02:00
52 changed files with 1448 additions and 350 deletions

No files matched your search

+1 -1
View File
@@ -1,7 +1,7 @@
name: ci
env:
R2V: 6.2.2
R2V: 6.2.4
on:
push:
+1 -1
View File
@@ -9,7 +9,7 @@ LDFLAGS += $(R2_LDFLAGS)
LDLIBS += $(R2_LIBS)
DEPFLAGS = -MMD -MP
R2R_JOBS ?= 1
R2R_TIMEOUT ?= 30
R2R_TIMEOUT ?= 60
R2_LIBR_PLUGINS=$(shell r2 -H R2_LIBR_PLUGINS 2> /dev/null)
R2_LIBEXT=$(shell r2 -H R2_LIBEXT 2> /dev/null)
-include config.mk
Vendored
+5 -5
View File
@@ -127,12 +127,12 @@ done
: ${INSTALL_PROGRAM_STRIP:=${INSTALL} -m 755 -s}
: ${INSTALL_MAN:=${INSTALL} -m 444}
: ${INSTALL_LIB:=${INSTALL} -m 755 -c}
PKGNAME='r2flutter' ; VERSION='0.4.0' ; VERSION_MAJOR=0; VERSION_MINOR=4; VERSION_PATCH=0; VERSION_NUMBER=00400; CONTACT_MAIL="[email protected]" ; CONTACT_NAME="pancake" ; CONTACT="pancake <[email protected]>" ;
PKGNAME='r2flutter' ; VERSION='0.6.0' ; VERSION_MAJOR=0; VERSION_MINOR=6; VERSION_PATCH=0; VERSION_NUMBER=00600; CONTACT_MAIL="[email protected]" ; CONTACT_NAME="pancake" ; CONTACT="pancake <[email protected]>" ;
}
show_usage() {
cat <<EOF2
'configure' configures r2flutter-0.4.0 to adapt to many kinds of systems.
'configure' configures r2flutter-0.6.0 to adapt to many kinds of systems.
Usage: ./configure [OPTION]... [VAR=VALUE]...
@@ -214,10 +214,10 @@ ocho() {
show_version() {
if [ "$QUIET" = 1 ]; then
echo "0.4.0"
echo "0.6.0"
exit 0
fi
echo "r2flutter-0.4.0 configuration script done with acr v2.2.8.
echo "r2flutter-0.6.0 configuration script done with acr v2.2.8.
The 'Free Software Foundation' message is only for autodetection.
Originally written by pancake <nopcode.org>."
exit 0
@@ -246,7 +246,7 @@ case $flag in
show_version ; ;;
-r|--r|--report)
echo "PKGNAME: r2flutter"
echo "VERSION: 0.4.0"
echo "VERSION: 0.6.0"
echo "AUTHOR: pancake"
echo "EMAIL: [email protected]"
echo "LANGS: c"
+1 -1
View File
@@ -1,5 +1,5 @@
PKGNAME r2flutter
VERSION 0.4.0
VERSION 0.6.0
CONTACT pancake ; [email protected]
LANG_C!
+270
View File
@@ -1797,3 +1797,273 @@ misaligned). Not yet handled: the x64 ObjectPool *slot* decoder (`-O pp+off`)
still misreads compressed pool pointers, so a slot that the collector did not
already resolve stays a bare offset; and there is no committed x86_64 test
fixture.
## Dart 2.19 Type fill must not consume the legacy combined byte
The Flutter 3.7.12/Dart 2.19.0 snapshot hash
`adb4292f3ec25074ca70abcd2d5c7251` exposed a one-byte fill-stream drift:
`r2flutter -HHH` stopped after the `TypeArguments` cluster and reported the
later ObjectPool as `fill_not_parsed`. Consequently `-AAA` could not build the
synthetic PP image or apply ObjectPool string xrefs; `axt 0x31903` for
`https://www.game250522linecfg.xyz` was empty.
The cause was treating all Dart 2.x `Type::ReadFill` records alike. Through
2.18 their three references are followed by `ReadUnsigned(type_class_id)` and
a one-byte combined nullability/type-state field. Dart 2.19 replaces that pair
with one `ReadUnsigned(flags)`. Consuming the obsolete byte desynchronizes the
rest of the fill stream. Keep the old rule only before 2.19; with the corrected
rule the ObjectPool reaches cluster 652, `-p` reconstructs 44955 entries, and
`r2flutter -AAA` applies 11560 graph xrefs, including an `axt` result for
0x31903.
## Code xrefs need discarded instructions and correctly shifted PP offsets
The Dart 2.19 sample above has 29622 instruction-table entries, but only 7545
retained `Code` objects. `first_entry_with_code=22077` separates instructions
whose `Code` metadata was discarded from entries with retained metadata; it
does not separate non-executable stubs from application code. The VM writer
in `runtime/vm/app_snapshot.cc` orders discarded Code objects first. Both URL
loaders are in that prefix (IT indices 14623 and 15109).
An unlimited instruction-table extraction must use the table header's length,
not the clustered header's retained Code count. `-AAA` must collect all table
entrypoints in addition to named methods so these functions get basic blocks
and their PP loads are scanned. `has_code` still controls Code-index metadata
lookups; it must not decide whether instructions can be analyzed.
ARM64 decoders can return an already shifted `RArchValue.imm` for
`add x17, x27, 0x46, lsl 12`. Applying the text's shift to that value again
produces `0x46000000`, breaking the subsequent pool lookup. Read the unshifted
immediate from the instruction text and apply the shift exactly once.
Verified with `/tmp/mal/libapp.so` (identical to `libapp.so.noxrefs`):
- `0x85b028` in `fcn.0085afb0` loads `PP+0x46200` and now has a direct string
xref to `0x73986` (`https://reg.gptreg0706.xyz`).
- `0x836f98` in `fcn.00836f4c` loads `PP+0x4e6c8` and now has a direct string
xref to `0x31903` (`https://www.game250522linecfg.xyz`).
The unaligned `0x20022d` and `0x2086f5` sources are synthetic pool slots in
the serialized snapshot address range, not executable use sites. `-AA` keeps
those pool links; `-AAA` adds the instruction-to-string links. Regression tests
cover a cached discarded-Code table on Android, and real shifted PP loads on
Android w0rdle and iOS Runner without depending on the private samples.
## Dart 2.19 class metadata uses the modern fill walker
`-c` printed ~280 `size=0` classes for a Dart 2.19 Android sample because
`modern_can_extract_classes` only accepted `OBJECT_HEADER` (3.4+) snapshots.
The legacy class parser then failed on the first (unified `String`) cluster
and the string heuristic took over. The modern walker already handles
`CID_SHIFT1` (2.14-3.3) for `-H`, pools and xrefs, and the Class fill layout
(13 refs: name=0, library=7, super_type=9) matches 2.19 product snapshots.
Gating on `modern_supported` recovers 6914 classes, 1515 fields and 7398
methods on that sample.
## Class methods resolve entry points from InstructionTable slots
`Function` fills only carry a code index; `DartMethodInfo.entry_point` was
never set, so every class method printed `0x00000000` on all versions. The
index is the raw InstructionTable slot, not the `first_entry_with_code`
relative `DartInstructionTableEntry.code_index`. Slots below
`first_entry_with_code` belong to functions whose Code object was discarded
(`dwarf_stack_traces_mode`), but they still own real instructions, so they are
mapped too. On the 2.19 sample 22077 of 29622 slots are like that; `-f` used
to list only the 7545 Code-backed ones and now also gets the 4614 named
methods in the discarded range through `dart_app_merge_class_methods`.
InstructionTable names stay authoritative there: `dedup_instructions` makes
several methods share one body, so class-derived names only replace
placeholder (`func.*`, `method.fn_*`) names.
## Signed 64-bit varints use the 192 end-byte marker
`cs_read_tagged64` decoded the final varint byte as `raw - 64` instead of
`raw - 192` (`ReadStream::Read<T>` uses `kEndByteMarker`), adding
`128 << shift` to every Mint/Smi. Field offsets (`host_offset_or_field_id`)
came out as 128 + words, e.g. `_Closure._context` at `+0x428` instead of
`+0x28` on 64-bit, and the `SemanticsAction` const map key `tap` decoded as
129 instead of 1. `cs_read_tagged32` was already correct.
## ObjectPool instances can carry strings through their fields
`t/libapp_1.so` loads `PP+0x17200` at `0xfb4ae8`. The slot references
snapshot object 165628, a `Ubb` instance derived from `_Enum`; its string field
references object 33665 (`privilegedAccess`, payload `0xbcedf`). Blutter's
`pp.txt` confirms enum index 9 and the name at instance offset `0x10`.
A direct-String-only PP lookup consequently misses this use.
Analysis now collects direct strings and immediate string fields of pool
instances. `-AA` links the synthetic pool slot (`0x30a1b0` in this sample) to
the string, and `-AAA` links the loading instruction and adds a `contains
string` comment while retaining the `instance` label. Each slot can contain
multiple strings; they must not collapse to its first string field. This
analysis path stops at immediate fields rather than traversing metadata or
arbitrary object graphs.
Instance field counts include an eight-byte header expressed in compressed
words: two words on compressed Android snapshots, one on uncompressed iOS.
Using a fixed two-word header skips the enum name on iOS. The collector also
loads ROData strings for uncompressed snapshots and preserves VM base-object
strings. Regression tests use `Brightness.dark` in Android w0rdle and iOS
Runner to cover both layouts, pool xrefs, and instruction comments/xrefs.
PP resolution can initialize `ctx->layout` from a local stack buffer. Callers
that need to continue parsing after it returns must keep their own live
layout across resolution and free it afterward; otherwise the field-string
collector can silently miss all slots on either platform.
Instance fill offsets are indexed once per cluster while collecting strings.
Rescanning a cluster prefix for each PP slot makes large const collections
quadratic, even when only immediate fields are read.
## Reliable string extraction shares the PP instance-field collector
`-z` previously accepted only direct String ObjectPool entries, so it omitted
`privilegedAccess` even after analysis could recover its enum instance field.
Reliable extraction now uses the same direct-string and immediate-instance-field
collector as PP analysis. This also resolves uncompressed iOS ROData strings
and VM base-object strings through that collector.
Strings remain deduplicated by snapshot string reference. `-xz` distinguishes
`object_pool.entry` from `object_pool.instance_field`, recording the containing
pool entry and field index. Both reference kinds use the synthetic PP slot as
the xref source. The shared extraction makes the separate instance-field xref
pass redundant; `-AA` now applies the references from the extracted strings.
Regression cases cover the instance-only `AppLifecycleState.detached` name on
Android w0rdle and iOS Runner, plus a name (`dark`) reachable both directly and
through an instance to check that it is emitted once.
## Collection values propagate their strings into PP analysis
Android w0rdle `PP+0x5d0` and iOS Runner `PP+0x3d0` hold the ten
`PointerChange` enum instances. The value inspector decoded their names, but
the analysis collector rejected every collection before inspecting its
contents. The same restriction missed strings in map/set backing arrays.
The value collector now follows arrays, weak arrays, growable arrays, maps,
sets, and instances, while excluding Class/Function/type metadata. Map/set
storage adds an extra hop, so value traversal allows eight edges of depth
and at most 4096 reference visits per pool entry. Per-entry visited depths
stop cycles and allow revisiting a shared object reached by a shorter path.
Null references count toward the visit budget too.
Array and reference-object fill offsets are indexed once per cluster, as
instance offsets already were, to avoid repeatedly scanning array prefixes.
`-xz` marks strings reached through collections as `object_pool.collection`
and records their immediate leaf kind/index. `-AA` links their pool slots;
`-AAA` adds instruction xrefs and `contains string` comments while retaining
the collection's original kind. Regression fixtures cover both platforms and
cyclic arrays with shared elements behind mutable/const map and set storage.
## Field ObjectPool entries resolve their owner and name
W0rdle `PP+0x60` references Field object 52876 (`Platform.isAndroid`). The
field fill was decoded for class extraction but the PP resolver classified
Field by its generic allocation kind (`simple`) and retained no name/owner
references. iOS Runner `PP+0x120` similarly references
`Zone._current@4048458` (object 40563).
The shared resolver now records Field name/owner references while walking
fills and resolves owners through Class, Library, or bounded PatchClass
chains. Field labels use `Owner.name`, preserve private-name suffixes, and
apply the obfuscation map to both parts. Missing owners leave the field name
alone; missing names leave a `field` kind without a fabricated label. PP
inspection, cluster summaries, reference inspection, and analysis comments
share that resolution.
Uncompressed isolate field names are ROData strings. Loading those strings
in the shared resolver enables field labels on iOS as well as compressed
Android snapshots and replaces duplicate ROData loading in value graphs and
string collection. Regression fixtures cover both platforms, patched and
cyclic owners, missing names/owners, and deobfuscation.
## Direct Code pool references use Code allocation indices
ObjectPool resolution recognized Code objects but only assigned a target index
for Function references. Code-slot loads and indirect calls therefore had a
`code` annotation without a navigable instruction target.
The main Code allocation range has the same order as the Code-backed part of
the InstructionTable. Its zero-based index is `ref - cluster.start_ref`, which
matches `DartInstructionTableEntry.code_index`; the table extractor handles
`first_entry_with_code` when mapping that index to a payload address. Do not
reuse the serialized Function index here: that index is biased by one and
refers to the whole table. Deferred Code objects follow the main allocation
range but have no instructions in this loading unit, so they keep an unresolved
target. A discarded record in older Code clusters still occupies its ordinal.
On Android w0rdle, `PP+0x40` holds ref 33249 in the Code cluster starting at
22433, so it resolves to index 10816 and table address `0x3f1074`. Analysis now
adds a CALL xref from `blr x0` at `0x3f28cc`, preserving the PP association
through the Code entry-point load. `PP+0x48` resolves to the following index.
On iOS AuthPass, `PP+0x68` holds ref 168520 in the Code cluster starting at
132121, yielding index 36399 and table address `0x6d9620`. The synthetic
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.
## 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.
## Duplicate Dart method names need unique r2 flags and function names
Several functions can share a Dart name (e.g. many `Native._anon_closure`).
r2 flag and function names are unique, so `r_flag_set` on a repeated name
moves the flag to the later function. Older r2 left a stale per-address entry
behind, which let `af` still pick the moved `method.*` name; current r2 does
not, and `af` also prefers symbol-space flags such as Mach-O `sym.func.*`.
The first function keeps `method.<name>` and repeats get a `.0x<addr>` suffix,
computed once in app order and shared by the flags, the `afs` prototypes and
`dart_dumper_name_functions`, which renames analyzed functions after the `af`
pass. The signature `af` storm also disables `anal.vars`, `anal.calls` and
`anal.jmp.tbl` like `flutter_ensure_functions`; var recovery was about half
of its cost (AuthPass `-AAA` 47s -> 29s).
+1
View File
@@ -11,6 +11,7 @@ char *dart_dumper_dump_r2(DartApp *app);
char *dart_dumper_dump_funcs(DartApp *app, int fmt);
void dart_dumper_apply_to_core(DartApp *app, bool apply_signatures);
void dart_dumper_apply_signatures(DartApp *app);
void dart_dumper_name_functions(DartApp *app);
// Disable anal.trycatch while r2flutter runs many `af` commands, when no try.*
// flag exists for it to find. Returns the previous value to pass to the restore.
bool dart_core_trycatch_suspend(RCore *core);
+2 -2
View File
@@ -1,9 +1,9 @@
#ifndef R2FLUTTER_VERSION_H
#define R2FLUTTER_VERSION_H
#define R2FLUTTER_VERSION "0.4.0"
#define R2FLUTTER_VERSION "0.6.0"
#define R2FLUTTER_VERSION_MAJOR 0
#define R2FLUTTER_VERSION_MINOR 4
#define R2FLUTTER_VERSION_MINOR 6
#define R2FLUTTER_VERSION_PATCH 0
#endif
+1 -1
View File
@@ -1,7 +1,7 @@
project(
'r2flutter',
'c',
version : '0.4.0',
version : '0.6.0',
meson_version : '>=0.56.0',
default_options : ['warning_level=2', 'werror=false']
)
+13 -4
View File
@@ -265,6 +265,9 @@ static int dart_function_cmp(const DartFunction *fa, const DartFunction *fb) {
return 0;
}
// Above placeholder names (func.*, method.fn_*) but below any resolved name.
#define DART_CLASS_METHOD_NAME_QUALITY 20
static int dart_function_name_quality(const char *name) {
if (R_STR_ISEMPTY (name)) {
return 0;
@@ -476,14 +479,17 @@ static char *dart_format_method_full_name(const DartClassInfo *ci, const DartMet
if (!mi || R_STR_ISEMPTY (mi->name)) {
return NULL;
}
char *lib = dart_sanitize_component (ci && ci->library_name? ci->library_name: "app", true);
// Without a library name use the InstructionTable form (method.Owner.leaf)
// so both sources agree on the name of the same entry.
char *lib = ci && ci->library_name? dart_sanitize_component (ci->library_name, true): NULL;
char *owner = dart_sanitize_component (ci && ci->name? ci->name: mi->owner_name, false);
char *leaf = dart_format_method_leaf_name (ci? ci->name: mi->owner_name, mi);
const char *name = leaf? leaf: mi->name;
char *out = NULL;
if (R_STR_ISNOTEMPTY (owner)) {
out = r_str_newf ("method.%s.%s.%s", lib, owner, leaf? leaf: mi->name);
out = lib? r_str_newf ("method.%s.%s.%s", lib, owner, name): r_str_newf ("method.%s.%s", owner, name);
} else {
out = r_str_newf ("method.%s..%s", lib, leaf? leaf: mi->name);
out = lib? r_str_newf ("method.%s..%s", lib, name): r_str_newf ("method.%s", name);
}
r_name_filter (out, 0);
free (lib);
@@ -533,11 +539,14 @@ static void dart_app_merge_class_methods(DartApp *app) {
continue;
}
char *fullname = dart_format_method_full_name (ci, mi);
// InstructionTable names stay authoritative: deduplicated code is
// shared by several methods, so class metadata only fills the
// placeholder names the table could not resolve.
dart_app_add_or_update_fn (app,
fullname? fullname: mi->name,
dart_normalize_code_addr (mi->entry_point),
0,
dart_function_name_quality (fullname? fullname: mi->name),
R_MIN (dart_function_name_quality (fullname? fullname: mi->name), DART_CLASS_METHOD_NAME_QUALITY),
mi->signature);
free (fullname);
}
+80 -8
View File
@@ -124,8 +124,61 @@ static char *make_method_flagname(const char *name) {
return flagname;
}
static void dump_func_r2(RStrBuf *sb, const DartFunction *fn, bool quiet) {
char *flagname = make_method_flagname (fn->name);
// Flag and function names are unique in r2: setting an existing flag name
// moves it, so a later function sharing a Dart name (e.g. several
// Native._anon_closure) would steal it from the first one. Suffix repeated
// names with the entry address.
static char *make_unique_method_flagname(HtPP *seen, const char *name, ut64 addr) {
char *flagname = make_method_flagname (name);
bool found = false;
ut64 owner = (ut64) (size_t)ht_pp_find (seen, flagname, &found);
if (found && owner != addr) {
char *unique = r_str_newf ("%s.0x%" PFMT64x, flagname, addr);
free (flagname);
return unique;
}
ht_pp_insert (seen, flagname, (void *) (size_t)addr);
return flagname;
}
static void method_flagname_free(HtUPKv *kv) {
free (kv->value);
}
// Map each function address to its unique method.* name, in app order.
static HtUP *method_flagnames(DartApp *app) {
HtUP *names = ht_up_new (NULL, method_flagname_free, NULL);
HtPP *seen = ht_pp_new0 ();
DartFunction *fn;
R_VEC_FOREACH (app->functions, fn) {
if (fn->name && !ht_up_find (names, fn->addr, NULL)) {
ht_up_insert (names, fn->addr, make_unique_method_flagname (seen, fn->name, fn->addr));
}
}
ht_pp_free (seen);
return names;
}
// Give each analyzed function its Dart name: r2's `af` prefers flags in the
// symbols space (e.g. Mach-O sym.func.*) over the method.* flags set here.
void dart_dumper_name_functions(DartApp *app) {
if (!app || !app->core || !app->functions) {
return;
}
HtUP *names = method_flagnames (app);
DartFunction *fn;
R_VEC_FOREACH (app->functions, fn) {
RAnalFunction *fcn = r_anal_get_function_at (app->core->anal, fn->addr);
const char *name = fcn? ht_up_find (names, fn->addr, NULL): NULL;
if (name && strcmp (fcn->name, name)) {
r_anal_function_rename (fcn, name);
}
}
ht_up_free (names);
}
static void dump_func_r2(RStrBuf *sb, HtPP *seen, const DartFunction *fn, bool quiet) {
char *flagname = make_unique_method_flagname (seen, fn->name, fn->addr);
r_strbuf_appendf (sb, "f %s = 0x%" PFMT64x "\n", flagname, (uint64_t)fn->addr);
if (!quiet) {
r_strbuf_appendf (sb, "'@0x%" PFMT64x "'CC %s\n", (uint64_t)fn->addr, fn->name);
@@ -145,13 +198,15 @@ char *dart_dumper_dump_r2(DartApp *app) {
r_strbuf_appendf (sb, "f app.heap_base = 0x%" PFMT64x "\n", (uint64_t)app->heap_base);
if (app->functions) {
HtPP *seen = ht_pp_new0 ();
DartFunction *fn;
R_VEC_FOREACH (app->functions, fn) {
if (!fn->name) {
continue;
}
dump_func_r2 (sb, fn, quiet);
dump_func_r2 (sb, seen, fn, quiet);
}
ht_pp_free (seen);
}
r_strbuf_append (sb, "dr x27=`e anal.gp`\n");
@@ -219,6 +274,7 @@ char *dart_dumper_dump_funcs(DartApp *app, int fmt) {
}
RStrBuf *sb = r_strbuf_new ("");
HtPP *seen = ht_pp_new0 ();
R_VEC_FOREACH (app->functions, fn) {
if (!fn->name) {
continue;
@@ -227,12 +283,13 @@ char *dart_dumper_dump_funcs(DartApp *app, int fmt) {
break;
}
if (fmt == 'r') {
dump_func_r2 (sb, fn, app->dctx.quiet);
dump_func_r2 (sb, seen, fn, app->dctx.quiet);
} else {
dump_func_text (sb, fn);
}
count++;
}
ht_pp_free (seen);
return drain_trimmed_strbuf (sb);
}
@@ -419,17 +476,27 @@ void dart_dumper_apply_signatures(DartApp *app) {
}
}
qsort (order, n, sizeof (DartFunction *), dart_fn_addr_desc);
HtUP *names = method_flagnames (app);
// Keep the analysis local to each function start; the surrounding pass owns
// broader discovery. String ESIL emulation is very slow and irrelevant to
// prototypes, so disable it for the duration of this pass.
// Variable recovery, call-following and jump tables are not needed for the
// prototypes either, and var recovery alone is about half of this `af` cost.
int prev_hasnext = r_config_get_i (core->config, "anal.hasnext");
int prev_emustr = r_config_get_i (core->config, "emu.str");
bool prev_calls = r_config_get_b (core->config, "anal.calls");
bool prev_vars = r_config_get_b (core->config, "anal.vars");
bool prev_jmptbl = r_config_get_b (core->config, "anal.jmp.tbl");
r_config_set_i (core->config, "anal.hasnext", 0);
r_config_set_i (core->config, "emu.str", 0);
r_config_set_b (core->config, "anal.calls", false);
r_config_set_b (core->config, "anal.vars", false);
r_config_set_b (core->config, "anal.jmp.tbl", false);
const bool prev_trycatch = dart_core_trycatch_suspend (core);
for (size_t i = 0; i < n; i++) {
fn = order[i];
char *name = strdup (fn->name? fn->name: "fcn");
const char *flagname = ht_up_find (names, fn->addr, NULL);
char *name = strdup (flagname? flagname: "fcn");
r_name_filter (name, 0);
if (R_STR_ISEMPTY (name)) {
free (name);
@@ -452,7 +519,11 @@ void dart_dumper_apply_signatures(DartApp *app) {
}
r_config_set_i (core->config, "anal.hasnext", prev_hasnext);
r_config_set_i (core->config, "emu.str", prev_emustr);
r_config_set_b (core->config, "anal.calls", prev_calls);
r_config_set_b (core->config, "anal.vars", prev_vars);
r_config_set_b (core->config, "anal.jmp.tbl", prev_jmptbl);
dart_core_trycatch_restore (core, prev_trycatch);
ht_up_free (names);
free (order);
}
@@ -468,16 +539,17 @@ void dart_dumper_apply_to_core(DartApp *app, bool apply_signatures) {
}
if (app->functions) {
HtUP *names = method_flagnames (app);
DartFunction *fn;
R_VEC_FOREACH (app->functions, fn) {
if (!fn->name) {
const char *flagname = ht_up_find (names, fn->addr, NULL);
if (!fn->name || !flagname) {
continue;
}
char *flagname = make_method_flagname (fn->name);
r_flag_set (core->flags, flagname, fn->addr, fn->size);
r_meta_set_string (core->anal, R_META_TYPE_COMMENT, fn->addr, fn->name);
free (flagname);
}
ht_up_free (names);
}
r_core_cmd0 (core, "e emu.str=true");
+43
View File
@@ -1488,6 +1488,46 @@ static void resolve_class_and_field_names(DartCtx *ctx, RList *class_list, RList
}
}
// Function fills only carry an InstructionTable slot; decode the table once
// and turn each method's slot into its entry address.
static void resolve_method_entry_points(DartCtx *ctx, RList *class_list) {
const int dump_snapshot_json = ctx->dump_snapshot_json;
const int dump_fns_limit = ctx->dump_fns_limit;
ctx->dump_snapshot_json = 0;
ctx->dump_fns_limit = 0;
RVecDartInstructionTableEntry *it = dart_pool_extract_instruction_table (ctx);
ctx->dump_snapshot_json = dump_snapshot_json;
ctx->dump_fns_limit = dump_fns_limit;
if (!it) {
return;
}
ut64 count = 0;
DartInstructionTableEntry *ie;
R_VEC_FOREACH (it, ie) {
count = R_MAX (count, ie->index + 1);
}
// Function::code_index is the raw InstructionTable slot, not the
// first_entry_with_code-relative index kept in ie->code_index. Slots below
// first_entry_with_code lost their Code object (discarded in AOT) but
// still own real instructions, so map them too.
ut64 *addr_by_code_index = calloc (count + 1, sizeof (ut64));
R_VEC_FOREACH (it, ie) {
addr_by_code_index[ie->index] = ie->address;
}
RListIter *iter, *miter;
DartClassInfo *ci;
DartMethodInfo *mi;
r_list_foreach (class_list, iter, ci) {
r_list_foreach (ci->methods, miter, mi) {
if (!mi->entry_point && mi->code_index < count) {
mi->entry_point = addr_by_code_index[mi->code_index];
}
}
}
free (addr_by_code_index);
dart_instruction_table_list_free (it);
}
RList *dart_pool_extract_classes(DartCtx *ctx) {
if (!ctx || !ctx->core) {
return NULL;
@@ -1693,6 +1733,9 @@ RList *dart_pool_extract_classes(DartCtx *ctx) {
}
}
dart_ctx_fini_layout (ctx, layout_owned);
if (modern_classes) {
resolve_method_entry_points (ctx, class_list);
}
return class_list;
}
+366 -193
View File
@@ -120,6 +120,8 @@ typedef struct {
ut64 *function_name_ref;
ut64 *function_owner_ref;
ut64 *function_code_index;
ut64 *field_name_ref;
ut64 *field_owner_ref;
} ModernRefResolver;
typedef struct {
@@ -392,6 +394,7 @@ typedef struct {
// handful of clusters. Their readers are gated on this flag; everything
// else is shared.
bool legacy_format;
bool legacy_type_combined_byte;
bool shift1_format;
bool closure_variable;
// every CID this layout defines, indexed by kind; -1 when the kind is absent
@@ -468,6 +471,7 @@ static ModernCidCache modern_cid_cache_init(const DartVerLayout *layout) {
.typed_data_internal_limit = dart_cid_typed_data_limit (layout),
.typed_data_stride = dart_cid_typed_data_stride (layout),
.legacy_format = modern_layout_is_legacy (layout),
.legacy_type_combined_byte = layout && dart_version_compare (layout->dart_version, "2.19.0") == -1,
.shift1_format = layout && layout->tag_style == DART_TAG_STYLE_CID_SHIFT1,
.closure_variable = layout && layout->closure_variable,
};
@@ -756,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;
@@ -1077,14 +1081,26 @@ static ModernFillSpec modern_get_fill_spec(const ModernCidCache *cids, int cid)
static const ModernFillSpecRule legacy_rules[] = {
// FfiTrampolineData: ReadFromTo (4) + ReadUnsigned (callback_id).
{ DART_CID_FFI_TRAMPOLINE_DATA, MODERN_FILL_REFS, 4, -1, -1, 1, { MODERN_SCALAR_UNSIGNED } },
// Type: ReadFromTo (3) + ReadUnsigned (type_class_id) + Read<uint8_t>.
{ DART_CID_TYPE, MODERN_FILL_REFS, 3, -1, -1, 2, { MODERN_SCALAR_UNSIGNED, MODERN_SCALAR_UINT8 } },
// TypeParameter: ReadFromTo (3) + Read<int32_t> + 3x Read<uint8_t>.
{ DART_CID_TYPE_PARAMETER, MODERN_FILL_REFS, 3, -1, -1, 4, { MODERN_SCALAR_TAGGED32, MODERN_SCALAR_UINT8, MODERN_SCALAR_UINT8, MODERN_SCALAR_UINT8 } },
// SubtypeTestCache::ReadFill reads only the cache_ ref.
{ DART_CID_SUBTYPE_TEST_CACHE, MODERN_FILL_REFS, 1, -1, -1, 0, { 0 } },
};
if (cids->legacy_format) {
if (modern_cid_eq (cid, cids->type) && cids->legacy_type_combined_byte) {
// Through Dart 2.18 Type::ReadFill has type_class_id followed by a
// combined nullability/type-state byte. Dart 2.19 replaced those with
// a single flags ReadUnsigned. Keeping the old byte for 2.19 shifts
// every following fill cluster and makes the ObjectPool unreachable.
return modern_fill_spec (&(ModernFillSpecRule){
.kind = MODERN_FILL_REFS,
.num_refs = 3,
.name_idx = -1,
.owner_idx = -1,
.scalar_count = 2,
.scalars = { MODERN_SCALAR_UNSIGNED, MODERN_SCALAR_UINT8 },
});
}
for (size_t i = 0; i < R_ARRAY_SIZE (legacy_rules); i++) {
if (modern_cid_of_kind (cids, legacy_rules[i].cid_kind) == cid) {
return modern_fill_spec (&legacy_rules[i]);
@@ -1287,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;
}
@@ -1957,6 +1976,8 @@ static void modern_ref_resolver_fini(ModernRefResolver *resolver) {
free (resolver->function_name_ref);
free (resolver->function_owner_ref);
free (resolver->function_code_index);
free (resolver->field_name_ref);
free (resolver->field_owner_ref);
memset (resolver, 0, sizeof (*resolver));
}
@@ -2021,6 +2042,37 @@ static bool modern_resolver_read_function_fill(ModernWalk *w, ClusterStream *s,
return true;
}
static bool modern_resolver_read_field_fill(ModernWalk *w, ClusterStream *s, const ModernClusterMeta *meta) {
ModernRefResolver *resolver = w->user;
const ModernFillSpec spec = modern_get_fill_spec (&w->cids, meta->cid);
for (ut64 j = 0; j < meta->count; j++) {
ut64 name_ref = 0;
ut64 owner_ref = 0;
for (int k = 0; k < spec.num_refs; k++) {
ut64 ref = 0;
if (!cs_read_ref_id (s, &ref)) {
return false;
}
if (k == spec.name_idx) {
name_ref = ref;
} else if (k == spec.owner_idx) {
owner_ref = ref;
}
}
for (int k = 0; k < spec.scalar_count; k++) {
if (!modern_skip_scalar (s, spec.scalars[k])) {
return false;
}
}
const ut64 ref = meta->start_ref + j;
if (ref < resolver->refs_count) {
resolver->field_name_ref[ref] = name_ref;
resolver->field_owner_ref[ref] = owner_ref;
}
}
return true;
}
static bool modern_resolver_read_library_fill(ModernWalk *w, ClusterStream *s, const ModernClusterMeta *meta) {
ModernRefResolver *resolver = (ModernRefResolver *)w->user;
const ModernFillSpec spec = modern_get_fill_spec (&w->cids, meta->cid);
@@ -2109,6 +2161,7 @@ static bool modern_direct_resolver_read_strings(ModernWalk *w, ClusterStream *s,
static const ModernFillHandler modern_resolver_fill_handlers[] = {
{ DART_CID_CLASS, modern_resolver_read_class_fill },
{ DART_CID_FUNCTION, modern_resolver_read_function_fill },
{ DART_CID_FIELD, modern_resolver_read_field_fill },
{ DART_CID_LIBRARY, modern_resolver_read_library_fill },
{ DART_CID_PATCH_CLASS, modern_resolver_read_patch_class_fill },
{ 0, NULL },
@@ -2135,7 +2188,9 @@ static bool modern_ref_resolver_init(ModernRefResolver *resolver, DartCtx *ctx,
resolver->function_name_ref = (ut64 *)calloc ((size_t)resolver->refs_count, sizeof (ut64));
resolver->function_owner_ref = (ut64 *)calloc ((size_t)resolver->refs_count, sizeof (ut64));
resolver->function_code_index = (ut64 *)calloc ((size_t)resolver->refs_count, sizeof (ut64));
if (!resolver->cluster_by_ref || !resolver->strings_by_ref || !resolver->string_addr_by_ref || !resolver->string_flags_by_ref || !resolver->class_name_ref || !resolver->class_id_by_ref || !resolver->library_name_ref || !resolver->patch_wrapped_ref || !resolver->function_name_ref || !resolver->function_owner_ref || !resolver->function_code_index) {
resolver->field_name_ref = (ut64 *)calloc ((size_t)resolver->refs_count, sizeof (ut64));
resolver->field_owner_ref = (ut64 *)calloc ((size_t)resolver->refs_count, sizeof (ut64));
if (!resolver->cluster_by_ref || !resolver->strings_by_ref || !resolver->string_addr_by_ref || !resolver->string_flags_by_ref || !resolver->class_name_ref || !resolver->class_id_by_ref || !resolver->library_name_ref || !resolver->patch_wrapped_ref || !resolver->function_name_ref || !resolver->function_owner_ref || !resolver->function_code_index || !resolver->field_name_ref || !resolver->field_owner_ref) {
modern_ref_resolver_fini (resolver);
return false;
}
@@ -2159,6 +2214,16 @@ static bool modern_ref_resolver_init(ModernRefResolver *resolver, DartCtx *ctx,
w.on_string_fill = modern_resolver_read_strings;
w.user = resolver;
modern_walk_fill_recorded (&w, meta, num_clusters);
// Uncompressed snapshots keep field and owner names in the data image.
const ut64 bases[] = { ctx->iso_data, ctx->vm_data };
for (size_t i = 0; i < R_ARRAY_SIZE (bases); i++) {
DartSnapshotHeader sh = { 0 };
ut64 base = bases[i];
if (base && dart_snapshot_header_read (ctx, base, &sh) && sh.ok && sh.cluster_start == meta[0].tag_offset) {
modern_load_rodata_strings (ctx, meta, num_clusters, base, sh.total_len, resolver->strings_by_ref, resolver->string_addr_by_ref, resolver->refs_count);
break;
}
}
return true;
}
@@ -2208,6 +2273,9 @@ static const char *modern_ref_kind_for_meta(const ModernCidCache *cids, const Mo
if (modern_cid_eq (meta->cid, cids->function)) {
return "function";
}
if (modern_cid_eq (meta->cid, cids->field)) {
return "field";
}
if (modern_cid_eq (meta->cid, cids->library)) {
return "library";
}
@@ -2220,6 +2288,22 @@ static const char *modern_ref_kind_for_meta(const ModernCidCache *cids, const Mo
return modern_alloc_kind_name (meta->alloc_kind);
}
static char *modern_build_field_name(DartCtx *ctx, const char *owner, const char *name) {
if (R_STR_ISEMPTY (name)) {
return NULL;
}
char *owner_name = owner? strdup (owner): NULL;
char *field_name = strdup (name);
dart_obf_apply (ctx, &owner_name);
dart_obf_apply (ctx, &field_name);
char *full = R_STR_ISNOTEMPTY (owner_name) && strcmp (owner_name, "::")
? r_str_newf ("%s.%s", owner_name, field_name)
: strdup (field_name);
free (owner_name);
free (field_name);
return full;
}
static void modern_ref_resolver_resolve(ModernRefResolver *resolver, const ModernCidCache *cids, ut64 ref, ModernResolvedRef *resolved) {
if (!resolved) {
return;
@@ -2255,22 +2339,38 @@ static void modern_ref_resolver_resolve(ModernRefResolver *resolver, const Moder
}
return;
}
if (!strcmp (resolved->kind, "code")) {
// Instruction-table code indices follow the main Code allocation order.
// Deferred objects have no instructions in this loading unit's table.
ut64 index = ref - meta->start_ref;
if (index < meta->main_count) {
resolved->code_index = index;
}
return;
}
const ModernRefNameMap names = {
.strings_by_ref = resolver->strings_by_ref,
.class_name_ref = resolver->class_name_ref,
.library_name_ref = resolver->library_name_ref,
.patch_wrapped_ref = resolver->patch_wrapped_ref,
.function_name_ref = resolver->function_name_ref,
.refs_count = resolver->refs_count,
};
if (!strcmp (resolved->kind, "function")) {
ut64 name_ref = resolver->function_name_ref[ref];
const char *method_name = name_ref < resolver->refs_count? resolver->strings_by_ref[name_ref]: NULL;
const ModernRefNameMap names = {
.strings_by_ref = resolver->strings_by_ref,
.class_name_ref = resolver->class_name_ref,
.library_name_ref = resolver->library_name_ref,
.patch_wrapped_ref = resolver->patch_wrapped_ref,
.function_name_ref = resolver->function_name_ref,
.refs_count = resolver->refs_count,
};
const char *owner_name = modern_resolve_ref_name (&names, resolver->function_owner_ref[ref], 0);
resolved->name = modern_build_full_name (resolver->ctx, owner_name, method_name);
resolved->code_index = resolver->function_code_index[ref];
return;
}
if (!strcmp (resolved->kind, "field")) {
ut64 name_ref = resolver->field_name_ref[ref];
const char *field_name = name_ref < resolver->refs_count? resolver->strings_by_ref[name_ref]: NULL;
const char *owner_name = modern_resolve_ref_name (&names, resolver->field_owner_ref[ref], 0);
resolved->name = modern_build_field_name (resolver->ctx, owner_name, field_name);
return;
}
if (!strcmp (resolved->kind, "library")) {
ut64 name_ref = resolver->library_name_ref[ref];
if (name_ref < resolver->refs_count && R_STR_ISNOTEMPTY (resolver->strings_by_ref[name_ref])) {
@@ -2885,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;
@@ -2995,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 = {
@@ -3144,6 +3286,10 @@ static ModernValueNode *modern_value_graph_node(ModernValueGraph *graph, ut64 re
}
if (!strcmp (node->kind, "instance")) {
node->name = modern_value_class_name (graph, meta->cid);
} else if (!strcmp (node->kind, "field")) {
ModernResolvedRef resolved;
modern_ref_resolver_resolve (&graph->resolver, &graph->cids, ref, &resolved);
node->name = resolved.name;
}
if (meta->fill_kind == MODERN_FILL_NONE) {
node->complete = true;
@@ -3154,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")) {
@@ -3183,15 +3331,7 @@ ModernValueGraph *modern_value_graph_new(const ModernReq *req, ut64 root_ref) {
for (ut64 i = 0; i < req->num_clusters; i++) {
graph->valid_refs_count = R_MAX (graph->valid_refs_count, graph->meta[i].start_ref + graph->meta[i].count);
}
const ut64 snapshot_bases[] = { ctx->iso_data, ctx->vm_data };
for (size_t i = 0; i < R_ARRAY_SIZE (snapshot_bases); i++) {
DartSnapshotHeader sh = { 0 };
const ut64 base = snapshot_bases[i];
if (base && dart_snapshot_header_read (ctx, base, &sh) && sh.ok && sh.cluster_start == req->cluster_start) {
modern_load_rodata_strings (ctx, graph->meta, req->num_clusters, base, sh.total_len, graph->resolver.strings_by_ref, graph->resolver.string_addr_by_ref, graph->resolver.refs_count);
break;
}
}
graph->nodes = (ModernValueNode **)calloc ((size_t)graph->resolver.refs_count, sizeof (ModernValueNode *));
if (!graph->nodes) {
modern_value_graph_free (graph);
@@ -3265,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);
}
@@ -3332,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;
@@ -3477,6 +3624,9 @@ bool modern_resolve_pp_slot(const ModernReq *req, ut64 pp_offset, ModernPoolSlot
return ok;
}
#define MODERN_POOL_VALUE_MAX_DEPTH 8
#define MODERN_POOL_VALUE_MAX_REFS 4096
typedef struct {
const ModernRefResolver *resolver;
const ModernCidCache *cids;
@@ -3484,9 +3634,14 @@ typedef struct {
void *user;
HtUP *seen;
ut64 pp_offset;
ut64 pp_base;
ut64 pool_ref;
ut64 pool_index;
ut64 entry_index;
bool values_only;
bool via_collection;
ut64 remaining;
ut64 *value_offsets;
} ModernPoolStringRefCollector;
static void modern_emit_pool_string_ref(ModernPoolStringRefCollector *collector, ut64 target_ref, const char *target_kind, ut64 field_index, ut64 string_ref) {
@@ -3496,6 +3651,7 @@ static void modern_emit_pool_string_ref(ModernPoolStringRefCollector *collector,
}
ModernPoolStrRef info = {
.pp_offset = collector->pp_offset,
.pp_base = collector->pp_base,
.pool_ref = collector->pool_ref,
.pool_index = collector->pool_index,
.entry_index = collector->entry_index,
@@ -3504,6 +3660,8 @@ static void modern_emit_pool_string_ref(ModernPoolStringRefCollector *collector,
.field_index = field_index,
.string_ref = string_ref,
.string_addr = resolver->string_addr_by_ref[string_ref],
.string_flags = resolver->string_flags_by_ref[string_ref],
.via_collection = collector->via_collection,
.string_value = resolver->strings_by_ref[string_ref],
};
collector->cb (&info, collector->user);
@@ -3512,71 +3670,70 @@ static void modern_emit_pool_string_ref(ModernPoolStringRefCollector *collector,
static bool modern_collect_ref_strings(ModernPoolStringRefCollector *collector, ut64 target_ref, const char *target_kind, ut64 field_index, int depth);
static bool modern_collect_array_strings(ModernPoolStringRefCollector *collector, const ModernClusterMeta *meta, ut64 target_ref, bool weak, int depth) {
const ModernRefResolver *resolver = collector? collector->resolver: NULL;
if (!collector || !resolver || !meta || target_ref < meta->start_ref || target_ref >= meta->start_ref + meta->count) {
return false;
}
ClusterStream s = {
.ctx = resolver->ctx,
.cursor = meta->fill_offset,
.end = meta->fill_end,
};
ut64 target_index = target_ref - meta->start_ref;
for (ut64 i = 0; i < meta->count; i++) {
ut64 length = 0;
ut64 ref = 0;
if (!cs_read_unsigned (&s, &length)) {
return false;
}
if (!weak && !cs_read_ref_id (&s, &ref)) {
return false;
}
for (ut64 j = 0; j < length; j++) {
if (!cs_read_ref_id (&s, &ref)) {
const ModernRefResolver *resolver = collector->resolver;
ClusterStream s = { .ctx = resolver->ctx, .cursor = meta->fill_offset, .end = meta->fill_end };
if (!collector->value_offsets[meta->start_ref]) {
for (ut64 i = 0; i < meta->count; i++) {
collector->value_offsets[meta->start_ref + i] = s.cursor;
ut64 length = 0;
ut64 ref = 0;
if (!cs_read_unsigned (&s, &length) || (!weak && !cs_read_ref_id (&s, &ref))) {
collector->value_offsets[meta->start_ref] = 0;
return false;
}
if (i == target_index) {
(void)modern_collect_ref_strings (collector, ref, weak? "weak_array": "array", j, depth + 1);
for (ut64 j = 0; j < length; j++) {
if (!cs_read_ref_id (&s, &ref)) {
collector->value_offsets[meta->start_ref] = 0;
return false;
}
}
}
if (i == target_index) {
return true;
}
}
return false;
s.cursor = collector->value_offsets[target_ref];
ut64 length = 0;
ut64 ref = 0;
if (!cs_read_unsigned (&s, &length) || (!weak && !cs_read_ref_id (&s, &ref))) {
return false;
}
for (ut64 j = 0; j < length && collector->remaining; j++) {
if (!cs_read_ref_id (&s, &ref)) {
return false;
}
(void)modern_collect_ref_strings (collector, ref, weak? "weak_array": "array", j, depth + 1);
}
return true;
}
static bool modern_collect_refs_object_strings(ModernPoolStringRefCollector *collector, const ModernClusterMeta *meta, const ModernFillSpec *spec, ut64 target_ref, int depth) {
const ModernRefResolver *resolver = collector? collector->resolver: NULL;
if (!collector || !resolver || !meta || !spec || target_ref < meta->start_ref || target_ref >= meta->start_ref + meta->count) {
return false;
}
ClusterStream s = {
.ctx = resolver->ctx,
.cursor = meta->fill_offset,
.end = meta->fill_end,
};
ut64 target_index = target_ref - meta->start_ref;
for (ut64 i = 0; i < meta->count; i++) {
for (int j = 0; j < spec->num_refs; j++) {
ut64 ref = 0;
if (!cs_read_ref_id (&s, &ref)) {
return false;
const ModernRefResolver *resolver = collector->resolver;
ClusterStream s = { .ctx = resolver->ctx, .cursor = meta->fill_offset, .end = meta->fill_end };
if (!collector->value_offsets[meta->start_ref]) {
for (ut64 i = 0; i < meta->count; i++) {
collector->value_offsets[meta->start_ref + i] = s.cursor;
for (int j = 0; j < spec->num_refs; j++) {
ut64 ref = 0;
if (!cs_read_ref_id (&s, &ref)) {
collector->value_offsets[meta->start_ref] = 0;
return false;
}
}
if (i == target_index) {
(void)modern_collect_ref_strings (collector, ref, modern_fill_kind_name (meta->fill_kind), (ut64)j, depth + 1);
for (int j = 0; j < spec->scalar_count; j++) {
if (!modern_skip_scalar (&s, spec->scalars[j])) {
collector->value_offsets[meta->start_ref] = 0;
return false;
}
}
}
for (int j = 0; j < spec->scalar_count; j++) {
if (!modern_skip_scalar (&s, spec->scalars[j])) {
return false;
}
}
if (i == target_index) {
return true;
}
}
return false;
s.cursor = collector->value_offsets[target_ref];
for (int j = 0; j < spec->num_refs && collector->remaining; j++) {
ut64 ref = 0;
if (!cs_read_ref_id (&s, &ref)) {
return false;
}
(void)modern_collect_ref_strings (collector, ref, modern_fill_kind_name (meta->fill_kind), (ut64)j, depth + 1);
}
return true;
}
static bool modern_collect_class_strings(ModernPoolStringRefCollector *collector, const ModernClusterMeta *meta, const ModernFillSpec *spec, ut64 target_ref, int depth) {
@@ -3640,49 +3797,73 @@ static bool modern_collect_instance_strings(ModernPoolStringRefCollector *collec
if (!cs_read_unsigned (&s, &bitmap)) {
return false;
}
int header_words = 2;
int num_fields = meta->next_field_offset_words - header_words;
if (num_fields < 0) {
num_fields = 0;
}
ut64 target_index = target_ref - meta->start_ref;
for (ut64 i = 0; i < meta->count; i++) {
for (int j = 0; j < num_fields; j++) {
int field_word_idx = header_words + j;
bool is_unboxed = ((bitmap >> field_word_idx) & 1ULL) != 0;
if (is_unboxed) {
const int word_size = resolver->ctx->compressed_word_size? resolver->ctx->compressed_word_size: 4;
const int header_words = 8 / word_size;
const int num_fields = R_MAX (meta->next_field_offset_words - header_words, 0);
// Index the cluster once. Rescanning its prefix for every pool slot is
// quadratic for large collections of const instances such as icons.
if (!collector->value_offsets[meta->start_ref]) {
for (ut64 i = 0; i < meta->count; i++) {
collector->value_offsets[meta->start_ref + i] = s.cursor;
for (int j = 0; j < num_fields; j++) {
const int field_word = header_words + j;
const bool unboxed = field_word < 64 && ((bitmap >> field_word) & 1ULL);
ut32 tmp32 = 0;
if (!cs_read_tagged32 (&s, &tmp32) || !cs_read_tagged32 (&s, &tmp32)) {
return false;
}
} else {
ut64 ref = 0;
if (!cs_read_ref_id (&s, &ref)) {
if (unboxed) {
if (!cs_read_tagged32 (&s, &tmp32) || !cs_read_tagged32 (&s, &tmp32)) {
collector->value_offsets[meta->start_ref] = 0;
return false;
}
} else if (!cs_read_ref_id (&s, &ref)) {
collector->value_offsets[meta->start_ref] = 0;
return false;
}
if (i == target_index) {
(void)modern_collect_ref_strings (collector, ref, "instance", (ut64)j, depth + 1);
}
}
}
if (i == target_index) {
return true;
}
s.cursor = collector->value_offsets[target_ref];
for (int j = 0; j < num_fields && collector->remaining; j++) {
const int field_word = header_words + j;
const bool unboxed = field_word < 64 && ((bitmap >> field_word) & 1ULL);
if (unboxed) {
ut32 tmp32 = 0;
if (!cs_read_tagged32 (&s, &tmp32) || !cs_read_tagged32 (&s, &tmp32)) {
return false;
}
} else {
ut64 ref = 0;
if (!cs_read_ref_id (&s, &ref)) {
return false;
}
(void)modern_collect_ref_strings (collector, ref, "instance", (ut64)j, depth + 1);
}
}
return false;
return true;
}
static bool modern_collect_ref_strings(ModernPoolStringRefCollector *collector, ut64 target_ref, const char *target_kind, ut64 field_index, int depth) {
const ModernRefResolver *resolver = collector? collector->resolver: NULL;
if (!collector || !resolver || !target_ref || target_ref >= resolver->refs_count || depth > 2) {
if (!collector || !resolver || !collector->remaining) {
return false;
}
ut64 seen_key = target_ref ^ ((ut64)depth << 56);
if (collector->seen && ht_up_find (collector->seen, seen_key, NULL)) {
collector->remaining--;
if (!target_ref || target_ref >= resolver->refs_count || depth > (collector->values_only? MODERN_POOL_VALUE_MAX_DEPTH: 2)) {
return false;
}
ut64 previous_depth = (ut64)ht_up_find (collector->seen, target_ref, NULL);
if (previous_depth && previous_depth <= (ut64)depth + 1) {
return true;
}
if (collector->seen) {
ht_up_insert (collector->seen, seen_key, (void *)1);
if (previous_depth) {
ht_up_update (collector->seen, target_ref, (void *) (ut64) (depth + 1));
} else {
ht_up_insert (collector->seen, target_ref, (void *) (ut64) (depth + 1));
}
// Isolate base objects can include VM strings without a local cluster.
if (R_STR_ISNOTEMPTY (resolver->strings_by_ref[target_ref])) {
modern_emit_pool_string_ref (collector, target_ref, target_kind, field_index, target_ref);
return true;
}
const ModernClusterMeta *meta = modern_resolver_cluster_for_ref (resolver, target_ref);
if (!meta) {
@@ -3695,11 +3876,27 @@ static bool modern_collect_ref_strings(ModernPoolStringRefCollector *collector,
if (!meta->fill_parsed || !meta->fill_ok || meta->fill_offset >= meta->fill_end || !meta->count) {
return false;
}
if (modern_is_array_cid (collector->cids, meta->cid)) {
return modern_collect_array_strings (collector, meta, target_ref, false, depth);
const ModernCidCache *cids = collector->cids;
bool array = modern_is_array_cid (cids, meta->cid);
bool weak = modern_cid_eq (meta->cid, cids->weak_array);
bool collection = array || weak || modern_cid_eq (meta->cid, cids->map) ||
modern_cid_eq (meta->cid, cids->const_map) || modern_cid_eq (meta->cid, cids->set) ||
modern_cid_eq (meta->cid, cids->const_set) || modern_cid_eq (meta->cid, cids->growable_array);
if (collector->values_only && !collection && meta->fill_kind != MODERN_FILL_INSTANCE) {
return false;
}
if (modern_cid_eq (meta->cid, collector->cids->weak_array)) {
return modern_collect_array_strings (collector, meta, target_ref, true, depth);
if (collection) {
bool previous = collector->via_collection;
collector->via_collection = true;
bool ok;
if (array || weak) {
ok = modern_collect_array_strings (collector, meta, target_ref, weak, depth);
} else {
ModernFillSpec spec = modern_get_fill_spec (cids, meta->cid);
ok = modern_collect_refs_object_strings (collector, meta, &spec, target_ref, depth);
}
collector->via_collection = previous;
return ok;
}
if (meta->fill_kind == MODERN_FILL_CLASS) {
ModernFillSpec spec = modern_get_fill_spec (collector->cids, meta->cid);
@@ -3715,7 +3912,7 @@ static bool modern_collect_ref_strings(ModernPoolStringRefCollector *collector,
return false;
}
bool modern_collect_pool_strrefs(const ModernReq *req, HtUP *pp_offsets, ModernPoolStrRefCb cb, void *user) {
static bool modern_collect_pool_strrefs_internal(const ModernReq *req, HtUP *pp_offsets, ModernPoolStrRefCb cb, void *user, bool values_only) {
DartCtx *ctx = req? req->ctx: NULL;
if (!ctx || !cb || !modern_supported (ctx)) {
return false;
@@ -3730,6 +3927,13 @@ bool modern_collect_pool_strrefs(const ModernReq *req, HtUP *pp_offsets, ModernP
modern_cluster_meta_free (meta, req->num_clusters);
return false;
}
ut64 *value_offsets = (ut64 *)calloc ((size_t)resolver.refs_count, sizeof (ut64));
if (!value_offsets) {
modern_ref_resolver_fini (&resolver);
modern_cluster_meta_free (meta, req->num_clusters);
return false;
}
const ModernCidCache cids = modern_cid_cache_init (ctx->layout);
for (ut64 i = 0; i < req->num_clusters; i++) {
ModernClusterMeta *m = &meta[i];
@@ -3742,6 +3946,9 @@ bool modern_collect_pool_strrefs(const ModernReq *req, HtUP *pp_offsets, ModernP
.end = m->fill_end,
};
for (ut64 pool_index = 0; pool_index < m->count; pool_index++) {
ut64 pool_paddr = s.cursor;
bool has_paddr = modern_vaddr_to_paddr (ctx, s.cursor, &pool_paddr);
ut64 pp_base = modern_pp_vaddr_from_paddr (ctx, pool_paddr, has_paddr);
ut64 length = 0;
if (!cs_read_unsigned (&s, &length)) {
ok = false;
@@ -3768,9 +3975,13 @@ bool modern_collect_pool_strrefs(const ModernReq *req, HtUP *pp_offsets, ModernP
.user = user,
.seen = seen,
.pp_offset = pp_offset,
.pp_base = pp_base,
.pool_ref = m->start_ref + pool_index,
.pool_index = pool_index,
.entry_index = entry_index,
.values_only = values_only,
.remaining = MODERN_POOL_VALUE_MAX_REFS,
.value_offsets = value_offsets,
};
(void)modern_collect_ref_strings (&collector, entry.ref, "object_pool.entry", entry_index, 0);
ht_up_free (seen);
@@ -3783,11 +3994,22 @@ bool modern_collect_pool_strrefs(const ModernReq *req, HtUP *pp_offsets, ModernP
break;
}
}
free (value_offsets);
modern_ref_resolver_fini (&resolver);
modern_cluster_meta_free (meta, req->num_clusters);
return ok;
}
bool modern_collect_pool_strrefs(const ModernReq *req, HtUP *pp_offsets, ModernPoolStrRefCb cb, void *user) {
return modern_collect_pool_strrefs_internal (req, pp_offsets, cb, user, false);
}
// Follow bounded value graphs through instances and collections, excluding
// unrelated metadata graphs and preserving the pool object's actual kind.
bool modern_collect_pool_value_strrefs(const ModernReq *req, HtUP *pp_offsets, ModernPoolStrRefCb cb, void *user) {
return modern_collect_pool_strrefs_internal (req, pp_offsets, cb, user, true);
}
static ut64 modern_find_sb(const ModernReq *req) {
DartCtx *ctx = req? req->ctx: NULL;
if (!ctx) {
@@ -3881,118 +4103,69 @@ bool modern_collect_direct_pool_strrefs(const ModernReq *req, HtUP *pp_offsets,
return ok && found_pool;
}
static bool modern_object_pool_string_ref_exists(RList *refs, ut64 pool_ref, ut64 entry_index) {
if (!refs) {
return false;
}
static bool modern_object_pool_string_ref_exists(RList *refs, const ModernPoolStrRef *info, const char *kind) {
RListIter *it;
DartStringRef *sr;
r_list_foreach (refs, it, sr) {
if (sr && sr->object_ref == pool_ref && sr->field_offset == (ut32)entry_index && R_STR_ISNOTEMPTY (sr->kind) && !strcmp (sr->kind, "object_pool.entry")) {
if (sr && sr->object_ref == info->pool_ref && sr->field_offset == (ut32)info->entry_index && sr->kind && !strcmp (sr->kind, kind)) {
return true;
}
}
return false;
}
static void modern_add_object_pool_string_ref(DartStringInfo *si, ut64 pool_ref, ut64 pool_index, ut64 entry_index, ut64 pp_base, ut64 pp_off) {
if (!si || !si->references || modern_object_pool_string_ref_exists (si->references, pool_ref, entry_index)) {
static void modern_add_object_pool_string_ref(DartStringInfo *si, const ModernPoolStrRef *info) {
bool indirect = strcmp (info->target_kind, "object_pool.entry") != 0;
const char *kind = info->via_collection? "object_pool.collection": indirect? "object_pool.instance_field"
: "object_pool.entry";
if (modern_object_pool_string_ref_exists (si->references, info, kind)) {
return;
}
DartStringRef *sr = R_NEW0 (DartStringRef);
sr->object_ref = pool_ref;
sr->source_addr = pp_base + pp_off;
sr->object_ref = info->pool_ref;
sr->source_addr = info->pp_base + info->pp_offset;
sr->object_type = DART_REF_OTHER;
sr->field_offset = (ut32)entry_index;
sr->kind = strdup ("object_pool.entry");
sr->object_name = r_str_newf ("pool=%" PRIu64 " index=%" PRIu64 " entry=%" PRIu64 " pp_off=0x%" PFMT64x, (uint64_t)pool_ref, (uint64_t)pool_index, (uint64_t)entry_index, (ut64)pp_off);
sr->field_offset = (ut32)info->entry_index;
sr->kind = strdup (kind);
sr->object_name = r_str_newf ("pool=%" PRIu64 " index=%" PRIu64 " entry=%" PRIu64 " pp_off=0x%" PFMT64x, (uint64_t)info->pool_ref, (uint64_t)info->pool_index, (uint64_t)info->entry_index, info->pp_offset);
if (info->via_collection) {
sr->object_name = r_str_appendf (sr->object_name, " leaf=%s[%" PRIu64 "]", info->target_kind, (uint64_t)info->field_index);
} else if (indirect) {
sr->object_name = r_str_appendf (sr->object_name, " field=%" PRIu64, (uint64_t)info->field_index);
}
r_list_append (si->references, sr);
}
static void modern_append_object_pool_string(RList *strings, HtUP *seen_refs, ut64 *ref_counter, const ModernRefResolver *resolver, const ModernEmitCtx *emit, const ModernPoolEntry *entry) {
const ut64 string_ref = entry? entry->ref: UT64_MAX;
if (!strings || !seen_refs || !resolver || !emit || !entry || string_ref >= resolver->refs_count || R_STR_ISEMPTY (resolver->strings_by_ref[string_ref])) {
return;
}
DartStringInfo *si = ht_up_find (seen_refs, string_ref, NULL);
typedef struct {
RList *strings;
HtUP *seen_refs;
ut64 *next_ref;
} ModernPoolStringExtractCtx;
static void modern_append_object_pool_string(const ModernPoolStrRef *info, void *user) {
ModernPoolStringExtractCtx *ux = user;
DartStringInfo *si = ht_up_find (ux->seen_refs, info->string_ref, NULL);
if (!si) {
si = R_NEW0 (DartStringInfo);
si->ref_id = ref_counter? (*ref_counter)++: 0;
si->value = strdup (resolver->strings_by_ref[string_ref]);
si->ref_id = ux->next_ref? (*ux->next_ref)++: 0;
si->value = strdup (info->string_value);
si->length = (ut32)strlen (si->value);
si->flags = resolver->string_flags_by_ref[string_ref] | DART_STRING_CANONICAL | DART_STRING_OBJECT_POOL;
si->address = resolver->string_addr_by_ref[string_ref];
si->flags = info->string_flags | DART_STRING_CANONICAL | DART_STRING_OBJECT_POOL;
si->address = info->string_addr;
si->category = dart_string_classify_value (si->value);
si->references = r_list_newf ((RListFree)dart_string_ref_free);
r_list_append (strings, si);
ht_up_insert (seen_refs, string_ref, si);
r_list_append (ux->strings, si);
ht_up_insert (ux->seen_refs, info->string_ref, si);
}
modern_add_object_pool_string_ref (si, emit->pool_ref, emit->pool_index, entry->index, emit->pp_base, modern_pool_entry_pp_offset (resolver->ctx, entry->index));
modern_add_object_pool_string_ref (si, info);
}
bool modern_extract_pool_strings(const ModernReq *req, RList *strings, HtUP *seen, ut64 *next_ref) {
DartCtx *ctx = req? req->ctx: NULL;
if (!ctx || !strings || !seen || !modern_supported (ctx)) {
if (!strings || !seen) {
return false;
}
ModernClusterMeta *meta = modern_parse_cluster_meta (ctx, req->cluster_start, req->cluster_end, req->num_clusters, req->num_base_objects);
if (!meta) {
return false;
}
ModernRefResolver resolver = { 0 };
bool ok = modern_ref_resolver_init (&resolver, ctx, meta, req->num_clusters, req->num_base_objects);
if (!ok) {
modern_cluster_meta_free (meta, req->num_clusters);
return false;
}
const ModernCidCache cids = modern_cid_cache_init (ctx->layout);
ModernEmitCtx emit = { .ctx = ctx };
for (ut64 i = 0; i < req->num_clusters; i++) {
ModernClusterMeta *m = &meta[i];
if (m->fill_kind != MODERN_FILL_OBJECT_POOL || !m->fill_parsed || !m->fill_ok || m->fill_offset >= m->fill_end || !m->count) {
continue;
}
ClusterStream s = {
.ctx = ctx,
.cursor = m->fill_offset,
.end = m->fill_end,
};
for (ut64 pool_index = 0; pool_index < m->count; pool_index++) {
emit.pool_index = pool_index;
emit.pool_ref = m->start_ref + pool_index;
ut64 pool_paddr = s.cursor;
bool has_paddr = modern_vaddr_to_paddr (ctx, s.cursor, &pool_paddr);
emit.pp_base = modern_pp_vaddr_from_paddr (ctx, pool_paddr, has_paddr);
ut64 length = 0;
if (!cs_read_unsigned (&s, &length)) {
ok = false;
break;
}
for (ut64 entry_index = 0; entry_index < length; entry_index++) {
ModernPoolEntry entry;
if (!modern_read_pool_entry (&s, ctx, entry_index, &entry)) {
ok = false;
break;
}
if (entry.behavior != 0 || entry.type != 1 || entry.ref >= resolver.refs_count) {
continue;
}
const ModernClusterMeta *target = modern_resolver_cluster_for_ref (&resolver, entry.ref);
if (target && modern_is_string_cid (&cids, target->cid)) {
modern_append_object_pool_string (strings, seen, next_ref, &resolver, &emit, &entry);
}
}
if (!ok) {
break;
}
}
if (!ok) {
break;
}
}
modern_ref_resolver_fini (&resolver);
modern_cluster_meta_free (meta, req->num_clusters);
return ok;
ModernPoolStringExtractCtx ux = { strings, seen, next_ref };
return modern_collect_pool_value_strrefs (req, NULL, modern_append_object_pool_string, &ux);
}
// Walk every direct (tagged, snapshotable) ObjectPool entry once and hand the
@@ -4450,7 +4623,7 @@ static bool modern_load_vm_base_strings(DartCtx *ctx, char **strings_by_ref, ut6
}
static bool modern_can_extract_classes(DartCtx *ctx) {
if (!ctx || !ctx->layout || ctx->layout->tag_style != DART_TAG_STYLE_OBJECT_HEADER) {
if (!modern_supported (ctx)) {
return false;
}
// Both pointer widths are supported: the class/field fill readers decode
+3 -1
View File
@@ -460,7 +460,9 @@ static int decode_pool_and_emit(DartItEmitRequest *req) {
goto cleanup;
}
const ut64 layout_cap = ctx->layout && ctx->layout->it_cap? ctx->layout->it_cap: 20000;
const ut64 max_entries = req->max_entries > 0? req->max_entries: (req->on_it? itlen: layout_cap);
// itlen counts retained Code objects, not instructions whose Code metadata
// was discarded. Let the table header bound an unlimited IT extraction.
const ut64 max_entries = req->max_entries > 0? req->max_entries: (req->on_it? 0: layout_cap);
req->data_image_base = data_image_base;
req->data_image_end = data_image_end;
req->itlen = itlen;
+4
View File
@@ -169,6 +169,7 @@ typedef struct {
typedef struct {
ut64 pp_offset;
ut64 pp_base;
ut64 pool_ref;
ut64 pool_index;
ut64 entry_index;
@@ -177,6 +178,8 @@ typedef struct {
ut64 field_index;
ut64 string_ref;
ut64 string_addr;
ut32 string_flags;
bool via_collection;
const char *string_value;
} ModernPoolStrRef;
@@ -291,6 +294,7 @@ const char *modern_value_graph_root_kind(ModernValueGraph *graph);
bool modern_collect_direct_pool_strrefs(const ModernReq *req, HtUP *pp_offs, ModernPoolStrRefCb cb, void *user);
bool modern_collect_direct_pool_refs(const ModernReq *req, ModernPoolRefCb cb, void *user);
bool modern_collect_pool_strrefs(const ModernReq *req, HtUP *pp_offs, ModernPoolStrRefCb cb, void *user);
bool modern_collect_pool_value_strrefs(const ModernReq *req, HtUP *pp_offs, ModernPoolStrRefCb cb, void *user);
bool modern_extract_pool_strings(const ModernReq *req, RList *strings, HtUP *seen, ut64 *next_ref);
bool modern_scan_names(DartCtx *ctx, ut64 start, ut64 end, ut64 nclusters, ut64 itlen);
bool modern_extract_classes(const ModernReq *req, RList *classes);
+10 -19
View File
@@ -483,33 +483,24 @@ bool cs_read_tagged32(ClusterStream *s, ut32 *out) {
return false;
}
// ReadStream::Read<int64_t>: 7-bit groups, then a final byte holding a signed
// value in [-64, 63] biased by kEndByteMarker (192).
bool cs_read_tagged64(ClusterStream *s, int64_t *out) {
ut8 raw = 0;
if (!cs_read_u8 (s, &raw)) {
return false;
}
int8_t b = (int8_t)raw;
if (b < 0) {
if (out) {
*out = (int64_t)b + 192;
}
return true;
}
int64_t v = (int64_t)raw;
int shift = 7;
for (int i = 1; i < 10; i++) {
ut64 v = 0;
int shift = 0;
for (int i = 0; i < 10; i++) {
ut8 raw = 0;
if (!cs_read_u8 (s, &raw)) {
return false;
}
b = (int8_t)raw;
if (b < 0) {
v |= ((int64_t)b + 192) << shift;
if (raw > 0x7f) {
v |= (ut64) ((int64_t)raw - 192) << shift;
if (out) {
*out = v;
*out = (int64_t)v;
}
return true;
}
v |= ((int64_t)raw) << shift;
v |= (ut64)raw << shift;
shift += 7;
}
return false;
+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) {
+2 -1
View File
@@ -564,6 +564,7 @@ R_API RLibStruct radare_plugin = {
.type = R_LIB_TYPE_CORE,
.data = &r_core_plugin_r2flutter,
.version = R2_VERSION,
.abiversion = R2_ABIVERSION
.abiversion = R2_ABIVERSION,
.pkgname = "r2flutter"
};
#endif
+76 -23
View File
@@ -60,6 +60,7 @@ typedef struct {
typedef struct {
ut64 string_addr;
char *string_value;
bool indirect;
} FlutterPpStringRef;
typedef struct {
@@ -93,15 +94,19 @@ static void flutter_stack_slot_free(void *p) {
free (p);
}
static void flutter_pp_string_ref_free(HtUPKv *kv) {
FlutterPpStringRef *ref = kv->value;
static void flutter_pp_string_ref_free(void *p) {
FlutterPpStringRef *ref = p;
free (ref->string_value);
free (ref);
}
static void flutter_pp_string_bucket_free(HtUPKv *kv) {
r_list_free (kv->value);
}
static void flutter_pp_string_map_init(FlutterPpStringMap *map) {
memset (map, 0, sizeof (*map));
map->by_pp_off = ht_up_new (NULL, flutter_pp_string_ref_free, NULL);
map->by_pp_off = ht_up_new (NULL, flutter_pp_string_bucket_free, NULL);
}
static void flutter_pp_string_map_fini(FlutterPpStringMap *map) {
@@ -302,6 +307,22 @@ static bool flutter_parse_shifted_imm(const char *line, ut64 *imm) {
if (!lsl) {
return false;
}
// RArchValue.imm may already include the shift (depending on the ARM
// decoder). Read the unshifted third operand before applying it once.
const char *arg = strchr (line, ',');
arg = arg? strchr (arg + 1, ','): NULL;
if (!arg || arg >= lsl) {
return false;
}
arg++;
while (isspace ((ut8)*arg) || *arg == '#') {
arg++;
}
char *end;
ut64 value = strtoull (arg, &end, 0);
if (end == arg) {
return false;
}
const char *p = lsl + 3;
while (isspace ((ut8)*p)) {
p++;
@@ -312,11 +333,11 @@ static bool flutter_parse_shifted_imm(const char *line, ut64 *imm) {
if (!isdigit ((ut8)*p)) {
return false;
}
int shift = atoi (p);
if (shift <= 0 || shift >= 63) {
ut64 shift = strtoull (p, NULL, 0);
if (shift >= 64) {
return false;
}
*imm <<= shift;
*imm = value << shift;
return true;
}
@@ -440,7 +461,7 @@ static bool flutter_parse_opinfo(const RAnalOp *op, FlutterOpInfo *info) {
if (info->n_regs == 0 && (type == R_ANAL_OP_TYPE_RCALL || type == R_ANAL_OP_TYPE_UCALL || type == R_ANAL_OP_TYPE_ICALL || type == R_ANAL_OP_TYPE_IRCALL)) {
flutter_parse_first_operand_reg (op->mnemonic, info);
}
if (info->has_imm && R_STR_ISNOTEMPTY (op->mnemonic)) {
if (type == R_ANAL_OP_TYPE_ADD && info->has_imm && R_STR_ISNOTEMPTY (op->mnemonic)) {
(void)flutter_parse_shifted_imm (op->mnemonic, &info->imm);
}
return info->n_regs > 0 || info->has_mem || info->has_imm;
@@ -539,7 +560,7 @@ static bool flutter_model_load(FlutterAnalModel *model, RCore *core, DartCtx *dc
}
DartCtx ctx = *dctx;
ctx.core = core;
dart_recovery_model_load (&ctx, model, DART_RECOVERY_STRINGS | DART_RECOVERY_CLASSES | DART_RECOVERY_CLASS_FIELDS | DART_RECOVERY_METHOD_INDEX);
dart_recovery_model_load (&ctx, model, DART_RECOVERY_STRINGS | DART_RECOVERY_CLASSES | DART_RECOVERY_CLASS_FIELDS | DART_RECOVERY_METHOD_INDEX | DART_RECOVERY_IT);
if (!model->strings || !model->classes) {
flutter_model_fini (model);
return false;
@@ -551,13 +572,23 @@ static void flutter_pp_string_map_add(FlutterPpStringMap *map, const ModernPoolS
if (!map || !map->by_pp_off || !info || R_STR_ISEMPTY (info->string_value) || !info->string_addr) {
return;
}
if (ht_up_find (map->by_pp_off, info->pp_offset, NULL)) {
return;
RList *refs = ht_up_find (map->by_pp_off, info->pp_offset, NULL);
if (!refs) {
refs = r_list_newf (flutter_pp_string_ref_free);
ht_up_insert (map->by_pp_off, info->pp_offset, refs);
}
RListIter *it;
FlutterPpStringRef *existing;
r_list_foreach (refs, it, existing) {
if (existing->string_addr == info->string_addr) {
return;
}
}
FlutterPpStringRef *ref = R_NEW0 (FlutterPpStringRef);
ref->string_addr = info->string_addr;
ref->string_value = strdup (info->string_value);
ht_up_insert (map->by_pp_off, info->pp_offset, ref);
ref->indirect = strcmp (info->target_kind, "object_pool.entry") != 0;
r_list_append (refs, ref);
}
static void flutter_pp_string_ref_cb(const ModernPoolStrRef *info, void *user) {
@@ -579,7 +610,7 @@ static bool flutter_collect_pp_strings_from_snapshot(DartCtx *ctx, ut64 snapshot
.num_clusters = sh.nc,
.num_base_objects = sh.nb,
};
return modern_collect_direct_pool_strrefs (&req, wanted, flutter_pp_string_ref_cb, map);
return modern_collect_pool_value_strrefs (&req, wanted, flutter_pp_string_ref_cb, map);
}
static void flutter_pp_string_map_load(DartCtx *ctx, HtUP *wanted, FlutterPpStringMap *map) {
@@ -600,7 +631,7 @@ static void flutter_pp_string_map_load(DartCtx *ctx, HtUP *wanted, FlutterPpStri
}
}
static const FlutterPpStringRef *flutter_pp_string_map_get(FlutterPpStringMap *map, ut64 pp_off) {
static RList *flutter_pp_string_map_get(FlutterPpStringMap *map, ut64 pp_off) {
return map && map->by_pp_off? ht_up_find (map->by_pp_off, pp_off, NULL): NULL;
}
@@ -873,6 +904,17 @@ static RVecFlutterEntry *flutter_collect_entries(RCore *core, const DartApp *app
flutter_collect_entries_from_flags (seen, entries, core);
flutter_collect_entries_from_app (seen, entries, app);
flutter_collect_entries_from_model (seen, entries, model);
// Entries before first_entry_with_code still contain executable Dart code;
// only their Code objects were discarded by the precompiler.
if (model->it_entries) {
DartInstructionTableEntry *entry;
R_VEC_FOREACH (model->it_entries, entry) {
if (entry->address && !ht_up_find (seen, entry->address, NULL)) {
RVecFlutterEntry_push_back (entries, &entry->address);
ht_up_insert (seen, entry->address, entries);
}
}
}
ht_up_free (seen);
return entries;
}
@@ -1188,22 +1230,26 @@ static bool flutter_process_pp_load(RCore *core, FlutterAnalState *state, Flutte
if (pp_refs && pp_refs->pp_base) {
flutter_add_ref (core, at, pp_refs->pp_base + slot_off, R_ANAL_REF_TYPE_DATA | R_ANAL_REF_TYPE_READ);
}
const FlutterPpStringRef *string_ref = flutter_pp_string_map_get (pp_strings, slot_off);
if (string_ref) {
RList *strings = flutter_pp_string_map_get (pp_strings, slot_off);
FlutterPpStringRef *first = strings? r_list_first (strings): NULL;
if (!first || first->indirect) {
if (!flutter_annotate_pp_ref (core, pp_refs, at, slot_off, false, stats)) {
char *msg = r_str_newf ("dart: PP slot +0x%" PFMT64x, slot_off);
flutter_append_comment (core, at, msg, stats);
free (msg);
}
}
RListIter *it;
FlutterPpStringRef *string_ref;
r_list_foreach (strings, it, string_ref) {
flutter_set_flag (core, "dart.str", string_ref->string_value, string_ref->string_addr, (ut32)strlen (string_ref->string_value), true);
flutter_add_ref (core, at, string_ref->string_addr, R_ANAL_REF_TYPE_STRN | R_ANAL_REF_TYPE_READ);
stats->string_refs++;
char *escaped = r_str_escape_utf8 (string_ref->string_value, false, true);
char *msg = r_str_newf ("dart: PP slot +0x%" PFMT64x " string \"%s\"", pp_off, escaped);
char *msg = r_str_newf ("dart: PP slot +0x%" PFMT64x " %sstring \"%s\"", pp_off, string_ref->indirect? "contains ": "", escaped);
flutter_append_comment (core, at, msg, stats);
free (msg);
free (escaped);
} else if (!flutter_annotate_pp_ref (core, pp_refs, at, slot_off, false, stats)) {
char *msg = r_str_newf ("dart: "
"PP slot +0x%" PFMT64x,
slot_off);
flutter_append_comment (core, at, msg, stats);
free (msg);
}
stats->pp_refs++;
return true;
@@ -1324,9 +1370,15 @@ static void flutter_scan_function(RCore *core, FlutterAnalModel *model, FlutterP
}
HtUP *seen_ops = ht_up_new0 ();
RListIter *it;
RAnalBlock *bb;
#if R2_ABIVERSION >= 168
RAnalBlock **it;
R_VEC_FOREACH (&fcn->bbs, it) {
bb = *it;
#else
RListIter *it;
r_list_foreach (fcn->bbs, it, bb) {
#endif
if (!bb || bb->size == 0) {
continue;
}
@@ -1520,6 +1572,7 @@ bool r2flutter_analysis_run(RCore *core, DartCtx *dctx, bool quiet) {
RVecFlutterEntry *entries = flutter_collect_entries (core, app, &model);
flutter_ensure_functions (core, entries);
dart_dumper_name_functions (app);
flutter_scan_functions (core, &model, &pp_strings, &pp_refs, entries, &stats);
int graph_xrefs = r2flutter_apply_pool_xrefs (core, dctx);
+1 -1
View File
@@ -6,7 +6,7 @@
"expect": {
"contains": [
"class _Closure@0150898 (size=64)",
"+0x428 dynamic _context@0150898",
"+0x28 dynamic _context@0150898",
"class _Uri@0150898 (size=96)",
"+0x0 dynamic _portMap@1026248 static final"
],
+4 -4
View File
@@ -8,8 +8,8 @@ EOF
EXPECT=<<EOF
class Pointer (size=32)
methods:
0x00000000 get:hashCode (GetterFunction)
0x00000000 == (RegularFunction)
0x00115f2c get:hashCode (GetterFunction)
0x001299fc == (RegularFunction)
class NativeType (size=16)
class NativeFunction (size=16)
EOF
@@ -27,8 +27,8 @@ EXPECT=<<EOF
# class Pointer (size=32)
# type_params: 1
"td struct dart_Pointer { };"
# method 0x00000000 get:hashCode (GetterFunction)
# method 0x00000000 == (RegularFunction)
# method 0x00115f2c get:hashCode (GetterFunction)
# method 0x001299fc == (RegularFunction)
# class NativeType (size=16)
EOF
RUN
+4 -4
View File
@@ -8,8 +8,8 @@ EOF
EXPECT=<<EOF
class Pointer (size=32)
methods:
0x00000000 get:hashCode (GetterFunction)
0x00000000 == (RegularFunction)
0x00c6ab64 get:hashCode (GetterFunction)
0x00d8bde8 == (RegularFunction)
class NativeType (size=16)
class NativeFunction (size=16)
EOF
@@ -43,8 +43,8 @@ EXPECT=<<EOF
# class Pointer (size=32)
# type_params: 1
"td struct dart_Pointer { };"
# method 0x00000000 get:hashCode (GetterFunction)
# method 0x00000000 == (RegularFunction)
# method 0x00c6ab64 get:hashCode (GetterFunction)
# method 0x00d8bde8 == (RegularFunction)
# class NativeType (size=16)
EOF
RUN
+9 -9
View File
@@ -33,14 +33,14 @@ ic dart_Future
EOF
EXPECT=<<EOF
class dart_Future
0x00000000 method dart_Future sC Future_error
0x00000000 method dart_Future sC Future_
0x00000000 method dart_Future s _anonymous_closure_
0x00000000 method dart_Future sC Future_value
0x00000000 method dart_Future s wait
0x00000000 method dart_Future s handleError
0x00000000 method dart_Future sC Future_delayed
0x00000000 method dart_Future sC Future_microtask
0x00000000 method dart_Future sC Future_sync
0x005d4f54 method dart_Future sC Future_error
0x005d70cc method dart_Future sC Future_
0x005d71b8 method dart_Future s _anonymous_closure_
0x005e4a98 method dart_Future sC Future_value
0x0060d7d4 method dart_Future s wait
0x0060dfac method dart_Future s handleError
0x0062edcc method dart_Future sC Future_delayed
0x006d5018 method dart_Future sC Future_microtask
0x00943c08 method dart_Future sC Future_sync
EOF
RUN
+1 -1
View File
@@ -117,7 +117,7 @@ dart
0x00000020 dart field 2 genericItems
0x00000028 dart field 3 callback
0x00000000 [0x00000000 - 0x00000000] 0 dart class 1 dart_TestInterface
pf [16].qppp fieldA items genericItems callback
'pf [16].qppp fieldA items genericItems callback
EOF
RUN
+25
View File
@@ -0,0 +1,25 @@
NAME=pp-code-resolve-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
r2flutter -q -j -O pp+0x40~{pp_slot.resolved_kind}
r2flutter -q -j -O pp+0x40~{pp_slot.resolved_code_index}
r2flutter -q -j -O pp+0x48~{pp_slot.resolved_code_index}
r2flutter -q -j -HHH -l 500~{snapshots[1].clusters[365].object_pools[0].entries[6].resolved_code_index}
r2flutter -q -AAA
CC. @ 0x3f28cc
axfj @ 0x3f28cc~{[0].to}
axfj @ 0x3f28cc~{[0].type}
EOF
EXPECT=<<EOF
code
10816
10817
10816
dart: indirect call via PP+0x40 code method.fn_10816
4132980
CALL
EOF
RUN
+18
View File
@@ -0,0 +1,18 @@
NAME=pp-code-resolve-ios
FILE=bins/ios/AuthPass.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
r2flutter -q -j -O pp+0x68~{pp_slot.resolved_kind}
r2flutter -q -j -O pp+0x68~{pp_slot.resolved_code_index}
r2flutter -q -j -O pp+0x70~{pp_slot.resolved_code_index}
r2flutter -q -j -HHH -l 1100~{snapshots[1].clusters[1028].object_pools[0].entries[11].resolved_code_index}
EOF
EXPECT=<<EOF
code
36399
36400
36399
EOF
RUN
+23
View File
@@ -0,0 +1,23 @@
NAME=pp-code-resolve-main-discarded-and-deferred
FILE=malloc://0x8c
CMDS=<<EOF
L ../src/r2/core_flutter.so
L ../src/r2/core_flutter.dylib
# Main Code refs include a discarded entry; the last Code is deferred.
wx 50414421f5f5dcdc84000000000000000200000000000000333063303531636239313932663664363831326265336630623837613633353670726f647563742061726d363420636f6d707265737365642d706f696e746572730082878283800040c483c0c8c081c00060c5818480808080808080808080808080808080808080808080841183118411851186
r2flutter -q -j -O pp+0x10~{pp_slot.resolved_kind}
r2flutter -q -j -O pp+0x10~{pp_slot.resolved_code_index}
r2flutter -q -j -O pp+0x18~{pp_slot.resolved_code_index}
r2flutter -q -j -O pp+0x20~{pp_slot.resolved_code_index}
r2flutter -q -j -O pp+0x28~{pp_slot.resolved_kind}
r2flutter -q -j -O pp+0x28~{=}~resolved_code_index~?
EOF
EXPECT=<<EOF
code
0
1
2
code
0
EOF
RUN
+24
View File
@@ -0,0 +1,24 @@
NAME=pp-collection-cycles-and-map-set-storage
FILE=malloc://0xb4
CMDS=<<EOF
L ../src/r2/core_flutter.so
L ../src/r2/core_flutter.dylib
wx 50414421f5f5dcdcac000000000000000200000000000000333063303531636239313932663664363831326265336630623837613633353670726f647563742061726d363420636f6d707265737365642d706f696e746572730082878780800020d781a00040d681840040d5810060d5810000d6810020d6810060c58185a0636f6c6c656374696f6e2d6379636c6584808483838580808480808080848080808084808080808480808511841185118611871188
r2flutter -q -j -xz~{[0].value}
r2flutter -q -j -xz~{[0].refs[0].name}
r2flutter -q -j -xz~{[0].refs[1].name}
r2flutter -q -j -xz~{[0].refs[2].name}
r2flutter -q -j -xz~{[0].refs[3].name}
r2flutter -q -j -xz~{[0].refs[4].name}
r2flutter -q -j -xz~{[0].refs[4].kind}
EOF
EXPECT=<<EOF
collection-cycle
pool=9 index=0 entry=0 pp_off=0x10 leaf=array[1]
pool=9 index=0 entry=1 pp_off=0x18 leaf=array[1]
pool=9 index=0 entry=2 pp_off=0x20 leaf=array[1]
pool=9 index=0 entry=3 pp_off=0x28 leaf=array[1]
pool=9 index=0 entry=4 pp_off=0x30 leaf=array[1]
object_pool.collection
EOF
RUN
@@ -0,0 +1,28 @@
NAME=pp-collection-string-xref-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
r2flutter -q -AA
axtq @ 0x42ad3~0xdb177
axtq @ 0x4d912~0xdb177
r2flutter -q -AAA
CC. @ 0x19c21c~PP slot +0x5d0 array~?
CC. @ 0x19c21c~contains string "cancel"~?
CC. @ 0x19c21c~contains string "hover"~?
axtq @ 0x42ad3~0x19c21c
axtq @ 0x4d912~0x19c21c
r2flutter -q -r -xz~0x42ad3 0xdb177
EOF
EXPECT=<<EOF
0xdb177
0xdb177
1
1
1
0x19c21c
0x19c21c
ax 0x42ad3 0xdb177
EOF
RUN
+28
View File
@@ -0,0 +1,28 @@
NAME=pp-collection-string-xref-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
r2flutter -q -AA
axtq @ 0x2ab3a0~0x22d6a1
axtq @ 0x2bbf30~0x22d6a1
r2flutter -q -AAA
CC. @ 0x15f4c~PP slot +0x3d0 array~?
CC. @ 0x15f4c~contains string "cancel"~?
CC. @ 0x15f4c~contains string "hover"~?
axtq @ 0x2ab3a0~0x15f4c
axtq @ 0x2bbf30~0x15f4c
r2flutter -q -r -xz~0x2ab3a0 0x22d6a1
EOF
EXPECT=<<EOF
0x22d6a1
0x22d6a1
1
1
1
0x15f4c
0x15f4c
ax 0x2ab3a0 0x22d6a1
EOF
RUN
+21
View File
@@ -0,0 +1,21 @@
NAME=r2-pp-analysis-discarded-code-android
FILE=bins/android/first/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
# Simulate fewer retained Code objects than instruction entries, with all
# entries before first_entry_with_code. Patches stay in the IO cache.
wx 1e8a @ 0x8f7f
wv4 1311 @ 0x1f558
r2flutter -q -i~?
r2flutter -q -l 2 -i~?
r2flutter -q -AAA
axtq @ 0x2710
EOF
EXPECT=<<EOF
1311
2
0x8b914
EOF
RUN
+21
View File
@@ -0,0 +1,21 @@
NAME=pp-field-resolve-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
r2flutter -q -j -O pp+0x60~{pp_slot.resolved_kind}
r2flutter -q -O pp+0x60
r2flutter -q -j -O ref:52876~{snapshot.name}
r2flutter -q -j -HHH -l 500~{snapshots[1].clusters[365].object_pools[0].entries[10].resolved_name}
r2flutter -q -AAA
CC. @ 0x1eee60
EOF
EXPECT=<<EOF
field
Platform.isAndroid
Platform.isAndroid
Platform.isAndroid
dart: PP slot +0x60 field Platform.isAndroid
EOF
RUN
+21
View File
@@ -0,0 +1,21 @@
NAME=pp-field-resolve-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
r2flutter -q -j -O pp+0x120~{pp_slot.resolved_kind}
r2flutter -q -O pp+0x120
r2flutter -q -j -O ref:40563~{snapshot.name}
r2flutter -q -j -HHH -l 500~{snapshots[1].clusters[320].object_pools[0].entries[34].resolved_name}
r2flutter -q -AAA
CC. @ 0xfb3c
EOF
EXPECT=<<EOF
field
Zone._current@4048458
Zone._current@4048458
Zone._current@4048458
dart: PP slot +0x120 field Zone._current@4048458
EOF
RUN
+30
View File
@@ -0,0 +1,30 @@
NAME=pp-field-resolve-patched-owner-and-missing-metadata
FILE=malloc://0xbc
CMDS=<<EOF
L ../src/r2/core_flutter.so
L ../src/r2/core_flutter.dylib
wx 50414421f5f5dcdcb4000000000000000200000000000000333063303531636239313932663664363831326265336630623837613633353670726f647563742061726d363420636f6d707265737365642d706f696e746572730082898580800020d7838888900020c180810040c1810060c2830060c5818388666e5f3188666e5f3090746f704c6576656c8380808080808080808080808048c1c4c4c0c0c0c080868084878080c08085808080c080bf868080c0808311881189118a
r2flutter -q -j -O pp+0x10~{pp_slot.resolved_kind}
r2flutter -q -O pp+0x10
r2flutter -q -j -O ref:8~{snapshot.name}
r2flutter -q -O pp+0x18
r2flutter -q -O pp+0x20
r2flutter -q -j -O pp+0x20~{=}~resolved_name~?
wx 87 @ 0xa1
r2flutter -q -O pp+0x10
wx 86 @ 0xa1
r2flutter -q -O pp+0x10 -m custom/obf-map.txt
r2flutter -q -j -O ref:8 -m custom/obf-map.txt~{snapshot.name}
EOF
EXPECT=<<EOF
field
fn_1.fn_0
fn_1.fn_0
topLevel
field
0
fn_0
build.initState
build.initState
EOF
RUN
@@ -0,0 +1,18 @@
NAME=r2-pp-instance-string-xref-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
r2flutter -q -AA
axtq @ 0x3d29e~0xdb87f
r2flutter -q -AAA
CC. @ 0x19d630
axtq @ 0x3d29e~0x19d630
EOF
EXPECT=<<EOF
0xdb87f
dart: PP slot +0xcd8 instance | dart: PP slot +0xcd8 contains string "dark"
0x19d630
EOF
RUN
+18
View File
@@ -0,0 +1,18 @@
NAME=r2-pp-instance-string-xref-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
r2flutter -q -AA
axtq @ 0x2a3300~0x22dd19
r2flutter -q -AAA
CC. @ 0x330ac
axtq @ 0x2a3300~0x330ac
EOF
EXPECT=<<EOF
0x22dd19
dart: PP slot +0xa48 instance | dart: PP slot +0xa48 contains string "dark"
0x330ac
EOF
RUN
@@ -0,0 +1,15 @@
NAME=r2-pp-shifted-string-xref-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
r2flutter -q -AAA
CC. @ 0x178c70
axtq @ 0x51746~0x178c70
EOF
EXPECT=<<EOF
dart: PP slot +0xb5f8 string "Unexpected state: "
0x178c70
EOF
RUN
+15
View File
@@ -0,0 +1,15 @@
NAME=r2-pp-shifted-string-xref-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
r2flutter -q -AAA
CC. @ 0x44604
axtq @ 0x27af40~0x44604
EOF
EXPECT=<<EOF
dart: PP slot +0xa590 string "callOnHover"
0x44604
EOF
RUN
+23
View File
@@ -0,0 +1,23 @@
NAME=record-values-android
FILE=bins/android/mafia/libapp.so
CMDS=<<EOF
L ../src/r2/core_flutter.so
L ../src/r2/core_flutter.dylib
r2flutter -q -j -O ref:158051~{snapshot.kind}
r2flutter -q -j -O ref:158051~{snapshot.complete}
r2flutter -q -j -O ref:158051~{snapshot.shape}
r2flutter -q -j -O ref:158051~{snapshot.length}
r2flutter -q -j -O ref:158051~{snapshot.fields[0].ref}
r2flutter -q -j -O ref:158051~{snapshot.fields[1].value.name}
r2flutter -q -j -O ref:158051~{snapshot.fields[1].value.fields[2].value.value}
EOF
EXPECT=<<EOF
record
true
2
2
1001
XmlAttributeType
DOUBLE_QUOTE
EOF
RUN
+14
View File
@@ -0,0 +1,14 @@
NAME=record-values-malformed
FILE=malloc://0xfd
CMDS=<<EOF
L ../src/r2/core_flutter.so
L ../src/r2/core_flutter.dylib
wx 50414421f5f5dcdcf5000000000000000200000000000000333063303531636239313932663664363831326265336630623837613633353670726f647563742061726d363420636f6d707265737365642d706f696e746572730082898280800020cf82c1be0060d08580828281018180818384020084868381880181838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383
r2flutter -q -j -O ref:6~{snapshot.kind}
r2flutter -q -j -O ref:6~{snapshot.complete}
EOF
EXPECT=<<EOF
record
false
EOF
RUN
+34
View File
@@ -0,0 +1,34 @@
NAME=record-values-synthetic
FILE=malloc://0xfd
CMDS=<<EOF
L ../src/r2/core_flutter.so
L ../src/r2/core_flutter.dylib
wx 50414421f5f5dcdcf5000000000000000200000000000000333063303531636239313932663664363831326265336630623837613633353670726f647563742061726d363420636f6d707265737365642d706f696e746572730082898280800020cf82c1be0060d08580828281018180828384020084868381880181838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383
r2flutter -q -j -O ref:5~{snapshot.complete}
r2flutter -q -j -O ref:5~{snapshot.length}
r2flutter -q -j -O ref:6~{snapshot.fields[0].value.value}
r2flutter -q -j -O ref:6~{snapshot.fields[1].value.value}
r2flutter -q -j -O ref:7~{snapshot.shape}
r2flutter -q -j -O ref:7~{snapshot.field_names_index}
r2flutter -q -j -O ref:7~{snapshot.fields[0].value.fields[1].value.value}
r2flutter -q -j -O ref:8~{snapshot.fields[0].value.cycle}
r2flutter -q -j -O ref:9~{snapshot.length}
r2flutter -q -j -O ref:9~{snapshot.values_truncated}
r2flutter -q -j -O ref:9~{snapshot.fields[127].value.value}
r2flutter -q -j -O ref:9~{=}~name = ~?
EOF
EXPECT=<<EOF
true
0
1
-2
65538
1
-2
true
129
true
1
128
EOF
RUN
+20
View File
@@ -0,0 +1,20 @@
NAME=record-values-uncompressed
FILE=malloc://0x100
CMDS=<<EOF
L ../src/r2/core_flutter.so
L ../src/r2/core_flutter.dylib
wx 50414421f5f5dcdcf8000000000000000200000000000000333063303531636239313932663664363831326265336630623837613633353670726f647563742061726d3634206e6f2d636f6d707265737365642d706f696e746572730082898280800020cf82c1be0060d08580828281018180828384020084868381880181838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383838383
r2flutter -q -j -O ref:6~{snapshot.kind}
r2flutter -q -j -O ref:6~{snapshot.complete}
r2flutter -q -j -O ref:6~{snapshot.length}
r2flutter -q -j -O ref:6~{snapshot.fields[1].value.value}
r2flutter -O ref:6~kind=record
EOF
EXPECT=<<EOF
record
true
2
-2
ref=6 kind=record cid=67 shape=2 length=2 complete=true
EOF
RUN
+1 -1
View File
@@ -25,7 +25,7 @@ map
true
array
48
129
1
instance
SemanticsAction
1
+11 -11
View File
@@ -56,7 +56,7 @@ L ../src/r2/core_flutter.so
r2flutter -q -j -xz~{[0]}
EOF
EXPECT=<<EOF
{"ref":10404,"len":25,"value":"useShouldInterceptRequest","category":"lib","two_byte":false,"canonical":true,"addr":302465,"refs":[{"obj":195985,"type":"other","kind":"object_pool.entry","name":"pool=195985 index=0 entry=44440 pp_off=0x56cd0"}]}
{"ref":2429,"len":15,"value":"<optimized out>","category":"app","two_byte":false,"canonical":true,"addr":2133,"refs":[{"obj":195985,"type":"other","kind":"object_pool.entry","name":"pool=195985 index=0 entry=7053 pp_off=0xdc78"}]}
EOF
RUN
@@ -68,9 +68,9 @@ L ../src/r2/core_flutter.so
r2flutter -q -z~:0..3
EOF
EXPECT=<<EOF
useShouldInterceptRequest
There was an error but it could not be loaded from IsarCore.
exercise
<optimized out>
\x1a
4
EOF
RUN
@@ -82,10 +82,10 @@ L ../src/r2/core_flutter.so
r2flutter -q -r -x -z~:0..4
EOF
EXPECT=<<EOF
iz+ 0x00049d81 25
f str.useShouldInterceptRequest.0x49d81 25 @ 0x00049d81
Cs8 25 @ 0x00049d81
ax 0x49d81 0x3dbe24
iz+ 0x00000855 15
f str._optimized_out_.0x855 15 @ 0x00000855
Cs8 15 @ 0x00000855
ax 0x855 0x392dcc
EOF
RUN
@@ -97,8 +97,8 @@ L ../src/r2/core_flutter.so
r2flutter -qz~:0..3
EOF
EXPECT=<<EOF
useShouldInterceptRequest
There was an error but it could not be loaded from IsarCore.
exercise
<optimized out>
\x1a
4
EOF
RUN
+18
View File
@@ -0,0 +1,18 @@
NAME=strings-instance-pool-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
r2flutter -q -z~^detached$
r2flutter -q -z~^dark$
r2flutter -xz~object_pool.instance_field~pp_off=0x4780
r2flutter -q -r -xz~0x19649 0xdf327
EOF
EXPECT=<<EOF
detached
dark
# object_pool.instance_field other pool=54352 index=0 entry=2286 pp_off=0x4780 field=2 [ref=54352] @ 0xdf327
ax 0x19649 0xdf327
EOF
RUN
+18
View File
@@ -0,0 +1,18 @@
NAME=strings-instance-pool-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
r2flutter -q -z~^detached$
r2flutter -q -z~^dark$
r2flutter -xz~object_pool.instance_field~pp_off=0x38b8
r2flutter -q -r -xz~0x26fa30 0x230b89
EOF
EXPECT=<<EOF
detached
dark
# object_pool.instance_field other pool=41741 index=0 entry=1813 pp_off=0x38b8 field=1 [ref=41741] @ 0x230b89
ax 0x26fa30 0x230b89
EOF
RUN
+2 -2
View File
@@ -10,8 +10,8 @@ EXPECT=<<EOF
# class Pointer (size=32)
# type_params: 1
"td struct dart_Pointer { };"
# method 0x00000000 get:hashCode (GetterFunction)
# method 0x00000000 == (RegularFunction)
# method 0x00115f2c get:hashCode (GetterFunction)
# method 0x001299fc == (RegularFunction)
# class NativeType (size=16)
EOF
RUN
+4 -4
View File
@@ -8,8 +8,8 @@ EOF
EXPECT=<<EOF
class Pointer (size=32)
methods:
0x00000000 get:hashCode (GetterFunction)
0x00000000 == (RegularFunction)
0x00c6ab64 get:hashCode (GetterFunction)
0x00d8bde8 == (RegularFunction)
class NativeType (size=16)
class NativeFunction (size=16)
EOF
@@ -27,8 +27,8 @@ EXPECT=<<EOF
# class Pointer (size=32)
# type_params: 1
"td struct dart_Pointer { };"
# method 0x00000000 get:hashCode (GetterFunction)
# method 0x00000000 == (RegularFunction)
# method 0x00c6ab64 get:hashCode (GetterFunction)
# method 0x00d8bde8 == (RegularFunction)
# class NativeType (size=16)
EOF
RUN
+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
+3 -3
View File
@@ -8,8 +8,8 @@ EOF
EXPECT=<<EOF
metadata class.name class Pointer [ref=36652] -> string Pointer [ref=151] @ 0x1b24d0
metadata class.library class Pointer [ref=36652] -> library library#41733 [ref=41733]
metadata method.owner method Pointer.get:hashCode [ref=31064] -> class Pointer [ref=36652]
metadata method.name method Pointer.get:hashCode [ref=31064] -> string get:hashCode [ref=7217] @ 0x2b0230
metadata method.owner method Pointer.get:hashCode [ref=31064] @ 0x115f2c -> class Pointer [ref=36652]
metadata method.name method Pointer.get:hashCode [ref=31064] @ 0x115f2c -> string get:hashCode [ref=7217] @ 0x2b0230
EOF
RUN
@@ -21,7 +21,7 @@ L ../src/r2/core_flutter.so
r2flutter -j -l 3 -x
EOF
EXPECT=<<EOF
[{"kind":"class.name","origin":"metadata","src":{"type":"class","name":"Pointer","ref":36652},"dst":{"type":"string","name":"Pointer","ref":151,"addr":1778896}},{"kind":"class.library","origin":"metadata","src":{"type":"class","name":"Pointer","ref":36652},"dst":{"type":"library","name":"library#41733","ref":41733}},{"kind":"method.owner","origin":"metadata","src":{"type":"method","name":"Pointer.get:hashCode","ref":31064},"dst":{"type":"class","name":"Pointer","ref":36652}}]
[{"kind":"class.name","origin":"metadata","src":{"type":"class","name":"Pointer","ref":36652},"dst":{"type":"string","name":"Pointer","ref":151,"addr":1778896}},{"kind":"class.library","origin":"metadata","src":{"type":"class","name":"Pointer","ref":36652},"dst":{"type":"library","name":"library#41733","ref":41733}},{"kind":"method.owner","origin":"metadata","src":{"type":"method","name":"Pointer.get:hashCode","ref":31064,"addr":1138476},"dst":{"type":"class","name":"Pointer","ref":36652}}]
EOF
RUN