mirror of
https://github.com/radareorg/r2flutter.git
synced 2026-11-08 07:28:14 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae66842877 | ||
|
|
e6b9eefae9 | ||
|
|
5d86dff9fa | ||
|
|
14c1847177 | ||
|
|
edd90ba5c7 | ||
|
|
a7317c7705 | ||
|
|
3fe9dff0cc | ||
|
|
2e85b70bda | ||
|
|
b45d487fd3 | ||
|
|
b1994cd98e | ||
|
|
3b9d12efea | ||
|
|
f5d7bb15f1 | ||
|
|
66b294ab7c | ||
|
|
5a3da4b149 | ||
|
|
545a2161ca | ||
|
|
6bad309eb6 | ||
|
|
283242b724 | ||
|
|
81acc1c5df | ||
|
|
5d745cdd6b | ||
|
|
54c7aed1ba | ||
|
|
75ad39a7cb | ||
|
|
1f36c65729 | ||
|
|
2e9bb55763 | ||
|
|
f85bd3beae | ||
|
|
791547c855 | ||
|
|
ef2fd323d7 | ||
|
|
bea45d8348 | ||
|
|
43671cfcb3 | ||
|
|
2db2a74bc7 | ||
|
|
02efb309a2 | ||
|
|
08001fe45b | ||
|
|
5d3ffce739 | ||
|
|
b6ea0b8923 | ||
|
|
540efcf5e1 | ||
|
|
25dc49ef8f | ||
|
|
6a7e01b0c9 | ||
|
|
334c97b88d | ||
|
|
f10d30dd6f | ||
|
|
f138b952c9 | ||
|
|
833f89a5d9 | ||
|
|
897a98d24d | ||
|
|
e9fa905562 | ||
|
|
33bb19bbe0 | ||
|
|
d11ebc35b9 | ||
|
|
5592349d99 | ||
|
|
986e2cb42a | ||
|
|
cd6b655c0c | ||
|
|
239178152b | ||
|
|
d387c0b15b | ||
|
|
19d8b96fc5 | ||
|
|
7e1803998e | ||
|
|
33588f29a7 | ||
|
|
4eede7cb33 | ||
|
|
48736a2a89 | ||
|
|
f680d5551a | ||
|
|
07b0ca1e4e | ||
|
|
7f6449b82a | ||
|
|
3b8516b706 | ||
|
|
6b3730c535 | ||
|
|
fd857f5918 | ||
|
|
2a8d3151e8 | ||
|
|
d7ff3c5b78 | ||
|
|
4886597928 | ||
|
|
8257b88040 | ||
|
|
bfaf13a013 | ||
|
|
5cf313c681 | ||
|
|
553d583298 | ||
|
|
78013b15e1 | ||
|
|
20d18a1694 | ||
|
|
06adc8d617 | ||
|
|
a86cf02636 | ||
|
|
91c3e94630 |
No files matched your search
@@ -1,7 +1,7 @@
|
||||
name: ci
|
||||
|
||||
env:
|
||||
R2V: 6.1.6
|
||||
R2V: 6.2.2
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Installing radare2 ${{ env.R2V }}
|
||||
uses: radareorg/github-actions@v2
|
||||
uses: radareorg/github-actions@v3
|
||||
with:
|
||||
version: ${{ env.R2V }}
|
||||
from-git: false
|
||||
@@ -33,9 +33,9 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Install build dependencies
|
||||
run: sudo apt-get update -y && sudo apt-get install -y build-essential meson ninja-build pkg-config
|
||||
run: sudo apt-get update -y && sudo apt-get install -y build-essential meson ninja-build
|
||||
- name: Installing radare2 ${{ env.R2V }}
|
||||
uses: radareorg/github-actions@v2
|
||||
uses: radareorg/github-actions@v3
|
||||
with:
|
||||
version: ${{ env.R2V }}
|
||||
from-git: false
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Installing radare2 ${{ env.R2V }}
|
||||
uses: radareorg/github-actions@v2
|
||||
uses: radareorg/github-actions@v3
|
||||
with:
|
||||
version: ${{ env.R2V }}
|
||||
from-git: false
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install build dependencies
|
||||
run: sudo apt-get update -y && sudo apt-get install -y build-essential pkg-config
|
||||
run: sudo apt-get update -y && sudo apt-get install -y build-essential
|
||||
- name: Build radare2 from source with r2flutter
|
||||
run: |
|
||||
git clone --depth=1 https://github.com/radareorg/radare2.git
|
||||
|
||||
@@ -4,8 +4,9 @@ Scope
|
||||
- This document is for agents working in this repo to implement and iterate on the Dart AOT snapshot/ObjectPool parser and CLI.
|
||||
|
||||
Build
|
||||
- Requires radare2 dev headers (`r_core`) and `pkg-config`.
|
||||
- Build with `make` (uses pkg-config to resolve r2 includes/libs).
|
||||
- Requires radare2 (`r2` binary in PATH) to resolve include/link flags via `r2 -H`.
|
||||
- Do NOT use `pkg-config` — the build system uses `r2 -H R2_CFLAGS` and `r2 -H R2_LDFLAGS` instead.
|
||||
- Build with `make` (uses `r2 -H` to resolve r2 includes/libs).
|
||||
- Do not build with gcc oneliners, always use `make`
|
||||
- Run `make fmt` to format/indent the source code
|
||||
- To update the version change it in configure.acr and then run autogen.sh
|
||||
|
||||
@@ -4,7 +4,7 @@ Overview
|
||||
- Goal: Extract real Dart function names and entrypoints from Flutter/Dart AOT snapshots (libapp.so) without relying on afl/aflj or the Dart VM.
|
||||
- Tools:
|
||||
- r2flutter: Small C utility that loads `libapp.so` with radare2, locates AOT snapshots, and decodes the ObjectPool to emit names + addresses.
|
||||
- scripts/gen_offsets.sh: Generates a per-snapshot-hash offsets file (`r2flutter/offsets.json`) to keep the decoder stable across Dart versions.
|
||||
- scripts/update-dart-version: Unified script that pulls the Dart SDK, computes the snapshot hash, extracts CID values, and updates all tables.
|
||||
- Key property: r2flutter builds standalone (no Dart VM headers/libs) and does not parse afl output.
|
||||
|
||||
Repo Layout (relevant parts)
|
||||
@@ -13,18 +13,18 @@ Repo Layout (relevant parts)
|
||||
- dart_app.[ch]: Minimal app model; wires callback for extracted functions; emits addNames.r2/r2_dart_struct.h.
|
||||
- dart_pool_parse.c: Snapshot discovery (via r2), offsets loading (offsets.json), and clustered snapshot/ObjectPool decoder (constant pool → names).
|
||||
- dart_dumper.[ch]: Emits radare2 flags/comments, PP (x27) helper flags, and a basic struct header.
|
||||
- Makefile: Pure-C build; links against libr2 via pkg-config; no Dart VM linkage.
|
||||
- Makefile: Pure-C build; links against libr2 via `r2 -H`; no Dart VM linkage.
|
||||
- third_party/dartvm/: Vendored VM sources (headers only) to document snapshot/cluster formats used by the decoder (no linking).
|
||||
- arm64-v8a/: Example directory with libapp.so/libflutter.so for testing.
|
||||
- scripts/
|
||||
- gen_offsets.sh: Generates `r2flutter/offsets.json` keyed by snapshot hash using only local binaries + curl (when needed for future enhancement).
|
||||
- update-dart-version: Unified script to add new Dart versions (pulls SDK, computes hash, extracts CIDs, updates all tables).
|
||||
- blutter/: Standalone C++ reference implementation (with VM linkage) used as conceptual reference. r2flutter must not depend on it.
|
||||
|
||||
Quick Start
|
||||
- Requirements: radare2 development headers (pkg-config), make, a libapp.so and libflutter.so pair.
|
||||
- Requirements: radare2 (`r2` in PATH for `r2 -H` flag resolution), make, a libapp.so and libflutter.so pair.
|
||||
- Generate offsets for your libapp:
|
||||
- `./scripts/gen_offsets.sh r2flutter/arm64-v8a`
|
||||
- This produces `r2flutter/offsets.json` keyed by snapshot hash (read from VM snapshot data in libapp.so).
|
||||
- `./scripts/update-dart-version --hash <snapshot-hash> <dart-version>`
|
||||
- This produces `r2flutter/offsets.json` keyed by snapshot hash.
|
||||
- Build and run r2flutter:
|
||||
- `make -C r2flutter`
|
||||
- `./bin/r2flutter r2flutter/arm64-v8a r2flutter/out`
|
||||
@@ -59,13 +59,14 @@ Usage in radare2 sessions (Android ELF and iOS Mach-O)
|
||||
- You’ll see `method.*` flags and comments across the binary.
|
||||
|
||||
Maintaining for New Dart Versions
|
||||
1) Generate/Update offsets.json:
|
||||
- Run: `./scripts/gen_offsets.sh <dir-with-libapp+libflutter>`
|
||||
- This produces a new `hash → { compressed_word_size, heap_object_tag }` entry in `r2flutter/offsets.json`.
|
||||
- For deeper stability (field offsets), enhance `gen_offsets.sh` to:
|
||||
- `curl` the VM sources for the detected Dart version/commit (e.g., class_id.h, object.h, app_snapshot.cc).
|
||||
- Parse constants and offsets to compute a richer layout JSON.
|
||||
- Commit the updated offsets.json.
|
||||
1) Add a new Dart version:
|
||||
- Run: `./scripts/update-dart-version <dart-version>`
|
||||
- This pulls the Dart SDK from GitHub, computes the snapshot hash (MD5 of VM source files),
|
||||
extracts CID values from class_id.h, and updates both `offsets.json` and `src/lib/dart_version.c`.
|
||||
- For a local SDK checkout: `./scripts/update-dart-version --sdk-dir /path/to/dart-sdk <version>`
|
||||
- To add a hash manually (no SDK pull): `./scripts/update-dart-version --hash <md5> <version>`
|
||||
- To regenerate all tables from known data: `./scripts/update-dart-version --regenerate`
|
||||
- Commit the updated offsets.json and dart_version.c.
|
||||
2) Decoder tuning:
|
||||
- If the clustered snapshot changes (e.g., new EntryType or field order), adjust the decoder logic in `dart_pool_parse.c` referencing `third_party/dartvm/` sources.
|
||||
- Avoid adding a runtime dependency: keep vendored snapshots sources only as references (no linking).
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
CC ?= gcc
|
||||
R2_CFLAGS ?= $(shell r2 -H R2_CFLAGS)
|
||||
R2_LDFLAGS ?= $(shell r2 -H R2_LDFLAGS)
|
||||
R2_LIBS ?= $(shell r2 -H R2_LIBS)
|
||||
CFLAGS ?= -Wall -Wextra -O2 -fPIC
|
||||
CFLAGS += -Iinclude -Iinclude/r2flutter
|
||||
CFLAGS += $(R2_CFLAGS)
|
||||
LDFLAGS += $(R2_LDFLAGS)
|
||||
LDLIBS += $(R2_LIBS)
|
||||
DEPFLAGS = -MMD -MP
|
||||
R2R_JOBS ?= 1
|
||||
R2R_TIMEOUT ?= 30
|
||||
@@ -32,10 +34,14 @@ DEP_FILES = $(LIB_OBJ:.o=.d) $(MAIN_OBJ:.o=.d) $(ANALYSIS_OBJ:.o=.d)
|
||||
# Artifacts
|
||||
BIN_FILE = $(BIN_DIR)/r2flutter
|
||||
STATIC_LIB = $(BUILD_DIR)/libr2flutter.a
|
||||
INC_VER = $(SRC_DIR)/../include/r2flutter/r2flutter_version.h
|
||||
INC_VER = include/r2flutter/r2flutter_version.h
|
||||
|
||||
all: $(BIN_FILE) $(INC_VER)
|
||||
|
||||
# Regenerate C files from offsets.json (the single source of truth)
|
||||
gen:
|
||||
python3 scripts/update-dart-version --generate
|
||||
|
||||
$(INC_VER):
|
||||
./configure
|
||||
|
||||
@@ -43,7 +49,7 @@ $(STATIC_LIB): $(LIB_OBJ)
|
||||
ar rcs $@ $^
|
||||
|
||||
$(BIN_FILE): $(STATIC_LIB) $(MAIN_OBJ) $(ANALYSIS_OBJ) | $(shell mkdir -p $(BIN_DIR))
|
||||
$(CC) $(CFLAGS) -o $@ $(MAIN_OBJ) $(ANALYSIS_OBJ) -L$(BUILD_DIR) -lr2flutter $(LDFLAGS) -Wl,-rpath,$(PREFIX)/lib -g
|
||||
$(CC) $(CFLAGS) -o $@ $(MAIN_OBJ) $(ANALYSIS_OBJ) -L$(BUILD_DIR) -lr2flutter $(LDFLAGS) $(LDLIBS) -Wl,-rpath,$(PREFIX)/lib -g
|
||||
|
||||
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
|
||||
@mkdir -p $(dir $@)
|
||||
|
||||
@@ -1,144 +1,123 @@
|
||||
# r2flutter - Dart/Flutter support for Radare2
|
||||
# r2flutter
|
||||
|
||||
<p>
|
||||
<img border="0" align="left" width="125px" height="125px" src="doc/images/r2flutter500.png" />
|
||||
<p align="center">
|
||||
<img width="160" src="doc/images/r2flutter500.png" alt="r2flutter logo" />
|
||||
</p>
|
||||
|
||||
[](https://github.com/trufae/r2flutter/actions/workflows/ci.yml)
|
||||
|
||||
<br />
|
||||
The default parser uses radare2 for binary loading but does not rely on existing
|
||||
r2 analysis (`afl`/`aflj`); Dart-aware code analysis is opt-in with `-AAA`. The
|
||||
Dart parser is self-contained C code (no Dart VM headers or libs) and reads AOT
|
||||
snapshots, data images, clusters, ObjectPool records, and instruction tables
|
||||
directly.
|
||||
</p>
|
||||
**r2flutter brings Dart and Flutter AOT awareness to [radare2](https://rada.re/).**
|
||||
It reads the snapshots embedded in release builds and turns their metadata into
|
||||
useful names, addresses, strings, classes, and references for reverse
|
||||
engineering. It is available both as the `bin/r2flutter` command-line tool and
|
||||
as an `r2flutter` command inside radare2.
|
||||
|
||||
## Features
|
||||
Give it an extracted Android `libapp.so`, an Android directory containing one,
|
||||
an iOS `.app` bundle, or a direct AOT binary. AArch64 is the primary analysis
|
||||
target. The parser has in-tree layouts for Dart 2.10 through 3.12; see the
|
||||
[support matrix](doc/support.md) for platform and version details.
|
||||
|
||||
- Support iOS and Android Flutter apps
|
||||
- Accepts direct binaries, Android directories with `libapp.so`, and iOS `.app`
|
||||
bundles with `Frameworks/App.framework/App`
|
||||
- Derives names/addresses from Dart snapshots, data images, clusters,
|
||||
ObjectPool records, and instruction tables
|
||||
- Recovers snapshot headers, functions, classes, fields, string refs, xrefs,
|
||||
reconstructed ObjectPool PP data, and best-effort SBOM/component reports
|
||||
- Uses radare2 for binary loading while keeping the Dart parser self-contained
|
||||
- Prints text, JSON, or radare2 command output for supported dump actions
|
||||
## What it can do
|
||||
|
||||
## Supported Dart/Flutter Versions
|
||||
- Find Dart AOT snapshots in Flutter apps and supported standalone Dart
|
||||
containers.
|
||||
- Recover snapshot headers, functions, classes, fields, type names, strings,
|
||||
ObjectPool values, instruction-table entries, and metadata references.
|
||||
- Apply recovered names, flags, comments, classes, and code references to a
|
||||
radare2 session.
|
||||
- Emit readable text, JSON for automation, or radare2 commands for importing
|
||||
into another session.
|
||||
- Use Flutter's `--save-obfuscation-map` output to restore recovered names.
|
||||
|
||||
r2flutter supports Dart AOT snapshots from **Dart 2.10.0 to 3.10.7** (Flutter 1.22.x to 3.38.x).
|
||||
r2flutter is a metadata and analysis aid, not a Dart source-code decompiler.
|
||||
Recovery depends on what survived in the AOT snapshot, so some names and
|
||||
references are necessarily best effort.
|
||||
|
||||
The detailed support matrix, including tag-style changes and Android/iOS-specific notes, lives in [doc/support.md](doc/support.md).
|
||||
## Build and install
|
||||
|
||||
Unknown snapshot hashes default to the v3.9.2 profile with ObjectHeader tag encoding.
|
||||
You need `radare2` 6.2.2 or newer (or a git build of 6.2.1), including its
|
||||
development headers, and `r2` must be on `PATH`. The build obtains the
|
||||
required flags from `r2 -H`. Older radare2 versions lack the `ic+` attribute
|
||||
support used to register class metadata and are rejected at compile time.
|
||||
|
||||
## Building
|
||||
|
||||
This project can be used from the system shell or directly integrated inside radare2:
|
||||
|
||||
```bash
|
||||
```sh
|
||||
git clone https://github.com/trufae/r2flutter.git
|
||||
cd r2flutter
|
||||
make
|
||||
```
|
||||
|
||||
To get the plugin inside your r2 shell do that:
|
||||
The command-line tool is now ready at `bin/r2flutter`.
|
||||
|
||||
```bash
|
||||
To install the radare2 plugin for your user account:
|
||||
|
||||
```sh
|
||||
make user-install
|
||||
```
|
||||
|
||||
## Usage
|
||||
For a system-wide command-line tool and plugin, build the plugin first and
|
||||
then use the usual install prefix:
|
||||
|
||||
```bash
|
||||
Usage: bin/r2flutter [options] <libapp_path_or_dir>
|
||||
Modifiers:
|
||||
-h Show help
|
||||
-j Output in JSON format
|
||||
-q Compact output; suppress non-essential detail
|
||||
-r Output r2 commands for the selected action
|
||||
-n Heuristic fallback for unknown functions; may assign wrong names
|
||||
-v Verbose (stderr debug info)
|
||||
-vv More verbose (dump headers)
|
||||
-V Show version
|
||||
Actions:
|
||||
-A Analyze Dart snapshot and apply flags/comments
|
||||
-AA Analyze with field extraction enabled
|
||||
-AAA Run Dart-aware code analysis and recover code refs
|
||||
-c Print extracted class information
|
||||
-f Print all extracted functions (addr name)
|
||||
-H Print Dart AOT snapshot header info
|
||||
-HH Print extended snapshot header and cluster layout
|
||||
-HHH Decode selected cluster payloads for diagnostics
|
||||
-i Print instruction table entries to stdout
|
||||
-O <addr|pp+off> Decode Dart tagged/object pointer or ObjectPool PP slot
|
||||
-p Print reconstructed ObjectPool PP value
|
||||
-R Print radare2 script for snapshot analysis
|
||||
-S Print best-effort recovered SBOM/components
|
||||
-T Print string-based type names
|
||||
-x Print metadata/data-image xrefs; with -z, include string refs/ax in -r
|
||||
-z Print reliable ObjectPool-referenced strings (-q prints values only)
|
||||
-zz Print all fuzzy/carved extracted strings (-xzz includes refs/ax in -r)
|
||||
Options:
|
||||
-l <N> Limit output to N items
|
||||
-m <file> Load Flutter obfuscation map JSON
|
||||
```sh
|
||||
make r2
|
||||
sudo make install
|
||||
```
|
||||
|
||||
`bin/r2flutter` runs one action per invocation. Directory inputs resolve Android
|
||||
first (`libapp.so`), then iOS (`Frameworks/App.framework/App`).
|
||||
## First look at an app
|
||||
|
||||
`-f` and the `-A` analysis flow skip loader-provided ELF/Mach-O stub symbols; radare2 already gets those from `RBin`, so `r2flutter` stays focused on Dart-derived metadata.
|
||||
Start with the snapshot header. It confirms that r2flutter found a Dart AOT
|
||||
image and shows the detected Dart profile:
|
||||
|
||||
`-m` consumes the JSON array emitted by Flutter/Dart `--save-obfuscation-map`. r2flutter inverts that mapping and applies it to recovered functions, instruction-table names, classes, fields, and method owners.
|
||||
|
||||
`-n` is intentionally opt-in. It consumes `package:` and `dart:` strings from the data image as a sequential fallback for otherwise unnamed functions, so it can produce plausible but incorrect names when the string order does not match the instruction table.
|
||||
|
||||
`-S` emits a best-effort recovered component report, not a complete dependency
|
||||
inventory. JSON output uses `format: "r2flutter-recovered-sbom"` and
|
||||
`complete: false`; package versions are usually `null` unless explicit packaged
|
||||
metadata such as `pubspec.lock`, `.dart_tool/package_config.json`, or framework
|
||||
`Info.plist` survived in the input bundle.
|
||||
|
||||
Dump actions honor the global format modifiers:
|
||||
|
||||
```bash
|
||||
bin/r2flutter -i test/bins/ios/Runner.app
|
||||
bin/r2flutter -j -l 16 -i test/bins/ios/Runner.app
|
||||
bin/r2flutter -r -l 16 -i test/bins/ios/Runner.app
|
||||
bin/r2flutter -jS test/bins/ios/Runner.app
|
||||
bin/r2flutter -HHH -l 36 test/bins/android/mafia/libapp.so
|
||||
bin/r2flutter -O pp+0x68 test/bins/android/mafia/libapp.so
|
||||
bin/r2flutter -qrz test/bins/ios/Runner.app
|
||||
bin/r2flutter -rz test/bins/ios/Runner.app
|
||||
bin/r2flutter -rzz test/bins/ios/Runner.app
|
||||
```sh
|
||||
bin/r2flutter -H path/to/libapp.so
|
||||
```
|
||||
|
||||
## radare2 Plugin
|
||||
Then try a small, practical extraction:
|
||||
|
||||
`make user-install` installs the core plugin command as `r2flutter`. Inside r2,
|
||||
the same modifiers apply to dump actions:
|
||||
```sh
|
||||
# Recovered Dart function entrypoints and names
|
||||
bin/r2flutter -f path/to/libapp.so
|
||||
|
||||
```text
|
||||
r2flutter -A analyze and apply flags/comments
|
||||
r2flutter -AA analyze with field extraction enabled
|
||||
r2flutter -AAA run Dart-aware code analysis and recover refs/comments
|
||||
r2flutter -C apply Dart classes, fields, methods and types
|
||||
r2flutter -jS print recovered components/SBOM as JSON
|
||||
r2flutter -r -p map a synthetic ObjectPool and set anal.gp/x27
|
||||
# A compact JSON header, useful for scripts
|
||||
bin/r2flutter -jH path/to/libapp.so
|
||||
|
||||
# The first 20 instruction-table entries
|
||||
bin/r2flutter -l 20 -i path/to/libapp.so
|
||||
```
|
||||
|
||||
Plugin config keys:
|
||||
Directory and bundle inputs work too. For example, pass an Android directory
|
||||
containing `libapp.so`, or an iOS `Runner.app` bundle directly.
|
||||
|
||||
- `r2flutter.mapfile`: Flutter obfuscation map JSON path
|
||||
- `r2flutter.namepool`: enable heuristic name-pool fallback
|
||||
After `make user-install`, open the same binary in radare2 and apply the Dart
|
||||
metadata to the analysis session:
|
||||
|
||||
## Dependencies
|
||||
```sh
|
||||
r2 -q path/to/libapp.so
|
||||
> r2flutter -AAA
|
||||
> afl~method
|
||||
```
|
||||
|
||||
- radare2 (with development headers)
|
||||
- pkg-config
|
||||
`-AAA` is the Dart-aware analysis pass. For a concise list of every action and
|
||||
output modifier, run `bin/r2flutter -h` or `r2flutter -h` inside radare2.
|
||||
|
||||
## Other Projects
|
||||
## Learn more
|
||||
|
||||
- https://github.com/dart-lang/sdk
|
||||
- https://github.com/worawit/blutter
|
||||
- https://github.com/zboralski/unflutter
|
||||
- https://github.com/Impact-I/reFlutter
|
||||
- [Support matrix](doc/support.md) — accepted containers, platform scope, and
|
||||
Dart layout coverage.
|
||||
- [AuthPass walkthrough](doc/tuto-authpass.md) — a complete iOS AOT analysis
|
||||
example using recovered method names in radare2.
|
||||
- [ObjectPool internals](doc/objpool.md) and [instruction-table entries](doc/its.md)
|
||||
— the two central structures behind function and constant recovery.
|
||||
- [Strings](doc/strings.md) and [cross references](doc/xrefs.md) — what can be
|
||||
recovered and how reliable each result is.
|
||||
- [Flutter obfuscation maps](doc/obfuscate.md) — restore names from a map
|
||||
produced during a Flutter build.
|
||||
|
||||
## Related projects
|
||||
|
||||
- [Dart SDK](https://github.com/dart-lang/sdk)
|
||||
- [blutter](https://github.com/worawit/blutter)
|
||||
- [unflutter](https://github.com/zboralski/unflutter)
|
||||
- [reFlutter](https://github.com/Impact-I/reFlutter)
|
||||
- [dae](https://github.com/ejfkdev/dae)
|
||||
|
||||
Released under the [MIT license](LICENSE.txt).
|
||||
@@ -0,0 +1,307 @@
|
||||
# r2flutter — Analysis & Correction Roadmap
|
||||
|
||||
Review of the library (`src/lib`), the r2 core plugin (`src/r2`), and the CLI
|
||||
(`src/tool`) for inconsistent, confusing, duplicated, or wrong assumptions when
|
||||
analyzing Flutter/Dart AOT snapshots on iOS and Android. Findings are verified
|
||||
against the source, the sample binaries in `test/bins`, real Dart SDK sources in
|
||||
`third_party/dart-sdk`, and a locally built Dart 3.12.2 AOT snapshot.
|
||||
|
||||
Maintainer priorities: **1) robust Dart-VM version identification** ·
|
||||
**2) performance** · **3) iOS/Android correctness parity**.
|
||||
|
||||
Items are ranked **P0** (do next) → **P3** (nice to have). Severity is implied by
|
||||
rank; each item states the concrete failure it causes.
|
||||
|
||||
---
|
||||
|
||||
## Status — what has been fixed (do not redo)
|
||||
|
||||
| Commit | What landed |
|
||||
|--------|-------------|
|
||||
| `d387c0b` | Unknown-hash **fingerprinting** replaces the blind hardcoded "v3.9.2 defaults" (`dart_newest_profile()` auto-tracks the profile table). `-H` reports **`version_source`** (`exact-hash`/`override`/`fingerprint`/`unknown`) and labels guessed layouts `~<ver>+ (fingerprint)`. Report and decode paths now share the same layout (previously decode used a degenerate zeroed struct). Test: `header-fingerprint`. |
|
||||
| `2391781` | Explicit **3.7/3.8/3.10/3.11/3.12 CID + layout profiles** (parsed from each release's real `class_id.h`). Also **verified**: the floored tables were already byte-for-byte correct for *every* release 2.10.0–3.12.1, so no CID value changed — the rows are explicit coverage + a regression anchor. |
|
||||
| `cd6b655` | Discovery hardening: removed the **bogus instruction-blob classification** (only data snapshots carry magic `0xf5f5dcdc`; a stray hit could poison `iso_instr`); symbol-derived addresses are no longer clobbered by scan guesses; `vaddr==0` duplicates can't erase a good address; magic hits deduped; Mach-O segments skipped. |
|
||||
| `0716a95` | **Structural instruction-image location** for symbol-less Mach-O: `vm_instr` = first executable section, `iso_instr = align_up(vm_instr + ImageSize, 64)` from the Dart Image header, with cross-checks that fall back to `0` rather than emit junk. Verified on stripped Runner (`0x4000`/`0xe700`, full 7671 IT entries) and AuthPass (`0x5a80`/`0xfdc0`). The `discovery-stripped-ios` test clones the mapped thin slice with `o:`, clears symbol/export metadata with `wv4`, and reloads it with `ooi`. |
|
||||
| `d11ebc3` | **P0.1**: `modern_get_fill_spec`'s fallback `rules[]` is keyed by `DartCidKind` and resolved through a per-layout `by_kind[]` table built once in `modern_cid_cache_init`, instead of literal 3.6-era CIDs. Fixes a whole-block off-by-one on 3.2–3.5 layouts (36 CIDs from `SENTINEL` upward got the neighbouring class's ref/scalar layout) and `MonomorphicSmiableCall`/`UnlinkedCall` on 3.9+, where the two swapped places. Abstract classes that never form a cluster (`CallSiteData`, `AbstractType`, `TypedDataBase`) no longer claim a spec. |
|
||||
| `33bb19b` | **P2.5**: one shared cluster walker (`ModernWalk`) replaces the five hand-rolled tag→cid→alloc→fill loops. Consumers register readers in a `DartCidKind`-keyed table and the walker skips every class they don't claim, so tag decoding, alloc/fill skipping, `start_ref` bookkeeping and the inline-string decision each exist **once** (were 4–5× each). `modern_walk_fill_recorded` serves the resolver, which reads each cluster from its own recorded offset. Verified behaviour-preserving: output byte-identical on 5 samples × 21 actions. |
|
||||
| `e9fa905` | **Part of P0.2**: the read-only-data rule (`modern_walk_is_rodata`) — the six classes Dart moves into the data image when pointer compression is off (exactly `Serializer::ReadOnlyObjectType`) now correctly emit no fill records. Inert until the gate opens; see P0.2 for what still blocks it. |
|
||||
| `897a98d` | **P1.1**: one `dart_cid_from_tags` helper now decodes `CID_INT32`, `CID_SHIFT1`, and `OBJECT_HEADER` tags for object inspection, modern and legacy cluster walks, data-image scanners, and InstructionTable discovery. This removes the duplicate decoders and fixes `CID_INT32` being treated as `OBJECT_HEADER` in two paths. |
|
||||
| `833f89a` | **P2.1**: deleted the legacy cluster walker — `deserialize_clusters`, `decode_class_cluster`, `decode_function_cluster`, `resolve_names`, the `DartClass`/`DartPoolFunction` types and the `DartCtx.classes`/`.functions` fields that only it wrote (244 lines). It had been running on every `-f`/`-A` alongside the modern scanner while contributing nothing: verified before removal by stubbing it out (50/50 outputs identical), and after by diffing 95 sample×action outputs against the previous commit. `decode_string_cluster`/`skip_generic_cluster`/`free_dart_string` stay — the legacy **class** parser in `dart_pool_classes.c` still uses them for CID_SHIFT1 samples. |
|
||||
| `f10d30d` | **P2.2**: the fake `kXxxCid` enum is gone. Only 3 of its 28 values (`Class`, `PatchClass`, `Function`) matched any real Dart release, so the legacy class parser decoded Class/Function clusters and silently skipped String/Array/Type/Mint/Double — the ones it needed for names. Its `switch` is now an `if` chain over `LegacyClusterCids`, resolved per layout via `dart_cid_get`. The two sites that fell back to a *fabricated* id when resolution failed (`dart_pool_data_image.c`, `dart_pool_it.c`) now fail closed, and a third raw `kFieldCid` comparison was resolved the same way. `builtin_type_name` keeps working on `Type.type_class_id` — a class id packed in the type flags, not a ref despite the field name — but against real ids. The `ti->kind` discriminator, which was never a snapshot id, became `DartTypeKind`. |
|
||||
| `f10d30d` | **Perf, found via P2.2**: `cid_table_for_layout` resolved the CID table by comparing version *strings* against every row on **every** `dart_cid_get` call, and that call sits per-object on some paths. A one-entry memo keyed on the layout pointer makes it a pointer compare. Synthetic `-x` 4862 → **37 ms** (131×), mafia `-f` 1008 → 550 ms, mafia `-c` 756 → 312 ms. Output unchanged on all 95 comparisons. This was already the dominant cost *before* P2.2 — more evidence for the P3.1 note that the double cluster walk was the wrong suspect. |
|
||||
| `251bec7` | **Initial `--dump-xrefs`**: real-binary `-x` tests (`xrefs-android`, `xrefs-ios`) land, so the ObjectPool-use scanner is no longer covered only by the synthetic blob. |
|
||||
| `25dc49e` | **P3.5 + two `-x` correctness bugs**: `collect_pool_uses_from_entry` read JSON keys `pdj` does not emit. `offset` (real key: `addr`) always returned 0, so *every* pool use fell back to `entry->address` — the reported address was the function start, never the referencing instruction. `opstr` (real keys: `disasm`/`opcode`) was always NULL, making two of the three parser calls duplicates. The fixed 96-op window also ran past short functions (median Dart function is 28 instructions) into their neighbours; it is now bounded by the next entry's address. After the fix 100% of pool uses lie inside the function they are attributed to (before, 100% sat at a function start, which is why the attribution check could not fail). mafia `-x` 7783 → 4738 ms. Also hardens the `dart_cid_get` memo to key on a copy of the version text, not the (freeable) layout pointer. |
|
||||
| `334c97b` | **P2.3**: `cid_table_for_layout`'s fallback for a layout with no usable version now goes through `dart_newest_profile()` instead of the literal `"3.9.2"`, so it agrees with the baseline the version fingerprinter picks for an unknown hash. Verified a no-op today (the two tables have zero differing CIDs); it stops being one the moment a release shifts the numbering. |
|
||||
| `540efcf` | **P0.2 largely landed — the modern gate is now open.** `dart_modern_is_supported_snapshot` accepts `cws == 8` **and** `CID_SHIFT1`, not just `cws == 4` / OBJECT_HEADER. The fill desync that blocked this (the roadmap's cluster-12 failure on `hello.aot`) is resolved by `modern_skip_fill_instance` (version-dependent instance fill shape) and `modern_load_rodata_strings`, which reads the six RO-data string classes from the data image where uncompressed builds put them. **Result:** iOS Runner (3.4.3) recovers **6389 method names** where it emitted `func.<addr>` placeholders before; `hello.aot` (3.11.5) recovers 1130/1325. Tests `dump-funcs-ios-runner`/`-authpass`/`dump-it-ios-runner` updated to assert real names. **Still gated:** class/field extraction (`modern_can_extract_classes` keeps `cws == 4`), so `-c` on cws=8 still shows the weak `size=0` fallback — see the trimmed P0.2. |
|
||||
| `b6ea0b8` | **Synthetic fixture reconciled** with the varint-tag switch. `6a7e01b` correctly made the legacy class parser read cluster tags as varints (Dart writes them with `Read<uint32_t>`, a varint), which broke `bins/synthetic/field_snapshot.bin` — it stores raw LE u32 tags. Rather than re-encode the blob, the parser branches on `synthetic_legacy_parse`: raw u32 for the fixture, varint for real snapshots. All tests green again. The fixture still has no generator (see P4). |
|
||||
| `02efb30` | **String comments in `-AAA` disasm**: `PP+N` ObjectPool loads that resolve to a string now emit a `; dart: string "…"` comment at the referencing instruction. New test `analysis-string-comments`. |
|
||||
| `2db2a74` | **`-AAA` PP-string comments work on iOS.** A new direct resolver (`dart_modern_collect_direct_object_pool_string_refs`) resolves ObjectPool slots to string literals straight from the cluster graph, so `flutter_analysis.c` can annotate loads without a disasm round-trip. New test `analysis-string-comments-ios` asserts an `https` string comment on Runner. (Does **not** touch `-x`, which still round-trips through `pdj` — see the P3.1 note.) |
|
||||
| `bea45d8` | **Public API rename sweep**: `dart_modern_is_supported_snapshot`→`modern_supported`, `dart_ctx_effective_version/_hash`→`dart_ctx_version/_hash`, `dart_pool_enumerate`→`dart_pool_scan`, `dart_pool_dump_pp`→`dump_pool`, `dart_pool_extract_object_pool_strings`→`extract_pool_strings`, `dart_pool_dump_entrypoint`→`dump_entry`. No behaviour change. |
|
||||
| `43671cf` / `ef2fd32` / `791547c` | Test-only (authpass tutorial uses pure r2 commands; extras/analysis output) and README. No source behaviour change. |
|
||||
| *(uncommitted)* | **P0.2 second half — class/field extraction on `cws == 8`.** Two changes: `modern_extract_classes` now calls `modern_load_rodata_strings` after the cluster walk (the string classes live in the RO-data image on uncompressed builds, so their names were unresolved — every class came out `class_NNNN size=…`), and `modern_can_extract_classes` drops the `cws == 4` gate. The fill readers already decoded varints and scaled by `compressed_word_size`, so no reader change was needed. **Result** (OBJECT_HEADER cws=8): `hello.aot` 480/501 classes with real names+sizes+fields+methods (was 0), Runner 2233/2254 (was 0). cws=4 byte-identical (mafia unchanged). Graceful fallback preserved where the modern walk can't run: 2.18.2 (`android/first`) still desyncs in fill and AuthPass is `CID_SHIFT1` (class gate still requires OBJECT_HEADER) — both byte-identical to before. Goldens `classes-ios`/`types-ios`/`xrefs-ios` regenerated to the real output. |
|
||||
|
||||
Corrections to earlier assumptions, worth remembering:
|
||||
- **The CID tables are not the problem.** Flooring is exactly correct 2.10→3.12,
|
||||
verified against SDK sources. Only a *future* CID shift needs action, and the
|
||||
generator adds a profile automatically when one appears.
|
||||
- **The `kDart*` snapshot symbols survive `strip`** — they are external/global and
|
||||
load-bearing (the engine resolves them at runtime). Symbol-less iOS is an edge
|
||||
case (obfuscators/repackagers), not the norm.
|
||||
- **The VM snapshot's canonical string cluster carries no canonical-set table**,
|
||||
unlike the isolate's. Measured on `android/mafia`: skipping one there consumes
|
||||
the next cluster's bytes and desynchronises the whole walk. This contradicts
|
||||
the SDK, where `StringDeserializationCluster::ReadAlloc` always calls
|
||||
`BuildCanonicalSetFromLayout` and both snapshots are read with
|
||||
`is_non_root_unit=false`. Unexplained; `modern_vm_strings_alloc` encodes the
|
||||
measured behaviour. Worth revisiting — it may mean the VM cluster range is
|
||||
being located slightly wrong and the strings we recover are a happy accident.
|
||||
|
||||
---
|
||||
|
||||
## The finding that reshaped priorities — now largely acted on
|
||||
|
||||
Historically **only 1 of 5 sample binaries** reached the "modern" (good) cluster
|
||||
decoder, because `dart_modern_is_supported_snapshot()` gated on
|
||||
`tag_style == OBJECT_HEADER && cws == 4`. `540efcf` opened that gate:
|
||||
|
||||
| Sample | Dart | tag_style | cws | modern **name** path | modern **class/field** path |
|
||||
|--------|------|-----------|-----|-----------------|-----------------|
|
||||
| `android/mafia` | 3.9.2 | OBJECT_HEADER | 4 | **yes** | **yes** |
|
||||
| `android/first` | 2.18.2 | OBJECT_HEADER | 8 | **yes** | no — fill desync (see below) |
|
||||
| `ios/Runner.app` | 3.4.3 | OBJECT_HEADER | 8 | **yes** | **yes** (2233/2254) |
|
||||
| `ios/AuthPass.app` | 3.2.5 | CID_SHIFT1 | 8 | **yes** | no — class gate is OBJECT_HEADER-only |
|
||||
| `macos/hello.aot` | 3.11.5 | OBJECT_HEADER | 8 | **yes** | **yes** (480/501) |
|
||||
|
||||
Name recovery runs on all five; class/field extraction now runs on every
|
||||
**OBJECT_HEADER** sample regardless of pointer width. Two hold-outs remain, both
|
||||
falling back gracefully (byte-identical to the old string-scan output):
|
||||
- **`android/first` (2.18.2)**: the modern fill walk desyncs at cluster 0
|
||||
(`cid=92`) — a genuinely version-dependent fill shape in a pre-3.0 release, the
|
||||
same class of problem P0.1 addressed for CIDs but for fill *layout*. This is the
|
||||
only remaining fill-desync case in the corpus.
|
||||
- **`ios/AuthPass.app` (3.2.5, CID_SHIFT1)**: `modern_can_extract_classes` still
|
||||
requires OBJECT_HEADER. The name path handles CID_SHIFT1; extending class
|
||||
extraction to it is untried.
|
||||
|
||||
---
|
||||
|
||||
## P0 — correctness bugs with no test coverage
|
||||
|
||||
### P0.2 (done for OBJECT_HEADER; two edge cases left)
|
||||
Class/field extraction on `cws == 8` **landed** (see status table): the gate
|
||||
dropped and `modern_load_rodata_strings` now runs on the class path. `hello.aot`
|
||||
and Runner recover real classes, fields and methods. Two follow-ups remain, both
|
||||
lower-value than the main win and both currently falling back cleanly:
|
||||
|
||||
1. **2.18.2 fill desync** (`android/first`). The modern walk fails at cluster 0
|
||||
(`cid=92`, an `Array`/string cluster in that era). Some pre-3.0 fill shape
|
||||
differs from `rules[]`. To fix: bisect the first divergent cluster by diffing
|
||||
per-class `ReadFill` between an old SDK and current (`third_party` has both),
|
||||
using byte-per-object as the oracle, then make the affected rule version-aware
|
||||
— same method the roadmap sketched for the (now-resolved) cluster-12 case.
|
||||
Guard rails: `-c` must stay byte-identical on the working samples.
|
||||
2. **CID_SHIFT1 class extraction** (`ios/AuthPass.app`, 3.2.5).
|
||||
`modern_can_extract_classes` requires OBJECT_HEADER; the name path already
|
||||
handles CID_SHIFT1, so the cluster walk works — the untested part is whether
|
||||
the class *fill* reader is correct under the shifted tag encoding. Try opening
|
||||
it and check AuthPass `-c` against a known class list.
|
||||
|
||||
### P0.3 Test corpus does not cover the decoders we ship
|
||||
Progress: iOS **name** recovery on cws=8 is now asserted
|
||||
(`dump-funcs-ios-runner`/`-authpass`, `dump-it-ios-runner`), and `-x` has
|
||||
real-binary coverage (`xrefs-android`/`-ios`). Remaining gaps:
|
||||
- a **Dart 3.4/3.5-era compressed-pointer** app (P0.1 is fixed but still has no
|
||||
end-to-end regression test; it was verified only by resolving `rules[]` against
|
||||
each layout in isolation);
|
||||
- cws=8 class/field extraction is now covered on both paths: iOS (`classes-ios`,
|
||||
`types-ios` on Runner) and macOS/standalone (`dump-classes-macos` on
|
||||
`hello.aot`, added alongside this note). The macOS golden is a real regression
|
||||
anchor — reverting the `cws==8` gate fails all six of its assertions;
|
||||
- more **iOS** binaries generally, ideally real obfuscated/stripped release
|
||||
`App`s (also broadens the `0716a95` structural path, per `COMMIT2.md`);
|
||||
- a **32-bit ARM** app if one can be found (validates P1.2).
|
||||
The remaining gaps (3.4/3.5-era compressed, obfuscated iOS, 32-bit) all need
|
||||
apps **collected externally** — the installed Dart SDK is 3.12.2, so a locally
|
||||
built AOT would exercise the modern decoder but *not* the old CID layout P0.1
|
||||
corrected, which is the one that still lacks an end-to-end test.
|
||||
|
||||
---
|
||||
|
||||
## P1 — correctness, lower prevalence or already-guarded
|
||||
|
||||
### P1.2 Hardcoded 8-byte target word / arm64 assumptions
|
||||
- `modern_target_word_size()` (`dart_pool_modern.c:1196-1199`) **ignores `ctx` and
|
||||
returns 8** — it takes `ctx` only to `(void)` it. All ObjectPool `pp_offset`
|
||||
math (`:1201-1208`) and the synthetic-PP builder (`:2243-2262`) are wrong on
|
||||
32-bit ARM, so `PP+N` lookups resolve to the wrong entry.
|
||||
- Entry-point stride hardcodes `*4` (`dart_pool_clusters.c:164`,
|
||||
`dart_pool_classes.c:170`, `dart_pool_it.c:349,399`).
|
||||
- `-AAA` (`src/r2/flutter_analysis.c:104-129`) is entirely arm64 (PP=`x27`,
|
||||
`x15/x29`, AAPCS64 clobber) with **no arch guard**; on arm32/x64 it emits wrong
|
||||
PP annotations instead of bailing. Neither frontend checks `asm.arch`/`bits`.
|
||||
**Fix:** gate on arch, derive the PP register, or refuse with a clear message.
|
||||
|
||||
### P1.3 Plugin/CLI frontend drift (AGENTS.md violations)
|
||||
Two hand-maintained parsers: CLI getopt (`main.c:118`) vs plugin
|
||||
(`core_flutter.c:192+`).
|
||||
- **`-r` is missing in the plugin** (it only has `-*`), directly violating
|
||||
AGENTS.md ("-q, -j and -r flags are modifiers"). `r2flutter -f -r` errors.
|
||||
- Plugin **silently no-ops under `r2 -n`** (`dart_app_new_from_core` returns NULL
|
||||
via `R_UNWRAP4(core,bin,cur,file)`), so `-f/-A/-R` do nothing while `-H/-z/-i`
|
||||
work — and `r2 -n` is the workflow AGENTS.md recommends.
|
||||
- CLI help advertises `[jr*]`/`-c*` but `*` is not in the getopt string.
|
||||
- Plugin help omits `-q` and `-f <N>`; `-C` is plugin-only; `-E` is CLI-only;
|
||||
`-f <N>` grammar differs (CLI treats the number as the path).
|
||||
**Fix:** one shared parser/dispatch in the library so the modifier contract holds
|
||||
once. Cheap, visible, and unblocks consistent `-q/-j/-r` everywhere.
|
||||
|
||||
### P1.4 iOS `LC_NOTE` (`__dart_app_snap`) container is CLI-only
|
||||
`main.c:84-97` extracts the embedded inner Mach-O; the plugin has no equivalent,
|
||||
so `r2flutter -H` inside r2 analyses the outer VM shell and fails, and
|
||||
`dctx.container_*` stays empty. **Fix:** move detection/extraction into the
|
||||
library so both frontends share it.
|
||||
|
||||
### P1.5 Single-data-snapshot aliasing
|
||||
`pick_vm_iso_by_size` (`dart_pool_discovery.c:25-49`) with one snapshot assigns
|
||||
the same address to both `vm_data` and `iso_data` (visible on the synthetic bin:
|
||||
both `0x4`), so one is parsed as the other. The `sbom-synthetic` golden currently
|
||||
*encodes* this, so fixing it means deciding the right semantics (probably: lone
|
||||
snapshot is the isolate, leave `vm_data = 0`) and updating that test.
|
||||
|
||||
---
|
||||
|
||||
## P2 — duplication, dead code, structural debt
|
||||
|
||||
### P2.4 CID/layout data still lives in three places
|
||||
`offsets.json` (source of truth, generated from SDK), `dart_cid.c` `cid_tables[]`
|
||||
(generated ✔), and `dart_offsets.h` `dart_hash_entries[]` — which only carries
|
||||
`hash → compressed_word_size`, a value already derivable from the profile and then
|
||||
overridden by the feature flags anyway (cws is decided three times, flags win).
|
||||
**Fix:** drop the hash→cws table or derive it.
|
||||
|
||||
### P2.6 `data_image_base` recomputed in ≥3 places with a magic-16 fallback
|
||||
`dart_object.c:304-305`, `dart_pool_classes.c:1646-1655`, plus IT callers each do
|
||||
`snapshot + align_up(total_len, layout->max_alignment ?: 16)`. Extract one helper
|
||||
and confirm alignment is genuinely derived (it can be raised to 64) rather than
|
||||
silently defaulted.
|
||||
|
||||
### P2.7 `-x`/`-z` special-case in both frontends (partly overtaken)
|
||||
`core_flutter.c` (`case 'x'`) and `main.c` set `string_refs = true` for *any*
|
||||
`-x`. The dispatch is now live (`case 'x' → dart_pool_dump_xrefs`), so the
|
||||
"dead branch" framing no longer holds — but the unconditional `string_refs`
|
||||
plus the `if (cmd->action != 'z')` guard still reads as accidental coupling
|
||||
between `-x` and `-z`. Re-audit against current line numbers before acting;
|
||||
lower priority than when first written.
|
||||
|
||||
---
|
||||
|
||||
## P3 — performance
|
||||
|
||||
Read caching (`read_mem`, `dart_pool_snapshot.c:10-44`) already turns per-byte
|
||||
varint reads into a 1 MB sliding window. Remaining hotspots, largest first:
|
||||
|
||||
### P3.1 Redundant full passes over the same regions
|
||||
A single `-f`/`-A` walks the cluster stream twice and the data image 2–3 times.
|
||||
|
||||
**This was claimed to be the biggest perf win; measured, it is not.** Removing
|
||||
the whole legacy cluster walk changes runtime by ~1%, inside noise (mafia `-f`
|
||||
993 → 1005 ms; AuthPass `-f` 616 → 618 ms) — it bails out early rather than
|
||||
re-walking the stream.
|
||||
|
||||
The real cost was elsewhere, and profiling found it: `dart_cid_get` re-resolved
|
||||
the CID table by version-string comparison on every call (see the status table).
|
||||
Memoising it took mafia `-f` from 1008 → 550 ms and the synthetic `-x` from 4862
|
||||
→ 37 ms — far more than P2.1 could ever have given. **Profile before spending
|
||||
time here.**
|
||||
|
||||
`-x` was the next slowest; `25dc49e` fixed its correctness bugs and the window
|
||||
bound cut mafia `-x` 7783 → 4738 ms. The `pdj` JSON round-trip is now gone too
|
||||
(*uncommitted*): `collect_pool_uses_from_entry` decodes each instruction with
|
||||
`r_core_anal_op` and feeds `op->mnemonic` to the existing operand parsers — the
|
||||
same disassembly text, minus the JSON build/parse, matching the `-AAA` path in
|
||||
`flutter_analysis.c`. Output byte-identical on all 5 samples; mafia `-x`
|
||||
6052 → 3842 ms (~1.6×). AuthPass gains less (12776 → 11398 ms) — it spends most
|
||||
of its `-x` time in name resolution, not disasm. A full structured-operand
|
||||
rewrite (reading `RAnalValue` instead of `op->mnemonic` text) could skip capstone
|
||||
text rendering too, but arm64 operand modelling is unreliable enough that it
|
||||
would need per-encoding output diffing — not worth the risk for the remainder.
|
||||
|
||||
**The 4096-entry scan cap is justified, not a bug — measured.** Removing it
|
||||
(scanning all 37258 IT entries) takes mafia `-x` from ~3.8 s to **55.8 s**; the
|
||||
per-instruction disasm dominates and there is no cheap way around it. The real
|
||||
wart is that `-l` conflates two limits: the output-line cap (`append_xref_info`)
|
||||
*and* the pool-use entry-scan cap (`collect_pool_uses`, default 4096 when no
|
||||
`-l`). Passing `-l 100` to trim output also silently narrows the scan to 100
|
||||
entries. Untangling them (a separate scan-depth control) belongs with the
|
||||
frontend-parser work (P1.3), not here.
|
||||
|
||||
### P3.2 Brute-force InstructionTable header search
|
||||
`dart_pool_it.c:233-330` probes `{16,0,8,12} × delta -64..64 step 4`, then scans
|
||||
the whole data image every 16 bytes (`:209-231`), then a third linear scan
|
||||
`+0x40..+0x40000 step 8` (`:289-320`); `decode_pool_and_emit` additionally retries
|
||||
`dart_it_emit_varint` across `delta -64..64`. O(image) per lookup because the
|
||||
header location isn't derived. **Fix:** compute it from the layout — the Image
|
||||
header format is now understood (see `COMMIT2.md`: word0 `ImageSize`, word1
|
||||
`InstructionsSectionOffset`, 64-byte header), which should make the IT header
|
||||
directly addressable.
|
||||
|
||||
### P3.3 Per-value snapshot-header re-parse
|
||||
`dart_object.c:296-312` calls `dart_snapshot_header_read(ctx, ctx->iso_data, …)`
|
||||
for *every* scalar value decode just to recompute `data_base`. Cache on the ctx.
|
||||
|
||||
### P3.4 Read-cache tail cliff + forward-only thrash
|
||||
The 1 MB refill (`:37`) needs the whole range readable, so the last <1 MB of each
|
||||
section degrades to one syscall per byte; the window is forward-only, so name
|
||||
resolution alternating between the cluster stream and the string heap refills
|
||||
repeatedly. Consider a small multi-window/LRU cache and a short-read-tolerant refill.
|
||||
|
||||
---
|
||||
|
||||
## P4 — test quality (beyond the corpus gaps in P0.3)
|
||||
|
||||
- **The synthetic fixture has no generator and is fragile.** `bins/synthetic/field_snapshot.bin`
|
||||
is a hand-authored blob. It has already forced two special cases as the parser
|
||||
was corrected: P2.2 had to renumber its cluster tags to real CIDs, and `b6ea0b8`
|
||||
had to branch the tag reader on `synthetic_legacy_parse` because it stores raw
|
||||
LE u32 tags while real snapshots use varints. Every future layout/encoding fix
|
||||
risks a third. **Write a generator** so the fixture tracks the parser instead of
|
||||
pinning obsolete conventions; the `synthetic_legacy_parse` branch could then go.
|
||||
- **Obfuscation-map tests are tautological**: `test/db/cmd/obf-map`,
|
||||
`test/custom/obf-funcs-ios.json`, `obf-it-android.json` load a map whose keys
|
||||
match nothing in the sample, so output is identical to the no-`-m` run. They
|
||||
prove loading doesn't crash, never that renaming works. Add a fixture whose
|
||||
keys actually occur.
|
||||
- `analyze-aaa-android.json` only greps `"Flutter analysis:"` — passes even if
|
||||
nothing is recovered. Assert a recovered count/name.
|
||||
- Header tests pin entire JSON blobs incl. full CID tables: deterministic but
|
||||
brittle (one new field broke ~10 tests during `d387c0b`). Consider
|
||||
field-subset assertions like the `test/custom` specs already use.
|
||||
- Missing behaviours: plugin `-r`, plugin under `r2 -n`, gapped-layout structural
|
||||
discovery (macOS, see `COMMIT2.md`), stripped **ELF**.
|
||||
- Directory-form input only recognises Android `libapp.so` / iOS
|
||||
`App.framework/App` (`main.c:62-74`); `-H <dir-with-hello.aot>` errors "does
|
||||
not exist". Broaden the resolver or document it.
|
||||
|
||||
---
|
||||
|
||||
## Suggested execution order
|
||||
|
||||
1. **P1.3** — shared frontend parser (fixes the `-r`/`r2 -n` AGENTS.md
|
||||
violations). While there, untangle `-l` for `-x`: give the pool-use scan its
|
||||
own depth control instead of reusing the output-line limit (see P3.1).
|
||||
2. **P0.2 edge cases** — the 2.18.2 fill desync and CID_SHIFT1 class extraction
|
||||
(both above). Smaller than the main win; do only if those eras matter.
|
||||
3. **P0.3** — a 3.4/3.5-era compressed sample for the still-untested P0.1 fix
|
||||
(needs an externally-collected app; the local SDK is 3.12.2).
|
||||
4. **P1.2** — arch gating.
|
||||
5. **Write a synthetic-fixture generator** (see P4) so parser fixes stop needing
|
||||
blob special-cases.
|
||||
6. **P3.2** — now a *lower* suspect than it looked; it never showed up in the
|
||||
`-x` profile. Remaining P2 cleanups and test hardening.
|
||||
|
||||
Done since last revision: P0.2 name+class paths (`540efcf`, `f85bd3b`), the
|
||||
macOS class regression test (`75ad39a`), and the `-x` `r_anal_op` rewrite
|
||||
(uncommitted).
|
||||
|
||||
`COMMIT2.md` tracks the narrower follow-ups left over from the discovery work
|
||||
(gapped layouts, more iOS fixtures, stripped ELF, P1.5 semantics).
|
||||
@@ -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.2.0' ; VERSION_MAJOR=0; VERSION_MINOR=2; VERSION_PATCH=0; VERSION_NUMBER=00200; CONTACT_MAIL="[email protected]" ; CONTACT_NAME="pancake" ; CONTACT="pancake <[email protected]>" ;
|
||||
PKGNAME='r2flutter' ; VERSION='0.3.0' ; VERSION_MAJOR=0; VERSION_MINOR=3; VERSION_PATCH=0; VERSION_NUMBER=00300; CONTACT_MAIL="[email protected]" ; CONTACT_NAME="pancake" ; CONTACT="pancake <[email protected]>" ;
|
||||
}
|
||||
|
||||
show_usage() {
|
||||
cat <<EOF2
|
||||
'configure' configures r2flutter-0.2.0 to adapt to many kinds of systems.
|
||||
'configure' configures r2flutter-0.3.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.2.0"
|
||||
echo "0.3.0"
|
||||
exit 0
|
||||
fi
|
||||
echo "r2flutter-0.2.0 configuration script done with acr v2.2.8.
|
||||
echo "r2flutter-0.3.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,11 +246,10 @@ case $flag in
|
||||
show_version ; ;;
|
||||
-r|--r|--report)
|
||||
echo "PKGNAME: r2flutter"
|
||||
echo "VERSION: 0.2.0"
|
||||
echo "VERSION: 0.3.0"
|
||||
echo "AUTHOR: pancake"
|
||||
echo "EMAIL: [email protected]"
|
||||
echo "LANGS: c"
|
||||
echo "PKGCONFIG: r_core"
|
||||
echo "FLAGS: --with-precompiled-agent"
|
||||
exit 0
|
||||
;;
|
||||
@@ -321,7 +320,7 @@ parse_options "$1"
|
||||
shift
|
||||
done
|
||||
|
||||
ENVWORDS="MANDIR DESCRIPTION INFODIR LIBDIR INCLUDEDIR LOCALSTATEDIR ETCDIR SYSCONFDIR DATADIR DOCDIR LIBEXECDIR SBINDIR BINDIR EPREFIX PREFIX SPREFIX TARGET HOST BUILD INSTALL INSTALL_LIB INSTALL_MAN INSTALL_PROGRAM INSTALL_PROGRAM_STRIP INSTALL_DIR INSTALL_SCRIPT INSTALL_DATA HOST_OS HOST_CPU BUILD_OS BUILD_CPU TARGET_OS TARGET_CPU VERSION VERSION_MAJOR VERSION_MINOR VERSION_PATCH VERSION_NUMBER PKGCFG_LIBDIR PKGCFG_INCDIR PKGNAME VPATH CONTACT CONTACT_NAME CONTACT_MAIL PKGCONFIG CC CFLAGS CPPFLAGS LDFLAGS HAVE_LANG_C R2_VERSION PRECOMPILED_AGENT R2_CFLAGS R2_LDFLAGS HAVE_PKGCFG_R_CORE"
|
||||
ENVWORDS="MANDIR DESCRIPTION INFODIR LIBDIR INCLUDEDIR LOCALSTATEDIR ETCDIR SYSCONFDIR DATADIR DOCDIR LIBEXECDIR SBINDIR BINDIR EPREFIX PREFIX SPREFIX TARGET HOST BUILD INSTALL INSTALL_LIB INSTALL_MAN INSTALL_PROGRAM INSTALL_PROGRAM_STRIP INSTALL_DIR INSTALL_SCRIPT INSTALL_DATA HOST_OS HOST_CPU BUILD_OS BUILD_CPU TARGET_OS TARGET_CPU VERSION VERSION_MAJOR VERSION_MINOR VERSION_PATCH VERSION_NUMBER PKGCFG_LIBDIR PKGCFG_INCDIR PKGNAME VPATH CONTACT CONTACT_NAME CONTACT_MAIL CC CFLAGS CPPFLAGS LDFLAGS HAVE_LANG_C R2_VERSION R2_CFLAGS R2_LDFLAGS R2_LIBS PRECOMPILED_AGENT"
|
||||
|
||||
create_environ
|
||||
|
||||
@@ -336,37 +335,6 @@ ocho "checking for working directories... ${WODIS}"
|
||||
ocho "using prefix '${PREFIX}'"
|
||||
ACR_RMFILES=" test.c a.out a.exe"
|
||||
|
||||
ochof "checking for pkg-config... "
|
||||
ENVWORDS="${ENVWORDS} HAVE_PKGCONFIG"
|
||||
if [ -n "$PKGCONFIG" ]; then
|
||||
if [ -x "$PKGCONFIG" ]; then
|
||||
_HAVE_PKGCONFIG="${PKGCONFIG}"
|
||||
else
|
||||
ocho "PKGCONFIG env is not executable"
|
||||
PKGCONFIG=""
|
||||
fi
|
||||
else
|
||||
IFS=:
|
||||
for A in ${PATH} ; do
|
||||
if [ -x "${A}/pkg-config" ]; then
|
||||
PKGCONFIG="${A}/pkg-config"
|
||||
elif [ -x "${A}/pkgconf" ]; then
|
||||
PKGCONFIG="${A}/pkgconf"
|
||||
else
|
||||
continue
|
||||
fi
|
||||
break
|
||||
done
|
||||
unset IFS
|
||||
fi
|
||||
if [ -n "${PKGCONFIG}" ]; then
|
||||
ocho ${PKGCONFIG}
|
||||
HAVE_PKGCONFIG=1
|
||||
else
|
||||
HAVE_PKGCONFIG=0
|
||||
echo "Cannot find pkg-config or pkgconf in PATH" >&2
|
||||
exit 1
|
||||
ocho no; fi
|
||||
COMPILER=CC
|
||||
ochof "checking for c compiler... "
|
||||
HAVE_LANG_C=1
|
||||
@@ -390,21 +358,33 @@ else
|
||||
echo "not found"
|
||||
echo 'required'; exit 1
|
||||
fi
|
||||
if [ -z "${PKGCONFIG}" ]; then pkg-config --version >/dev/null 2>&1 ; if [ 0 = 0 ]; then PKGCONFIG=pkg-config ; else PKGCONFIG=pkgconf ; fi; fi
|
||||
type ${PKGCONFIG} > /dev/null 2>&1 || echo "ERROR: Cannot find valid PKGCONFIG, pkg-config or pkgconf in PATH"
|
||||
echo 'Using PKGCONFIG: '${PKGCONFIG}
|
||||
printf 'checking pkg-config flags for r_core... '
|
||||
tmp=`${PKGCONFIG} --cflags r_core 2>/dev/null`
|
||||
if [ $? != 0 ]; then echo no ; HAVE_PKGCFG_R_CORE=0;
|
||||
R2_CFLAGS='';R2_LDFLAGS='';
|
||||
echo 'This package is required'
|
||||
exit 1
|
||||
else
|
||||
R2_CFLAGS=$tmp;
|
||||
tmp=`${PKGCONFIG} --libs r_core 2>/dev/null`
|
||||
printf "checking exec r2 -H R2_CFLAGS... "
|
||||
R2_CFLAGS="`r2 -H R2_CFLAGS 2>/dev/null`"
|
||||
if [ $? = 0 ]; then
|
||||
echo yes; HAVE_PKGCFG_R_CORE=1;
|
||||
R2_LDFLAGS=$tmp; fi; fi
|
||||
echo "ok"
|
||||
else
|
||||
R2_CFLAGS=""
|
||||
echo "not found"
|
||||
echo 'required'; exit 1
|
||||
fi
|
||||
printf "checking exec r2 -H R2_LDFLAGS... "
|
||||
R2_LDFLAGS="`r2 -H R2_LDFLAGS 2>/dev/null`"
|
||||
if [ $? = 0 ]; then
|
||||
echo "ok"
|
||||
else
|
||||
R2_LDFLAGS=""
|
||||
echo "not found"
|
||||
echo 'required'; exit 1
|
||||
fi
|
||||
printf "checking exec r2 -H R2_LIBS... "
|
||||
R2_LIBS="`r2 -H R2_LIBS 2>/dev/null`"
|
||||
if [ $? = 0 ]; then
|
||||
echo "ok"
|
||||
else
|
||||
R2_LIBS=""
|
||||
echo "not found"
|
||||
echo 'required'; exit 1
|
||||
fi
|
||||
SEDFLAGS=" -e '"
|
||||
COUNT=0
|
||||
for A in ${ENVWORDS} ; do
|
||||
|
||||
+4
-5
@@ -1,17 +1,16 @@
|
||||
PKGNAME r2flutter
|
||||
VERSION 0.2.0
|
||||
VERSION 0.3.0
|
||||
CONTACT pancake ; [email protected]
|
||||
|
||||
USE_PKGCONFIG!
|
||||
|
||||
LANG_C!
|
||||
|
||||
EXEC! R2_VERSION r2 -v ;
|
||||
EXEC! R2_CFLAGS r2 -H R2_CFLAGS ;
|
||||
EXEC! R2_LDFLAGS r2 -H R2_LDFLAGS ;
|
||||
EXEC! R2_LIBS r2 -H R2_LIBS ;
|
||||
|
||||
ARG_WITH PRECOMPILED_AGENT precompiled-agent do not require node/npm ;
|
||||
|
||||
PKGCFG! R2_CFLAGS R2_LDFLAGS r_core
|
||||
|
||||
REPORT R2_VERSION ;
|
||||
|
||||
SUBDIRS ./config.mk ;
|
||||
+14
-3
@@ -125,7 +125,7 @@ If you need to extract version-specific logic from the Dart SDK source :
|
||||
|
||||
## Summary
|
||||
|
||||
**You do NOT need to download anything at runtime.** The version hash in the snapshot header (32-byte ASCII at offset 0x04) maps to a finite set of constraint profiles that can be statically defined. unflutter proves this works for all Dart versions from 2.10.0 to 3.10.7 without any runtime SDK compilation or network downloads .
|
||||
**You do NOT need to download anything at runtime.** The version hash in the snapshot header (32-byte ASCII at offset 0x04) maps to a finite set of constraint profiles that can be statically defined. unflutter proves this works for all Dart versions from 2.10.0 to 3.12.1 without any runtime SDK compilation or network downloads .
|
||||
|
||||
The only time you'd need dynamic updates is when Flutter releases a new version with breaking snapshot format changes - which happens infrequently and follows a predictable release cycle (stable, beta, dev channels) .
|
||||
|
||||
@@ -136,7 +136,7 @@ The only time you'd need dynamic updates is when Flutter releases a new version
|
||||
r2flutter implements Option 1 (Static Constraint Tables) from this document. The implementation in `src/lib/dart_pool_parse.c` includes:
|
||||
|
||||
### Hash-to-Version Mapping
|
||||
- 40+ known snapshot hashes from Flutter 1.22.x (Dart 2.10.0) to Flutter 3.38.x (Dart 3.10.7)
|
||||
- 40+ known snapshot hashes from Flutter 1.22.x (Dart 2.10.0) to Flutter 3.41.x (Dart 3.12.1)
|
||||
- Sources: unflutter version.go, reFlutter enginehash.csv, blutter precompiled SDKs
|
||||
|
||||
### Version Profiles
|
||||
@@ -154,7 +154,18 @@ Each supported Dart version has a profile with:
|
||||
| 2.13.0 | Int32 CID | Split canonical/non-canonical clusters |
|
||||
| 2.14.0-2.17.6 | CID Shift1 | `(cid << 1) \| canonical` |
|
||||
| 2.18.0-3.3.0 | CID Shift1 | Various CID changes |
|
||||
| 3.4.3-3.10.7 | ObjectHeader | CID at bits 12-31 |
|
||||
| 3.4.3-3.12.1 | ObjectHeader | CID at bits 12-31 |
|
||||
|
||||
Unknown hashes fall back to v3.9.2 profile with ObjectHeader encoding.
|
||||
|
||||
## Snapshot Hashes Are Shared Within Patch-Groups
|
||||
|
||||
A snapshot hash typically covers a group of patch releases within a minor line,
|
||||
but not necessarily the entire minor version. For example, Dart 3.0.0-3.0.2 share
|
||||
one hash while 3.0.3-3.0.7 use a different one; Dart 3.11.0-3.11.1 share a hash
|
||||
separate from 3.11.3-3.11.5; Dart 3.12.0 and 3.12.1 each have distinct hashes.
|
||||
The version string in the mapping table uses the latest known patch in the group
|
||||
(e.g., `3.1.3` for the 3.1.x group, `3.11.5` for the 3.11.3-3.11.5 group).
|
||||
The snapshot format (CID table, tag encoding, layout) is identical across all
|
||||
patch versions sharing the same hash.
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# DAE Gap Implementation TODO
|
||||
|
||||
This roadmap tracks the high-confidence Dart AOT format and restoration gaps
|
||||
identified by comparing `third_party/dae` with r2flutter. IDs are stable so an
|
||||
item can be selected by name in follow-up work.
|
||||
|
||||
Items are ordered by dependency: stream framing and codecs first, versioned
|
||||
cluster layouts next, then metadata/value restoration and profile detection.
|
||||
|
||||
- [x] **R2F-DAE-001 — Parse version-specific snapshot headers**
|
||||
- Read the profile-defined header field count and semantics.
|
||||
- Preserve canonical-cluster and field-table fields where present.
|
||||
- Commit: `Parse Version-Specific Snapshot Headers`
|
||||
- [x] **R2F-DAE-002 — Add versioned snapshot reference decoding**
|
||||
- Use unsigned LEB128 references through Dart 2.17.
|
||||
- Use the `ReadRefId` +128 encoding from Dart 2.18 onward.
|
||||
- Commit: `Add Versioned Snapshot Reference Decoding`
|
||||
- [x] **R2F-DAE-003 — Handle versioned ObjectPool entry bits**
|
||||
- Model low-seven-bit, swapped low-seven-bit, and modern bitfield eras.
|
||||
- Commit: `Handle Versioned ObjectPool Entry Bits`
|
||||
- [x] **R2F-DAE-004 — Parse versioned Code cluster layouts**
|
||||
- Profile Code reference counts, text-offset deltas, and leading refs.
|
||||
- Commit: `Parse Versioned Code Cluster Layouts`
|
||||
- [x] **R2F-DAE-005 — Support Dart 3.13 single snapshots**
|
||||
- Add hashes/CIDs and merged VM/isolate snapshot handling.
|
||||
- Add the alternate symbols and 3.13 Class, Code, and Closure layouts.
|
||||
- Test complete and truncated combined snapshots, including partial-fill failure reporting.
|
||||
- Commit: `Support Dart 3.13 Single Snapshots`
|
||||
- [x] **R2F-DAE-006 — Retain serialized class layout metadata**
|
||||
- Keep class ID, library/super references, next-field offset, and bitmap.
|
||||
- Test ordinary, top-level, and truncated Class fills.
|
||||
- Commit: `Retain Serialized Class Layout Metadata`
|
||||
- [x] **R2F-DAE-007 — Retain snapshot object value graph**
|
||||
- Preserve Instance, Array, Map, and Set values by object reference.
|
||||
- Expose bounded recursive values through the object inspector.
|
||||
- Commit: `Retain Snapshot Object Value Graph`
|
||||
- [x] **R2F-DAE-008 — Probe snapshot layouts for unknown hashes**
|
||||
- Validate candidate layouts structurally instead of selecting the newest.
|
||||
- Commit: `Probe Snapshot Layouts for Unknown Hashes`
|
||||
+11
@@ -49,6 +49,15 @@ bin/r2flutter -r -l 16 -i test/bins/ios/Runner.app
|
||||
|
||||
`-l` is useful because real apps can have thousands of entries.
|
||||
|
||||
Entrypoint and instruction-image marking:
|
||||
|
||||
```bash
|
||||
bin/r2flutter -E test/bins/ios/Runner.app
|
||||
bin/r2flutter -rE test/bins/ios/Runner.app
|
||||
```
|
||||
|
||||
`-E` reports the first decoded code entry from the InstructionTable. This is different from the loader symbol for `_kDartVmSnapshotInstructions`, which points at the VM instruction image data, not a normal program entrypoint. With `-r`, `-E` also emits `Cd 4 ...` metadata commands for the VM and isolate instruction images so radare2 renders them as dword arrays instead of treating the snapshot symbol as code.
|
||||
|
||||
## Fields Exposed By r2flutter
|
||||
|
||||
Plain output shows:
|
||||
@@ -65,9 +74,11 @@ JSON also includes:
|
||||
- `canonical_stack_map_entries_offset`
|
||||
- per-entry `pc_offset`
|
||||
- per-entry `stack_map_offset`
|
||||
- `-E` JSON includes `entrypoint`, `index`, `code_index`, and instruction-image sizes when known
|
||||
|
||||
## Notes
|
||||
|
||||
- Output is written to `stdout`, not `stderr`.
|
||||
- `-j` and `-r` now affect `-i` the same way they affect the other dump commands.
|
||||
- `-j`, `-q`, and `-r` also affect `-E`; use `-qE` when only the address is needed.
|
||||
- The current parser finds the table by scanning the mapped data image for a valid `InstructionsTable::Data` header when the snapshot's `it_off` field does not point exactly at the payload start.
|
||||
+548
-18
@@ -218,6 +218,24 @@ This recovers real enums in the shipped fixtures on both platforms, for example
|
||||
|
||||
**Finding**: `-j -H` emits the same snapshot/cluster fields as the old dump-snapshot JSON, plus layout metadata (version, tag style, CID table).
|
||||
|
||||
## Dart Entrypoint Is The First Code IT Entry, Not The Snapshot Symbol
|
||||
|
||||
**Finding**: The loader symbol `_kDartVmSnapshotInstructions` points at the Dart VM instruction image header/data. It is useful as a blob boundary, but it is not a normal executable entrypoint for the app.
|
||||
|
||||
On `/tmp/libapp.so`, the isolate InstructionTable decodes to `length=32855` with `first_entry_with_code=24809`. Entries before that are VM stubs; the first real code entry is:
|
||||
|
||||
```text
|
||||
24809 0xbf150c code method.fn_0
|
||||
```
|
||||
|
||||
`-E` now reports that first code entry and ignores `-l`, because a user-supplied or default limit can stop before the first code entry on stub-heavy snapshots. `-rE` emits a small r2 script:
|
||||
|
||||
- `f dart.entrypoint = ...`
|
||||
- `Cd 4 <count> @ dart.vm_instr`
|
||||
- `Cd 4 <count> @ dart.iso_instr`
|
||||
|
||||
The instruction-image sizes come from the Dart instruction image header (`uint64 size`, `uint64 header_size`) rather than the clustered snapshot header magic used by data snapshots.
|
||||
|
||||
This makes the legacy dump-snapshot flag redundant and keeps scripts relying on `cluster` and snapshot addresses intact.
|
||||
|
||||
## Direct-Ref Annotation Prefixes Are Better As Data Than Control Flow
|
||||
@@ -573,7 +591,7 @@ The `instruction_table_data_offset` field points into the Data Image.
|
||||
|
||||
The 32-byte ASCII MD5 hash at offset 0x14 identifies the exact Dart VM version and snapshot format. This hash maps to a finite set of constraint profiles that can be statically defined.
|
||||
|
||||
r2flutter maintains 40+ known snapshot hashes from Flutter 1.22.x (Dart 2.10.0) to Flutter 3.38.x (Dart 3.10.7).
|
||||
r2flutter maintains 40+ known snapshot hashes from Flutter 1.22.x (Dart 2.10.0) to Flutter 3.41.x (Dart 3.12.1).
|
||||
|
||||
### Version Profiles Include:
|
||||
- Tag encoding style (CID-Int32, CID-Shift1, ObjectHeader)
|
||||
@@ -631,7 +649,7 @@ _Uint16List\x00\x00\x00\x00\x00...
|
||||
|
||||
Blutter (in `third_party/blutter`) currently only supports Android ELF (arm64). It compiles the actual Dart VM runtime for the target version and walks live ClassTable/ObjectPool structures via `Dart_Initialize`. iOS support is listed as a TODO. The key difference: Blutter's `ElfHelper::MapLibAppSo()` has incomplete Mach-O handling.
|
||||
|
||||
## String Object Storage in ROData Image (Dart 3.4.3+)
|
||||
## String Object Storage in No-Compressed-Pointer ROData Images
|
||||
|
||||
**Finding**: In AOT snapshots without compressed pointers, canonical strings are stored as raw Dart heap objects in the ROData image section, NOT as serialized cluster data.
|
||||
|
||||
@@ -647,7 +665,7 @@ Offset 0x08: [8 bytes] Length (SMI-tagged: actual_length << 1)
|
||||
Offset 0x10: [variable] String data (1 byte per char for OneByteString)
|
||||
```
|
||||
|
||||
Objects are aligned to 8 bytes.
|
||||
Objects in the tested 64-bit AOT images are aligned to 16 bytes.
|
||||
|
||||
### RODataSerializationCluster
|
||||
|
||||
@@ -668,11 +686,18 @@ Strings in compressed-pointer snapshots are serialized inline in the cluster str
|
||||
|
||||
### ROData Location
|
||||
|
||||
The ROData image follows the isolate snapshot data, aligned to `kMaxObjectAlignment` (16 bytes):
|
||||
The ROData image follows the snapshot stream at Dart's fixed
|
||||
`kObjectStartAlignment` (64 bytes). Do not reuse the 16-byte heap-object
|
||||
alignment for this boundary:
|
||||
```
|
||||
rodata_start = iso_data + align16(iso_snapshot_length)
|
||||
rodata_start = iso_data + align64(iso_snapshot_length)
|
||||
```
|
||||
|
||||
AuthPass demonstrates why the distinction matters: its isolate stream ends at
|
||||
`0xa0a3c2`, so the image starts at `0xa0a400`, not `0xa0a3d0`. The deltas inside
|
||||
each `RODataSerializationCluster::WriteAlloc` record are still measured in
|
||||
16-byte object-alignment units.
|
||||
|
||||
The ROData section extends to the end of the `.rodata` (Android) or `__TEXT.__const` (iOS) section.
|
||||
|
||||
### CID Values Vary by Dart Version
|
||||
@@ -816,7 +841,7 @@ Current r2 command export is split across the standalone `-R` script mode, the p
|
||||
|
||||
## Parser Split Boundaries
|
||||
|
||||
The modern object-header cluster naming code now lives in `src/lib/dart_pool_modern.c`. Snapshot header parsing, Dart unsigned decoding, UTF-16 decoding, bounded memory reads, and the shared cluster stream reader live in `src/lib/dart_pool_snapshot.c`. Snapshot discovery lives in `src/lib/dart_pool_discovery.c`. Code-name and name-pool discovery lives in `src/lib/dart_pool_names.c`. String extraction and string dump formatting live in `src/lib/dart_pool_strings.c`. Raw data-image `Field` / `Function` object scanning lives in `src/lib/dart_pool_data_image.c`. InstructionTable fixed/varint/linear decoding lives in `src/lib/dart_pool_it.c`. Cross-reference collection and dump formatting lives in `src/lib/dart_pool_xrefs.c`. Legacy clustered snapshot decoding lives in `src/lib/dart_pool_clusters.c`. Class extraction, enum recovery, and class dump formatting live in `src/lib/dart_pool_classes.c`. These are intentionally separated from the public parser API through `src/lib/dart_pool_parse_priv.h`.
|
||||
The modern two-pass cluster naming code now lives in `src/lib/dart_pool_modern.c`. Snapshot header parsing, Dart unsigned decoding, UTF-16 decoding, bounded memory reads, and the shared cluster stream reader live in `src/lib/dart_pool_snapshot.c`. Snapshot discovery lives in `src/lib/dart_pool_discovery.c`. Code-name and name-pool discovery lives in `src/lib/dart_pool_names.c`. String extraction and string dump formatting live in `src/lib/dart_pool_strings.c`. Raw data-image `Field` / `Function` object scanning lives in `src/lib/dart_pool_data_image.c`. InstructionTable fixed/varint/linear decoding lives in `src/lib/dart_pool_it.c`. Cross-reference collection and dump formatting lives in `src/lib/dart_pool_xrefs.c`. Legacy clustered snapshot decoding lives in `src/lib/dart_pool_clusters.c`. Class extraction, enum recovery, and class dump formatting live in `src/lib/dart_pool_classes.c`. These are intentionally separated from the public parser API through `src/lib/dart_pool_parse_priv.h`.
|
||||
|
||||
Keep future splits on the same boundary: move whole parser subsystems behind private headers first, then reduce the remaining orchestration in `dart_pool_parse.c`. The remaining parser file should mostly be public API orchestration and header rendering.
|
||||
|
||||
@@ -849,7 +874,11 @@ The fallback class-name scanner reads fixed-size chunks that are not guaranteed
|
||||
|
||||
Validate the CLI obfuscation map only after `dart_app_new_from_core ()` copies the initial `DartCtx`; loading it before that copy would share one hash table across two independently finalized contexts. After validation, keep the map loaded in the context that will use it instead of immediately forcing a second lazy parse.
|
||||
|
||||
Modern object-header cluster helpers run only after `dart_modern_is_supported_snapshot ()` succeeds. Past that public gate, `ctx`, `ctx->layout`, and compressed 32-bit pointer mode are invariants for `src/lib/dart_pool_modern.c`, so private CID accessors should read layout fields directly and stay `static inline`. Keep null/layout fallback checks on the public gate, not inside every CID comparison or fill-skip path.
|
||||
Modern cluster helpers run only after `modern_supported ()`
|
||||
succeeds. Past that public gate, `ctx`, `ctx->layout`, a validated
|
||||
`CID_SHIFT1`/`OBJECT_HEADER` style, and a 4- or 8-byte compressed-word size are
|
||||
invariants for `src/lib/dart_pool_modern.c`. Keep null/layout fallback checks on
|
||||
the public gate rather than duplicating them inside every CID comparison.
|
||||
|
||||
## 2026-06-01: Class and Field Cluster Recovery
|
||||
|
||||
@@ -861,15 +890,15 @@ later in `WriteFill`. A parser that tries to decode fields during alloc will
|
||||
desynchronize and eventually fall back to string-only class names.
|
||||
|
||||
For compressed ObjectHeader snapshots (`cws=4`, e.g. Android mafia / Dart
|
||||
3.9.2), `dart_modern_extract_classes_from_clusters ()` now walks the alloc
|
||||
3.9.2), `modern_extract_classes ()` now walks the alloc
|
||||
section to collect cluster ref ranges and Smi/Mint values, then walks fill to
|
||||
recover Class, Field, Function, and String metadata. This populates `ic` with
|
||||
real class members and avoids the old `0fields` class-only result.
|
||||
|
||||
Non-compressed ObjectHeader snapshots (`cws=8`, e.g. Android first and the iOS
|
||||
sample) still need a separate ROData string/ref path. Until that exists, the
|
||||
class extractor skips the legacy alloc-phase decoder and reports string-based
|
||||
fallback classes explicitly instead of logging bogus huge CIDs.
|
||||
Non-compressed snapshots (`cws=8`) now have the separate ROData string/ref path
|
||||
needed for semantic function naming. Full structured class/field extraction is
|
||||
still gated independently; when unavailable, `-c` reports string-based fallback
|
||||
classes rather than running the invalid alloc-phase legacy decoder.
|
||||
|
||||
## Extended Header Cluster Walk
|
||||
|
||||
@@ -893,11 +922,12 @@ PP/ObjectPool work inspectable and feeds the focused `-p` path.
|
||||
|
||||
`-p` now builds a synthetic static ObjectPool image from the decoded fill stream
|
||||
when the parser can reach a concrete ObjectPool payload. Plain quiet output is
|
||||
the address pair (`vaddr=0x100000000 paddr=...` in current builds). JSON output
|
||||
marks the result as `kind=synthetic` and reports top-level `vaddr`/`paddr`, plus
|
||||
the source cluster/ref/length. `-r -p` emits a radare2 setup script that maps the
|
||||
synthetic image with `o malloc://`, writes it with `wx`, and sets both
|
||||
`e anal.gp` and `dr x27` to the synthetic `vaddr`.
|
||||
the address pair (`vaddr=<bin.baddr+paddr> paddr=...`; when `bin.baddr` is zero,
|
||||
`vaddr` equals the serialized payload `paddr`). JSON output marks the result as
|
||||
`kind=synthetic` and reports top-level `vaddr`/`paddr`, plus the source
|
||||
cluster/ref/length. `-r -p` emits a radare2 setup script that maps the synthetic
|
||||
image with `o malloc://`, writes it with `wx`, and sets both `e anal.gp` and
|
||||
`dr x27` to the reconstructed `vaddr`.
|
||||
|
||||
The runtime PP value itself is not serialized in the snapshot. Dart's VM creates
|
||||
the live object pool during deserialization, so tagged entries that point to heap
|
||||
@@ -947,7 +977,7 @@ Use the shared `dart_cid_get ()` / `DartCidKind` API as the source of truth for
|
||||
|
||||
## String Xrefs In R2 Scripts
|
||||
|
||||
`-z -r -x` and `-zzrx` emit `ax` commands for string references after registering the recovered strings. ObjectPool string references use the reconstructed synthetic PP address space (`0x100000000 + pp_off`), matching the `-r -p` PP mapping. Metadata-only references that have an object ref but no concrete file address use a synthetic object-ref namespace (`0x200000000 + object_ref`) so r2 can still carry the relation as an xref.
|
||||
`-z -r -x` and `-zzrx` emit `ax` commands for string references after registering the recovered strings. ObjectPool string references use the reconstructed PP map address (`pp.vaddr + pp_off`), matching the `-r -p` PP mapping. Metadata-only references that have an object ref but no concrete file address use a synthetic object-ref namespace (`0x200000000 + object_ref`) so r2 can still carry the relation as an xref.
|
||||
|
||||
## Recovered Component Reports Are Not Complete SBOMs
|
||||
|
||||
@@ -1036,3 +1066,503 @@ mapping. On the `mafia` fixture, `-f` recovers many
|
||||
`method.IsarCoreBindings.isar_*` Dart wrappers and `-z` recovers `libisar.so`
|
||||
plus `isar_*` strings, but the fixture does not ship `libisar.so`, so no final
|
||||
native vaddr is available statically; keep obfuscation-expanded semantic names heap-owned (`char *`) so lookup keys are not truncated.
|
||||
|
||||
## Snapshot Profile Overrides
|
||||
|
||||
The snapshot hash embedded in a binary normally selects the Dart layout profile,
|
||||
but patched samples can carry a misleading hash. `-D <hash|version>` overrides
|
||||
the profile used by r2flutter analysis without modifying the input binary. A
|
||||
32-byte hash is treated as the effective snapshot hash; a Dart version string
|
||||
such as `3.8.1` selects the static profile for that version.
|
||||
|
||||
## Offset Table Management
|
||||
|
||||
**Finding**: The offset arrays (`code_entry_point_offsets`, `code_owner_offsets`,
|
||||
`function_name_offsets`) are **identical across the known Dart versions**
|
||||
(`2.10.0`-`3.12.2`). They are stored once under a top-level `defaults` key in
|
||||
`offsets.json` and hardcoded as fallback in `init_layout_hints ()` in
|
||||
`dart_pool_names.c`.
|
||||
|
||||
**Maintenance workflow**:
|
||||
|
||||
1. **Adding a new Dart version** (pulls SDK, computes hash, extracts CIDs):
|
||||
`./scripts/update-dart-version 3.13.0`
|
||||
|
||||
2. **Adding from a local SDK checkout**:
|
||||
`./scripts/update-dart-version --sdk-dir /path/to/dart-sdk 3.13.0`
|
||||
|
||||
3. **Adding a hash manually** (no SDK pull):
|
||||
`./scripts/update-dart-version --hash <md5> 3.13.0`
|
||||
|
||||
4. **Regenerating all tables** from known data:
|
||||
`./scripts/update-dart-version --generate`
|
||||
|
||||
5. **Syncing stable Dart SDK tags**:
|
||||
`./scripts/update-dart-version --sync-stable-tags --min-version 2.10.0`
|
||||
|
||||
6. **List known versions**:
|
||||
`./scripts/update-dart-version --list`
|
||||
|
||||
**Data sources**:
|
||||
- `offsets.json` — the source of truth. `hashes` maps snapshot MD5 values to
|
||||
exact Dart version strings, while `profiles` is range-keyed by the first
|
||||
version that introduced a layout/CID tuple.
|
||||
- `src/lib/dart_version.c` — generated `known_hashes[]` plus range-start
|
||||
`version_profiles[]`.
|
||||
- `src/lib/dart_cid.c` — generated range-start CID tables.
|
||||
- `third_party/sdk/runtime/vm/class_id.h` — Dart SDK source defining CID enum
|
||||
values.
|
||||
|
||||
**Key insight**: A Dart version `V` uses the profile with the greatest
|
||||
range-start key `<= V`. Most new Dart patch releases therefore add only one
|
||||
line under `hashes`; a new `profiles` line is needed only when the layout or CID
|
||||
table actually changes. Unknown hashes still use the hardcoded modern fallback.
|
||||
|
||||
## The `first` Android Fixture Uses ObjectHeader Tags
|
||||
|
||||
The `test/bins/android/first` snapshot hash `f91b8b03bf7f30a5e983fd19b23d978d`
|
||||
maps to Dart `2.18.2`, but its serialized heap objects use ObjectHeader CID
|
||||
bits. For example, the tagged object `0x1651` points at `0x1650`, whose header
|
||||
decodes CID `93` only via `(header >> 12) & 0xfffff`; interpreting it as
|
||||
`CID_SHIFT1` makes `-O` report an unresolved raw value. Keep the `2.18.2`
|
||||
profile as `OBJECT_HEADER` unless another fixture proves a hash-specific split
|
||||
is needed. The standalone `dart-lang/sdk` `2.18.2` tag also has a different CID
|
||||
table (`String=90`, `NumPredefinedCids=159`) from this Flutter-engine fixture,
|
||||
so the generator keeps `2.18.2` on the `3.9.2`-shaped fallback CID table used by
|
||||
the sample. This is documented as a Flutter-engine snapshot quirk, not as
|
||||
standalone Dart SDK `2.18.2` CID data.
|
||||
|
||||
Serialized-cluster CID extraction must go through `dart_cid_from_tags()`.
|
||||
Serialized snapshots use
|
||||
three encodings: a raw 32-bit CID through Dart 2.13, a CID shifted left by one
|
||||
through Dart 3.3, and the ObjectHeader bitfield from Dart 3.4 onward. Keeping the
|
||||
formula in one helper prevents cluster parsers from silently disagreeing. Raw
|
||||
heap objects are a separate tag domain: data-image strings, fields, functions,
|
||||
and InstructionTable backing objects always decode CID bits 12..31 from their
|
||||
ObjectHeader, even when the surrounding serialized cluster stream uses
|
||||
`CID_SHIFT1`.
|
||||
|
||||
On the iOS fixtures (`test/bins/ios/Runner.app` and `AuthPass.app`), the
|
||||
snapshot has no compressed pointers; AuthPass uses `CID_SHIFT1` (Dart 3.2.5)
|
||||
and Runner uses `OBJECT_HEADER` (Dart 3.4.3). The legacy class loop's fixed-u32
|
||||
tag read was only the first bug. Production snapshots serialize all cluster
|
||||
alloc records before all fill records, so interleaving alloc/fill parsing can
|
||||
never stay synchronized.
|
||||
|
||||
The zero-hash synthetic field snapshot is a deliberate exception to the iOS
|
||||
tag rule. Its compatibility format stores each cluster tag as a fixed
|
||||
little-endian object-header `u32`; for example, `0x0005e000` encodes CID 94.
|
||||
Reading that word with `cs_read_tagged32()` produces CID 1 and consumes only two
|
||||
of its four bytes, desynchronizing every later cluster. Keep fixed-width reads
|
||||
confined to the existing `synthetic_legacy_parse` predicate and use tagged
|
||||
reads for real legacy snapshots.
|
||||
|
||||
The modern two-pass walker now handles both tag styles and both compressed word
|
||||
sizes. The old iOS path additionally required these layout corrections:
|
||||
|
||||
- `CID_SHIFT1` uses bit 0 as the canonical flag.
|
||||
- Dart 3.2/3.8 `FunctionType` fill is six references, one byte of flags, a
|
||||
serialized `uint32_t` packed parameter count, and a serialized `uint16_t`
|
||||
packed type-parameter count. Both integer writes go through Dart's tagged
|
||||
`BaseWriteStream::Raw` encoding rather than occupying fixed byte widths.
|
||||
- `Instance::NextFieldOffset()` is one compressed word when cws=8 and two when
|
||||
cws=4. Each unboxed machine-word field is emitted as two serialized 32-bit
|
||||
chunks in both modes.
|
||||
- ROData alloc values are cumulative image offsets in object-alignment units;
|
||||
the associated string bytes are not in the fill stream.
|
||||
- A later unsupported ObjectPool fill must not invalidate already decoded Code,
|
||||
Function, Class, PatchClass, and Library fills.
|
||||
|
||||
With those fixes, AuthPass resolves VM/isolate ROData string refs and maps
|
||||
35,928 semantic code-slot names. It recovers
|
||||
`_RawSecureSocket._secureHandshake`, both bad-certificate registration methods,
|
||||
and `_RawSecureSocket._onBadCertificateWrapper` at `0x3199d4`. Runner resolves
|
||||
its VM and isolate ROData through the same path and maps 7,271 slots. Keep matching
|
||||
stable name substrings because numeric Dart private-name suffixes vary by
|
||||
snapshot.
|
||||
|
||||
Finally, a loader-generated Mach-O name such as `func.003199d4` is a fallback,
|
||||
not a higher-quality source. RBin names must not overwrite a semantic name
|
||||
already joined from `Code.owner -> Function -> String`.
|
||||
|
||||
## Dart SDK Stable Tag Refresh
|
||||
|
||||
Running `scripts/update-dart-version --sync-stable-tags --min-version 2.10.0`
|
||||
against `dart-lang/sdk` found 118 stable numeric tags from `2.10.0` through
|
||||
`3.12.2`. In expanded form those tags collapse to 19 range profiles and 61
|
||||
distinct hashes. The duplicate-hash aliases all had matching CID signatures, so
|
||||
a hash can still map to one representative version without losing CID
|
||||
correctness for explicit version overrides.
|
||||
|
||||
The only profile mismatch against the current inference rules is still
|
||||
`2.18.2`: the source-derived default would be `CID_SHIFT1`, but the in-tree
|
||||
Android fixture needs `OBJECT_HEADER`. Keep preserving existing profiles during
|
||||
batch syncs unless a fixture proves the inferred profile is better. The same
|
||||
fixture also needs a CID-table alias because the standalone SDK tag's CID table
|
||||
does not match the Flutter-engine snapshot.
|
||||
|
||||
The previous sync parser did not understand Dart `3.12.x`'s `CLASS_ID_LIST`
|
||||
macro form and emitted an empty CID table for `3.12.0+`. The parser now expands
|
||||
that macro form; Dart `3.12.x` floors to the same modern CID family as `3.9.x`
|
||||
(`String=93`, `ObjectPool=23`, `NumPredefinedCids=175`).
|
||||
|
||||
Older tags `2.10.0` through `2.14.4` do not contain `runtime/vm/app_snapshot.cc`
|
||||
or `runtime/vm/app_snapshot.h`. The generator reports those missing hash inputs
|
||||
and computes the MD5 from the files present in that tag, matching the existing
|
||||
script behavior for local SDK checkouts.
|
||||
|
||||
## ObjectPool Xrefs Should Resolve From Serialized PP Data
|
||||
|
||||
`bin/r2flutter -p` reports a reconstructed PP map address derived from the
|
||||
serialized ObjectPool payload physical offset plus the current binary base.
|
||||
That address is useful after the `-r -p` script maps a malloc-backed ObjectPool
|
||||
image and sets `anal.gp`/`x27`, but it should not be required for xref recovery.
|
||||
The r2 setup script must clear `anal.roregs` before `dr x27=...` and then
|
||||
restore `anal.roregs=x27,gp,zero`; otherwise radare2 refuses to update `x27`
|
||||
when it is already marked read-only.
|
||||
|
||||
For xrefs, prefer resolving collected PP offsets against the serialized
|
||||
ObjectPool fill payload (`DartPpInfo.paddr` / `source_addr`) and then walking
|
||||
reachable metadata refs to the real serialized string payload address.
|
||||
|
||||
It is isolate string-cluster ref `6433` (`payload=0x47d3e`, length `39`). A full
|
||||
ObjectPool/code xref scan found code-to-string xrefs for nearby serialized
|
||||
strings, but no code-reachable ObjectPool path to ref `6433`. Treat that as
|
||||
"present in the snapshot string cluster" rather than a proven code reference
|
||||
until another metadata container or instruction pattern links to it.
|
||||
|
||||
## Performance: `-p` was CPU-bound on per-byte IO and RBin string scanning
|
||||
|
||||
`bin/r2flutter -p` (and every cluster-stream walker) used to burn ~100% CPU for
|
||||
several seconds on large `libapp.so` / `App` images. Two bottlenecks:
|
||||
|
||||
1. `read_mem()` issued one `r_io_read_at()` through the full radare2 IO stack
|
||||
(banks, caches, interval trees, mmap seek/lseek) for *every byte*, because
|
||||
the cluster stream decoders (`cs_read_u8`, `cs_read_ref_id`,
|
||||
`cs_read_tagged32/64`, `cs_read_unsigned`) read one byte at a time while
|
||||
walking the whole `modern_parse_cluster_meta` stream twice. `read_mem()` now
|
||||
keeps a forward-sliding 1 MiB window cache in `DartCtx`, collapsing millions
|
||||
of IO calls into a handful. It is scoped to a single command invocation and
|
||||
released in `dart_obf_fini()`. The window is only served when the whole
|
||||
request is contained in it; a windowed refill that fails (e.g. near the end
|
||||
of a mapped region) falls back to an exact read, so semantics are identical.
|
||||
|
||||
2. `r_core_bin_load()` in the standalone tool ran RBin's `string_scan_range`
|
||||
over the entire binary at load time (the single largest profile entry).
|
||||
r2flutter never consumes RBin's string list — it has its own Dart-aware
|
||||
string extraction — so the CLI now sets `bin.strings=false` before loading.
|
||||
The r2 core plugin path is left untouched (it runs inside the user session).
|
||||
|
||||
Net effect on `test/bins/android/mafia/libapp.so` (14 MB): `-p` dropped from
|
||||
~2.6s to ~0.05s (~50x), with byte-identical output across all actions and the
|
||||
whole custom testsuite still green. The shared read cache also speeds up the
|
||||
heavier `-x`/`-z` walkers.
|
||||
|
||||
## AuthPass iOS TLS wrapper behavior
|
||||
|
||||
`test/bins/ios/AuthPass.app` uses Dart 3.2.5 with `CID_SHIFT1` tags. Its
|
||||
`_RawSecureSocket._onBadCertificateWrapper` differs from the Android SSL test
|
||||
fixtures: the normal path loads the app-provided callback, calls it indirectly
|
||||
with `blr x2`, and returns the callback result unchanged. The Dart true/false
|
||||
singletons are still recoverable from the nearby handshake/schedule-filter
|
||||
cluster (`x22 + 0x20` is true, `x22 + 0x30` is false), so the same AArch64 entry
|
||||
patch (`add x0, x22, 0x20; ret`) is valid when the goal is to force acceptance
|
||||
at the Dart bad-certificate wrapper boundary.
|
||||
|
||||
## PP-relative string comments in r2 disassembly
|
||||
|
||||
`r2flutter -AAA` must account for shifted ARM64 PP-base materialization such as
|
||||
`add x2, x27, 0x18, lsl 12`. radare2 may print this as `lsl 12` without a `#`.
|
||||
If the shift is ignored, the analyzer records `PP slot +0x470` for a following
|
||||
`ldr x2, [x2, 0x458]` instead of the real `PP slot +0x18458`, so ObjectPool
|
||||
string resolution cannot attach the expected string comment.
|
||||
|
||||
The analysis pass now accepts both `lsl #N` and `lsl N`, builds a direct
|
||||
ObjectPool string map, and annotates PP-relative string loads. On
|
||||
`ssl_test2/libapp.so`, the blutter-comparable check is:
|
||||
|
||||
```sh
|
||||
r2 -q -e scr.color=0 -e bin.relocs.apply=true \
|
||||
-c 'r2flutter -AAA' \
|
||||
-c 's 0x9da730' \
|
||||
-c 'pd 16' \
|
||||
-c q ssl_test2/libapp.so
|
||||
```
|
||||
|
||||
The key instruction should now include:
|
||||
|
||||
```text
|
||||
0x009da744 ... ldr x2, [x2, 0x458] ; ... "premium" ; dart: PP slot +0x18458 string "premium"
|
||||
```
|
||||
|
||||
### PP string comments: resolver and analysis cost
|
||||
|
||||
The first implementation initialized `ModernRefResolver` for a direct
|
||||
ObjectPool string lookup. That resolver also allocated ref-to-cluster,
|
||||
class/library/function, flags, and code-index tables and walked unrelated
|
||||
metadata fills. A direct PP string lookup only needs `string_ref -> value` and
|
||||
`string_ref -> payload address`, so `ModernDirectStringResolver` now owns just
|
||||
those two arrays. It still loads VM base strings when resolving an isolate
|
||||
snapshot, because isolate ObjectPool entries may reference canonical VM strings.
|
||||
|
||||
PP offsets are local to one ObjectPool. Isolate and VM maps must therefore not
|
||||
be merged: the isolate ObjectPool is authoritative, and the VM ObjectPool is
|
||||
only a fallback when no isolate pool can be decoded. Merging both can attach a
|
||||
valid VM string to an unrelated isolate PP offset.
|
||||
|
||||
On uncompressed iOS snapshots, string clusters may use `ROData` allocation and
|
||||
have no serialized fill payload. Their direct refs are resolved from the data
|
||||
image object, with the string payload at `object_addr + 16`. This is why Runner
|
||||
previously decoded a PP entry as `resolved_kind=string` but could not print its
|
||||
value or annotate its load.
|
||||
|
||||
The instruction scan no longer requests OPEX JSON and reparses it once per
|
||||
instruction. It asks r2 for `R_ARCH_OP_MASK_VAL` operands and uses the decoded
|
||||
register/immediate values directly. ARM64 memory operands still use the already
|
||||
generated mnemonic for the base/displacement because the current ARM backend
|
||||
does not reliably set `RArchValue.memref`. ESIL is not useful in this hot path:
|
||||
it costs more to generate and execute, and tagged-object PP entries contain no
|
||||
runtime heap pointer in the serialized synthetic image. ESIL can propagate
|
||||
`x27 + offset`, but it cannot reconstruct the missing object pointer; the
|
||||
snapshot ref table remains the source of truth.
|
||||
|
||||
On the local fixtures this reduced `r2flutter -AAA` from about 1.61 s to 1.57 s
|
||||
for Android `first/libapp.so` and from about 11.06 s to 10.43 s for iOS Runner,
|
||||
while Runner gained 2,355 PP-string annotations that the old path missed.
|
||||
|
||||
## Parser naming and argument discipline
|
||||
|
||||
Public entrypoints keep the `dart_` namespace but use short subject/verb names:
|
||||
`dart_pool_scan ()`, `dart_pool_dump_pool ()`, and `dart_dumper_dump_r2 ()`.
|
||||
They should describe the operation without repeating implementation detail such
|
||||
as "effective", "embedded", or "for radare2".
|
||||
|
||||
Private modern-snapshot helpers use the `modern_` prefix and compact terms that
|
||||
are already established in the parser: `pp`, `strref`, and `sb` (snapshot
|
||||
base). Do not export those helpers through `include/r2flutter/`; keep their
|
||||
types and declarations in `dart_pool_parse_priv.h`.
|
||||
|
||||
Do not add argument-bundle structs merely to shorten a signature. First remove
|
||||
inputs that the context or the request already determines. For example,
|
||||
`dart_pool_scan ()` derives its base from `DartCtx`, `modern_build_pp ()`
|
||||
derives the matching snapshot base from its cluster range, and the modern fill
|
||||
callback derives its fill specification from the walker. This removed duplicate
|
||||
arguments while retaining explicit parser state and ownership.
|
||||
|
||||
## radare2 released-version compatibility
|
||||
|
||||
The class metadata API changed after radare2 6.1: current git uses nested
|
||||
`RBinAttr` members and adds `R_BIN_ATTR_MIXIN` and `R_BIN_ATTR_LATE`, while
|
||||
6.1 stores the corresponding class, field, and symbol metadata directly.
|
||||
Gate the enhanced metadata path on `R_BIN_ATTR_MIXIN`. The fallback must keep
|
||||
the older direct members (`instance_size`, `size`, `offset`, `kind`, and symbol
|
||||
`size`) and emit the older `ic+` argument form. This lets package builds use
|
||||
released r2 versions while git builds retain the richer attributes.
|
||||
|
||||
The same gate applies when reading existing symbols: use `sym->size` on the
|
||||
released API and `sym->attr.size` on the newer one.
|
||||
|
||||
Current git r2 derives a class-field offset from the command address. Emit
|
||||
field declarations as `'@0x<offset>'ic+Class..field <type>` rather than an
|
||||
`offset=0x<offset>` attribute (or a trailing `@` modifier). This keeps the
|
||||
address separate from `ic+`'s metadata arguments and works for both script
|
||||
emission and direct core-command application.
|
||||
## Snapshot Header Fields Are Versioned Data
|
||||
|
||||
The clustered header after the feature string is not a stable five-value
|
||||
tuple. Dart 2.10 writes four fields; 2.12-2.13 add a separate canonical-cluster
|
||||
count; 2.14-2.15 carry a field-table length but no instruction-table data
|
||||
offset; 2.16-2.17 write six fields; and 2.18+ use the modern five-field
|
||||
`num_base_objects`, `num_objects`, `num_clusters`, `instructions_table_len`,
|
||||
`instructions_table_rodata_offset` layout.
|
||||
|
||||
The parser must select the ordered header schema from the snapshot hash before
|
||||
locating the first allocation cluster. Treating every header as the modern
|
||||
five-field form consumes a cluster byte on 2.10, starts before the final header
|
||||
field on 2.16-2.17, and mistakes the canonical-cluster count for the total on
|
||||
2.12-2.13.
|
||||
|
||||
## Snapshot Reference IDs Changed in Dart 2.18
|
||||
|
||||
Dart 2.10 through 2.17 serialize object references with the same little-endian
|
||||
seven-bit unsigned integer used for counts and lengths. The last byte has its
|
||||
high bit set. Dart 2.18 introduced `ReadRefId`: seven-bit groups are accumulated
|
||||
in big-endian order, the terminal byte is interpreted as signed, and 128 is
|
||||
added to the result.
|
||||
|
||||
All cluster reference reads must use the snapshot profile's reference codec.
|
||||
For example, Dart 2.17 encodes reference 300 as `2c 82`; interpreting those
|
||||
bytes with the newer codec produces a different reference and can silently
|
||||
misalign subsequent object fields.
|
||||
|
||||
## ObjectPool Entries Have Three Bit-Packing Eras
|
||||
|
||||
The ObjectPool cluster itself does not need separate parser implementations.
|
||||
r2flutter's normalized pool-entry model remains useful, but the byte-to-model
|
||||
translation must be selected independently from other legacy cluster rules.
|
||||
|
||||
Dart 2.10 through 3.1 put the entry type in bits 0-6 and patchability in bit 7;
|
||||
type 0 is a tagged-object reference and type 1 an immediate. Dart 3.2 retains
|
||||
that bit width but swaps those two type values. Dart 3.3 and later use the
|
||||
modern layout: a four-bit type, bit 4 patchability, and bits 5-7 snapshot
|
||||
behavior. A broad Dart-2-versus-Dart-3 check therefore misdecodes 3.0-3.2 and
|
||||
may skip a serialized payload, shifting every following pool entry.
|
||||
|
||||
## Code Cluster Framing Has Independent Transitions
|
||||
|
||||
Code allocation and fill cannot be keyed only to a broad legacy flag. Dart
|
||||
2.10-2.13 allocation writes the main count followed directly by the deferred
|
||||
count. Dart 2.14 and later add a state word for every main and deferred Code.
|
||||
|
||||
The fill has seven object references through Dart 2.16 and six from Dart 2.17
|
||||
onward. Dart 2.10-2.15 also put an instruction text-offset delta before each
|
||||
main Code's payload info. Dart 3.13 adds two cluster-level references before
|
||||
the per-Code records. These fields are profiled separately so a format change
|
||||
in one dimension does not imply a change in another.
|
||||
|
||||
r2flutter keeps its existing normalized Code and instruction-table model. The
|
||||
profile is applied while framing the Code stream and while recovering Code
|
||||
owners; DAE's Dart 2.18 negative fill adjustment is intentionally not copied,
|
||||
because it compensates for drift in DAE's own generic fill walker rather than
|
||||
a serialized SDK field.
|
||||
|
||||
## Dart 3.13 Uses One Combined Snapshot
|
||||
|
||||
Dart 3.13 replaces the separate VM and isolate AOT snapshots with one full
|
||||
snapshot (`kind=2`). Its exported symbols are `_kDartSnapshotData` and
|
||||
`_kDartSnapshotText`. r2flutter stores those addresses in its existing isolate
|
||||
slots internally so the parser APIs remain compatible, but reports a single
|
||||
`Combined` snapshot and does not invent VM addresses in user-facing output.
|
||||
Unknown hashes begin on the newest split-snapshot compatibility profile;
|
||||
otherwise merely adding a 3.13 profile would silently reinterpret older dev
|
||||
builds as combined snapshots. Exact hashes and explicit overrides are positive
|
||||
evidence for the new topology. Structural probing can upgrade an unknown
|
||||
`kind=2` snapshot to the single-snapshot profile only when discovery found one
|
||||
data snapshot and the complete cluster stream validates.
|
||||
|
||||
The same release changes three cluster details independently: Class allocation
|
||||
becomes a single fixed-size count, Code fill gains two leading references, and
|
||||
Closure becomes variable-sized with an allocation length followed during fill
|
||||
by one unsigned value, three fixed references, and that many variable
|
||||
references. The Class nullable-field bitmap is omitted only for synthetic
|
||||
top-level class IDs. That offset is itself versioned: `1 << 16` through Dart
|
||||
2.18 and `1 << 20` from Dart 2.19 onward. Applying the newer threshold to a
|
||||
2.18 snapshot, or the older threshold to 3.13, shifts subsequent fills for
|
||||
ordinary high class IDs.
|
||||
|
||||
Keep partial cluster summaries when a fill is truncated. Earlier clusters are
|
||||
still useful, while the failing cluster is marked `failed` and later clusters
|
||||
remain `not_parsed`. This is more informative than rejecting the whole snapshot
|
||||
and makes malformed-input behavior explicit to JSON consumers.
|
||||
|
||||
## Class Records Carry More Than Display Names
|
||||
|
||||
The modern Class fill's 13 references include the name at index 0, library at
|
||||
index 7, and super Type at index 9. The following tagged values include the
|
||||
class ID, instance size, next-field offset, type-argument offset, and type
|
||||
parameter count. A nullable-field bitmap follows for ordinary classes but is
|
||||
absent for synthetic top-level classes.
|
||||
|
||||
r2flutter keeps byte-based sizes and offsets in `DartClassInfo`, matching its
|
||||
existing field and RBin APIs, while retaining the raw class ID, library and
|
||||
super-Type references, and bitmap. The super-Type reference is not stored in
|
||||
the older `super_class_ref` slot: that slot denotes a resolved Class object,
|
||||
whereas the serialized value points to a Type object. JSON exposes both forms
|
||||
without conflating them. A failed Class fill is discarded atomically, so a
|
||||
truncated bitmap cannot leak partially initialized class metadata.
|
||||
|
||||
## Snapshot Values Are Best Resolved On Demand
|
||||
|
||||
DAE eagerly retains every serialized Instance, Array, Map, and Set. r2flutter
|
||||
already resolves ObjectPool entries and inspects runtime heap addresses, so an
|
||||
on-demand graph keyed by snapshot reference fits better: it avoids permanent
|
||||
memory proportional to the whole snapshot and lets both `pp+offset` and
|
||||
`ref:N` use the same inspector. Nodes cache their decoded fields during one
|
||||
inspection. Rendering stops at cycles, a fixed recursion depth, and 128
|
||||
children, while retaining the serialized collection length and truncation
|
||||
status.
|
||||
|
||||
Array fills contain their length again, followed by a type-arguments reference
|
||||
and element references. Modern Map and Set fills use five references:
|
||||
type-arguments, hash mask, backing data Array, used-data count, and deleted-key
|
||||
marker. Instance fills start with one cluster bitmap. Reference fields consume
|
||||
one reference ID; bitmap-marked unboxed fields consume two tagged 32-bit
|
||||
halves. The number of header words is `8 / compressed_word_size`, so it must
|
||||
not be copied from DAE's fixed 64-bit profile assumptions.
|
||||
|
||||
Uncompressed snapshots store strings in the read-only data image rather than
|
||||
the cluster fill. Recursive value inspection therefore has to enrich its
|
||||
reference resolver from the matching snapshot's data image; otherwise iOS
|
||||
graphs retain string references but incorrectly report their values as
|
||||
incomplete. A truncated fill still produces a node with `complete=false` and
|
||||
no partially trusted edges, and an invalid reference is reported as
|
||||
`out_of_range`.
|
||||
|
||||
## Unknown Hashes Need Structural Evidence Before Layout Selection
|
||||
|
||||
The existing unknown-hash handling remains the safe final fallback, but it is
|
||||
not enough to select a historical or single-snapshot layout. Snapshot discovery
|
||||
now reads the invariant outer envelope (magic, bounded total length, kind,
|
||||
32-byte hexadecimal hash, and terminated feature string) without first
|
||||
guessing a versioned inner header. This avoids a circular dependency where the
|
||||
fallback profile determines which unknown snapshots can be discovered.
|
||||
|
||||
For an unknown hash, r2flutter tries built-in profiles with the discovered
|
||||
split/combined topology. Each candidate must decode a sane versioned header,
|
||||
parse every allocation and fill cluster without drift, assign contiguous
|
||||
reference ranges, stay within the declared snapshot extent, and finish at
|
||||
exactly `num_objects + 1`. Structurally valid candidates are scored using the
|
||||
same useful semantic signal as DAE: four times the Function count plus Class
|
||||
and Library counts. Equal scores retain the earliest compatible range-start
|
||||
profile, because the bytes prove a wire-layout family rather than an exact SDK
|
||||
release; the displayed `~<version>+` is therefore an honest lower bound.
|
||||
|
||||
Unlike DAE, r2flutter validates the primary application/isolate snapshot rather
|
||||
than requiring both VM and isolate fills. Its modern walker models the
|
||||
application metadata needed by r2flutter but does not claim complete support
|
||||
for every VM-only cluster found in Android snapshots, so making VM fill success
|
||||
mandatory would reject otherwise clean candidates. A real Android Dart 3.9
|
||||
snapshot narrows to the 3.9-3.12-compatible family, an iOS Dart 3.4 snapshot
|
||||
narrows to the 3.4-3.5 family, the legacy fixture uniquely selects 2.17, and a
|
||||
combined fixture selects the 3.13 family.
|
||||
|
||||
Malformed fills never replace the initial compatibility profile. Explicit
|
||||
version/hash overrides and exact known hashes also bypass the probe, preserving
|
||||
deterministic user control and fast known-version behavior. Verbose mode reports
|
||||
candidate validity, scores, the selected range start, or the reason the parser
|
||||
kept the fingerprint fallback.
|
||||
|
||||
## Keep Snapshot Fixtures Inside radare2
|
||||
|
||||
Small serialized snapshots can live directly in r2r records as `malloc://`
|
||||
files populated with `wx`. For tests derived from real binaries, `o: $s` makes
|
||||
an in-memory copy of the current file; subsequent `wx` or `wv8` commands mutate
|
||||
that copy without touching the fixture on disk. Open real inputs with `-n` when
|
||||
the test depends on physical offsets, and patch every occurrence that the old
|
||||
whole-file replacement changed.
|
||||
|
||||
`o:` copies from the current seek, so seek to `0` before cloning a complete
|
||||
mapped image. It creates and selects the malloc descriptor before filling its
|
||||
bytes; after changing executable metadata, use `ooi` to parse the completed
|
||||
in-memory image again. Plain `ob` only lists or selects an existing bin object
|
||||
and leaves a clone without section or symbol information. For fat Mach-O, a
|
||||
normal bin load maps the selected thin slice at virtual address zero, which
|
||||
lets the stripped-discovery test clone and patch the slice without a temporary
|
||||
file or a Python Mach-O rewriter.
|
||||
|
||||
r2r starts radare2 with `-NN` and `R2_NOPLUGINS=1`, so core-plugin tests must
|
||||
load the local `core_flutter` library explicitly with `L`. Tests try the native
|
||||
`.dylib` and `.so` names to remain portable. JSON-path (`~{path}`) and gron
|
||||
(`~{=}`) filters replace most Python extraction pipelines and keep expected
|
||||
output focused. Empty command output followed by `~{=}` exposed a radare2
|
||||
console bug: the empty-buffer guard covered JSON formatting but not gron mode,
|
||||
allowing `r_json_parsedup` to receive NULL. The guard and its `cons_grep`
|
||||
regression test live in the sibling radare2 tree.
|
||||
|
||||
On macOS, a core plugin built with `-undefined dynamic_lookup` must resolve
|
||||
radare2 APIs from the host process. Linking `R2_LIBS` as well can load a second
|
||||
installation's `libr_*` dylibs when their install names point at another
|
||||
prefix. The ABI number can still match while host structures cross library
|
||||
instances, producing hangs or crashes in parser-heavy commands. The Darwin
|
||||
plugin target therefore leaves radare2 symbols unresolved; Linux continues to
|
||||
link the libraries reported by `r2 -H R2_LIBS`.
|
||||
+24
-14
@@ -17,8 +17,8 @@ instruction tables, and strings directly.
|
||||
| Mach-O details | Thin Mach-O64 and fat Mach-O/fat64; arm64 slice is preferred. LC_NOTE owner `__dart_app_snap` is detected and the embedded Dart Mach-O payload is extracted to a temporary file. |
|
||||
| Architecture | ARM64/AArch64 is the real target. Instruction-table entrypoints are based on ARM64 code offsets, PP tracking uses `x27`, and the radare2 analysis pass understands AArch64 registers/op metadata. |
|
||||
| Snapshot kind | Clustered Dart AOT snapshots with magic `0xdcdcf5f5`, 32-byte snapshot hashes, feature strings, and unsigned varint header fields. |
|
||||
| Dart layouts | In-tree profiles cover Dart `2.10.0` through `3.10.7`, roughly Flutter `1.22.x` through `3.38.x`. |
|
||||
| Unknown hashes | First tries `r2flutter/offsets.json` or `offsets.json`; otherwise uses v3.9.2-shaped ObjectHeader defaults while reporting the Dart version as `unknown`. |
|
||||
| Dart layouts | In-tree profiles cover Dart `2.10.0` through `3.14.0`. Profiles are keyed at layout-change boundaries and resolved by floor: e.g. `3.7`/`3.8` share the `3.6.0` CID layout and `3.10`–`3.12` share the `3.9.0` layout. CID tables through 3.12.1 were verified byte-for-byte against each release's `runtime/vm/class_id.h`; 3.13–3.14 add the combined-snapshot layout. |
|
||||
| Unknown hashes | Git/dev builds carry an unlisted snapshot hash. r2flutter first probes compatible split- or single-snapshot profiles by parsing the complete isolate cluster stream, checking reference/object counts and fill boundaries, and scoring Function/Class/Library records. A match is reported as `~<range-start>+ (structural-probe)` with `version_source=structural-probe`. If no profile validates, the existing newest-compatible split profile remains as a conservative `~<version>+ (fingerprint)` fallback. Exact hashes and explicit `-D` overrides bypass probing. |
|
||||
| Obfuscation maps | Flutter/Dart `--save-obfuscation-map` JSON arrays are supported with `-m <file>` or `e r2flutter.mapfile=<file>` in the plugin. |
|
||||
|
||||
## Layout Profiles
|
||||
@@ -44,7 +44,7 @@ The CID column is compacted as:
|
||||
| 2.18.0 | 3.3.x | `CID_SHIFT1` | 4 | 5 | 159 | `5/7/17/90/91/92/87/59/21` | SuspendState added. |
|
||||
| 2.19.0 | 3.7.x | `CID_SHIFT1` | 4 | 5 | 176 | `5/7/17/92/93/94/89/60/21` | RecordType/Record added. |
|
||||
| 3.0.5 | 3.10.x - 3.12.x | `CID_SHIFT1` | 4 | 5 | 177 | `5/7/18/93/94/95/90/61/22` | WeakArray added. |
|
||||
| 3.1.0 | 3.13.x | `CID_SHIFT1` | 4 | 5 | 176 | `5/7/18/92/93/94/89/60/22` | TypeRef removed. |
|
||||
| 3.1.3 | 3.13.x | `CID_SHIFT1` | 4 | 5 | 176 | `5/7/18/92/93/94/89/60/22` | TypeRef removed. |
|
||||
| 3.2.5 | 3.16.x | `CID_SHIFT1` | 4 | 5 | 176 | `5/7/18/92/93/94/89/60/22` | PoolType swapped. |
|
||||
| 3.3.0 | 3.19.x | `CID_SHIFT1` | 4 | 5 | 176 | `5/7/18/92/93/94/89/60/22` | Last Shift1 profile in-tree. |
|
||||
| 3.4.3 | 3.22.x | `OBJECT_HEADER` | 4 | 5 | 174 | `5/7/18/92/93/94/89/60/22` | ObjectHeader tag encoding begins. |
|
||||
@@ -52,8 +52,13 @@ The CID column is compacted as:
|
||||
| 3.6.2 | 3.27.x | `OBJECT_HEADER` | 4 | 5 | 175 | `5/7/18/93/94/95/90/61/23` | CID table update. |
|
||||
| 3.7.0 | 3.29.x | `OBJECT_HEADER` | 4 | 5 | 175 | `5/7/18/93/94/95/90/61/23` | Same modern profile family. |
|
||||
| 3.8.1 | 3.32.x | `OBJECT_HEADER` | 4 | 5 | 175 | `5/7/18/93/94/95/90/61/23` | Same modern profile family. |
|
||||
| 3.9.2 | 3.35.x | `OBJECT_HEADER` | 4 | 5 | 175 | `5/7/18/93/94/95/90/61/23` | Unknown-hash fallback baseline. |
|
||||
| 3.10.7 | 3.38.x | `OBJECT_HEADER` | 4 | 5 | 175 | `5/7/18/93/94/95/90/61/23` | Newest in-tree profile. |
|
||||
| 3.9.2 | 3.35.x | `OBJECT_HEADER` | 4 | 5 | 175 | `5/7/18/93/94/95/90/61/23` | Modern split profile family. |
|
||||
| 3.10.7 | 3.38.x | `OBJECT_HEADER` | 4 | 5 | 175 | `5/7/18/93/94/95/90/61/23` | Same modern profile family. |
|
||||
| 3.11.5 | 3.39.x | `OBJECT_HEADER` | 4 | 5 | 175 | `5/7/18/93/94/95/90/61/23` | Same modern profile family. |
|
||||
| 3.12.0 | 3.41.x | `OBJECT_HEADER` | 4 | 5 | 175 | `5/7/18/93/94/95/90/61/23` | Same modern profile family. |
|
||||
| 3.12.1 | 3.41.x | `OBJECT_HEADER` | 4 | 5 | 175 | `5/7/18/93/94/95/90/61/23` | Newest split compatibility profile. |
|
||||
| 3.13.0 | — | `OBJECT_HEADER` | 4 | 5 | 176 | `5/7/18/93/94/95/90/61/23` | Combined snapshot; alternate Class/Code/Closure framing. |
|
||||
| 3.14.0 | — | `OBJECT_HEADER` | 4 | 5 | 176 | `5/7/18/93/94/95/90/61/23` | Same combined-snapshot profile family. |
|
||||
|
||||
All profiles use heap object tag `1`, default alignment `16`, and instruction
|
||||
table capacity `20000` before feature-flag or `offsets.json` adjustments.
|
||||
@@ -73,7 +78,7 @@ These hashes are mapped directly by `src/lib/dart_version.c`.
|
||||
| 2.18.0 | `b0e899ec5a90e4661501f0b69e9dd70f`, `b6d0a1f034d158b0d37b51d559379697`, `8e50e448b241be23b9e990094f4dca39`, `6a9b5a03a7e784a4558b10c769f188d9` |
|
||||
| 2.19.0 | `adb4292f3ec25074ca70abcd2d5c7251`, `501ef5cbd64ca70b6b42672346af6a8a` |
|
||||
| 3.0.5 | `90b56a561f70cd55e972cb49b79b3d8b`, `aa64af18e7d086041ac127cc4bc50c5e`, `36b0375d284ee2af0d0fffc6e6e48fde`, `16ad76edd19b537bf6ea64fdd31977a7` |
|
||||
| 3.1.0 | `7dbbeeb8ef7b91338640dca3927636de` |
|
||||
| 3.1.3 | `7dbbeeb8ef7b91338640dca3927636de` |
|
||||
| 3.2.5 | `f71c76320d35b65f1164dbaa6d95fe09` |
|
||||
| 3.3.0 | `ee1eb666c76a5cb7746faf39d0b97547` |
|
||||
| 3.4.3 | `d20a1be77c3d3c41b2a5accaee1ce549` |
|
||||
@@ -83,11 +88,16 @@ These hashes are mapped directly by `src/lib/dart_version.c`.
|
||||
| 3.8.1 | `830f4f59e7969c70b595182826435c19` |
|
||||
| 3.9.2 | `97ff04a728735e6b6b098bdf983faaba` |
|
||||
| 3.10.7 | `1ce86630892e2dca9a8543fdb8ed8e22` |
|
||||
| 3.11.5 | `78da37fed6bf1489361a312568249f3f` |
|
||||
| 3.12.0 | `41be3daaabd524b8aa7423bc24584957` |
|
||||
| 3.12.1 | `ace654289f5abc240509fc941453ebc5` |
|
||||
| 3.13.0 | `0451907c2eaa8467e848c0067bfe8ed4` |
|
||||
| 3.14.0 | `7a1ea3f6f5cf1089a7f6e55d7f20dbfd` |
|
||||
|
||||
`offsets.json` can enrich or override a hash with `compressed_word_size`,
|
||||
`heap_object_tag`, `max_alignment`, `it_cap`, `code_entry_point_offsets`,
|
||||
`code_owner_offsets`, and `function_name_offsets`. The code looks for it first
|
||||
under `r2flutter/offsets.json`, then in the current directory as `offsets.json`.
|
||||
`include/r2flutter/dart_offsets.h` contains the hash→compressed_word_size lookup
|
||||
table and the default offset arrays, compiled statically into r2flutter. No
|
||||
external JSON file is needed at runtime. The header is auto-generated by
|
||||
`scripts/update-dart-version`.
|
||||
|
||||
## Snapshot Discovery
|
||||
|
||||
@@ -356,7 +366,7 @@ name, then applies it to:
|
||||
| `-HH` | Dump the snapshot header plus VM/isolate cluster allocation/fill layout. |
|
||||
| `-HHH` | Add selected cluster payload diagnostics, currently ObjectPool entry decoding/status plus conservative ref resolution. |
|
||||
| `-i` | Dump instruction-table entries. |
|
||||
| `-O <addr\|pp+off>` | Decode a Dart Smi/tagged heap pointer/object address, or decode a reconstructed ObjectPool PP slot such as `pp+0x68`. Honors `-j`, `-q`, and `-r`. |
|
||||
| `-O <addr\|pp+off\|ref:N>` | Decode a Dart Smi/tagged heap pointer/object address, a reconstructed ObjectPool PP slot such as `pp+0x68`, or a serialized snapshot reference. Snapshot values are recursively bounded and preserve arrays, maps, sets, and instance fields. Honors `-j`, `-q`, and `-r`. |
|
||||
| `-p` | Print the reconstructed static ObjectPool PP address pair; with `-r`, map the synthetic pool image and set `anal.gp`/`x27` from the synthetic vaddr. |
|
||||
| `-R` | Dump a radare2 script for applying method flags/comments and PP helpers. |
|
||||
| `-S` | Dump best-effort recovered components/SBOM with source, confidence, evidence, and nullable versions. |
|
||||
@@ -405,7 +415,7 @@ Plugin actions:
|
||||
| `r2flutter -HH` | Print snapshot header info plus VM/isolate cluster allocation/fill layout. |
|
||||
| `r2flutter -HHH` | Print cluster layout plus selected payload diagnostics, currently ObjectPool entries/status and ref resolution. |
|
||||
| `r2flutter -i` | Print instruction-table entries. |
|
||||
| `r2flutter -O addr` | Decode a Dart Smi/tagged heap pointer/object address, or decode a reconstructed ObjectPool PP slot such as `pp+0x68`. Honors `-j`, `-q`, and `-r`. |
|
||||
| `r2flutter -O spec` | Decode a Dart Smi/tagged heap pointer/object address, a reconstructed ObjectPool PP slot such as `pp+0x68`, or a serialized snapshot reference such as `ref:209553`. Honors `-j`, `-q`, and `-r`. |
|
||||
| `r2flutter -p` | Print the reconstructed static ObjectPool PP address pair. |
|
||||
| `r2flutter -R` | Print full radare2 script output. |
|
||||
| `r2flutter -S` | Dump best-effort recovered components/SBOM from the current binary context. |
|
||||
@@ -446,8 +456,8 @@ The `-R` path emits:
|
||||
- `PP=x27`; per-function PP offset helper scanning is intentionally skipped by
|
||||
default in script output because it is too slow on large apps
|
||||
- `-r -p` is the focused PP setup path: it opens a malloc map at the synthetic
|
||||
PP base, writes the reconstructed ObjectPool image, and sets `e anal.gp` plus
|
||||
`dr x27`
|
||||
PP base, writes the reconstructed ObjectPool image, sets `e anal.gp`, clears
|
||||
`anal.roregs` before assigning `x27`, then restores `anal.roregs=x27,gp,zero`
|
||||
|
||||
## Practical Limits
|
||||
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
# AuthPass.app Dart AOT TLS tutorial
|
||||
|
||||
This tutorial uses the iOS fixture:
|
||||
|
||||
```text
|
||||
test/bins/ios/AuthPass.app
|
||||
```
|
||||
|
||||
Run commands from the repository root. The Dart AOT image is not the top-level
|
||||
`Runner` executable; it is:
|
||||
|
||||
```sh
|
||||
IOS_BUNDLE=test/bins/ios/AuthPass.app
|
||||
IOS_APP="$IOS_BUNDLE/Frameworks/App.framework/App"
|
||||
```
|
||||
|
||||
Rebuild the tools and plugin before following the r2 steps:
|
||||
|
||||
```sh
|
||||
make
|
||||
make r2
|
||||
make user-install
|
||||
```
|
||||
|
||||
## Challenge 1: recover Dart AOT method names and map the TLS handshake path
|
||||
|
||||
Start by verifying that r2flutter recognizes the snapshot:
|
||||
|
||||
```sh
|
||||
bin/r2flutter -j -H "$IOS_BUNDLE"
|
||||
```
|
||||
|
||||
Expected important values:
|
||||
|
||||
```text
|
||||
"dart_version":"3.2.5"
|
||||
"hash":"f71c76320d35b65f1164dbaa6d95fe09"
|
||||
"it_len":37092
|
||||
"tag_style":"CID_SHIFT1"
|
||||
```
|
||||
|
||||
Dump recovered functions and resolve the TLS methods by semantic name. Do not
|
||||
hardcode the private suffix; AuthPass uses `14069316`, but that value changes
|
||||
between snapshots.
|
||||
|
||||
```sh
|
||||
FUNCS=$(mktemp)
|
||||
bin/r2flutter -q -f "$IOS_BUNDLE" > "$FUNCS"
|
||||
|
||||
BAD_CERT_ADDR=$(awk '/method\._RawSecureSocket_.*\._onBadCertificateWrapper_/ { print $1; exit }' "$FUNCS")
|
||||
REGISTER_ADDR=$(awk '/method\._SecureFilterImpl_.*\.registerBadCertificateCallback$/ { print $1; exit }' "$FUNCS")
|
||||
REGISTER_IMPL_ADDR=$(awk '/method\._SecureFilterImpl_.*\._registerBadCertificateCallback_/ { print $1; exit }' "$FUNCS")
|
||||
HANDSHAKE_ADDR=$(awk '/method\._RawSecureSocket_.*\._secureHandshake_/ { print $1; exit }' "$FUNCS")
|
||||
MARK_TRUSTED_ADDR=$(awk '/method\._SecureFilterImpl_.*\._markAsTrusted_/ { print $1; exit }' "$FUNCS")
|
||||
|
||||
printf 'wrapper=%s register=%s register_impl=%s handshake=%s mark=%s\n' \
|
||||
"$BAD_CERT_ADDR" "$REGISTER_ADDR" "$REGISTER_IMPL_ADDR" "$HANDSHAKE_ADDR" "$MARK_TRUSTED_ADDR"
|
||||
rm -f "$FUNCS"
|
||||
```
|
||||
|
||||
Expected output:
|
||||
|
||||
```text
|
||||
wrapper=0x3199d4 register=0x3194ec register_impl=0x31954c handshake=0x31749c mark=0x31925c
|
||||
```
|
||||
|
||||
Apply r2flutter flags in r2 and confirm that the same method flags exist:
|
||||
|
||||
```sh
|
||||
r2 -q -e scr.color=0 -e bin.relocs.apply=true \
|
||||
-c 'r2flutter -A' \
|
||||
-c 'f~onBadCertificateWrapper' \
|
||||
-c 'f~registerBadCertificateCallback' \
|
||||
-c 'f~secureHandshake' \
|
||||
-c q "$IOS_APP"
|
||||
```
|
||||
|
||||
Expected method flags:
|
||||
|
||||
```text
|
||||
0x003199d4 0 method._RawSecureSocket_14069316._onBadCertificateWrapper_14069316
|
||||
0x003194ec 0 method._SecureFilterImpl_14069316.registerBadCertificateCallback
|
||||
0x0031954c 0 method._SecureFilterImpl_14069316._registerBadCertificateCallback_14069316
|
||||
0x0031749c 0 method._RawSecureSocket_14069316._secureHandshake_14069316
|
||||
```
|
||||
|
||||
Now disassemble the handshake:
|
||||
|
||||
```sh
|
||||
r2 -q -e scr.color=0 -e bin.relocs.apply=true \
|
||||
-c 'r2flutter -A' \
|
||||
-c "s ${HANDSHAKE_ADDR:-0x31749c}" \
|
||||
-c 'pd 104' \
|
||||
-c q "$IOS_APP"
|
||||
```
|
||||
|
||||
Useful lines:
|
||||
|
||||
```asm
|
||||
0x003174e4 bl method._SecureFilterImpl_14069316.handshake
|
||||
0x00317514 bl method._RawSecureSocket_14069316._secureHandshake_14069316
|
||||
0x0031752c add x1, x22, 0x30
|
||||
0x0031753c bl method._RawSecureSocket_14069316._readSocket_14069316
|
||||
0x00317548 bl method._RawSecureSocket_14069316._writeSocket_14069316
|
||||
0x00317554 bl method._RawSecureSocket_14069316._scheduleFilter_14069316
|
||||
0x00317588 bl method._RawSecureSocket_14069316._reportError_14069316
|
||||
0x003175ac add x0, x22, 0x20
|
||||
```
|
||||
|
||||
The boolean constants are visible in this cluster:
|
||||
|
||||
- `x22 + 0x20` is Dart `true`.
|
||||
- `x22 + 0x30` is Dart `false`.
|
||||
|
||||
## Challenge 2: patch `_RawSecureSocket._onBadCertificateWrapper`
|
||||
|
||||
Disassemble the wrapper:
|
||||
|
||||
```sh
|
||||
r2 -q -e scr.color=0 -e bin.relocs.apply=true \
|
||||
-c 'r2flutter -A' \
|
||||
-c "s ${BAD_CERT_ADDR:-0x3199d4}" \
|
||||
-c 'pd 24' \
|
||||
-c q "$IOS_APP"
|
||||
```
|
||||
|
||||
Important instructions:
|
||||
|
||||
```asm
|
||||
0x003199d4 stp x29, x30, [x15, -0x10]!
|
||||
0x003199ec ldr x0, [x29, 0x18]
|
||||
0x003199f0 ldur x1, [x0, 0x5f]
|
||||
0x003199f4 cmp x1, x22
|
||||
0x003199f8 b.eq 0x319a28
|
||||
0x00319a00 stp x16, x1, [x15]
|
||||
0x00319a0c ldur x2, [x0, 0x37]
|
||||
0x00319a10 blr x2
|
||||
0x00319a1c ret
|
||||
```
|
||||
|
||||
AuthPass differs from the Android samples: on the normal path the wrapper
|
||||
returns the app-provided bad-certificate callback result unchanged. The bypass
|
||||
target is still the wrapper entry. Returning Dart `true` before the callback
|
||||
logic makes the certificate decision succeed.
|
||||
|
||||
For this fixture, `rabin2 -S "$IOS_APP"` reports equal `__text` physical and
|
||||
virtual addresses:
|
||||
|
||||
```sh
|
||||
rabin2 -S "$IOS_APP" | rg '__TEXT.__text'
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
```text
|
||||
0 0x00005a80 0x6f8bd0 0x00005a80 0x6f8bd0 -r-x 0x0 REGULAR 0.__TEXT.__text
|
||||
```
|
||||
|
||||
That means the recovered method virtual address is also the raw file offset for
|
||||
this fixture. If this is not true for another Mach-O, translate with:
|
||||
|
||||
```text
|
||||
section_paddr + (method_vaddr - section_vaddr)
|
||||
```
|
||||
|
||||
Patch a copy first:
|
||||
|
||||
```sh
|
||||
cp "$IOS_APP" "$IOS_APP.bak"
|
||||
|
||||
r2 -q -w -n -e scr.color=0 \
|
||||
-c "s ${BAD_CERT_ADDR:-0x3199d4}" \
|
||||
-c 'wx c0820091c0035fd6' \
|
||||
-c q "$IOS_APP"
|
||||
```
|
||||
|
||||
Patch bytes:
|
||||
|
||||
```text
|
||||
c0820091 c0035fd6
|
||||
```
|
||||
|
||||
Decoded AArch64:
|
||||
|
||||
```asm
|
||||
add x0, x22, 0x20
|
||||
ret
|
||||
```
|
||||
|
||||
Verify:
|
||||
|
||||
```sh
|
||||
r2 -q -n -a arm -b 64 -e scr.color=0 \
|
||||
-c "s ${BAD_CERT_ADDR:-0x3199d4}" \
|
||||
-c 'p8 8' \
|
||||
-c 'pd 2' \
|
||||
-c q "$IOS_APP"
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
```text
|
||||
c0820091c0035fd6
|
||||
add x0, x22, 0x20
|
||||
ret
|
||||
```
|
||||
|
||||
Restore the fixture after testing:
|
||||
|
||||
```sh
|
||||
cp "$IOS_APP.bak" "$IOS_APP"
|
||||
```
|
||||
|
||||
You can also use the generic qjs helper on a copy:
|
||||
|
||||
```sh
|
||||
TMP_APP=$(mktemp /tmp/r2flutter-authpass-app.XXXXXX)
|
||||
cp "$IOS_APP" "$TMP_APP"
|
||||
r2 -q -w -e bin.relocs.apply=true \
|
||||
-i scripts/dart_cert_bypass.qjs \
|
||||
"$TMP_APP"
|
||||
r2 -q -n -a arm -b 64 -e scr.color=0 \
|
||||
-c 's 0x3199d4' \
|
||||
-c 'p8 8' \
|
||||
-c 'pd 2' \
|
||||
-c q "$TMP_APP"
|
||||
rm -f "$TMP_APP"
|
||||
```
|
||||
|
||||
The script resolves `method._RawSecureSocket_*._onBadCertificateWrapper_*`
|
||||
through r2flutter flags, translates the method address to a file offset using
|
||||
section metadata, and writes the same two AArch64 instructions. It expects the
|
||||
actual Dart image to be opened in r2: Android `libapp.so` or iOS
|
||||
`Frameworks/App.framework/App`.
|
||||
|
||||
## Challenge 3: trace callback registration and direct xrefs
|
||||
|
||||
Disassemble callback registration:
|
||||
|
||||
```sh
|
||||
r2 -q -e scr.color=0 -e bin.relocs.apply=true \
|
||||
-c 'r2flutter -A' \
|
||||
-c "s ${REGISTER_ADDR:-0x3194ec}" \
|
||||
-c 'pd 44' \
|
||||
-c q "$IOS_APP"
|
||||
```
|
||||
|
||||
Useful lines:
|
||||
|
||||
```asm
|
||||
0x0031950c stur x0, [x1, 0xf]
|
||||
0x00319530 bl method._SecureFilterImpl_14069316._registerBadCertificateCallback_14069316
|
||||
0x00319534 mov x0, x22
|
||||
0x00319540 ret
|
||||
```
|
||||
|
||||
Run lightweight reference analysis and query the resolved TLS addresses:
|
||||
|
||||
```sh
|
||||
r2 -q -e scr.color=0 -e bin.relocs.apply=true \
|
||||
-c 'r2flutter -A' \
|
||||
-c 'aar' \
|
||||
-c "axt @ ${BAD_CERT_ADDR:-0x3199d4}" \
|
||||
-c "axt @ ${REGISTER_ADDR:-0x3194ec}" \
|
||||
-c "axt @ ${REGISTER_IMPL_ADDR:-0x31954c}" \
|
||||
-c "axt @ ${HANDSHAKE_ADDR:-0x31749c}" \
|
||||
-c q "$IOS_APP"
|
||||
```
|
||||
|
||||
Observed direct xrefs:
|
||||
|
||||
```text
|
||||
0x3199bc -> 0x3199d4 _RawSecureSocket._anon_closure -> _onBadCertificateWrapper
|
||||
0x316cd8 -> 0x3194ec _RawSecureSocket constructor -> registerBadCertificateCallback
|
||||
0x319530 -> 0x31954c registerBadCertificateCallback -> _registerBadCertificateCallback
|
||||
0x317028 -> 0x31749c _RawSecureSocket constructor -> _secureHandshake
|
||||
0x317514 -> 0x31749c _secureHandshake -> _secureHandshake
|
||||
0x3178b4 -> 0x31749c _tryFilter -> _secureHandshake
|
||||
0x317e04 -> 0x31749c _closeHandler -> _secureHandshake
|
||||
```
|
||||
|
||||
The call into the app-provided certificate callback inside the wrapper is
|
||||
indirect (`blr x2`), so it is not expected to produce a stable direct `axt`
|
||||
target. The stable evidence is the callback registration, the wrapper body, the
|
||||
TLS method cluster, and the direct references to the wrapper.
|
||||
|
||||
## Regression tests
|
||||
|
||||
The tutorial workflow is covered by:
|
||||
|
||||
```sh
|
||||
r2r -u test/db/cmd/authpass-r2-tutorial
|
||||
```
|
||||
|
||||
The tests validate:
|
||||
|
||||
- r2 plugin flag recovery for the AuthPass TLS methods.
|
||||
- wrapper and handshake disassembly landmarks.
|
||||
- direct xref/callsite sources without running full analysis in the test.
|
||||
- the generic qjs bypass script on a temporary copy.
|
||||
|
||||
Patching the embedded iOS framework invalidates the bundle code signature. A
|
||||
real deployment requires re-signing with the appropriate identity/provisioning
|
||||
workflow.
|
||||
@@ -18,6 +18,7 @@ typedef struct DartFunction {
|
||||
ut64 addr;
|
||||
ut64 size;
|
||||
int quality;
|
||||
char *signature; // recovered Dart signature "RET(T,T,...)", or NULL
|
||||
} DartFunction;
|
||||
|
||||
void dart_function_fini(DartFunction *fn);
|
||||
@@ -43,8 +44,8 @@ DartApp *dart_app_new(const char *path);
|
||||
DartApp *dart_app_new_from_core(RCore *core, DartCtx *dctx);
|
||||
void dart_app_free(DartApp *app);
|
||||
void dart_app_load_info(DartApp *app);
|
||||
bool dart_app_find_macho_embedded_dart(const char *path, DartAppEmbeddedPayload *out);
|
||||
char *dart_app_extract_embedded_payload(const char *path, const DartAppEmbeddedPayload *payload);
|
||||
bool dart_app_find_macho_dart(const char *path, DartAppEmbeddedPayload *out);
|
||||
char *dart_app_extract_payload(const char *path, const DartAppEmbeddedPayload *payload);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -91,8 +91,8 @@ typedef enum {
|
||||
const char *dart_cid_kind_name(DartCidKind kind);
|
||||
int dart_cid_get(const DartVerLayout *layout, DartCidKind kind);
|
||||
bool dart_cid_is(const DartVerLayout *layout, int cid, DartCidKind kind);
|
||||
int dart_cid_typed_data_internal_base(const DartVerLayout *layout);
|
||||
int dart_cid_typed_data_internal_limit(const DartVerLayout *layout);
|
||||
int dart_cid_typed_data_base(const DartVerLayout *layout);
|
||||
int dart_cid_typed_data_limit(const DartVerLayout *layout);
|
||||
int dart_cid_typed_data_stride(const DartVerLayout *layout);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
char *dart_dumper_dump4radare2(DartApp *app);
|
||||
char *dart_dumper_dump_r2(DartApp *app);
|
||||
char *dart_dumper_dump_funcs(DartApp *app, int fmt);
|
||||
void dart_dumper_apply_to_core(DartApp *app);
|
||||
void dart_dumper_apply_to_core(DartApp *app, bool apply_signatures);
|
||||
void dart_dumper_apply_signatures(DartApp *app);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
// Auto-generated from offsets.json by scripts/update-dart-version
|
||||
// Hash to compressed_word_size lookup table
|
||||
#ifndef R2C_DART_OFFSETS_H
|
||||
#define R2C_DART_OFFSETS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Hash entry: snapshot MD5 hash (32 hex chars) + compressed_word_size
|
||||
typedef struct {
|
||||
const char hash[33];
|
||||
int compressed_word_size;
|
||||
} DartHashEntry;
|
||||
|
||||
static const DartHashEntry dart_hash_entries[] = {
|
||||
{ "0451907c2eaa8467e848c0067bfe8ed4", 4 }, // Dart 3.13.0
|
||||
{ "0c7f1aa25fc597a7b3b9a19ed390afa1", 8 }, // Dart 2.13.0
|
||||
{ "0eb277b50d6ce4069261c69150ce52c9", 4 }, // Dart 2.17.0
|
||||
{ "1441d6b13b8623fa7fbf61433abebd31", 4 }, // Dart 2.17.6
|
||||
{ "16ad76edd19b537bf6ea64fdd31977a7", 4 }, // Dart 3.0.5
|
||||
{ "1ce86630892e2dca9a8543fdb8ed8e22", 4 }, // Dart 3.10.7
|
||||
{ "24d9d411c2f90c8fbe8907f99e89d4b0", 8 }, // Dart 2.15.0
|
||||
{ "2858c2c0920495f00b9bce9edf6a8cd9", 4 }, // Dart 3.6.2
|
||||
{ "287683014dfbd311f6400c655c97a26d", 4 }, // Dart 2.18.6
|
||||
{ "30c051cb9192f6d6812be3f0b87a6356", 4 }, // Dart 3.6.0
|
||||
{ "3318fe66091c0ffbb64faec39976cb7d", 8 }, // Dart 2.16.0
|
||||
{ "34f6eec64e9371856eaaa278ccf56538", 8 }, // Dart 2.13.0
|
||||
{ "36b0375d284ee2af0d0fffc6e6e48fde", 4 }, // Dart 3.0.5
|
||||
{ "41be3daaabd524b8aa7423bc24584957", 4 }, // Dart 3.12.0
|
||||
{ "501ef5cbd64ca70b6b42672346af6a8a", 4 }, // Dart 2.19.0
|
||||
{ "5191d02bb1d36a9f8609aa7495894e76", 4 }, // Dart 3.1.0
|
||||
{ "5b97292b25f0a715613b7a28e0734f77", 8 }, // Dart 2.12.0
|
||||
{ "6093345773c65683f60c20c8e62cbe4a", 8 }, // Dart 2.16.0
|
||||
{ "617d044d9faa87a98c29f8aad5d2f220", 4 }, // Dart 3.0.3
|
||||
{ "659a72e41e3276e882709901c27de33d", 8 }, // Dart 2.14.0
|
||||
{ "6a9b5a03a7e784a4558b10c769f188d9", 4 }, // Dart 2.18.0
|
||||
{ "71dcc635f2e5dca494525aa13ca9006b", 4 }, // Dart 3.5.0
|
||||
{ "77b867742ae1ace67fbbb9a9a0d0334e", 4 }, // Dart 3.3.0
|
||||
{ "77bd0a83c49bfc555a44a0740f15003a", 4 }, // Dart 2.18.0
|
||||
{ "78da37fed6bf1489361a312568249f3f", 4 }, // Dart 3.11.5
|
||||
{ "7a1ea3f6f5cf1089a7f6e55d7f20dbfd", 4 }, // Dart 3.14.0
|
||||
{ "7a5b240780941844bae88eca5dbaa7b8", 8 }, // Dart 2.13.0
|
||||
{ "7b96ef6dda75bdd1d70be8c2f7cb814e", 8 }, // Dart 2.15.0
|
||||
{ "7dbbeeb8ef7b91338640dca3927636de", 4 }, // Dart 3.1.3
|
||||
{ "80a49c7111088100a233b2ae788e1f48", 4 }, // Dart 3.5.0
|
||||
{ "830f4f59e7969c70b595182826435c19", 4 }, // Dart 3.8.1
|
||||
{ "8e50e448b241be23b9e990094f4dca39", 4 }, // Dart 2.18.0
|
||||
{ "8ee4ef7a67df9845fba331734198a953", 8 }, // Dart 2.10.0
|
||||
{ "90b56a561f70cd55e972cb49b79b3d8b", 4 }, // Dart 3.0.5
|
||||
{ "97ff04a728735e6b6b098bdf983faaba", 4 }, // Dart 3.9.2
|
||||
{ "9cf77f4405212c45daf608e1cd646852", 8 }, // Dart 2.14.0
|
||||
{ "9d6362266b78e4198f784da1a28fd82b", 8 }, // Dart 3.2.0
|
||||
{ "a0cb0c928b23bc17a26e062b351dc44d", 4 }, // Dart 2.17.6
|
||||
{ "aa64af18e7d086041ac127cc4bc50c5e", 4 }, // Dart 3.0.5
|
||||
{ "ace654289f5abc240509fc941453ebc5", 4 }, // Dart 3.12.1
|
||||
{ "adb4292f3ec25074ca70abcd2d5c7251", 4 }, // Dart 2.19.0
|
||||
{ "adf563436d12ba0d50ea5beb7f3be1bb", 8 }, // Dart 2.16.0
|
||||
{ "afcf461a16dfa5e88f88a2aa68764c99", 4 }, // Dart 2.19.0
|
||||
{ "b0e899ec5a90e4661501f0b69e9dd70f", 4 }, // Dart 2.18.0
|
||||
{ "b6d0a1f034d158b0d37b51d559379697", 4 }, // Dart 2.18.0
|
||||
{ "bae16a6858d8c0fe2c437b275eb807cd", 4 }, // Dart 3.7.0
|
||||
{ "c03fb294bfdaca2b44c552ad35e52d45", 8 }, // Dart 2.14.0
|
||||
{ "cda356e9bae476c70de33809fd92e009", 4 }, // Dart 3.5.0
|
||||
{ "ce5d37cf26ea4faac543cd216f3b96d1", 4 }, // Dart 3.0.0
|
||||
{ "d20a1be77c3d3c41b2a5accaee1ce549", 8 }, // Dart 3.4.3
|
||||
{ "d56742caf7b3b3f4bd2df93a9bbb5503", 8 }, // Dart 2.16.0
|
||||
{ "d91c0e6f35f0eb2e44124e8f42aa44a7", 4 }, // Dart 3.7.0
|
||||
{ "d979fa911b038a8682e98cc8c8569261", 8 }, // Dart 2.10.0
|
||||
{ "ded6ef11c73fdc638d6ff6d3ad22a67b", 4 }, // Dart 2.17.6
|
||||
{ "e036be9d5df68283e1a043e348fa7ff4", 8 }, // Dart 2.12.0
|
||||
{ "e4a09dbf2bb120fe4674e0576617a0dc", 8 }, // Dart 2.13.0
|
||||
{ "e727fae79fb45aac639252a9453b785e", 4 }, // Dart 2.19.1
|
||||
{ "ee1eb666c76a5cb7746faf39d0b97547", 4 }, // Dart 3.3.0
|
||||
{ "f10776149bf76be288def3c2ca73bdc1", 8 }, // Dart 2.15.0
|
||||
{ "f71c76320d35b65f1164dbaa6d95fe09", 8 }, // Dart 3.2.5
|
||||
{ "f91b8b03bf7f30a5e983fd19b23d978d", 4 }, // Dart 2.18.2
|
||||
{ "f956f595844a2f845a55707faaaa51e4", 4 }, // Dart 3.6.2
|
||||
{ "fa07b8a2d866963d1117ecdcea9287c8", 4 }, // Dart 3.8.0
|
||||
{ "fe53c4fdba846005b70514aec3840296", 8 }, // Dart 3.4.0
|
||||
};
|
||||
|
||||
#define DART_HASH_ENTRIES_COUNT 64
|
||||
|
||||
// Default offset arrays (identical across all known Dart versions)
|
||||
#define DART_DEFAULT_EP_OFFSETS \
|
||||
{ 8, 24, 16, 32 }
|
||||
#define DART_DEFAULT_EP_OFFSETS_COUNT 4
|
||||
#define DART_DEFAULT_OWNER_OFFSETS \
|
||||
{ 56 }
|
||||
#define DART_DEFAULT_OWNER_OFFSETS_COUNT 1
|
||||
#define DART_DEFAULT_NAME_OFFSETS \
|
||||
{ 24, 8 }
|
||||
#define DART_DEFAULT_NAME_OFFSETS_COUNT 2
|
||||
|
||||
#endif // R2C_DART_OFFSETS_H
|
||||
@@ -82,12 +82,17 @@ typedef struct DartClassInfo {
|
||||
char *library_name;
|
||||
ut64 library_ref;
|
||||
ut64 super_class_ref;
|
||||
ut64 super_type_ref;
|
||||
ut64 interfaces_ref;
|
||||
char *super_class_name;
|
||||
ut32 class_id;
|
||||
ut32 instance_size;
|
||||
ut32 next_field_offset;
|
||||
ut32 type_argument_offset;
|
||||
ut32 num_type_parameters;
|
||||
ut32 flags;
|
||||
ut64 field_bitmap;
|
||||
bool field_bitmap_present;
|
||||
RList *enums;
|
||||
RList *fields;
|
||||
RList *interfaces;
|
||||
@@ -149,6 +154,7 @@ typedef struct DartInstructionTableEntry {
|
||||
ut32 stack_map_offset;
|
||||
bool has_code;
|
||||
char *name;
|
||||
char *signature;
|
||||
} DartInstructionTableEntry;
|
||||
|
||||
void dart_instruction_table_entry_fini(DartInstructionTableEntry *ie);
|
||||
@@ -160,11 +166,14 @@ typedef void(*DartPoolFunctionCallback)(const char *name, ut64 addr, ut64 size,
|
||||
// Main API - all functions take DartCtx*
|
||||
// ============================================================================
|
||||
|
||||
int dart_pool_enumerate(DartCtx *ctx, const char *libapp_path, DartPoolFunctionCallback on_fn, void *user, ut64 *out_base, ut64 *out_heap_base);
|
||||
bool dart_ctx_set_profile_override(DartCtx *ctx, const char *spec);
|
||||
const char *dart_ctx_version(DartCtx *ctx);
|
||||
const char *dart_ctx_hash(DartCtx *ctx);
|
||||
int dart_pool_scan(DartCtx *ctx, DartPoolFunctionCallback on_fn, void *user);
|
||||
char *dart_pool_dump_header(DartCtx *ctx, int fmt);
|
||||
char *dart_pool_dump_header_ext(DartCtx *ctx, int fmt);
|
||||
char *dart_pool_dump_header_deep(DartCtx *ctx, int fmt);
|
||||
char *dart_pool_dump_pp(DartCtx *ctx, int fmt);
|
||||
char *dart_pool_dump_pool(DartCtx *ctx, int fmt);
|
||||
char *dart_pool_dump_object(DartCtx *ctx, const char *spec, int fmt);
|
||||
|
||||
// ============================================================================
|
||||
@@ -189,7 +198,7 @@ void dart_string_ref_free(DartStringRef *sr);
|
||||
// ============================================================================
|
||||
|
||||
RList *dart_pool_extract_strings(DartCtx *ctx);
|
||||
RList *dart_pool_extract_object_pool_strings(DartCtx *ctx);
|
||||
RList *dart_pool_extract_pool_strings(DartCtx *ctx);
|
||||
char *dart_pool_dump_strings(DartCtx *ctx, int fmt);
|
||||
char *dart_pool_dump_strings_fuzzy(DartCtx *ctx, int fmt);
|
||||
void dart_string_list_free(RList *list);
|
||||
@@ -201,6 +210,7 @@ char *dart_pool_dump_sbom(DartCtx *ctx, const char *input_path, int fmt);
|
||||
// ============================================================================
|
||||
|
||||
RVecDartInstructionTableEntry *dart_pool_extract_instruction_table(DartCtx *ctx);
|
||||
char *dart_pool_dump_entry(DartCtx *ctx, int fmt);
|
||||
char *dart_pool_dump_it(DartCtx *ctx, int fmt);
|
||||
void dart_instruction_table_entry_free(DartInstructionTableEntry *ie);
|
||||
void dart_instruction_table_list_free(RVecDartInstructionTableEntry *list);
|
||||
|
||||
@@ -41,19 +41,27 @@ typedef struct {
|
||||
ut64 container_payload_offset;
|
||||
ut64 container_payload_size;
|
||||
ut64 container_macho_offset;
|
||||
char snapshot_hash_actual[33];
|
||||
char snapshot_hash[33];
|
||||
char snapshot_hash_override[33];
|
||||
char dart_version_override[32];
|
||||
const DartVerLayout *layout;
|
||||
int compressed_word_size;
|
||||
int version_source; // DartVersionSource: how the layout/version was chosen
|
||||
bool layout_probed;
|
||||
HtUP *name_by_ep;
|
||||
char **name_by_code_index;
|
||||
ut64 name_by_code_index_count;
|
||||
// Recovered Dart signature per code index, format "RET(T,T,...)" where the
|
||||
// first param of an instance method is the receiver typed as the owner
|
||||
// class. Parallel to name_by_code_index; NULL where unresolved.
|
||||
char **signature_by_code_index;
|
||||
ut64 signature_by_code_index_count;
|
||||
ut8 *owner_kind_by_code_index;
|
||||
ut64 owner_kind_by_code_index_count;
|
||||
RList *name_pool;
|
||||
int name_pool_idx;
|
||||
RList *strings;
|
||||
RList *classes;
|
||||
RList *functions;
|
||||
void **refs;
|
||||
ut64 refs_count;
|
||||
ut64 num_base_objects;
|
||||
@@ -76,6 +84,13 @@ typedef struct {
|
||||
const char *obf_map_path;
|
||||
HtPP *obf_by_obfuscated;
|
||||
bool obf_map_tried;
|
||||
// Windowed read cache for read_mem, to avoid one r_io_read_at syscall per
|
||||
// byte while decoding cluster streams (which read one byte at a time).
|
||||
// Scoped to a single command invocation and released by dart_obf_fini.
|
||||
ut8 *rmem_cache;
|
||||
ut64 rmem_cache_addr;
|
||||
int rmem_cache_len;
|
||||
int rmem_cache_cap;
|
||||
} DartCtx;
|
||||
|
||||
#endif
|
||||
@@ -16,10 +16,52 @@ extern "C" {
|
||||
typedef enum {
|
||||
DART_TAG_STYLE_CID_INT32 = 0, // v2.10-2.13: raw int32 CID
|
||||
DART_TAG_STYLE_CID_SHIFT1 = 1, // v2.14-3.3: (cid << 1) | canonical
|
||||
DART_TAG_STYLE_OBJECT_HEADER = 2 // v3.4.3+: ObjectHeader with ClassIdTag at bits 12-31
|
||||
DART_TAG_STYLE_OBJECT_HEADER = 2 // v3.4.3+ and known 2.18.2 exception
|
||||
} DartTagStyle;
|
||||
|
||||
// Ordered fields following the snapshot feature string. The field count alone
|
||||
// is insufficient because the meaning of the five-field layouts changed.
|
||||
typedef enum {
|
||||
DART_HEADER_STYLE_MODERN = 0, // nb, no, nc, itlen, itdata (2.18+)
|
||||
DART_HEADER_STYLE_210, // nb, no, nc, field_table_len
|
||||
DART_HEADER_STYLE_212, // nb, no, canonical_nc, nc, field_table_len
|
||||
DART_HEADER_STYLE_214, // nb, no, nc, field_table_len, itlen
|
||||
DART_HEADER_STYLE_216 // nb, no, nc, field_table_len, itlen, itdata
|
||||
} DartHeaderStyle;
|
||||
|
||||
// Object references use the ordinary unsigned stream integer through Dart
|
||||
// 2.17. Dart 2.18 introduced a distinct big-endian RefId encoding whose
|
||||
// signed terminal byte is biased by 128.
|
||||
typedef enum {
|
||||
DART_REF_ENCODING_REF_ID128 = 0,
|
||||
DART_REF_ENCODING_UNSIGNED
|
||||
} DartRefEncoding;
|
||||
|
||||
// ObjectPool entry type/patch/behavior bit packing changed independently of
|
||||
// the surrounding cluster layout.
|
||||
typedef enum {
|
||||
DART_POOL_ENCODING_MODERN = 0, // type[0..3], patch[4], behavior[5..7]
|
||||
DART_POOL_ENCODING_LOW7, // type[0..6], patch[7], tagged=0, immediate=1
|
||||
DART_POOL_ENCODING_LOW7_SWAPPED // type[0..6], patch[7], immediate=0, tagged=1
|
||||
} DartPoolEncoding;
|
||||
|
||||
typedef enum {
|
||||
DART_CODE_ALLOC_STATE_DEFERRED = 0, // count, state words, deferred count/state words
|
||||
DART_CODE_ALLOC_DEFERRED_ONLY // count, deferred count
|
||||
} DartCodeAlloc;
|
||||
|
||||
// How the Dart version/layout for a snapshot was determined. Reported so users
|
||||
// can distinguish exact, structurally validated, and fallback profiles.
|
||||
typedef enum {
|
||||
DART_VERSION_SOURCE_EXACT = 0, // snapshot hash matched a known SDK release
|
||||
DART_VERSION_SOURCE_OVERRIDE = 1, // user forced a profile via -D
|
||||
DART_VERSION_SOURCE_PROBE = 2, // unknown hash, layout selected by structural validation
|
||||
DART_VERSION_SOURCE_FINGERPRINT = 3, // unknown hash, conservative fallback profile
|
||||
DART_VERSION_SOURCE_UNKNOWN = 4 // no snapshot available
|
||||
} DartVersionSource;
|
||||
|
||||
// Version layout information for Dart snapshots
|
||||
// CID values are stored in dart_cid.c's cid_tables[] and accessed via dart_cid_get()
|
||||
typedef struct {
|
||||
char hash[33];
|
||||
const char *dart_version;
|
||||
@@ -29,36 +71,55 @@ typedef struct {
|
||||
ut64 it_cap;
|
||||
DartTagStyle tag_style;
|
||||
int header_fields;
|
||||
int cid_class;
|
||||
int cid_function;
|
||||
int cid_code;
|
||||
int cid_string;
|
||||
int cid_one_byte_string;
|
||||
int cid_two_byte_string;
|
||||
int cid_array;
|
||||
int cid_mint;
|
||||
int cid_object_pool;
|
||||
int num_predefined_cids;
|
||||
DartHeaderStyle header_style;
|
||||
DartRefEncoding ref_encoding;
|
||||
DartPoolEncoding pool_encoding;
|
||||
DartCodeAlloc code_alloc;
|
||||
int code_refs;
|
||||
int code_leading_refs;
|
||||
bool code_has_text_offset;
|
||||
bool single_snapshot;
|
||||
bool class_alloc_fixed;
|
||||
bool closure_variable;
|
||||
bool class_top_level_cid20;
|
||||
} DartVerLayout;
|
||||
|
||||
// Lookup Dart version from a snapshot hash (MD5)
|
||||
// Returns NULL if hash is not recognized
|
||||
const char *dart_version_from_hash(const char *hash);
|
||||
|
||||
// Lookup version profile from a Dart version string
|
||||
// Returns NULL if version is not recognized
|
||||
// Lookup version profile from a Dart version string using floor semantics.
|
||||
// Returns NULL if the version string is not a dotted numeric version.
|
||||
const DartVerLayout *dart_profile_from_version(const char *version);
|
||||
|
||||
// Pick a layout structure based on hash, with fallback to defaults
|
||||
// Returns a newly allocated DartVerLayout (caller must free)
|
||||
// If hash is unknown, uses v3.9.2 defaults
|
||||
DartVerLayout *dart_pick_layout_by_hash(const char *hash);
|
||||
// Enumerate the built-in range-start profiles for structural probing.
|
||||
int dart_profile_count(void);
|
||||
const DartVerLayout *dart_profile_at(int index);
|
||||
|
||||
// Free a DartVerLayout allocated by dart_pick_layout_by_hash
|
||||
// Pick a layout structure based on hash, with fallback to a compatible baseline
|
||||
// Returns a newly allocated DartVerLayout (caller must free)
|
||||
// If hash is unknown, fingerprints against the newest split-snapshot profile.
|
||||
DartVerLayout *dart_layout_from_hash(const char *hash);
|
||||
|
||||
// Newest known layout profile.
|
||||
const DartVerLayout *dart_newest_profile(void);
|
||||
|
||||
// Conservative profile used until an unknown hash can be structurally probed.
|
||||
// Single-snapshot mechanics require positive evidence and are not inherited.
|
||||
const DartVerLayout *dart_fingerprint_profile(void);
|
||||
|
||||
// Human-readable name for a DartVersionSource value.
|
||||
const char *dart_version_source_str(int source);
|
||||
|
||||
// Free a DartVerLayout allocated by dart_layout_from_hash
|
||||
void dart_ver_layout_free(DartVerLayout *layout);
|
||||
|
||||
void dart_version_set_verbose(int level);
|
||||
|
||||
// Compare dotted Dart SDK versions numerically.
|
||||
// Returns -1, 0, 1, or -2 when either input is not a dotted numeric version.
|
||||
int dart_version_compare(const char *a, const char *b);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#ifndef R2FLUTTER_VERSION_H
|
||||
#define R2FLUTTER_VERSION_H
|
||||
|
||||
#define R2FLUTTER_VERSION "0.2.0"
|
||||
#define R2FLUTTER_VERSION "0.3.0"
|
||||
#define R2FLUTTER_VERSION_MAJOR 0
|
||||
#define R2FLUTTER_VERSION_MINOR 2
|
||||
#define R2FLUTTER_VERSION_MINOR 3
|
||||
#define R2FLUTTER_VERSION_PATCH 0
|
||||
|
||||
#endif
|
||||
+10
-3
@@ -1,12 +1,19 @@
|
||||
project(
|
||||
'r2flutter',
|
||||
'c',
|
||||
version : '0.2.0',
|
||||
meson_version : '>=0.53.0',
|
||||
version : '0.3.0',
|
||||
meson_version : '>=0.55.0',
|
||||
default_options : ['warning_level=2', 'werror=false']
|
||||
)
|
||||
|
||||
r_core_dep = dependency('r_core', method: 'pkg-config')
|
||||
r2 = find_program('r2', required: true)
|
||||
r2_cflags_res = run_command(r2, '-H', 'R2_CFLAGS', check: true)
|
||||
r2_ldflags_res = run_command(r2, '-H', 'R2_LDFLAGS', check: true)
|
||||
r2_libs_res = run_command(r2, '-H', 'R2_LIBS', check: true)
|
||||
r_core_dep = declare_dependency(
|
||||
compile_args: r2_cflags_res.stdout().strip().split(),
|
||||
link_args: r2_ldflags_res.stdout().strip().split() + r2_libs_res.stdout().strip().split(),
|
||||
)
|
||||
r2flutter_inc = include_directories('include')
|
||||
|
||||
r2flutter_sources = files(
|
||||
|
||||
+97
-742
@@ -1,746 +1,101 @@
|
||||
{
|
||||
"arch": "arm64",
|
||||
"defaults": {
|
||||
"code_entry_point_offsets": [8, 24, 16, 32],
|
||||
"code_owner_offsets": [56],
|
||||
"function_name_offsets": [24, 8]
|
||||
},
|
||||
"profiles": {
|
||||
"2.10.0": {"cws": 8, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_INT32", "header_fields": 4, "typed_data_internal_base": 108, "typed_data_internal_limit": 150, "typed_data_stride": 3, "cids": {"ILLEGAL": 0, "CLASS": 4, "PATCH_CLASS": 5, "FUNCTION": 6, "CLOSURE_DATA": 7, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 77, "CODE": 16, "OBJECT_POOL": 20, "PC_DESCRIPTORS": 21, "CODE_SOURCE_MAP": 22, "COMPRESSED_STACK_MAPS": 23, "EXCEPTION_HANDLERS": 25, "CONTEXT": 26, "CONTEXT_SCOPE": 27, "SINGLE_TARGET_CACHE": 29, "UNLINKED_CALL": 30, "MONOMORPHIC_SMIABLE_CALL": 31, "CALL_SITE_DATA": 32, "ICDATA": 33, "MEGAMORPHIC_CACHE": 34, "SUBTYPE_TEST_CACHE": 35, "LOADING_UNIT": 36, "LANGUAGE_ERROR": 39, "UNHANDLED_EXCEPTION": 40, "INSTANCE": 42, "LIBRARY_PREFIX": 43, "TYPE_ARGUMENTS": 44, "TYPE": 46, "TYPE_REF": 47, "TYPE_PARAMETER": 48, "CLOSURE": 49, "MINT": 53, "DOUBLE": 54, "FLOAT32X4": 57, "INT32X4": 58, "FLOAT64X2": 59, "TYPED_DATA": 61, "EXTERNAL_TYPED_DATA": 62, "TYPED_DATA_VIEW": 63, "CAPABILITY": 66, "RECEIVE_PORT": 67, "SEND_PORT": 68, "STACK_TRACE": 69, "REGEXP": 70, "WEAK_PROPERTY": 71, "FUTURE_OR": 74, "USER_TAG": 75, "TRANSFERABLE_TYPED_DATA": 76, "ARRAY": 78, "IMMUTABLE_ARRAY": 79, "GROWABLE_OBJECT_ARRAY": 56, "STRING": 80, "ONE_BYTE_STRING": 81, "TWO_BYTE_STRING": 82, "NUM_PREDEFINED_CIDS": 156}},
|
||||
"2.12.0": {"cws": 8, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_INT32", "header_fields": 5, "typed_data_internal_base": 100, "typed_data_internal_limit": 142, "typed_data_stride": 3, "cids": {"ILLEGAL": 0, "CLASS": 4, "PATCH_CLASS": 5, "FUNCTION": 6, "CLOSURE_DATA": 7, "FFI_TRAMPOLINE_DATA": 8, "FIELD": 9, "SCRIPT": 10, "LIBRARY": 11, "NAMESPACE": 12, "KERNEL_PROGRAM_INFO": 13, "WEAK_SERIALIZATION_REFERENCE": 74, "CODE": 14, "OBJECT_POOL": 17, "PC_DESCRIPTORS": 18, "CODE_SOURCE_MAP": 19, "COMPRESSED_STACK_MAPS": 20, "EXCEPTION_HANDLERS": 22, "CONTEXT": 23, "CONTEXT_SCOPE": 24, "SINGLE_TARGET_CACHE": 25, "UNLINKED_CALL": 26, "MONOMORPHIC_SMIABLE_CALL": 27, "CALL_SITE_DATA": 28, "ICDATA": 29, "MEGAMORPHIC_CACHE": 30, "SUBTYPE_TEST_CACHE": 31, "LOADING_UNIT": 32, "LANGUAGE_ERROR": 35, "UNHANDLED_EXCEPTION": 36, "INSTANCE": 38, "LIBRARY_PREFIX": 39, "TYPE_ARGUMENTS": 40, "TYPE": 42, "FUNCTION_TYPE": 43, "TYPE_REF": 44, "TYPE_PARAMETER": 45, "CLOSURE": 46, "MINT": 50, "DOUBLE": 51, "FLOAT32X4": 54, "INT32X4": 55, "FLOAT64X2": 56, "TYPED_DATA": 58, "EXTERNAL_TYPED_DATA": 59, "TYPED_DATA_VIEW": 60, "CAPABILITY": 63, "RECEIVE_PORT": 64, "SEND_PORT": 65, "STACK_TRACE": 66, "REGEXP": 67, "WEAK_PROPERTY": 68, "FUTURE_OR": 71, "USER_TAG": 72, "TRANSFERABLE_TYPED_DATA": 73, "ARRAY": 75, "IMMUTABLE_ARRAY": 76, "GROWABLE_OBJECT_ARRAY": 53, "STRING": 77, "ONE_BYTE_STRING": 78, "TWO_BYTE_STRING": 79, "NUM_PREDEFINED_CIDS": 148}},
|
||||
"2.13.0": {"cws": 8, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_INT32", "header_fields": 5, "typed_data_internal_base": 100, "typed_data_internal_limit": 142, "typed_data_stride": 3, "cids": {"ILLEGAL": 0, "CLASS": 4, "PATCH_CLASS": 5, "FUNCTION": 6, "CLOSURE_DATA": 7, "FFI_TRAMPOLINE_DATA": 8, "FIELD": 9, "SCRIPT": 10, "LIBRARY": 11, "NAMESPACE": 12, "KERNEL_PROGRAM_INFO": 13, "WEAK_SERIALIZATION_REFERENCE": 14, "CODE": 15, "OBJECT_POOL": 18, "PC_DESCRIPTORS": 19, "CODE_SOURCE_MAP": 20, "COMPRESSED_STACK_MAPS": 21, "EXCEPTION_HANDLERS": 23, "CONTEXT": 24, "CONTEXT_SCOPE": 25, "SINGLE_TARGET_CACHE": 26, "UNLINKED_CALL": 27, "MONOMORPHIC_SMIABLE_CALL": 28, "CALL_SITE_DATA": 29, "ICDATA": 30, "MEGAMORPHIC_CACHE": 31, "SUBTYPE_TEST_CACHE": 32, "LOADING_UNIT": 33, "LANGUAGE_ERROR": 36, "UNHANDLED_EXCEPTION": 37, "INSTANCE": 39, "LIBRARY_PREFIX": 40, "TYPE_ARGUMENTS": 41, "TYPE": 43, "FUNCTION_TYPE": 44, "TYPE_REF": 45, "TYPE_PARAMETER": 46, "CLOSURE": 47, "MINT": 51, "DOUBLE": 52, "FLOAT32X4": 55, "INT32X4": 56, "FLOAT64X2": 57, "TYPED_DATA": 59, "EXTERNAL_TYPED_DATA": 60, "TYPED_DATA_VIEW": 61, "CAPABILITY": 64, "RECEIVE_PORT": 65, "SEND_PORT": 66, "STACK_TRACE": 67, "REGEXP": 68, "WEAK_PROPERTY": 69, "FUTURE_OR": 72, "USER_TAG": 73, "TRANSFERABLE_TYPED_DATA": 74, "ARRAY": 75, "IMMUTABLE_ARRAY": 76, "GROWABLE_OBJECT_ARRAY": 54, "STRING": 77, "ONE_BYTE_STRING": 78, "TWO_BYTE_STRING": 79, "NUM_PREDEFINED_CIDS": 148}},
|
||||
"2.14.0": {"cws": 8, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_SHIFT1", "header_fields": 5, "typed_data_internal_base": 104, "typed_data_internal_limit": 146, "typed_data_stride": 3, "cids": {"ILLEGAL": 0, "CLASS": 4, "PATCH_CLASS": 5, "FUNCTION": 6, "TYPE_PARAMETERS": 7, "CLOSURE_DATA": 8, "FFI_TRAMPOLINE_DATA": 9, "FIELD": 10, "SCRIPT": 11, "LIBRARY": 12, "NAMESPACE": 13, "KERNEL_PROGRAM_INFO": 14, "WEAK_SERIALIZATION_REFERENCE": 15, "CODE": 16, "OBJECT_POOL": 20, "PC_DESCRIPTORS": 21, "CODE_SOURCE_MAP": 22, "COMPRESSED_STACK_MAPS": 23, "EXCEPTION_HANDLERS": 25, "CONTEXT": 26, "CONTEXT_SCOPE": 27, "SENTINEL": 28, "SINGLE_TARGET_CACHE": 29, "UNLINKED_CALL": 30, "MONOMORPHIC_SMIABLE_CALL": 31, "CALL_SITE_DATA": 32, "ICDATA": 33, "MEGAMORPHIC_CACHE": 34, "SUBTYPE_TEST_CACHE": 35, "LOADING_UNIT": 36, "LANGUAGE_ERROR": 39, "UNHANDLED_EXCEPTION": 40, "INSTANCE": 42, "LIBRARY_PREFIX": 43, "TYPE_ARGUMENTS": 44, "TYPE": 46, "FUNCTION_TYPE": 47, "TYPE_REF": 48, "TYPE_PARAMETER": 49, "CLOSURE": 50, "MINT": 54, "DOUBLE": 55, "FLOAT32X4": 58, "INT32X4": 59, "FLOAT64X2": 60, "TYPED_DATA": 62, "EXTERNAL_TYPED_DATA": 63, "TYPED_DATA_VIEW": 64, "CAPABILITY": 67, "RECEIVE_PORT": 68, "SEND_PORT": 69, "STACK_TRACE": 70, "REGEXP": 71, "WEAK_PROPERTY": 72, "FUTURE_OR": 74, "USER_TAG": 75, "TRANSFERABLE_TYPED_DATA": 76, "ARRAY": 79, "IMMUTABLE_ARRAY": 80, "GROWABLE_OBJECT_ARRAY": 57, "STRING": 81, "ONE_BYTE_STRING": 82, "TWO_BYTE_STRING": 83, "NUM_PREDEFINED_CIDS": 152}},
|
||||
"2.15.0": {"cws": 8, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_SHIFT1", "header_fields": 5, "typed_data_internal_base": 106, "typed_data_internal_limit": 148, "typed_data_stride": 3, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "CODE": 17, "OBJECT_POOL": 21, "PC_DESCRIPTORS": 22, "CODE_SOURCE_MAP": 23, "COMPRESSED_STACK_MAPS": 24, "EXCEPTION_HANDLERS": 26, "CONTEXT": 27, "CONTEXT_SCOPE": 28, "SENTINEL": 29, "SINGLE_TARGET_CACHE": 30, "UNLINKED_CALL": 31, "MONOMORPHIC_SMIABLE_CALL": 32, "CALL_SITE_DATA": 33, "ICDATA": 34, "MEGAMORPHIC_CACHE": 35, "SUBTYPE_TEST_CACHE": 36, "LOADING_UNIT": 37, "LANGUAGE_ERROR": 40, "UNHANDLED_EXCEPTION": 41, "INSTANCE": 43, "LIBRARY_PREFIX": 44, "TYPE_ARGUMENTS": 45, "TYPE": 47, "FUNCTION_TYPE": 48, "TYPE_REF": 49, "TYPE_PARAMETER": 50, "CLOSURE": 51, "MINT": 55, "DOUBLE": 56, "FLOAT32X4": 58, "INT32X4": 59, "FLOAT64X2": 60, "TYPED_DATA": 62, "EXTERNAL_TYPED_DATA": 63, "TYPED_DATA_VIEW": 64, "CAPABILITY": 67, "RECEIVE_PORT": 68, "SEND_PORT": 69, "STACK_TRACE": 70, "REGEXP": 71, "WEAK_PROPERTY": 72, "FUTURE_OR": 74, "USER_TAG": 75, "TRANSFERABLE_TYPED_DATA": 76, "ARRAY": 81, "IMMUTABLE_ARRAY": 82, "GROWABLE_OBJECT_ARRAY": 83, "STRING": 84, "ONE_BYTE_STRING": 85, "TWO_BYTE_STRING": 86, "NUM_PREDEFINED_CIDS": 154}},
|
||||
"2.16.0": {"cws": 8, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_SHIFT1", "header_fields": 6, "typed_data_internal_base": 106, "typed_data_internal_limit": 148, "typed_data_stride": 3, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "CODE": 17, "OBJECT_POOL": 21, "PC_DESCRIPTORS": 22, "CODE_SOURCE_MAP": 23, "COMPRESSED_STACK_MAPS": 24, "EXCEPTION_HANDLERS": 26, "CONTEXT": 27, "CONTEXT_SCOPE": 28, "SENTINEL": 29, "SINGLE_TARGET_CACHE": 30, "UNLINKED_CALL": 31, "MONOMORPHIC_SMIABLE_CALL": 32, "CALL_SITE_DATA": 33, "ICDATA": 34, "MEGAMORPHIC_CACHE": 35, "SUBTYPE_TEST_CACHE": 36, "LOADING_UNIT": 37, "LANGUAGE_ERROR": 40, "UNHANDLED_EXCEPTION": 41, "INSTANCE": 43, "LIBRARY_PREFIX": 44, "TYPE_ARGUMENTS": 45, "TYPE": 47, "FUNCTION_TYPE": 48, "TYPE_REF": 49, "TYPE_PARAMETER": 50, "CLOSURE": 51, "MINT": 55, "DOUBLE": 56, "FLOAT32X4": 58, "INT32X4": 59, "FLOAT64X2": 60, "TYPED_DATA": 62, "EXTERNAL_TYPED_DATA": 63, "TYPED_DATA_VIEW": 64, "CAPABILITY": 67, "RECEIVE_PORT": 68, "SEND_PORT": 69, "STACK_TRACE": 70, "REGEXP": 71, "WEAK_PROPERTY": 72, "FUTURE_OR": 74, "USER_TAG": 75, "TRANSFERABLE_TYPED_DATA": 76, "ARRAY": 81, "IMMUTABLE_ARRAY": 82, "GROWABLE_OBJECT_ARRAY": 83, "STRING": 84, "ONE_BYTE_STRING": 85, "TWO_BYTE_STRING": 86, "NUM_PREDEFINED_CIDS": 154}},
|
||||
"2.17.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_SHIFT1", "header_fields": 6, "typed_data_internal_base": 110, "typed_data_internal_limit": 152, "typed_data_stride": 3, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "CODE": 17, "OBJECT_POOL": 21, "PC_DESCRIPTORS": 22, "CODE_SOURCE_MAP": 23, "COMPRESSED_STACK_MAPS": 24, "EXCEPTION_HANDLERS": 26, "CONTEXT": 27, "CONTEXT_SCOPE": 28, "SENTINEL": 29, "SINGLE_TARGET_CACHE": 30, "UNLINKED_CALL": 31, "MONOMORPHIC_SMIABLE_CALL": 32, "CALL_SITE_DATA": 33, "ICDATA": 34, "MEGAMORPHIC_CACHE": 35, "SUBTYPE_TEST_CACHE": 36, "LOADING_UNIT": 37, "LANGUAGE_ERROR": 40, "UNHANDLED_EXCEPTION": 41, "INSTANCE": 43, "LIBRARY_PREFIX": 44, "TYPE_ARGUMENTS": 45, "TYPE": 47, "FUNCTION_TYPE": 52, "TYPE_REF": 53, "TYPE_PARAMETER": 54, "CLOSURE": 55, "MINT": 59, "DOUBLE": 60, "FLOAT32X4": 62, "INT32X4": 63, "FLOAT64X2": 64, "TYPED_DATA": 66, "EXTERNAL_TYPED_DATA": 67, "TYPED_DATA_VIEW": 68, "CAPABILITY": 71, "RECEIVE_PORT": 72, "SEND_PORT": 73, "STACK_TRACE": 74, "REGEXP": 75, "WEAK_PROPERTY": 76, "WEAK_REFERENCE": 77, "FUTURE_OR": 79, "USER_TAG": 80, "TRANSFERABLE_TYPED_DATA": 81, "ARRAY": 86, "IMMUTABLE_ARRAY": 87, "GROWABLE_OBJECT_ARRAY": 88, "STRING": 89, "ONE_BYTE_STRING": 90, "TWO_BYTE_STRING": 91, "NUM_PREDEFINED_CIDS": 158}},
|
||||
"2.18.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_SHIFT1", "header_fields": 5, "typed_data_internal_base": 111, "typed_data_internal_limit": 153, "typed_data_stride": 3, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "CODE": 17, "OBJECT_POOL": 21, "PC_DESCRIPTORS": 22, "CODE_SOURCE_MAP": 23, "COMPRESSED_STACK_MAPS": 24, "EXCEPTION_HANDLERS": 26, "CONTEXT": 27, "CONTEXT_SCOPE": 28, "SENTINEL": 29, "SINGLE_TARGET_CACHE": 30, "UNLINKED_CALL": 31, "MONOMORPHIC_SMIABLE_CALL": 32, "CALL_SITE_DATA": 33, "ICDATA": 34, "MEGAMORPHIC_CACHE": 35, "SUBTYPE_TEST_CACHE": 36, "LOADING_UNIT": 37, "LANGUAGE_ERROR": 40, "UNHANDLED_EXCEPTION": 41, "INSTANCE": 43, "LIBRARY_PREFIX": 44, "TYPE_ARGUMENTS": 45, "TYPE": 47, "FUNCTION_TYPE": 52, "TYPE_REF": 53, "TYPE_PARAMETER": 54, "CLOSURE": 55, "MINT": 59, "DOUBLE": 60, "FLOAT32X4": 62, "INT32X4": 63, "FLOAT64X2": 64, "TYPED_DATA": 66, "EXTERNAL_TYPED_DATA": 67, "TYPED_DATA_VIEW": 68, "CAPABILITY": 71, "RECEIVE_PORT": 72, "SEND_PORT": 73, "STACK_TRACE": 74, "SUSPEND_STATE": 75, "REGEXP": 76, "WEAK_PROPERTY": 77, "WEAK_REFERENCE": 78, "FUTURE_OR": 80, "USER_TAG": 81, "TRANSFERABLE_TYPED_DATA": 82, "MAP": 83, "CONST_MAP": 84, "SET": 85, "CONST_SET": 86, "ARRAY": 87, "IMMUTABLE_ARRAY": 88, "GROWABLE_OBJECT_ARRAY": 89, "STRING": 90, "ONE_BYTE_STRING": 91, "TWO_BYTE_STRING": 92, "NUM_PREDEFINED_CIDS": 159}},
|
||||
"2.18.2": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "OBJECT_HEADER", "header_fields": 5, "typed_data_internal_base": 112, "typed_data_internal_limit": 168, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 23, "PC_DESCRIPTORS": 24, "CODE_SOURCE_MAP": 25, "COMPRESSED_STACK_MAPS": 26, "EXCEPTION_HANDLERS": 28, "CONTEXT": 29, "CONTEXT_SCOPE": 30, "SENTINEL": 31, "SINGLE_TARGET_CACHE": 32, "UNLINKED_CALL": 35, "MONOMORPHIC_SMIABLE_CALL": 33, "CALL_SITE_DATA": 34, "ICDATA": 36, "MEGAMORPHIC_CACHE": 37, "SUBTYPE_TEST_CACHE": 38, "LOADING_UNIT": 39, "LANGUAGE_ERROR": 42, "UNHANDLED_EXCEPTION": 43, "INSTANCE": 45, "LIBRARY_PREFIX": 46, "TYPE_ARGUMENTS": 47, "TYPE": 49, "FUNCTION_TYPE": 50, "RECORD_TYPE": 51, "TYPE_PARAMETER": 52, "CLOSURE": 57, "MINT": 61, "DOUBLE": 62, "FLOAT32X4": 64, "INT32X4": 65, "FLOAT64X2": 66, "RECORD": 67, "TYPED_DATA": 69, "EXTERNAL_TYPED_DATA": 70, "TYPED_DATA_VIEW": 71, "CAPABILITY": 74, "RECEIVE_PORT": 75, "SEND_PORT": 76, "STACK_TRACE": 77, "SUSPEND_STATE": 78, "REGEXP": 79, "WEAK_PROPERTY": 80, "WEAK_REFERENCE": 81, "FUTURE_OR": 83, "USER_TAG": 84, "TRANSFERABLE_TYPED_DATA": 85, "MAP": 86, "CONST_MAP": 87, "SET": 88, "CONST_SET": 89, "ARRAY": 90, "IMMUTABLE_ARRAY": 91, "GROWABLE_OBJECT_ARRAY": 92, "STRING": 93, "ONE_BYTE_STRING": 94, "TWO_BYTE_STRING": 95, "NUM_PREDEFINED_CIDS": 175}, "note": "Uses fallback CIDs from 3.9.2 for the in-tree Flutter engine snapshot; not standalone Dart SDK 2.18.2 CIDs."},
|
||||
"2.18.3": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_SHIFT1", "header_fields": 5, "typed_data_internal_base": 111, "typed_data_internal_limit": 153, "typed_data_stride": 3, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "CODE": 17, "OBJECT_POOL": 21, "PC_DESCRIPTORS": 22, "CODE_SOURCE_MAP": 23, "COMPRESSED_STACK_MAPS": 24, "EXCEPTION_HANDLERS": 26, "CONTEXT": 27, "CONTEXT_SCOPE": 28, "SENTINEL": 29, "SINGLE_TARGET_CACHE": 30, "UNLINKED_CALL": 31, "MONOMORPHIC_SMIABLE_CALL": 32, "CALL_SITE_DATA": 33, "ICDATA": 34, "MEGAMORPHIC_CACHE": 35, "SUBTYPE_TEST_CACHE": 36, "LOADING_UNIT": 37, "LANGUAGE_ERROR": 40, "UNHANDLED_EXCEPTION": 41, "INSTANCE": 43, "LIBRARY_PREFIX": 44, "TYPE_ARGUMENTS": 45, "TYPE": 47, "FUNCTION_TYPE": 52, "TYPE_REF": 53, "TYPE_PARAMETER": 54, "CLOSURE": 55, "MINT": 59, "DOUBLE": 60, "FLOAT32X4": 62, "INT32X4": 63, "FLOAT64X2": 64, "TYPED_DATA": 66, "EXTERNAL_TYPED_DATA": 67, "TYPED_DATA_VIEW": 68, "CAPABILITY": 71, "RECEIVE_PORT": 72, "SEND_PORT": 73, "STACK_TRACE": 74, "SUSPEND_STATE": 75, "REGEXP": 76, "WEAK_PROPERTY": 77, "WEAK_REFERENCE": 78, "FUTURE_OR": 80, "USER_TAG": 81, "TRANSFERABLE_TYPED_DATA": 82, "MAP": 83, "CONST_MAP": 84, "SET": 85, "CONST_SET": 86, "ARRAY": 87, "IMMUTABLE_ARRAY": 88, "GROWABLE_OBJECT_ARRAY": 89, "STRING": 90, "ONE_BYTE_STRING": 91, "TWO_BYTE_STRING": 92, "NUM_PREDEFINED_CIDS": 159}},
|
||||
"2.19.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_SHIFT1", "header_fields": 5, "typed_data_internal_base": 113, "typed_data_internal_limit": 169, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "CODE": 17, "OBJECT_POOL": 21, "PC_DESCRIPTORS": 22, "CODE_SOURCE_MAP": 23, "COMPRESSED_STACK_MAPS": 24, "EXCEPTION_HANDLERS": 26, "CONTEXT": 27, "CONTEXT_SCOPE": 28, "SENTINEL": 29, "SINGLE_TARGET_CACHE": 30, "UNLINKED_CALL": 31, "MONOMORPHIC_SMIABLE_CALL": 32, "CALL_SITE_DATA": 33, "ICDATA": 34, "MEGAMORPHIC_CACHE": 35, "SUBTYPE_TEST_CACHE": 36, "LOADING_UNIT": 37, "LANGUAGE_ERROR": 40, "UNHANDLED_EXCEPTION": 41, "INSTANCE": 43, "LIBRARY_PREFIX": 44, "TYPE_ARGUMENTS": 45, "TYPE": 47, "FUNCTION_TYPE": 48, "RECORD_TYPE": 49, "TYPE_REF": 50, "TYPE_PARAMETER": 51, "CLOSURE": 56, "MINT": 60, "DOUBLE": 61, "FLOAT32X4": 63, "INT32X4": 64, "FLOAT64X2": 65, "RECORD": 66, "TYPED_DATA": 68, "EXTERNAL_TYPED_DATA": 69, "TYPED_DATA_VIEW": 70, "CAPABILITY": 73, "RECEIVE_PORT": 74, "SEND_PORT": 75, "STACK_TRACE": 76, "SUSPEND_STATE": 77, "REGEXP": 78, "WEAK_PROPERTY": 79, "WEAK_REFERENCE": 80, "FUTURE_OR": 82, "USER_TAG": 83, "TRANSFERABLE_TYPED_DATA": 84, "MAP": 85, "CONST_MAP": 86, "SET": 87, "CONST_SET": 88, "ARRAY": 89, "IMMUTABLE_ARRAY": 90, "GROWABLE_OBJECT_ARRAY": 91, "STRING": 92, "ONE_BYTE_STRING": 93, "TWO_BYTE_STRING": 94, "NUM_PREDEFINED_CIDS": 176}},
|
||||
"3.0.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_SHIFT1", "header_fields": 5, "typed_data_internal_base": 114, "typed_data_internal_limit": 170, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 22, "PC_DESCRIPTORS": 23, "CODE_SOURCE_MAP": 24, "COMPRESSED_STACK_MAPS": 25, "EXCEPTION_HANDLERS": 27, "CONTEXT": 28, "CONTEXT_SCOPE": 29, "SENTINEL": 30, "SINGLE_TARGET_CACHE": 31, "UNLINKED_CALL": 32, "MONOMORPHIC_SMIABLE_CALL": 33, "CALL_SITE_DATA": 34, "ICDATA": 35, "MEGAMORPHIC_CACHE": 36, "SUBTYPE_TEST_CACHE": 37, "LOADING_UNIT": 38, "LANGUAGE_ERROR": 41, "UNHANDLED_EXCEPTION": 42, "INSTANCE": 44, "LIBRARY_PREFIX": 45, "TYPE_ARGUMENTS": 46, "TYPE": 48, "FUNCTION_TYPE": 49, "RECORD_TYPE": 50, "TYPE_REF": 51, "TYPE_PARAMETER": 52, "CLOSURE": 57, "MINT": 61, "DOUBLE": 62, "FLOAT32X4": 64, "INT32X4": 65, "FLOAT64X2": 66, "RECORD": 67, "TYPED_DATA": 69, "EXTERNAL_TYPED_DATA": 70, "TYPED_DATA_VIEW": 71, "CAPABILITY": 74, "RECEIVE_PORT": 75, "SEND_PORT": 76, "STACK_TRACE": 77, "SUSPEND_STATE": 78, "REGEXP": 79, "WEAK_PROPERTY": 80, "WEAK_REFERENCE": 81, "FUTURE_OR": 83, "USER_TAG": 84, "TRANSFERABLE_TYPED_DATA": 85, "MAP": 86, "CONST_MAP": 87, "SET": 88, "CONST_SET": 89, "ARRAY": 90, "IMMUTABLE_ARRAY": 91, "GROWABLE_OBJECT_ARRAY": 92, "STRING": 93, "ONE_BYTE_STRING": 94, "TWO_BYTE_STRING": 95, "NUM_PREDEFINED_CIDS": 177}},
|
||||
"3.1.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_SHIFT1", "header_fields": 5, "typed_data_internal_base": 113, "typed_data_internal_limit": 169, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 22, "PC_DESCRIPTORS": 23, "CODE_SOURCE_MAP": 24, "COMPRESSED_STACK_MAPS": 25, "EXCEPTION_HANDLERS": 27, "CONTEXT": 28, "CONTEXT_SCOPE": 29, "SENTINEL": 30, "SINGLE_TARGET_CACHE": 31, "UNLINKED_CALL": 32, "MONOMORPHIC_SMIABLE_CALL": 33, "CALL_SITE_DATA": 34, "ICDATA": 35, "MEGAMORPHIC_CACHE": 36, "SUBTYPE_TEST_CACHE": 37, "LOADING_UNIT": 38, "LANGUAGE_ERROR": 41, "UNHANDLED_EXCEPTION": 42, "INSTANCE": 44, "LIBRARY_PREFIX": 45, "TYPE_ARGUMENTS": 46, "TYPE": 48, "FUNCTION_TYPE": 49, "RECORD_TYPE": 50, "TYPE_PARAMETER": 51, "CLOSURE": 56, "MINT": 60, "DOUBLE": 61, "FLOAT32X4": 63, "INT32X4": 64, "FLOAT64X2": 65, "RECORD": 66, "TYPED_DATA": 68, "EXTERNAL_TYPED_DATA": 69, "TYPED_DATA_VIEW": 70, "CAPABILITY": 73, "RECEIVE_PORT": 74, "SEND_PORT": 75, "STACK_TRACE": 76, "SUSPEND_STATE": 77, "REGEXP": 78, "WEAK_PROPERTY": 79, "WEAK_REFERENCE": 80, "FUTURE_OR": 82, "USER_TAG": 83, "TRANSFERABLE_TYPED_DATA": 84, "MAP": 85, "CONST_MAP": 86, "SET": 87, "CONST_SET": 88, "ARRAY": 89, "IMMUTABLE_ARRAY": 90, "GROWABLE_OBJECT_ARRAY": 91, "STRING": 92, "ONE_BYTE_STRING": 93, "TWO_BYTE_STRING": 94, "NUM_PREDEFINED_CIDS": 176}},
|
||||
"3.2.0": {"cws": 8, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_SHIFT1", "header_fields": 5, "typed_data_internal_base": 113, "typed_data_internal_limit": 169, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 22, "PC_DESCRIPTORS": 23, "CODE_SOURCE_MAP": 24, "COMPRESSED_STACK_MAPS": 25, "EXCEPTION_HANDLERS": 27, "CONTEXT": 28, "CONTEXT_SCOPE": 29, "SENTINEL": 30, "SINGLE_TARGET_CACHE": 31, "UNLINKED_CALL": 32, "MONOMORPHIC_SMIABLE_CALL": 33, "CALL_SITE_DATA": 34, "ICDATA": 35, "MEGAMORPHIC_CACHE": 36, "SUBTYPE_TEST_CACHE": 37, "LOADING_UNIT": 38, "LANGUAGE_ERROR": 41, "UNHANDLED_EXCEPTION": 42, "INSTANCE": 44, "LIBRARY_PREFIX": 45, "TYPE_ARGUMENTS": 46, "TYPE": 48, "FUNCTION_TYPE": 49, "RECORD_TYPE": 50, "TYPE_PARAMETER": 51, "CLOSURE": 56, "MINT": 60, "DOUBLE": 61, "FLOAT32X4": 63, "INT32X4": 64, "FLOAT64X2": 65, "RECORD": 66, "TYPED_DATA": 68, "EXTERNAL_TYPED_DATA": 69, "TYPED_DATA_VIEW": 70, "CAPABILITY": 73, "RECEIVE_PORT": 74, "SEND_PORT": 75, "STACK_TRACE": 76, "SUSPEND_STATE": 77, "REGEXP": 78, "WEAK_PROPERTY": 79, "WEAK_REFERENCE": 80, "FUTURE_OR": 82, "USER_TAG": 83, "TRANSFERABLE_TYPED_DATA": 84, "MAP": 85, "CONST_MAP": 86, "SET": 87, "CONST_SET": 88, "ARRAY": 89, "IMMUTABLE_ARRAY": 90, "GROWABLE_OBJECT_ARRAY": 91, "STRING": 92, "ONE_BYTE_STRING": 93, "TWO_BYTE_STRING": 94, "NUM_PREDEFINED_CIDS": 176}},
|
||||
"3.3.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "CID_SHIFT1", "header_fields": 5, "typed_data_internal_base": 113, "typed_data_internal_limit": 169, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 22, "PC_DESCRIPTORS": 23, "CODE_SOURCE_MAP": 24, "COMPRESSED_STACK_MAPS": 25, "EXCEPTION_HANDLERS": 27, "CONTEXT": 28, "CONTEXT_SCOPE": 29, "SENTINEL": 30, "SINGLE_TARGET_CACHE": 31, "UNLINKED_CALL": 32, "MONOMORPHIC_SMIABLE_CALL": 33, "CALL_SITE_DATA": 34, "ICDATA": 35, "MEGAMORPHIC_CACHE": 36, "SUBTYPE_TEST_CACHE": 37, "LOADING_UNIT": 38, "LANGUAGE_ERROR": 41, "UNHANDLED_EXCEPTION": 42, "INSTANCE": 44, "LIBRARY_PREFIX": 45, "TYPE_ARGUMENTS": 46, "TYPE": 48, "FUNCTION_TYPE": 49, "RECORD_TYPE": 50, "TYPE_PARAMETER": 51, "CLOSURE": 56, "MINT": 60, "DOUBLE": 61, "FLOAT32X4": 63, "INT32X4": 64, "FLOAT64X2": 65, "RECORD": 66, "TYPED_DATA": 68, "EXTERNAL_TYPED_DATA": 69, "TYPED_DATA_VIEW": 70, "CAPABILITY": 73, "RECEIVE_PORT": 74, "SEND_PORT": 75, "STACK_TRACE": 76, "SUSPEND_STATE": 77, "REGEXP": 78, "WEAK_PROPERTY": 79, "WEAK_REFERENCE": 80, "FUTURE_OR": 82, "USER_TAG": 83, "TRANSFERABLE_TYPED_DATA": 84, "MAP": 85, "CONST_MAP": 86, "SET": 87, "CONST_SET": 88, "ARRAY": 89, "IMMUTABLE_ARRAY": 90, "GROWABLE_OBJECT_ARRAY": 91, "STRING": 92, "ONE_BYTE_STRING": 93, "TWO_BYTE_STRING": 94, "NUM_PREDEFINED_CIDS": 176}},
|
||||
"3.4.0": {"cws": 8, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "OBJECT_HEADER", "header_fields": 5, "typed_data_internal_base": 111, "typed_data_internal_limit": 167, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 22, "PC_DESCRIPTORS": 23, "CODE_SOURCE_MAP": 24, "COMPRESSED_STACK_MAPS": 25, "EXCEPTION_HANDLERS": 27, "CONTEXT": 28, "CONTEXT_SCOPE": 29, "SENTINEL": 30, "SINGLE_TARGET_CACHE": 31, "UNLINKED_CALL": 32, "MONOMORPHIC_SMIABLE_CALL": 33, "CALL_SITE_DATA": 34, "ICDATA": 35, "MEGAMORPHIC_CACHE": 36, "SUBTYPE_TEST_CACHE": 37, "LOADING_UNIT": 38, "LANGUAGE_ERROR": 41, "UNHANDLED_EXCEPTION": 42, "INSTANCE": 44, "LIBRARY_PREFIX": 45, "TYPE_ARGUMENTS": 46, "TYPE": 48, "FUNCTION_TYPE": 49, "RECORD_TYPE": 50, "TYPE_PARAMETER": 51, "CLOSURE": 56, "MINT": 60, "DOUBLE": 61, "FLOAT32X4": 63, "INT32X4": 64, "FLOAT64X2": 65, "RECORD": 66, "TYPED_DATA": 68, "EXTERNAL_TYPED_DATA": 69, "TYPED_DATA_VIEW": 70, "CAPABILITY": 73, "RECEIVE_PORT": 74, "SEND_PORT": 75, "STACK_TRACE": 76, "SUSPEND_STATE": 77, "REGEXP": 78, "WEAK_PROPERTY": 79, "WEAK_REFERENCE": 80, "FUTURE_OR": 82, "USER_TAG": 83, "TRANSFERABLE_TYPED_DATA": 84, "MAP": 85, "CONST_MAP": 86, "SET": 87, "CONST_SET": 88, "ARRAY": 89, "IMMUTABLE_ARRAY": 90, "GROWABLE_OBJECT_ARRAY": 91, "STRING": 92, "ONE_BYTE_STRING": 93, "TWO_BYTE_STRING": 94, "NUM_PREDEFINED_CIDS": 174}},
|
||||
"3.5.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "OBJECT_HEADER", "header_fields": 5, "typed_data_internal_base": 111, "typed_data_internal_limit": 167, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 22, "PC_DESCRIPTORS": 23, "CODE_SOURCE_MAP": 24, "COMPRESSED_STACK_MAPS": 25, "EXCEPTION_HANDLERS": 27, "CONTEXT": 28, "CONTEXT_SCOPE": 29, "SENTINEL": 30, "SINGLE_TARGET_CACHE": 31, "UNLINKED_CALL": 32, "MONOMORPHIC_SMIABLE_CALL": 33, "CALL_SITE_DATA": 34, "ICDATA": 35, "MEGAMORPHIC_CACHE": 36, "SUBTYPE_TEST_CACHE": 37, "LOADING_UNIT": 38, "LANGUAGE_ERROR": 41, "UNHANDLED_EXCEPTION": 42, "INSTANCE": 44, "LIBRARY_PREFIX": 45, "TYPE_ARGUMENTS": 46, "TYPE": 48, "FUNCTION_TYPE": 49, "RECORD_TYPE": 50, "TYPE_PARAMETER": 51, "CLOSURE": 56, "MINT": 60, "DOUBLE": 61, "FLOAT32X4": 63, "INT32X4": 64, "FLOAT64X2": 65, "RECORD": 66, "TYPED_DATA": 68, "EXTERNAL_TYPED_DATA": 69, "TYPED_DATA_VIEW": 70, "CAPABILITY": 73, "RECEIVE_PORT": 74, "SEND_PORT": 75, "STACK_TRACE": 76, "SUSPEND_STATE": 77, "REGEXP": 78, "WEAK_PROPERTY": 79, "WEAK_REFERENCE": 80, "FUTURE_OR": 82, "USER_TAG": 83, "TRANSFERABLE_TYPED_DATA": 84, "MAP": 85, "CONST_MAP": 86, "SET": 87, "CONST_SET": 88, "ARRAY": 89, "IMMUTABLE_ARRAY": 90, "GROWABLE_OBJECT_ARRAY": 91, "STRING": 92, "ONE_BYTE_STRING": 93, "TWO_BYTE_STRING": 94, "NUM_PREDEFINED_CIDS": 174}},
|
||||
"3.6.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "OBJECT_HEADER", "header_fields": 5, "typed_data_internal_base": 112, "typed_data_internal_limit": 168, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 23, "PC_DESCRIPTORS": 24, "CODE_SOURCE_MAP": 25, "COMPRESSED_STACK_MAPS": 26, "EXCEPTION_HANDLERS": 28, "CONTEXT": 29, "CONTEXT_SCOPE": 30, "SENTINEL": 31, "SINGLE_TARGET_CACHE": 32, "UNLINKED_CALL": 33, "MONOMORPHIC_SMIABLE_CALL": 34, "CALL_SITE_DATA": 35, "ICDATA": 36, "MEGAMORPHIC_CACHE": 37, "SUBTYPE_TEST_CACHE": 38, "LOADING_UNIT": 39, "LANGUAGE_ERROR": 42, "UNHANDLED_EXCEPTION": 43, "INSTANCE": 45, "LIBRARY_PREFIX": 46, "TYPE_ARGUMENTS": 47, "TYPE": 49, "FUNCTION_TYPE": 50, "RECORD_TYPE": 51, "TYPE_PARAMETER": 52, "CLOSURE": 57, "MINT": 61, "DOUBLE": 62, "FLOAT32X4": 64, "INT32X4": 65, "FLOAT64X2": 66, "RECORD": 67, "TYPED_DATA": 69, "EXTERNAL_TYPED_DATA": 70, "TYPED_DATA_VIEW": 71, "CAPABILITY": 74, "RECEIVE_PORT": 75, "SEND_PORT": 76, "STACK_TRACE": 77, "SUSPEND_STATE": 78, "REGEXP": 79, "WEAK_PROPERTY": 80, "WEAK_REFERENCE": 81, "FUTURE_OR": 83, "USER_TAG": 84, "TRANSFERABLE_TYPED_DATA": 85, "MAP": 86, "CONST_MAP": 87, "SET": 88, "CONST_SET": 89, "ARRAY": 90, "IMMUTABLE_ARRAY": 91, "GROWABLE_OBJECT_ARRAY": 92, "STRING": 93, "ONE_BYTE_STRING": 94, "TWO_BYTE_STRING": 95, "NUM_PREDEFINED_CIDS": 175}},
|
||||
"3.7.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "OBJECT_HEADER", "header_fields": 5, "typed_data_internal_base": 112, "typed_data_internal_limit": 168, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 23, "PC_DESCRIPTORS": 24, "CODE_SOURCE_MAP": 25, "COMPRESSED_STACK_MAPS": 26, "EXCEPTION_HANDLERS": 28, "CONTEXT": 29, "CONTEXT_SCOPE": 30, "SENTINEL": 31, "SINGLE_TARGET_CACHE": 32, "UNLINKED_CALL": 33, "MONOMORPHIC_SMIABLE_CALL": 34, "CALL_SITE_DATA": 35, "ICDATA": 36, "MEGAMORPHIC_CACHE": 37, "SUBTYPE_TEST_CACHE": 38, "LOADING_UNIT": 39, "LANGUAGE_ERROR": 42, "UNHANDLED_EXCEPTION": 43, "INSTANCE": 45, "LIBRARY_PREFIX": 46, "TYPE_ARGUMENTS": 47, "TYPE": 49, "FUNCTION_TYPE": 50, "RECORD_TYPE": 51, "TYPE_PARAMETER": 52, "CLOSURE": 57, "MINT": 61, "DOUBLE": 62, "FLOAT32X4": 64, "INT32X4": 65, "FLOAT64X2": 66, "RECORD": 67, "TYPED_DATA": 69, "EXTERNAL_TYPED_DATA": 70, "TYPED_DATA_VIEW": 71, "CAPABILITY": 74, "RECEIVE_PORT": 75, "SEND_PORT": 76, "STACK_TRACE": 77, "SUSPEND_STATE": 78, "REGEXP": 79, "WEAK_PROPERTY": 80, "WEAK_REFERENCE": 81, "FUTURE_OR": 83, "USER_TAG": 84, "TRANSFERABLE_TYPED_DATA": 85, "MAP": 86, "CONST_MAP": 87, "SET": 88, "CONST_SET": 89, "ARRAY": 90, "IMMUTABLE_ARRAY": 91, "GROWABLE_OBJECT_ARRAY": 92, "STRING": 93, "ONE_BYTE_STRING": 94, "TWO_BYTE_STRING": 95, "NUM_PREDEFINED_CIDS": 175}},
|
||||
"3.8.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "OBJECT_HEADER", "header_fields": 5, "typed_data_internal_base": 112, "typed_data_internal_limit": 168, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 23, "PC_DESCRIPTORS": 24, "CODE_SOURCE_MAP": 25, "COMPRESSED_STACK_MAPS": 26, "EXCEPTION_HANDLERS": 28, "CONTEXT": 29, "CONTEXT_SCOPE": 30, "SENTINEL": 31, "SINGLE_TARGET_CACHE": 32, "UNLINKED_CALL": 33, "MONOMORPHIC_SMIABLE_CALL": 34, "CALL_SITE_DATA": 35, "ICDATA": 36, "MEGAMORPHIC_CACHE": 37, "SUBTYPE_TEST_CACHE": 38, "LOADING_UNIT": 39, "LANGUAGE_ERROR": 42, "UNHANDLED_EXCEPTION": 43, "INSTANCE": 45, "LIBRARY_PREFIX": 46, "TYPE_ARGUMENTS": 47, "TYPE": 49, "FUNCTION_TYPE": 50, "RECORD_TYPE": 51, "TYPE_PARAMETER": 52, "CLOSURE": 57, "MINT": 61, "DOUBLE": 62, "FLOAT32X4": 64, "INT32X4": 65, "FLOAT64X2": 66, "RECORD": 67, "TYPED_DATA": 69, "EXTERNAL_TYPED_DATA": 70, "TYPED_DATA_VIEW": 71, "CAPABILITY": 74, "RECEIVE_PORT": 75, "SEND_PORT": 76, "STACK_TRACE": 77, "SUSPEND_STATE": 78, "REGEXP": 79, "WEAK_PROPERTY": 80, "WEAK_REFERENCE": 81, "FUTURE_OR": 83, "USER_TAG": 84, "TRANSFERABLE_TYPED_DATA": 85, "MAP": 86, "CONST_MAP": 87, "SET": 88, "CONST_SET": 89, "ARRAY": 90, "IMMUTABLE_ARRAY": 91, "GROWABLE_OBJECT_ARRAY": 92, "STRING": 93, "ONE_BYTE_STRING": 94, "TWO_BYTE_STRING": 95, "NUM_PREDEFINED_CIDS": 175}},
|
||||
"3.9.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "OBJECT_HEADER", "header_fields": 5, "typed_data_internal_base": 112, "typed_data_internal_limit": 168, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 23, "PC_DESCRIPTORS": 24, "CODE_SOURCE_MAP": 25, "COMPRESSED_STACK_MAPS": 26, "EXCEPTION_HANDLERS": 28, "CONTEXT": 29, "CONTEXT_SCOPE": 30, "SENTINEL": 31, "SINGLE_TARGET_CACHE": 32, "UNLINKED_CALL": 35, "MONOMORPHIC_SMIABLE_CALL": 33, "CALL_SITE_DATA": 34, "ICDATA": 36, "MEGAMORPHIC_CACHE": 37, "SUBTYPE_TEST_CACHE": 38, "LOADING_UNIT": 39, "LANGUAGE_ERROR": 42, "UNHANDLED_EXCEPTION": 43, "INSTANCE": 45, "LIBRARY_PREFIX": 46, "TYPE_ARGUMENTS": 47, "TYPE": 49, "FUNCTION_TYPE": 50, "RECORD_TYPE": 51, "TYPE_PARAMETER": 52, "CLOSURE": 57, "MINT": 61, "DOUBLE": 62, "FLOAT32X4": 64, "INT32X4": 65, "FLOAT64X2": 66, "RECORD": 67, "TYPED_DATA": 69, "EXTERNAL_TYPED_DATA": 70, "TYPED_DATA_VIEW": 71, "CAPABILITY": 74, "RECEIVE_PORT": 75, "SEND_PORT": 76, "STACK_TRACE": 77, "SUSPEND_STATE": 78, "REGEXP": 79, "WEAK_PROPERTY": 80, "WEAK_REFERENCE": 81, "FUTURE_OR": 83, "USER_TAG": 84, "TRANSFERABLE_TYPED_DATA": 85, "MAP": 86, "CONST_MAP": 87, "SET": 88, "CONST_SET": 89, "ARRAY": 90, "IMMUTABLE_ARRAY": 91, "GROWABLE_OBJECT_ARRAY": 92, "STRING": 93, "ONE_BYTE_STRING": 94, "TWO_BYTE_STRING": 95, "NUM_PREDEFINED_CIDS": 175}},
|
||||
"3.10.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "OBJECT_HEADER", "header_fields": 5, "typed_data_internal_base": 112, "typed_data_internal_limit": 168, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 23, "PC_DESCRIPTORS": 24, "CODE_SOURCE_MAP": 25, "COMPRESSED_STACK_MAPS": 26, "EXCEPTION_HANDLERS": 28, "CONTEXT": 29, "CONTEXT_SCOPE": 30, "SENTINEL": 31, "SINGLE_TARGET_CACHE": 32, "UNLINKED_CALL": 35, "MONOMORPHIC_SMIABLE_CALL": 33, "CALL_SITE_DATA": 34, "ICDATA": 36, "MEGAMORPHIC_CACHE": 37, "SUBTYPE_TEST_CACHE": 38, "LOADING_UNIT": 39, "LANGUAGE_ERROR": 42, "UNHANDLED_EXCEPTION": 43, "INSTANCE": 45, "LIBRARY_PREFIX": 46, "TYPE_ARGUMENTS": 47, "TYPE": 49, "FUNCTION_TYPE": 50, "RECORD_TYPE": 51, "TYPE_PARAMETER": 52, "CLOSURE": 57, "MINT": 61, "DOUBLE": 62, "FLOAT32X4": 64, "INT32X4": 65, "FLOAT64X2": 66, "RECORD": 67, "TYPED_DATA": 69, "EXTERNAL_TYPED_DATA": 70, "TYPED_DATA_VIEW": 71, "CAPABILITY": 74, "RECEIVE_PORT": 75, "SEND_PORT": 76, "STACK_TRACE": 77, "SUSPEND_STATE": 78, "REGEXP": 79, "WEAK_PROPERTY": 80, "WEAK_REFERENCE": 81, "FUTURE_OR": 83, "USER_TAG": 84, "TRANSFERABLE_TYPED_DATA": 85, "MAP": 86, "CONST_MAP": 87, "SET": 88, "CONST_SET": 89, "ARRAY": 90, "IMMUTABLE_ARRAY": 91, "GROWABLE_OBJECT_ARRAY": 92, "STRING": 93, "ONE_BYTE_STRING": 94, "TWO_BYTE_STRING": 95, "NUM_PREDEFINED_CIDS": 175}},
|
||||
"3.11.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "OBJECT_HEADER", "header_fields": 5, "typed_data_internal_base": 112, "typed_data_internal_limit": 168, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 23, "PC_DESCRIPTORS": 24, "CODE_SOURCE_MAP": 25, "COMPRESSED_STACK_MAPS": 26, "EXCEPTION_HANDLERS": 28, "CONTEXT": 29, "CONTEXT_SCOPE": 30, "SENTINEL": 31, "SINGLE_TARGET_CACHE": 32, "UNLINKED_CALL": 35, "MONOMORPHIC_SMIABLE_CALL": 33, "CALL_SITE_DATA": 34, "ICDATA": 36, "MEGAMORPHIC_CACHE": 37, "SUBTYPE_TEST_CACHE": 38, "LOADING_UNIT": 39, "LANGUAGE_ERROR": 42, "UNHANDLED_EXCEPTION": 43, "INSTANCE": 45, "LIBRARY_PREFIX": 46, "TYPE_ARGUMENTS": 47, "TYPE": 49, "FUNCTION_TYPE": 50, "RECORD_TYPE": 51, "TYPE_PARAMETER": 52, "CLOSURE": 57, "MINT": 61, "DOUBLE": 62, "FLOAT32X4": 64, "INT32X4": 65, "FLOAT64X2": 66, "RECORD": 67, "TYPED_DATA": 69, "EXTERNAL_TYPED_DATA": 70, "TYPED_DATA_VIEW": 71, "CAPABILITY": 74, "RECEIVE_PORT": 75, "SEND_PORT": 76, "STACK_TRACE": 77, "SUSPEND_STATE": 78, "REGEXP": 79, "WEAK_PROPERTY": 80, "WEAK_REFERENCE": 81, "FUTURE_OR": 83, "USER_TAG": 84, "TRANSFERABLE_TYPED_DATA": 85, "MAP": 86, "CONST_MAP": 87, "SET": 88, "CONST_SET": 89, "ARRAY": 90, "IMMUTABLE_ARRAY": 91, "GROWABLE_OBJECT_ARRAY": 92, "STRING": 93, "ONE_BYTE_STRING": 94, "TWO_BYTE_STRING": 95, "NUM_PREDEFINED_CIDS": 175}},
|
||||
"3.12.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "OBJECT_HEADER", "header_fields": 5, "typed_data_internal_base": 112, "typed_data_internal_limit": 168, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 23, "PC_DESCRIPTORS": 24, "CODE_SOURCE_MAP": 25, "COMPRESSED_STACK_MAPS": 26, "EXCEPTION_HANDLERS": 28, "CONTEXT": 29, "CONTEXT_SCOPE": 30, "SENTINEL": 31, "SINGLE_TARGET_CACHE": 32, "UNLINKED_CALL": 35, "MONOMORPHIC_SMIABLE_CALL": 33, "CALL_SITE_DATA": 34, "ICDATA": 36, "MEGAMORPHIC_CACHE": 37, "SUBTYPE_TEST_CACHE": 38, "LOADING_UNIT": 39, "LANGUAGE_ERROR": 42, "UNHANDLED_EXCEPTION": 43, "INSTANCE": 45, "LIBRARY_PREFIX": 46, "TYPE_ARGUMENTS": 47, "TYPE": 49, "FUNCTION_TYPE": 50, "RECORD_TYPE": 51, "TYPE_PARAMETER": 52, "CLOSURE": 57, "MINT": 61, "DOUBLE": 62, "FLOAT32X4": 64, "INT32X4": 65, "FLOAT64X2": 66, "RECORD": 67, "TYPED_DATA": 69, "EXTERNAL_TYPED_DATA": 70, "TYPED_DATA_VIEW": 71, "CAPABILITY": 74, "RECEIVE_PORT": 75, "SEND_PORT": 76, "STACK_TRACE": 77, "SUSPEND_STATE": 78, "REGEXP": 79, "WEAK_PROPERTY": 80, "WEAK_REFERENCE": 81, "FUTURE_OR": 83, "USER_TAG": 84, "TRANSFERABLE_TYPED_DATA": 85, "MAP": 86, "CONST_MAP": 87, "SET": 88, "CONST_SET": 89, "ARRAY": 90, "IMMUTABLE_ARRAY": 91, "GROWABLE_OBJECT_ARRAY": 92, "STRING": 93, "ONE_BYTE_STRING": 94, "TWO_BYTE_STRING": 95, "NUM_PREDEFINED_CIDS": 175}},
|
||||
"3.13.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "OBJECT_HEADER", "header_fields": 5, "typed_data_internal_base": 113, "typed_data_internal_limit": 169, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 23, "PC_DESCRIPTORS": 24, "CODE_SOURCE_MAP": 25, "COMPRESSED_STACK_MAPS": 26, "EXCEPTION_HANDLERS": 28, "CONTEXT": 29, "CONTEXT_SCOPE": 30, "SENTINEL": 31, "SINGLE_TARGET_CACHE": 32, "UNLINKED_CALL": 35, "MONOMORPHIC_SMIABLE_CALL": 33, "CALL_SITE_DATA": 34, "ICDATA": 36, "MEGAMORPHIC_CACHE": 37, "SUBTYPE_TEST_CACHE": 38, "LOADING_UNIT": 39, "LANGUAGE_ERROR": 42, "UNHANDLED_EXCEPTION": 43, "INSTANCE": 45, "LIBRARY_PREFIX": 46, "TYPE_ARGUMENTS": 47, "TYPE": 49, "FUNCTION_TYPE": 50, "RECORD_TYPE": 51, "TYPE_PARAMETER": 52, "CLOSURE": 57, "MINT": 61, "DOUBLE": 62, "FLOAT32X4": 64, "INT32X4": 65, "FLOAT64X2": 66, "RECORD": 67, "TYPED_DATA": 69, "EXTERNAL_TYPED_DATA": 70, "TYPED_DATA_VIEW": 71, "CAPABILITY": 74, "RECEIVE_PORT": 75, "SEND_PORT": 76, "STACK_TRACE": 77, "SUSPEND_STATE": 78, "REGEXP": 79, "WEAK_PROPERTY": 80, "WEAK_REFERENCE": 81, "FUTURE_OR": 83, "USER_TAG": 84, "TRANSFERABLE_TYPED_DATA": 85, "MAP": 86, "CONST_MAP": 87, "SET": 88, "CONST_SET": 89, "ARRAY": 90, "IMMUTABLE_ARRAY": 91, "GROWABLE_OBJECT_ARRAY": 92, "STRING": 93, "ONE_BYTE_STRING": 94, "TWO_BYTE_STRING": 95, "NUM_PREDEFINED_CIDS": 176}},
|
||||
"3.14.0": {"cws": 4, "heap_object_tag": 1, "max_alignment": 16, "it_cap": 20000, "tag_style": "OBJECT_HEADER", "header_fields": 5, "typed_data_internal_base": 113, "typed_data_internal_limit": 169, "typed_data_stride": 4, "cids": {"ILLEGAL": 0, "NATIVE_POINTER": 1, "CLASS": 5, "PATCH_CLASS": 6, "FUNCTION": 7, "TYPE_PARAMETERS": 8, "CLOSURE_DATA": 9, "FFI_TRAMPOLINE_DATA": 10, "FIELD": 11, "SCRIPT": 12, "LIBRARY": 13, "NAMESPACE": 14, "KERNEL_PROGRAM_INFO": 15, "WEAK_SERIALIZATION_REFERENCE": 16, "WEAK_ARRAY": 17, "CODE": 18, "OBJECT_POOL": 23, "PC_DESCRIPTORS": 24, "CODE_SOURCE_MAP": 25, "COMPRESSED_STACK_MAPS": 26, "EXCEPTION_HANDLERS": 28, "CONTEXT": 29, "CONTEXT_SCOPE": 30, "SENTINEL": 31, "SINGLE_TARGET_CACHE": 32, "UNLINKED_CALL": 35, "MONOMORPHIC_SMIABLE_CALL": 33, "CALL_SITE_DATA": 34, "ICDATA": 36, "MEGAMORPHIC_CACHE": 37, "SUBTYPE_TEST_CACHE": 38, "LOADING_UNIT": 39, "LANGUAGE_ERROR": 42, "UNHANDLED_EXCEPTION": 43, "INSTANCE": 45, "LIBRARY_PREFIX": 46, "TYPE_ARGUMENTS": 47, "TYPE": 49, "FUNCTION_TYPE": 50, "RECORD_TYPE": 51, "TYPE_PARAMETER": 52, "CLOSURE": 57, "MINT": 61, "DOUBLE": 62, "FLOAT32X4": 64, "INT32X4": 65, "FLOAT64X2": 66, "RECORD": 67, "TYPED_DATA": 69, "EXTERNAL_TYPED_DATA": 70, "TYPED_DATA_VIEW": 71, "CAPABILITY": 74, "RECEIVE_PORT": 75, "SEND_PORT": 76, "STACK_TRACE": 77, "SUSPEND_STATE": 78, "REGEXP": 79, "WEAK_PROPERTY": 80, "WEAK_REFERENCE": 81, "FUTURE_OR": 83, "USER_TAG": 84, "TRANSFERABLE_TYPED_DATA": 85, "MAP": 86, "CONST_MAP": 87, "SET": 88, "CONST_SET": 89, "ARRAY": 90, "IMMUTABLE_ARRAY": 91, "GROWABLE_OBJECT_ARRAY": 92, "STRING": 93, "ONE_BYTE_STRING": 94, "TWO_BYTE_STRING": 95, "NUM_PREDEFINED_CIDS": 176}}
|
||||
},
|
||||
"hashes": {
|
||||
"f91b8b03bf7f30a5e983fd19b23d978d": {
|
||||
"hash": "f91b8b03bf7f30a5e983fd19b23d978d",
|
||||
"compressed_word_size": 4,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"notes": "Generic arm64 AOT fallback with source-backed Code/Function offsets; version still unknown",
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"8ee4ef7a67df9845fba331734198a953": {
|
||||
"hash": "8ee4ef7a67df9845fba331734198a953",
|
||||
"compressed_word_size": 8,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"e4a09dbf2bb120fe4674e0576617a0dc": {
|
||||
"hash": "e4a09dbf2bb120fe4674e0576617a0dc",
|
||||
"compressed_word_size": 8,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"34f6eec64e9371856eaaa278ccf56538": {
|
||||
"hash": "34f6eec64e9371856eaaa278ccf56538",
|
||||
"compressed_word_size": 8,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"7a5b240780941844bae88eca5dbaa7b8": {
|
||||
"hash": "7a5b240780941844bae88eca5dbaa7b8",
|
||||
"compressed_word_size": 8,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"9cf77f4405212c45daf608e1cd646852": {
|
||||
"hash": "9cf77f4405212c45daf608e1cd646852",
|
||||
"compressed_word_size": 8,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"659a72e41e3276e882709901c27de33d": {
|
||||
"hash": "659a72e41e3276e882709901c27de33d",
|
||||
"compressed_word_size": 8,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"f10776149bf76be288def3c2ca73bdc1": {
|
||||
"hash": "f10776149bf76be288def3c2ca73bdc1",
|
||||
"compressed_word_size": 8,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"24d9d411c2f90c8fbe8907f99e89d4b0": {
|
||||
"hash": "24d9d411c2f90c8fbe8907f99e89d4b0",
|
||||
"compressed_word_size": 8,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"d56742caf7b3b3f4bd2df93a9bbb5503": {
|
||||
"hash": "d56742caf7b3b3f4bd2df93a9bbb5503",
|
||||
"compressed_word_size": 8,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"3318fe66091c0ffbb64faec39976cb7d": {
|
||||
"hash": "3318fe66091c0ffbb64faec39976cb7d",
|
||||
"compressed_word_size": 8,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"adf563436d12ba0d50ea5beb7f3be1bb": {
|
||||
"hash": "adf563436d12ba0d50ea5beb7f3be1bb",
|
||||
"compressed_word_size": 8,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"1441d6b13b8623fa7fbf61433abebd31": {
|
||||
"hash": "1441d6b13b8623fa7fbf61433abebd31",
|
||||
"compressed_word_size": 4,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"a0cb0c928b23bc17a26e062b351dc44d": {
|
||||
"hash": "a0cb0c928b23bc17a26e062b351dc44d",
|
||||
"compressed_word_size": 4,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"ded6ef11c73fdc638d6ff6d3ad22a67b": {
|
||||
"hash": "ded6ef11c73fdc638d6ff6d3ad22a67b",
|
||||
"compressed_word_size": 4,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"b0e899ec5a90e4661501f0b69e9dd70f": {
|
||||
"hash": "b0e899ec5a90e4661501f0b69e9dd70f",
|
||||
"compressed_word_size": 4,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"b6d0a1f034d158b0d37b51d559379697": {
|
||||
"hash": "b6d0a1f034d158b0d37b51d559379697",
|
||||
"compressed_word_size": 4,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"8e50e448b241be23b9e990094f4dca39": {
|
||||
"hash": "8e50e448b241be23b9e990094f4dca39",
|
||||
"compressed_word_size": 4,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"6a9b5a03a7e784a4558b10c769f188d9": {
|
||||
"hash": "6a9b5a03a7e784a4558b10c769f188d9",
|
||||
"compressed_word_size": 4,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"adb4292f3ec25074ca70abcd2d5c7251": {
|
||||
"hash": "adb4292f3ec25074ca70abcd2d5c7251",
|
||||
"compressed_word_size": 4,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"501ef5cbd64ca70b6b42672346af6a8a": {
|
||||
"hash": "501ef5cbd64ca70b6b42672346af6a8a",
|
||||
"compressed_word_size": 4,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"90b56a561f70cd55e972cb49b79b3d8b": {
|
||||
"hash": "90b56a561f70cd55e972cb49b79b3d8b",
|
||||
"compressed_word_size": 4,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"aa64af18e7d086041ac127cc4bc50c5e": {
|
||||
"hash": "aa64af18e7d086041ac127cc4bc50c5e",
|
||||
"compressed_word_size": 4,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"36b0375d284ee2af0d0fffc6e6e48fde": {
|
||||
"hash": "36b0375d284ee2af0d0fffc6e6e48fde",
|
||||
"compressed_word_size": 4,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"16ad76edd19b537bf6ea64fdd31977a7": {
|
||||
"hash": "16ad76edd19b537bf6ea64fdd31977a7",
|
||||
"compressed_word_size": 4,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"7dbbeeb8ef7b91338640dca3927636de": {
|
||||
"hash": "7dbbeeb8ef7b91338640dca3927636de",
|
||||
"compressed_word_size": 4,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"f71c76320d35b65f1164dbaa6d95fe09": {
|
||||
"hash": "f71c76320d35b65f1164dbaa6d95fe09",
|
||||
"compressed_word_size": 8,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"ee1eb666c76a5cb7746faf39d0b97547": {
|
||||
"hash": "ee1eb666c76a5cb7746faf39d0b97547",
|
||||
"compressed_word_size": 4,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"d20a1be77c3d3c41b2a5accaee1ce549": {
|
||||
"hash": "d20a1be77c3d3c41b2a5accaee1ce549",
|
||||
"compressed_word_size": 8,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"80a49c7111088100a233b2ae788e1f48": {
|
||||
"hash": "80a49c7111088100a233b2ae788e1f48",
|
||||
"compressed_word_size": 4,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"cda356e9bae476c70de33809fd92e009": {
|
||||
"hash": "cda356e9bae476c70de33809fd92e009",
|
||||
"compressed_word_size": 4,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"2858c2c0920495f00b9bce9edf6a8cd9": {
|
||||
"hash": "2858c2c0920495f00b9bce9edf6a8cd9",
|
||||
"compressed_word_size": 4,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"f956f595844a2f845a55707faaaa51e4": {
|
||||
"hash": "f956f595844a2f845a55707faaaa51e4",
|
||||
"compressed_word_size": 4,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"d91c0e6f35f0eb2e44124e8f42aa44a7": {
|
||||
"hash": "d91c0e6f35f0eb2e44124e8f42aa44a7",
|
||||
"compressed_word_size": 4,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"830f4f59e7969c70b595182826435c19": {
|
||||
"hash": "830f4f59e7969c70b595182826435c19",
|
||||
"compressed_word_size": 4,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"97ff04a728735e6b6b098bdf983faaba": {
|
||||
"hash": "97ff04a728735e6b6b098bdf983faaba",
|
||||
"compressed_word_size": 4,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
},
|
||||
"1ce86630892e2dca9a8543fdb8ed8e22": {
|
||||
"hash": "1ce86630892e2dca9a8543fdb8ed8e22",
|
||||
"compressed_word_size": 4,
|
||||
"heap_object_tag": 1,
|
||||
"max_alignment": 16,
|
||||
"it_cap": 20000,
|
||||
"code_entry_point_offsets": [
|
||||
8,
|
||||
24,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"code_owner_offsets": [
|
||||
56
|
||||
],
|
||||
"function_name_offsets": [
|
||||
24,
|
||||
8
|
||||
]
|
||||
}
|
||||
"8ee4ef7a67df9845fba331734198a953": "2.10.0",
|
||||
"d979fa911b038a8682e98cc8c8569261": "2.10.0",
|
||||
"5b97292b25f0a715613b7a28e0734f77": "2.12.0",
|
||||
"e036be9d5df68283e1a043e348fa7ff4": "2.12.0",
|
||||
"0c7f1aa25fc597a7b3b9a19ed390afa1": "2.13.0",
|
||||
"34f6eec64e9371856eaaa278ccf56538": "2.13.0",
|
||||
"7a5b240780941844bae88eca5dbaa7b8": "2.13.0",
|
||||
"e4a09dbf2bb120fe4674e0576617a0dc": "2.13.0",
|
||||
"659a72e41e3276e882709901c27de33d": "2.14.0",
|
||||
"9cf77f4405212c45daf608e1cd646852": "2.14.0",
|
||||
"c03fb294bfdaca2b44c552ad35e52d45": "2.14.0",
|
||||
"24d9d411c2f90c8fbe8907f99e89d4b0": "2.15.0",
|
||||
"7b96ef6dda75bdd1d70be8c2f7cb814e": "2.15.0",
|
||||
"f10776149bf76be288def3c2ca73bdc1": "2.15.0",
|
||||
"3318fe66091c0ffbb64faec39976cb7d": "2.16.0",
|
||||
"6093345773c65683f60c20c8e62cbe4a": "2.16.0",
|
||||
"adf563436d12ba0d50ea5beb7f3be1bb": "2.16.0",
|
||||
"d56742caf7b3b3f4bd2df93a9bbb5503": "2.16.0",
|
||||
"0eb277b50d6ce4069261c69150ce52c9": "2.17.0",
|
||||
"1441d6b13b8623fa7fbf61433abebd31": "2.17.6",
|
||||
"a0cb0c928b23bc17a26e062b351dc44d": "2.17.6",
|
||||
"ded6ef11c73fdc638d6ff6d3ad22a67b": "2.17.6",
|
||||
"6a9b5a03a7e784a4558b10c769f188d9": "2.18.0",
|
||||
"77bd0a83c49bfc555a44a0740f15003a": "2.18.0",
|
||||
"8e50e448b241be23b9e990094f4dca39": "2.18.0",
|
||||
"b0e899ec5a90e4661501f0b69e9dd70f": "2.18.0",
|
||||
"b6d0a1f034d158b0d37b51d559379697": "2.18.0",
|
||||
"f91b8b03bf7f30a5e983fd19b23d978d": "2.18.2",
|
||||
"287683014dfbd311f6400c655c97a26d": "2.18.6",
|
||||
"501ef5cbd64ca70b6b42672346af6a8a": "2.19.0",
|
||||
"adb4292f3ec25074ca70abcd2d5c7251": "2.19.0",
|
||||
"afcf461a16dfa5e88f88a2aa68764c99": "2.19.0",
|
||||
"e727fae79fb45aac639252a9453b785e": "2.19.1",
|
||||
"ce5d37cf26ea4faac543cd216f3b96d1": "3.0.0",
|
||||
"617d044d9faa87a98c29f8aad5d2f220": "3.0.3",
|
||||
"16ad76edd19b537bf6ea64fdd31977a7": "3.0.5",
|
||||
"36b0375d284ee2af0d0fffc6e6e48fde": "3.0.5",
|
||||
"90b56a561f70cd55e972cb49b79b3d8b": "3.0.5",
|
||||
"aa64af18e7d086041ac127cc4bc50c5e": "3.0.5",
|
||||
"5191d02bb1d36a9f8609aa7495894e76": "3.1.0",
|
||||
"7dbbeeb8ef7b91338640dca3927636de": "3.1.3",
|
||||
"9d6362266b78e4198f784da1a28fd82b": "3.2.0",
|
||||
"f71c76320d35b65f1164dbaa6d95fe09": "3.2.5",
|
||||
"77b867742ae1ace67fbbb9a9a0d0334e": "3.3.0",
|
||||
"ee1eb666c76a5cb7746faf39d0b97547": "3.3.0",
|
||||
"fe53c4fdba846005b70514aec3840296": "3.4.0",
|
||||
"d20a1be77c3d3c41b2a5accaee1ce549": "3.4.3",
|
||||
"71dcc635f2e5dca494525aa13ca9006b": "3.5.0",
|
||||
"80a49c7111088100a233b2ae788e1f48": "3.5.0",
|
||||
"cda356e9bae476c70de33809fd92e009": "3.5.0",
|
||||
"30c051cb9192f6d6812be3f0b87a6356": "3.6.0",
|
||||
"2858c2c0920495f00b9bce9edf6a8cd9": "3.6.2",
|
||||
"f956f595844a2f845a55707faaaa51e4": "3.6.2",
|
||||
"bae16a6858d8c0fe2c437b275eb807cd": "3.7.0",
|
||||
"d91c0e6f35f0eb2e44124e8f42aa44a7": "3.7.0",
|
||||
"fa07b8a2d866963d1117ecdcea9287c8": "3.8.0",
|
||||
"830f4f59e7969c70b595182826435c19": "3.8.1",
|
||||
"97ff04a728735e6b6b098bdf983faaba": "3.9.2",
|
||||
"1ce86630892e2dca9a8543fdb8ed8e22": "3.10.7",
|
||||
"78da37fed6bf1489361a312568249f3f": "3.11.5",
|
||||
"41be3daaabd524b8aa7423bc24584957": "3.12.0",
|
||||
"ace654289f5abc240509fc941453ebc5": "3.12.1",
|
||||
"0451907c2eaa8467e848c0067bfe8ed4": "3.13.0",
|
||||
"7a1ea3f6f5cf1089a7f6e55d7f20dbfd": "3.14.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Patch Dart AOT RawSecureSocket bad-certificate wrappers in an opened r2 file.
|
||||
*
|
||||
* Usage:
|
||||
* r2 -w -e bin.relocs.apply=true -i scripts/dart_cert_bypass.qjs libapp.so
|
||||
* r2 -w -e bin.relocs.apply=true -i scripts/dart_cert_bypass.qjs Runner.app/Frameworks/App.framework/App
|
||||
*
|
||||
* The opened file must be the Dart AOT image itself, not an APK/IPA/bundle
|
||||
* directory. Android usually uses libapp.so. iOS Flutter apps usually use
|
||||
* Frameworks/App.framework/App.
|
||||
*
|
||||
* This script is intentionally suffix-agnostic: it resolves the
|
||||
* method._RawSecureSocket_*._onBadCertificateWrapper_* flag produced by
|
||||
* r2flutter, translates the method vaddr to a raw file offset, and writes:
|
||||
*
|
||||
* add x0, x22, 0x20
|
||||
* ret
|
||||
*
|
||||
* Those bytes are AArch64-specific and return Dart true in the observed
|
||||
* Android/iOS AOT snapshots.
|
||||
*/
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function trim (s) {
|
||||
return String (s || "").replace (/^\s+|\s+$/g, "");
|
||||
}
|
||||
|
||||
function cmd (s) {
|
||||
return r2.cmd (s);
|
||||
}
|
||||
|
||||
function cmdj (s) {
|
||||
try {
|
||||
return r2.cmdj (s);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function fail (msg) {
|
||||
console.log ("error=" + msg);
|
||||
throw new Error (msg);
|
||||
}
|
||||
|
||||
function hex (n) {
|
||||
var s = Number (n).toString (16);
|
||||
if (s.length & 1) {
|
||||
s = "0" + s;
|
||||
}
|
||||
return "0x" + s;
|
||||
}
|
||||
|
||||
function findWrapperFlag () {
|
||||
var out = cmd ("f~onBadCertificateWrapper");
|
||||
var lines = out.split (/\r?\n/);
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var line = trim (lines[i]);
|
||||
var m = line.match (/^(0x[0-9a-fA-F]+)\s+\d+\s+(method\._RawSecureSocket_.*\._onBadCertificateWrapper_.*)$/);
|
||||
if (m) {
|
||||
return {
|
||||
addr: parseInt (m[1], 16),
|
||||
name: m[2],
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function fileOffsetFor (addr) {
|
||||
var sections = cmdj ("iSj") || [];
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
var s = sections[i];
|
||||
var vaddr = Number (s.vaddr);
|
||||
var vsize = Number (s.vsize || s.size);
|
||||
var paddr = Number (s.paddr);
|
||||
if (!isFinite (vaddr) || !isFinite (vsize) || !isFinite (paddr)) {
|
||||
continue;
|
||||
}
|
||||
if (addr >= vaddr && addr < vaddr + vsize) {
|
||||
return {
|
||||
offset: paddr + (addr - vaddr),
|
||||
section: s.name || "",
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
offset: addr,
|
||||
section: "unmapped-fallback",
|
||||
};
|
||||
}
|
||||
|
||||
cmd ("e scr.color=0");
|
||||
cmd ("e bin.relocs.apply=true");
|
||||
|
||||
var arch = trim (cmd ("e asm.arch"));
|
||||
var bits = trim (cmd ("e asm.bits"));
|
||||
if (arch.indexOf ("arm") === -1 || bits !== "64") {
|
||||
fail ("unsupported-arch-" + arch + "-" + bits + "-expected-arm64");
|
||||
}
|
||||
|
||||
var flag = findWrapperFlag ();
|
||||
if (!flag) {
|
||||
cmd ("r2flutter -A");
|
||||
flag = findWrapperFlag ();
|
||||
}
|
||||
if (!flag) {
|
||||
fail ("missing-RawSecureSocket-onBadCertificateWrapper-flag");
|
||||
}
|
||||
|
||||
var loc = fileOffsetFor (flag.addr);
|
||||
var oldIoVa = trim (cmd ("e io.va"));
|
||||
var oldSeek = trim (cmd ("s"));
|
||||
var patch = "c0820091c0035fd6";
|
||||
|
||||
cmd ("e io.va=false");
|
||||
var before = trim (cmd ("p8 8 @ " + hex (loc.offset)));
|
||||
cmd ("wx " + patch + " @ " + hex (loc.offset));
|
||||
var after = trim (cmd ("p8 8 @ " + hex (loc.offset)));
|
||||
cmd ("e io.va=" + oldIoVa);
|
||||
if (oldSeek) {
|
||||
cmd ("s " + oldSeek);
|
||||
}
|
||||
|
||||
if (after !== patch) {
|
||||
fail ("write-failed-before-" + before + "-after-" + after);
|
||||
}
|
||||
|
||||
console.log ("target=" + flag.name);
|
||||
console.log ("vaddr=" + hex (flag.addr));
|
||||
console.log ("file_offset=" + hex (loc.offset));
|
||||
console.log ("section=" + loc.section);
|
||||
console.log ("before=" + before);
|
||||
console.log ("after=" + after);
|
||||
console.log ("status=" + (before === patch ? "already-patched" : "patched"));
|
||||
})();
|
||||
@@ -1,173 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import json, subprocess, sys, os, struct, re
|
||||
|
||||
def run(cmd, cwd=None):
|
||||
p = subprocess.run(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
return p.returncode, p.stdout, p.stderr
|
||||
|
||||
def r2flutter_json(target):
|
||||
rc, out, err = run([os.path.join(os.path.dirname(__file__), '..', 'bin', 'r2flutter'), '-q', '-j', '-H', target])
|
||||
if rc != 0 or not out.strip():
|
||||
raise RuntimeError('r2flutter failed: %s' % err)
|
||||
return json.loads(out.strip().splitlines()[-1])
|
||||
|
||||
def round_up(x,a):
|
||||
return (x + (a-1)) & ~(a-1)
|
||||
|
||||
HEX_RE = re.compile(r"0x([0-9a-fA-F]+)")
|
||||
|
||||
def search_hex(target, start, end, hexpat):
|
||||
rc, out, err = run(['r2', '-nnq', '-c', f'e search.in=range; e search.from=0x{start:x}; e search.to=0x{end:x}; /x {hexpat}', target])
|
||||
addrs = []
|
||||
for line in out.splitlines():
|
||||
m = HEX_RE.search(line)
|
||||
if m:
|
||||
addrs.append(int(m.group(1), 16))
|
||||
return addrs
|
||||
|
||||
def read_qword(target, addr):
|
||||
rc, out, err = run(['r2', '-nnq', '-c', f'pxq 1 @ 0x{addr:x}', target])
|
||||
for line in out.splitlines():
|
||||
m = HEX_RE.search(line)
|
||||
if m:
|
||||
return int(m.group(1), 16)
|
||||
return 0
|
||||
|
||||
def le64(buf, off):
|
||||
if off < 0 or off+8 > len(buf):
|
||||
return 0
|
||||
return struct.unpack_from('<Q', bytes(buf), off)[0]
|
||||
|
||||
def read_cstr(buf, off, maxlen=128):
|
||||
if off < 0 or off >= len(buf):
|
||||
return ''
|
||||
end = off
|
||||
n = 0
|
||||
out = []
|
||||
while end < len(buf) and n < maxlen:
|
||||
b = buf[end]
|
||||
if b == 0:
|
||||
break
|
||||
if 32 <= b < 127 or b == 9:
|
||||
out.append(chr(b))
|
||||
else:
|
||||
return ''
|
||||
end += 1
|
||||
n += 1
|
||||
return ''.join(out)
|
||||
|
||||
def derive_offsets(target):
|
||||
j = r2flutter_json(target)
|
||||
iso_data = j['iso_data']
|
||||
iso_instr = j['iso_instr']
|
||||
total = j['cluster']['total']
|
||||
cws = j.get('cws', 4)
|
||||
align = 16
|
||||
base = iso_data + round_up(total, align)
|
||||
end = iso_instr if iso_instr > base else base + (1<<22)
|
||||
# Candidate offsets within Code and Function objects (64-bit typical)
|
||||
ep_cands = [0x10,0x18,0x20,0x28,0x30]
|
||||
owner_cands = [0x10,0x18,0x20,0x28,0x30]
|
||||
name_cands = [0x10,0x18,0x20,0x28,0x30]
|
||||
good_ep, good_owner, good_name = set(), set(), set()
|
||||
|
||||
# Fast path: search for common prefixes in strings to get candidate name addresses
|
||||
names = []
|
||||
for pat in (b'package:', b'dart:'):
|
||||
hexpat = pat.hex()
|
||||
addrs = search_hex(target, base, end, hexpat)
|
||||
names.extend(addrs)
|
||||
|
||||
# For each candidate String address, locate owner(Function) via pointers.
|
||||
# Consider both 64-bit and 32-bit compressed pointers.
|
||||
max_hits = 800
|
||||
for sa in names[:200]: # cap initial string candidates
|
||||
le_name = sa.to_bytes(8, 'little').hex()
|
||||
owner_ptrs = search_hex(target, base, end, le_name)
|
||||
# also try 32-bit compressed encodings for (sa-base)
|
||||
rel = sa - base
|
||||
for sh in (0,1,2,3):
|
||||
enc = (rel >> sh) & 0xffffffff
|
||||
for tag in (0,1,3,7):
|
||||
val = (enc | tag) & 0xffffffff
|
||||
le4 = val.to_bytes(4, 'little').hex()
|
||||
owner_ptrs += search_hex(target, base, end, le4)
|
||||
# Each owner_ptr is at (owner + name_off)
|
||||
for owner_ptr in owner_ptrs[:12]: # cap per-name refs
|
||||
for name_off in name_cands:
|
||||
owner = owner_ptr - name_off
|
||||
if owner < base or owner >= end:
|
||||
continue
|
||||
# Find Code.owner refs: pointers to 'owner' within range (64-bit and 32-bit compressed)
|
||||
le_owner = owner.to_bytes(8, 'little').hex()
|
||||
code_owner_ptrs = search_hex(target, base, end, le_owner)
|
||||
rel_owner = owner - base
|
||||
for sh2 in (0,1,2,3):
|
||||
enc2 = (rel_owner >> sh2) & 0xffffffff
|
||||
for tag2 in (0,1,3,7):
|
||||
val2 = (enc2 | tag2) & 0xffffffff
|
||||
code_owner_ptrs += search_hex(target, base, end, val2.to_bytes(4, 'little').hex())
|
||||
for cop in code_owner_ptrs[:16]:
|
||||
# cop == (code_base + owner_off)
|
||||
for owner_off in owner_cands:
|
||||
code_base = cop - owner_off
|
||||
if code_base < base or code_base >= end:
|
||||
continue
|
||||
for ep_off in ep_cands:
|
||||
ep = read_qword(target, code_base + ep_off)
|
||||
if iso_instr <= ep < iso_instr + (1<<26) and (ep & 3) == 0:
|
||||
good_owner.add(owner_off)
|
||||
good_name.add(name_off)
|
||||
good_ep.add(ep_off)
|
||||
max_hits -= 1
|
||||
break
|
||||
if max_hits <= 0:
|
||||
break
|
||||
if max_hits <= 0:
|
||||
break
|
||||
if max_hits <= 0:
|
||||
break
|
||||
if max_hits <= 0:
|
||||
break
|
||||
if max_hits <= 0:
|
||||
break
|
||||
return {
|
||||
'hash': j['hash'],
|
||||
'compressed_word_size': cws,
|
||||
'heap_object_tag': 1,
|
||||
'max_alignment': align,
|
||||
'it_cap': 20000,
|
||||
'code_entry_point_offsets': sorted(good_ep),
|
||||
'code_owner_offsets': sorted(good_owner),
|
||||
'function_name_offsets': sorted(good_name),
|
||||
}
|
||||
|
||||
def merge_offsets(path, entry):
|
||||
if os.path.exists(path):
|
||||
with open(path, 'r') as f:
|
||||
obj = json.load(f)
|
||||
else:
|
||||
obj = {'arch': 'arm64', 'hashes': {}}
|
||||
h = entry['hash']
|
||||
obj.setdefault('hashes', {})
|
||||
obj['hashes'][h] = entry
|
||||
with open(path, 'w') as f:
|
||||
json.dump(obj, f, indent=2)
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print('Usage: derive_offsets.py <path-to-libapp.so-or-.app>', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
target = sys.argv[1]
|
||||
# resolve .app path to App binary
|
||||
if target.endswith('.app'):
|
||||
appbin = os.path.join(target, 'Frameworks', 'App.framework', 'App')
|
||||
if os.path.exists(appbin):
|
||||
target = appbin
|
||||
entry = derive_offsets(target)
|
||||
out = os.path.join(os.path.dirname(__file__), '..', 'offsets.json')
|
||||
merge_offsets(out, entry)
|
||||
print(json.dumps(entry, indent=2))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,60 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Generate r2flutter/offsets.json for the detected snapshot hash and Dart version.
|
||||
# Usage: scripts/gen_offsets.sh <libdir>
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "Usage: $0 <libdir-with-libapp.so-and-libflutter.so>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
LIBDIR="$1"
|
||||
LIBAPP="$LIBDIR/libapp.so"
|
||||
LIBFLUTTER="$LIBDIR/libflutter.so"
|
||||
|
||||
if [ ! -f "$LIBAPP" ] || [ ! -f "$LIBFLUTTER" ]; then
|
||||
echo "Missing libapp.so or libflutter.so in $LIBDIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SNAPSHOT_DATA_VADDR=$(readelf -Ws "$LIBAPP" | grep '_kDartVmSnapshotData$' | awk '{print "0x"$2}' | head -n 1)
|
||||
if [ -z "$SNAPSHOT_DATA_VADDR" ]; then
|
||||
echo "Failed to find _kDartVmSnapshotData symbol in $LIBAPP" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
HASH_OFFSET=$((SNAPSHOT_DATA_VADDR+20))
|
||||
|
||||
HASH=$(dd if="$LIBAPP" bs=1 skip=$((HASH_OFFSET)) count=32 2>/dev/null | tr -dc '0-9a-f' | head -c 32)
|
||||
if [ -z "$HASH" ]; then
|
||||
echo "Failed to extract snapshot hash from $LIBAPP" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERLINE="$(strings -a "$LIBFLUTTER" | sed -n 's/^\([0-9]\+\.[0-9]\+\.[0-9]\+\) (stable).*/\1/p' | head -n1)"
|
||||
if [ -z "$VERLINE" ]; then
|
||||
echo "Warning: Cannot determine Dart version from $LIBFLUTTER; continuing with hash only" >&2
|
||||
fi
|
||||
|
||||
echo "Detected snapshot hash: $HASH, dart version: ${VERLINE:-unknown}"
|
||||
|
||||
# Prepare minimal offsets JSON with reasonable defaults; future versions of this
|
||||
# generator can curl dart-lang/sdk sources and compute more fields.
|
||||
OUTJSON="offsets.json"
|
||||
mkdir -p "$(dirname "$OUTJSON")"
|
||||
|
||||
cat > "$OUTJSON" <<JSON
|
||||
{
|
||||
"arch": "arm64",
|
||||
"hashes": {
|
||||
"$HASH": {
|
||||
"compressed_word_size": 4,
|
||||
"heap_object_tag": 1,
|
||||
"notes": "Autogenerated skeleton for $VERLINE; refine via SDK source parsing"
|
||||
}
|
||||
}
|
||||
}
|
||||
JSON
|
||||
|
||||
echo "Wrote $OUTJSON"
|
||||
Executable
+1233
File diff suppressed because it is too large.
Load diff
+16
-16
@@ -141,7 +141,7 @@ static bool find_dart_note_in_fat_macho(RBuffer *b, ut64 fsize, bool fat64, Dart
|
||||
return false;
|
||||
}
|
||||
|
||||
bool dart_app_find_macho_embedded_dart(const char *path, DartAppEmbeddedPayload *out) {
|
||||
bool dart_app_find_macho_dart(const char *path, DartAppEmbeddedPayload *out) {
|
||||
if (R_STR_ISEMPTY (path) || !out) {
|
||||
return false;
|
||||
}
|
||||
@@ -186,7 +186,7 @@ static FILE *open_payload_temp(char **out_path) {
|
||||
return out;
|
||||
}
|
||||
|
||||
char *dart_app_extract_embedded_payload(const char *path, const DartAppEmbeddedPayload *payload) {
|
||||
char *dart_app_extract_payload(const char *path, const DartAppEmbeddedPayload *payload) {
|
||||
if (R_STR_ISEMPTY (path) || !payload || !payload->payload_size) {
|
||||
return NULL;
|
||||
}
|
||||
@@ -231,6 +231,7 @@ char *dart_app_extract_embedded_payload(const char *path, const DartAppEmbeddedP
|
||||
|
||||
void dart_function_fini(DartFunction *fn) {
|
||||
free (fn->name);
|
||||
free (fn->signature);
|
||||
}
|
||||
|
||||
static ut64 dart_normalize_code_addr(ut64 addr) {
|
||||
@@ -284,7 +285,7 @@ static char *dart_strdup_filtered(const char *name) {
|
||||
return dup;
|
||||
}
|
||||
|
||||
static void dart_app_add_or_update_fn(DartApp *app, const char *name, ut64 addr, ut64 size, int quality) {
|
||||
static void dart_app_add_or_update_fn(DartApp *app, const char *name, ut64 addr, ut64 size, int quality, const char *signature) {
|
||||
if (!app || !app->functions || !addr) {
|
||||
return;
|
||||
}
|
||||
@@ -300,6 +301,9 @@ static void dart_app_add_or_update_fn(DartApp *app, const char *name, ut64 addr,
|
||||
if (size > fn->size) {
|
||||
fn->size = size;
|
||||
}
|
||||
if (R_STR_ISNOTEMPTY (signature) && !fn->signature) {
|
||||
fn->signature = strdup (signature);
|
||||
}
|
||||
if (quality > fn->quality || (!fn->name && filtered)) {
|
||||
free (fn->name);
|
||||
fn->name = filtered;
|
||||
@@ -321,6 +325,7 @@ static void dart_app_add_or_update_fn(DartApp *app, const char *name, ut64 addr,
|
||||
newfn->addr = addr;
|
||||
newfn->size = size;
|
||||
newfn->quality = quality;
|
||||
newfn->signature = R_STR_ISNOTEMPTY (signature)? strdup (signature): NULL;
|
||||
if (filtered) {
|
||||
newfn->name = filtered;
|
||||
} else {
|
||||
@@ -490,7 +495,8 @@ static void dart_app_load_it_functions(DartApp *app) {
|
||||
entry->name? entry->name: "method.unknown",
|
||||
dart_normalize_code_addr (entry->address),
|
||||
0,
|
||||
dart_function_name_quality (entry->name));
|
||||
dart_function_name_quality (entry->name),
|
||||
entry->signature);
|
||||
}
|
||||
dart_instruction_table_list_free (entries);
|
||||
}
|
||||
@@ -520,7 +526,8 @@ static void dart_app_merge_class_methods(DartApp *app) {
|
||||
fullname? fullname: mi->name,
|
||||
dart_normalize_code_addr (mi->entry_point),
|
||||
0,
|
||||
dart_function_name_quality (fullname? fullname: mi->name));
|
||||
dart_function_name_quality (fullname? fullname: mi->name),
|
||||
mi->signature);
|
||||
free (fullname);
|
||||
}
|
||||
}
|
||||
@@ -528,10 +535,7 @@ static void dart_app_merge_class_methods(DartApp *app) {
|
||||
}
|
||||
|
||||
DartApp *dart_app_new(const char *path) {
|
||||
DartApp *app = (DartApp *)calloc (1, sizeof (DartApp));
|
||||
if (!app) {
|
||||
return NULL;
|
||||
}
|
||||
DartApp *app = R_NEW0 (DartApp);
|
||||
app->file_path = path? strdup (path): NULL;
|
||||
app->functions = RVecDartFunction_new ();
|
||||
return app;
|
||||
@@ -573,7 +577,8 @@ static void add_fn_cb(const char *name, ut64 addr, ut64 size, void *user) {
|
||||
name,
|
||||
dart_normalize_code_addr (addr),
|
||||
size,
|
||||
dart_function_name_quality (name));
|
||||
dart_function_name_quality (name),
|
||||
NULL);
|
||||
}
|
||||
|
||||
void dart_app_load_info(DartApp *app) {
|
||||
@@ -589,12 +594,7 @@ void dart_app_load_info(DartApp *app) {
|
||||
dart_app_load_it_functions (app);
|
||||
dart_app_merge_class_methods (app);
|
||||
if (RVecDartFunction_length (app->functions) == 0) {
|
||||
ut64 base = 0, heap_base = 0;
|
||||
int rc = dart_pool_enumerate (&app->dctx, app->file_path, add_fn_cb, app, &base, &heap_base);
|
||||
if (rc == 0) {
|
||||
app->base_addr = base;
|
||||
app->heap_base = heap_base;
|
||||
}
|
||||
(void)dart_pool_scan (&app->dctx, add_fn_cb, app);
|
||||
}
|
||||
RVecDartFunction_sort (app->functions, dart_function_cmp);
|
||||
if (app->dctx.verbose) {
|
||||
|
||||
+65
-1538
File diff suppressed because it is too large.
Load diff
+150
-2
@@ -133,7 +133,7 @@ static void dump_func_r2(RStrBuf *sb, const DartFunction *fn, bool quiet) {
|
||||
free (flagname);
|
||||
}
|
||||
|
||||
char *dart_dumper_dump4radare2(DartApp *app) {
|
||||
char *dart_dumper_dump_r2(DartApp *app) {
|
||||
RStrBuf *sb = r_strbuf_new ("");
|
||||
const bool quiet = app && app->dctx.quiet;
|
||||
|
||||
@@ -182,6 +182,9 @@ static void dump_func_json(PJ *pj, const DartFunction *fn) {
|
||||
if (fn->size) {
|
||||
pj_kn (pj, "size", fn->size);
|
||||
}
|
||||
if (R_STR_ISNOTEMPTY (fn->signature)) {
|
||||
pj_ks (pj, "signature", fn->signature);
|
||||
}
|
||||
pj_end (pj);
|
||||
}
|
||||
|
||||
@@ -233,7 +236,145 @@ char *dart_dumper_dump_funcs(DartApp *app, int fmt) {
|
||||
return drain_trimmed_strbuf (sb);
|
||||
}
|
||||
|
||||
void dart_dumper_apply_to_core(DartApp *app) {
|
||||
// Map a single Dart type token to a C-ish spelling radare2's `afs` accepts.
|
||||
static void dart_type_to_c(RStrBuf *sb, const char *tok) {
|
||||
if (R_STR_ISEMPTY (tok) || !strcmp (tok, "dynamic")) {
|
||||
r_strbuf_append (sb, "void *");
|
||||
} else if (!strcmp (tok, "void")) {
|
||||
r_strbuf_append (sb, "void");
|
||||
} else if (!strcmp (tok, "int")) {
|
||||
r_strbuf_append (sb, "int64_t");
|
||||
} else if (!strcmp (tok, "double")) {
|
||||
r_strbuf_append (sb, "double");
|
||||
} else if (!strcmp (tok, "bool")) {
|
||||
r_strbuf_append (sb, "bool");
|
||||
} else if (!strcmp (tok, "String")) {
|
||||
r_strbuf_append (sb, "char *");
|
||||
} else {
|
||||
// Object / user-class type: an opaque pointer named after the class.
|
||||
char *safe = strdup (tok);
|
||||
r_name_filter (safe, 0);
|
||||
r_strbuf_appendf (sb, "%s *", R_STR_ISNOTEMPTY (safe)? safe: "void");
|
||||
free (safe);
|
||||
}
|
||||
}
|
||||
|
||||
// Turn a recovered Dart signature "RET(T,T,...)" into a radare2 `afs` prototype
|
||||
// and return it, or NULL if the signature is not in that canonical form.
|
||||
static char *dart_signature_to_afs_proto(const char *fname, const char *sig) {
|
||||
if (R_STR_ISEMPTY (sig) || strchr (sig, ' ') || strchr (sig, '=')) {
|
||||
return NULL; // not our canonical "RET(T,...)" form
|
||||
}
|
||||
const char *lp = strchr (sig, '(');
|
||||
if (!lp) {
|
||||
return NULL;
|
||||
}
|
||||
char *ret = r_str_ndup (sig, (int) (lp - sig));
|
||||
if (R_STR_ISEMPTY (ret)) {
|
||||
free (ret);
|
||||
return NULL;
|
||||
}
|
||||
char *inside = strdup (lp + 1);
|
||||
char *rp = strrchr (inside, ')');
|
||||
if (rp) {
|
||||
*rp = '\0';
|
||||
}
|
||||
char *safe_name = strdup (fname);
|
||||
r_name_filter (safe_name, 0);
|
||||
RStrBuf *sb = r_strbuf_new ("");
|
||||
dart_type_to_c (sb, ret);
|
||||
r_strbuf_appendf (sb, " %s(", R_STR_ISNOTEMPTY (safe_name)? safe_name: "fcn");
|
||||
int argc = 0;
|
||||
if (R_STR_ISNOTEMPTY (inside)) {
|
||||
RList *args = r_str_split_list (inside, ",", 0);
|
||||
RListIter *it;
|
||||
char *a;
|
||||
r_list_foreach (args, it, a) {
|
||||
if (argc > 0) {
|
||||
r_strbuf_append (sb, ", ");
|
||||
}
|
||||
dart_type_to_c (sb, r_str_trim_head_ro (a));
|
||||
// The first parameter of an instance method is the receiver.
|
||||
if (argc == 0) {
|
||||
r_strbuf_append (sb, " this");
|
||||
} else {
|
||||
r_strbuf_appendf (sb, " arg%d", argc);
|
||||
}
|
||||
argc++;
|
||||
}
|
||||
r_list_free (args);
|
||||
}
|
||||
r_strbuf_append (sb, ")");
|
||||
free (ret);
|
||||
free (inside);
|
||||
free (safe_name);
|
||||
return r_strbuf_drain (sb);
|
||||
}
|
||||
|
||||
static int dart_fn_addr_desc(const void *a, const void *b) {
|
||||
const DartFunction *fa = *(const DartFunction *const *)a;
|
||||
const DartFunction *fb = *(const DartFunction *const *)b;
|
||||
if (fa->addr < fb->addr) {
|
||||
return 1;
|
||||
}
|
||||
if (fa->addr > fb->addr) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Define a function at `addr` if needed and set its recovered signature so that
|
||||
// `afsj`/`afcrj` report real parameter and return types. Functions are analysed
|
||||
// in descending address order: a Dart function whose analysis overruns into the
|
||||
// next one then stops at the already-defined higher function instead of merging.
|
||||
void dart_dumper_apply_signatures(DartApp *app) {
|
||||
if (!app || !app->core || !app->functions) {
|
||||
return;
|
||||
}
|
||||
RCore *core = app->core;
|
||||
size_t total = RVecDartFunction_length (app->functions);
|
||||
if (!total) {
|
||||
return;
|
||||
}
|
||||
DartFunction **order = (DartFunction **)malloc (total * sizeof (DartFunction *));
|
||||
if (!order) {
|
||||
return;
|
||||
}
|
||||
size_t n = 0;
|
||||
DartFunction *fn;
|
||||
R_VEC_FOREACH (app->functions, fn) {
|
||||
if (fn->addr && R_STR_ISNOTEMPTY (fn->signature)) {
|
||||
order[n++] = fn;
|
||||
}
|
||||
}
|
||||
qsort (order, n, sizeof (DartFunction *), dart_fn_addr_desc);
|
||||
// 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.
|
||||
int prev_hasnext = r_config_get_i (core->config, "anal.hasnext");
|
||||
int prev_emustr = r_config_get_i (core->config, "emu.str");
|
||||
r_config_set_i (core->config, "anal.hasnext", 0);
|
||||
r_config_set_i (core->config, "emu.str", 0);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
fn = order[i];
|
||||
char *proto = dart_signature_to_afs_proto (fn->name? fn->name: "fcn", fn->signature);
|
||||
if (!proto) {
|
||||
continue;
|
||||
}
|
||||
if (!r_anal_get_function_at (core->anal, fn->addr)) {
|
||||
r_core_cmdf (core, "af @ 0x%" PFMT64x, fn->addr);
|
||||
}
|
||||
if (r_anal_get_function_at (core->anal, fn->addr)) {
|
||||
r_core_cmdf (core, "\"afs %s\" @ 0x%" PFMT64x, proto, fn->addr);
|
||||
}
|
||||
free (proto);
|
||||
}
|
||||
r_config_set_i (core->config, "anal.hasnext", prev_hasnext);
|
||||
r_config_set_i (core->config, "emu.str", prev_emustr);
|
||||
free (order);
|
||||
}
|
||||
|
||||
void dart_dumper_apply_to_core(DartApp *app, bool apply_signatures) {
|
||||
if (!app || !app->core) {
|
||||
return;
|
||||
}
|
||||
@@ -258,4 +399,11 @@ void dart_dumper_apply_to_core(DartApp *app) {
|
||||
}
|
||||
|
||||
r_core_cmd0 (core, "e emu.str=true");
|
||||
// Applying signatures forces analysis (af) of every typed function, which is
|
||||
// slow on a large snapshot; only do it at the deepest level (-AAA). Lighter
|
||||
// consumers (e.g. the reflow decompiler) read per-function signatures from
|
||||
// the fast `-fj` dump instead.
|
||||
if (apply_signatures) {
|
||||
dart_dumper_apply_signatures (app);
|
||||
}
|
||||
}
|
||||
@@ -101,6 +101,12 @@ void dart_obf_fini(DartCtx *ctx) {
|
||||
ht_pp_free (ctx->obf_by_obfuscated);
|
||||
ctx->obf_by_obfuscated = NULL;
|
||||
ctx->obf_map_tried = false;
|
||||
// Release the read_mem window cache (scoped to one command invocation).
|
||||
free (ctx->rmem_cache);
|
||||
ctx->rmem_cache = NULL;
|
||||
ctx->rmem_cache_addr = 0;
|
||||
ctx->rmem_cache_len = 0;
|
||||
ctx->rmem_cache_cap = 0;
|
||||
}
|
||||
|
||||
char *dart_obf_resolve(DartCtx *ctx, const char *name) {
|
||||
|
||||
+77
-33
@@ -50,7 +50,7 @@ typedef struct {
|
||||
ut64 index;
|
||||
ut8 bits;
|
||||
DartPpInfo pp;
|
||||
DartModernPoolSlotInfo slot;
|
||||
ModernPoolSlot slot;
|
||||
} DartPpSlotInfo;
|
||||
|
||||
static void dart_object_info_fini(DartObjectInfo *info) {
|
||||
@@ -76,7 +76,7 @@ static void dart_pp_slot_info_fini(DartPpSlotInfo *info) {
|
||||
return;
|
||||
}
|
||||
dart_pp_info_fini (&info->pp);
|
||||
dart_modern_pool_slot_info_fini (&info->slot);
|
||||
modern_slot_fini (&info->slot);
|
||||
memset (info, 0, sizeof (*info));
|
||||
}
|
||||
|
||||
@@ -85,19 +85,6 @@ static int dart_object_heap_tag(DartCtx *ctx) {
|
||||
return tag;
|
||||
}
|
||||
|
||||
static int dart_object_cid_from_header(DartCtx *ctx, ut64 header) {
|
||||
DartTagStyle style = ctx && ctx->layout? ctx->layout->tag_style: DART_TAG_STYLE_OBJECT_HEADER;
|
||||
switch (style) {
|
||||
case DART_TAG_STYLE_CID_INT32:
|
||||
return (int) (header & 0xffffffffU);
|
||||
case DART_TAG_STYLE_CID_SHIFT1:
|
||||
return (int) ((header >> 1) & 0xffffffffU);
|
||||
case DART_TAG_STYLE_OBJECT_HEADER:
|
||||
default:
|
||||
return (int) ((header >> 12) & 0xfffffU);
|
||||
}
|
||||
}
|
||||
|
||||
static const char *dart_object_cid_name(DartCtx *ctx, int cid) {
|
||||
const DartVerLayout *layout = ctx? ctx->layout: NULL;
|
||||
for (int i = 0; i < DART_CID_KIND_COUNT; i++) {
|
||||
@@ -147,6 +134,17 @@ static bool dart_object_parse_pp_spec(const char *spec, ut64 *out) {
|
||||
return dart_object_parse_u64 (p + 3, out);
|
||||
}
|
||||
|
||||
static bool dart_object_parse_ref_spec(const char *spec, ut64 *out) {
|
||||
if (R_STR_ISEMPTY (spec) || !out) {
|
||||
return false;
|
||||
}
|
||||
const char *p = r_str_trim_head_ro (spec);
|
||||
if (tolower ((ut8)p[0]) != 'r' || tolower ((ut8)p[1]) != 'e' || tolower ((ut8)p[2]) != 'f' || p[3] != ':') {
|
||||
return false;
|
||||
}
|
||||
return dart_object_parse_u64 (p + 4, out);
|
||||
}
|
||||
|
||||
static char *dart_object_bytes_preview(const ut8 *buf, ut64 len) {
|
||||
RStrBuf *sb = r_strbuf_new ("");
|
||||
for (ut64 i = 0; i < len; i++) {
|
||||
@@ -265,7 +263,7 @@ static bool dart_object_decode_at(DartCtx *ctx, ut64 addr, DartObjectInfo *out)
|
||||
}
|
||||
out->addr = addr;
|
||||
out->header = r_read_le64 (hdr);
|
||||
out->cid = dart_object_cid_from_header (ctx, out->header);
|
||||
out->cid = (int)dart_cid_from_object_header (out->header);
|
||||
if (out->cid <= 0 || out->cid >= 0xfffff) {
|
||||
return false;
|
||||
}
|
||||
@@ -357,14 +355,14 @@ static bool dart_pp_slot_decode(DartCtx *ctx, ut64 offset, DartPpSlotInfo *slot)
|
||||
if (slot->bits_ok) {
|
||||
DartSnapshotHeader sh = { 0 };
|
||||
if (dart_snapshot_header_read (ctx, slot->pp.snapshot_base, &sh) && sh.ok) {
|
||||
const DartModernClusterRequest req = {
|
||||
const ModernReq req = {
|
||||
ctx,
|
||||
sh.cluster_start,
|
||||
slot->pp.snapshot_base + sh.total_len,
|
||||
sh.nc,
|
||||
sh.nb
|
||||
};
|
||||
slot->slot_ok = dart_modern_resolve_pp_slot (&req, offset, &slot->slot);
|
||||
slot->slot_ok = modern_resolve_pp_slot (&req, offset, &slot->slot);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -391,11 +389,13 @@ static void dart_object_json_object_fields(PJ *pj, const DartObjectInfo *obj) {
|
||||
}
|
||||
}
|
||||
|
||||
static void dart_object_json_value(PJ *pj, const DartValueInfo *value, bool decode_raw) {
|
||||
static void dart_object_json_value(PJ *pj, const DartValueInfo *value, bool decode_raw, bool snapshot_ref) {
|
||||
pj_ko (pj, "value");
|
||||
pj_kn (pj, "raw", value->raw);
|
||||
pj_kb (pj, "decoded", decode_raw);
|
||||
if (!decode_raw) {
|
||||
if (snapshot_ref) {
|
||||
pj_ks (pj, "kind", "snapshot_ref");
|
||||
} else if (!decode_raw) {
|
||||
pj_ks (pj, "kind", "object_pool_ref");
|
||||
} else if (value->selected >= 0) {
|
||||
pj_ks (pj, "kind", "object");
|
||||
@@ -443,7 +443,7 @@ static void dart_object_json_pool_slot(PJ *pj, const DartPpSlotInfo *slot) {
|
||||
}
|
||||
pj_kb (pj, "resolved", slot->slot_ok);
|
||||
if (slot->slot_ok) {
|
||||
const DartModernPoolSlotInfo *si = &slot->slot;
|
||||
const ModernPoolSlot *si = &slot->slot;
|
||||
pj_kn (pj, "cluster_index", si->cluster_index);
|
||||
pj_kn (pj, "pool_index", si->pool_index);
|
||||
pj_kn (pj, "pool_ref", si->pool_ref);
|
||||
@@ -478,7 +478,7 @@ static void dart_object_json_pool_slot(PJ *pj, const DartPpSlotInfo *slot) {
|
||||
pj_end (pj);
|
||||
}
|
||||
|
||||
static char *dart_object_dump_json(const char *spec, const DartValueInfo *value, const DartPpSlotInfo *slot, bool decode_raw) {
|
||||
static char *dart_object_dump_json(const char *spec, const DartValueInfo *value, const DartPpSlotInfo *slot, ModernValueGraph *graph, bool decode_raw) {
|
||||
PJ *pj = pj_new ();
|
||||
if (!pj) {
|
||||
return strdup ("{\"error\":\"Failed to create JSON\"}");
|
||||
@@ -495,6 +495,8 @@ static char *dart_object_dump_json(const char *spec, const DartValueInfo *value,
|
||||
pj_ks (pj, "kind", "pp_slot");
|
||||
}
|
||||
dart_object_json_pool_slot (pj, slot);
|
||||
} else if (graph) {
|
||||
pj_ks (pj, "kind", "snapshot_ref");
|
||||
} else if (decode_raw && value->selected >= 0) {
|
||||
pj_ks (pj, "kind", "object");
|
||||
} else if (value->is_smi) {
|
||||
@@ -502,7 +504,8 @@ static char *dart_object_dump_json(const char *spec, const DartValueInfo *value,
|
||||
} else {
|
||||
pj_ks (pj, "kind", "unknown");
|
||||
}
|
||||
dart_object_json_value (pj, value, decode_raw);
|
||||
dart_object_json_value (pj, value, decode_raw, graph && (!slot || !slot->present));
|
||||
modern_value_graph_json (pj, graph);
|
||||
pj_end (pj);
|
||||
return pj_drain (pj);
|
||||
}
|
||||
@@ -565,7 +568,7 @@ static void dart_object_text_pool_slot(RStrBuf *sb, const DartPpSlotInfo *slot)
|
||||
r_strbuf_appendf (sb, "pp.bits: 0x%02x\n", (unsigned int)slot->bits);
|
||||
}
|
||||
if (slot->slot_ok) {
|
||||
const DartModernPoolSlotInfo *si = &slot->slot;
|
||||
const ModernPoolSlot *si = &slot->slot;
|
||||
r_strbuf_appendf (sb, "pool.ref: %" PRIu64 "\n", (uint64_t)si->pool_ref);
|
||||
r_strbuf_appendf (sb, "pool.index: %" PRIu64 "\n", (uint64_t)si->pool_index);
|
||||
r_strbuf_appendf (sb, "entry.index: %" PRIu64 "\n", (uint64_t)si->index);
|
||||
@@ -594,7 +597,7 @@ static void dart_object_text_pool_slot(RStrBuf *sb, const DartPpSlotInfo *slot)
|
||||
}
|
||||
}
|
||||
|
||||
static char *dart_object_dump_text(const char *spec, const DartValueInfo *value, const DartPpSlotInfo *slot, bool decode_raw, bool quiet) {
|
||||
static char *dart_object_dump_text(const char *spec, const DartValueInfo *value, const DartPpSlotInfo *slot, ModernValueGraph *graph, bool decode_raw, bool quiet) {
|
||||
RStrBuf *sb = r_strbuf_new ("");
|
||||
if (quiet) {
|
||||
if (slot && slot->present && slot->slot_ok && R_STR_ISNOTEMPTY (slot->slot.resolved_name)) {
|
||||
@@ -608,6 +611,10 @@ static char *dart_object_dump_text(const char *spec, const DartValueInfo *value,
|
||||
return r_strbuf_drain (sb);
|
||||
}
|
||||
}
|
||||
if (graph) {
|
||||
r_strbuf_append (sb, modern_value_graph_root_kind (graph));
|
||||
return r_strbuf_drain (sb);
|
||||
}
|
||||
r_strbuf_appendf (sb, "raw=0x%" PFMT64x, value->raw);
|
||||
return r_strbuf_drain (sb);
|
||||
}
|
||||
@@ -615,16 +622,19 @@ static char *dart_object_dump_text(const char *spec, const DartValueInfo *value,
|
||||
if (slot && slot->present) {
|
||||
dart_object_text_pool_slot (sb, slot);
|
||||
}
|
||||
dart_object_text_value (sb, value, decode_raw);
|
||||
if (!graph || (slot && slot->present)) {
|
||||
dart_object_text_value (sb, value, decode_raw);
|
||||
}
|
||||
modern_value_graph_text (sb, graph);
|
||||
return r_strbuf_drain (sb);
|
||||
}
|
||||
|
||||
static char *dart_object_dump_r2(const DartValueInfo *value, const DartPpSlotInfo *slot, bool decode_raw) {
|
||||
static char *dart_object_dump_r2(const DartValueInfo *value, const DartPpSlotInfo *slot, ModernValueGraph *graph, bool decode_raw) {
|
||||
RStrBuf *sb = r_strbuf_new ("");
|
||||
if (slot && slot->present && slot->read_ok) {
|
||||
r_strbuf_appendf (sb, "'@0x%" PFMT64x "'CC Dart PP slot offset=0x%" PFMT64x " raw=0x%" PFMT64x, slot->addr, slot->offset, slot->raw);
|
||||
if (slot->slot_ok) {
|
||||
const DartModernPoolSlotInfo *si = &slot->slot;
|
||||
const ModernPoolSlot *si = &slot->slot;
|
||||
r_strbuf_appendf (sb, " entry=%" PRIu64 " type=%s behavior=%s", (uint64_t)si->index, si->type_name? si->type_name: "unknown", si->behavior_name? si->behavior_name: "unknown");
|
||||
if (R_STR_ISNOTEMPTY (si->resolved_kind)) {
|
||||
r_strbuf_appendf (sb, " resolved=%s", si->resolved_kind);
|
||||
@@ -650,6 +660,9 @@ static char *dart_object_dump_r2(const DartValueInfo *value, const DartPpSlotInf
|
||||
} else if (decode_raw && value->is_smi) {
|
||||
r_strbuf_appendf (sb, "'CC Dart Smi raw=0x%" PFMT64x " value=%" PFMT64d "\n", value->raw, value->smi);
|
||||
}
|
||||
if (graph) {
|
||||
r_strbuf_appendf (sb, "'CC Dart snapshot value kind=%s\n", modern_value_graph_root_kind (graph));
|
||||
}
|
||||
return r_strbuf_drain (sb);
|
||||
}
|
||||
|
||||
@@ -670,8 +683,10 @@ char *dart_pool_dump_object(DartCtx *ctx, const char *spec, int fmt) {
|
||||
DartVerLayout *layout_owned = ctx->layout? NULL: dart_ctx_init_layout (ctx, &layout_tmp);
|
||||
ut64 raw = 0;
|
||||
ut64 pp_offset = 0;
|
||||
ut64 snapshot_ref = 0;
|
||||
bool is_pp = dart_object_parse_pp_spec (spec, &pp_offset);
|
||||
if (!is_pp) {
|
||||
bool is_ref = dart_object_parse_ref_spec (spec, &snapshot_ref);
|
||||
if (!is_pp && !is_ref) {
|
||||
if (!dart_object_parse_u64 (spec, &raw)) {
|
||||
if (layout_owned) {
|
||||
dart_ctx_fini_layout (ctx, layout_owned);
|
||||
@@ -684,6 +699,15 @@ char *dart_pool_dump_object(DartCtx *ctx, const char *spec, int fmt) {
|
||||
if (raw >= DART_SYNTHETIC_PP_BASE) {
|
||||
pp_offset = raw - DART_SYNTHETIC_PP_BASE;
|
||||
is_pp = true;
|
||||
} else {
|
||||
DartPpInfo pp_info = { 0 };
|
||||
if (dart_resolve_pp_info (ctx, &pp_info)) {
|
||||
if (raw >= pp_info.base && raw - pp_info.base < pp_info.size) {
|
||||
pp_offset = raw - pp_info.base;
|
||||
is_pp = true;
|
||||
}
|
||||
dart_pp_info_fini (&pp_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
DartValueInfo value;
|
||||
@@ -698,15 +722,35 @@ char *dart_pool_dump_object(DartCtx *ctx, const char *spec, int fmt) {
|
||||
decode_raw = ! (slot.slot_ok && slot.slot.behavior == 0 && slot.slot.type == 1);
|
||||
}
|
||||
}
|
||||
dart_value_decode (ctx, raw, &value);
|
||||
if (!is_ref) {
|
||||
dart_value_decode (ctx, raw, &value);
|
||||
} else {
|
||||
decode_raw = false;
|
||||
}
|
||||
ModernValueGraph *graph = NULL;
|
||||
if (is_ref || (slot.slot_ok && slot.slot.behavior == 0 && slot.slot.type == 1)) {
|
||||
const ut64 snapshot_base = is_pp? slot.pp.snapshot_base: ctx->iso_data;
|
||||
DartSnapshotHeader sh = { 0 };
|
||||
if (snapshot_base && dart_snapshot_header_read (ctx, snapshot_base, &sh) && sh.ok) {
|
||||
const ModernReq req = {
|
||||
ctx,
|
||||
sh.cluster_start,
|
||||
snapshot_base + sh.total_len,
|
||||
sh.nc,
|
||||
sh.nb
|
||||
};
|
||||
graph = modern_value_graph_new (&req, is_ref? snapshot_ref: slot.slot.ref);
|
||||
}
|
||||
}
|
||||
char *out = NULL;
|
||||
if (fmt == 'j') {
|
||||
out = dart_object_dump_json (spec, &value, is_pp? &slot: NULL, decode_raw);
|
||||
out = dart_object_dump_json (spec, &value, is_pp? &slot: NULL, graph, decode_raw);
|
||||
} else if (fmt == 'r') {
|
||||
out = dart_object_dump_r2 (&value, is_pp? &slot: NULL, decode_raw);
|
||||
out = dart_object_dump_r2 (&value, is_pp? &slot: NULL, graph, decode_raw);
|
||||
} else {
|
||||
out = dart_object_dump_text (spec, &value, is_pp? &slot: NULL, decode_raw, ctx->quiet);
|
||||
out = dart_object_dump_text (spec, &value, is_pp? &slot: NULL, graph, decode_raw, ctx->quiet);
|
||||
}
|
||||
modern_value_graph_free (graph);
|
||||
dart_value_info_fini (&value);
|
||||
dart_pp_slot_info_fini (&slot);
|
||||
if (layout_owned) {
|
||||
|
||||
+284
-89
@@ -2,6 +2,10 @@
|
||||
|
||||
#include "dart_pool_parse_priv.h"
|
||||
|
||||
#ifdef R_BIN_ATTR_MIXIN
|
||||
#define R2_HAS_BIN_ATTR 1
|
||||
#endif
|
||||
|
||||
static DartFieldInfo *dart_field_info_clone(const DartFieldInfo *fi) {
|
||||
if (!fi) {
|
||||
return NULL;
|
||||
@@ -47,10 +51,58 @@ static void dart_interface_info_free(void *p) {
|
||||
}
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
kFieldCid_extract = 10,
|
||||
kLibraryCid_extract = 12,
|
||||
} ExtraCids;
|
||||
// Cluster classes the legacy (non-OBJECT_HEADER) parser knows how to decode,
|
||||
// resolved for the snapshot's own layout. Absent classes resolve to -1 and then
|
||||
// match nothing, which is the point: comparing against a hardcoded id silently
|
||||
// decoded the wrong cluster on every release that shifted the numbering.
|
||||
typedef struct {
|
||||
int string;
|
||||
int one_byte_string;
|
||||
int two_byte_string;
|
||||
int class_;
|
||||
int field;
|
||||
int function;
|
||||
int type;
|
||||
int function_type;
|
||||
int type_parameter;
|
||||
int type_arguments;
|
||||
int array;
|
||||
int immutable_array;
|
||||
int library;
|
||||
} LegacyClusterCids;
|
||||
|
||||
static LegacyClusterCids legacy_cluster_cids(const DartVerLayout *layout) {
|
||||
const LegacyClusterCids lc = {
|
||||
.string = dart_cid_get (layout, DART_CID_STRING),
|
||||
.one_byte_string = dart_cid_get (layout, DART_CID_ONE_BYTE_STRING),
|
||||
.two_byte_string = dart_cid_get (layout, DART_CID_TWO_BYTE_STRING),
|
||||
.class_ = dart_cid_get (layout, DART_CID_CLASS),
|
||||
.field = dart_cid_get (layout, DART_CID_FIELD),
|
||||
.function = dart_cid_get (layout, DART_CID_FUNCTION),
|
||||
.type = dart_cid_get (layout, DART_CID_TYPE),
|
||||
.function_type = dart_cid_get (layout, DART_CID_FUNCTION_TYPE),
|
||||
.type_parameter = dart_cid_get (layout, DART_CID_TYPE_PARAMETER),
|
||||
.type_arguments = dart_cid_get (layout, DART_CID_TYPE_ARGUMENTS),
|
||||
.array = dart_cid_get (layout, DART_CID_ARRAY),
|
||||
.immutable_array = dart_cid_get (layout, DART_CID_IMMUTABLE_ARRAY),
|
||||
.library = dart_cid_get (layout, DART_CID_LIBRARY),
|
||||
};
|
||||
return lc;
|
||||
}
|
||||
|
||||
static inline bool legacy_cid_is(ut64 cid, int resolved) {
|
||||
return resolved >= 0 && cid == (ut64)resolved;
|
||||
}
|
||||
|
||||
// Clusters whose decoders may legitimately give up part-way (variable-length
|
||||
// payloads we do not fully model); the rest are treated as stream corruption.
|
||||
static bool legacy_cid_recoverable(ut64 cid, const LegacyClusterCids *lc) {
|
||||
return legacy_cid_is (cid, lc->function) ||
|
||||
legacy_cid_is (cid, lc->function_type) ||
|
||||
legacy_cid_is (cid, lc->type_arguments) ||
|
||||
legacy_cid_is (cid, lc->array) ||
|
||||
legacy_cid_is (cid, lc->immutable_array);
|
||||
}
|
||||
|
||||
#define DART_TYPE_CLASS_ID_SHIFT 3
|
||||
#define DART_TYPE_CLASS_ID_MASK ((1U << 20) - 1)
|
||||
@@ -415,7 +467,7 @@ static int decode_type_cluster_ext(ClusterStream *s, DartCtx *ctx, RList *type_l
|
||||
for (ut64 i = 0; i < count; i++) {
|
||||
DartTypeInfo *ti = R_NEW0 (DartTypeInfo);
|
||||
ti->ref_id = (*ref_counter)++;
|
||||
ti->kind = kTypeCid;
|
||||
ti->kind = DART_TYPE_KIND_TYPE;
|
||||
ut64 type_test_stub_ref = 0;
|
||||
ut64 hash_ref = 0;
|
||||
ut64 args_ref = 0;
|
||||
@@ -451,7 +503,7 @@ static int decode_type_parameter_cluster_ext(ClusterStream *s, DartCtx *ctx, RLi
|
||||
for (ut64 i = 0; i < count; i++) {
|
||||
DartTypeInfo *ti = R_NEW0 (DartTypeInfo);
|
||||
ti->ref_id = (*ref_counter)++;
|
||||
ti->kind = kTypeParameterCid;
|
||||
ti->kind = DART_TYPE_KIND_TYPE_PARAMETER;
|
||||
ut64 type_test_stub_ref = 0;
|
||||
ut64 hash_ref = 0;
|
||||
ut64 owner_ref = 0;
|
||||
@@ -518,7 +570,7 @@ static int decode_function_type_cluster_ext(ClusterStream *s, DartCtx *ctx, RLis
|
||||
for (ut64 i = 0; i < count; i++) {
|
||||
DartTypeInfo *ti = R_NEW0 (DartTypeInfo);
|
||||
ti->ref_id = (*ref_counter)++;
|
||||
ti->kind = kFunctionTypeCid;
|
||||
ti->kind = DART_TYPE_KIND_FUNCTION_TYPE;
|
||||
ut64 type_test_stub_ref = 0;
|
||||
ut64 hash_ref = 0;
|
||||
if (!cs_read_ref_id (s, &type_test_stub_ref) || !cs_read_ref_id (s, &hash_ref) || !cs_read_ref_id (s, &ti->type_parameters_ref) || !cs_read_ref_id (s, &ti->result_type_ref) || !cs_read_ref_id (s, &ti->parameter_types_ref) || !cs_read_ref_id (s, &ti->named_parameter_names_ref)) {
|
||||
@@ -658,23 +710,27 @@ static char *dup_ref_string_obf(DartCtx *ctx, ut64 ref) {
|
||||
return value;
|
||||
}
|
||||
|
||||
static const char *builtin_type_name(ut64 cid) {
|
||||
switch (cid) {
|
||||
case kStringCid:
|
||||
case kOneByteStringCid:
|
||||
case kTwoByteStringCid:
|
||||
// `Type.type_class_id` is a class id packed into the type's flags, not a ref, so
|
||||
// it is compared against this layout's real ids.
|
||||
static const char *builtin_type_name(DartCtx *ctx, ut64 cid) {
|
||||
const DartVerLayout *layout = ctx? ctx->layout: NULL;
|
||||
if (legacy_cid_is (cid, dart_cid_get (layout, DART_CID_STRING)) ||
|
||||
legacy_cid_is (cid, dart_cid_get (layout, DART_CID_ONE_BYTE_STRING)) ||
|
||||
legacy_cid_is (cid, dart_cid_get (layout, DART_CID_TWO_BYTE_STRING))) {
|
||||
return "String";
|
||||
case kArrayCid:
|
||||
case kImmutableArrayCid:
|
||||
case kGrowableObjectArrayCid:
|
||||
return "List";
|
||||
case kMintCid:
|
||||
return "int";
|
||||
case kDoubleCid:
|
||||
return "double";
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
if (legacy_cid_is (cid, dart_cid_get (layout, DART_CID_ARRAY)) ||
|
||||
legacy_cid_is (cid, dart_cid_get (layout, DART_CID_IMMUTABLE_ARRAY)) ||
|
||||
legacy_cid_is (cid, dart_cid_get (layout, DART_CID_GROWABLE_OBJECT_ARRAY))) {
|
||||
return "List";
|
||||
}
|
||||
if (legacy_cid_is (cid, dart_cid_get (layout, DART_CID_MINT))) {
|
||||
return "int";
|
||||
}
|
||||
if (legacy_cid_is (cid, dart_cid_get (layout, DART_CID_DOUBLE))) {
|
||||
return "double";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static DartTypeInfo *find_type_by_ref(RList *type_list, ut64 ref) {
|
||||
@@ -855,7 +911,7 @@ static char *resolve_type_name_depth(DartCtx *ctx, RList *class_list, RList *typ
|
||||
if (R_STR_ISNOTEMPTY (ti->name)) {
|
||||
return strdup (ti->name);
|
||||
}
|
||||
if (ti->kind == kTypeParameterCid) {
|
||||
if (ti->kind == DART_TYPE_KIND_TYPE_PARAMETER) {
|
||||
bool is_function_param = (ti->flags & DART_TYPE_PARAMETER_FUNCTION_FLAG) != 0;
|
||||
const char *base_prefix = is_function_param? "F": "C";
|
||||
const char *index_prefix = is_function_param? "Y": "X";
|
||||
@@ -868,7 +924,7 @@ static char *resolve_type_name_depth(DartCtx *ctx, RList *class_list, RList *typ
|
||||
}
|
||||
return name;
|
||||
}
|
||||
if (ti->kind == kFunctionTypeCid) {
|
||||
if (ti->kind == DART_TYPE_KIND_FUNCTION_TYPE) {
|
||||
char *name = resolve_function_type_name (ctx, class_list, type_list, type_args_list, array_list, ti, depth);
|
||||
if (name) {
|
||||
ti->name = strdup (name);
|
||||
@@ -876,7 +932,7 @@ static char *resolve_type_name_depth(DartCtx *ctx, RList *class_list, RList *typ
|
||||
return name;
|
||||
}
|
||||
char *name = NULL;
|
||||
const char *builtin = builtin_type_name (ti->type_class_ref);
|
||||
const char *builtin = builtin_type_name (ctx, ti->type_class_ref);
|
||||
if (builtin) {
|
||||
name = strdup (builtin);
|
||||
} else {
|
||||
@@ -1319,7 +1375,7 @@ static void resolve_class_interfaces(DartCtx *ctx, RList *class_list, RList *typ
|
||||
char *name = resolve_type_name (ctx, class_list, type_list, type_args_list, array_list, type_ref);
|
||||
ut64 class_ref = 0;
|
||||
DartTypeInfo *ti = find_type_by_ref (type_list, type_ref);
|
||||
if (ti && ti->kind == kTypeCid) {
|
||||
if (ti && ti->kind == DART_TYPE_KIND_TYPE) {
|
||||
DartClassInfo *target_ci = find_class_by_ref (class_list, ti->type_class_ref);
|
||||
if (target_ci) {
|
||||
class_ref = target_ci->ref_id;
|
||||
@@ -1464,8 +1520,8 @@ RList *dart_pool_extract_classes(DartCtx *ctx) {
|
||||
ut64 total_refs = sh.nb + sh.no + 16;
|
||||
ctx->refs_count = total_refs;
|
||||
ctx->refs = (void **)calloc (total_refs, sizeof (void *));
|
||||
const DartModernClusterRequest req = { ctx, sh.cluster_start, snapshot_base + sh.total_len, sh.nc, sh.nb };
|
||||
modern_classes = dart_modern_extract_classes_from_clusters (&req, class_list);
|
||||
const ModernReq req = { ctx, sh.cluster_start, snapshot_base + sh.total_len, sh.nc, sh.nb };
|
||||
modern_classes = modern_extract_classes (&req, class_list);
|
||||
if (modern_classes) {
|
||||
if (ctx->verbose > 0) {
|
||||
int modern_fields = 0;
|
||||
@@ -1504,71 +1560,50 @@ RList *dart_pool_extract_classes(DartCtx *ctx) {
|
||||
.end = snapshot_base + sh.total_len
|
||||
};
|
||||
ut64 ref_counter = sh.nb + 1;
|
||||
const LegacyClusterCids lc = legacy_cluster_cids (ctx->layout);
|
||||
for (ut64 ci2 = 0; ci2 < sh.nc && stream.cursor < stream.end; ci2++) {
|
||||
uint32_t tags = 0;
|
||||
if (!cs_read_u32 (&stream, &tags)) {
|
||||
ut32 tags = 0;
|
||||
bool tags_read = synthetic_legacy_parse
|
||||
? cs_read_u32 (&stream, &tags)
|
||||
: cs_read_tagged32 (&stream, &tags);
|
||||
if (!tags_read) {
|
||||
break;
|
||||
}
|
||||
uint32_t cid = (tags >> 12) & 0xFFFFF;
|
||||
const ut32 cid = dart_cid_from_tags (ctx, tags);
|
||||
bool is_canonical = ((tags >> 1) & 1) != 0;
|
||||
(void)is_canonical;
|
||||
if (ctx->verbose > 1) {
|
||||
fprintf (stderr, "[r2flutter] Cluster %" PRIu64 ": cid=%u (Class=%d,Field=%d,String=%d)\n", ci2, cid, kClassCid, kFieldCid_extract, kOneByteStringCid);
|
||||
fprintf (stderr, "[r2flutter] Cluster %" PRIu64 ": cid=%u (Class=%d,Field=%d,String=%d)\n", ci2, cid, lc.class_, lc.field, lc.one_byte_string);
|
||||
}
|
||||
int rc = 0;
|
||||
switch (cid) {
|
||||
case kOneByteStringCid:
|
||||
case kTwoByteStringCid:
|
||||
case kStringCid:
|
||||
if (legacy_cid_is (cid, lc.one_byte_string) || legacy_cid_is (cid, lc.two_byte_string) || legacy_cid_is (cid, lc.string)) {
|
||||
rc = decode_string_cluster (&stream, ctx, &ref_counter, false);
|
||||
break;
|
||||
case kClassCid:
|
||||
} else if (legacy_cid_is (cid, lc.class_)) {
|
||||
rc = decode_class_cluster_ext (&stream, ctx, class_list, &ref_counter);
|
||||
break;
|
||||
case kFieldCid_extract:
|
||||
} else if (legacy_cid_is (cid, lc.field)) {
|
||||
rc = decode_field_cluster_ext (&stream, ctx, field_list, &ref_counter);
|
||||
break;
|
||||
case kFunctionCid:
|
||||
} else if (legacy_cid_is (cid, lc.function)) {
|
||||
rc = decode_function_cluster_ext (&stream, ctx, method_list, &ref_counter);
|
||||
if (rc < 0) {
|
||||
rc = 0;
|
||||
skip_generic_cluster (&stream);
|
||||
}
|
||||
break;
|
||||
case kTypeCid:
|
||||
} else if (legacy_cid_is (cid, lc.type)) {
|
||||
rc = decode_type_cluster_ext (&stream, ctx, type_list, &ref_counter);
|
||||
break;
|
||||
case kFunctionTypeCid:
|
||||
} else if (legacy_cid_is (cid, lc.function_type)) {
|
||||
rc = decode_function_type_cluster_ext (&stream, ctx, type_list, &ref_counter);
|
||||
if (rc < 0) {
|
||||
rc = 0;
|
||||
skip_generic_cluster (&stream);
|
||||
}
|
||||
break;
|
||||
case kTypeParameterCid:
|
||||
} else if (legacy_cid_is (cid, lc.type_parameter)) {
|
||||
rc = decode_type_parameter_cluster_ext (&stream, ctx, type_list, &ref_counter);
|
||||
break;
|
||||
case kTypeArgumentsCid:
|
||||
} else if (legacy_cid_is (cid, lc.type_arguments)) {
|
||||
rc = decode_type_arguments_cluster_ext (&stream, ctx, type_args_list, &ref_counter);
|
||||
if (rc < 0) {
|
||||
rc = 0;
|
||||
skip_generic_cluster (&stream);
|
||||
}
|
||||
break;
|
||||
case kArrayCid:
|
||||
case kImmutableArrayCid:
|
||||
} else if (legacy_cid_is (cid, lc.array) || legacy_cid_is (cid, lc.immutable_array)) {
|
||||
rc = decode_array_cluster_ext (&stream, ctx, array_list, &ref_counter);
|
||||
if (rc < 0) {
|
||||
rc = 0;
|
||||
skip_generic_cluster (&stream);
|
||||
}
|
||||
break;
|
||||
case kLibraryCid_extract:
|
||||
} else if (legacy_cid_is (cid, lc.library)) {
|
||||
rc = decode_library_cluster_ext (&stream, ctx, libraries, &ref_counter);
|
||||
break;
|
||||
default:
|
||||
} else {
|
||||
skip_generic_cluster (&stream);
|
||||
}
|
||||
// A cluster we recognise but cannot decode is not fatal: skip its
|
||||
// bytes and carry on rather than abandoning the whole snapshot.
|
||||
if (rc < 0 && legacy_cid_recoverable (cid, &lc)) {
|
||||
rc = 0;
|
||||
skip_generic_cluster (&stream);
|
||||
break;
|
||||
}
|
||||
if (rc < 0) {
|
||||
break;
|
||||
@@ -1643,15 +1678,14 @@ RList *dart_pool_extract_classes(DartCtx *ctx) {
|
||||
r_list_free (method_list);
|
||||
r_list_free (field_list);
|
||||
if (ctx->dump_fields && r_list_length (class_list) > 0) {
|
||||
ut64 kAlign = ctx->layout && ctx->layout->max_alignment? (ut64)ctx->layout->max_alignment: 16;
|
||||
ut64 data_image_base = snapshot_base + ((sh.total_len + (kAlign - 1)) & ~ (kAlign - 1));
|
||||
ut64 data_image_base = dart_snapshot_data_image_base (snapshot_base, sh.total_len);
|
||||
ut64 data_image_end = ctx->iso_instr? ctx->iso_instr: (data_image_base + (4ULL << 20));
|
||||
scan_fields_from_data_image (ctx, class_list, data_image_base, data_image_end);
|
||||
scan_methods_from_data_image (ctx, class_list, data_image_base, data_image_end);
|
||||
if (ctx->vm_data) {
|
||||
DartSnapshotHeader vm_sh;
|
||||
if (dart_snapshot_header_read (ctx, ctx->vm_data, &vm_sh)) {
|
||||
ut64 vm_data_base = ctx->vm_data + ((vm_sh.total_len + (kAlign - 1)) & ~ (kAlign - 1));
|
||||
ut64 vm_data_base = dart_snapshot_data_image_base (ctx->vm_data, vm_sh.total_len);
|
||||
ut64 vm_data_end = ctx->vm_instr? ctx->vm_instr: (vm_data_base + (4ULL << 20));
|
||||
scan_fields_from_data_image (ctx, class_list, vm_data_base, vm_data_end);
|
||||
scan_methods_from_data_image (ctx, class_list, vm_data_base, vm_data_end);
|
||||
@@ -1665,6 +1699,9 @@ RList *dart_pool_extract_classes(DartCtx *ctx) {
|
||||
static void dump_class_json(PJ *pj, const DartClassInfo *ci) {
|
||||
pj_o (pj);
|
||||
pj_kn (pj, "ref", ci->ref_id);
|
||||
if (ci->class_id) {
|
||||
pj_ki (pj, "class_id", ci->class_id);
|
||||
}
|
||||
if (ci->name) {
|
||||
pj_ks (pj, "name", ci->name);
|
||||
}
|
||||
@@ -1677,10 +1714,15 @@ static void dump_class_json(PJ *pj, const DartClassInfo *ci) {
|
||||
}
|
||||
pj_end (pj);
|
||||
}
|
||||
if (ci->super_class_name || ci->super_class_ref) {
|
||||
if (ci->super_class_name || ci->super_class_ref || ci->super_type_ref) {
|
||||
pj_k (pj, "super");
|
||||
pj_o (pj);
|
||||
pj_kn (pj, "ref", ci->super_class_ref);
|
||||
if (ci->super_class_ref) {
|
||||
pj_kn (pj, "ref", ci->super_class_ref);
|
||||
}
|
||||
if (ci->super_type_ref) {
|
||||
pj_kn (pj, "type_ref", ci->super_type_ref);
|
||||
}
|
||||
if (ci->super_class_name) {
|
||||
pj_ks (pj, "name", ci->super_class_name);
|
||||
}
|
||||
@@ -1711,8 +1753,14 @@ static void dump_class_json(PJ *pj, const DartClassInfo *ci) {
|
||||
pj_k (pj, "layout");
|
||||
pj_o (pj);
|
||||
pj_ki (pj, "instance_size", ci->instance_size);
|
||||
if (ci->next_field_offset) {
|
||||
pj_ki (pj, "next_field_offset", ci->next_field_offset);
|
||||
}
|
||||
pj_ki (pj, "type_params", ci->num_type_parameters);
|
||||
pj_ki (pj, "type_arg_offset", ci->type_argument_offset);
|
||||
if (ci->field_bitmap_present) {
|
||||
pj_kn (pj, "field_bitmap", ci->field_bitmap);
|
||||
}
|
||||
pj_end (pj);
|
||||
pj_k (pj, "flags");
|
||||
pj_o (pj);
|
||||
@@ -1968,6 +2016,104 @@ static void append_comment_cmd(RStrBuf *sb, ut64 addr, const char *msg) {
|
||||
free (b64);
|
||||
}
|
||||
|
||||
#ifdef R2_HAS_BIN_ATTR
|
||||
static ut64 dart_class_attrbits(const DartClassInfo *ci) {
|
||||
ut64 a = 0;
|
||||
if (ci->flags & DART_CLASS_ABSTRACT) {
|
||||
a |= R_BIN_ATTR_ABSTRACT;
|
||||
}
|
||||
if (ci->flags & DART_CLASS_ENUM) {
|
||||
a |= R_BIN_ATTR_ENUM;
|
||||
}
|
||||
if (ci->flags & DART_CLASS_MIXIN) {
|
||||
a |= R_BIN_ATTR_MIXIN;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
static ut64 dart_field_attrbits(const DartFieldInfo *fi) {
|
||||
ut64 a = 0;
|
||||
if (fi->flags & DART_FIELD_STATIC) {
|
||||
a |= R_BIN_ATTR_STATIC;
|
||||
}
|
||||
if (fi->flags & DART_FIELD_FINAL) {
|
||||
a |= R_BIN_ATTR_FINAL;
|
||||
}
|
||||
if (fi->flags & DART_FIELD_CONST) {
|
||||
a |= R_BIN_ATTR_CONST;
|
||||
}
|
||||
if (fi->flags & DART_FIELD_LATE) {
|
||||
a |= R_BIN_ATTR_LATE;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
static ut64 dart_method_attrbits(const DartMethodInfo *mi) {
|
||||
const char *kind = method_kind_name (mi->kind_tag);
|
||||
ut64 a = method_is_static (mi)? R_BIN_ATTR_STATIC: 0;
|
||||
if (strstr (kind, "Getter")) {
|
||||
a |= R_BIN_ATTR_GETTER;
|
||||
} else if (strstr (kind, "Setter")) {
|
||||
a |= R_BIN_ATTR_SETTER;
|
||||
} else if (!strcmp (kind, "Constructor")) {
|
||||
a |= R_BIN_ATTR_CONSTRUCTOR;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
#endif
|
||||
|
||||
// the dart_*_ic_args helpers build the ic+ argument string (leading space, may be empty)
|
||||
// using the attribute names that r_bin_attr_forclass/symbol/field parse back
|
||||
|
||||
static char *dart_class_ic_args(const DartClassInfo *ci) {
|
||||
#ifdef R2_HAS_BIN_ATTR
|
||||
RStrBuf *sb = r_strbuf_new ("");
|
||||
if (ci->instance_size > 0) {
|
||||
r_strbuf_appendf (sb, " size=%u", ci->instance_size);
|
||||
}
|
||||
const char *ns = ci->library_name;
|
||||
if (R_STR_ISNOTEMPTY (ns) && !ns[strspn (ns, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.:/-")]) {
|
||||
r_strbuf_appendf (sb, " ns=%s", ns);
|
||||
}
|
||||
char *attrs = r_bin_attr_tostring (dart_class_attrbits (ci), false);
|
||||
if (R_STR_ISNOTEMPTY (attrs)) {
|
||||
r_strbuf_appendf (sb, " %s", attrs);
|
||||
}
|
||||
free (attrs);
|
||||
return r_strbuf_drain (sb);
|
||||
#else
|
||||
(void)ci;
|
||||
return strdup ("");
|
||||
#endif
|
||||
}
|
||||
|
||||
static char *dart_field_ic_args(const DartFieldInfo *fi) {
|
||||
#ifdef R2_HAS_BIN_ATTR
|
||||
char *attrs = r_bin_attr_tostring (dart_field_attrbits (fi), false);
|
||||
char *res = r_str_newf (" %s%s%s", field_c_type (fi), R_STR_ISNOTEMPTY (attrs)? " ": "", r_str_get (attrs));
|
||||
free (attrs);
|
||||
return res;
|
||||
#else
|
||||
return r_str_newf (" %s", field_c_type (fi));
|
||||
#endif
|
||||
}
|
||||
|
||||
static char *dart_method_ic_args(const DartMethodInfo *mi) {
|
||||
#ifdef R2_HAS_BIN_ATTR
|
||||
char *attrs = r_bin_attr_tostring (dart_method_attrbits (mi), false);
|
||||
if (R_STR_ISEMPTY (attrs)) {
|
||||
free (attrs);
|
||||
return strdup ("");
|
||||
}
|
||||
char *res = r_str_newf (" %s", attrs);
|
||||
free (attrs);
|
||||
return res;
|
||||
#else
|
||||
(void)mi;
|
||||
return strdup ("");
|
||||
#endif
|
||||
}
|
||||
|
||||
static void append_ic_inheritance_cmd(RStrBuf *sb, const char *class_name, const char *base_name) {
|
||||
if (!sb || R_STR_ISEMPTY (class_name) || R_STR_ISEMPTY (base_name)) {
|
||||
return;
|
||||
@@ -1981,15 +2127,19 @@ static void append_ic_field_cmd(RStrBuf *sb, const char *class_name, const DartF
|
||||
return;
|
||||
}
|
||||
char *field_name = member_export_name (fi->name, "field");
|
||||
r_strbuf_appendf (sb, "ic+%s..%s %s @ 0x%x\n", class_name, field_name, field_c_type (fi), fi->offset);
|
||||
char *args = dart_field_ic_args (fi);
|
||||
r_strbuf_appendf (sb, "'@0x%x'ic+%s..%s%s\n", fi->offset, class_name, field_name, args);
|
||||
free (args);
|
||||
free (field_name);
|
||||
}
|
||||
|
||||
static void append_ic_method_cmd(RStrBuf *sb, const char *class_name, const char *method_name, ut64 addr) {
|
||||
static void append_ic_method_cmd(RStrBuf *sb, const char *class_name, const char *method_name, ut64 addr, const DartMethodInfo *mi) {
|
||||
if (!sb || R_STR_ISEMPTY (class_name) || R_STR_ISEMPTY (method_name)) {
|
||||
return;
|
||||
}
|
||||
r_strbuf_appendf (sb, "ic+%s.%s @ 0x%" PFMT64x "\n", class_name, method_name, addr);
|
||||
char *args = dart_method_ic_args (mi);
|
||||
r_strbuf_appendf (sb, "ic+%s.%s%s @ 0x%" PFMT64x "\n", class_name, method_name, args, addr);
|
||||
free (args);
|
||||
}
|
||||
|
||||
static void dump_class_r2_metadata(RStrBuf *sb, const DartClassInfo *ci, bool quiet) {
|
||||
@@ -2001,7 +2151,9 @@ static void dump_class_r2_metadata(RStrBuf *sb, const DartClassInfo *ci, bool qu
|
||||
if (!quiet) {
|
||||
r_strbuf_appendf (sb, "# r2 class %s original=%s\n", class_ref, ci->name);
|
||||
}
|
||||
r_strbuf_appendf (sb, "ic+%s @ 0\n", class_ref);
|
||||
char *cargs = dart_class_ic_args (ci);
|
||||
r_strbuf_appendf (sb, "ic+%s%s @ 0\n", class_ref, cargs);
|
||||
free (cargs);
|
||||
r_strbuf_appendf (sb, "ac %s\n", class_ref);
|
||||
#if 0
|
||||
// TOO SLOW
|
||||
@@ -2058,7 +2210,7 @@ static void dump_class_r2_metadata(RStrBuf *sb, const DartClassInfo *ci, bool qu
|
||||
const char *method_ref = R_STR_ISNOTEMPTY (method_name)? method_name: "method";
|
||||
char *cc = method_dyncc (ci, mi);
|
||||
ut64 addr = method_export_addr (mi);
|
||||
append_ic_method_cmd (sb, class_ref, method_ref, addr);
|
||||
append_ic_method_cmd (sb, class_ref, method_ref, addr, mi);
|
||||
if (!quiet) {
|
||||
r_strbuf_appendf (sb, "# method %s.%s kind=%s mode=%s cc=%s", class_ref, method_ref, method_kind_name (mi->kind_tag), method_mode_name (mi), cc);
|
||||
if (R_STR_ISNOTEMPTY (mi->signature)) {
|
||||
@@ -2248,6 +2400,7 @@ char *dart_pool_dump_classes(DartCtx *ctx, int fmt) {
|
||||
dump_class_text (sb, ci, fmt, type_view, quiet);
|
||||
}
|
||||
if (fmt == 'r') {
|
||||
r_strbuf_append (sb, "e bin.lang=dart\n");
|
||||
if (!quiet) {
|
||||
r_strbuf_append (sb, "# Dart class symbols and analysis metadata\n");
|
||||
}
|
||||
@@ -2296,8 +2449,19 @@ static RBinClass *core_bin_get_or_add_class(RCore *core, const char *class_name,
|
||||
}
|
||||
klass->origin = R_BIN_CLASS_ORIGIN_USER;
|
||||
klass->addr = 0;
|
||||
klass->instance_size = ci? ci->instance_size: 0;
|
||||
klass->index = r_list_length (klasses);
|
||||
#ifdef R2_HAS_BIN_ATTR
|
||||
if (ci) {
|
||||
klass->attr.size = ci->instance_size;
|
||||
klass->attr.flags = dart_class_attrbits (ci);
|
||||
klass->attr.lang = R_BIN_LANG_DART;
|
||||
if (R_STR_ISNOTEMPTY (ci->library_name)) {
|
||||
klass->attr.ns = strdup (ci->library_name);
|
||||
}
|
||||
}
|
||||
#else
|
||||
klass->instance_size = ci? ci->instance_size: 0;
|
||||
#endif
|
||||
r_list_append (klasses, klass);
|
||||
return klass;
|
||||
}
|
||||
@@ -2361,9 +2525,16 @@ static void core_apply_bin_field(RBinClass *klass, const DartFieldInfo *fi, int
|
||||
field->vaddr = fi->offset;
|
||||
field->paddr = fi->offset;
|
||||
field->value = fi->offset;
|
||||
#ifdef R2_HAS_BIN_ATTR
|
||||
field->attr.size = 8;
|
||||
field->attr.offset = fi->offset;
|
||||
field->attr.kind = R_BIN_FIELD_KIND_FIELD;
|
||||
field->attr.flags = dart_field_attrbits (fi);
|
||||
#else
|
||||
field->size = 8;
|
||||
field->offset = fi->offset;
|
||||
field->kind = R_BIN_FIELD_KIND_FIELD;
|
||||
#endif
|
||||
field->type = r_bin_name_new (R_STR_ISNOTEMPTY (fi->type_name)? fi->type_name: "dynamic");
|
||||
(*count)++;
|
||||
}
|
||||
@@ -2389,7 +2560,12 @@ static void core_apply_bin_method(RBinClass *klass, const DartMethodInfo *mi, in
|
||||
sym->type = "method";
|
||||
sym->paddr = addr;
|
||||
sym->vaddr = addr;
|
||||
#ifdef R2_HAS_BIN_ATTR
|
||||
sym->attr.flags = dart_method_attrbits (mi);
|
||||
sym->attr.lang = R_BIN_LANG_DART;
|
||||
#else
|
||||
sym->size = 0;
|
||||
#endif
|
||||
(*count)++;
|
||||
}
|
||||
free (method_name);
|
||||
@@ -2484,14 +2660,26 @@ static void core_apply_type(RCore *core, const DartClassInfo *ci, const char *cl
|
||||
.name = field_name,
|
||||
.type = strdup (field_c_type (fi)),
|
||||
.offset = fi->offset,
|
||||
#if R2_ABIVERSION >= 125
|
||||
.bitsize = 64,
|
||||
#else
|
||||
.size = 64,
|
||||
};
|
||||
#endif
|
||||
};
|
||||
#if R2_ABIVERSION >= 125
|
||||
RAnalStructMember *slot = RVecAnalTypeMember_emplace_back (&type->struct_data.members);
|
||||
#else
|
||||
RAnalStructMember *slot = RVecAnalStructMember_emplace_back (&type->struct_data.members);
|
||||
#endif
|
||||
if (slot) {
|
||||
*slot = member;
|
||||
(*field_count)++;
|
||||
} else {
|
||||
#if R2_ABIVERSION >= 125
|
||||
anal_type_member_fini (&member);
|
||||
#else
|
||||
anal_struct_member_fini (&member);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2547,7 +2735,9 @@ static void core_apply_ic_field(RCore *core, const char *class_name, const DartF
|
||||
return;
|
||||
}
|
||||
char *field_name = member_export_name (fi->name, "field");
|
||||
core_apply_ic_command (core, r_str_newf ("ic+%s..%s %s @ 0x%x", class_name, field_name, field_c_type (fi), fi->offset));
|
||||
char *args = dart_field_ic_args (fi);
|
||||
core_apply_ic_command (core, r_str_newf ("'@0x%x'ic+%s..%s%s", fi->offset, class_name, field_name, args));
|
||||
free (args);
|
||||
free (field_name);
|
||||
}
|
||||
|
||||
@@ -2557,7 +2747,9 @@ static void core_apply_ic_method(RCore *core, const char *class_name, const Dart
|
||||
}
|
||||
char *method_name = member_export_name (mi->name, "method");
|
||||
ut64 addr = method_export_addr (mi);
|
||||
core_apply_ic_command (core, r_str_newf ("ic+%s.%s @ 0x%" PFMT64x, class_name, method_name, addr));
|
||||
char *args = dart_method_ic_args (mi);
|
||||
core_apply_ic_command (core, r_str_newf ("ic+%s.%s%s @ 0x%" PFMT64x, class_name, method_name, args, addr));
|
||||
free (args);
|
||||
free (method_name);
|
||||
}
|
||||
|
||||
@@ -2565,7 +2757,9 @@ static void core_apply_ic_class(RCore *core, const DartClassInfo *ci, const char
|
||||
if (!core || !ci || R_STR_ISEMPTY (class_name)) {
|
||||
return;
|
||||
}
|
||||
core_apply_ic_command (core, r_str_newf ("ic+%s @ 0", class_name));
|
||||
char *cargs = dart_class_ic_args (ci);
|
||||
core_apply_ic_command (core, r_str_newf ("ic+%s%s @ 0", class_name, cargs));
|
||||
free (cargs);
|
||||
if (R_STR_ISNOTEMPTY (ci->super_class_name)) {
|
||||
char *super_name = class_export_name (ci->super_class_name);
|
||||
core_apply_ic_inheritance (core, class_name, super_name);
|
||||
@@ -2612,6 +2806,7 @@ int dart_pool_apply_classes_to_core(DartCtx *ctx) {
|
||||
int method_count = 0;
|
||||
int type_field_count = 0;
|
||||
int anal_method_count = 0;
|
||||
r_core_cmd0 (ctx->core, "e bin.lang=dart");
|
||||
RListIter *it;
|
||||
DartClassInfo *ci;
|
||||
r_list_foreach (classes, it, ci) {
|
||||
|
||||
@@ -16,22 +16,6 @@ void free_dart_string(void *p) {
|
||||
}
|
||||
}
|
||||
|
||||
static void free_dart_class(void *p) {
|
||||
DartClass *dc = (DartClass *)p;
|
||||
if (dc) {
|
||||
free (dc->name);
|
||||
free (dc);
|
||||
}
|
||||
}
|
||||
|
||||
static void free_dart_func(void *p) {
|
||||
DartPoolFunction *df = (DartPoolFunction *)p;
|
||||
if (df) {
|
||||
free (df->name);
|
||||
free (df);
|
||||
}
|
||||
}
|
||||
|
||||
int decode_string_cluster(ClusterStream *s, DartCtx *ctx, ut64 *ref_counter, bool is_canonical) {
|
||||
(void)is_canonical;
|
||||
ut64 count = 0;
|
||||
@@ -98,88 +82,6 @@ int decode_string_cluster(ClusterStream *s, DartCtx *ctx, ut64 *ref_counter, boo
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int decode_class_cluster(ClusterStream *s, DartCtx *ctx, ut64 *ref_counter, bool is_canonical) {
|
||||
(void)is_canonical;
|
||||
ut64 count = 0;
|
||||
if (!cs_read_unsigned (s, &count)) {
|
||||
return -1;
|
||||
}
|
||||
if (count == 0 || count > 50000) {
|
||||
return 0;
|
||||
}
|
||||
if (ctx->verbose > 1) {
|
||||
fprintf (stderr, "[r2flutter] Class cluster: count=%" PRIu64 "\n", count);
|
||||
}
|
||||
for (ut64 i = 0; i < count; i++) {
|
||||
DartClass *dc = R_NEW0 (DartClass);
|
||||
dc->ref_id = (*ref_counter)++;
|
||||
uint32_t instance_size = 0;
|
||||
cs_read_u32 (s, &instance_size);
|
||||
dc->instance_size = (int)instance_size;
|
||||
ut64 name_ref = 0;
|
||||
cs_read_ref_id (s, &name_ref);
|
||||
dc->name_ref = name_ref;
|
||||
ut64 library_ref = 0;
|
||||
cs_read_ref_id (s, &library_ref);
|
||||
dc->library_ref = library_ref;
|
||||
ut64 skip_count = 6;
|
||||
for (ut64 j = 0; j < skip_count; j++) {
|
||||
ut64 dummy = 0;
|
||||
cs_read_ref_id (s, &dummy);
|
||||
}
|
||||
if (ctx->classes) {
|
||||
r_list_append (ctx->classes, dc);
|
||||
}
|
||||
if (ctx->refs && dc->ref_id < ctx->refs_count) {
|
||||
ctx->refs[dc->ref_id] = dc;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int decode_function_cluster(ClusterStream *s, DartCtx *ctx, ut64 *ref_counter, ut64 iso_instr, bool is_canonical) {
|
||||
(void)is_canonical;
|
||||
ut64 count = 0;
|
||||
if (!cs_read_unsigned (s, &count)) {
|
||||
return -1;
|
||||
}
|
||||
if (count == 0 || count > 100000) {
|
||||
return 0;
|
||||
}
|
||||
if (ctx->verbose > 1) {
|
||||
fprintf (stderr, "[r2flutter] Function cluster: count=%" PRIu64 "\n", count);
|
||||
}
|
||||
for (ut64 i = 0; i < count; i++) {
|
||||
DartPoolFunction *df = R_NEW0 (DartPoolFunction);
|
||||
df->ref_id = (*ref_counter)++;
|
||||
ut64 name_ref = 0;
|
||||
cs_read_ref_id (s, &name_ref);
|
||||
df->name_ref = name_ref;
|
||||
ut64 owner_ref = 0;
|
||||
cs_read_ref_id (s, &owner_ref);
|
||||
df->owner_ref = owner_ref;
|
||||
ut64 code_idx = 0;
|
||||
cs_read_unsigned (s, &code_idx);
|
||||
if (code_idx > 0 && iso_instr > 0) {
|
||||
df->entry_point = iso_instr + (code_idx - 1) * 4;
|
||||
}
|
||||
ut64 skip_refs = 4;
|
||||
for (ut64 j = 0; j < skip_refs; j++) {
|
||||
ut64 dummy = 0;
|
||||
cs_read_ref_id (s, &dummy);
|
||||
}
|
||||
uint32_t kind_tag = 0;
|
||||
cs_read_u32 (s, &kind_tag);
|
||||
if (ctx->functions) {
|
||||
r_list_append (ctx->functions, df);
|
||||
}
|
||||
if (ctx->refs && df->ref_id < ctx->refs_count) {
|
||||
ctx->refs[df->ref_id] = df;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void skip_generic_cluster(ClusterStream *stream) {
|
||||
ut64 count = 0;
|
||||
if (cs_read_unsigned (stream, &count)) {
|
||||
@@ -198,99 +100,3 @@ void skip_generic_cluster(ClusterStream *stream) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int deserialize_clusters(DartCtx *ctx, ut64 cluster_start, ut64 cluster_end, ut64 num_clusters, ut64 iso_instr) {
|
||||
if (!ctx || !ctx->core || cluster_start >= cluster_end) {
|
||||
return -1;
|
||||
}
|
||||
ctx->strings = r_list_newf (free_dart_string);
|
||||
ctx->classes = r_list_newf (free_dart_class);
|
||||
ctx->functions = r_list_newf (free_dart_func);
|
||||
ut64 total_refs = ctx->num_base_objects + ctx->num_objects + 16;
|
||||
ctx->refs_count = total_refs;
|
||||
ctx->refs = (void **)calloc (total_refs, sizeof (void *));
|
||||
ClusterStream stream = {
|
||||
.ctx = ctx,
|
||||
.cursor = cluster_start,
|
||||
.end = cluster_end
|
||||
};
|
||||
ut64 ref_counter = ctx->num_base_objects + 1;
|
||||
for (ut64 ci = 0; ci < num_clusters && stream.cursor < stream.end; ci++) {
|
||||
uint32_t tags = 0;
|
||||
if (!cs_read_u32 (&stream, &tags)) {
|
||||
if (ctx->verbose > 0) {
|
||||
fprintf (stderr, "[r2flutter] Failed to read cluster tag at %" PRIu64 "\n", ci);
|
||||
}
|
||||
break;
|
||||
}
|
||||
uint32_t cid = (tags >> 12) & 0xFFFFF;
|
||||
bool is_canonical = ((tags >> 1) & 1) != 0;
|
||||
if (ctx->verbose > 1) {
|
||||
fprintf (stderr, "[r2flutter] Cluster %" PRIu64 ": cid=%u canonical=%d cursor=0x%" PFMT64x "\n", ci, cid, is_canonical, stream.cursor);
|
||||
}
|
||||
int rc = 0;
|
||||
switch (cid) {
|
||||
case kOneByteStringCid:
|
||||
case kTwoByteStringCid:
|
||||
case kStringCid:
|
||||
rc = decode_string_cluster (&stream, ctx, &ref_counter, is_canonical);
|
||||
break;
|
||||
case kClassCid:
|
||||
rc = decode_class_cluster (&stream, ctx, &ref_counter, is_canonical);
|
||||
break;
|
||||
case kFunctionCid:
|
||||
rc = decode_function_cluster (&stream, ctx, &ref_counter, iso_instr, is_canonical);
|
||||
break;
|
||||
default:
|
||||
skip_generic_cluster (&stream);
|
||||
break;
|
||||
}
|
||||
if (rc < 0) {
|
||||
if (ctx->verbose > 0) {
|
||||
fprintf (stderr, "[r2flutter] Error decoding cluster cid=%u\n", cid);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ctx->verbose > 0) {
|
||||
fprintf (stderr, "[r2flutter] Decoded: strings=%d classes=%d functions=%d\n", ctx->strings? r_list_length (ctx->strings): 0, ctx->classes? r_list_length (ctx->classes): 0, ctx->functions? r_list_length (ctx->functions): 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void resolve_names(DartCtx *ctx) {
|
||||
if (!ctx || !ctx->refs) {
|
||||
return;
|
||||
}
|
||||
if (ctx->classes) {
|
||||
RListIter *it;
|
||||
DartClass *dc;
|
||||
r_list_foreach (ctx->classes, it, dc) {
|
||||
if (!dc || dc->name) {
|
||||
continue;
|
||||
}
|
||||
if (dc->name_ref > 0 && dc->name_ref < ctx->refs_count) {
|
||||
DartString *ds = (DartString *)ctx->refs[dc->name_ref];
|
||||
if (ds && ds->value) {
|
||||
dc->name = strdup (ds->value);
|
||||
dart_obf_apply (ctx, &dc->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ctx->functions) {
|
||||
RListIter *it;
|
||||
DartPoolFunction *df;
|
||||
r_list_foreach (ctx->functions, it, df) {
|
||||
if (!df || df->name) {
|
||||
continue;
|
||||
}
|
||||
if (df->name_ref > 0 && df->name_ref < ctx->refs_count) {
|
||||
DartString *ds = (DartString *)ctx->refs[df->name_ref];
|
||||
if (ds && ds->value) {
|
||||
df->name = strdup (ds->value);
|
||||
dart_obf_apply (ctx, &df->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,21 +21,6 @@ void init_function_layout(DartCtx *ctx, DartFunctionLayout *fl) {
|
||||
}
|
||||
}
|
||||
|
||||
static ut32 extract_cid_from_header(DartCtx *ctx, ut64 header) {
|
||||
if (!ctx || !ctx->layout) {
|
||||
return 0;
|
||||
}
|
||||
switch (ctx->layout->tag_style) {
|
||||
case DART_TAG_STYLE_OBJECT_HEADER:
|
||||
return (ut32) ((header >> 12) & 0xFFFFF);
|
||||
case DART_TAG_STYLE_CID_SHIFT1:
|
||||
return (ut32) (header >> 1);
|
||||
case DART_TAG_STYLE_CID_INT32:
|
||||
default:
|
||||
return (ut32) ((header >> 12) & 0xFFFFF);
|
||||
}
|
||||
}
|
||||
|
||||
static bool read_object_pointer(DartCtx *ctx, const ut8 *buf, ut32 off, bool use_compressed, ut64 data_base, ut64 data_end, bool restrict_range, ut64 *out_addr) {
|
||||
(void)ctx;
|
||||
if (use_compressed) {
|
||||
@@ -79,7 +64,8 @@ bool read_data_image_field(DartCtx *ctx, ut64 pos, ut64 data_start, ut64 data_en
|
||||
return false;
|
||||
}
|
||||
ut64 header = r_read_le64 (hdr);
|
||||
if ((int) ((header >> 12) & 0xFFFFF) != kFieldCid) {
|
||||
const int cid_field = dart_cid_get (ctx->layout, DART_CID_FIELD);
|
||||
if (cid_field < 0 || (int)dart_cid_from_object_header (header) != cid_field) {
|
||||
return false;
|
||||
}
|
||||
bool use_compressed = ctx->compressed_word_size == 4;
|
||||
@@ -172,9 +158,10 @@ bool read_data_image_method(DartCtx *ctx, ut64 pos, ut64 data_start, ut64 data_e
|
||||
if (!read_mem (ctx, pos, buf, sizeof (buf))) {
|
||||
return false;
|
||||
}
|
||||
const int resolved_function_cid = dart_cid_get (ctx->layout, DART_CID_FUNCTION);
|
||||
const int cid_function = resolved_function_cid >= 0? resolved_function_cid: kFunctionCid;
|
||||
if ((int)extract_cid_from_header (ctx, r_read_le64 (buf)) != cid_function) {
|
||||
// No Function id for this layout means we cannot tell a Function object from
|
||||
// anything else; report nothing rather than match against a guessed id.
|
||||
const int cid_function = dart_cid_get (ctx->layout, DART_CID_FUNCTION);
|
||||
if (cid_function < 0 || (int)dart_cid_from_object_header (r_read_le64 (buf)) != cid_function) {
|
||||
return false;
|
||||
}
|
||||
ut64 entry = r_read_le64 (buf + fl->entry_off);
|
||||
|
||||
+190
-32
@@ -3,6 +3,21 @@
|
||||
#include "dart_pool_parse_priv.h"
|
||||
|
||||
static bool looks_like_data_snapshot(DartCtx *ctx, ut64 base, ut64 *out_total_len) {
|
||||
DartSnapshotHeader outer;
|
||||
if (!dart_snapshot_fingerprint_read (ctx, base, &outer)) {
|
||||
return false;
|
||||
}
|
||||
if (!outer.kind || outer.kind > 4) {
|
||||
return false;
|
||||
}
|
||||
const bool known_layout = dart_version_from_hash (outer.hash) != NULL ||
|
||||
R_STR_ISNOTEMPTY (ctx? ctx->dart_version_override: NULL);
|
||||
if (!known_layout) {
|
||||
if (out_total_len) {
|
||||
*out_total_len = outer.total_len;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
DartSnapshotHeader hdr;
|
||||
if (!dart_snapshot_header_read (ctx, base, &hdr)) {
|
||||
return false;
|
||||
@@ -22,6 +37,16 @@ static bool looks_like_data_snapshot(DartCtx *ctx, ut64 base, ut64 *out_total_le
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool dart_snapshot_is_single(DartCtx *ctx, ut64 base) {
|
||||
DartSnapshotHeader outer;
|
||||
if (!base || !dart_snapshot_fingerprint_read (ctx, base, &outer)) {
|
||||
return false;
|
||||
}
|
||||
const char *version = dart_version_from_hash (outer.hash);
|
||||
const DartVerLayout *layout = version? dart_profile_from_version (version): NULL;
|
||||
return layout && layout->single_snapshot;
|
||||
}
|
||||
|
||||
static void pick_vm_iso_by_size(const ut64 *addrs, const ut64 *lens, int count, ut64 *vm_out, ut64 *iso_out) {
|
||||
if (count < 1) {
|
||||
return;
|
||||
@@ -48,6 +73,18 @@ static void pick_vm_iso_by_size(const ut64 *addrs, const ut64 *lens, int count,
|
||||
}
|
||||
}
|
||||
|
||||
static ut64 rebase_bin_addr(DartCtx *ctx, ut64 addr) {
|
||||
if (!ctx || !ctx->core || !ctx->core->bin || !ctx->core->io || !addr) {
|
||||
return addr;
|
||||
}
|
||||
ut64 baddr = r_bin_get_baddr (ctx->core->bin);
|
||||
if (!baddr || addr >= baddr || baddr > UT64_MAX - addr) {
|
||||
return addr;
|
||||
}
|
||||
ut64 rebased = baddr + addr;
|
||||
return r_io_v2p (ctx->core->io, rebased) != UT64_MAX? rebased: addr;
|
||||
}
|
||||
|
||||
static int collect_snapshot_magics_in_range(DartCtx *ctx, ut64 start, ut64 size, ut64 *found_addrs, int found_cap, int found_cnt) {
|
||||
if (!ctx || !found_addrs || found_cap <= 0 || found_cnt >= found_cap || size < 4) {
|
||||
return found_cnt;
|
||||
@@ -61,8 +98,22 @@ static int collect_snapshot_magics_in_range(DartCtx *ctx, ut64 start, ut64 size,
|
||||
}
|
||||
for (int j = 0; j <= toread - 4; j += 4) {
|
||||
uint32_t val = r_read_le32 (buf + j);
|
||||
if (val == DART_SNAPSHOT_MAGIC) {
|
||||
found_addrs[found_cnt++] = addr + j;
|
||||
if (val != DART_SNAPSHOT_MAGIC) {
|
||||
continue;
|
||||
}
|
||||
const ut64 hit = addr + j;
|
||||
// The read window overlaps the previous chunk by 16 bytes so a magic
|
||||
// straddling a boundary is not missed; dedup so those overlap bytes
|
||||
// (and Mach-O segment/section double-coverage) don't count twice.
|
||||
bool dup = false;
|
||||
for (int d = 0; d < found_cnt; d++) {
|
||||
if (found_addrs[d] == hit) {
|
||||
dup = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!dup) {
|
||||
found_addrs[found_cnt++] = hit;
|
||||
if (found_cnt >= found_cap) {
|
||||
return found_cnt;
|
||||
}
|
||||
@@ -72,6 +123,89 @@ static int collect_snapshot_magics_in_range(DartCtx *ctx, ut64 start, ut64 size,
|
||||
return found_cnt;
|
||||
}
|
||||
|
||||
// A Dart Image starts with a kObjectStartAlignment (64)-byte header whose first
|
||||
// host word is ImageSize (header + payload) and second is
|
||||
// InstructionsSectionOffset. Bare-instructions AOT lays the two instruction
|
||||
// images consecutively at the start of the first executable section:
|
||||
// [VmInstructions][IsolateInstructions], each padded up to this alignment.
|
||||
#define DART_IMAGE_ALIGN 64
|
||||
#define DART_IMAGE_HEADER_MIN 64
|
||||
|
||||
static ut64 dart_align_up(ut64 v, ut64 a) {
|
||||
return (v + (a - 1)) & ~ (a - 1);
|
||||
}
|
||||
|
||||
// Read an Image header at addr and return its ImageSize in *size_out, true only
|
||||
// if it is a plausible instructions image: it fits before the first data image
|
||||
// and its InstructionsSection offset (word 1) is a small nonzero aligned value
|
||||
// inside the image.
|
||||
static bool dart_image_at(DartCtx *ctx, ut64 addr, ut64 data_lo, ut64 *size_out) {
|
||||
ut8 hdr[16];
|
||||
if (!read_mem (ctx, addr, hdr, sizeof (hdr))) {
|
||||
return false;
|
||||
}
|
||||
ut64 size = r_read_le64 (hdr), isoff = r_read_le64 (hdr + 8);
|
||||
*size_out = size;
|
||||
return size >= DART_IMAGE_HEADER_MIN && addr + size <= data_lo &&
|
||||
isoff >= 8 && (isoff & 7) == 0 && isoff < size && isoff < 0x10000;
|
||||
}
|
||||
|
||||
// When the four kDart* symbols are stripped, the magic scan can only find the
|
||||
// data snapshots (instruction images carry no magic). Recover vm_instr/iso_instr
|
||||
// structurally: vm_instr is the start of the first executable section, and the
|
||||
// isolate instructions image follows the VM one and ends where the first data
|
||||
// image begins. Every step is cross-checked; if anything is off we leave the
|
||||
// addresses at 0, so a wrong/unknown layout degrades to "no IT names" rather
|
||||
// than junk addresses. No-op when symbols already resolved the instructions.
|
||||
static void locate_instr_images_structural(DartCtx *ctx) {
|
||||
if (!ctx || !ctx->core || !ctx->core->bin || ctx->vm_instr || ctx->iso_instr) {
|
||||
return;
|
||||
}
|
||||
ut64 data_lo = ctx->vm_data && ctx->iso_data
|
||||
? R_MIN (ctx->vm_data, ctx->iso_data)
|
||||
: (ctx->vm_data? ctx->vm_data: ctx->iso_data);
|
||||
if (!data_lo) {
|
||||
return;
|
||||
}
|
||||
RVecRBinSection *sections = r_bin_get_sections_vec (ctx->core->bin);
|
||||
if (!sections) {
|
||||
return;
|
||||
}
|
||||
ut64 vm_instr = 0, sz1 = 0, sz2 = 0;
|
||||
RBinSection *sec;
|
||||
R_VEC_FOREACH (sections, sec) {
|
||||
if (sec && !sec->is_segment && sec->vaddr && sec->vsize && (sec->perm & R_PERM_X)) {
|
||||
vm_instr = rebase_bin_addr (ctx, sec->vaddr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!vm_instr || vm_instr >= data_lo || !dart_image_at (ctx, vm_instr, data_lo, &sz1)) {
|
||||
return;
|
||||
}
|
||||
if (dart_snapshot_is_single (ctx, ctx->iso_data? ctx->iso_data: ctx->vm_data)) {
|
||||
ctx->iso_instr = vm_instr;
|
||||
if (ctx->verbose > 0) {
|
||||
fprintf (stderr, "[r2flutter] located single instruction image structurally: snapshot_instr=0x%" PFMT64x "\n", (ut64)vm_instr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
ut64 iso_instr = dart_align_up (vm_instr + sz1, DART_IMAGE_ALIGN);
|
||||
if (iso_instr <= vm_instr || iso_instr >= data_lo || !dart_image_at (ctx, iso_instr, data_lo, &sz2)) {
|
||||
return;
|
||||
}
|
||||
// The isolate instructions image is the last text image: it must end right
|
||||
// where the first data image begins (within a page of alignment slack).
|
||||
ut64 iso_end = dart_align_up (iso_instr + sz2, DART_IMAGE_ALIGN);
|
||||
if (iso_end > data_lo || data_lo - iso_end > 0x4000) {
|
||||
return;
|
||||
}
|
||||
ctx->vm_instr = vm_instr;
|
||||
ctx->iso_instr = iso_instr;
|
||||
if (ctx->verbose > 0) {
|
||||
fprintf (stderr, "[r2flutter] located instruction images structurally: vm_instr=0x%" PFMT64x " iso_instr=0x%" PFMT64x "\n", (ut64)vm_instr, (ut64)iso_instr);
|
||||
}
|
||||
}
|
||||
|
||||
int find_snapshots(DartCtx *ctx) {
|
||||
if (!ctx || !ctx->core) {
|
||||
return -1;
|
||||
@@ -92,6 +226,8 @@ int find_snapshots(DartCtx *ctx) {
|
||||
"_kDartIsolateSnapshotInstructions",
|
||||
"DartIsolateSnapshotInstructions",
|
||||
};
|
||||
const char *single_data_names[2] = { "_kDartSnapshotData", "DartSnapshotData" };
|
||||
const char *single_text_names[2] = { "_kDartSnapshotText", "DartSnapshotText" };
|
||||
ut64 *outs[4] = { &ctx->vm_data, &ctx->vm_instr, &ctx->iso_data, &ctx->iso_instr };
|
||||
if (core->bin) {
|
||||
RVecRBinSymbol *v = r_bin_get_symbols_vec (core->bin);
|
||||
@@ -106,9 +242,20 @@ int find_snapshots(DartCtx *ctx) {
|
||||
continue;
|
||||
}
|
||||
for (int k = 0; k < 8; k++) {
|
||||
if (!strcmp (nm, names[k])) {
|
||||
// Only assign when this symbol carries an address: Mach-O
|
||||
// often lists a name twice and a later vaddr==0 duplicate
|
||||
// must not erase the good value we already resolved.
|
||||
if (!strcmp (nm, names[k]) && sym->vaddr) {
|
||||
int idx = k / 2;
|
||||
*outs[idx] = sym->vaddr? sym->vaddr: 0;
|
||||
*outs[idx] = rebase_bin_addr (ctx, sym->vaddr);
|
||||
}
|
||||
}
|
||||
for (int k = 0; k < 2; k++) {
|
||||
if (!strcmp (nm, single_data_names[k]) && sym->vaddr) {
|
||||
ctx->iso_data = rebase_bin_addr (ctx, sym->vaddr);
|
||||
}
|
||||
if (!strcmp (nm, single_text_names[k]) && sym->vaddr) {
|
||||
ctx->iso_instr = rebase_bin_addr (ctx, sym->vaddr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,6 +264,9 @@ int find_snapshots(DartCtx *ctx) {
|
||||
if (ctx->vm_data && ctx->vm_instr && ctx->iso_data && ctx->iso_instr) {
|
||||
return 0;
|
||||
}
|
||||
if (ctx->iso_data && ctx->iso_instr && dart_snapshot_is_single (ctx, ctx->iso_data)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
RVecRBinSection *sections = r_bin_get_sections_vec (core->bin);
|
||||
ut64 found_addrs[32];
|
||||
@@ -127,7 +277,12 @@ int find_snapshots(DartCtx *ctx) {
|
||||
if (!sec || !sec->vaddr || !sec->vsize) {
|
||||
continue;
|
||||
}
|
||||
ut64 vaddr = sec->vaddr;
|
||||
// r_bin_get_sections_vec returns both segments and their child
|
||||
// sections on Mach-O; scanning both covers the same bytes twice.
|
||||
if (sec->is_segment) {
|
||||
continue;
|
||||
}
|
||||
ut64 vaddr = rebase_bin_addr (ctx, sec->vaddr);
|
||||
ut64 size = sec->vsize;
|
||||
if (ctx->verbose > 0) {
|
||||
fprintf (stderr, "[r2flutter] scanning section '%s' vaddr=0x%" PFMT64x " size=0x%" PFMT64x "\n", sec->name? sec->name: "(null)", (ut64)vaddr, (ut64)size);
|
||||
@@ -148,40 +303,43 @@ int find_snapshots(DartCtx *ctx) {
|
||||
}
|
||||
}
|
||||
if (found_cnt >= 1) {
|
||||
// Only data snapshots carry the snapshot magic; instruction images do
|
||||
// not. The old code classified every non-data magic hit as an
|
||||
// "instructions" snapshot and fed its bogus total_len into the vm/iso
|
||||
// picker, so a stray f5f5dcdc byte run could poison vm_instr/iso_instr.
|
||||
// Derive only the data snapshots here; instruction images come from
|
||||
// symbols (and, once implemented, structural section location).
|
||||
ut64 data_addrs[4];
|
||||
ut64 data_lens[4];
|
||||
int data_cnt = 0;
|
||||
ut64 instr_addrs[4];
|
||||
ut64 instr_lens[4];
|
||||
int instr_cnt = 0;
|
||||
for (int i = 0; i < found_cnt; i++) {
|
||||
ut8 hdr2[16];
|
||||
if (!read_mem (ctx, found_addrs[i] + 4, hdr2, sizeof (hdr2))) {
|
||||
continue;
|
||||
}
|
||||
ut64 total_len = r_read_le64 (hdr2) + 4;
|
||||
ut64 classified_len = 0;
|
||||
bool is_data = looks_like_data_snapshot (ctx, found_addrs[i], &classified_len);
|
||||
if (is_data) {
|
||||
if (data_cnt < 4) {
|
||||
data_addrs[data_cnt] = found_addrs[i];
|
||||
data_lens[data_cnt] = total_len;
|
||||
data_cnt++;
|
||||
}
|
||||
} else {
|
||||
if (instr_cnt < 4) {
|
||||
instr_addrs[instr_cnt] = found_addrs[i];
|
||||
instr_lens[instr_cnt] = total_len;
|
||||
instr_cnt++;
|
||||
}
|
||||
for (int i = 0; i < found_cnt && data_cnt < 4; i++) {
|
||||
ut64 total_len = 0;
|
||||
if (looks_like_data_snapshot (ctx, found_addrs[i], &total_len)) {
|
||||
data_addrs[data_cnt] = found_addrs[i];
|
||||
data_lens[data_cnt] = total_len;
|
||||
data_cnt++;
|
||||
}
|
||||
}
|
||||
if (data_cnt >= 1) {
|
||||
pick_vm_iso_by_size (data_addrs, data_lens, data_cnt, &ctx->vm_data, &ctx->iso_data);
|
||||
}
|
||||
if (instr_cnt >= 1) {
|
||||
pick_vm_iso_by_size (instr_addrs, instr_lens, instr_cnt, &ctx->vm_instr, &ctx->iso_instr);
|
||||
// Preserve any addresses already resolved from symbols; only fill
|
||||
// the ones still unset so scan guesses cannot overwrite good values.
|
||||
ut64 scan_vm_data = 0;
|
||||
ut64 scan_iso_data = 0;
|
||||
if (data_cnt == 1 && dart_snapshot_is_single (ctx, data_addrs[0])) {
|
||||
scan_iso_data = data_addrs[0];
|
||||
} else {
|
||||
pick_vm_iso_by_size (data_addrs, data_lens, data_cnt, &scan_vm_data, &scan_iso_data);
|
||||
}
|
||||
if (!ctx->vm_data) {
|
||||
ctx->vm_data = scan_vm_data;
|
||||
}
|
||||
if (!ctx->iso_data) {
|
||||
ctx->iso_data = scan_iso_data;
|
||||
}
|
||||
}
|
||||
// Symbols are absent (we reached the scan): try to recover the
|
||||
// instruction images from the section layout so IT name recovery works.
|
||||
locate_instr_images_structural (ctx);
|
||||
if (ctx->vm_data || ctx->iso_data || ctx->vm_instr || ctx->iso_instr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
+21
-17
@@ -23,6 +23,16 @@ static char *read_it_name(DartCtx *ctx, ut64 addr) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *resolve_it_entry_signature(DartCtx *ctx, const DartInstructionTableEntry *entry) {
|
||||
if (ctx && ctx->signature_by_code_index && entry->has_code && entry->code_index < ctx->signature_by_code_index_count) {
|
||||
const char *sig = ctx->signature_by_code_index[entry->code_index];
|
||||
if (R_STR_ISNOTEMPTY (sig)) {
|
||||
return strdup (sig);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *resolve_it_entry_name(DartCtx *ctx, HtUP *sym_by_addr, ut64 data_image_base, const DartInstructionTableEntry *entry) {
|
||||
char *out = NULL;
|
||||
if (ctx && ctx->name_by_code_index && entry->has_code && entry->code_index < ctx->name_by_code_index_count) {
|
||||
@@ -31,7 +41,10 @@ static char *resolve_it_entry_name(DartCtx *ctx, HtUP *sym_by_addr, ut64 data_im
|
||||
out = strdup (ns);
|
||||
}
|
||||
}
|
||||
if (sym_by_addr) {
|
||||
// A stripped Mach-O/ELF often contributes synthetic func.<address>
|
||||
// symbols for every function start. Keep a Dart-derived name when the
|
||||
// cluster graph supplied one; RBin is only a fallback for unmapped slots.
|
||||
if (!out && sym_by_addr) {
|
||||
RBinSymbol *bs = (RBinSymbol *)ht_up_find (sym_by_addr, entry->address, NULL);
|
||||
if (bs && bs->name) {
|
||||
const char *resolved = r_bin_name_tostring (bs->name);
|
||||
@@ -147,22 +160,9 @@ static bool read_it_data_header_from_string_object(DartCtx *ctx, ut64 obj_addr,
|
||||
return false;
|
||||
}
|
||||
ut64 tags = r_read_le64 (objhdr);
|
||||
ut32 cid = 0;
|
||||
switch (ctx->layout->tag_style) {
|
||||
case DART_TAG_STYLE_OBJECT_HEADER:
|
||||
cid = (ut32) ((tags >> 12) & 0xFFFFF);
|
||||
break;
|
||||
case DART_TAG_STYLE_CID_SHIFT1:
|
||||
cid = (ut32) (tags >> 1);
|
||||
break;
|
||||
case DART_TAG_STYLE_CID_INT32:
|
||||
default:
|
||||
cid = (ut32) ((tags >> 12) & 0xFFFFF);
|
||||
break;
|
||||
}
|
||||
const int resolved_string_cid = dart_cid_get (ctx->layout, DART_CID_ONE_BYTE_STRING);
|
||||
const int string_cid = resolved_string_cid >= 0? resolved_string_cid: kOneByteStringCid;
|
||||
if ((int)cid != string_cid) {
|
||||
const ut32 cid = dart_cid_from_object_header (tags);
|
||||
const int string_cid = dart_cid_get (ctx->layout, DART_CID_ONE_BYTE_STRING);
|
||||
if (string_cid < 0 || (int)cid != string_cid) {
|
||||
return false;
|
||||
}
|
||||
ut64 len_smi = r_read_le64 (objhdr + 8);
|
||||
@@ -402,8 +402,10 @@ int dart_it_emit_fixed(const DartItEmitRequest *req) {
|
||||
.has_code = has_code,
|
||||
};
|
||||
entry.name = resolve_it_entry_name (ctx, req->sym_by_addr, req->data_image_base, &entry);
|
||||
entry.signature = resolve_it_entry_signature (ctx, &entry);
|
||||
emit_it_entry_record (&entry, req);
|
||||
free (entry.name);
|
||||
free (entry.signature);
|
||||
emitted++;
|
||||
}
|
||||
return 0;
|
||||
@@ -462,8 +464,10 @@ int dart_it_emit_varint(const DartItEmitRequest *req) {
|
||||
.has_code = has_code,
|
||||
};
|
||||
entry.name = resolve_it_entry_name (ctx, req->sym_by_addr, req->data_image_base, &entry);
|
||||
entry.signature = resolve_it_entry_signature (ctx, &entry);
|
||||
emit_it_entry_record (&entry, req);
|
||||
free (entry.name);
|
||||
free (entry.signature);
|
||||
emitted++;
|
||||
}
|
||||
return 0;
|
||||
|
||||
+2316
-551
File diff suppressed because it is too large.
Load diff
+85
-50
@@ -13,55 +13,13 @@ typedef struct {
|
||||
|
||||
static void init_layout_hints(LayoutHints *lh) {
|
||||
memset (lh, 0, sizeof (*lh));
|
||||
lh->ep_offs[lh->ep_offs_n++] = 0x10;
|
||||
lh->ep_offs[lh->ep_offs_n++] = 0x18;
|
||||
lh->ep_offs[lh->ep_offs_n++] = 0x20;
|
||||
lh->owner_offs[lh->owner_offs_n++] = 0x10;
|
||||
lh->owner_offs[lh->owner_offs_n++] = 0x18;
|
||||
lh->owner_offs[lh->owner_offs_n++] = 0x20;
|
||||
lh->name_offs[lh->name_offs_n++] = 0x10;
|
||||
lh->name_offs[lh->name_offs_n++] = 0x18;
|
||||
lh->name_offs[lh->name_offs_n++] = 0x20;
|
||||
}
|
||||
|
||||
static void read_json_u64_array(const RJson *parent, const char *key, ut64 *out, int *out_n, int max) {
|
||||
const RJson *arr = r_json_get (parent, key);
|
||||
if (arr && arr->type == R_JSON_ARRAY) {
|
||||
*out_n = 0;
|
||||
size_t n = arr->children.count;
|
||||
for (size_t i = 0; i < n && *out_n < max; i++) {
|
||||
const RJson *el = r_json_item (arr, i);
|
||||
if (el && el->type == R_JSON_INTEGER) {
|
||||
out[(*out_n)++] = (ut64)el->num.u_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void enrich_layout_hints_from_json(LayoutHints *lh, const char *hash) {
|
||||
if (!lh || !hash) {
|
||||
return;
|
||||
}
|
||||
char *s = r_file_slurp ("r2flutter/offsets.json", NULL);
|
||||
if (!s) {
|
||||
s = r_file_slurp ("offsets.json", NULL);
|
||||
}
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
RJson *j = r_json_parse (s);
|
||||
if (!j) {
|
||||
free (s);
|
||||
return;
|
||||
}
|
||||
const RJson *item = r_json_get (r_json_get (j, "hashes"), hash);
|
||||
if (item) {
|
||||
read_json_u64_array (item, "code_entry_point_offsets", lh->ep_offs, &lh->ep_offs_n, 8);
|
||||
read_json_u64_array (item, "code_owner_offsets", lh->owner_offs, &lh->owner_offs_n, 8);
|
||||
read_json_u64_array (item, "function_name_offsets", lh->name_offs, &lh->name_offs_n, 8);
|
||||
}
|
||||
r_json_free (j);
|
||||
free (s);
|
||||
lh->ep_offs[lh->ep_offs_n++] = 8;
|
||||
lh->ep_offs[lh->ep_offs_n++] = 24;
|
||||
lh->ep_offs[lh->ep_offs_n++] = 16;
|
||||
lh->ep_offs[lh->ep_offs_n++] = 32;
|
||||
lh->owner_offs[lh->owner_offs_n++] = 56;
|
||||
lh->name_offs[lh->name_offs_n++] = 24;
|
||||
lh->name_offs[lh->name_offs_n++] = 8;
|
||||
}
|
||||
|
||||
static bool read_heap_ptr(DartCtx *ctx, ut64 addr, ut64 data_image_base, ut64 *out_abs) {
|
||||
@@ -102,10 +60,88 @@ static bool read_heap_ptr(DartCtx *ctx, ut64 addr, ut64 data_image_base, ut64 *o
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns 1 for a decoded text string, 0 when addr is not a recognised 64-bit
|
||||
// string object, and -1 for a recognised string whose payload is not text.
|
||||
static int try_read_dart_string_object64(DartCtx *ctx, ut64 addr, char *out, int outsz) {
|
||||
if (!ctx || !ctx->layout || ctx->compressed_word_size != 8) {
|
||||
return false;
|
||||
}
|
||||
ut8 hdr[16];
|
||||
if (!read_mem (ctx, addr, hdr, sizeof (hdr))) {
|
||||
return false;
|
||||
}
|
||||
const ut32 cid = dart_cid_from_object_header (r_read_le64 (hdr));
|
||||
const int one_byte_cid = dart_cid_get (ctx->layout, DART_CID_ONE_BYTE_STRING);
|
||||
const int two_byte_cid = dart_cid_get (ctx->layout, DART_CID_TWO_BYTE_STRING);
|
||||
const bool is_two_byte = two_byte_cid >= 0 && cid == (ut32)two_byte_cid;
|
||||
if (!is_two_byte && (one_byte_cid < 0 || cid != (ut32)one_byte_cid)) {
|
||||
return false;
|
||||
}
|
||||
const ut64 length_smi = r_read_le64 (hdr + 8);
|
||||
if ((length_smi & 1) != 0) {
|
||||
return -1;
|
||||
}
|
||||
const ut64 length = length_smi >> 1;
|
||||
if (!length || length > 1024) {
|
||||
return -1;
|
||||
}
|
||||
const ut64 bytes_len = is_two_byte? length * 2: length;
|
||||
ut8 *bytes = (ut8 *)malloc ((size_t)bytes_len);
|
||||
if (!bytes || !read_mem (ctx, addr + 16, bytes, (int)bytes_len)) {
|
||||
free (bytes);
|
||||
return -1;
|
||||
}
|
||||
if (is_two_byte) {
|
||||
for (ut64 i = 0; i < length; i++) {
|
||||
const ut16 code = r_read_le16 (bytes + (i * 2));
|
||||
if (code < 0x20 || (code >= 0x7f && code < 0xa0)) {
|
||||
free (bytes);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
char *utf8 = dart_utf16le_to_utf8 (bytes, bytes_len);
|
||||
free (bytes);
|
||||
if (R_STR_ISEMPTY (utf8)) {
|
||||
free (utf8);
|
||||
return -1;
|
||||
}
|
||||
r_str_ncpy (out, utf8, outsz);
|
||||
free (utf8);
|
||||
return true;
|
||||
}
|
||||
int written = 0;
|
||||
for (ut64 i = 0; i < length; i++) {
|
||||
const ut8 ch = bytes[i];
|
||||
if (ch < 0x20 || (ch >= 0x7f && ch < 0xa0)) {
|
||||
free (bytes);
|
||||
return -1;
|
||||
}
|
||||
if (ch < 0x80) {
|
||||
if (written + 1 >= outsz) {
|
||||
break;
|
||||
}
|
||||
out[written++] = (char)ch;
|
||||
} else {
|
||||
if (written + 2 >= outsz) {
|
||||
break;
|
||||
}
|
||||
out[written++] = (char) (0xc0 | (ch >> 6));
|
||||
out[written++] = (char) (0x80 | (ch & 0x3f));
|
||||
}
|
||||
}
|
||||
out[written] = '\0';
|
||||
free (bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool try_read_dart_string(DartCtx *ctx, ut64 addr, char *out, int outsz) {
|
||||
if (!ctx || !out || outsz <= 1) {
|
||||
return false;
|
||||
}
|
||||
const int object_string = try_read_dart_string_object64 (ctx, addr, out, outsz);
|
||||
if (object_string) {
|
||||
return object_string > 0;
|
||||
}
|
||||
ut8 hdr[32];
|
||||
if (!read_mem (ctx, addr, hdr, sizeof (hdr))) {
|
||||
return false;
|
||||
@@ -177,7 +213,6 @@ HtUP *scan_code_names(DartCtx *ctx, ut64 data_image_base, ut64 data_image_end) {
|
||||
}
|
||||
LayoutHints lh;
|
||||
init_layout_hints (&lh);
|
||||
enrich_layout_hints_from_json (&lh, ctx->snapshot_hash);
|
||||
HtUP *name_by_ep = ht_up_new0 ();
|
||||
if (!name_by_ep) {
|
||||
return NULL;
|
||||
|
||||
+573
-201
File diff suppressed because it is too large.
Load diff
@@ -17,62 +17,40 @@ typedef struct {
|
||||
ut64 end;
|
||||
} ClusterStream;
|
||||
|
||||
static inline ut32 dart_cid_from_tags(const DartCtx *ctx, ut64 tags) {
|
||||
const DartTagStyle style = ctx && ctx->layout? ctx->layout->tag_style: DART_TAG_STYLE_OBJECT_HEADER;
|
||||
switch (style) {
|
||||
case DART_TAG_STYLE_CID_INT32:
|
||||
return (ut32)tags;
|
||||
case DART_TAG_STYLE_CID_SHIFT1:
|
||||
return (ut32) (tags >> 1);
|
||||
case DART_TAG_STYLE_OBJECT_HEADER:
|
||||
default:
|
||||
return (ut32) ((tags >> 12) & 0xfffffU);
|
||||
}
|
||||
}
|
||||
|
||||
// Which flavour of type a DartTypeInfo holds. Not snapshot class ids: these
|
||||
// never leave the parser, so they carry no version dependency.
|
||||
typedef enum {
|
||||
kIllegalCid = 0,
|
||||
kClassCid = 5,
|
||||
kPatchClassCid = 6,
|
||||
kFunctionCid = 7,
|
||||
kClosureDataCid = 8,
|
||||
kFfiTrampolineDataCid = 9,
|
||||
kFieldCid = 10,
|
||||
kScriptCid = 11,
|
||||
kLibraryCid = 12,
|
||||
kNamespaceCid = 13,
|
||||
kKernelProgramInfoCid = 14,
|
||||
kCodeCid = 40,
|
||||
kInstructionsCid = 41,
|
||||
kObjectPoolCid = 45,
|
||||
kCodeSourceMapCid = 49,
|
||||
kPcDescriptorsCid = 50,
|
||||
kStringCid = 72,
|
||||
kOneByteStringCid = 73,
|
||||
kTwoByteStringCid = 74,
|
||||
kArrayCid = 75,
|
||||
kImmutableArrayCid = 76,
|
||||
kGrowableObjectArrayCid = 77,
|
||||
kMintCid = 78,
|
||||
kDoubleCid = 79,
|
||||
kTypedDataBaseCid = 80,
|
||||
kTypeCid = 110,
|
||||
kFunctionTypeCid = 111,
|
||||
kRecordTypeCid = 112,
|
||||
kTypeParametersCid = 113,
|
||||
kTypeParameterCid = 114,
|
||||
kTypeArgumentsCid = 115,
|
||||
kNumPredefinedCids = 128
|
||||
} DartCid;
|
||||
|
||||
typedef struct {
|
||||
ut64 ref_id;
|
||||
ut64 name_ref;
|
||||
ut64 library_ref;
|
||||
char *name;
|
||||
int instance_size;
|
||||
} DartClass;
|
||||
|
||||
typedef struct {
|
||||
ut64 ref_id;
|
||||
ut64 name_ref;
|
||||
ut64 owner_ref;
|
||||
ut64 code_ref;
|
||||
ut64 entry_point;
|
||||
char *name;
|
||||
} DartPoolFunction;
|
||||
DART_TYPE_KIND_TYPE = 1,
|
||||
DART_TYPE_KIND_FUNCTION_TYPE,
|
||||
DART_TYPE_KIND_TYPE_PARAMETER,
|
||||
} DartTypeKind;
|
||||
|
||||
#define DART_SNAPSHOT_MAGIC 0xdcdcf5f5
|
||||
#define DART_SNAPSHOT_FIXED_SIZE (4 + 8 + 8)
|
||||
#define DART_SNAPSHOT_HASH_SIZE 32
|
||||
#define DART_SNAPSHOT_FEATURES_SCAN_MAX 2048
|
||||
#define DART_IMAGE_ALIGNMENT 64
|
||||
|
||||
static inline ut32 dart_cid_from_object_header(ut64 tags) {
|
||||
return (ut32) ((tags >> 12) & 0xfffffU);
|
||||
}
|
||||
|
||||
static inline ut64 dart_snapshot_data_image_base(ut64 snapshot_base, ut64 snapshot_len) {
|
||||
return snapshot_base + ((snapshot_len + (DART_IMAGE_ALIGNMENT - 1)) & ~ (DART_IMAGE_ALIGNMENT - 1));
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
bool ok;
|
||||
@@ -84,6 +62,8 @@ typedef struct {
|
||||
ut64 nb;
|
||||
ut64 no;
|
||||
ut64 nc;
|
||||
ut64 ncc;
|
||||
ut64 field_table_len;
|
||||
ut64 itlen;
|
||||
ut64 itdata;
|
||||
ut64 cluster_start;
|
||||
@@ -185,7 +165,22 @@ typedef struct {
|
||||
char *resolved_name;
|
||||
int resolved_cid;
|
||||
ut64 resolved_code_index;
|
||||
} DartModernPoolSlotInfo;
|
||||
} ModernPoolSlot;
|
||||
|
||||
typedef struct {
|
||||
ut64 pp_offset;
|
||||
ut64 pool_ref;
|
||||
ut64 pool_index;
|
||||
ut64 entry_index;
|
||||
ut64 target_ref;
|
||||
const char *target_kind;
|
||||
ut64 field_index;
|
||||
ut64 string_ref;
|
||||
ut64 string_addr;
|
||||
const char *string_value;
|
||||
} ModernPoolStrRef;
|
||||
|
||||
typedef void(*ModernPoolStrRefCb)(const ModernPoolStrRef *info, void *user);
|
||||
|
||||
typedef void(*DartInstructionTableEntryCallback)(const DartInstructionTableEntry *entry, void *user);
|
||||
|
||||
@@ -210,7 +205,9 @@ typedef struct {
|
||||
ut64 cluster_end;
|
||||
ut64 num_clusters;
|
||||
ut64 num_base_objects;
|
||||
} DartModernClusterRequest;
|
||||
} ModernReq;
|
||||
|
||||
typedef struct modern_value_graph_t ModernValueGraph;
|
||||
|
||||
typedef struct {
|
||||
DartCtx *ctx;
|
||||
@@ -223,7 +220,7 @@ typedef struct {
|
||||
const char *r2_scope;
|
||||
RStrBuf *sb;
|
||||
PJ *pj;
|
||||
} DartModernClusterSummaryRequest;
|
||||
} ModernSummaryReq;
|
||||
|
||||
bool read_mem(DartCtx *ctx, ut64 addr, void *buf, int len);
|
||||
bool read_u32_at(DartCtx *ctx, ut64 addr, ut32 *out);
|
||||
@@ -232,7 +229,8 @@ bool dart_read_unsigned_at(DartCtx *ctx, ut64 addr, ut64 *out_val, ut64 *out_nex
|
||||
bool dart_read_unsigned_buf(const ut8 *buf, ut64 size, ut64 pos, ut64 *out_val, ut64 *out_next);
|
||||
char *dart_utf16le_to_utf8(const ut8 *buf, ut64 size);
|
||||
bool dart_snapshot_header_read(DartCtx *ctx, ut64 base, DartSnapshotHeader *out);
|
||||
bool dart_snapshot_header_read_buf(const ut8 *buf, ut64 size, DartSnapshotHeader *out);
|
||||
bool dart_snapshot_fingerprint_read(DartCtx *ctx, ut64 base, DartSnapshotHeader *out);
|
||||
bool dart_snapshot_header_read_buf(const ut8 *buf, ut64 size, const DartVerLayout *layout, DartSnapshotHeader *out);
|
||||
DartVerLayout *dart_ctx_init_layout(DartCtx *ctx, DartVerLayout *tmp);
|
||||
void dart_ctx_fini_layout(DartCtx *ctx, DartVerLayout *owned);
|
||||
int find_snapshots(DartCtx *ctx);
|
||||
@@ -262,18 +260,24 @@ bool cs_read_bytes(ClusterStream *s, ut8 *buf, int len);
|
||||
void free_dart_string(void *p);
|
||||
int decode_string_cluster(ClusterStream *s, DartCtx *ctx, ut64 *ref_counter, bool is_canonical);
|
||||
void skip_generic_cluster(ClusterStream *stream);
|
||||
int deserialize_clusters(DartCtx *ctx, ut64 cluster_start, ut64 cluster_end, ut64 num_clusters, ut64 iso_instr);
|
||||
void resolve_names(DartCtx *ctx);
|
||||
|
||||
bool modern_skip_n_bytes(ClusterStream *s, ut64 len);
|
||||
bool dart_modern_is_supported_snapshot(DartCtx *ctx);
|
||||
bool dart_modern_emit_cluster_summary(const DartModernClusterSummaryRequest *req);
|
||||
bool dart_modern_build_synthetic_pp(const DartModernClusterRequest *req, ut64 snapshot_base, const char *snapshot_label, ut64 data_image_base, DartPpInfo *out);
|
||||
bool dart_modern_resolve_pp_slot(const DartModernClusterRequest *req, ut64 pp_offset, DartModernPoolSlotInfo *out);
|
||||
void dart_modern_pool_slot_info_fini(DartModernPoolSlotInfo *info);
|
||||
bool dart_modern_extract_object_pool_strings_from_clusters(const DartModernClusterRequest *req, RList *strings, HtUP *seen_refs, ut64 *ref_counter);
|
||||
bool dart_modern_scan_names_from_clusters(DartCtx *ctx, ut64 cluster_start, ut64 cluster_end, ut64 num_clusters, ut64 itlen);
|
||||
bool dart_modern_extract_classes_from_clusters(const DartModernClusterRequest *req, RList *class_list);
|
||||
bool modern_supported(DartCtx *ctx);
|
||||
bool modern_emit_summary(const ModernSummaryReq *req);
|
||||
bool modern_probe_snapshot(const ModernReq *req, ut64 num_objects, ut64 *semantic_score);
|
||||
bool modern_build_pp(const ModernReq *req, DartPpInfo *out);
|
||||
bool modern_resolve_pp_slot(const ModernReq *req, ut64 pp_off, ModernPoolSlot *out);
|
||||
void modern_slot_fini(ModernPoolSlot *slot);
|
||||
ModernValueGraph *modern_value_graph_new(const ModernReq *req, ut64 root_ref);
|
||||
void modern_value_graph_free(ModernValueGraph *graph);
|
||||
void modern_value_graph_json(PJ *pj, ModernValueGraph *graph);
|
||||
void modern_value_graph_text(RStrBuf *sb, ModernValueGraph *graph);
|
||||
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_pool_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);
|
||||
DartStringCategory dart_string_classify_value(const char *s);
|
||||
|
||||
void init_function_layout(DartCtx *ctx, DartFunctionLayout *fl);
|
||||
|
||||
+164
-29
@@ -1,11 +1,46 @@
|
||||
/* r2flutter - MIT - Copyright 2026 - pancake */
|
||||
|
||||
#include <ctype.h>
|
||||
#include "dart_pool_parse_priv.h"
|
||||
|
||||
// Window size for the read cache. Cluster streams are decoded one byte at a
|
||||
// time, so serving those reads from a prefetched window turns millions of
|
||||
// r_io_read_at calls into a handful.
|
||||
#define RMEM_WINDOW (1024 * 1024)
|
||||
|
||||
bool read_mem(DartCtx *ctx, ut64 addr, void *buf, int len) {
|
||||
if (!ctx || !ctx->core || !buf || len <= 0) {
|
||||
return false;
|
||||
}
|
||||
// Fast path: request fully contained in the cached window.
|
||||
if (ctx->rmem_cache && addr >= ctx->rmem_cache_addr) {
|
||||
ut64 off = addr - ctx->rmem_cache_addr;
|
||||
if (off + (ut64)len <= (ut64)ctx->rmem_cache_len) {
|
||||
memcpy (buf, ctx->rmem_cache + off, (size_t)len);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Reads larger than the window bypass the cache.
|
||||
if (len > RMEM_WINDOW) {
|
||||
return r_io_read_at (ctx->core->io, addr, (ut8 *)buf, len);
|
||||
}
|
||||
if (!ctx->rmem_cache) {
|
||||
ctx->rmem_cache = malloc (RMEM_WINDOW);
|
||||
if (!ctx->rmem_cache) {
|
||||
return r_io_read_at (ctx->core->io, addr, (ut8 *)buf, len);
|
||||
}
|
||||
ctx->rmem_cache_cap = RMEM_WINDOW;
|
||||
}
|
||||
// Refill the window at addr. r_io_read_at only succeeds when the whole
|
||||
// range is readable, so a successful window read means every byte in it is
|
||||
// valid and safe to serve. On failure (e.g. near the end of a mapped
|
||||
// region) fall back to an exact read and keep any previous window intact.
|
||||
if (r_io_read_at (ctx->core->io, addr, ctx->rmem_cache, ctx->rmem_cache_cap)) {
|
||||
ctx->rmem_cache_addr = addr;
|
||||
ctx->rmem_cache_len = ctx->rmem_cache_cap;
|
||||
memcpy (buf, ctx->rmem_cache, (size_t)len);
|
||||
return true;
|
||||
}
|
||||
return r_io_read_at (ctx->core->io, addr, (ut8 *)buf, len);
|
||||
}
|
||||
|
||||
@@ -106,7 +141,71 @@ char *dart_utf16le_to_utf8(const ut8 *buf, ut64 size) {
|
||||
return out;
|
||||
}
|
||||
|
||||
bool dart_snapshot_header_read(DartCtx *ctx, ut64 base, DartSnapshotHeader *out) {
|
||||
static const DartVerLayout *dart_snapshot_header_layout(DartCtx *ctx, const char *hash) {
|
||||
if (ctx && ctx->layout) {
|
||||
return ctx->layout;
|
||||
}
|
||||
if (ctx && R_STR_ISNOTEMPTY (ctx->dart_version_override)) {
|
||||
const DartVerLayout *layout = dart_profile_from_version (ctx->dart_version_override);
|
||||
if (layout) {
|
||||
return layout;
|
||||
}
|
||||
}
|
||||
const char *version = dart_version_from_hash (hash);
|
||||
const DartVerLayout *layout = version? dart_profile_from_version (version): NULL;
|
||||
return layout? layout: dart_newest_profile ();
|
||||
}
|
||||
|
||||
static bool dart_snapshot_header_set_fields(DartSnapshotHeader *out, const DartVerLayout *layout, const ut64 *fields, int count) {
|
||||
if (!out || !layout || !fields || count != layout->header_fields) {
|
||||
return false;
|
||||
}
|
||||
switch (layout->header_style) {
|
||||
case DART_HEADER_STYLE_210:
|
||||
out->nb = fields[0];
|
||||
out->no = fields[1];
|
||||
out->nc = fields[2];
|
||||
out->field_table_len = fields[3];
|
||||
break;
|
||||
case DART_HEADER_STYLE_212:
|
||||
if (UT64_MAX - fields[2] < fields[3]) {
|
||||
return false;
|
||||
}
|
||||
out->nb = fields[0];
|
||||
out->no = fields[1];
|
||||
out->ncc = fields[2];
|
||||
out->nc = fields[2] + fields[3];
|
||||
out->field_table_len = fields[4];
|
||||
break;
|
||||
case DART_HEADER_STYLE_214:
|
||||
out->nb = fields[0];
|
||||
out->no = fields[1];
|
||||
out->nc = fields[2];
|
||||
out->field_table_len = fields[3];
|
||||
out->itlen = fields[4];
|
||||
break;
|
||||
case DART_HEADER_STYLE_216:
|
||||
out->nb = fields[0];
|
||||
out->no = fields[1];
|
||||
out->nc = fields[2];
|
||||
out->field_table_len = fields[3];
|
||||
out->itlen = fields[4];
|
||||
out->itdata = fields[5];
|
||||
break;
|
||||
case DART_HEADER_STYLE_MODERN:
|
||||
out->nb = fields[0];
|
||||
out->no = fields[1];
|
||||
out->nc = fields[2];
|
||||
out->itlen = fields[3];
|
||||
out->itdata = fields[4];
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool dart_snapshot_outer_read(DartCtx *ctx, ut64 base, DartSnapshotHeader *out, ut64 *out_cursor) {
|
||||
if (!ctx || !out) {
|
||||
return false;
|
||||
}
|
||||
@@ -119,17 +218,34 @@ bool dart_snapshot_header_read(DartCtx *ctx, ut64 base, DartSnapshotHeader *out)
|
||||
if (out->magic != DART_SNAPSHOT_MAGIC) {
|
||||
return false;
|
||||
}
|
||||
out->total_len = r_read_le64 (hdr + 4) + 4;
|
||||
const ut64 stored_len = r_read_le64 (hdr + 4);
|
||||
if (stored_len > UT64_MAX - 4) {
|
||||
return false;
|
||||
}
|
||||
out->total_len = stored_len + 4;
|
||||
if (out->total_len < DART_SNAPSHOT_FIXED_SIZE + DART_SNAPSHOT_HASH_SIZE + 1 || out->total_len > (1ULL << 34) || base > UT64_MAX - out->total_len) {
|
||||
return false;
|
||||
}
|
||||
RIOMap *map = r_io_map_get_at (ctx->core->io, base);
|
||||
if (map && base + out->total_len - 1 > r_io_map_to (map)) {
|
||||
return false;
|
||||
}
|
||||
out->kind = r_read_le64 (hdr + 12);
|
||||
ut64 cursor = base + DART_SNAPSHOT_FIXED_SIZE;
|
||||
if (!read_mem (ctx, cursor, out->hash, DART_SNAPSHOT_HASH_SIZE)) {
|
||||
return false;
|
||||
}
|
||||
out->hash[DART_SNAPSHOT_HASH_SIZE] = '\0';
|
||||
for (int i = 0; i < DART_SNAPSHOT_HASH_SIZE; i++) {
|
||||
if (!isxdigit ((ut8)out->hash[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
cursor += DART_SNAPSHOT_HASH_SIZE;
|
||||
ut8 b = 0;
|
||||
int scanned = 0;
|
||||
while (scanned < DART_SNAPSHOT_FEATURES_SCAN_MAX) {
|
||||
const ut64 snapshot_end = base + out->total_len;
|
||||
while (scanned < DART_SNAPSHOT_FEATURES_SCAN_MAX && cursor + (ut64)scanned < snapshot_end) {
|
||||
if (!read_mem (ctx, cursor + scanned, &b, 1)) {
|
||||
return false;
|
||||
}
|
||||
@@ -138,7 +254,7 @@ bool dart_snapshot_header_read(DartCtx *ctx, ut64 base, DartSnapshotHeader *out)
|
||||
}
|
||||
scanned++;
|
||||
}
|
||||
if (b) {
|
||||
if (b || cursor + (ut64)scanned >= snapshot_end) {
|
||||
return false;
|
||||
}
|
||||
int tocopy = R_MIN (scanned, (int)sizeof (out->flags) - 1);
|
||||
@@ -147,20 +263,36 @@ bool dart_snapshot_header_read(DartCtx *ctx, ut64 base, DartSnapshotHeader *out)
|
||||
}
|
||||
out->flags[tocopy] = '\0';
|
||||
cursor += (ut64)scanned + 1;
|
||||
if (cursor >= snapshot_end) {
|
||||
return false;
|
||||
}
|
||||
if (out_cursor) {
|
||||
*out_cursor = cursor;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dart_snapshot_fingerprint_read(DartCtx *ctx, ut64 base, DartSnapshotHeader *out) {
|
||||
return dart_snapshot_outer_read (ctx, base, out, NULL);
|
||||
}
|
||||
|
||||
bool dart_snapshot_header_read(DartCtx *ctx, ut64 base, DartSnapshotHeader *out) {
|
||||
ut64 cursor = 0;
|
||||
if (!dart_snapshot_outer_read (ctx, base, out, &cursor)) {
|
||||
return false;
|
||||
}
|
||||
const DartVerLayout *layout = dart_snapshot_header_layout (ctx, out->hash);
|
||||
if (!layout || layout->header_fields < 4 || layout->header_fields > 6) {
|
||||
return false;
|
||||
}
|
||||
ut64 next = cursor;
|
||||
if (!dart_read_unsigned_at (ctx, next, &out->nb, &next)) {
|
||||
return false;
|
||||
ut64 fields[6] = { 0 };
|
||||
for (int i = 0; i < layout->header_fields; i++) {
|
||||
if (!dart_read_unsigned_at (ctx, next, &fields[i], &next)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!dart_read_unsigned_at (ctx, next, &out->no, &next)) {
|
||||
return false;
|
||||
}
|
||||
if (!dart_read_unsigned_at (ctx, next, &out->nc, &next)) {
|
||||
return false;
|
||||
}
|
||||
if (!dart_read_unsigned_at (ctx, next, &out->itlen, &next)) {
|
||||
return false;
|
||||
}
|
||||
if (!dart_read_unsigned_at (ctx, next, &out->itdata, &next)) {
|
||||
if (!dart_snapshot_header_set_fields (out, layout, fields, layout->header_fields)) {
|
||||
return false;
|
||||
}
|
||||
out->cluster_start = next;
|
||||
@@ -168,7 +300,7 @@ bool dart_snapshot_header_read(DartCtx *ctx, ut64 base, DartSnapshotHeader *out)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dart_snapshot_header_read_buf(const ut8 *buf, ut64 size, DartSnapshotHeader *out) {
|
||||
bool dart_snapshot_header_read_buf(const ut8 *buf, ut64 size, const DartVerLayout *layout, DartSnapshotHeader *out) {
|
||||
if (!buf || !out || size < DART_SNAPSHOT_FIXED_SIZE + DART_SNAPSHOT_HASH_SIZE + 1) {
|
||||
return false;
|
||||
}
|
||||
@@ -202,20 +334,20 @@ bool dart_snapshot_header_read_buf(const ut8 *buf, ut64 size, DartSnapshotHeader
|
||||
}
|
||||
out->flags[tocopy] = '\0';
|
||||
cursor += (ut64)scanned + 1;
|
||||
if (!layout) {
|
||||
layout = dart_snapshot_header_layout (NULL, out->hash);
|
||||
}
|
||||
if (!layout || layout->header_fields < 4 || layout->header_fields > 6) {
|
||||
return false;
|
||||
}
|
||||
ut64 next = cursor;
|
||||
if (!dart_read_unsigned_buf (buf, size, next, &out->nb, &next)) {
|
||||
return false;
|
||||
ut64 fields[6] = { 0 };
|
||||
for (int i = 0; i < layout->header_fields; i++) {
|
||||
if (!dart_read_unsigned_buf (buf, size, next, &fields[i], &next)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!dart_read_unsigned_buf (buf, size, next, &out->no, &next)) {
|
||||
return false;
|
||||
}
|
||||
if (!dart_read_unsigned_buf (buf, size, next, &out->nc, &next)) {
|
||||
return false;
|
||||
}
|
||||
if (!dart_read_unsigned_buf (buf, size, next, &out->itlen, &next)) {
|
||||
return false;
|
||||
}
|
||||
if (!dart_read_unsigned_buf (buf, size, next, &out->itdata, &next)) {
|
||||
if (!dart_snapshot_header_set_fields (out, layout, fields, layout->header_fields)) {
|
||||
return false;
|
||||
}
|
||||
if (next >= out->total_len) {
|
||||
@@ -257,6 +389,9 @@ bool cs_read_unsigned(ClusterStream *s, ut64 *out) {
|
||||
}
|
||||
|
||||
bool cs_read_ref_id(ClusterStream *s, ut64 *out) {
|
||||
if (s && s->ctx && s->ctx->layout && s->ctx->layout->ref_encoding == DART_REF_ENCODING_UNSIGNED) {
|
||||
return cs_read_unsigned (s, out);
|
||||
}
|
||||
int64_t result = 0;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
ut8 raw = 0;
|
||||
|
||||
@@ -410,7 +410,7 @@ static void scan_packed_strings_from_snapshot(DartCtx *ctx, ut64 snapshot_base,
|
||||
return;
|
||||
}
|
||||
DartSnapshotHeader hdr;
|
||||
if (!dart_snapshot_header_read_buf (buf, total_len, &hdr)) {
|
||||
if (!dart_snapshot_header_read_buf (buf, total_len, ctx->layout, &hdr)) {
|
||||
free (buf);
|
||||
return;
|
||||
}
|
||||
@@ -658,7 +658,7 @@ RList *dart_pool_extract_strings(DartCtx *ctx) {
|
||||
return string_list;
|
||||
}
|
||||
|
||||
RList *dart_pool_extract_object_pool_strings(DartCtx *ctx) {
|
||||
RList *dart_pool_extract_pool_strings(DartCtx *ctx) {
|
||||
if (!ctx || !ctx->core || !ctx->core->bin) {
|
||||
return NULL;
|
||||
}
|
||||
@@ -667,7 +667,7 @@ RList *dart_pool_extract_object_pool_strings(DartCtx *ctx) {
|
||||
if (find_snapshots (ctx) == 0 && ctx->vm_data) {
|
||||
DartVerLayout layout_tmp;
|
||||
DartVerLayout *layout_owned = dart_ctx_init_layout (ctx, &layout_tmp);
|
||||
if (dart_modern_is_supported_snapshot (ctx)) {
|
||||
if (modern_supported (ctx)) {
|
||||
const ut64 snapshots[] = {
|
||||
ctx->vm_data,
|
||||
ctx->iso_data
|
||||
@@ -682,8 +682,8 @@ RList *dart_pool_extract_object_pool_strings(DartCtx *ctx) {
|
||||
if (!seen_refs) {
|
||||
continue;
|
||||
}
|
||||
const DartModernClusterRequest req = { ctx, sh.cluster_start, base + sh.total_len, sh.nc, sh.nb };
|
||||
(void)dart_modern_extract_object_pool_strings_from_clusters (&req, string_list, seen_refs, &ref_counter);
|
||||
const ModernReq req = { ctx, sh.cluster_start, base + sh.total_len, sh.nc, sh.nb };
|
||||
(void)modern_extract_pool_strings (&req, string_list, seen_refs, &ref_counter);
|
||||
ht_up_free (seen_refs);
|
||||
}
|
||||
}
|
||||
@@ -867,7 +867,7 @@ static char *dump_strings_list(DartCtx *ctx, RList *strings, int fmt) {
|
||||
}
|
||||
|
||||
char *dart_pool_dump_strings(DartCtx *ctx, int fmt) {
|
||||
RList *strings = dart_pool_extract_object_pool_strings (ctx);
|
||||
RList *strings = dart_pool_extract_pool_strings (ctx);
|
||||
char *out = dump_strings_list (ctx, strings, fmt);
|
||||
dart_string_list_free (strings);
|
||||
return out;
|
||||
|
||||
+364
-42
@@ -26,6 +26,19 @@ typedef struct {
|
||||
char *fn_name;
|
||||
} DartPoolUseInfo;
|
||||
|
||||
typedef struct {
|
||||
ut64 pp_off;
|
||||
RList *uses;
|
||||
} DartPpUseBucket;
|
||||
|
||||
typedef struct {
|
||||
DartCtx *ctx;
|
||||
RList *buckets;
|
||||
RList *xrefs;
|
||||
ut64 *count;
|
||||
ut64 limit;
|
||||
} DartSerializedPoolXrefCtx;
|
||||
|
||||
static void dart_xref_info_free(void *p) {
|
||||
DartXrefInfo *xi = (DartXrefInfo *)p;
|
||||
if (xi) {
|
||||
@@ -45,6 +58,12 @@ static void dart_pool_use_info_free(void *p) {
|
||||
free (ui);
|
||||
}
|
||||
|
||||
static void dart_pp_use_bucket_free(void *p) {
|
||||
DartPpUseBucket *bucket = (DartPpUseBucket *)p;
|
||||
r_list_free (bucket->uses);
|
||||
free (bucket);
|
||||
}
|
||||
|
||||
static char *xref_join_names(const char *owner, const char *name) {
|
||||
if (R_STR_ISNOTEMPTY (owner) && R_STR_ISNOTEMPTY (name)) {
|
||||
return r_str_newf ("%s.%s", owner, name);
|
||||
@@ -178,19 +197,18 @@ static void collect_data_image_xrefs(DartCtx *ctx, DartRecoveryModel *model, RLi
|
||||
}
|
||||
DartVerLayout layout_tmp;
|
||||
DartVerLayout *layout_owned = dart_ctx_init_layout (ctx, &layout_tmp);
|
||||
ut64 kAlign = ctx->layout && ctx->layout->max_alignment? (ut64)ctx->layout->max_alignment: 16;
|
||||
RList *seen_fields = r_list_newf (free);
|
||||
HtUP *seen_ep = ht_up_new0 ();
|
||||
DartSnapshotHeader sh;
|
||||
if (dart_snapshot_header_read (ctx, ctx->iso_data, &sh)) {
|
||||
ut64 data_image_base = ctx->iso_data + ((sh.total_len + (kAlign - 1)) & ~ (kAlign - 1));
|
||||
ut64 data_image_base = dart_snapshot_data_image_base (ctx->iso_data, sh.total_len);
|
||||
ut64 data_image_end = ctx->iso_instr? ctx->iso_instr: (data_image_base + (4ULL << 20));
|
||||
collect_field_scan_xrefs (ctx, model, xrefs, seen_fields, count, limit, data_image_base, data_image_end);
|
||||
collect_method_scan_xrefs (ctx, model, xrefs, seen_ep, count, limit, data_image_base, data_image_end);
|
||||
}
|
||||
if (ctx->vm_data && !xref_limit_reached (*count, limit) &&
|
||||
dart_snapshot_header_read (ctx, ctx->vm_data, &sh)) {
|
||||
ut64 vm_data_base = ctx->vm_data + ((sh.total_len + (kAlign - 1)) & ~ (kAlign - 1));
|
||||
ut64 vm_data_base = dart_snapshot_data_image_base (ctx->vm_data, sh.total_len);
|
||||
ut64 vm_data_end = ctx->vm_instr? ctx->vm_instr: (vm_data_base + (4ULL << 20));
|
||||
collect_field_scan_xrefs (ctx, model, xrefs, seen_fields, count, limit, vm_data_base, vm_data_end);
|
||||
collect_method_scan_xrefs (ctx, model, xrefs, seen_ep, count, limit, vm_data_base, vm_data_end);
|
||||
@@ -240,6 +258,191 @@ static bool xref_parse_pp_offset(const char *opstr, ut64 *out) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static char *xref_lower_dup(const char *s) {
|
||||
if (R_STR_ISEMPTY (s)) {
|
||||
return NULL;
|
||||
}
|
||||
char *d = strdup (s);
|
||||
for (char *p = d; *p; p++) {
|
||||
*p = (char)tolower ((ut8)*p);
|
||||
if (*p == '#') {
|
||||
*p = ' ';
|
||||
}
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
static void xref_skip_spaces(const char **p) {
|
||||
while (p && *p && (**p == ' ' || **p == '\t')) {
|
||||
(*p)++;
|
||||
}
|
||||
}
|
||||
|
||||
static bool xref_parse_reg_token(const char **p, int *out) {
|
||||
if (!p || !*p || !out) {
|
||||
return false;
|
||||
}
|
||||
xref_skip_spaces (p);
|
||||
if (**p != 'x' && **p != 'w') {
|
||||
return false;
|
||||
}
|
||||
(*p)++;
|
||||
if (!isdigit ((ut8) * *p)) {
|
||||
return false;
|
||||
}
|
||||
int reg = 0;
|
||||
while (isdigit ((ut8) * *p)) {
|
||||
reg = (reg * 10) + (**p - '0');
|
||||
(*p)++;
|
||||
}
|
||||
if (reg < 0 || reg > 30) {
|
||||
return false;
|
||||
}
|
||||
*out = reg;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool xref_parse_comma(const char **p) {
|
||||
xref_skip_spaces (p);
|
||||
if (!p || !*p || **p != ',') {
|
||||
return false;
|
||||
}
|
||||
(*p)++;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool xref_parse_number_token(const char **p, ut64 *out) {
|
||||
if (!p || !*p || !out) {
|
||||
return false;
|
||||
}
|
||||
xref_skip_spaces (p);
|
||||
bool neg = false;
|
||||
if (**p == '-') {
|
||||
neg = true;
|
||||
(*p)++;
|
||||
}
|
||||
const char *start = *p;
|
||||
int base = 10;
|
||||
if (r_str_startswith (start, "0x")) {
|
||||
base = 16;
|
||||
*p += 2;
|
||||
start = *p;
|
||||
while (isxdigit ((ut8) * *p)) {
|
||||
(*p)++;
|
||||
}
|
||||
} else {
|
||||
while (isdigit ((ut8) * *p)) {
|
||||
(*p)++;
|
||||
}
|
||||
}
|
||||
if (*p == start) {
|
||||
return false;
|
||||
}
|
||||
ut64 v = strtoull (start, NULL, base);
|
||||
*out = neg? (ut64) (- (st64)v): v;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool xref_parse_add_pp_offset(const char *text, const bool reg_valid[31], const ut64 reg_off[31], int *dst_out, ut64 *off_out) {
|
||||
char *lower = xref_lower_dup (text);
|
||||
if (!lower) {
|
||||
return false;
|
||||
}
|
||||
const char *p = lower;
|
||||
if (r_str_startswith (p, "add ")) {
|
||||
p += 4;
|
||||
}
|
||||
int dst = -1;
|
||||
int src = -1;
|
||||
ut64 imm = 0;
|
||||
bool ok = xref_parse_reg_token (&p, &dst) &&
|
||||
xref_parse_comma (&p) &&
|
||||
xref_parse_reg_token (&p, &src) &&
|
||||
xref_parse_comma (&p) &&
|
||||
xref_parse_number_token (&p, &imm);
|
||||
if (ok) {
|
||||
xref_skip_spaces (&p);
|
||||
if (*p == ',') {
|
||||
p++;
|
||||
xref_skip_spaces (&p);
|
||||
}
|
||||
if (r_str_startswith (p, "lsl")) {
|
||||
p += 3;
|
||||
ut64 shift = 0;
|
||||
if (xref_parse_number_token (&p, &shift) && shift < 64) {
|
||||
imm <<= shift;
|
||||
}
|
||||
}
|
||||
ut64 base = 0;
|
||||
if (src == 27) {
|
||||
base = 0;
|
||||
} else if (src >= 0 && src < 31 && reg_valid[src]) {
|
||||
base = reg_off[src];
|
||||
} else {
|
||||
ok = false;
|
||||
}
|
||||
if (ok) {
|
||||
if (dst_out) {
|
||||
*dst_out = dst;
|
||||
}
|
||||
if (off_out) {
|
||||
*off_out = base + imm;
|
||||
}
|
||||
}
|
||||
}
|
||||
free (lower);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool xref_parse_mem_pp_offset(const char *text, const bool reg_valid[31], const ut64 reg_off[31], ut64 *off_out) {
|
||||
char *lower = xref_lower_dup (text);
|
||||
if (!lower) {
|
||||
return false;
|
||||
}
|
||||
bool ok = false;
|
||||
const char *b = strchr (lower, '[');
|
||||
while (b && !ok) {
|
||||
const char *p = b + 1;
|
||||
int reg = -1;
|
||||
if (xref_parse_reg_token (&p, ®)) {
|
||||
ut64 base = 0;
|
||||
if (reg == 27) {
|
||||
ok = true;
|
||||
} else if (reg >= 0 && reg < 31 && reg_valid[reg]) {
|
||||
base = reg_off[reg];
|
||||
ok = true;
|
||||
}
|
||||
if (ok) {
|
||||
ut64 imm = 0;
|
||||
if (xref_parse_comma (&p)) {
|
||||
(void)xref_parse_number_token (&p, &imm);
|
||||
}
|
||||
if (off_out) {
|
||||
*off_out = base + imm;
|
||||
}
|
||||
}
|
||||
}
|
||||
b = strchr (b + 1, '[');
|
||||
}
|
||||
free (lower);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int xref_parse_dest_reg(const char *text) {
|
||||
char *lower = xref_lower_dup (text);
|
||||
if (!lower) {
|
||||
return -1;
|
||||
}
|
||||
const char *p = lower;
|
||||
while (isalpha ((ut8)*p)) {
|
||||
p++;
|
||||
}
|
||||
int reg = -1;
|
||||
(void)xref_parse_reg_token (&p, ®);
|
||||
free (lower);
|
||||
return reg;
|
||||
}
|
||||
|
||||
static ut64 xref_object_pool_base(DartCtx *ctx) {
|
||||
if (!ctx || !ctx->core || !ctx->core->config) {
|
||||
return 0;
|
||||
@@ -304,51 +507,62 @@ static bool xref_pool_use_seen(HtUP *seen, ut64 at, ut64 pp_off) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static void collect_pool_uses_from_entry(DartCtx *ctx, const DartInstructionTableEntry *entry, RList *uses, HtUP *seen) {
|
||||
#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.
|
||||
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;
|
||||
}
|
||||
r_strf_var (cmd, 128, "pdj 96 @ 0x%" PFMT64x, entry->address);
|
||||
char *s = r_core_cmd_str (ctx->core, cmd);
|
||||
if (!s) {
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
RJson *j = r_json_parse (s);
|
||||
if (!j) {
|
||||
free (s);
|
||||
return;
|
||||
}
|
||||
const RJson *ops = r_json_get (j, "ops");
|
||||
const RJson *arr = ops? ops: j;
|
||||
for (size_t i = 0;; i++) {
|
||||
const RJson *item = r_json_item (arr, i);
|
||||
if (!item) {
|
||||
// Decode the function's instructions one at a time via r_core_anal_op and
|
||||
// feed op->mnemonic to the operand parsers, rather than rendering the whole
|
||||
// window to `pdj` JSON and parsing that back. Same disassembly text, minus
|
||||
// the JSON build/parse round-trip; matches the -AAA path in flutter_analysis.
|
||||
bool reg_valid[31] = { false };
|
||||
ut64 reg_off[31] = { 0 };
|
||||
ut64 at = entry->address;
|
||||
for (ut64 i = 0; i < nops; i++) {
|
||||
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)) {
|
||||
r_anal_op_free (op);
|
||||
break;
|
||||
}
|
||||
const char *opstr = r_json_get_str (item, "opstr");
|
||||
if (R_STR_ISEMPTY (opstr)) {
|
||||
opstr = r_json_get_str (item, "opcode");
|
||||
}
|
||||
const char *m = op->mnemonic;
|
||||
ut64 pp_off = 0;
|
||||
if (!xref_parse_pp_offset (opstr, &pp_off)) {
|
||||
continue;
|
||||
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)) {
|
||||
DartPoolUseInfo *ui = R_NEW0 (DartPoolUseInfo);
|
||||
ui->at = at;
|
||||
ui->fn_index = entry->index;
|
||||
ui->pp_off = pp_off;
|
||||
ui->fn_name = R_STR_ISNOTEMPTY (entry->name)? strdup (entry->name): xref_it_label (entry->index);
|
||||
r_list_append (uses, ui);
|
||||
}
|
||||
ut64 at = (ut64)r_json_get_num (item, "offset");
|
||||
if (!at) {
|
||||
at = entry->address;
|
||||
int add_dst = -1;
|
||||
ut64 add_off = 0;
|
||||
if (xref_parse_add_pp_offset (m, reg_valid, reg_off, &add_dst, &add_off) && add_dst >= 0 && add_dst < 31) {
|
||||
reg_valid[add_dst] = true;
|
||||
reg_off[add_dst] = add_off;
|
||||
} else {
|
||||
const int dst = xref_parse_dest_reg (m);
|
||||
if (dst >= 0 && dst < 31) {
|
||||
reg_valid[dst] = false;
|
||||
}
|
||||
}
|
||||
if (xref_pool_use_seen (seen, at, pp_off)) {
|
||||
continue;
|
||||
}
|
||||
DartPoolUseInfo *ui = R_NEW0 (DartPoolUseInfo);
|
||||
ui->at = at;
|
||||
ui->fn_index = entry->index;
|
||||
ui->pp_off = pp_off;
|
||||
ui->fn_name = R_STR_ISNOTEMPTY (entry->name)? strdup (entry->name): xref_it_label (entry->index);
|
||||
r_list_append (uses, ui);
|
||||
at += op->size;
|
||||
r_anal_op_free (op);
|
||||
}
|
||||
r_json_free (j);
|
||||
free (s);
|
||||
}
|
||||
|
||||
static RList *collect_pool_uses(DartCtx *ctx, RVecDartInstructionTableEntry *entries, ut64 limit) {
|
||||
@@ -359,15 +573,26 @@ static RList *collect_pool_uses(DartCtx *ctx, RVecDartInstructionTableEntry *ent
|
||||
HtUP *seen = ht_up_new0 ();
|
||||
ut64 scanned = 0;
|
||||
ut64 scan_limit = limit > 0? limit: 4096;
|
||||
DartInstructionTableEntry *entry;
|
||||
R_VEC_FOREACH (entries, entry) {
|
||||
if (!entry->has_code || !entry->address) {
|
||||
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) {
|
||||
continue;
|
||||
}
|
||||
if (scan_limit > 0 && scanned >= scan_limit) {
|
||||
break;
|
||||
}
|
||||
collect_pool_uses_from_entry (ctx, entry, uses, seen);
|
||||
// Entries are emitted in ascending address order, so the next one with
|
||||
// code bounds this one.
|
||||
ut64 end_addr = 0;
|
||||
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;
|
||||
break;
|
||||
}
|
||||
}
|
||||
collect_pool_uses_from_entry (ctx, entry, end_addr, uses, seen);
|
||||
scanned++;
|
||||
}
|
||||
ht_up_free (seen);
|
||||
@@ -428,15 +653,112 @@ 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)) {
|
||||
return;
|
||||
}
|
||||
if (!info->string_addr || R_STR_ISEMPTY (info->string_value)) {
|
||||
return;
|
||||
}
|
||||
DartPpUseBucket *bucket = xref_bucket_by_pp (ux->buckets, info->pp_offset);
|
||||
if (!bucket || !bucket->uses) {
|
||||
return;
|
||||
}
|
||||
RListIter *it;
|
||||
DartPoolUseInfo *use;
|
||||
r_list_foreach (bucket->uses, it, use) {
|
||||
if (!use || xref_limit_reached (*ux->count, ux->limit)) {
|
||||
continue;
|
||||
}
|
||||
append_xref_info (ux->xrefs, ux->count, ux->limit, "code", "code.string", "code", use->fn_name, use->fn_index, use->at, "string", info->string_value, info->string_ref, info->string_addr);
|
||||
}
|
||||
}
|
||||
|
||||
static bool collect_serialized_object_pool_xrefs(DartCtx *ctx, RList *uses, RList *xrefs, ut64 *count, ut64 limit) {
|
||||
if (!ctx || !ctx->core || !uses || !xrefs || xref_limit_reached (*count, limit)) {
|
||||
return false;
|
||||
}
|
||||
if (find_snapshots (ctx) != 0) {
|
||||
return false;
|
||||
}
|
||||
HtUP *wanted = ht_up_new0 ();
|
||||
RList *buckets = r_list_newf (dart_pp_use_bucket_free);
|
||||
RListIter *it;
|
||||
DartPoolUseInfo *use;
|
||||
r_list_foreach (uses, it, use) {
|
||||
if (!use) {
|
||||
continue;
|
||||
}
|
||||
DartPpUseBucket *bucket = xref_bucket_by_pp (buckets, use->pp_off);
|
||||
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);
|
||||
}
|
||||
r_list_append (bucket->uses, use);
|
||||
}
|
||||
DartVerLayout layout_tmp;
|
||||
DartVerLayout *layout_owned = ctx->layout? NULL: dart_ctx_init_layout (ctx, &layout_tmp);
|
||||
DartSerializedPoolXrefCtx ux = {
|
||||
.ctx = ctx,
|
||||
.buckets = buckets,
|
||||
.xrefs = xrefs,
|
||||
.count = count,
|
||||
.limit = limit,
|
||||
};
|
||||
bool ok = false;
|
||||
const ut64 snapshots[] = {
|
||||
ctx->vm_data,
|
||||
ctx->iso_data
|
||||
};
|
||||
for (size_t i = 0; i < R_ARRAY_SIZE (snapshots) && !xref_limit_reached (*count, limit); i++) {
|
||||
ut64 base = snapshots[i];
|
||||
DartSnapshotHeader sh = { 0 };
|
||||
if (!base || !dart_snapshot_header_read (ctx, base, &sh) || !sh.ok) {
|
||||
continue;
|
||||
}
|
||||
const ModernReq req = {
|
||||
.ctx = ctx,
|
||||
.cluster_start = sh.cluster_start,
|
||||
.cluster_end = base + sh.total_len,
|
||||
.num_clusters = sh.nc,
|
||||
.num_base_objects = sh.nb,
|
||||
};
|
||||
ok |= modern_collect_pool_strrefs (&req, wanted, xref_serialized_pool_string_cb, &ux);
|
||||
}
|
||||
if (layout_owned) {
|
||||
dart_ctx_fini_layout (ctx, layout_owned);
|
||||
}
|
||||
r_list_free (buckets);
|
||||
ht_up_free (wanted);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void collect_object_pool_xrefs(DartCtx *ctx, DartRecoveryModel *model, RList *xrefs, ut64 *count, ut64 limit) {
|
||||
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);
|
||||
(void)collect_serialized_object_pool_xrefs (ctx, uses, xrefs, count, limit);
|
||||
ut64 pool_base = xref_object_pool_base (ctx);
|
||||
if (!pool_base) {
|
||||
r_list_free (uses);
|
||||
return;
|
||||
}
|
||||
RList *uses = collect_pool_uses (ctx, model->it_entries, limit);
|
||||
ut64 heap_base = xref_flag_addr (ctx->core, "app.heap_base");
|
||||
RListIter *it;
|
||||
DartPoolUseInfo *use;
|
||||
|
||||
+10
-5
@@ -181,11 +181,16 @@ static void sbom_add_snapshot_runtime(DartCtx *ctx, RList *components) {
|
||||
if (ctx->vm_data && R_STR_ISEMPTY (ctx->snapshot_hash)) {
|
||||
DartSnapshotHeader hdr;
|
||||
if (dart_snapshot_header_read (ctx, ctx->vm_data, &hdr)) {
|
||||
memcpy (ctx->snapshot_hash, hdr.hash, 32);
|
||||
ctx->snapshot_hash[32] = '\0';
|
||||
memcpy (ctx->snapshot_hash_actual, hdr.hash, 32);
|
||||
ctx->snapshot_hash_actual[32] = '\0';
|
||||
if (R_STR_ISNOTEMPTY (ctx->snapshot_hash_override)) {
|
||||
r_str_ncpy (ctx->snapshot_hash, ctx->snapshot_hash_override, sizeof (ctx->snapshot_hash));
|
||||
} else {
|
||||
r_str_ncpy (ctx->snapshot_hash, ctx->snapshot_hash_actual, sizeof (ctx->snapshot_hash));
|
||||
}
|
||||
}
|
||||
}
|
||||
const char *version = dart_version_from_hash (ctx->snapshot_hash);
|
||||
const char *version = dart_ctx_version (ctx);
|
||||
char evidence[96];
|
||||
snprintf (evidence, sizeof (evidence), "snapshot_hash=%s", ctx->snapshot_hash[0]? ctx->snapshot_hash: "unknown");
|
||||
sbom_add_component (components, "runtime", "dart-sdk", version, "snapshot_header", version? 100: 70, evidence);
|
||||
@@ -465,7 +470,7 @@ static void sbom_dump_component_json(PJ *pj, const DartSbomComponent *c) {
|
||||
}
|
||||
|
||||
static char *sbom_dump_json(DartCtx *ctx, RList *components, const char *input_path) {
|
||||
const char *version = dart_version_from_hash (ctx->snapshot_hash);
|
||||
const char *version = dart_ctx_version (ctx);
|
||||
PJ *pj = pj_new ();
|
||||
pj_o (pj);
|
||||
pj_ks (pj, "format", "r2flutter-recovered-sbom");
|
||||
@@ -525,7 +530,7 @@ static char *sbom_dump_text(DartCtx *ctx, RList *components) {
|
||||
const bool quiet = ctx && ctx->quiet;
|
||||
RStrBuf *sb = r_strbuf_new ("");
|
||||
if (!quiet) {
|
||||
const char *version = dart_version_from_hash (ctx->snapshot_hash);
|
||||
const char *version = dart_ctx_version (ctx);
|
||||
r_strbuf_append (sb, "# Dart/Flutter Recovered Components\n");
|
||||
r_strbuf_append (sb, "# complete: false\n");
|
||||
r_strbuf_append (sb, "# versions: explicit metadata only; AOT snapshots usually do not serialize pub package versions\n");
|
||||
|
||||
+178
-98
@@ -2,8 +2,8 @@
|
||||
|
||||
// Dart SDK version detection and layout profiles
|
||||
//
|
||||
// Supported Dart SDK versions: 2.10.0 - 3.10.7
|
||||
// Based on unflutter's version profiles and CID tables.
|
||||
// Supported Dart SDK versions: 2.10.0 - 3.14.0
|
||||
// Based on Dart SDK source tags plus external snapshot-hash references.
|
||||
|
||||
#include <r_core.h>
|
||||
#include "../../include/r2flutter/dart_version.h"
|
||||
@@ -14,111 +14,153 @@ void dart_version_set_verbose(int level) {
|
||||
G_VERBOSE = level;
|
||||
}
|
||||
|
||||
static bool parse_dart_version(const char *version, int *major, int *minor, int *patch) {
|
||||
char tail = 0;
|
||||
return version && sscanf (version, "%d.%d.%d%c", major, minor, patch, &tail) == 3;
|
||||
}
|
||||
|
||||
int dart_version_compare(const char *a, const char *b) {
|
||||
int av[3], bv[3];
|
||||
if (!parse_dart_version (a, &av[0], &av[1], &av[2]) || !parse_dart_version (b, &bv[0], &bv[1], &bv[2])) {
|
||||
return -2;
|
||||
}
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (av[i] < bv[i]) {
|
||||
return -1;
|
||||
}
|
||||
if (av[i] > bv[i]) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Known snapshot hashes mapped to Dart SDK versions
|
||||
// Sources: unflutter version.go, reFlutter enginehash.csv, blutter precompiled SDKs
|
||||
static const struct {
|
||||
const char *hash;
|
||||
const char *version;
|
||||
} known_hashes[] = {
|
||||
// Dart 2.10.x (Flutter 1.22.x)
|
||||
// Dart 2.10.x
|
||||
{ "8ee4ef7a67df9845fba331734198a953", "2.10.0" },
|
||||
// Dart 2.13.x (Flutter 2.2.x - 2.3.x)
|
||||
{ "e4a09dbf2bb120fe4674e0576617a0dc", "2.13.0" },
|
||||
{ "d979fa911b038a8682e98cc8c8569261", "2.10.0" },
|
||||
// Dart 2.12.x
|
||||
{ "5b97292b25f0a715613b7a28e0734f77", "2.12.0" },
|
||||
{ "e036be9d5df68283e1a043e348fa7ff4", "2.12.0" },
|
||||
// Dart 2.13.x
|
||||
{ "0c7f1aa25fc597a7b3b9a19ed390afa1", "2.13.0" },
|
||||
{ "34f6eec64e9371856eaaa278ccf56538", "2.13.0" },
|
||||
{ "7a5b240780941844bae88eca5dbaa7b8", "2.13.0" },
|
||||
// Dart 2.14.x (Flutter 2.4.x - 2.5.x)
|
||||
{ "9cf77f4405212c45daf608e1cd646852", "2.14.0" },
|
||||
{ "e4a09dbf2bb120fe4674e0576617a0dc", "2.13.0" },
|
||||
// Dart 2.14.x
|
||||
{ "659a72e41e3276e882709901c27de33d", "2.14.0" },
|
||||
// Dart 2.15.x (Flutter 2.6.x - 2.7.x)
|
||||
{ "f10776149bf76be288def3c2ca73bdc1", "2.15.0" },
|
||||
{ "9cf77f4405212c45daf608e1cd646852", "2.14.0" },
|
||||
{ "c03fb294bfdaca2b44c552ad35e52d45", "2.14.0" },
|
||||
// Dart 2.15.x
|
||||
{ "24d9d411c2f90c8fbe8907f99e89d4b0", "2.15.0" },
|
||||
// Dart 2.16.x (Flutter 2.8.x - 2.16.x)
|
||||
{ "d56742caf7b3b3f4bd2df93a9bbb5503", "2.16.0" },
|
||||
{ "7b96ef6dda75bdd1d70be8c2f7cb814e", "2.15.0" },
|
||||
{ "f10776149bf76be288def3c2ca73bdc1", "2.15.0" },
|
||||
// Dart 2.16.x
|
||||
{ "3318fe66091c0ffbb64faec39976cb7d", "2.16.0" },
|
||||
{ "6093345773c65683f60c20c8e62cbe4a", "2.16.0" },
|
||||
{ "adf563436d12ba0d50ea5beb7f3be1bb", "2.16.0" },
|
||||
// Dart 2.17.x (Flutter 2.17.x)
|
||||
{ "d56742caf7b3b3f4bd2df93a9bbb5503", "2.16.0" },
|
||||
// Dart 2.17.x
|
||||
{ "0eb277b50d6ce4069261c69150ce52c9", "2.17.0" },
|
||||
{ "1441d6b13b8623fa7fbf61433abebd31", "2.17.6" },
|
||||
{ "a0cb0c928b23bc17a26e062b351dc44d", "2.17.6" },
|
||||
{ "ded6ef11c73fdc638d6ff6d3ad22a67b", "2.17.6" },
|
||||
// Dart 2.18.x (Flutter 3.3.x)
|
||||
// Dart 2.18.x
|
||||
{ "287683014dfbd311f6400c655c97a26d", "2.18.6" },
|
||||
{ "6a9b5a03a7e784a4558b10c769f188d9", "2.18.0" },
|
||||
{ "77bd0a83c49bfc555a44a0740f15003a", "2.18.0" },
|
||||
{ "8e50e448b241be23b9e990094f4dca39", "2.18.0" },
|
||||
{ "b0e899ec5a90e4661501f0b69e9dd70f", "2.18.0" },
|
||||
{ "b6d0a1f034d158b0d37b51d559379697", "2.18.0" },
|
||||
{ "8e50e448b241be23b9e990094f4dca39", "2.18.0" },
|
||||
{ "6a9b5a03a7e784a4558b10c769f188d9", "2.18.0" },
|
||||
// Dart 2.19.x (Flutter 3.7.x)
|
||||
{ "adb4292f3ec25074ca70abcd2d5c7251", "2.19.0" },
|
||||
{ "f91b8b03bf7f30a5e983fd19b23d978d", "2.18.2" },
|
||||
// Dart 2.19.x
|
||||
{ "501ef5cbd64ca70b6b42672346af6a8a", "2.19.0" },
|
||||
// Dart 3.0.x (Flutter 3.10.x - 3.12.x)
|
||||
{ "adb4292f3ec25074ca70abcd2d5c7251", "2.19.0" },
|
||||
{ "afcf461a16dfa5e88f88a2aa68764c99", "2.19.0" },
|
||||
{ "e727fae79fb45aac639252a9453b785e", "2.19.1" },
|
||||
// Dart 3.0.x
|
||||
{ "16ad76edd19b537bf6ea64fdd31977a7", "3.0.5" },
|
||||
{ "36b0375d284ee2af0d0fffc6e6e48fde", "3.0.5" },
|
||||
{ "617d044d9faa87a98c29f8aad5d2f220", "3.0.3" },
|
||||
{ "90b56a561f70cd55e972cb49b79b3d8b", "3.0.5" },
|
||||
{ "aa64af18e7d086041ac127cc4bc50c5e", "3.0.5" },
|
||||
{ "36b0375d284ee2af0d0fffc6e6e48fde", "3.0.5" },
|
||||
{ "16ad76edd19b537bf6ea64fdd31977a7", "3.0.5" },
|
||||
// Dart 3.1.x (Flutter 3.13.x)
|
||||
{ "7dbbeeb8ef7b91338640dca3927636de", "3.1.0" },
|
||||
// Dart 3.2.x (Flutter 3.16.x)
|
||||
{ "ce5d37cf26ea4faac543cd216f3b96d1", "3.0.0" },
|
||||
// Dart 3.1.x
|
||||
{ "5191d02bb1d36a9f8609aa7495894e76", "3.1.0" },
|
||||
{ "7dbbeeb8ef7b91338640dca3927636de", "3.1.3" },
|
||||
// Dart 3.2.x
|
||||
{ "9d6362266b78e4198f784da1a28fd82b", "3.2.0" },
|
||||
{ "f71c76320d35b65f1164dbaa6d95fe09", "3.2.5" },
|
||||
// Dart 3.3.x (Flutter 3.19.x)
|
||||
// Dart 3.3.x
|
||||
{ "77b867742ae1ace67fbbb9a9a0d0334e", "3.3.0" },
|
||||
{ "ee1eb666c76a5cb7746faf39d0b97547", "3.3.0" },
|
||||
// Dart 3.4.x (Flutter 3.22.x)
|
||||
// Dart 3.4.x
|
||||
{ "d20a1be77c3d3c41b2a5accaee1ce549", "3.4.3" },
|
||||
// Dart 3.5.x (Flutter 3.24.x)
|
||||
{ "fe53c4fdba846005b70514aec3840296", "3.4.0" },
|
||||
// Dart 3.5.x
|
||||
{ "71dcc635f2e5dca494525aa13ca9006b", "3.5.0" },
|
||||
{ "80a49c7111088100a233b2ae788e1f48", "3.5.0" },
|
||||
{ "cda356e9bae476c70de33809fd92e009", "3.5.0" },
|
||||
// Dart 3.6.x
|
||||
{ "2858c2c0920495f00b9bce9edf6a8cd9", "3.6.2" },
|
||||
// Dart 3.6.x (Flutter 3.27.x)
|
||||
{ "30c051cb9192f6d6812be3f0b87a6356", "3.6.0" },
|
||||
{ "f956f595844a2f845a55707faaaa51e4", "3.6.2" },
|
||||
// Dart 3.7.x (Flutter 3.29.x)
|
||||
// Dart 3.7.x
|
||||
{ "bae16a6858d8c0fe2c437b275eb807cd", "3.7.0" },
|
||||
{ "d91c0e6f35f0eb2e44124e8f42aa44a7", "3.7.0" },
|
||||
// Dart 3.8.x (Flutter 3.32.x)
|
||||
// Dart 3.8.x
|
||||
{ "830f4f59e7969c70b595182826435c19", "3.8.1" },
|
||||
// Dart 3.9.x (Flutter 3.35.x)
|
||||
{ "fa07b8a2d866963d1117ecdcea9287c8", "3.8.0" },
|
||||
// Dart 3.9.x
|
||||
{ "97ff04a728735e6b6b098bdf983faaba", "3.9.2" },
|
||||
// Dart 3.10.x (Flutter 3.38.x)
|
||||
// Dart 3.10.x
|
||||
{ "1ce86630892e2dca9a8543fdb8ed8e22", "3.10.7" },
|
||||
// Dart 3.11.x
|
||||
{ "78da37fed6bf1489361a312568249f3f", "3.11.5" },
|
||||
// Dart 3.12.x
|
||||
{ "41be3daaabd524b8aa7423bc24584957", "3.12.0" },
|
||||
{ "ace654289f5abc240509fc941453ebc5", "3.12.1" },
|
||||
// Dart 3.13.x
|
||||
{ "0451907c2eaa8467e848c0067bfe8ed4", "3.13.0" },
|
||||
// Dart 3.14.x
|
||||
{ "7a1ea3f6f5cf1089a7f6e55d7f20dbfd", "3.14.0" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
// Version profiles with CID tables - based on unflutter's version.go
|
||||
// Range-start version profiles. CID tables are generated in dart_cid.c.
|
||||
static const DartVerLayout version_profiles[] = {
|
||||
// Dart 2.10.0 - raw int32 tag, 4 header fields, pre-FunctionType split
|
||||
{ "", "2.10.0", 8, 1, 16, 20000, DART_TAG_STYLE_CID_INT32, 4, 4, 6, 16, 80, 81, 82, 78, 53, 20, 156 },
|
||||
// Dart 2.13.0 - raw int32 tag, 5 header fields, split canonical
|
||||
{ "", "2.13.0", 8, 1, 16, 20000, DART_TAG_STYLE_CID_INT32, 5, 4, 6, 15, 77, 78, 79, 75, 51, 18, 148 },
|
||||
// Dart 2.14.0 - shift1 tag, 5 header fields
|
||||
{ "", "2.14.0", 8, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, 4, 6, 16, 81, 82, 83, 79, 54, 20, 152 },
|
||||
// Dart 2.15.0 - shift1 tag, 5 header fields, NativePointer inserted
|
||||
{ "", "2.15.0", 8, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, 5, 7, 17, 82, 83, 84, 79, 55, 21, 153 },
|
||||
// Dart 2.16.0 - shift1 tag, 6 header fields, adds ConstMap/ConstSet
|
||||
{ "", "2.16.0", 8, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 6, 5, 7, 17, 84, 85, 86, 81, 55, 21, 154 },
|
||||
// Dart 2.17.6 - shift1 tag, 6 header fields
|
||||
{ "", "2.17.6", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 6, 5, 7, 17, 89, 90, 91, 86, 59, 21, 158 },
|
||||
// Dart 2.18.0 - shift1 tag, 5 header fields, adds SuspendState
|
||||
{ "", "2.18.0", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, 5, 7, 17, 90, 91, 92, 87, 59, 21, 159 },
|
||||
// Dart 2.19.0 - shift1 tag, 5 header fields, adds RecordType/Record
|
||||
{ "", "2.19.0", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, 5, 7, 17, 92, 93, 94, 89, 60, 21, 176 },
|
||||
// Dart 3.0.5 - shift1 tag, 5 header fields, adds WeakArray
|
||||
{ "", "3.0.5", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, 5, 7, 18, 93, 94, 95, 90, 61, 22, 177 },
|
||||
// Dart 3.1.0 - shift1 tag, 5 header fields
|
||||
{ "", "3.1.0", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, 5, 7, 18, 92, 93, 94, 89, 60, 22, 176 },
|
||||
// Dart 3.2.5 - shift1 tag, 5 header fields, PoolType swapped
|
||||
{ "", "3.2.5", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, 5, 7, 18, 92, 93, 94, 89, 60, 22, 176 },
|
||||
// Dart 3.3.0 - shift1 tag, 5 header fields
|
||||
{ "", "3.3.0", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, 5, 7, 18, 92, 93, 94, 89, 60, 22, 176 },
|
||||
// Dart 3.4.3 - ObjectHeader tag, 5 header fields
|
||||
{ "", "3.4.3", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, 5, 7, 18, 92, 93, 94, 89, 60, 22, 174 },
|
||||
// Dart 3.5.0 - ObjectHeader tag, 5 header fields
|
||||
{ "", "3.5.0", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, 5, 7, 18, 92, 93, 94, 89, 60, 22, 174 },
|
||||
// Dart 3.6.2 - ObjectHeader tag, 5 header fields
|
||||
{ "", "3.6.2", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, 5, 7, 18, 93, 94, 95, 90, 61, 23, 175 },
|
||||
// Dart 3.7.0 - ObjectHeader tag, 5 header fields
|
||||
{ "", "3.7.0", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, 5, 7, 18, 93, 94, 95, 90, 61, 23, 175 },
|
||||
// Dart 3.8.1 - ObjectHeader tag, 5 header fields
|
||||
{ "", "3.8.1", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, 5, 7, 18, 93, 94, 95, 90, 61, 23, 175 },
|
||||
// Dart 3.9.2 - ObjectHeader tag, 5 header fields
|
||||
{ "", "3.9.2", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, 5, 7, 18, 93, 94, 95, 90, 61, 23, 175 },
|
||||
// Dart 3.10.7 - ObjectHeader tag, 5 header fields
|
||||
{ "", "3.10.7", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, 5, 7, 18, 93, 94, 95, 90, 61, 23, 175 },
|
||||
{ { 0 }, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||
{ "", "2.10.0", 8, 1, 16, 20000, DART_TAG_STYLE_CID_INT32, 4, DART_HEADER_STYLE_210, DART_REF_ENCODING_UNSIGNED, DART_POOL_ENCODING_LOW7, DART_CODE_ALLOC_DEFERRED_ONLY, 7, 0, true, false, false, false, false }, // Dart 2.10.0
|
||||
{ "", "2.12.0", 8, 1, 16, 20000, DART_TAG_STYLE_CID_INT32, 5, DART_HEADER_STYLE_212, DART_REF_ENCODING_UNSIGNED, DART_POOL_ENCODING_LOW7, DART_CODE_ALLOC_DEFERRED_ONLY, 7, 0, true, false, false, false, false }, // Dart 2.12.0
|
||||
{ "", "2.13.0", 8, 1, 16, 20000, DART_TAG_STYLE_CID_INT32, 5, DART_HEADER_STYLE_212, DART_REF_ENCODING_UNSIGNED, DART_POOL_ENCODING_LOW7, DART_CODE_ALLOC_DEFERRED_ONLY, 7, 0, true, false, false, false, false }, // Dart 2.13.0
|
||||
{ "", "2.14.0", 8, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, DART_HEADER_STYLE_214, DART_REF_ENCODING_UNSIGNED, DART_POOL_ENCODING_LOW7, DART_CODE_ALLOC_STATE_DEFERRED, 7, 0, true, false, false, false, false }, // Dart 2.14.0
|
||||
{ "", "2.15.0", 8, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, DART_HEADER_STYLE_214, DART_REF_ENCODING_UNSIGNED, DART_POOL_ENCODING_LOW7, DART_CODE_ALLOC_STATE_DEFERRED, 7, 0, true, false, false, false, false }, // Dart 2.15.0
|
||||
{ "", "2.16.0", 8, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 6, DART_HEADER_STYLE_216, DART_REF_ENCODING_UNSIGNED, DART_POOL_ENCODING_LOW7, DART_CODE_ALLOC_STATE_DEFERRED, 7, 0, false, false, false, false, false }, // Dart 2.16.0
|
||||
{ "", "2.17.0", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 6, DART_HEADER_STYLE_216, DART_REF_ENCODING_UNSIGNED, DART_POOL_ENCODING_LOW7, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, false }, // Dart 2.17.0
|
||||
{ "", "2.18.0", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_LOW7, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, false }, // Dart 2.18.0
|
||||
{ "", "2.18.2", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_LOW7, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, false }, // Dart 2.18.2
|
||||
{ "", "2.18.3", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_LOW7, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, false }, // Dart 2.18.3
|
||||
{ "", "2.19.0", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_LOW7, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 2.19.0
|
||||
{ "", "3.0.0", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_LOW7, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 3.0.0
|
||||
{ "", "3.1.0", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_LOW7, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 3.1.0
|
||||
{ "", "3.2.0", 8, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_LOW7_SWAPPED, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 3.2.0
|
||||
{ "", "3.3.0", 4, 1, 16, 20000, DART_TAG_STYLE_CID_SHIFT1, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_MODERN, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 3.3.0
|
||||
{ "", "3.4.0", 8, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_MODERN, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 3.4.0
|
||||
{ "", "3.5.0", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_MODERN, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 3.5.0
|
||||
{ "", "3.6.0", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_MODERN, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 3.6.0
|
||||
{ "", "3.7.0", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_MODERN, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 3.7.0
|
||||
{ "", "3.8.0", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_MODERN, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 3.8.0
|
||||
{ "", "3.9.0", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_MODERN, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 3.9.0
|
||||
{ "", "3.10.0", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_MODERN, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 3.10.0
|
||||
{ "", "3.11.0", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_MODERN, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 3.11.0
|
||||
{ "", "3.12.0", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_MODERN, DART_CODE_ALLOC_STATE_DEFERRED, 6, 0, false, false, false, false, true }, // Dart 3.12.0
|
||||
{ "", "3.13.0", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_MODERN, DART_CODE_ALLOC_STATE_DEFERRED, 6, 2, false, true, true, true, true }, // Dart 3.13.0
|
||||
{ "", "3.14.0", 4, 1, 16, 20000, DART_TAG_STYLE_OBJECT_HEADER, 5, DART_HEADER_STYLE_MODERN, DART_REF_ENCODING_REF_ID128, DART_POOL_ENCODING_MODERN, DART_CODE_ALLOC_STATE_DEFERRED, 6, 2, false, true, true, true, true }, // Dart 3.14.0
|
||||
{ { 0 }, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||
};
|
||||
|
||||
const char *dart_version_from_hash(const char *hash) {
|
||||
@@ -133,23 +175,71 @@ const char *dart_version_from_hash(const char *hash) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const DartVerLayout *dart_newest_profile(void) {
|
||||
const DartVerLayout *newest = version_profiles;
|
||||
for (int i = 0; version_profiles[i].dart_version; i++) {
|
||||
newest = &version_profiles[i];
|
||||
}
|
||||
return newest;
|
||||
}
|
||||
|
||||
const DartVerLayout *dart_fingerprint_profile(void) {
|
||||
const DartVerLayout *baseline = version_profiles;
|
||||
for (int i = 0; version_profiles[i].dart_version; i++) {
|
||||
if (!version_profiles[i].single_snapshot) {
|
||||
baseline = &version_profiles[i];
|
||||
}
|
||||
}
|
||||
return baseline;
|
||||
}
|
||||
|
||||
const char *dart_version_source_str(int source) {
|
||||
switch (source) {
|
||||
case DART_VERSION_SOURCE_EXACT:
|
||||
return "exact-hash";
|
||||
case DART_VERSION_SOURCE_OVERRIDE:
|
||||
return "override";
|
||||
case DART_VERSION_SOURCE_PROBE:
|
||||
return "structural-probe";
|
||||
case DART_VERSION_SOURCE_FINGERPRINT:
|
||||
return "fingerprint";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
int dart_profile_count(void) {
|
||||
int count = 0;
|
||||
while (version_profiles[count].dart_version) {
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
const DartVerLayout *dart_profile_at(int index) {
|
||||
return index >= 0 && index < dart_profile_count ()? &version_profiles[index]: NULL;
|
||||
}
|
||||
|
||||
const DartVerLayout *dart_profile_from_version(const char *version) {
|
||||
if (!version) {
|
||||
return NULL;
|
||||
}
|
||||
const DartVerLayout *best = NULL;
|
||||
for (int i = 0; version_profiles[i].dart_version; i++) {
|
||||
if (!strcmp (version_profiles[i].dart_version, version)) {
|
||||
return &version_profiles[i];
|
||||
const int cmp = dart_version_compare (version, version_profiles[i].dart_version);
|
||||
if (cmp == -2) {
|
||||
return NULL;
|
||||
}
|
||||
if (cmp < 0) {
|
||||
break;
|
||||
}
|
||||
best = &version_profiles[i];
|
||||
}
|
||||
return NULL;
|
||||
return best? best: version_profiles;
|
||||
}
|
||||
|
||||
DartVerLayout *dart_pick_layout_by_hash(const char *hash) {
|
||||
DartVerLayout *dvl = calloc (1, sizeof (DartVerLayout));
|
||||
if (!dvl) {
|
||||
return NULL;
|
||||
}
|
||||
DartVerLayout *dart_layout_from_hash(const char *hash) {
|
||||
DartVerLayout *dvl = R_NEW0 (DartVerLayout);
|
||||
const char *version = dart_version_from_hash (hash);
|
||||
const DartVerLayout *profile = version? dart_profile_from_version (version): NULL;
|
||||
if (profile) {
|
||||
@@ -162,26 +252,16 @@ DartVerLayout *dart_pick_layout_by_hash(const char *hash) {
|
||||
}
|
||||
return dvl;
|
||||
}
|
||||
// Default to v3.9.2 layout for unknown hashes
|
||||
dvl->compressed_word_size = 4;
|
||||
dvl->heap_object_tag = 1;
|
||||
dvl->max_alignment = 16;
|
||||
dvl->it_cap = 20000;
|
||||
dvl->tag_style = DART_TAG_STYLE_OBJECT_HEADER;
|
||||
dvl->header_fields = 5;
|
||||
dvl->cid_class = 5;
|
||||
dvl->cid_function = 7;
|
||||
dvl->cid_code = 18;
|
||||
dvl->cid_string = 93;
|
||||
dvl->cid_one_byte_string = 94;
|
||||
dvl->cid_two_byte_string = 95;
|
||||
dvl->cid_array = 90;
|
||||
dvl->cid_mint = 61;
|
||||
dvl->cid_object_pool = 23;
|
||||
dvl->num_predefined_cids = 175;
|
||||
dvl->dart_version = "unknown";
|
||||
// A single-snapshot profile changes discovery as well as cluster framing, so
|
||||
// an unknown hash must not inherit one without structural evidence. Keep the
|
||||
// newest compatible split-snapshot baseline until probing selects a layout.
|
||||
const DartVerLayout *baseline = dart_fingerprint_profile ();
|
||||
memcpy (dvl, baseline, sizeof (DartVerLayout));
|
||||
if (hash && *hash) {
|
||||
r_str_ncpy (dvl->hash, hash, sizeof (dvl->hash));
|
||||
}
|
||||
if (G_VERBOSE > 0) {
|
||||
fprintf (stderr, "[r2flutter] Unknown snapshot hash, using v3.9.2 defaults (hash=%s)\n", hash? hash: "(null)");
|
||||
fprintf (stderr, "[r2flutter] Unknown snapshot hash, fingerprinting as compatible profile %s (hash=%s)\n", baseline->dart_version, hash? hash: "(null)");
|
||||
}
|
||||
return dvl;
|
||||
}
|
||||
|
||||
+12
-4
@@ -1,9 +1,13 @@
|
||||
CC ?= gcc
|
||||
CFLAGS += -Wall -Wextra -O2 -fPIC
|
||||
CFLAGS += -I../../include -I../../include/r2flutter
|
||||
CFLAGS += $(shell pkg-config --cflags r_core 2>/dev/null || echo "-I/usr/local/include/libr")
|
||||
R2_CFLAGS ?= $(shell r2 -H R2_CFLAGS 2>/dev/null)
|
||||
R2_LDFLAGS ?= $(shell r2 -H R2_LDFLAGS 2>/dev/null)
|
||||
R2_LIBS ?= $(shell r2 -H R2_LIBS 2>/dev/null)
|
||||
R2_PLUGDIR ?= $(shell r2 -H R2_USER_PLUGINS)
|
||||
LDFLAGS += $(shell pkg-config --libs r_core 2>/dev/null || echo "-L/usr/local/lib -lr_core -lr_util -ldl")
|
||||
CFLAGS += $(R2_CFLAGS)
|
||||
LDFLAGS += $(R2_LDFLAGS)
|
||||
R2_PLUGIN_LIBS = $(R2_LIBS)
|
||||
DEPFLAGS = -MMD -MP
|
||||
|
||||
# Detect platform for shared library extension
|
||||
@@ -11,6 +15,9 @@ UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
EXT_SO = dylib
|
||||
LDFLAGS += -dynamiclib -undefined dynamic_lookup
|
||||
# Resolve radare2 symbols from the host. Linking a second installation's
|
||||
# libr_* dylibs into a core plugin corrupts ABI-compatible host structures.
|
||||
R2_PLUGIN_LIBS =
|
||||
else
|
||||
EXT_SO = so
|
||||
LDFLAGS += -shared
|
||||
@@ -25,11 +32,12 @@ CORE_PLUGIN = core_flutter.$(EXT_SO)
|
||||
# Library
|
||||
R2FLUTTER_LIB_DIR = ../../build
|
||||
R2FLUTTER_LIB = $(R2FLUTTER_LIB_DIR)/libr2flutter.a
|
||||
R2FLUTTER_LIB_TARGET = build/libr2flutter.a
|
||||
|
||||
all: $(CORE_PLUGIN)
|
||||
|
||||
$(CORE_PLUGIN): $(CORE_OBJ) $(R2FLUTTER_LIB)
|
||||
$(CC) $(LDFLAGS) -o $@ $(CORE_OBJ) $(R2FLUTTER_LIB)
|
||||
$(CC) $(LDFLAGS) -o $@ $(CORE_OBJ) $(R2FLUTTER_LIB) $(R2_PLUGIN_LIBS) $(LDLIBS)
|
||||
|
||||
core_flutter.o: core_flutter.c
|
||||
$(CC) $(CFLAGS) $(DEPFLAGS) -c $< -o $@
|
||||
@@ -38,7 +46,7 @@ flutter_analysis.o: flutter_analysis.c flutter_analysis.h
|
||||
$(CC) $(CFLAGS) $(DEPFLAGS) -c $< -o $@
|
||||
|
||||
$(R2FLUTTER_LIB):
|
||||
$(MAKE) -C ../.. $(R2FLUTTER_LIB_DIR)/libr2flutter.a
|
||||
$(MAKE) -C ../.. $(R2FLUTTER_LIB_TARGET)
|
||||
|
||||
user-install: $(CORE_PLUGIN)
|
||||
mkdir -p $(R2_PLUGDIR)
|
||||
|
||||
+113
-33
@@ -11,6 +11,7 @@
|
||||
|
||||
#define R2FLUTTER_CFG_MAPFILE "r2flutter.mapfile"
|
||||
#define R2FLUTTER_CFG_NAMEPOOL "r2flutter.namepool"
|
||||
#define R2FLUTTER_CFG_PROFILE "r2flutter.profile"
|
||||
|
||||
typedef struct {
|
||||
char action;
|
||||
@@ -20,6 +21,8 @@ typedef struct {
|
||||
int string_depth;
|
||||
bool string_refs;
|
||||
bool help;
|
||||
bool subcmd_help;
|
||||
char invalid_flag;
|
||||
char *obf_map_path;
|
||||
char *object_spec;
|
||||
} R2FlutterCmd;
|
||||
@@ -31,55 +34,87 @@ static bool r2flutter_core_init(RCorePluginSession *cps) {
|
||||
r_config_node_desc (node, "Flutter obfuscation map JSON path");
|
||||
node = r_config_set_b (cfg, R2FLUTTER_CFG_NAMEPOOL, false);
|
||||
r_config_node_desc (node, "Enable heuristic name-pool fallback resolution");
|
||||
node = r_config_set (cfg, R2FLUTTER_CFG_PROFILE, "");
|
||||
r_config_node_desc (node, "Override Dart snapshot profile by 32-byte hash or Dart version");
|
||||
r_config_lock (cfg, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool r2flutter_core_fini(RCorePluginSession *cps) {
|
||||
#if R2_PLUGIN_INCORE
|
||||
// removing config vars causes an UAF. this is a temporal workaround
|
||||
#else
|
||||
RConfig *cfg = cps->core->config;
|
||||
r_config_lock (cfg, false);
|
||||
r_config_rm (cfg, R2FLUTTER_CFG_MAPFILE);
|
||||
r_config_rm (cfg, R2FLUTTER_CFG_NAMEPOOL);
|
||||
r_config_rm (cfg, R2FLUTTER_CFG_PROFILE);
|
||||
r_config_lock (cfg, true);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
static void r2flutter_apply_config(RCore *core, DartCtx *dctx) {
|
||||
dctx->obf_map_path = r_config_get (core->config, R2FLUTTER_CFG_MAPFILE);
|
||||
dctx->use_name_pool = r_config_get_b (core->config, R2FLUTTER_CFG_NAMEPOOL);
|
||||
const char *profile = r_config_get (core->config, R2FLUTTER_CFG_PROFILE);
|
||||
if (R_STR_ISNOTEMPTY (profile) && !dart_ctx_set_profile_override (dctx, profile)) {
|
||||
R_LOG_WARN ("Ignoring unsupported Dart snapshot profile override: %s", profile);
|
||||
}
|
||||
}
|
||||
|
||||
static void r2flutter_help(RCore *core);
|
||||
|
||||
static void r2flutter_subcmd_help(RCore *core, char action) {
|
||||
switch (action) {
|
||||
case 'i':
|
||||
r_cons_printf (core->cons,
|
||||
"Usage: r2flutter -i[jr*] dump instruction table entries\n"
|
||||
"| -i plain text output\n"
|
||||
"| -ie dump Dart code entrypoint; with -* mark instruction snapshots as dword arrays\n"
|
||||
"| -ij JSON output\n"
|
||||
"| -ir, -i* r2 commands output\n");
|
||||
break;
|
||||
case 'e':
|
||||
r_cons_printf (core->cons,
|
||||
"Usage: r2flutter -ie[jr*] dump Dart code entrypoint\n"
|
||||
"| -ie plain text output\n"
|
||||
"| -iej JSON output\n"
|
||||
"| -ier, -ie* r2 commands output; mark instruction snapshots as dword arrays\n");
|
||||
break;
|
||||
default:
|
||||
r2flutter_help (core);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void r2flutter_help(RCore *core) {
|
||||
r_cons_printf (core->cons,
|
||||
"Usage: r2flutter [-jqrnv] [-l N] [-m file] <action>\n"
|
||||
"| r2flutter show same -h help message\n"
|
||||
"| r2flutter -j <act> output JSON for dump actions\n"
|
||||
"| r2flutter -q <act> compact output; quiet analysis logs\n"
|
||||
"| r2flutter -r <act> output r2 commands for dump actions\n"
|
||||
"| r2flutter -n use heuristic name-pool fallback; names may be wrong\n"
|
||||
"| r2flutter -v increase parser verbosity\n"
|
||||
"Usage: r2flutter [qjr*] <action>\n"
|
||||
"| r2flutter -A[AA] analyze dart snapshot and apply flags/comments\n"
|
||||
"| r2flutter -c[jr*] dump classes\n"
|
||||
"| r2flutter -C apply Dart classes, fields, methods and types\n"
|
||||
"| r2flutter -D prof override Dart snapshot profile by hash or version\n"
|
||||
"| r2flutter -E[jr*] dump Dart code entrypoint\n"
|
||||
"| r2flutter -f[jr*] dump recovered functions\n"
|
||||
"| r2flutter -H[HH] dump Dart AOT snapshot header info\n"
|
||||
"| r2flutter -i[jr*] dump instruction table entries\n"
|
||||
"| r2flutter -ie[jr*] dump Dart code entrypoint; with -r/-* mark instruction snapshots as dword arrays\n"
|
||||
"| r2flutter -l N limit function/instruction-table/xref output\n"
|
||||
"| r2flutter -m file load Flutter obfuscation map JSON\n"
|
||||
"| r2flutter -A analyze dart snapshot and apply flags/comments\n"
|
||||
"| r2flutter -AA analyze with field extraction enabled\n"
|
||||
"| r2flutter -AAA run Dart-aware code analysis and recover code refs\n"
|
||||
"| r2flutter -c dump classes\n"
|
||||
"| r2flutter -C apply Dart classes, fields, methods and types\n"
|
||||
"| r2flutter -f dump recovered functions\n"
|
||||
"| r2flutter -H dump Dart AOT snapshot header info\n"
|
||||
"| r2flutter -HH dump Dart AOT snapshot header and cluster layout\n"
|
||||
"| r2flutter -HHH decode selected cluster payloads for diagnostics\n"
|
||||
"| r2flutter -h show this help\n"
|
||||
"| r2flutter -i dump instruction table entries\n"
|
||||
"| r2flutter -O addr decode Dart tagged/object pointer or ObjectPool PP slot\n"
|
||||
"| r2flutter -p print reconstructed ObjectPool PP value\n"
|
||||
"| r2flutter -n use heuristic name-pool fallback; names may be wrong\n"
|
||||
"| r2flutter -O spec decode a tagged pointer, PP slot, or snapshot ref:N\n"
|
||||
"| r2flutter -p[jr*] print reconstructed ObjectPool PP value\n"
|
||||
"| r2flutter -R dump full radare2 script (like standalone -R)\n"
|
||||
"| r2flutter -S dump best-effort recovered SBOM/components\n"
|
||||
"| r2flutter -T dump string-based type names\n"
|
||||
"| r2flutter -S[jr*] dump best-effort recovered SBOM/components\n"
|
||||
"| r2flutter -T[jr*] dump string-based type names\n"
|
||||
"| r2flutter -x[jr*] dump metadata/data-image xrefs; with -z, include string refs/ax in -r/-*\n"
|
||||
"| r2flutter -z[jr*] dump reliable ObjectPool-referenced strings\n"
|
||||
"| r2flutter -zz[jr*] dump all fuzzy/carved strings (-xzz includes refs/ax in -r/-*)\n"
|
||||
"| r2flutter -h show this help\n"
|
||||
"| r2flutter -v increase parser verbosity\n"
|
||||
"| r2flutter -V show version\n"
|
||||
"| r2flutter -x dump metadata/data-image xrefs; with -z, include string refs/ax in -r\n"
|
||||
"| r2flutter -z dump reliable ObjectPool-referenced strings (-q prints values only)\n"
|
||||
"| r2flutter -zz dump all fuzzy/carved strings (-xzz includes refs/ax in -r)\n");
|
||||
"Modifiers: q=quiet, j=json, r/*=r2 commands\n");
|
||||
}
|
||||
|
||||
static bool r2flutter_analyze(RCore *core, DartCtx *dctx, int quiet) {
|
||||
@@ -96,7 +131,7 @@ static bool r2flutter_analyze(RCore *core, DartCtx *dctx, int quiet) {
|
||||
}
|
||||
}
|
||||
|
||||
dart_dumper_apply_to_core (app);
|
||||
dart_dumper_apply_to_core (app, false);
|
||||
|
||||
dart_app_free (app);
|
||||
return true;
|
||||
@@ -108,7 +143,7 @@ static bool r2flutter_dump_r2script(RCore *core, DartCtx *dctx) {
|
||||
return false;
|
||||
}
|
||||
dart_app_load_info (app);
|
||||
char *script = dart_dumper_dump4radare2 (app);
|
||||
char *script = dart_dumper_dump_r2 (app);
|
||||
if (script) {
|
||||
r_cons_print (core->cons, script);
|
||||
free (script);
|
||||
@@ -163,6 +198,8 @@ static bool r2flutter_parse_cmd(const char *args, DartCtx *dctx, R2FlutterCmd *c
|
||||
cmd->string_depth = 0;
|
||||
cmd->string_refs = false;
|
||||
cmd->help = false;
|
||||
cmd->subcmd_help = false;
|
||||
cmd->invalid_flag = 0;
|
||||
cmd->obf_map_path = NULL;
|
||||
cmd->object_spec = NULL;
|
||||
char *dup = strdup (args);
|
||||
@@ -177,6 +214,10 @@ static bool r2flutter_parse_cmd(const char *args, DartCtx *dctx, R2FlutterCmd *c
|
||||
continue;
|
||||
}
|
||||
if (word[0] != '-') {
|
||||
if (!strcmp (word, "?")) {
|
||||
cmd->subcmd_help = true;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const char *flags = word + 1;
|
||||
@@ -191,6 +232,9 @@ static bool r2flutter_parse_cmd(const char *args, DartCtx *dctx, R2FlutterCmd *c
|
||||
case 'c':
|
||||
cmd->action = flag;
|
||||
break;
|
||||
case 'E':
|
||||
cmd->action = 'e';
|
||||
break;
|
||||
case 'C':
|
||||
cmd->action = flag;
|
||||
break;
|
||||
@@ -215,6 +259,14 @@ static bool r2flutter_parse_cmd(const char *args, DartCtx *dctx, R2FlutterCmd *c
|
||||
dctx->dump_it = true;
|
||||
cmd->action = flag;
|
||||
break;
|
||||
case 'e':
|
||||
if (cmd->action == 'i') {
|
||||
cmd->action = flag;
|
||||
} else {
|
||||
cmd->invalid_flag = flag;
|
||||
cmd->help = true;
|
||||
}
|
||||
break;
|
||||
case 'O':
|
||||
{
|
||||
const char *arg = r2flutter_opt_arg (tail, words, nwords, &i);
|
||||
@@ -231,6 +283,16 @@ static bool r2flutter_parse_cmd(const char *args, DartCtx *dctx, R2FlutterCmd *c
|
||||
case 'p':
|
||||
cmd->action = flag;
|
||||
break;
|
||||
case 'D':
|
||||
{
|
||||
const char *arg = r2flutter_opt_arg (tail, words, nwords, &i);
|
||||
if (!arg || !dart_ctx_set_profile_override (dctx, arg)) {
|
||||
cmd->help = true;
|
||||
break;
|
||||
}
|
||||
j += strlen (tail);
|
||||
break;
|
||||
}
|
||||
case 'j':
|
||||
cmd->fmt = 'j';
|
||||
break;
|
||||
@@ -265,6 +327,7 @@ static bool r2flutter_parse_cmd(const char *args, DartCtx *dctx, R2FlutterCmd *c
|
||||
dctx->quiet = 1;
|
||||
break;
|
||||
case 'r':
|
||||
case '*':
|
||||
cmd->fmt = 'r';
|
||||
break;
|
||||
case 'R':
|
||||
@@ -297,11 +360,16 @@ static bool r2flutter_parse_cmd(const char *args, DartCtx *dctx, R2FlutterCmd *c
|
||||
cmd->action = flag;
|
||||
cmd->string_depth++;
|
||||
break;
|
||||
case '?':
|
||||
cmd->subcmd_help = true;
|
||||
cmd->help = true;
|
||||
break;
|
||||
default:
|
||||
cmd->invalid_flag = flag;
|
||||
cmd->help = true;
|
||||
break;
|
||||
}
|
||||
if (cmd->help || (R_STR_ISNOTEMPTY (tail) && (flag == 'l' || flag == 'm' || flag == 'O'))) {
|
||||
if (cmd->help || (R_STR_ISNOTEMPTY (tail) && (flag == 'l' || flag == 'm' || flag == 'O' || flag == 'D'))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -342,6 +410,9 @@ static bool r2flutter_run_cmd(RCore *core, DartCtx *dctx, const R2FlutterCmd *cm
|
||||
dctx->dump_fields = 1;
|
||||
out = dart_pool_dump_classes (dctx, cmd->fmt);
|
||||
break;
|
||||
case 'e':
|
||||
out = dart_pool_dump_entry (dctx, cmd->fmt);
|
||||
break;
|
||||
case 'f':
|
||||
out = r2flutter_dump_functions (core, dctx, cmd->fmt);
|
||||
break;
|
||||
@@ -356,13 +427,13 @@ static bool r2flutter_run_cmd(RCore *core, DartCtx *dctx, const R2FlutterCmd *cm
|
||||
out = dart_pool_dump_object (dctx, cmd->object_spec, cmd->fmt);
|
||||
break;
|
||||
case 'p':
|
||||
out = dart_pool_dump_pp (dctx, cmd->fmt);
|
||||
out = dart_pool_dump_pool (dctx, cmd->fmt);
|
||||
break;
|
||||
case 'R':
|
||||
r2flutter_dump_r2script (core, dctx);
|
||||
return true;
|
||||
case 'S':
|
||||
out = dart_pool_dump_sbom (dctx, NULL, cmd->fmt);
|
||||
out = dart_pool_dump_sbom (dctx, core->io && core->io->desc? core->io->desc->name: NULL, cmd->fmt);
|
||||
break;
|
||||
case 'z':
|
||||
out = cmd->string_depth >= 2? dart_pool_dump_strings_fuzzy (dctx, cmd->fmt): dart_pool_dump_strings (dctx, cmd->fmt);
|
||||
@@ -416,7 +487,16 @@ static bool r_cmd_r2flutter_call(RCorePluginSession *cps, const char *input) {
|
||||
bool ret = true;
|
||||
R2FlutterCmd cmd = { 0 };
|
||||
if (!r2flutter_parse_cmd (args, &dctx, &cmd) || cmd.help) {
|
||||
r2flutter_help (core);
|
||||
if (cmd.subcmd_help) {
|
||||
r2flutter_subcmd_help (core, cmd.action);
|
||||
} else if (cmd.invalid_flag) {
|
||||
r_core_return_invalid_command (core, "r2flutter", cmd.invalid_flag);
|
||||
ret = false;
|
||||
} else {
|
||||
r2flutter_help (core);
|
||||
}
|
||||
} else if (cmd.subcmd_help) {
|
||||
r2flutter_subcmd_help (core, cmd.action);
|
||||
} else {
|
||||
ret = r2flutter_run_cmd (core, &dctx, &cmd);
|
||||
}
|
||||
@@ -425,8 +505,8 @@ static bool r_cmd_r2flutter_call(RCorePluginSession *cps, const char *input) {
|
||||
free (cmd.object_spec);
|
||||
return ret;
|
||||
}
|
||||
r2flutter_help (core);
|
||||
return true;
|
||||
r_core_return_invalid_command (core, "r2flutter", ch0);
|
||||
return false;
|
||||
}
|
||||
|
||||
RCorePlugin r_core_plugin_r2flutter = {
|
||||
|
||||
+220
-57
@@ -54,6 +54,15 @@ typedef struct {
|
||||
ut64 comments;
|
||||
} FlutterAnalStats;
|
||||
|
||||
typedef struct {
|
||||
ut64 string_addr;
|
||||
char *string_value;
|
||||
} FlutterPpStringRef;
|
||||
|
||||
typedef struct {
|
||||
HtUP *by_pp_off;
|
||||
} FlutterPpStringMap;
|
||||
|
||||
typedef struct {
|
||||
HtUP *seen;
|
||||
RVecFlutterEntry *entries;
|
||||
@@ -63,6 +72,25 @@ static void flutter_stack_slot_free(void *p) {
|
||||
free (p);
|
||||
}
|
||||
|
||||
static void flutter_pp_string_ref_free(HtUPKv *kv) {
|
||||
FlutterPpStringRef *ref = kv->value;
|
||||
free (ref->string_value);
|
||||
free (ref);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static void flutter_pp_string_map_fini(FlutterPpStringMap *map) {
|
||||
if (!map) {
|
||||
return;
|
||||
}
|
||||
ht_up_free (map->by_pp_off);
|
||||
memset (map, 0, sizeof (*map));
|
||||
}
|
||||
|
||||
static void flutter_model_fini(FlutterAnalModel *model) {
|
||||
dart_recovery_model_fini (model);
|
||||
}
|
||||
@@ -214,11 +242,17 @@ static bool flutter_parse_shifted_imm(const char *line, ut64 *imm) {
|
||||
if (!lsl) {
|
||||
return false;
|
||||
}
|
||||
const char *hash = strchr (lsl, '#');
|
||||
if (!hash) {
|
||||
const char *p = lsl + 3;
|
||||
while (isspace ((ut8)*p)) {
|
||||
p++;
|
||||
}
|
||||
if (*p == '#') {
|
||||
p++;
|
||||
}
|
||||
if (!isdigit ((ut8)*p)) {
|
||||
return false;
|
||||
}
|
||||
int shift = atoi (hash + 1);
|
||||
int shift = atoi (p);
|
||||
if (shift <= 0 || shift >= 63) {
|
||||
return false;
|
||||
}
|
||||
@@ -226,55 +260,114 @@ static bool flutter_parse_shifted_imm(const char *line, ut64 *imm) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static void flutter_opinfo_add_reg(FlutterOpInfo *info, const char *reg) {
|
||||
if (R_STR_ISEMPTY (reg) || info->n_regs >= (int)R_ARRAY_SIZE (info->regs)) {
|
||||
return;
|
||||
}
|
||||
r_str_ncpy (info->regs[info->n_regs], reg, sizeof (info->regs[0]));
|
||||
info->n_regs++;
|
||||
}
|
||||
|
||||
static void flutter_opinfo_add_value(FlutterOpInfo *info, const RArchValue *value, bool add_reg) {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
if (add_reg && R_STR_ISNOTEMPTY (value->reg)) {
|
||||
flutter_opinfo_add_reg (info, value->reg);
|
||||
}
|
||||
if (value->imm) {
|
||||
info->imm = (ut64)value->imm;
|
||||
info->has_imm = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void flutter_parse_mem_operand(const char *line, FlutterOpInfo *info) {
|
||||
const char *p = R_STR_ISNOTEMPTY (line)? strchr (line, '['): NULL;
|
||||
if (!p) {
|
||||
return;
|
||||
}
|
||||
p++;
|
||||
while (isspace ((ut8)*p)) {
|
||||
p++;
|
||||
}
|
||||
const char *reg = p;
|
||||
while (isalnum ((ut8)*p) || *p == '_') {
|
||||
p++;
|
||||
}
|
||||
const size_t reg_len = p - reg;
|
||||
if (!reg_len || reg_len >= sizeof (info->mem_base)) {
|
||||
return;
|
||||
}
|
||||
memcpy (info->mem_base, reg, reg_len);
|
||||
info->mem_base[reg_len] = '\0';
|
||||
info->has_mem = true;
|
||||
while (isspace ((ut8)*p)) {
|
||||
p++;
|
||||
}
|
||||
if (*p != ',') {
|
||||
return;
|
||||
}
|
||||
p++;
|
||||
while (isspace ((ut8)*p) || *p == '#') {
|
||||
p++;
|
||||
}
|
||||
char *end = NULL;
|
||||
st64 disp = (st64)strtoll (p, &end, 0);
|
||||
if (end != p) {
|
||||
info->mem_disp = disp;
|
||||
}
|
||||
}
|
||||
|
||||
static void flutter_parse_first_operand_reg(const char *line, FlutterOpInfo *info) {
|
||||
const char *p = R_STR_ISNOTEMPTY (line)? strchr (line, ' '): NULL;
|
||||
if (!p) {
|
||||
return;
|
||||
}
|
||||
while (isspace ((ut8)*p)) {
|
||||
p++;
|
||||
}
|
||||
const char *reg = p;
|
||||
while (isalnum ((ut8)*p) || *p == '_') {
|
||||
p++;
|
||||
}
|
||||
const size_t reg_len = p - reg;
|
||||
if (!reg_len || reg_len >= sizeof (info->regs[0])) {
|
||||
return;
|
||||
}
|
||||
char name[sizeof (info->regs[0])];
|
||||
memcpy (name, reg, reg_len);
|
||||
name[reg_len] = '\0';
|
||||
flutter_opinfo_add_reg (info, name);
|
||||
}
|
||||
|
||||
static bool flutter_parse_opinfo(const RAnalOp *op, FlutterOpInfo *info) {
|
||||
if (!op || !info) {
|
||||
return false;
|
||||
}
|
||||
memset (info, 0, sizeof (*info));
|
||||
const char *opex = r_strbuf_get ((RStrBuf *)&op->opex);
|
||||
if (R_STR_ISEMPTY (opex)) {
|
||||
return false;
|
||||
const int type = op->type & R_ANAL_OP_TYPE_MASK;
|
||||
const RArchValue *value = RVecRArchValue_at (&op->dsts, 0);
|
||||
if (type != R_ANAL_OP_TYPE_STORE) {
|
||||
flutter_opinfo_add_value (info, value, true);
|
||||
}
|
||||
char *opexdup = strdup (opex);
|
||||
RJson *j = r_json_parse (opexdup);
|
||||
if (!j) {
|
||||
free (opexdup);
|
||||
return false;
|
||||
const RArchValue *src;
|
||||
R_VEC_FOREACH (&op->srcs, src) {
|
||||
const bool add_reg = type != R_ANAL_OP_TYPE_LOAD;
|
||||
flutter_opinfo_add_value (info, src, add_reg);
|
||||
}
|
||||
const RJson *ops = r_json_get (j, "operands");
|
||||
for (size_t i = 0;; i++) {
|
||||
const RJson *item = r_json_item (ops, i);
|
||||
if (!item) {
|
||||
break;
|
||||
}
|
||||
const char *type = r_json_get_str (item, "type");
|
||||
if (R_STR_ISEMPTY (type)) {
|
||||
continue;
|
||||
}
|
||||
if (!strcmp (type, "reg")) {
|
||||
const char *value = r_json_get_str (item, "value");
|
||||
if (R_STR_ISNOTEMPTY (value) && info->n_regs < 4) {
|
||||
r_str_ncpy (info->regs[info->n_regs], value, sizeof (info->regs[0]));
|
||||
info->n_regs++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!strcmp (type, "mem")) {
|
||||
const char *base = r_json_get_str (item, "base");
|
||||
if (R_STR_ISNOTEMPTY (base)) {
|
||||
r_str_ncpy (info->mem_base, base, sizeof (info->mem_base));
|
||||
info->has_mem = true;
|
||||
}
|
||||
info->mem_disp = (st64)r_json_get_num (item, "disp");
|
||||
continue;
|
||||
}
|
||||
if (!strcmp (type, "imm")) {
|
||||
info->imm = r_json_get_num (item, "value");
|
||||
info->has_imm = true;
|
||||
}
|
||||
if (type == R_ANAL_OP_TYPE_LOAD || type == R_ANAL_OP_TYPE_STORE) {
|
||||
flutter_parse_mem_operand (op->mnemonic, info);
|
||||
}
|
||||
if (type == R_ANAL_OP_TYPE_STORE && info->n_regs == 0) {
|
||||
flutter_parse_first_operand_reg (op->mnemonic, info);
|
||||
}
|
||||
if (!info->has_imm && type == R_ANAL_OP_TYPE_ADD && op->val) {
|
||||
info->imm = op->val;
|
||||
info->has_imm = true;
|
||||
}
|
||||
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);
|
||||
}
|
||||
r_json_free (j);
|
||||
free (opexdup);
|
||||
if (info->has_imm && R_STR_ISNOTEMPTY (op->mnemonic)) {
|
||||
(void)flutter_parse_shifted_imm (op->mnemonic, &info->imm);
|
||||
}
|
||||
@@ -351,6 +444,59 @@ static bool flutter_model_load(FlutterAnalModel *model, RCore *core, DartCtx *dc
|
||||
return true;
|
||||
}
|
||||
|
||||
static void flutter_pp_string_map_add(FlutterPpStringMap *map, const ModernPoolStrRef *info) {
|
||||
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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
static void flutter_pp_string_ref_cb(const ModernPoolStrRef *info, void *user) {
|
||||
flutter_pp_string_map_add (user, info);
|
||||
}
|
||||
|
||||
static bool flutter_collect_pp_strings_from_snapshot(DartCtx *ctx, ut64 snapshot_base, HtUP *wanted, FlutterPpStringMap *map) {
|
||||
if (!ctx || !snapshot_base || !map) {
|
||||
return false;
|
||||
}
|
||||
DartSnapshotHeader sh = { 0 };
|
||||
if (!dart_snapshot_header_read (ctx, snapshot_base, &sh) || !sh.ok) {
|
||||
return false;
|
||||
}
|
||||
const ModernReq req = {
|
||||
.ctx = ctx,
|
||||
.cluster_start = sh.cluster_start,
|
||||
.cluster_end = snapshot_base + sh.total_len,
|
||||
.num_clusters = sh.nc,
|
||||
.num_base_objects = sh.nb,
|
||||
};
|
||||
return modern_collect_direct_pool_strrefs (&req, wanted, flutter_pp_string_ref_cb, map);
|
||||
}
|
||||
|
||||
static void flutter_pp_string_map_load(DartCtx *ctx, HtUP *wanted, FlutterPpStringMap *map) {
|
||||
if (!ctx || !map || find_snapshots (ctx) != 0) {
|
||||
return;
|
||||
}
|
||||
DartVerLayout layout_tmp;
|
||||
DartVerLayout *layout_owned = ctx->layout? NULL: dart_ctx_init_layout (ctx, &layout_tmp);
|
||||
if (!flutter_collect_pp_strings_from_snapshot (ctx, ctx->iso_data, wanted, map)) {
|
||||
flutter_collect_pp_strings_from_snapshot (ctx, ctx->vm_data, wanted, map);
|
||||
}
|
||||
if (layout_owned) {
|
||||
dart_ctx_fini_layout (ctx, layout_owned);
|
||||
}
|
||||
}
|
||||
|
||||
static const FlutterPpStringRef *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;
|
||||
}
|
||||
|
||||
static void flutter_apply_model_to_core(FlutterAnalModel *model, RCore *core, FlutterAnalStats *stats) {
|
||||
if (!model || !core) {
|
||||
return;
|
||||
@@ -740,7 +886,7 @@ static bool flutter_process_stack_store(FlutterAnalState *state, const FlutterOp
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool flutter_process_pp_load(RCore *core, FlutterAnalState *state, ut64 at, const FlutterOpInfo *info, FlutterAnalStats *stats) {
|
||||
static bool flutter_process_pp_load(RCore *core, FlutterAnalState *state, FlutterPpStringMap *pp_strings, ut64 at, const FlutterOpInfo *info, FlutterAnalStats *stats) {
|
||||
if (!state || !info || info->n_regs < 1 || !info->has_mem) {
|
||||
return false;
|
||||
}
|
||||
@@ -761,11 +907,23 @@ static bool flutter_process_pp_load(RCore *core, FlutterAnalState *state, ut64 a
|
||||
memset (&state->regs[dst], 0, sizeof (state->regs[dst]));
|
||||
state->regs[dst].has_pp_off = true;
|
||||
state->regs[dst].pp_off = pp_off;
|
||||
char *msg = r_str_newf ("dart: "
|
||||
"PP slot +0x%" PFMT64x,
|
||||
pp_off);
|
||||
flutter_append_comment (core, at, msg, stats);
|
||||
free (msg);
|
||||
const FlutterPpStringRef *string_ref = flutter_pp_string_map_get (pp_strings, pp_off);
|
||||
if (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);
|
||||
flutter_append_comment (core, at, msg, stats);
|
||||
free (msg);
|
||||
free (escaped);
|
||||
} else {
|
||||
char *msg = r_str_newf ("dart: "
|
||||
"PP slot +0x%" PFMT64x,
|
||||
pp_off);
|
||||
flutter_append_comment (core, at, msg, stats);
|
||||
free (msg);
|
||||
}
|
||||
stats->pp_refs++;
|
||||
return true;
|
||||
}
|
||||
@@ -837,7 +995,7 @@ static void flutter_process_indirect_call(RCore *core, FlutterAnalState *state,
|
||||
}
|
||||
}
|
||||
|
||||
static void flutter_scan_function(RCore *core, FlutterAnalModel *model, RAnalFunction *fcn, FlutterAnalStats *stats) {
|
||||
static void flutter_scan_function(RCore *core, FlutterAnalModel *model, FlutterPpStringMap *pp_strings, RAnalFunction *fcn, FlutterAnalStats *stats) {
|
||||
if (!core || !model || !fcn || !stats) {
|
||||
return;
|
||||
}
|
||||
@@ -866,7 +1024,7 @@ static void flutter_scan_function(RCore *core, FlutterAnalModel *model, RAnalFun
|
||||
at += 4;
|
||||
continue;
|
||||
}
|
||||
RAnalOp *op = r_core_anal_op (core, at, R_ARCH_OP_MASK_BASIC | R_ARCH_OP_MASK_OPEX | R_ARCH_OP_MASK_DISASM);
|
||||
RAnalOp *op = r_core_anal_op (core, at, R_ARCH_OP_MASK_BASIC | R_ARCH_OP_MASK_VAL | R_ARCH_OP_MASK_DISASM);
|
||||
if (!op || op->size <= 0) {
|
||||
r_anal_op_free (op);
|
||||
break;
|
||||
@@ -898,7 +1056,7 @@ static void flutter_scan_function(RCore *core, FlutterAnalModel *model, RAnalFun
|
||||
(void)flutter_process_add (&state, &info);
|
||||
break;
|
||||
case R_ANAL_OP_TYPE_LOAD:
|
||||
if (!flutter_process_pp_load (core, &state, at, &info, stats) &&
|
||||
if (!flutter_process_pp_load (core, &state, pp_strings, at, &info, stats) &&
|
||||
!flutter_process_stack_load (&state, &info) &&
|
||||
!flutter_process_field_load (core, model, &state, at, &info, stats) &&
|
||||
has_info && info.n_regs > 0) {
|
||||
@@ -938,7 +1096,7 @@ static void flutter_scan_function(RCore *core, FlutterAnalModel *model, RAnalFun
|
||||
flutter_state_fini (&state);
|
||||
}
|
||||
|
||||
static void flutter_scan_functions(RCore *core, FlutterAnalModel *model, RVecFlutterEntry *entries, FlutterAnalStats *stats) {
|
||||
static void flutter_scan_functions(RCore *core, FlutterAnalModel *model, FlutterPpStringMap *pp_strings, RVecFlutterEntry *entries, FlutterAnalStats *stats) {
|
||||
if (!core || !model || !entries || !stats) {
|
||||
return;
|
||||
}
|
||||
@@ -953,7 +1111,7 @@ static void flutter_scan_functions(RCore *core, FlutterAnalModel *model, RVecFlu
|
||||
continue;
|
||||
}
|
||||
ht_up_insert (seen_fcns, fcn->addr, fcn);
|
||||
flutter_scan_function (core, model, fcn, stats);
|
||||
flutter_scan_function (core, model, pp_strings, fcn, stats);
|
||||
}
|
||||
ht_up_free (seen_fcns);
|
||||
}
|
||||
@@ -966,7 +1124,7 @@ bool r2flutter_analysis_run(RCore *core, DartCtx *dctx, bool quiet) {
|
||||
return false;
|
||||
}
|
||||
dart_app_load_info (app);
|
||||
dart_dumper_apply_to_core (app);
|
||||
dart_dumper_apply_to_core (app, true);
|
||||
|
||||
FlutterAnalModel model;
|
||||
if (!flutter_model_load (&model, core, &app->dctx)) {
|
||||
@@ -979,9 +1137,13 @@ bool r2flutter_analysis_run(RCore *core, DartCtx *dctx, bool quiet) {
|
||||
flutter_apply_app_methods_to_core (core, app, &stats);
|
||||
flutter_apply_model_to_core (&model, core, &stats);
|
||||
|
||||
FlutterPpStringMap pp_strings;
|
||||
flutter_pp_string_map_init (&pp_strings);
|
||||
flutter_pp_string_map_load (&app->dctx, NULL, &pp_strings);
|
||||
|
||||
RVecFlutterEntry *entries = flutter_collect_entries (core, app, &model);
|
||||
flutter_ensure_functions (core, entries);
|
||||
flutter_scan_functions (core, &model, entries, &stats);
|
||||
flutter_scan_functions (core, &model, &pp_strings, entries, &stats);
|
||||
|
||||
if (!quiet) {
|
||||
R_LOG_INFO ("Flutter analysis: %d functions, %d calls, %d fields, %d classes, %d types, %d strings, %d PP refs",
|
||||
@@ -995,6 +1157,7 @@ bool r2flutter_analysis_run(RCore *core, DartCtx *dctx, bool quiet) {
|
||||
}
|
||||
|
||||
RVecFlutterEntry_free (entries);
|
||||
flutter_pp_string_map_fini (&pp_strings);
|
||||
flutter_model_fini (&model);
|
||||
dart_app_free (app);
|
||||
return true;
|
||||
|
||||
+48
-24
@@ -10,35 +10,33 @@
|
||||
#include "../r2/flutter_analysis.h"
|
||||
|
||||
static const char usage_text[] =
|
||||
"Usage: %s [options] <libapp_path_or_dir>\n"
|
||||
"Usage: %s [-jrv*] <libapp_path_or_dir>\n"
|
||||
"Modifiers:\n"
|
||||
" -h Show help\n"
|
||||
" -j Output in JSON format\n"
|
||||
" -n Heuristic fallback for unknown functions; may assign wrong names\n"
|
||||
" -q Compact output; suppress non-essential detail\n"
|
||||
" -r Output r2 commands for the selected action\n"
|
||||
" -n Heuristic fallback for unknown functions; may assign wrong names\n"
|
||||
" -v Verbose (stderr debug info)\n"
|
||||
" -vv More verbose (dump headers)\n"
|
||||
" -V Show version\n"
|
||||
"Actions:\n"
|
||||
" -A Analyze Dart snapshot and apply flags/comments\n"
|
||||
" -AA Analyze with field extraction enabled\n"
|
||||
" -AAA Run Dart-aware code analysis and recover code refs\n"
|
||||
" -c Print extracted class information\n"
|
||||
" -f Print all extracted functions (addr name)\n"
|
||||
" -H Print Dart AOT snapshot header info\n"
|
||||
" -HH Print extended snapshot header and cluster layout\n"
|
||||
" -HHH Decode selected cluster payloads for diagnostics\n"
|
||||
" -i Print instruction table entries to stdout\n"
|
||||
" -O <addr|pp+off> Decode Dart tagged/object pointer or ObjectPool PP slot\n"
|
||||
" -p Print reconstructed ObjectPool PP value\n"
|
||||
" -A[AA] Analyze Dart snapshot and apply flags/comments\n"
|
||||
" -c[jr*] Print extracted class information\n"
|
||||
" -f[jr*] Print all extracted functions (addr name)\n"
|
||||
" -H[HH] Print Dart AOT snapshot header info\n"
|
||||
" -i[jr*] Print instruction table entries (-ii same as -i)\n"
|
||||
" -ie[jr*], -E[jr*] Print Dart code entrypoint\n"
|
||||
" -O <addr|pp+off|ref:N> Decode a tagged pointer, PP slot, or snapshot reference\n"
|
||||
" -p[jr*] Print reconstructed ObjectPool PP value\n"
|
||||
" -R Print radare2 script for snapshot analysis\n"
|
||||
" -S Print best-effort recovered SBOM/components\n"
|
||||
" -T Print string-based type names\n"
|
||||
" -x Print metadata/data-image xrefs; with -z, include string refs/ax in -r\n"
|
||||
" -z Print reliable ObjectPool-referenced strings (-q prints values only)\n"
|
||||
" -zz Print all fuzzy/carved extracted strings (-xzz includes refs/ax in -r)\n"
|
||||
" -S[jr*] Print best-effort recovered SBOM/components\n"
|
||||
" -T[jr*] Print string-based type names\n"
|
||||
" -x[jr*] Print metadata/data-image xrefs; with -z, include string refs/ax in -r\n"
|
||||
" -z[jr*] Print reliable ObjectPool-referenced strings (-q prints values only)\n"
|
||||
" -zz[jr*] Print all fuzzy/carved extracted strings (-xzz includes refs/ax in -r)\n"
|
||||
"Options:\n"
|
||||
" -D <hash|version> Override Dart snapshot profile for analysis\n"
|
||||
" -l <N> Limit output to N items\n"
|
||||
" -m <file> Load Flutter obfuscation map JSON\n";
|
||||
|
||||
@@ -84,8 +82,8 @@ static char *resolve_input_path(const char *s, DartCtx *dctx, bool *extracted_in
|
||||
return NULL;
|
||||
}
|
||||
DartAppEmbeddedPayload payload;
|
||||
if (dart_app_find_macho_embedded_dart (s, &payload)) {
|
||||
char *inner = dart_app_extract_embedded_payload (s, &payload);
|
||||
if (dart_app_find_macho_dart (s, &payload)) {
|
||||
char *inner = dart_app_extract_payload (s, &payload);
|
||||
if (!inner) {
|
||||
R_LOG_ERROR ("Failed to extract embedded Dart Mach-O from: %s", s);
|
||||
return NULL;
|
||||
@@ -117,7 +115,7 @@ int main(int argc, char **argv) {
|
||||
.no_stubs = true
|
||||
};
|
||||
RGetopt opt;
|
||||
r_getopt_init (&opt, argc, (const char **)argv, "AcfhHijnm:O:pqrRSzTvVxl:");
|
||||
r_getopt_init (&opt, argc, (const char **)argv, "AcD:eEfHhijnm:O:pqrRSzTvVxl:");
|
||||
int c;
|
||||
while ((c = r_getopt_next (&opt)) != -1) {
|
||||
switch (c) {
|
||||
@@ -128,6 +126,9 @@ int main(int argc, char **argv) {
|
||||
case 'c':
|
||||
action = c;
|
||||
break;
|
||||
case 'E':
|
||||
action = 'e';
|
||||
break;
|
||||
case 'f':
|
||||
action = c;
|
||||
break;
|
||||
@@ -142,6 +143,14 @@ int main(int argc, char **argv) {
|
||||
action = c;
|
||||
dctx.dump_it = true;
|
||||
break;
|
||||
case 'e':
|
||||
if (action == 'i') {
|
||||
action = c;
|
||||
} else {
|
||||
R_LOG_ERROR ("Invalid flag: -%c", c);
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'O':
|
||||
action = c;
|
||||
object_spec = opt.arg;
|
||||
@@ -149,6 +158,12 @@ int main(int argc, char **argv) {
|
||||
case 'p':
|
||||
action = c;
|
||||
break;
|
||||
case 'D':
|
||||
if (!dart_ctx_set_profile_override (&dctx, opt.arg)) {
|
||||
R_LOG_ERROR ("Unsupported Dart snapshot profile override: %s", opt.arg);
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'j':
|
||||
fmt = c;
|
||||
break;
|
||||
@@ -200,6 +215,7 @@ int main(int argc, char **argv) {
|
||||
print_usage (argv[0]);
|
||||
return 1;
|
||||
default:
|
||||
R_LOG_ERROR ("Invalid flag: -%c", opt.opt);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -237,6 +253,11 @@ int main(int argc, char **argv) {
|
||||
free_resolved_path (libapp_path, extracted_inner);
|
||||
return 1;
|
||||
}
|
||||
// r2flutter does its own Dart-aware string/symbol extraction and never
|
||||
// consumes RBin's string list, so skip the costly RBin string scan that
|
||||
// r_core_bin_load runs over the whole binary (dominant load-time cost on
|
||||
// large libapp.so / App images).
|
||||
r_config_set_b (core->config, "bin.strings", false);
|
||||
r_core_bin_load (core, NULL, 0);
|
||||
|
||||
dctx.core = core;
|
||||
@@ -286,7 +307,7 @@ int main(int argc, char **argv) {
|
||||
R_LOG_INFO ("Loaded %zu functions from Dart snapshot", count);
|
||||
}
|
||||
}
|
||||
dart_dumper_apply_to_core (app);
|
||||
dart_dumper_apply_to_core (app, false);
|
||||
break;
|
||||
case 'z':
|
||||
output = string_depth >= 2? dart_pool_dump_strings_fuzzy (&dctx, fmt): dart_pool_dump_strings (&dctx, fmt);
|
||||
@@ -296,6 +317,9 @@ int main(int argc, char **argv) {
|
||||
dctx.dump_fields = 1;
|
||||
output = dart_pool_dump_classes (&dctx, fmt);
|
||||
break;
|
||||
case 'e':
|
||||
output = dart_pool_dump_entry (&app->dctx, fmt);
|
||||
break;
|
||||
case 'T':
|
||||
dctx.dump_classes = 3;
|
||||
output = dart_pool_dump_classes (&dctx, fmt);
|
||||
@@ -315,7 +339,7 @@ int main(int argc, char **argv) {
|
||||
output = dart_pool_dump_object (&app->dctx, object_spec, fmt);
|
||||
break;
|
||||
case 'p':
|
||||
output = dart_pool_dump_pp (&app->dctx, fmt);
|
||||
output = dart_pool_dump_pool (&app->dctx, fmt);
|
||||
break;
|
||||
case 'x':
|
||||
output = dart_pool_dump_xrefs (&dctx, fmt);
|
||||
@@ -325,7 +349,7 @@ int main(int argc, char **argv) {
|
||||
if (dctx.verbose) {
|
||||
R_LOG_ERROR ("Dumping radare2 script");
|
||||
}
|
||||
char *s = dart_dumper_dump4radare2 (app);
|
||||
char *s = dart_dumper_dump_r2 (app);
|
||||
printf ("%s\n", s);
|
||||
free (s);
|
||||
break;
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "appstrings-android-w0rdle",
|
||||
"cwd": "test",
|
||||
"cmd": ["../bin/r2flutter", "-q", "-z", "bins/android/w0rdle/libapp.so"],
|
||||
"timeout": 30,
|
||||
"expect": {
|
||||
"contains": [
|
||||
"assets/allowed_guesses.txt",
|
||||
"ph0wn CTF is awesome"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "dump-classes-macos",
|
||||
"cwd": "test",
|
||||
"cmd": ["../bin/r2flutter", "-c", "bins/macos/hello/hello.aot"],
|
||||
"timeout": 30,
|
||||
"expect": {
|
||||
"contains": [
|
||||
"class _Closure@0150898 (size=64)",
|
||||
"+0x428 dynamic _context@0150898",
|
||||
"class _Uri@0150898 (size=96)",
|
||||
"+0x0 dynamic _portMap@1026248 static final"
|
||||
],
|
||||
"not_contains": [
|
||||
"class _Uint16List (size=0)",
|
||||
"class FutureOr (size=0)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "dump-funcs-android-w0rdle",
|
||||
"cwd": "test",
|
||||
"cmd": ["../bin/r2flutter", "-q", "-f", "bins/android/w0rdle/libapp.so"],
|
||||
"timeout": 30,
|
||||
"expect": {
|
||||
"contains": [
|
||||
"method.WordService.getWordToFind",
|
||||
"method.Flutterdle.updateAfterSuccessfulGuess"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"timeout": 30,
|
||||
"expect": {
|
||||
"contains": [
|
||||
"func.0000fe28",
|
||||
"method._HashMap_3220832.addAll",
|
||||
"0xfe28"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"timeout": 30,
|
||||
"expect": {
|
||||
"contains": [
|
||||
"func.0000e768",
|
||||
"method.PointerEvent.get:radiusMinor",
|
||||
"0xe768"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
"cmd": ["../bin/r2flutter", "-l", "3", "-i", "bins/ios/Runner.app"],
|
||||
"timeout": 20,
|
||||
"expect": {
|
||||
"equals": "# length=7670 first_entry_with_code=0 canonical_stack_map_entries_offset=160751\n0 0xe768 code func.0000e768\n1 0xe770 code func.0000e770\n2 0xe7a0 code func.0000e7a0"
|
||||
"equals": "# length=7670 first_entry_with_code=0 canonical_stack_map_entries_offset=160751\n0 0xe768 code method.PointerEvent.get:radiusMinor\n1 0xe770 code func.0000e770\n2 0xe7a0 code method.PointerEvent.get:distance"
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,6 @@
|
||||
"cmd": ["../bin/r2flutter", "-j", "-l", "2", "-i", "bins/ios/Runner.app"],
|
||||
"timeout": 20,
|
||||
"expect": {
|
||||
"equals": "{\"length\":7670,\"first_entry_with_code\":0,\"canonical_stack_map_entries_offset\":160751,\"entries\":[{\"index\":0,\"code_index\":0,\"address\":59240,\"pc_offset\":104,\"stack_map_offset\":61376,\"kind\":\"code\",\"name\":\"func.0000e768\"},{\"index\":1,\"code_index\":1,\"address\":59248,\"pc_offset\":112,\"stack_map_offset\":61376,\"kind\":\"code\",\"name\":\"func.0000e770\"}]}"
|
||||
"equals": "{\"length\":7670,\"first_entry_with_code\":0,\"canonical_stack_map_entries_offset\":160751,\"entries\":[{\"index\":0,\"code_index\":0,\"address\":59240,\"pc_offset\":104,\"stack_map_offset\":61376,\"kind\":\"code\",\"name\":\"method.PointerEvent.get:radiusMinor\"},{\"index\":1,\"code_index\":1,\"address\":59248,\"pc_offset\":112,\"stack_map_offset\":61376,\"kind\":\"code\",\"name\":\"func.0000e770\"}]}"
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,6 @@
|
||||
"cmd": ["../bin/r2flutter", "-r", "-l", "2", "-i", "bins/ios/Runner.app"],
|
||||
"timeout": 20,
|
||||
"expect": {
|
||||
"equals": "# Dart InstructionTable entries\n# length=7670 first_entry_with_code=0 canonical_stack_map_entries_offset=160751\n# it[0] func.0000e768\nf it.code_0 = 0xe768\n# it[1] func.0000e770\nf it.code_1 = 0xe770"
|
||||
"equals": "# Dart InstructionTable entries\n# length=7670 first_entry_with_code=0 canonical_stack_map_entries_offset=160751\n# it[0] method.PointerEvent.get:radiusMinor\nf it.code_0 = 0xe768\n# it[1] func.0000e770\nf it.code_1 = 0xe770"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "dump-strings-android-w0rdle",
|
||||
"cwd": "test",
|
||||
"cmd": ["../bin/r2flutter", "-q", "-z", "bins/android/w0rdle/libapp.so"],
|
||||
"timeout": 30,
|
||||
"expect": {
|
||||
"contains": [
|
||||
"ph0wn",
|
||||
"y@cht",
|
||||
"answer",
|
||||
"ph0wn{you_w1n_darts_stage1}"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "header-android-w0rdle",
|
||||
"cwd": "test",
|
||||
"cmd": ["../bin/r2flutter", "-H", "bins/android/w0rdle/libapp.so"],
|
||||
"timeout": 30,
|
||||
"expect": {
|
||||
"contains": [
|
||||
"dart_version: 2.18.0",
|
||||
"tag_style: CID_SHIFT1",
|
||||
"cid_string: 90",
|
||||
"cid_object_pool: 21"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,10 @@
|
||||
"timeout": 30,
|
||||
"expect": {
|
||||
"contains": [
|
||||
"snapshot_hash=78da37fed6bf1489361a312568249f3f dart_version=unknown",
|
||||
"snapshot_hash=78da37fed6bf1489361a312568249f3f dart_version=3.11.5",
|
||||
"detail: object_pool_entries",
|
||||
"34 cid=23 alloc=object_pool fill=none count=1",
|
||||
"object_pool_decode: fill_not_parsed"
|
||||
"34 cid=23 alloc=object_pool fill=object_pool count=1",
|
||||
"object_pool ref=9145 index=0 length=1380"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "header-json-android-w0rdle",
|
||||
"cwd": "test",
|
||||
"cmd": ["../bin/r2flutter", "-Hj", "bins/android/w0rdle/libapp.so"],
|
||||
"timeout": 30,
|
||||
"expect": {
|
||||
"json": {
|
||||
"dart_version": "2.18.0",
|
||||
"hash": "b0e899ec5a90e4661501f0b69e9dd70f",
|
||||
"cws": 4,
|
||||
"tag_style": "CID_SHIFT1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,10 @@
|
||||
"contains": [
|
||||
"Usage:",
|
||||
"-i",
|
||||
"-H",
|
||||
"-HH",
|
||||
"-HHH",
|
||||
"-H[HH]",
|
||||
"-p",
|
||||
"-S",
|
||||
"-AA",
|
||||
"-AAA"
|
||||
"-A[AA]"
|
||||
],
|
||||
"not_contains": [
|
||||
"--no-stubs"
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "methods-android-w0rdle",
|
||||
"cwd": "test",
|
||||
"cmd": ["../bin/r2flutter", "-q", "-f", "bins/android/w0rdle/libapp.so"],
|
||||
"timeout": 30,
|
||||
"expect": {
|
||||
"contains": [
|
||||
"method.WordService.getWordToFind",
|
||||
"method.WordService._readFile_746434692",
|
||||
"method.Flutterdle.didWin",
|
||||
"method.CachingAssetBundle.loadString"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,6 @@
|
||||
"cmd": ["../bin/r2flutter", "-j", "-f", "-l", "3", "-m", "custom/obf-map.txt", "bins/ios/Runner.app"],
|
||||
"timeout": 30,
|
||||
"expect": {
|
||||
"equals": "[{\"addr\":59240,\"name\":\"func.0000e768\"},{\"addr\":59248,\"name\":\"func.0000e770\"},{\"addr\":59296,\"name\":\"func.0000e7a0\"}]"
|
||||
"equals": "[{\"addr\":59240,\"name\":\"method.PointerEvent.get:radiusMinor\",\"signature\":\"dynamic(PointerEvent)\"},{\"addr\":59248,\"name\":\"func.0000e770\"},{\"addr\":59296,\"name\":\"method.PointerEvent.get:distance\",\"signature\":\"dynamic(PointerEvent)\"}]"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "objdecode-android-w0rdle",
|
||||
"cwd": "test",
|
||||
"cmd": ["../bin/r2flutter", "-O", "pp+36864", "bins/android/w0rdle/libapp.so"],
|
||||
"timeout": 30,
|
||||
"expect": {
|
||||
"contains": [
|
||||
"target.kind: string",
|
||||
"target.name: \"floatingActionButtonTheme\""
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "objdecode-func-android-w0rdle",
|
||||
"cwd": "test",
|
||||
"cmd": ["../bin/r2flutter", "-O", "pp+1240", "bins/android/w0rdle/libapp.so"],
|
||||
"timeout": 30,
|
||||
"expect": {
|
||||
"contains": [
|
||||
"target.kind: function",
|
||||
"target.name: \"method."
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "pool-android-w0rdle",
|
||||
"cwd": "test",
|
||||
"cmd": ["../bin/r2flutter", "-p", "bins/android/w0rdle/libapp.so"],
|
||||
"timeout": 30,
|
||||
"expect": {
|
||||
"contains": [
|
||||
"length: 12351 entries"
|
||||
],
|
||||
"not_contains": [
|
||||
"PP not resolved"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "signatures-android-w0rdle",
|
||||
"cwd": "test",
|
||||
"cmd": ["../bin/r2flutter", "-q", "-j", "-f", "bins/android/w0rdle/libapp.so"],
|
||||
"timeout": 30,
|
||||
"expect": {
|
||||
"contains": [
|
||||
"\"signature\":\"String(_StringBase@0150898,dynamic)\"",
|
||||
"\"signature\":\"dynamic(WordService)\""
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
NAME=r2-analysis-string-comment
|
||||
FILE=bins/android/first/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 -AAA
|
||||
pd 1 @ 0x8b914~&_nativeGetFloat64,dart: string[0]
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x0008b914
|
||||
EOF
|
||||
RUN
|
||||
@@ -0,0 +1,13 @@
|
||||
NAME=r2-analysis-pp-string-comment-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 -AAA
|
||||
pd 1 @ 0x176d0~&PP slot,string,https[0]
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x000176d0
|
||||
EOF
|
||||
RUN
|
||||
@@ -0,0 +1,102 @@
|
||||
NAME=r2-authpass-tls-flags
|
||||
FILE=bins/ios/AuthPass.app/Frameworks/App.framework/App
|
||||
ARGS=-NN -e bin.relocs.apply=true
|
||||
CMDS=<<EOF
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -A
|
||||
f~onBadCertificateWrapper~method
|
||||
f~registerBadCertificateCallback~method~!_registerBadCertificateCallback_
|
||||
f~_registerBadCertificateCallback_~method
|
||||
f~_secureHandshake_~method
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x003199d4 0 method._RawSecureSocket_14069316._onBadCertificateWrapper_14069316
|
||||
0x003194ec 0 method._SecureFilterImpl_14069316.registerBadCertificateCallback
|
||||
0x0031954c 0 method._SecureFilterImpl_14069316._registerBadCertificateCallback_14069316
|
||||
0x0031749c 0 method._RawSecureSocket_14069316._secureHandshake_14069316
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=r2-authpass-callsite-xrefs
|
||||
FILE=bins/ios/AuthPass.app/Frameworks/App.framework/App
|
||||
ARGS=-NN -e bin.relocs.apply=true
|
||||
CMDS=<<EOF
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -A
|
||||
pd 1 @ 0x3199bc~onBadCertificateWrapper[0]
|
||||
pd 1 @ 0x316cd8~registerBadCertificateCallback[0]
|
||||
pd 1 @ 0x319530~_registerBadCertificateCallback_[0]
|
||||
pd 1 @ 0x317028~_secureHandshake_[0]
|
||||
pd 1 @ 0x317514~_secureHandshake_[0]
|
||||
pd 1 @ 0x3178b4~_secureHandshake_[0]
|
||||
pd 1 @ 0x317e04~_secureHandshake_[0]
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x003199bc
|
||||
0x00316cd8
|
||||
0x00319530
|
||||
0x00317028
|
||||
0x00317514
|
||||
0x003178b4
|
||||
0x00317e04
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=r2-authpass-handshake-disasm
|
||||
FILE=bins/ios/AuthPass.app/Frameworks/App.framework/App
|
||||
ARGS=-NN -e bin.relocs.apply=true
|
||||
CMDS=<<EOF
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -A
|
||||
pd 1 @ 0x3174e4
|
||||
pd 1 @ 0x31752c
|
||||
pd 1 @ 0x317554
|
||||
pd 1 @ 0x3175ac
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x003174e4 3f060094 bl sym.func.00318de0 ; method._SecureFilterImpl_14069316.handshake ; method._SecureFilterImpl_14069316.handshake(0x0, 0x0, 0x0, 0x0)
|
||||
0x0031752c c1c20091 add x1, x22, 0x30
|
||||
0x00317554 13000094 bl sym.func.003175a0 ; method._RawSecureSocket_14069316._scheduleFilter_14069316 ; method._RawSecureSocket_14069316._scheduleFilter_14069316(0x0, 0x0, 0x0, 0x0)
|
||||
0x003175ac c0820091 add x0, x22, 0x20 ; mach0_segment64_0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=r2-authpass-wrapper-disasm
|
||||
FILE=bins/ios/AuthPass.app/Frameworks/App.framework/App
|
||||
ARGS=-NN -e bin.relocs.apply=true
|
||||
CMDS=<<EOF
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -A
|
||||
pd 1 @ 0x3199f0
|
||||
pd 1 @ 0x319a10
|
||||
pd 1 @ 0x319a1c
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x003199f0 01f045f8 ldur x1, [x0, 0x5f]
|
||||
0x00319a10 40003fd6 blr x2 ; app.base(0x0, 0x0, 0x0, 0x0)
|
||||
0x00319a1c c0035fd6 ret
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=r2-authpass-qjs-bypass
|
||||
FILE=bins/ios/AuthPass.app/Frameworks/App.framework/App
|
||||
ARGS=-NN -e bin.relocs.apply=true
|
||||
CMDS=<<EOF
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -A
|
||||
f~onBadCertificateWrapper~method
|
||||
e io.cache=true
|
||||
s 0x3199d4
|
||||
wx c0820091c0035fd6
|
||||
p8 8
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x003199d4 0 method._RawSecureSocket_14069316._onBadCertificateWrapper_14069316
|
||||
c0820091c0035fd6
|
||||
EOF
|
||||
RUN
|
||||
@@ -0,0 +1,36 @@
|
||||
NAME=serialized-class-layout-metadata
|
||||
FILE=malloc://0x94
|
||||
CMDS=<<EOF
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
wx 50414421f5f5dcdc8c000000000000000200000000000000303435313930376332656161383436376538343863303036376266653865643470726f647563742061726d3634206e6f2d636f6d707265737365642d706f696e746572730086828180800020c182818080808080808280838080807022c4c4c3c2c1c0c08584808080808080858086808080070040c0c5c4c0c0c0c0
|
||||
r2flutter -q -j -c~{=}ref,class_id,layout.,flags.toplevel
|
||||
o malloc://0x7c
|
||||
wx 50414421f5f5dcdc74000000000000000200000000000000303435313930376332656161383436376538343863303036376266653865643470726f647563742061726d3634206e6f2d636f6d707265737365642d706f696e746572730086818180800020c181818080808080808280838080807022c4c4c3c2c1c0c0
|
||||
r2flutter -q -j -c
|
||||
r2flutter -q -j -HHH~{snapshots[0].clusters[0].fill_status}
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
json[0].ref = 7;
|
||||
json[0].class_id = 70000;
|
||||
json[0].library.ref = 2;
|
||||
json[0].super.type_ref = 3;
|
||||
json[0].layout.instance_size = 32;
|
||||
json[0].layout.next_field_offset = 24;
|
||||
json[0].layout.type_params = 1;
|
||||
json[0].layout.type_arg_offset = 16;
|
||||
json[0].layout.field_bitmap = 5;
|
||||
json[0].flags.toplevel = false;
|
||||
json[1].ref = 8;
|
||||
json[1].class_id = 1048583;
|
||||
json[1].library.ref = 5;
|
||||
json[1].super.type_ref = 6;
|
||||
json[1].layout.instance_size = 40;
|
||||
json[1].layout.next_field_offset = 32;
|
||||
json[1].layout.type_params = 0;
|
||||
json[1].layout.type_arg_offset = 0;
|
||||
json[1].flags.toplevel = true;
|
||||
[]
|
||||
failed
|
||||
EOF
|
||||
RUN
|
||||
@@ -1,7 +1,9 @@
|
||||
NAME=r2-classes-android
|
||||
FILE=-
|
||||
FILE=bins/android/first/libapp.so
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -r -c bins/android/first 2>/dev/null | sed -n '1,7p'
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -r -c~:0..7
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
# Dart classes extracted from snapshot
|
||||
@@ -15,9 +17,11 @@ EOF
|
||||
RUN
|
||||
|
||||
NAME=text-classes-android
|
||||
FILE=-
|
||||
FILE=bins/android/first/libapp.so
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -c bins/android/first 2>/dev/null | head -6
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -c~:0..6
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
class _Uint16List (size=0)
|
||||
|
||||
+20
-16
@@ -1,30 +1,34 @@
|
||||
NAME=text-classes-ios
|
||||
FILE=-
|
||||
FILE=bins/ios/Runner.app/Frameworks/App.framework/App
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -c bins/ios/Runner.app 2>/dev/null | sed -n '1,6p'
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -c~:0..6
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
class _Uint16List (size=0)
|
||||
class _ExternalInt64Array (size=0)
|
||||
class FutureOr (size=0)
|
||||
class _Set (size=0)
|
||||
class Int32List. (size=0)
|
||||
class Float32List (size=0)
|
||||
class Pointer (size=32)
|
||||
methods:
|
||||
0x00000000 get:hashCode (GetterFunction)
|
||||
0x00000000 == (RegularFunction)
|
||||
class NativeType (size=16)
|
||||
class NativeFunction (size=16)
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=r2-classes-ios
|
||||
FILE=-
|
||||
FILE=bins/ios/Runner.app/Frameworks/App.framework/App
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -r -c bins/ios/Runner.app 2>/dev/null | head -7
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -r -c~:0..7
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
# Dart classes extracted from snapshot
|
||||
# class _Uint16List (size=0)
|
||||
"td struct dart__Uint16List { };"
|
||||
# class _ExternalInt64Array (size=0)
|
||||
"td struct dart__ExternalInt64Array { };"
|
||||
# class FutureOr (size=0)
|
||||
"td struct dart_FutureOr { };"
|
||||
# class Pointer (size=32)
|
||||
# type_params: 1
|
||||
"td struct dart_Pointer { };"
|
||||
# method 0x00000000 get:hashCode (GetterFunction)
|
||||
# method 0x00000000 == (RegularFunction)
|
||||
# class NativeType (size=16)
|
||||
EOF
|
||||
RUN
|
||||
@@ -1,6 +1,10 @@
|
||||
NAME=text-classes-mafia
|
||||
FILE=-
|
||||
CMDS=!!../bin/r2flutter -c bins/android/mafia 2>/dev/null | head -6
|
||||
FILE=bins/android/mafia/libapp.so
|
||||
CMDS=<<EOF
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -c~:0..6
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
class Pointer (size=32)
|
||||
methods:
|
||||
@@ -12,17 +16,28 @@ EOF
|
||||
RUN
|
||||
|
||||
NAME=text-classes-mafia-modern-members
|
||||
FILE=-
|
||||
CMDS=!!../bin/r2flutter -c bins/android/mafia 2>/dev/null | awk '/class _RawReceivePort@1026248/{raw=1} raw && /_portMap@1026248 static final/{field=1} /firstMatch/{method=1} END { print "field=" field + 0; print "method=" method + 0 }'
|
||||
FILE=bins/android/mafia/libapp.so
|
||||
CMDS=<<EOF
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -c~?&_RawReceivePort@1026248,size=32
|
||||
r2flutter -c~?_portMap@1026248
|
||||
r2flutter -c~?firstMatch
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
field=1
|
||||
method=1
|
||||
1
|
||||
1
|
||||
1
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=r2-classes-mafia
|
||||
FILE=-
|
||||
CMDS=!!../bin/r2flutter -r -c bins/android/mafia 2>/dev/null | head -7
|
||||
FILE=bins/android/mafia/libapp.so
|
||||
CMDS=<<EOF
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -r -c~:0..7
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
# Dart classes extracted from snapshot
|
||||
# class Pointer (size=32)
|
||||
|
||||
@@ -1,19 +1,46 @@
|
||||
NAME=r2-plugin-C-mafia-future
|
||||
FILE=-
|
||||
FILE=bins/android/mafia/libapp.so
|
||||
ARGS=-NN -e bin.relocs.apply=true
|
||||
CMDS=<<EOF
|
||||
!!sh -c "p=../src/r2/core_flutter.dylib; [ -f \"\$p\" ] || p=../src/r2/core_flutter.so; r2 -NN -q -escr.color=0 -e bin.relocs.apply=true -i \"\$p\" -qc \"r2flutter -C\" -qc \"ic dart_Future\" -qc q bins/android/mafia/libapp.so 2>&1"
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -C
|
||||
icqq~dart_Future
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
INFO: Applied 8986 Dart classes, 1756 fields, 26609 methods
|
||||
dart_FutureOr
|
||||
dart_FutureProviderRef
|
||||
dart_FutureProvider
|
||||
dart_FutureBuilder
|
||||
dart_FutureProviderFamilyBuilder
|
||||
dart_FutureProviderFamily
|
||||
dart_FutureHandlerProviderElementMixin
|
||||
dart_FutureProviderElement
|
||||
dart_Future
|
||||
EOF
|
||||
RUN
|
||||
|
||||
# Remove BROKEN=1 when radare2 6.2.2 is released.
|
||||
NAME=r2-plugin-C-mafia-future-method-attributes
|
||||
BROKEN=1
|
||||
FILE=bins/android/mafia/libapp.so
|
||||
ARGS=-NN -e bin.relocs.apply=true
|
||||
CMDS=<<EOF
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -C
|
||||
ic dart_Future
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
class dart_Future
|
||||
0x00000000 method dart_Future Future_error
|
||||
0x00000000 method dart_Future Future_
|
||||
0x00000000 method dart_Future _anonymous_closure_
|
||||
0x00000000 method dart_Future Future_value
|
||||
0x00000000 method dart_Future wait
|
||||
0x00000000 method dart_Future handleError
|
||||
0x00000000 method dart_Future Future_delayed
|
||||
0x00000000 method dart_Future Future_microtask
|
||||
0x00000000 method dart_Future Future_sync
|
||||
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
|
||||
EOF
|
||||
RUN
|
||||
@@ -0,0 +1,26 @@
|
||||
NAME=versioned-code-cluster-layouts
|
||||
FILE=malloc://0x75
|
||||
CMDS=<<EOF
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
wx 50414421f5f5dcdc6d000000000000000300000000000000356239373239326232356630613731353631336237613238653037333466373770726f647563742061726d3634206e6f2d636f6d707265737365642d706f696e74657273008284818187ce8180d1818184878080808080808081002c82
|
||||
r2flutter -q -HHH -l 3~alloc=code:0
|
||||
r2flutter -q -HHH -l 3~bits=:0
|
||||
o malloc://0x76
|
||||
wx 50414421f5f5dcdc6e000000000000000300000000000000323464396434313163326639306338666265383930376639396538396434623070726f647563742061726d3634206e6f2d636f6d707265737365642d706f696e74657273008284828785e281c080ea818184878080808080808081002c82
|
||||
r2flutter -q -HHH -l 3~alloc=code:0
|
||||
r2flutter -q -HHH -l 3~bits=:0
|
||||
o malloc://0x75
|
||||
wx 50414421f5f5dcdc6d000000000000000300000000000000313434316436623133623836323366613766626636313433336162656264333170726f647563742061726d3634206e6f2d636f6d707265737365642d706f696e7465727300828482878589e281c080ea81818780808080808081002c82
|
||||
r2flutter -q -HHH -l 3~alloc=code:0
|
||||
r2flutter -q -HHH -l 3~bits=:0
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0 cid=14 alloc=code fill=code count=1 refs=3..4 flags=canonical alloc=0x63..0x65 fill=0x68..0x71 fill_status=ok main=1
|
||||
[0] pool_off=0xf pp_off=0x10 bits=0x00 type=tagged_object patch=patchable behavior=snapshotable stream=0x72 value=0x73 ref=300 resolved_kind=out_of_range
|
||||
0 cid=17 alloc=code fill=code count=1 refs=3..4 flags=- alloc=0x63..0x66 fill=0x69..0x72 fill_status=ok main=1
|
||||
[0] pool_off=0xf pp_off=0x10 bits=0x00 type=tagged_object patch=patchable behavior=snapshotable stream=0x73 value=0x74 ref=300 resolved_kind=out_of_range
|
||||
0 cid=17 alloc=code fill=code count=1 refs=3..4 flags=- alloc=0x64..0x67 fill=0x6a..0x71 fill_status=ok main=1
|
||||
[0] pool_off=0xf pp_off=0x10 bits=0x00 type=tagged_object patch=patchable behavior=snapshotable stream=0x72 value=0x73 ref=300 resolved_kind=out_of_range
|
||||
EOF
|
||||
RUN
|
||||
@@ -0,0 +1,21 @@
|
||||
NAME=discovery-stripped-ios-structural-instr
|
||||
FILE=bins/ios/Runner.app/Frameworks/App.framework/App
|
||||
CMDS=<<EOF
|
||||
s 0
|
||||
o: 0x321920
|
||||
wv4 0 @ 0x298
|
||||
wv4 0 @ 0x29c
|
||||
wv4 0 @ 0x2ac
|
||||
ooi
|
||||
is~?kDart
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -j -H~{=}vm_instr,iso_data,iso_instr
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0
|
||||
json.vm_instr = 16384;
|
||||
json.iso_data = 1807872;
|
||||
json.iso_instr = 59136;
|
||||
EOF
|
||||
RUN
|
||||
@@ -0,0 +1,44 @@
|
||||
NAME=entrypoint-ios
|
||||
FILE=bins/ios/Runner.app/Frameworks/App.framework/App
|
||||
CMDS=<<EOF
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -qE
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0xe768
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=json-entrypoint-ios
|
||||
FILE=bins/ios/Runner.app/Frameworks/App.framework/App
|
||||
CMDS=<<EOF
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -jE
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
{"entrypoint":59240,"index":0,"code_index":0,"kind":"code","name":"method.PointerEvent.get:radiusMinor","vm_instr":16384,"vm_instr_size":42736,"iso_instr":59136,"iso_instr_size":1712608}
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=r2-entrypoint-ios
|
||||
FILE=bins/ios/Runner.app/Frameworks/App.framework/App
|
||||
CMDS=<<EOF
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -rE
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
# Dart AOT entrypoint and instruction snapshot data
|
||||
fs dart
|
||||
f dart.entrypoint = 0xe768
|
||||
CC Dart code entrypoint it[0] method.PointerEvent.get:radiusMinor @ 0xe768
|
||||
f dart.vm_instr = 0x4000
|
||||
Cd 4 10684 @ 0x4000
|
||||
CC Dart vm_instr snapshot instructions dword array size=0xa6f0 @ 0x4000
|
||||
f dart.iso_instr = 0xe700
|
||||
Cd 4 428152 @ 0xe700
|
||||
CC Dart iso_instr snapshot instructions dword array size=0x1a21e0 @ 0xe700
|
||||
EOF
|
||||
RUN
|
||||
@@ -1,7 +1,9 @@
|
||||
NAME=text-fields-synthetic
|
||||
FILE=-
|
||||
FILE=bins/synthetic/field_snapshot.bin
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -c bins/synthetic/field_snapshot.bin 2>/dev/null
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -c
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
class TestClass implements TestInterface (size=48)
|
||||
@@ -20,9 +22,11 @@ EOF
|
||||
RUN
|
||||
|
||||
NAME=json-fields-synthetic
|
||||
FILE=-
|
||||
FILE=bins/synthetic/field_snapshot.bin
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -j -c bins/synthetic/field_snapshot.bin 2>/dev/null
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -j -c
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
[{"ref":12,"name":"TestClass","library":{"ref":11,"name":"package:test"},"interfaces":[{"ref":23,"class_ref":13,"name":"TestInterface"}],"layout":{"instance_size":48,"type_params":1,"type_arg_offset":0},"flags":{"abstract":false,"enum":false,"mixin":false,"toplevel":false},"fields":[{"name":"fieldA","type":"int","offset":16,"flags":{"static":false,"final":false,"const":false,"late":false}},{"name":"items","type":"List<int>","offset":24,"flags":{"static":false,"final":false,"const":false,"late":false}},{"name":"genericItems","type":"List<X0>","offset":32,"flags":{"static":false,"final":false,"const":false,"late":false}},{"name":"callback","type":"(int) => String","offset":40,"flags":{"static":false,"final":false,"const":false,"late":false}}],"methods":[{"name":"compute","entry":0,"owner":"TestClass","signature":"(int) => String","kind_tag":0,"kind":"RegularFunction"}]},{"ref":13,"name":"TestInterface","library":{"ref":11,"name":"package:test"},"layout":{"instance_size":0,"type_params":0,"type_arg_offset":0},"flags":{"abstract":false,"enum":false,"mixin":false,"toplevel":false}}]
|
||||
@@ -30,9 +34,11 @@ EOF
|
||||
RUN
|
||||
|
||||
NAME=strings-fields-synthetic
|
||||
FILE=-
|
||||
FILE=bins/synthetic/field_snapshot.bin
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -zz bins/synthetic/field_snapshot.bin 2>/dev/null
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -zz
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x00000018 74 :app "00000000000000000000000000000000product arm64 macos no-compressed-pointers"
|
||||
@@ -49,9 +55,11 @@ EOF
|
||||
RUN
|
||||
|
||||
NAME=json-strings-fields-synthetic
|
||||
FILE=-
|
||||
FILE=bins/synthetic/field_snapshot.bin
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -j -xzz bins/synthetic/field_snapshot.bin 2>/dev/null
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -j -xzz
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
[{"ref":0,"len":74,"value":"00000000000000000000000000000000product arm64 macos no-compressed-pointers","category":"app","two_byte":false,"canonical":true,"addr":24},{"ref":1,"len":9,"value":"TestClass","category":"lib","two_byte":false,"canonical":true,"addr":110,"refs":[{"obj":12,"type":"class","kind":"class.name","name":"TestClass"}]},{"ref":2,"len":12,"value":"package:test","category":"lib","two_byte":false,"canonical":true,"addr":120,"refs":[{"obj":11,"type":"library","kind":"library.name","name":"package:test"}]},{"ref":3,"len":6,"value":"fieldA","category":"lib","two_byte":false,"canonical":true,"addr":133,"refs":[{"obj":25,"type":"field","kind":"field.name","name":"TestClass.fieldA"}]},{"ref":4,"len":5,"value":"items","category":"lib","two_byte":false,"canonical":true,"addr":144,"refs":[{"obj":26,"type":"field","kind":"field.name","name":"TestClass.items"}]},{"ref":5,"len":12,"value":"genericItems","category":"lib","two_byte":false,"canonical":true,"addr":150,"refs":[{"obj":27,"type":"field","kind":"field.name","name":"TestClass.genericItems"}]},{"ref":6,"len":8,"value":"callback","category":"lib","two_byte":false,"canonical":true,"addr":163,"refs":[{"obj":28,"type":"field","kind":"field.name","name":"TestClass.callback"}]},{"ref":7,"len":7,"value":"compute","category":"lib","two_byte":false,"canonical":true,"addr":172,"refs":[{"obj":29,"type":"function","kind":"method.name","name":"TestClass.compute"}]},{"ref":8,"len":13,"value":"TestInterface","category":"lib","two_byte":false,"canonical":true,"addr":180,"refs":[{"obj":12,"type":"class","kind":"class.interface","name":"TestClass"},{"obj":13,"type":"class","kind":"class.name","name":"TestInterface"}]}]
|
||||
@@ -59,9 +67,11 @@ EOF
|
||||
RUN
|
||||
|
||||
NAME=xrefs-fields-synthetic
|
||||
FILE=-
|
||||
FILE=bins/synthetic/field_snapshot.bin
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -x bins/synthetic/field_snapshot.bin 2>/dev/null
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -x
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
metadata class.name class TestClass [ref=12] -> string TestClass [ref=2] @ 0x6e
|
||||
@@ -88,46 +98,64 @@ EOF
|
||||
RUN
|
||||
|
||||
NAME=r2-fields-synthetic-class-metadata
|
||||
FILE=-
|
||||
FILE=bins/synthetic/field_snapshot.bin
|
||||
ARGS=-NN
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -r -c bins/synthetic/field_snapshot.bin 2>/dev/null | grep -E '^(# Dart class symbols|ic\+dart_TestClass @ 0|ic\+dart_TestInterface @ 0|ic\+dart_TestClass:dart_TestInterface|ac dart_TestClass$|acb dart_TestClass dart_TestInterface 0|ic\+dart_TestClass\.\.callback void \* @ 0x28|ic\+dart_TestClass.compute @ 0x0|# field dart_TestClass.callback|# method dart_TestClass.compute)'
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -C
|
||||
e bin.lang
|
||||
ic
|
||||
t dart_TestClass
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
# Dart class symbols and analysis metadata
|
||||
ic+dart_TestClass @ 0
|
||||
ac dart_TestClass
|
||||
ic+dart_TestInterface @ 0
|
||||
ic+dart_TestClass:dart_TestInterface
|
||||
acb dart_TestClass dart_TestInterface 0
|
||||
# field dart_TestClass.callback +0x28 type=(int) => String instance
|
||||
ic+dart_TestClass..callback void * @ 0x28
|
||||
ic+dart_TestClass.compute @ 0x0
|
||||
# method dart_TestClass.compute kind=RegularFunction mode=instance cc=dyncc:x0+8,^:x0!T0 signature=(int) => String
|
||||
ic+dart_TestInterface @ 0
|
||||
dart
|
||||
0x00000000 [0x00000000 - 0x00000000] 0 dart class 0 dart_TestClass :: dart_TestInterface
|
||||
0x00000000 dart method 0 compute
|
||||
0x00000010 dart field 0 fieldA
|
||||
0x00000018 dart field 1 items
|
||||
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
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=r2-fields-synthetic-safe-type-script
|
||||
FILE=-
|
||||
FILE=bins/synthetic/field_snapshot.bin
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -r -c bins/synthetic/field_snapshot.bin 2>/dev/null | awk 'BEGIN { td = 0; bad = 0 } /^"td struct dart_TestClass \{ uint8_t _pad0\[0x10\]; int64_t fieldA; void \* items; void \* genericItems; void \* callback; \};"$/ { td = 1 } /^af @/ || /^afc / || /td struct\.dart/ || /\(int\) => String callback @/ { bad = 1 } END { print "td=" td; print "bad=" bad }'
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -r -c~?td struct dart_TestClass
|
||||
r2flutter -r -c~?^af
|
||||
r2flutter -r -c~?struct.dart
|
||||
r2flutter -r -c~?callback
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
td=1
|
||||
bad=0
|
||||
1
|
||||
0
|
||||
0
|
||||
3
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=r2-plugin-C-fields-synthetic
|
||||
FILE=-
|
||||
FILE=bins/synthetic/field_snapshot.bin
|
||||
CMDS=<<EOF
|
||||
!!sh -c "p=../src/r2/core_flutter.dylib; [ -f \"\$p\" ] || p=../src/r2/core_flutter.so; r2 -NN -q -i \"\$p\" -qc \"r2flutter -C\" -qc ic -qc \"t dart_TestClass\" bins/synthetic/field_snapshot.bin 2>/dev/null" | awk '/class .* dart_TestClass( :: dart_TestInterface)?$/ { cls = 1 } /class .* dart_TestClass :: dart_TestInterface$/ { super = 1 } /method.* compute$/ { method = 1 } /field.* fieldA$/ { field = 1 } /^pf \[16\]\.qppp fieldA items genericItems callback$/ { type = 1 } END { print "class=" cls + 0; print "super=" super + 0; print "method=" method + 0; print "field=" field + 0; print "type=" type + 0 }'
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -C
|
||||
ic~?&class,dart_TestClass
|
||||
ic~?&dart_TestClass,dart_TestInterface
|
||||
ic~?&method,compute
|
||||
ic~?&field,fieldA
|
||||
t dart_TestClass~?fieldA items genericItems callback
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
class=1
|
||||
super=1
|
||||
method=1
|
||||
field=1
|
||||
type=1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
1
|
||||
EOF
|
||||
RUN
|
||||
@@ -1,7 +1,9 @@
|
||||
NAME=text-funcs-android
|
||||
FILE=-
|
||||
FILE=bins/android/first/libapp.so
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -f -l 3 bins/android/first 2>/dev/null
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -f -l 3
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x66768 Object.noSuchMethod
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
NAME=text-funcs-authpass
|
||||
FILE=-
|
||||
FILE=bins/ios/AuthPass.app/Frameworks/App.framework/App
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -f -l 3 bins/ios/AuthPass.app
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -f -l 3
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0xfe28 func.0000fe28
|
||||
0xfed4 func.0000fed4
|
||||
0xff50 func.0000ff50
|
||||
0xfe28 method._HashMap_3220832.addAll
|
||||
0xfed4 method._HashMap_3220832.dyn:forEach
|
||||
0xff50 method.MapBase.dyn:map
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=json-funcs-authpass
|
||||
FILE=-
|
||||
FILE=bins/ios/AuthPass.app/Frameworks/App.framework/App
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -j -f -l 3 bins/ios/AuthPass.app
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -j -f -l 3
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
[{"addr":65064,"name":"func.0000fe28"},{"addr":65236,"name":"func.0000fed4"},{"addr":65360,"name":"func.0000ff50"}]
|
||||
[{"addr":65064,"name":"method._HashMap_3220832.addAll","signature":"dynamic(_HashMap@3220832)"},{"addr":65236,"name":"method._HashMap_3220832.dyn:forEach","signature":"dynamic(_HashMap@3220832,dynamic)"},{"addr":65360,"name":"method.MapBase.dyn:map","signature":"dynamic(MapBase,dynamic)"}]
|
||||
EOF
|
||||
RUN
|
||||
@@ -0,0 +1,14 @@
|
||||
NAME=text-funcs-authpass-semantic
|
||||
FILE=bins/ios/AuthPass.app/Frameworks/App.framework/App
|
||||
CMDS=<<EOF
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -q -f~_secureHandshake_,registerBadCertificateCallback,_registerBadCertificateCallback_,_onBadCertificateWrapper_
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x31749c method._RawSecureSocket_14069316._secureHandshake_14069316
|
||||
0x3194ec method._SecureFilterImpl_14069316.registerBadCertificateCallback
|
||||
0x31954c method._SecureFilterImpl_14069316._registerBadCertificateCallback_14069316
|
||||
0x3199d4 method._RawSecureSocket_14069316._onBadCertificateWrapper_14069316
|
||||
EOF
|
||||
RUN
|
||||
+11
-7
@@ -1,21 +1,25 @@
|
||||
NAME=json-funcs-ios
|
||||
FILE=-
|
||||
FILE=bins/ios/Runner.app/Frameworks/App.framework/App
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -j -f -l 3 bins/ios/Runner.app 2>/dev/null
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -j -f -l 3
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
[{"addr":59240,"name":"func.0000e768"},{"addr":59248,"name":"func.0000e770"},{"addr":59296,"name":"func.0000e7a0"}]
|
||||
[{"addr":59240,"name":"method.PointerEvent.get:radiusMinor","signature":"dynamic(PointerEvent)"},{"addr":59248,"name":"func.0000e770"},{"addr":59296,"name":"method.PointerEvent.get:distance","signature":"dynamic(PointerEvent)"}]
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=r2-funcs-ios
|
||||
FILE=-
|
||||
FILE=bins/ios/Runner.app/Frameworks/App.framework/App
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -r -f -l 2 bins/ios/Runner.app 2>/dev/null
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -r -f -l 2
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
f method.func.0000e768 = 0xe768
|
||||
'@0xe768'CC func.0000e768
|
||||
f method.PointerEvent.get:radiusMinor = 0xe768
|
||||
'@0xe768'CC method.PointerEvent.get:radiusMinor
|
||||
f method.func.0000e770 = 0xe770
|
||||
'@0xe770'CC func.0000e770
|
||||
EOF
|
||||
|
||||
+33
-17
@@ -1,7 +1,9 @@
|
||||
NAME=text-funcs-mafia
|
||||
FILE=-
|
||||
FILE=bins/android/mafia/libapp.so
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -f -l 3 bins/android/mafia 2>/dev/null
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -f -l 3
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x5b69c0 method.DateTime.compareTo
|
||||
@@ -11,9 +13,11 @@ EOF
|
||||
RUN
|
||||
|
||||
NAME=r2-funcs-mafia
|
||||
FILE=-
|
||||
FILE=bins/android/mafia/libapp.so
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -r -f -l 2 bins/android/mafia 2>/dev/null
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -r -f -l 2
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
f method.DateTime.compareTo = 0x5b69c0
|
||||
@@ -24,9 +28,11 @@ EOF
|
||||
RUN
|
||||
|
||||
NAME=quiet-r2-funcs-mafia
|
||||
FILE=-
|
||||
FILE=bins/android/mafia/libapp.so
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -qrf -l 2 bins/android/mafia 2>/dev/null
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -qrf -l 2
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
f method.DateTime.compareTo = 0x5b69c0
|
||||
@@ -35,12 +41,14 @@ EOF
|
||||
RUN
|
||||
|
||||
NAME=json-funcs-mafia
|
||||
FILE=-
|
||||
FILE=bins/android/mafia/libapp.so
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -j -f -l 3 bins/android/mafia 2>/dev/null
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -j -f -l 3
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
[{"addr":5990848,"name":"method.DateTime.compareTo"},{"addr":5991076,"name":"method.Duration.compareTo"},{"addr":5991268,"name":"method.Duration.dyn:_"}]
|
||||
[{"addr":5990848,"name":"method.DateTime.compareTo","signature":"dynamic(DateTime)"},{"addr":5991076,"name":"method.Duration.compareTo","signature":"dynamic(Duration)"},{"addr":5991268,"name":"method.Duration.dyn:_","signature":"dynamic(Duration,dynamic)"}]
|
||||
EOF
|
||||
RUN
|
||||
|
||||
@@ -48,9 +56,11 @@ RUN
|
||||
# stub.Allocate<ClassName>Stub, not shifted into a neighboring method name by
|
||||
# the name-pool fallback. 0x5b6bf4 is the AllocateDurationStub in this fixture.
|
||||
NAME=funcs-mafia-allocate-stub
|
||||
FILE=-
|
||||
FILE=bins/android/mafia/libapp.so
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -f -q bins/android/mafia 2>/dev/null | grep -E '^0x5b6bf4 '
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -f -q~0x5b6bf4
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x5b6bf4 stub.AllocateDurationStub
|
||||
@@ -60,9 +70,11 @@ RUN
|
||||
# The same address must NOT appear under any method.fn_* fallback name, which
|
||||
# was the bug before the Code-owner-cid fix.
|
||||
NAME=funcs-mafia-allocate-stub-not-fallback
|
||||
FILE=-
|
||||
FILE=bins/android/mafia/libapp.so
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -f -q bins/android/mafia 2>/dev/null | grep -c '^0x5b6bf4 method.fn_'
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -f -q~?&0x5b6bf4,method.fn_
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0
|
||||
@@ -72,9 +84,11 @@ RUN
|
||||
# Type-test stubs (Code owner is an AbstractType) must be synthesized as
|
||||
# stub.typetest_* rather than pulling from the name pool.
|
||||
NAME=funcs-mafia-typetest-stub-present
|
||||
FILE=-
|
||||
FILE=bins/android/mafia/libapp.so
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -f -q bins/android/mafia 2>/dev/null | grep -c ' stub.typetest_'
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -f -q~?stub.typetest_
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
1277
|
||||
@@ -85,9 +99,11 @@ RUN
|
||||
# the count is 4096; if it drops noticeably (e.g. back to zero) the owner-cid
|
||||
# lookup has regressed.
|
||||
NAME=funcs-mafia-allocate-stub-count
|
||||
FILE=-
|
||||
FILE=bins/android/mafia/libapp.so
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -f -q bins/android/mafia 2>/dev/null | grep -c ' stub.Allocate'
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -f -q~?stub.Allocate
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
4096
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
NAME=text-funcs-mafia-closure
|
||||
FILE=-
|
||||
FILE=bins/android/mafia/libapp.so
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -f -q bins/android/mafia 2>/dev/null | grep -E '0x5b72b0|0x5b74cc'
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -f -q~0x5b72b0,0x5b74cc
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x5b72b0 method.FlutterError._anon_closure
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
NAME=funcs-mafia-name-pool-window
|
||||
FILE=-
|
||||
FILE=bins/android/mafia/libapp.so
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -f -q -n -l 6 bins/android/mafia 2>/dev/null
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -f -q -n -l 6
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x5b69c0 method.DateTime.compareTo
|
||||
@@ -14,15 +16,19 @@ EOF
|
||||
RUN
|
||||
|
||||
NAME=funcs-mafia-name-pool-stub-counts
|
||||
FILE=-
|
||||
FILE=bins/android/mafia/libapp.so
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -f -q -n bins/android/mafia 2>/dev/null | awk '$1=="0x5b6bf4" || $1=="0x5b6f6c" || $1=="0x5bb750" {print} / stub\.Allocate/ {alloc++} / stub\.typetest_/ {type++} END {print "alloc=" alloc; print "typetest=" type}'
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -f -q -n~0x5b6bf4,0x5b6f6c,0x5bb750
|
||||
r2flutter -f -q -n~?stub.Allocate
|
||||
r2flutter -f -q -n~?stub.typetest_
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x5b6bf4 stub.AllocateDurationStub
|
||||
0x5b6f6c stub.typetest_13
|
||||
0x5bb750 stub.AllocateStringBufferStub
|
||||
alloc=4096
|
||||
typetest=1277
|
||||
4096
|
||||
1277
|
||||
EOF
|
||||
RUN
|
||||
@@ -1,7 +1,9 @@
|
||||
NAME=funcs-mafia-vm-symbol-operators
|
||||
FILE=-
|
||||
FILE=bins/android/mafia/libapp.so
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -f -q bins/android/mafia 2>/dev/null | awk '$1=="0xe11f60" || $1=="0xe1205c" || $1=="0xe12174" || $1=="0xe12220" || $1=="0xe122a8" {print} $1=="0xe12220" || $1=="0xe122a8" {names[$2]=1} END {n=0; for (name in names) {n++} print "unique_greaterthan=" n}'
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -f -q~0xe11f60,0xe1205c,0xe12174,0xe12220,0xe122a8
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0xe11f60 method._Double_0150898.op_add
|
||||
@@ -9,6 +11,5 @@ EXPECT=<<EOF
|
||||
0xe12174 method._Double_0150898.op_gt
|
||||
0xe12220 method._Double_0150898._greaterThan_0150898
|
||||
0xe122a8 method._IntegerImplementation_0150898.op_gt
|
||||
unique_greaterthan=2
|
||||
EOF
|
||||
RUN
|
||||
@@ -1,9 +1,11 @@
|
||||
NAME=json-header-android
|
||||
FILE=-
|
||||
FILE=bins/android/first/libapp.so
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -j -H bins/android/first 2>/dev/null
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -j -H
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
{"kind":3,"hash":"f91b8b03bf7f30a5e983fd19b23d978d","vm_data":512,"vm_instr":327680,"iso_data":36544,"iso_instr":419584,"cluster":{"base":1154,"objs":11342,"clusters":47,"it_len":1311,"it_off":64,"total":91657},"cws":8,"dart_version":"unknown","tag_style":"OBJECT_HEADER","alignment":16,"header_fields":5,"it_capacity":20000,"cid_table":{"cid_class":5,"cid_function":7,"cid_code":18,"cid_string":93,"cid_one_byte_string":94,"cid_two_byte_string":95,"cid_array":90,"cid_mint":61,"cid_object_pool":23,"num_predefined":175}}
|
||||
{"kind":3,"hash":"f91b8b03bf7f30a5e983fd19b23d978d","vm_data":512,"vm_instr":327680,"iso_data":36544,"iso_instr":419584,"cluster":{"base":1154,"objs":11342,"clusters":47,"it_len":1311,"it_off":64,"total":91657},"cws":8,"dart_version":"2.18.2","version_source":"exact-hash","tag_style":"OBJECT_HEADER","alignment":16,"header_fields":5,"it_capacity":20000,"cid_table":{"cid_class":5,"cid_function":7,"cid_code":18,"cid_string":93,"cid_one_byte_string":94,"cid_two_byte_string":95,"cid_array":90,"cid_mint":61,"cid_object_pool":23,"num_predefined":175}}
|
||||
EOF
|
||||
RUN
|
||||
@@ -1,9 +1,11 @@
|
||||
NAME=json-header-authpass
|
||||
FILE=-
|
||||
FILE=bins/ios/AuthPass.app/Frameworks/App.framework/App
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -j -H bins/ios/AuthPass.app
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -j -H
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
{"kind":3,"hash":"f71c76320d35b65f1164dbaa6d95fe09","vm_data":7333504,"vm_instr":23168,"iso_data":7368576,"iso_instr":64960,"cluster":{"base":1129,"objs":277373,"clusters":1044,"it_len":37092,"it_off":64,"total":3159106},"cws":8,"dart_version":"3.2.5","tag_style":"CID_SHIFT1","alignment":16,"header_fields":5,"it_capacity":20000,"cid_table":{"cid_class":5,"cid_function":7,"cid_code":18,"cid_string":92,"cid_one_byte_string":93,"cid_two_byte_string":94,"cid_array":89,"cid_mint":60,"cid_object_pool":22,"num_predefined":176}}
|
||||
{"kind":3,"hash":"f71c76320d35b65f1164dbaa6d95fe09","vm_data":7333504,"vm_instr":23168,"iso_data":7368576,"iso_instr":64960,"cluster":{"base":1129,"objs":277373,"clusters":1044,"it_len":37092,"it_off":64,"total":3159106},"cws":8,"dart_version":"3.2.5","version_source":"exact-hash","tag_style":"CID_SHIFT1","alignment":16,"header_fields":5,"it_capacity":20000,"cid_table":{"cid_class":5,"cid_function":7,"cid_code":18,"cid_string":92,"cid_one_byte_string":93,"cid_two_byte_string":94,"cid_array":89,"cid_mint":60,"cid_object_pool":22,"num_predefined":176}}
|
||||
EOF
|
||||
RUN
|
||||
@@ -1,12 +1,14 @@
|
||||
NAME=header-deep-macos
|
||||
FILE=-
|
||||
FILE=bins/macos/hello/hello.aot
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -q -HHH -l 36 bins/macos/hello/hello.aot 2>/dev/null | grep -Eo '^( detail: object_pool_entries| 34 cid=23 alloc=object_pool fill=none count=1| object_pool_decode: fill_not_parsed)'
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -q -HHH -l 36~object_pool_entries,34 cid=23,object_pool ref=9145
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
detail: object_pool_entries
|
||||
detail: object_pool_entries
|
||||
34 cid=23 alloc=object_pool fill=none count=1
|
||||
object_pool_decode: fill_not_parsed
|
||||
34 cid=23 alloc=object_pool fill=object_pool count=1 refs=9145..9146 flags=- alloc=0x5ee7b..0x5ee7e fill=0x6e81a..0x6f56e fill_status=ok items=count:1,first:1380,min:1380,max:1380,total:1380
|
||||
object_pool ref=9145 index=0 length=1380 stream=0x6e81a
|
||||
EOF
|
||||
RUN
|
||||
@@ -1,16 +1,18 @@
|
||||
NAME=header-extended-ios
|
||||
FILE=-
|
||||
FILE=bins/ios/Runner.app/Frameworks/App.framework/App
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -HH -l 1 bins/ios/Runner.app 2>/dev/null | grep -Eo '^(## (VM|Isolate) Clusters| columns:.*| 0 cid=92 alloc=rodata fill=none count=[0-9]+| \.\.\. [0-9]+ clusters omitted by -l)'
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -HH -l 1~VM Clusters,Isolate Clusters,columns,0 cid=92,clusters omitted
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
## VM Clusters
|
||||
columns: idx cid alloc fill count refs flags alloc_range fill_range extras
|
||||
0 cid=92 alloc=rodata fill=none count=884
|
||||
0 cid=92 alloc=rodata fill=none count=884 refs=97..981 flags=canonical,immutable alloc=0x1b09d9..0x1b10c6 fill=0x1b118b..0x1b118b fill_status=ok items=count:884,first:91,min:1,max:91,total:2018
|
||||
... 5 clusters omitted by -l
|
||||
## Isolate Clusters
|
||||
columns: idx cid alloc fill count refs flags alloc_range fill_range extras
|
||||
0 cid=92 alloc=rodata fill=none count=8589
|
||||
0 cid=92 alloc=rodata fill=none count=8589 refs=1157..9746 flags=canonical,immutable alloc=0x1b96dc..0x1bd9fd fill=0x1c8d24..0x1c8d24 fill_status=ok items=count:8589,first:12413,min:2,max:12413,total:36811
|
||||
... 334 clusters omitted by -l
|
||||
EOF
|
||||
RUN
|
||||
@@ -0,0 +1,13 @@
|
||||
NAME=header-fingerprint-unknown-hash
|
||||
FILE=bins/android/first/libapp.so
|
||||
CMDS=<<EOF
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -j -D ffffffffffffffffffffffffffffffff -H~{=}dart_version,version_source,tag_style
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
json.dart_version = "~3.12.0+ (fingerprint)";
|
||||
json.version_source = "fingerprint";
|
||||
json.tag_style = "OBJECT_HEADER";
|
||||
EOF
|
||||
RUN
|
||||
@@ -1,9 +1,11 @@
|
||||
NAME=json-header-ios
|
||||
FILE=-
|
||||
FILE=bins/ios/Runner.app/Frameworks/App.framework/App
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -j -H bins/ios/Runner.app 2>/dev/null
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -j -H
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
{"kind":3,"hash":"d20a1be77c3d3c41b2a5accaee1ce549","vm_data":1771776,"vm_instr":16384,"iso_data":1807872,"iso_instr":59136,"cluster":{"base":1156,"objs":51434,"clusters":335,"it_len":7670,"it_off":64,"total":536223},"cws":8,"dart_version":"3.4.3","tag_style":"OBJECT_HEADER","alignment":16,"header_fields":5,"it_capacity":20000,"cid_table":{"cid_class":5,"cid_function":7,"cid_code":18,"cid_string":92,"cid_one_byte_string":93,"cid_two_byte_string":94,"cid_array":89,"cid_mint":60,"cid_object_pool":22,"num_predefined":174}}
|
||||
{"kind":3,"hash":"d20a1be77c3d3c41b2a5accaee1ce549","vm_data":1771776,"vm_instr":16384,"iso_data":1807872,"iso_instr":59136,"cluster":{"base":1156,"objs":51434,"clusters":335,"it_len":7670,"it_off":64,"total":536223},"cws":8,"dart_version":"3.4.3","version_source":"exact-hash","tag_style":"OBJECT_HEADER","alignment":16,"header_fields":5,"it_capacity":20000,"cid_table":{"cid_class":5,"cid_function":7,"cid_code":18,"cid_string":92,"cid_one_byte_string":93,"cid_two_byte_string":94,"cid_array":89,"cid_mint":60,"cid_object_pool":22,"num_predefined":174}}
|
||||
EOF
|
||||
RUN
|
||||
@@ -1,9 +1,11 @@
|
||||
NAME=json-header-mafia
|
||||
FILE=-
|
||||
FILE=bins/android/mafia/libapp.so
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -j -H bins/android/mafia 2>/dev/null
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -j -H
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
{"kind":3,"hash":"97ff04a728735e6b6b098bdf983faaba","vm_data":832,"vm_instr":5898240,"iso_data":19072,"iso_instr":5990720,"cluster":{"base":1177,"objs":237774,"clusters":1105,"it_len":37258,"it_off":64,"total":4871502},"cws":4,"dart_version":"3.9.2","tag_style":"OBJECT_HEADER","alignment":64,"header_fields":5,"it_capacity":20000,"cid_table":{"cid_class":5,"cid_function":7,"cid_code":18,"cid_string":93,"cid_one_byte_string":94,"cid_two_byte_string":95,"cid_array":90,"cid_mint":61,"cid_object_pool":23,"num_predefined":175}}
|
||||
{"kind":3,"hash":"97ff04a728735e6b6b098bdf983faaba","vm_data":832,"vm_instr":5898240,"iso_data":19072,"iso_instr":5990720,"cluster":{"base":1177,"objs":237774,"clusters":1105,"it_len":37258,"it_off":64,"total":4871502},"cws":4,"dart_version":"3.9.2","version_source":"exact-hash","tag_style":"OBJECT_HEADER","alignment":64,"header_fields":5,"it_capacity":20000,"cid_table":{"cid_class":5,"cid_function":7,"cid_code":18,"cid_string":93,"cid_one_byte_string":94,"cid_two_byte_string":95,"cid_array":90,"cid_mint":61,"cid_object_pool":23,"num_predefined":175}}
|
||||
EOF
|
||||
RUN
|
||||
@@ -1,7 +1,9 @@
|
||||
NAME=header-markdown
|
||||
FILE=-
|
||||
FILE=bins/android/first/libapp.so
|
||||
CMDS=<<EOF
|
||||
!!../bin/r2flutter -H bins/android/first 2>/dev/null | grep -E '^(#{1,2} |={3,}|-{3,})'
|
||||
L ../src/r2/core_flutter.dylib
|
||||
L ../src/r2/core_flutter.so
|
||||
r2flutter -H~Dart AOT Snapshot Header,Snapshot Addresses,VM Snapshot,Isolate Snapshot,Class IDs
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
# Dart AOT Snapshot Header
|
||||
|
||||
Loaded 100 of 130 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user