├── .gitignore ├── LICENSE ├── README.md ├── blutter.py ├── blutter ├── CMakeLists.txt ├── sourcelist.cmake └── src │ ├── CodeAnalyzer.cpp │ ├── CodeAnalyzer.h │ ├── CodeAnalyzer_arm64.cpp │ ├── DartApp.cpp │ ├── DartApp.h │ ├── DartClass.cpp │ ├── DartClass.h │ ├── DartDumper.cpp │ ├── DartDumper.h │ ├── DartField.cpp │ ├── DartField.h │ ├── DartFnBase.h │ ├── DartFunction.cpp │ ├── DartFunction.h │ ├── DartLibrary.cpp │ ├── DartLibrary.h │ ├── DartLoader.cpp │ ├── DartLoader.h │ ├── DartStub.cpp │ ├── DartStub.h │ ├── DartThreadInfo.cpp │ ├── DartThreadInfo.h │ ├── DartTypes.cpp │ ├── DartTypes.h │ ├── Disassembler.cpp │ ├── Disassembler.h │ ├── Disassembler_arm64.cpp │ ├── Disassembler_arm64.h │ ├── ElfHelper.cpp │ ├── ElfHelper.h │ ├── FridaWriter.cpp │ ├── FridaWriter.h │ ├── HtArrayIterator.h │ ├── Util.cpp │ ├── Util.h │ ├── VarValue.cpp │ ├── VarValue.h │ ├── args.hxx │ ├── base.h │ ├── il.cpp │ ├── il.h │ ├── main.cpp │ ├── pch.cpp │ └── pch.h ├── dartvm_fetch_build.py ├── extract_dart_info.py └── scripts ├── CMakeLists.txt ├── dartvm_create_srclist.py ├── dartvm_make_version.py ├── extract_libflutter_functions.py ├── frida.template.js ├── generate_thread_offsets_cpp.py └── init_env_win.py /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | bin/ 3 | build/ 4 | tmp/ 5 | out/ 6 | dartsdk/ 7 | external/ 8 | packages/ 9 | # python 10 | __pycache__/ 11 | *.py[cod] 12 | *$py.class 13 | # C/C++ 14 | *.so 15 | *.dll 16 | *.lib 17 | venv/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Worawit Wangwarunyoo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # B(l)utter 4 | Flutter Mobile Application Reverse Engineering Tool by Compiling Dart AOT Runtime 5 | 6 | Currently the application supports only Android libapp.so (arm64 only). 7 | Also the application is currently work only against recent Dart versions. 8 | 9 | For high priority missing features, see [TODO](#todo) 10 | 11 | ## Termux 12 | 13 | - Same as debian but needs ndk . if you dont want ndk then remove android library dependencies related files in dartsdk 14 | - I actually liked [fmt](https://github.com/fmtlib/fmt.git) library thats the main reason replaced standard format 15 | - Install `fmt`: `pkg install fmt` 16 | - It should work for both dartsdk stable/beta builds didnt checked for dev builds 17 | - If any error related to capstone first check if is present in include dir 18 | ```pkg-config --cflags capstone``` 19 | 20 | **OR You can copy paste below command to install all requirements:** 21 | ``` 22 | pip install requests pyelftools && pkg install -y git cmake ninja build-essential pkg-config libicu capstone fmt 23 | ``` 24 | 25 | > [!NOTE] 26 | > In case you face errors related to `no member named 'format'`, you need to replace all occurance of __std::format__ with __fmt::format__ using below shell command: 27 | > ```shell 28 | > find -type f -exec sed -i 's/std::format/fmt::format/g' {} + 29 | > ``` 30 | 31 | 32 | https://github.com/dedshit/blutter-termux/assets/62318734/b7376844-96b0-4aa0-a395-9009d009132e 33 | 34 | 35 | ## Environment Setup 36 | This application uses C++20 Formatting library. It requires very recent C++ compiler such as g++>=13, Clang>=16. 37 | 38 | I recommend using Linux OS (only tested on Deiban sid/trixie) because it is easy to setup. 39 | 40 | ### Debian Unstable (gcc 13) 41 | - Install build tools and depenencies 42 | ``` 43 | apt install python3-pyelftools python3-requests git cmake ninja-build \ 44 | build-essential pkg-config libicu-dev libcapstone-dev libfmt-dev 45 | ``` 46 | 47 | ### Windows 48 | - Install git and python 3 49 | - Install latest Visual Studio with "Desktop development with C++" and "C++ CMake tools" 50 | - Install required libraries (libcapstone, libicu4c and fmt) 51 | ``` 52 | python scripts\init_env_win.py 53 | ``` 54 | - Start "x64 Native Tools Command Prompt" 55 | 56 | ### macOS Ventura and Sonoma (clang 16) 57 | - Install XCode 58 | - Install clang 16 and required tools 59 | ``` 60 | brew install llvm@16 cmake ninja pkg-config icu4c capstone fmt 61 | pip3 install pyelftools requests 62 | ``` 63 | 64 | ## Usage 65 | Blutter can analyze Flutter applications in several ways. 66 | 67 | ### APK File 68 | If you have an `.apk` file. Simply provide the path to the APK file and the output directory as arguments: 69 | ```shell 70 | python3 blutter.py path/to/app.apk out_dir 71 | ``` 72 | 73 | ### `.so` File(s) 74 | Blutter can also analyze `.so` files directly. This can be done in two ways: 75 | 76 | 1. **Analyzing `.so` files extracted from an APK:** 77 | 78 | If you have extracted the lib directory from an APK file, you can analyze it using Blutter. Provide the path to the lib directory and the output directory as arguments: 79 | ```shell 80 | python3 blutter.py path/to/app/lib/arm64-v8a out_dir 81 | ``` 82 | > The `blutter.py` will automatically detect the Dart version from the Flutter engine and use the appropriate executable to extract information from `libapp.so`. 83 | 84 | 2. **Analyzing `libapp.so` with a known Dart version:** 85 | 86 | If you only have `libapp.so` and know its Dart version, you can specify it to Blutter. Provide the Dart version with `--dart-version` option, the path to `libapp.so`, and the output directory as arguments: 87 | ```shell 88 | python3 blutter.py --dart-version X.X.X_android_arm64 libapp.so out_dir 89 | ``` 90 | > Replace `X.X.X` with your lib dart version such as "3.4.2_android_arm64". 91 | 92 | 93 | If the Blutter executable for the required Dart version does not exist, the script will automatically checkout the Dart source code and compile it. 94 | 95 | ## Update 96 | You can use ```git pull``` to update and run blutter.py with ```--rebuild``` option to force rebuild the executable 97 | ``` 98 | python3 blutter.py path/to/app/lib/arm64-v8a out_dir --rebuild 99 | ``` 100 | 101 | ## Output files 102 | - **asm/\*** libapp assemblies with symbols 103 | - **blutter_frida.js** the frida script template for the target application 104 | - **objs.txt** complete (nested) dump of Object from Object Pool 105 | - **pp.txt** all Dart objects in Object Pool 106 | 107 | 108 | ## Directories 109 | - **bin** contains blutter executables for each Dart version in "blutter_dartvm\\_\\_\" format 110 | - **blutter** contains source code. need building against Dart VM library 111 | - **build** contains building projects which can be deleted after finishing the build process 112 | - **dartsdk** contains checkout of Dart Runtime which can be deleted after finishing the build process 113 | - **external** contains 3rd party libraries for Windows only 114 | - **packages** contains the static libraries of Dart Runtime 115 | - **scripts** contains python scripts for getting/building Dart 116 | 117 | 118 | ## Generating Visual Studio Solution for Development 119 | I use Visual Studio to delevlop Blutter on Windows. ```--vs-sln``` options can be used to generate a Visual Studio solution. 120 | ``` 121 | python blutter.py path\to\lib\arm64-v8a build\vs --vs-sln 122 | ``` 123 | 124 | ## TODO 125 | - More code analysis 126 | - Function arguments and return type 127 | - Some psuedo code for code pattern 128 | - Generate better Frida script 129 | - More internal classes 130 | - Object modification 131 | - Obfuscated app (still missing many functions) 132 | - Reading iOS binary 133 | - Input as ipa 134 | -------------------------------------------------------------------------------- /blutter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import argparse 3 | import glob 4 | import mmap 5 | import os 6 | import platform 7 | import shutil 8 | import subprocess 9 | import sys 10 | import zipfile 11 | import tempfile 12 | 13 | from dartvm_fetch_build import DartLibInfo 14 | 15 | CMAKE_CMD = "cmake" 16 | NINJA_CMD = "ninja" 17 | 18 | SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) 19 | BIN_DIR = os.path.join(SCRIPT_DIR, "bin") 20 | PKG_INC_DIR = os.path.join(SCRIPT_DIR, "packages", "include") 21 | PKG_LIB_DIR = os.path.join(SCRIPT_DIR, "packages", "lib") 22 | BUILD_DIR = os.path.join(SCRIPT_DIR, "build") 23 | 24 | 25 | class BlutterInput: 26 | def __init__( 27 | self, 28 | libapp_path: str, 29 | dart_info: DartLibInfo, 30 | outdir: str, 31 | rebuild_blutter: bool, 32 | create_vs_sln: bool, 33 | no_analysis: bool, 34 | ida_fcn: bool, 35 | ): 36 | self.libapp_path = libapp_path 37 | self.dart_info = dart_info 38 | self.outdir = outdir 39 | self.rebuild_blutter = rebuild_blutter 40 | self.create_vs_sln = create_vs_sln 41 | self.ida_fcn = ida_fcn 42 | 43 | vers = dart_info.version.split(".", 2) 44 | if int(vers[0]) == 2 and int(vers[1]) < 15: 45 | if not no_analysis: 46 | print('Dart version <2.15, force "no-analysis" option') 47 | no_analysis = True 48 | self.no_analysis = no_analysis 49 | 50 | # Note: null-safety is detected in blutter application, so no need another build of blutter for null-safety 51 | self.name_suffix = "" 52 | if not dart_info.has_compressed_ptrs: 53 | self.name_suffix += "_no-compressed-ptrs" 54 | if no_analysis: 55 | self.name_suffix += "_no-analysis" 56 | if ida_fcn: 57 | self.name_suffix += "_ida-fcn" 58 | # derive blutter executable filename 59 | self.blutter_name = f"blutter_{dart_info.lib_name}{self.name_suffix}" 60 | self.blutter_file = os.path.join(BIN_DIR, self.blutter_name) + ( 61 | ".exe" if os.name == "nt" else "" 62 | ) 63 | 64 | 65 | def find_lib_files(indir: str): 66 | app_file = os.path.join(indir, "libapp.so") 67 | if not os.path.isfile(app_file): 68 | app_file = os.path.join(indir, "App") 69 | if not os.path.isfile(app_file): 70 | sys.exit("Cannot find libapp file") 71 | 72 | flutter_file = os.path.join(indir, "libflutter.so") 73 | if not os.path.isfile(flutter_file): 74 | flutter_file = os.path.join(indir, "Flutter") 75 | if not os.path.isfile(flutter_file): 76 | sys.exit("Cannot find libflutter file") 77 | 78 | return os.path.abspath(app_file), os.path.abspath(flutter_file) 79 | 80 | 81 | def extract_libs_from_apk(apk_file: str, out_dir: str): 82 | with zipfile.ZipFile(apk_file, "r") as zf: 83 | try: 84 | app_info = zf.getinfo("lib/arm64-v8a/libapp.so") 85 | flutter_info = zf.getinfo("lib/arm64-v8a/libflutter.so") 86 | except: 87 | sys.exit("Cannot find libapp.so or libflutter.so in the APK") 88 | 89 | zf.extract(app_info, out_dir) 90 | zf.extract(flutter_info, out_dir) 91 | 92 | app_file = os.path.join(out_dir, app_info.filename) 93 | flutter_file = os.path.join(out_dir, flutter_info.filename) 94 | return app_file, flutter_file 95 | 96 | 97 | def find_compat_macro(dart_version: str, no_analysis: bool, ida_fcn: bool): 98 | macros = [] 99 | include_path = os.path.join(PKG_INC_DIR, f"dartvm{dart_version}") 100 | vm_path = os.path.join(include_path, "vm") 101 | with open(os.path.join(vm_path, "class_id.h"), "rb") as f: 102 | mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) 103 | # Rename the default implementation classes of Map and Set https://github.com/dart-lang/sdk/commit/a2de36e708b8a8e15d3bd49eef2cede57e649436 104 | if mm.find(b"V(LinkedHashMap)") != -1: 105 | macros.append("-DOLD_MAP_SET_NAME=1") 106 | # Add immutable maps and sets https://github.com/dart-lang/sdk/commit/e8e9e1d15216788d4112e40f4408c52455d11113 107 | if mm.find(b"V(ImmutableLinkedHashMap)") == -1: 108 | macros.append("-DOLD_MAP_NO_IMMUTABLE=1") 109 | if mm.find(b" kLastInternalOnlyCid ") == -1: 110 | macros.append("-DNO_LAST_INTERNAL_ONLY_CID=1") 111 | # Remove TypeRef https://github.com/dart-lang/sdk/commit/2ee6fcf5148c34906c04c2ac518077c23891cd1b 112 | # in this commit also added RecordType as sub class of AbstractType 113 | # so assume Dart Records implementation is completed in this commit (before this commit is inconplete RecordType) 114 | if mm.find(b"V(TypeRef)") != -1: 115 | macros.append("-DHAS_TYPE_REF=1") 116 | # in main branch, RecordType is added in Dart 3.0 while TypeRef is removed in Dart 3.1 117 | # in Dart 2.19, RecordType might be added to a source code but incomplete 118 | if dart_version.startswith("3.") and mm.find(b"V(RecordType)") != -1: 119 | macros.append("-DHAS_RECORD_TYPE=1") 120 | 121 | with open(os.path.join(vm_path, "class_table.h"), "rb") as f: 122 | mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) 123 | # Clean up ClassTable (Merge ClassTable and SharedClassTable back together) 124 | # https://github.com/dart-lang/sdk/commit/4a4eedd860a8af2b1cb27e68d9feae5550d0f511 125 | # the commit moved GetUnboxedFieldsMapAt() from SharedClassTable to ClassTable 126 | if mm.find(b"class SharedClassTable {") != -1: 127 | macros.append("-DHAS_SHARED_CLASS_TABLE=1") 128 | 129 | with open(os.path.join(vm_path, "stub_code_list.h"), "rb") as f: 130 | mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) 131 | # Add InitLateStaticField and InitLateFinalStaticField stub 132 | # https://github.com/dart-lang/sdk/commit/37d45743e11970f0eacc0ec864e97891347185f5 133 | if mm.find(b"V(InitLateStaticField)") == -1: 134 | macros.append("-DNO_INIT_LATE_STATIC_FIELD=1") 135 | 136 | with open(os.path.join(vm_path, "object_store.h"), "rb") as f: 137 | mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) 138 | # [vm] Simplify and optimize method extractors 139 | # https://github.com/dart-lang/sdk/commit/b9b341f4a71b3ac8c9810eb24e318287798457ae#diff-545efb05c0f9e7191a855bca5e463f8f7f68079f74056f0040196c666b3bb8f0 140 | if mm.find(b"build_generic_method_extractor_code)") == -1: 141 | macros.append("-DNO_METHOD_EXTRACTOR_STUB=1") 142 | 143 | with open(os.path.join(vm_path, 'object.h'), 'rb') as f: 144 | mm = mmap.mmap(f.fileno(), 0, access = mmap.ACCESS_READ) 145 | # [vm] Refactor access to Integer value 146 | # https://github.com/dart-lang/sdk/commit/84fd647969f0d74ab63f0994d95b5fc26cac006a 147 | if mm.find(b'AsTruncatedInt64Value()') == -1: 148 | macros.append('-DUNIFORM_INTEGER_ACCESS=1') 149 | 150 | if no_analysis: 151 | macros.append("-DNO_CODE_ANALYSIS=1") 152 | 153 | if ida_fcn: 154 | macros.append("-DIDA_FCN=1") 155 | 156 | d_v = float('.'.join(dart_version.split('.')[:2])) 157 | if d_v >= float(3.5) : 158 | with open(os.path.join(vm_path, "compiler", "runtime_api.h"), "rb") as f: 159 | mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) 160 | if not mm.find(b" old_marking_stack_block_offset") == -1: 161 | # [vm] marking_stack_block_offset() changes since Dart Stable 3.5.0 162 | # https://github.com/worawit/blutter/issues/96#issue-2470674670 163 | macros.append("-DOLD_MARKING_STACK_BLOCK=1") 164 | 165 | return macros 166 | 167 | 168 | def cmake_blutter(input: BlutterInput): 169 | blutter_dir = os.path.join(SCRIPT_DIR, "blutter") 170 | builddir = os.path.join(BUILD_DIR, input.blutter_name) 171 | 172 | macros = find_compat_macro(input.dart_info.version, input.no_analysis, input.ida_fcn) 173 | 174 | my_env = None 175 | if platform.system() == "Darwin": 176 | llvm_path = ( 177 | subprocess.run( 178 | ["brew", "--prefix", "llvm@16"], capture_output=True, check=True 179 | ) 180 | .stdout.decode() 181 | .strip() 182 | ) 183 | clang_file = os.path.join(llvm_path, "bin", "clang") 184 | my_env = {**os.environ, "CC": clang_file, "CXX": clang_file + "++"} 185 | # cmake -GNinja -Bbuild -DCMAKE_BUILD_TYPE=Release 186 | subprocess.run( 187 | [ 188 | CMAKE_CMD, 189 | "-GNinja", 190 | "-B", 191 | builddir, 192 | f"-DDARTLIB={input.dart_info.lib_name}", 193 | f"-DNAME_SUFFIX={input.name_suffix}", 194 | "-DCMAKE_BUILD_TYPE=Release", 195 | "--log-level=NOTICE", 196 | ] 197 | + macros, 198 | cwd=blutter_dir, 199 | check=True, 200 | env=my_env, 201 | ) 202 | 203 | # build and install blutter 204 | subprocess.run([NINJA_CMD], cwd=builddir, check=True) 205 | subprocess.run([CMAKE_CMD, "--install", "."], cwd=builddir, check=True) 206 | 207 | 208 | def get_dart_lib_info(libapp_path: str, libflutter_path: str) -> DartLibInfo: 209 | # getting dart version 210 | from extract_dart_info import extract_dart_info 211 | 212 | dart_version, snapshot_hash, flags, arch, os_name = extract_dart_info( 213 | libapp_path, libflutter_path 214 | ) 215 | print( 216 | f"Dart version: {dart_version}, Snapshot: {snapshot_hash}, Target: {os_name} {arch}" 217 | ) 218 | print("flags: " + " ".join(flags)) 219 | 220 | has_compressed_ptrs = "compressed-pointers" in flags 221 | return DartLibInfo(dart_version, os_name, arch, has_compressed_ptrs, snapshot_hash) 222 | 223 | 224 | def build_and_run(input: BlutterInput): 225 | if not os.path.isfile(input.blutter_file) or input.rebuild_blutter: 226 | # before fetch and build, check the existence of compiled library first 227 | # so the src and build directories can be deleted 228 | if os.name == "nt": 229 | dartlib_file = os.path.join(PKG_LIB_DIR, input.dart_info.lib_name + ".lib") 230 | else: 231 | dartlib_file = os.path.join( 232 | PKG_LIB_DIR, "lib" + input.dart_info.lib_name + ".a" 233 | ) 234 | if not os.path.isfile(dartlib_file): 235 | from dartvm_fetch_build import fetch_and_build 236 | 237 | fetch_and_build(input.dart_info) 238 | 239 | input.rebuild_blutter = True 240 | 241 | # creating Visual Studio solution overrides building 242 | if input.create_vs_sln: 243 | macros = find_compat_macro(input.dart_info.version, input.no_analysis, input.ida_fcn) 244 | blutter_dir = os.path.join(SCRIPT_DIR, "blutter") 245 | dbg_output_path = os.path.abspath(os.path.join(input.outdir, "out")) 246 | dbg_cmd_args = f"-i {input.libapp_path} -o {dbg_output_path}" 247 | subprocess.run( 248 | [ 249 | CMAKE_CMD, 250 | "-G", 251 | "Visual Studio 17 2022", 252 | "-A", 253 | "x64", 254 | "-B", 255 | input.outdir, 256 | f"-DDARTLIB={input.dart_info.lib_name}", 257 | f"-DNAME_SUFFIX={input.name_suffix}", 258 | f"-DDBG_CMD:STRING={dbg_cmd_args}", 259 | ] 260 | + macros 261 | + [blutter_dir], 262 | check=True, 263 | ) 264 | dbg_exe_dir = os.path.join(input.outdir, 'Debug') 265 | os.makedirs(dbg_exe_dir, exist_ok=True) 266 | for filename in glob.glob(os.path.join(BIN_DIR, "*.dll")): 267 | shutil.copy(filename, dbg_exe_dir) 268 | else: 269 | if input.rebuild_blutter: 270 | # do not use SDK path for checking source code because Blutter does not depended on it and SDK might be removed 271 | cmake_blutter(input) 272 | assert os.path.isfile(input.blutter_file), ( 273 | "Build complete but cannot find Blutter binary: " + input.blutter_file 274 | ) 275 | 276 | # execute blutter 277 | subprocess.run( 278 | [input.blutter_file, "-i", input.libapp_path, "-o", input.outdir], 279 | check=True, 280 | ) 281 | 282 | 283 | def main_no_flutter( 284 | libapp_path: str, 285 | dart_version: str, 286 | outdir: str, 287 | rebuild_blutter: bool, 288 | create_vs_sln: bool, 289 | no_analysis: bool, 290 | ida_fcn: bool, 291 | ): 292 | version, os_name, arch = dart_version.split("_") 293 | dart_info = DartLibInfo(version, os_name, arch) 294 | input = BlutterInput( 295 | libapp_path, dart_info, outdir, rebuild_blutter, create_vs_sln, no_analysis, ida_fcn 296 | ) 297 | build_and_run(input) 298 | 299 | 300 | def main2( 301 | libapp_path: str, 302 | libflutter_path: str, 303 | outdir: str, 304 | rebuild_blutter: bool, 305 | create_vs_sln: bool, 306 | no_analysis: bool, 307 | ida_fcn: bool, 308 | ): 309 | dart_info = get_dart_lib_info(libapp_path, libflutter_path) 310 | input = BlutterInput( 311 | libapp_path, dart_info, outdir, rebuild_blutter, create_vs_sln, no_analysis, ida_fcn 312 | ) 313 | build_and_run(input) 314 | 315 | 316 | def main( 317 | indir: str, 318 | outdir: str, 319 | rebuild_blutter: bool, 320 | create_vs_sln: bool, 321 | no_analysis: bool, 322 | ida_fcn: bool, 323 | ): 324 | if indir.endswith(".apk"): 325 | with tempfile.TemporaryDirectory() as tmp_dir: 326 | libapp_file, libflutter_file = extract_libs_from_apk(indir, tmp_dir) 327 | main2( 328 | libapp_file, 329 | libflutter_file, 330 | outdir, 331 | rebuild_blutter, 332 | create_vs_sln, 333 | no_analysis, 334 | ida_fcn, 335 | ) 336 | else: 337 | libapp_file, libflutter_file = find_lib_files(indir) 338 | 339 | main2( 340 | libapp_file, 341 | libflutter_file, 342 | outdir, 343 | rebuild_blutter, 344 | create_vs_sln, 345 | no_analysis, 346 | ida_fcn, 347 | ) 348 | 349 | 350 | def run_command(command): 351 | """Just a simple function to run commands in subprocess LOL""" 352 | process = subprocess.Popen( 353 | command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True 354 | ) 355 | output, error = process.communicate() 356 | if error: 357 | return error.decode("utf-8") 358 | else: 359 | return output.decode("utf-8") 360 | 361 | 362 | def check_for_updates_and_pull(): 363 | # Fetch the latest data from the remote repository 364 | run_command("git fetch") 365 | 366 | # Check if the local branch is behind the remote one 367 | try_pull = run_command("git pull") 368 | 369 | if try_pull.__contains__("Already up to date."): 370 | print(try_pull) 371 | else: 372 | # Reset the local branch to the state of the remote one 373 | run_command("git reset --hard HEAD") 374 | 375 | # Pull the changes from the remote repository 376 | run_command("git pull") 377 | 378 | 379 | if __name__ == "__main__": 380 | parser = argparse.ArgumentParser( 381 | prog="B(l)utter", description="Reversing a flutter application tool" 382 | ) 383 | # TODO: accept ipa 384 | parser.add_argument( 385 | "indir", 386 | help="An apk or a directory that contains both libapp.so and libflutter.so", 387 | ) 388 | parser.add_argument("outdir", help="An output directory") 389 | parser.add_argument( 390 | "--rebuild", 391 | action="store_true", 392 | default=False, 393 | help="Force rebuild the Blutter executable", 394 | ) 395 | parser.add_argument( 396 | "--vs-sln", 397 | action="store_true", 398 | default=False, 399 | help="Generate Visual Studio solution at ", 400 | ) 401 | parser.add_argument( 402 | "--no-analysis", 403 | action="store_true", 404 | default=False, 405 | help="Do not build with code analysis", 406 | ) 407 | # rare usage scenario 408 | parser.add_argument( 409 | "--dart-version", 410 | help='Run without libflutter (indir become libapp.so) by specify dart version such as "3.4.2_android_arm64"', 411 | ) 412 | parser.add_argument( 413 | "--nu", 414 | action="store_false", 415 | default=True, 416 | help="Don't check for updates", 417 | ) 418 | parser.add_argument( 419 | "--ida-fcn", 420 | action="store_true", 421 | default=False, 422 | help="Generate IDA function names script, Doesn't Generates Thread and Object Pool structs comments", 423 | ) 424 | args = parser.parse_args() 425 | 426 | if args.nu: 427 | check_for_updates_and_pull() 428 | 429 | if args.dart_version is None: 430 | main(args.indir, args.outdir, args.rebuild, args.vs_sln, args.no_analysis, args.ida_fcn) 431 | else: 432 | main_no_flutter( 433 | args.indir, 434 | args.dart_version, 435 | args.outdir, 436 | args.rebuild, 437 | args.vs_sln, 438 | args.no_analysis, 439 | args.ida_fcn, 440 | ) 441 | -------------------------------------------------------------------------------- /blutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.20) 2 | 3 | project(blutter) 4 | if (NOT DEFINED DARTLIB) 5 | message(FATAL_ERROR "Please define DARTLIB to build the Blutter project") 6 | endif() 7 | if (NOT ${DARTLIB} MATCHES "^dartvm") 8 | message(FATAL_ERROR "DARTLIB name should start with 'dartvm'") 9 | endif() 10 | 11 | set(BINNAME "${PROJECT_NAME}_${DARTLIB}${NAME_SUFFIX}") 12 | 13 | set(CMAKE_INSTALL_PREFIX "${PROJECT_SOURCE_DIR}/../bin" CACHE PATH "" FORCE) 14 | 15 | set(CMAKE_CXX_STANDARD 20) 16 | set(CMAKE_CXX_STANDARD_REQUIRED True) 17 | set(CMAKE_CXX_EXTENSIONS OFF) 18 | if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 19 | cmake_path(GET CMAKE_CXX_COMPILER PARENT_PATH LLVM_BIN_DIR) 20 | endif() 21 | 22 | find_package(${DARTLIB} REQUIRED PATHS "${PROJECT_SOURCE_DIR}/../packages") 23 | if (MSVC) 24 | add_library(capstone SHARED IMPORTED) 25 | set_target_properties(capstone PROPERTIES IMPORTED_LOCATION "${PROJECT_SOURCE_DIR}/../external/capstone/capstone.dll") 26 | set_target_properties(capstone PROPERTIES IMPORTED_IMPLIB "${PROJECT_SOURCE_DIR}/../external/capstone/capstone_dll.lib") 27 | target_include_directories(capstone INTERFACE "${PROJECT_SOURCE_DIR}/../external/capstone/include/capstone") 28 | else() 29 | include(FindPkgConfig) 30 | pkg_search_module(CAPSTONE REQUIRED capstone) 31 | link_directories(${CAPSTONE_LIBDIR}) 32 | include_directories(AFTER ${CAPSTONE_INCLUDEDIR}) 33 | endif() 34 | 35 | # Add source to this project's executable. 36 | set(SRCDIR "src") 37 | include(sourcelist.cmake) 38 | list(TRANSFORM SRCS PREPEND "${SRCDIR}/") 39 | add_executable(${BINNAME} ${SRCS}) 40 | 41 | target_link_libraries(${BINNAME} PRIVATE ${DARTLIB} capstone) 42 | 43 | if (ANDROID) 44 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -llog") 45 | endif() 46 | 47 | target_precompile_headers(${BINNAME} PRIVATE "${SRCDIR}/pch.h") 48 | 49 | if (MSVC) 50 | # for dynamic function (exception) table 51 | target_link_libraries(${BINNAME} PRIVATE ntdll) 52 | # remove compiler exception handler options 53 | #string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) 54 | # remove compiler RTTI option 55 | string(REPLACE "/GR" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) 56 | #set(cc_opts /Oy /GR- /EHs-c-) 57 | if (CMAKE_GENERATOR MATCHES "Visual Studio") 58 | # use SEMIDBG for enabling ASSERT macro while linking against release build of Dart VM 59 | set(cc_opts /GR- /MD /D SEMIDBG) 60 | set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${BINNAME}) 61 | set_target_properties(${BINNAME} PROPERTIES VS_DEBUGGER_COMMAND_ARGUMENTS "${DBG_CMD}") 62 | else() 63 | # assume Ninja 64 | set(cc_opts /Oy /GR- /sdl- /Oi /GL /Gy /Zc:wchar_t /Zc:inline) 65 | target_link_options(${BINNAME} PRIVATE /LTCG /OPT:REF /OPT:ICF) 66 | endif() 67 | else() 68 | # TODO: gcc options to remove dead code in static dartvm library 69 | set(cc_opts 70 | -O3 -fno-rtti 71 | -fvisibility=hidden -fvisibility-inlines-hidden -fno-omit-frame-pointer 72 | #-Wl,--gc-sections 73 | #-fno-exceptions -Wall 74 | ) 75 | endif() 76 | 77 | get_filename_component(FRIDA_TEMPLATE_DIR "${PROJECT_SOURCE_DIR}/../scripts/" ABSOLUTE) 78 | set(defines NDEBUG FRIDA_TEMPLATE_DIR="${FRIDA_TEMPLATE_DIR}") 79 | if (OLD_MAP_SET_NAME) 80 | set(defines ${defines} OLD_MAP_SET_NAME) 81 | if (OLD_MAP_NO_IMMUTABLE) 82 | set(defines ${defines} OLD_MAP_NO_IMMUTABLE) 83 | endif() 84 | endif() 85 | if (NO_LAST_INTERNAL_ONLY_CID) 86 | set(defines ${defines} NO_LAST_INTERNAL_ONLY_CID) 87 | endif() 88 | if (HAS_SHARED_CLASS_TABLE) 89 | set(defines ${defines} HAS_SHARED_CLASS_TABLE) 90 | endif() 91 | if (HAS_TYPE_REF) 92 | set(defines ${defines} HAS_TYPE_REF) 93 | endif() 94 | if (HAS_RECORD_TYPE) 95 | set(defines ${defines} HAS_RECORD_TYPE) 96 | endif() 97 | if (NO_INIT_LATE_STATIC_FIELD) 98 | set(defines ${defines} NO_INIT_LATE_STATIC_FIELD) 99 | endif() 100 | if (NO_CODE_ANALYSIS) 101 | set(defines ${defines} NO_CODE_ANALYSIS) 102 | endif() 103 | if (NO_METHOD_EXTRACTOR_STUB) 104 | set(defines ${defines} NO_METHOD_EXTRACTOR_STUB) 105 | endif() 106 | if (UNIFORM_INTEGER_ACCESS) 107 | set(defines ${defines} UNIFORM_INTEGER_ACCESS) 108 | endif() 109 | if (OLD_MARKING_STACK_BLOCK) 110 | set(defines ${defines} OLD_MARKING_STACK_BLOCK) 111 | endif() 112 | if (IDA_FCN) 113 | set(defines ${defines} IDA_FCN) 114 | endif() 115 | target_compile_definitions(${BINNAME} PRIVATE ${defines}) 116 | 117 | target_compile_options(${BINNAME} PRIVATE ${cc_opts}) 118 | 119 | cmake_path(SET DEST_DIR NORMALIZE "${PROJECT_SOURCE_DIR}/../bin") 120 | 121 | install (TARGETS ${BINNAME} RUNTIME DESTINATION ${DEST_DIR}) 122 | -------------------------------------------------------------------------------- /blutter/sourcelist.cmake: -------------------------------------------------------------------------------- 1 | set(SRCS 2 | CodeAnalyzer.cpp 3 | CodeAnalyzer.h 4 | CodeAnalyzer_arm64.cpp 5 | DartApp.cpp 6 | DartApp.h 7 | DartClass.cpp 8 | DartClass.h 9 | DartDumper.cpp 10 | DartDumper.h 11 | DartField.cpp 12 | DartField.h 13 | DartFnBase.h 14 | DartFunction.cpp 15 | DartFunction.h 16 | DartLibrary.cpp 17 | DartLibrary.h 18 | DartLoader.cpp 19 | DartLoader.h 20 | DartStub.cpp 21 | DartStub.h 22 | DartThreadInfo.cpp 23 | DartThreadInfo.h 24 | DartTypes.cpp 25 | DartTypes.h 26 | Disassembler.cpp 27 | Disassembler.h 28 | Disassembler_arm64.cpp 29 | Disassembler_arm64.h 30 | ElfHelper.cpp 31 | ElfHelper.h 32 | FridaWriter.cpp 33 | FridaWriter.h 34 | HtArrayIterator.h 35 | Util.cpp 36 | Util.h 37 | VarValue.cpp 38 | VarValue.h 39 | args.hxx 40 | il.cpp 41 | il.h 42 | main.cpp 43 | #pch.cpp 44 | pch.h 45 | ) 46 | -------------------------------------------------------------------------------- /blutter/src/CodeAnalyzer.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "CodeAnalyzer.h" 3 | #include "DartApp.h" 4 | 5 | #ifndef NO_CODE_ANALYSIS 6 | 7 | AnalyzedFnData::AnalyzedFnData(DartApp& app, DartFunction& dartFn, AsmTexts asmTexts) 8 | : app(app), dartFn(dartFn), asmTexts(std::move(asmTexts)) 9 | { 10 | } 11 | 12 | void CodeAnalyzer::AnalyzeAll() 13 | { 14 | Disassembler disasmer; 15 | 16 | for (auto lib : app.libs) { 17 | if (lib->isInternal) 18 | continue; 19 | for (auto cls : lib->classes) { 20 | for (auto dartFn : cls->Functions()) { 21 | if (dartFn->Size() == 0) 22 | continue; 23 | 24 | // start from PayloadAddress or Address? 25 | // the assemblies will be deleted after finish analysis because assembly with details consume too much memory 26 | auto asm_insns = disasmer.Disasm((uint8_t*)dartFn->MemAddress(), dartFn->Size(), dartFn->Address()); 27 | 28 | dartFn->SetAnalyzedData(std::make_unique(app, *dartFn, convertAsm(asm_insns))); 29 | 30 | asm2il(dartFn, asm_insns); 31 | } 32 | } 33 | } 34 | } 35 | 36 | #endif // NO_CODE_ANALYSIS 37 | 38 | std::string FnParamInfo::ToString() const 39 | { 40 | std::string txt = type ? type->ToString() : "dynamic"; 41 | txt += ' '; 42 | txt += name.empty() ? "_" : name; 43 | if (val) { 44 | txt += " = "; 45 | txt += val->ToString(); 46 | } 47 | if (valReg.IsSet() || localOffset) { 48 | txt += " /* "; 49 | if (paramReg.IsSet()) { 50 | txt += paramReg.Name(); 51 | txt += " => "; 52 | } 53 | if (valReg.IsSet()) { 54 | txt += valReg.Name(); 55 | if (localOffset) 56 | txt += ", "; 57 | } 58 | if (localOffset) 59 | txt += fmt::format("fp-{:#x}", -localOffset); 60 | txt += " */"; 61 | } 62 | return txt; 63 | } 64 | 65 | FnParamInfo* FnParams::findValReg(A64::Register reg) 66 | { 67 | for (auto& param : params) { 68 | if (param.valReg == reg) { 69 | return ¶m; 70 | } 71 | } 72 | return nullptr; 73 | } 74 | 75 | bool FnParams::movValReg(A64::Register dstReg, A64::Register srcReg) 76 | { 77 | for (auto& param : params) { 78 | if (param.valReg == srcReg) { 79 | param.valReg = dstReg; 80 | return true; 81 | } 82 | } 83 | return false; 84 | } 85 | 86 | std::string FnParams::ToString() const 87 | { 88 | std::string txt; 89 | for (int i = 0; i < params.size(); i++) { 90 | if (i != 0) 91 | txt += ", "; 92 | if (i == numFixedParam) 93 | txt += isNamedParam ? '{' : '['; 94 | 95 | txt += params[i].ToString(); 96 | } 97 | 98 | if (numFixedParam < params.size()) 99 | txt += isNamedParam ? '}' : ']'; 100 | 101 | return txt; 102 | } 103 | -------------------------------------------------------------------------------- /blutter/src/CodeAnalyzer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Disassembler.h" 3 | #include "il.h" 4 | #include 5 | 6 | // forward declaration 7 | class DartApp; 8 | class DartFunction; 9 | 10 | struct AsmText { 11 | enum DataType : uint8_t { 12 | None, 13 | ThreadOffset, 14 | PoolOffset, 15 | Boolean, 16 | Call, 17 | }; 18 | 19 | uint64_t addr; 20 | char text[71]; // first 16 bytes are for mnemonic and spaces, after that is operands 21 | uint8_t dataType; 22 | union { 23 | uint64_t threadOffset; 24 | uint64_t poolOffset; 25 | bool boolVal; 26 | uint64_t callAddress; 27 | }; 28 | }; 29 | 30 | class AsmTexts { 31 | public: 32 | AsmTexts(std::vector asm_texts, uint64_t first_stack_limit_addr, int max_param_stack_offset) 33 | : first_addr{ asm_texts.front().addr }, last_addr{ asm_texts.back().addr }, first_stack_limit_addr{ first_stack_limit_addr }, 34 | max_param_stack_offset{ max_param_stack_offset }, asm_texts{ std::move(asm_texts) } {} 35 | 36 | std::vector& Data() { return asm_texts; } 37 | 38 | size_t AtIndex(uint64_t addr) { 39 | ASSERT(addr >= first_addr && addr <= last_addr); 40 | // TODO: below is specific to arm64 41 | // estimate index (normally 4 bytes per instruction for arm64) 42 | auto idx = (addr - first_addr) / 4; 43 | while (asm_texts[idx].addr < addr) 44 | ++idx; 45 | return idx; 46 | } 47 | 48 | AsmText& AtAddr(uint64_t addr) { return asm_texts[AtIndex(addr)]; } 49 | 50 | uint64_t FirstStackLimitAddress() const { return first_stack_limit_addr; } 51 | 52 | int MaxParamStackOffset() const { return max_param_stack_offset; } 53 | 54 | private: 55 | uint64_t first_addr; 56 | uint64_t last_addr; 57 | uint64_t first_stack_limit_addr; 58 | int max_param_stack_offset; 59 | std::vector asm_texts; 60 | }; 61 | 62 | struct FnParamInfo { 63 | A64::Register paramReg; // when parameter is passed with register 64 | int32_t paramOffset{ 0 }; // offset from FP (first param offset is 0x10. if it is optional param, value is 0) 65 | A64::Register valReg; 66 | int32_t localOffset{ 0 }; // offset from FP (local variable) 67 | DartType* type{ nullptr }; 68 | std::string name; 69 | std::unique_ptr val; 70 | 71 | explicit FnParamInfo() {} 72 | explicit FnParamInfo(A64::Register paramReg, A64::Register valReg, int32_t localOffset) : paramReg(paramReg), valReg(valReg), localOffset(localOffset) {} 73 | explicit FnParamInfo(A64::Register valReg) : valReg(valReg) {} 74 | explicit FnParamInfo(A64::Register valReg, int32_t localOffset) : valReg(valReg), localOffset(localOffset) {} 75 | explicit FnParamInfo(A64::Register valReg, std::unique_ptr val) : valReg(valReg), val(std::move(val)) {} 76 | explicit FnParamInfo(A64::Register valReg, int32_t localOffset, DartType* type, std::string name, std::unique_ptr val) 77 | : valReg(valReg), localOffset(localOffset), type(type), name(std::move(name)), val(std::move(val)) {} 78 | explicit FnParamInfo(A64::Register valReg, std::string name) : valReg(valReg), name(std::move(name)) {} 79 | explicit FnParamInfo(std::string name) : name(std::move(name)) {} 80 | 81 | std::string ToString() const; 82 | }; 83 | 84 | struct FnParams { 85 | int NumParam() const { return params.size(); } 86 | int NumOptionalParam() const { return params.size() - numFixedParam; } 87 | bool empty() const { return params.empty(); } 88 | void add(FnParamInfo&& param) { params.push_back(std::move(param)); } 89 | void addFixedParam(FnParamInfo&& param) { params.push_back(std::move(param)); numFixedParam++; } 90 | FnParamInfo& back() { return params.back(); } 91 | FnParamInfo& operator[](int i) { return params[i]; } 92 | 93 | FnParamInfo* findValReg(A64::Register reg); 94 | bool movValReg(A64::Register dstReg, A64::Register srcReg); 95 | std::string ToString() const; 96 | 97 | uint8_t numFixedParam{ 0 }; 98 | bool isNamedParam{ false }; 99 | std::vector params; 100 | }; 101 | 102 | class AnalyzingState { 103 | public: 104 | AnalyzingState(uint32_t stackSize) : local_vars{ stackSize / sizeof(void*), nullptr } { regs.fill(nullptr); } 105 | 106 | void SetRegister(A64::Register reg, VarValue* val) { regs[reg] = val; } 107 | void ClearRegister(A64::Register reg) { regs[reg] = nullptr; } 108 | VarValue* MoveRegister(A64::Register dstReg, A64::Register srcReg) { 109 | auto val = regs[srcReg]; 110 | if (dstReg != srcReg) { 111 | regs[dstReg] = val; 112 | regs[srcReg] = nullptr; // normally, Dart moves register for freeing the src register 113 | } 114 | return val; 115 | } 116 | VarValue* GetValue(A64::Register reg) { return regs[reg]; } 117 | 118 | static int localOffsetToIndex(int offset) { ASSERT(offset < 0); return (-offset - sizeof(void*)) / sizeof(void*); } 119 | static int indexToLocalOffset(int idx) { return -(idx + 1) * sizeof(void*); } 120 | void SetLocal(int offset, VarValue* val) { local_vars[localOffsetToIndex(offset)] = val; } 121 | VarValue* GetLocal(int offset) { return local_vars[localOffsetToIndex(offset)]; } 122 | 123 | //private: 124 | // variable storage. only reference here. 125 | std::array regs; 126 | std::vector local_vars; 127 | std::vector callee_args; 128 | }; 129 | 130 | class AnalyzingVars { 131 | public: 132 | AnalyzingVars() : valArgsDesc(std::make_unique(VarValue::ArgsDesc)), valCurrNumNameParam(std::make_unique(VarValue::CurrNumNameParam)) {} 133 | 134 | VarValue* ValParam(int idx) { 135 | if (idx >= valParams.size()) { 136 | int i = (int)valParams.size(); 137 | valParams.resize(idx + 1); 138 | for (; i < idx + 1; i++) 139 | valParams[i] = std::make_unique(i); 140 | } 141 | return valParams[idx].get(); 142 | } 143 | VarValue* ValArgsDesc() const { return valArgsDesc.get(); } 144 | 145 | // we need it to suppress an error about use without define. 146 | VarValue* ValCurrNumNameParam() const { return valCurrNumNameParam.get(); } 147 | 148 | // pending load value ILs for variables initialization in prologue 149 | std::vector> pending_ils; 150 | private: 151 | // VarValue of function parameter owner 152 | std::vector> valParams; 153 | std::unique_ptr valArgsDesc; 154 | std::unique_ptr valCurrNumNameParam; 155 | }; 156 | 157 | class AnalyzedFnData { 158 | public: 159 | AnalyzedFnData(DartApp& app, DartFunction& dartFn, AsmTexts asmTexts); 160 | void AddIL(std::unique_ptr insn) { 161 | il_insns.push_back(std::move(insn)); 162 | } 163 | 164 | ILInstr* LastIL() { 165 | return il_insns.back().get(); 166 | } 167 | 168 | void RemoveLastIL() { 169 | il_insns.pop_back(); 170 | } 171 | 172 | DartApp& app; 173 | DartFunction& dartFn; 174 | AsmTexts asmTexts; 175 | cs_insn* last_ret{ nullptr }; 176 | uint32_t stackSize{ 0 }; // for local variables (this includes space for call arguments) 177 | bool useFramePointer{ false }; 178 | uint64_t firstCheckStackOverflowAddr{ 0 }; 179 | FnParams params; 180 | std::vector> il_insns; 181 | DartType* returnType{ nullptr }; 182 | 183 | //int firstParamOffset{ 0 }; 184 | // TODO: initialization list in prologue, type argument (from ArgumentsDescriptor or Closure) 185 | A64::Register closureContextReg; 186 | int32_t closureContextLocalOffset{ 0 }; // offset from FP (local variable) 187 | // type argument from ArgumentsDescriptor 188 | A64::Register typeArgumentReg; 189 | int32_t typeArgumentLocalOffset{ 0 }; 190 | 191 | void InitState() { state = std::make_unique(stackSize); } 192 | void DestroyState() { state.release(); } 193 | AnalyzingState* State() const { return state.get(); } 194 | void InitVars() { vars = std::make_unique(); } 195 | void DestroyVars() { vars.release(); } 196 | AnalyzingVars* Vars() const { return vars.get(); } 197 | 198 | private: 199 | std::unique_ptr vars; 200 | std::unique_ptr state; 201 | 202 | friend class CodeAnalyzer; 203 | }; 204 | 205 | class CodeAnalyzer 206 | { 207 | public: 208 | CodeAnalyzer(DartApp& app) : app(app) {}; 209 | 210 | void AnalyzeAll(); 211 | 212 | private: 213 | static AsmTexts convertAsm(AsmInstructions& asm_insns); 214 | 215 | // implementation is specific to architecture 216 | void asm2il(DartFunction* dartFn, AsmInstructions& asm_insns); 217 | 218 | DartApp& app; 219 | }; 220 | -------------------------------------------------------------------------------- /blutter/src/DartApp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "DartLibrary.h" 3 | #include "DartClass.h" 4 | #include "DartFunction.h" 5 | #include "DartStub.h" 6 | #include 7 | 8 | class DartApp 9 | { 10 | public: 11 | explicit DartApp(const char* path); 12 | DartApp() = delete; 13 | ~DartApp(); 14 | 15 | void EnterScope(); 16 | void ExitScope(); 17 | 18 | void LoadInfo(); 19 | 20 | intptr_t base() const { return (intptr_t)lib_base; } 21 | uint32_t offset(intptr_t addr) const { return (uint32_t)(addr - base()); } 22 | uintptr_t heap_base() const { return heap_base_; } 23 | 24 | DartClass* GetClass(intptr_t cid); 25 | DartFnBase* GetFunction(uint64_t addr); 26 | DartField* GetStaticField(intptr_t offset) { return staticFields.at(offset); } 27 | 28 | dart::ObjectPool& GetObjectPool() { return *ppool; } 29 | DartTypeDb* TypeDb() { return typeDb.get(); } 30 | 31 | intptr_t DartIntCid() const { return dartIntCid; } 32 | intptr_t DartFutureCid() const { return dartFutureCid; } 33 | 34 | private: 35 | DartLibrary* addLibraryClass(const dart::Library& library, const dart::Class& cls); 36 | DartLibrary* addLibrary(const dart::Library& library); 37 | void loadFromClassTable(dart::IsolateGroup* ig); 38 | void loadStubs(dart::ObjectStore* store); 39 | DartFunction* addFunctionNoCheck(const dart::Function& func); 40 | void addFunction(uintptr_t ep_addr, const dart::Function& func); 41 | void findFunctionInHeap(); 42 | void finalizeFunctionsInfo(); 43 | void loadFromObjectPool(); 44 | void walkObject(dart::Object& obj); // to check field types from existed object 45 | 46 | const void* lib_base; 47 | const uint8_t* vm_snapshot_data; 48 | const uint8_t* vm_snapshot_instructions; 49 | const uint8_t* isolate_snapshot_data; 50 | const uint8_t* isolate_snapshot_instructions; 51 | 52 | dart::Isolate* isolate; 53 | uintptr_t heap_base_; 54 | dart::ObjectPool* ppool; 55 | bool inScope; 56 | 57 | // nativeLib contains all classes that has no library 58 | DartLibrary nativeLib; 59 | std::vector libs; 60 | // some class might be null 61 | std::vector classes; 62 | std::vector topClasses; 63 | std::unordered_map functions; 64 | std::unordered_map stubs; 65 | std::unordered_map staticFields; 66 | std::unique_ptr typeDb; 67 | 68 | // the dart Bulit-in type class id 69 | intptr_t dartIntCid; 70 | intptr_t dartDoubleCid; 71 | intptr_t dartStringCid; 72 | intptr_t dartBoolCid; 73 | intptr_t dartRecordCid; // dart verion >= 3.0 74 | intptr_t dartListCid; 75 | intptr_t dartSetCid; 76 | intptr_t dartMapCid; 77 | intptr_t dartRunesCid; 78 | intptr_t dartFutureCid; 79 | 80 | intptr_t throwStubAddr; 81 | 82 | friend class CodeAnalyzer; 83 | friend class DartAnalyzer; 84 | friend class DartDumper; 85 | friend class FridaWriter; 86 | }; 87 | 88 | -------------------------------------------------------------------------------- /blutter/src/DartClass.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "DartClass.h" 3 | #include "DartLibrary.h" 4 | #include "DartFunction.h" 5 | #include "HtArrayIterator.h" 6 | #include 7 | 8 | DartClass::DartClass(const DartLibrary& lib_, const dart::Class& cls) : 9 | lib(lib_), unboxed_fields_bitmap(0), superCls(nullptr), ptr(cls.ptr()), declarationType(nullptr), type(CLASS), 10 | num_type_arguments(0), num_type_parameters(0), mixin(nullptr), is_const_constructor(false), is_transformed_mixin(false) 11 | { 12 | auto zone = dart::Thread::Current()->zone(); 13 | 14 | ASSERT(cls.is_type_finalized()); 15 | 16 | id = (uint32_t)cls.id(); 17 | // empty string for top class (default is "::") 18 | if (!cls.IsTopLevel()) { 19 | // Note: Dart use "Object" as instance name because it is parent of all class 20 | name = cls.ScrubbedNameCString(); 21 | } 22 | 23 | // host_instance_size() is allocated size from heap (need alignment) 24 | // we need only exact size to know the offset of subclass members 25 | size = (int32_t)cls.host_next_field_offset(); 26 | type_argument_offset = (int32_t)cls.host_type_arguments_field_offset(); 27 | //const auto& supCls = Class::Handle(zone, cls.SuperClass()); // parent class 28 | 29 | // sizeof(UntaggedObject) == sizeof(uword); // 32 or 64 bits depended on architecture 30 | // UntaggedObject.HeapSize() includes sizeof(UntaggedObject) 31 | 32 | if (id == dart::kGrowableObjectArrayCid) { 33 | //auto dartField = new DartField(*this, dart::Field::RawCast(fieldPtr)); 34 | //fields.push_back(dartField); 35 | } 36 | 37 | if (!cls.is_loaded() || id <= dart::kLastInternalOnlyCid) { 38 | // can assume the class is native type (also no parent class) 39 | // there is no info (except class name) for native type. 40 | return; 41 | } 42 | 43 | if (!dart::ClassTable::IsTopLevelCid(id)) { 44 | //auto& supCls = dart::Class::Handle(zone, cls.SuperClass()); 45 | auto supClsPtr = cls.SuperClass(); 46 | 47 | auto superCid = supClsPtr.untag()->id(); 48 | if (superCid > 0 && (intptr_t)supClsPtr == (intptr_t)dart::Object::null()) 49 | superCid = 0; 50 | if (superCid) 51 | superCls = (DartClass*)(intptr_t)superCid; // temporary save class id as pointer. it will be set correctly after all classes are loaded 52 | 53 | if (cls.is_const()) 54 | is_const_constructor = true; 55 | // from "vm/class_finalizer.cc":"markImplemented()" 56 | // For a class used as an interface marks this class and all its superclasses implemented. 57 | // Note: mixin is a interface too 58 | // 59 | // the class is auto generated from user class with mixin(s) 60 | // to find the real parent class (after "extends" keyword), 61 | // we have to follow up the parent class until class has no mixin 62 | // the last usage class has no mixin 63 | // Note: the name of this class is prefixed with "_&&" 64 | is_transformed_mixin = cls.is_transformed_mixin_application(); 65 | 66 | // In AOT, all class is finalized 67 | //cls.is_finalized() 68 | if (cls.is_abstract()) 69 | type = ABSTRACT; 70 | else if (cls.is_enum_class()) 71 | type = ENUM; 72 | 73 | num_type_parameters = (uint32_t)cls.NumTypeParameters(); 74 | num_type_arguments = (uint32_t)cls.NumTypeArguments(); 75 | // parent class is needed to generete subvector type parameters of its parent, delay until superCls is set 76 | } 77 | // interfaces reference to other types. wait until all classes are loaded 78 | 79 | // where is nested class? 80 | { 81 | const auto& fields = dart::Array::Handle(zone, cls.fields()); 82 | intptr_t num = fields.Length(); 83 | for (intptr_t i = 0; i < num; i++) { 84 | auto fieldPtr = fields.At(i); 85 | AddField(fieldPtr); 86 | } 87 | } 88 | 89 | { 90 | const auto& funcs = dart::Array::Handle(zone, cls.functions()); 91 | intptr_t num_funcs = funcs.Length(); 92 | for (intptr_t i = 0; i < num_funcs; i++) { 93 | auto funcPtr = funcs.At(i); 94 | AddFunction(funcPtr); 95 | } 96 | } 97 | 98 | //{ 99 | // // Canonicalized const instances of this class (UntaggedClass) 100 | // // constants is HashTable. constants.Length() is array length (number of slots), not number of const instances 101 | // const auto& constants = dart::Array::Handle(zone, cls.constants()); 102 | // if (constants.ptr() != dart::Object::null_array().ptr()) { 103 | // // TODO: 104 | // auto& obj = dart::Object::Handle(zone); 105 | // HtArrayIterator it(constants); 106 | // while (it.MoveNext()) { 107 | // const auto obj_ptr = it.Current(); 108 | // ASSERT(obj_ptr.GetClassId() == id); 109 | // obj = obj_ptr; 110 | // if (id == dart::kImmutableArrayCid) { 111 | // } 112 | // else if (id == dart::kDoubleCid) { 113 | // auto val = dart::Double::Cast(obj).value(); 114 | // //std::cout << fmt::format(" const double: {}\n", val); 115 | // } 116 | // else if (id == dart::kMintCid) { 117 | // auto val = dart::Mint::Cast(obj).value(); 118 | // //std::cout << fmt::format(" const Mint: {:#x}\n", val); 119 | // } 120 | // else if (id == dart::kConstMapCid) { 121 | // auto& map = dart::Map::Cast(obj); 122 | // } 123 | // else if (id == dart::kInstanceCid) { 124 | // // just one const Object 125 | // } 126 | // else 127 | // id = id; 128 | // } 129 | // } 130 | //} 131 | } 132 | 133 | DartClass::DartClass(const DartLibrary& lib) 134 | : lib(lib), unboxed_fields_bitmap(0), id(0), superCls(nullptr), ptr(dart::Class::null()), declarationType(nullptr), 135 | name(""), type(CLASS), num_type_arguments(0), num_type_parameters(0), mixin(nullptr), 136 | type_argument_offset(0), size(0), is_const_constructor(false), is_transformed_mixin(false) 137 | { 138 | } 139 | 140 | DartClass::~DartClass() 141 | { 142 | for (auto field : fields) { 143 | delete field; 144 | } 145 | for (auto func : functions) { 146 | delete func; 147 | } 148 | } 149 | 150 | DartFunction* DartClass::AddFunction(const dart::ObjectPtr funcPtr) 151 | { 152 | auto dartFn = new DartFunction(*this, dart::Function::RawCast(funcPtr)); 153 | functions.push_back(dartFn); 154 | return dartFn; 155 | } 156 | 157 | DartFunction* DartClass::AddFunction(const dart::Code& code) 158 | { 159 | auto dartFn = new DartFunction(*this, code); 160 | functions.push_back(dartFn); 161 | return dartFn; 162 | } 163 | 164 | DartField* DartClass::AddField(const dart::ObjectPtr fieldPtr) 165 | { 166 | auto dartField = new DartField(*this, dart::Field::RawCast(fieldPtr)); 167 | fields.push_back(dartField); 168 | return dartField; 169 | } 170 | 171 | DartField* DartClass::AddField(intptr_t offset, DartAbstractType* type, bool nativeNumber) 172 | { 173 | auto dartField = FindField(offset); 174 | if (dartField == nullptr) { 175 | dartField = new DartField(*this, (uint32_t)offset, type); 176 | fields.push_back(dartField); 177 | } 178 | else { 179 | auto ctype = dartField->Type(); 180 | if (ctype != type) { 181 | // TODO: make it be type parameter 182 | //if (ctype->IsNull()) { 183 | // dartField->SetType(type); 184 | //} 185 | //else if (nativeNumber) { 186 | // // always assume double if possible (can be confirmed from register type in assembly) 187 | // if (type->Class().Id() == dart::kDoubleCid) 188 | // dartField->SetType(type); 189 | //} 190 | // TODO: subclass(polymorphic) or type parameter 191 | } 192 | } 193 | return dartField; 194 | } 195 | 196 | DartField* DartClass::FindField(intptr_t offset) 197 | { 198 | auto it = std::find_if(fields.begin(), fields.end(), [offset](const DartField* field) { return field->Offset() == offset; }); 199 | if (it == fields.end()) 200 | return nullptr; 201 | return *it; 202 | } 203 | 204 | std::string DartClass::FullNameWithPackage() const 205 | { 206 | return "[" + lib.url + "] " + name + typeVectorName; 207 | } 208 | 209 | void DartClass::PrintHead(std::ostream& of) 210 | { 211 | if (superCls == NULL) 212 | of << fmt::format("\n// class id: {}, size: {:#x}\n", id, size); 213 | else 214 | of << fmt::format("\n// class id: {}, size: {:#x}, field offset: {:#x}\n", id, size, superCls->size); 215 | if (dart::ClassTable::IsTopLevelCid(id)) { 216 | of << "class :: {\n"; 217 | return; 218 | } 219 | if (superCls == NULL) { 220 | of << fmt::format("class {};\n", name.c_str()); 221 | return; 222 | } 223 | 224 | if (is_const_constructor || is_transformed_mixin) { 225 | of << "// "; 226 | if (is_const_constructor) 227 | of << "const constructor, "; 228 | if (is_transformed_mixin) 229 | of << "transformed mixin,"; 230 | of << "\n"; 231 | } 232 | 233 | std::string cls_prefix; 234 | if (type == DartClass::CLASS) 235 | cls_prefix = "class"; 236 | else if (type == DartClass::ABSTRACT) 237 | cls_prefix = "abstract class"; 238 | else if (type == DartClass::ENUM) 239 | cls_prefix = "enum"; 240 | else 241 | cls_prefix = "maybe_class"; 242 | 243 | of << cls_prefix << " "; 244 | 245 | of << name << typeVectorName; 246 | 247 | of << " extends " << superCls->name << parentTypeVectorName;; 248 | 249 | // if there is a mixin, the last one is mixin 250 | if (!interfaces.empty() || mixin) { 251 | of << "\n "; 252 | if (!interfaces.empty()) { 253 | of << "implements "; 254 | of << std::accumulate(interfaces.begin() + 1, interfaces.end(), interfaces[0]->FullName(), 255 | [](std::string x, DartClass* y) { 256 | return x + ", " + y->FullName(); 257 | } 258 | ); 259 | } 260 | if (mixin) { 261 | of << " with " << mixin->FullName(); 262 | } 263 | } 264 | 265 | of << " {\n"; 266 | } 267 | 268 | void DartClass::PrintFoot(std::ostream& of) 269 | { 270 | of << "}\n"; 271 | } 272 | -------------------------------------------------------------------------------- /blutter/src/DartClass.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "DartField.h" 4 | 5 | class DartLibrary; 6 | class DartFunction; 7 | 8 | class DartClass 9 | { 10 | public: 11 | enum ClassType { 12 | CLASS, // [normal] class 13 | ABSTRACT, // abstract class 14 | ENUM, // enum (parent class is _Enum) 15 | }; 16 | explicit DartClass(const DartLibrary& lib, const dart::Class& cls); 17 | // for creating dummy class (only used for obfuscated app) 18 | explicit DartClass(const DartLibrary& lib); 19 | DartClass() = delete; 20 | DartClass(const DartClass&) = delete; 21 | DartClass(DartClass&&) = delete; 22 | DartClass& operator=(const DartClass&) = delete; 23 | ~DartClass(); 24 | 25 | DartFunction* AddFunction(const dart::ObjectPtr funcPtr); 26 | DartFunction* AddFunction(const dart::Code& code); 27 | DartField* AddField(const dart::ObjectPtr fieldPtr); 28 | DartField* AddField(intptr_t offset, DartAbstractType* type, bool nativeNumber = false); 29 | DartField* FindField(intptr_t offset); 30 | 31 | //bool IsNative() { return lib.ptr == nullptr; } 32 | bool IsTopClass() const { return dart::ClassTable::IsTopLevelCid(id); } 33 | uint32_t Id() const { return id; } 34 | const DartLibrary& Library() const { return lib; } 35 | dart::ClassPtr Ptr() const { return ptr; } 36 | DartClass* Parent() const { return superCls; } 37 | 38 | DartType* DeclarationType() { return declarationType; } 39 | 40 | const std::string& Name() const { return name; } 41 | std::string FullName() const { return name + typeVectorName; } 42 | std::string FullNameWithPackage() const; 43 | 44 | uint32_t NumTypeArguments() const { return num_type_arguments; } 45 | uint32_t NumTypeParameters() const { return num_type_parameters; } 46 | bool HasTypeArguments() const { return type_argument_offset != dart::Class::kNoTypeArguments; } 47 | int32_t TypeArgumentsOffset() const { return type_argument_offset; } 48 | 49 | int32_t Size() const { return size; } 50 | uint64_t FieldBitmap() const { return unboxed_fields_bitmap.Value(); } 51 | dart::UnboxedFieldBitmap UnboxedFieldsBitmap() const { return unboxed_fields_bitmap; } 52 | int32_t TypeArgumentOffset() const { return type_argument_offset; } 53 | 54 | std::vector& Fields() { return fields; } 55 | std::vector& Functions() { return functions; }; 56 | 57 | void PrintHead(std::ostream& of); 58 | void PrintFoot(std::ostream& of); 59 | 60 | private: 61 | const DartLibrary& lib; 62 | dart::UnboxedFieldBitmap unboxed_fields_bitmap; 63 | uint32_t id; // class id 64 | //uint32_t superCid; // parent class id 65 | DartClass* superCls; // super class, initialized as NULL, set after all classes are loaded 66 | const dart::ClassPtr ptr; 67 | DartType* declarationType; 68 | std::string name; 69 | std::string typeVectorName; // 70 | std::string parentTypeVectorName; // of parent class for this class 71 | ClassType type; 72 | //uint32_t parent_id; 73 | // num_type_arguments is declaration_args length 74 | // it is args in <>. the number is from number of this class and the parent 75 | //DartTypeArguments* declaration_args; 76 | uint32_t num_type_arguments; 77 | // num_type_params is this length (number of this class arguments) 78 | uint32_t num_type_parameters; 79 | //DartTypeParametersItem* type_params; 80 | std::vector interfaces; 81 | DartClass* mixin; 82 | //uint32_t parent_size; // offset to start of this class fields 83 | int32_t type_argument_offset; 84 | int32_t size; 85 | bool is_const_constructor; 86 | bool is_transformed_mixin; 87 | std::vector fields; 88 | std::vector functions; 89 | 90 | friend class DartApp; 91 | }; 92 | 93 | -------------------------------------------------------------------------------- /blutter/src/DartDumper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "DartApp.h" 3 | #include 4 | 5 | class DartDumper 6 | { 7 | public: 8 | DartDumper(DartApp& app) : app(app) {}; 9 | 10 | void Dump4Radare2(std::filesystem::path outDir); 11 | void Dump4Ida(std::filesystem::path outDir); 12 | 13 | std::vector> DumpStructHeaderFile(std::string outFile); 14 | 15 | void DumpCode(const char* out_dir); 16 | 17 | void DumpObjectPool(const char* filename); 18 | void DumpObjects(const char* filename); 19 | 20 | std::string ObjectToString(dart::Object& obj, bool simpleForm = false, bool nestedObj = false, int depth = 0); 21 | 22 | private: 23 | std::string getPoolObjectDescription(intptr_t offset, bool simpleForm = true); 24 | 25 | std::string dumpInstance(dart::Object& obj, bool simpleForm = false, bool nestedObj = false, int depth = 0); 26 | std::string dumpInstanceFields(dart::Object& obj, DartClass& dartCls, intptr_t ptr, intptr_t offset, bool simpleForm = false, bool nestedObj = false, int depth = 0); 27 | 28 | void applyStruct4Ida(std::ostream& of); 29 | 30 | const std::string& getQuoteString(dart::Object& obj); 31 | 32 | DartApp& app; 33 | // map for object ptr to unescape string with quote 34 | std::unordered_map quoteStringCache; 35 | }; 36 | -------------------------------------------------------------------------------- /blutter/src/DartField.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "DartField.h" 3 | #include "DartClass.h" 4 | #include "DartLibrary.h" 5 | 6 | DartField::DartField(const DartClass& cls_, dart::FieldPtr ptr_) : cls(cls_), type(nullptr), ptr(ptr_) 7 | { 8 | ASSERT(!ptr_.IsRawNull()); 9 | auto zone = dart::Thread::Current()->zone(); 10 | const auto& field = dart::Field::Handle(ptr_); 11 | name = field.UserVisibleNameCString(); 12 | // static field has no offset in object. it is offset of static list 13 | offset = (uint32_t)field.TargetOffset(); 14 | is_static = field.is_static(); 15 | is_late = field.is_late(); 16 | is_final = field.is_final(); 17 | is_const = field.is_const(); 18 | //field.is_covariant(); 19 | 20 | typePtr = field.type(); 21 | const auto& abType = dart::AbstractType::Handle(typePtr); 22 | dart::ZoneTextBuffer buffer(zone); 23 | abType.PrintName(dart::Object::kScrubbedName, &buffer); 24 | typeName = buffer.buffer(); 25 | } 26 | 27 | DartField::DartField(const DartClass& cls, uint32_t offset, DartAbstractType* type, std::string name) : 28 | cls(cls), type(type), name(std::move(name)), offset(offset), is_static(false), is_late(false), is_final(false), is_const(false) 29 | { 30 | } 31 | 32 | void DartField::Print(std::ostream& of) const 33 | { 34 | of << " "; 35 | if (ptr == nullptr) { 36 | // use concrete type 37 | ASSERT(type); 38 | of << type->ToString(); 39 | of << fmt::format(" field_{:x};\n", offset); 40 | } 41 | else { 42 | if (is_static) 43 | of << "static "; 44 | if (is_late) 45 | of << "late "; 46 | if (is_final) 47 | of << "final "; 48 | if (is_const) 49 | of << "const "; 50 | of << typeName << " " << name; 51 | of << fmt::format("; // offset: {:#x}\n", offset); 52 | } 53 | } 54 | 55 | std::string DartField::FullName() const 56 | { 57 | return "[" + cls.Library().url + "] " + cls.FullName() + "::" + name; 58 | } 59 | -------------------------------------------------------------------------------- /blutter/src/DartField.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "DartTypes.h" 4 | 5 | class DartClass; 6 | 7 | class DartField 8 | { 9 | public: 10 | DartField(const DartClass& cls, dart::FieldPtr ptr); 11 | DartField(const DartClass& cls, uint32_t offset, DartAbstractType* type, std::string name=""); 12 | DartField() = delete; 13 | DartField(const DartField&) = delete; 14 | DartField(DartField&&) = delete; 15 | DartField& operator=(const DartField&) = delete; 16 | 17 | void Print(std::ostream& of) const; 18 | 19 | dart::FieldPtr Ptr() const { return ptr; } 20 | const std::string& Name() const { return name; } 21 | std::string FullName() const; 22 | uint32_t Offset() const { return offset; } 23 | bool IsStatic() const { return is_static; } 24 | bool IsLate() const { return is_late; } 25 | bool IsFinal() const { return is_final; } 26 | bool IsConst() const { return is_const; } 27 | 28 | DartAbstractType* Type() const { return type; } 29 | void SetType(DartAbstractType* type) { this->type = type; } 30 | 31 | const DartClass& cls; 32 | 33 | private: 34 | DartAbstractType* type; 35 | dart::FieldPtr ptr; 36 | dart::AbstractTypePtr typePtr; 37 | std::string typeName; 38 | std::string name; 39 | uint32_t offset; 40 | bool is_static; 41 | bool is_late; 42 | bool is_final; 43 | bool is_const; 44 | //bool is_covariant; 45 | }; 46 | 47 | -------------------------------------------------------------------------------- /blutter/src/DartFnBase.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | // forward declaration 6 | class DartFunction; 7 | class DartStub; 8 | 9 | class DartFnBase 10 | { 11 | public: 12 | // For setting current library loaded address 13 | // because the Dart code and global data are independent (access through Object Pool) 14 | // so we can set a code address to be as if the library is loaded at 0 (to be same for every run) 15 | static void SetLibBase(intptr_t addr) { lib_base = addr; } 16 | 17 | DartFnBase(const DartFnBase&) = default; 18 | DartFnBase(DartFnBase&&) = delete; 19 | DartFnBase& operator=(const DartFnBase&) = delete; 20 | virtual ~DartFnBase() {} 21 | 22 | uint64_t Address() const { return ep_addr; } 23 | uint64_t MemAddress() const { return ep_addr + lib_base; } 24 | virtual int64_t Size() const { return size; } 25 | virtual uint64_t AddressEnd() const { return ep_addr + Size(); } 26 | bool ContainsAddress(uint64_t addr) { return addr >= Address() && addr < AddressEnd(); } 27 | 28 | virtual std::string FullName() const { return name; } 29 | virtual std::string Name() const { return name; } 30 | // TODO: use dart type to support function type and type parameters () 31 | virtual uint32_t ReturnType() const { return dart::kIllegalCid; } 32 | virtual bool IsStub() const { return false; } 33 | 34 | DartFunction* AsFunction() { ASSERT(!IsStub()); return reinterpret_cast(this); } 35 | DartStub* AsStub() { ASSERT(IsStub()); return reinterpret_cast(this); } 36 | 37 | protected: 38 | DartFnBase() : ep_addr(0), size(0) {} 39 | DartFnBase(uint64_t addr, int64_t size, std::string name) : ep_addr(addr), size(size), name(std::move(name)) {} 40 | 41 | uint64_t ep_addr; 42 | int64_t size; 43 | std::string name; 44 | 45 | static intptr_t lib_base; 46 | }; -------------------------------------------------------------------------------- /blutter/src/DartFunction.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "DartFunction.h" 3 | #include "DartClass.h" 4 | #include "DartLibrary.h" 5 | #include "DartApp.h" 6 | #include "VarValue.h" 7 | #include "CodeAnalyzer.h" 8 | #include 9 | #include 10 | 11 | // place static member because no DartFnBase source file 12 | intptr_t DartFnBase::lib_base; 13 | 14 | DartFunction::DartFunction(DartClass& cls, const dart::FunctionPtr ptr) : DartFnBase(), cls(cls), parent(nullptr), ptr(ptr), kind(NORMAL) 15 | { 16 | auto* zone = dart::Thread::Current()->zone(); 17 | 18 | const auto& func = dart::Function::Handle(zone, ptr); 19 | 20 | // might need internal name for complete getter and setter name 21 | name = func.UserVisibleNameCString(); 22 | 23 | is_native = func.is_native(); 24 | //is_closure = name == ""; 25 | is_closure = func.IsClosureFunction(); //func.IsNonImplicitClosureFunction(); 26 | //ASSERT(is_closure == (name == "")); 27 | // Note: https://github.com/dart-lang/sdk/commit/dcdfcc2b8decc8bf47881f2e93ee5503ea98b7cf#diff-fba8499e9b9e86f518a0ad80eeda6a4309ac5a82c5e9e0b0bf962505e1ecddba 28 | // IsFfiTrampoline() is changed to IsFfiCallbackTrampoline() 29 | is_ffi = func.kind() == dart::UntaggedFunction::kFfiTrampoline; 30 | if (!is_ffi) { 31 | //is_static = func.IsStaticFunction(); 32 | is_static = func.is_static(); 33 | is_const = func.is_const(); 34 | is_abstract = func.is_abstract(); 35 | is_async = func.IsAsyncFunction(); 36 | func.IsGetterFunction(); 37 | 38 | // function attributes 39 | switch (func.kind()) { 40 | case dart::UntaggedFunction::kConstructor: 41 | kind = CONSTRUCTOR; 42 | break; 43 | case dart::UntaggedFunction::kSetterFunction: 44 | case dart::UntaggedFunction::kImplicitSetter: 45 | kind = SETTER; 46 | break; 47 | case dart::UntaggedFunction::kGetterFunction: 48 | case dart::UntaggedFunction::kImplicitGetter: 49 | case dart::UntaggedFunction::kImplicitStaticGetter: 50 | //case dart::UntaggedFunction::kRecordFieldGetter: 51 | kind = GETTER; 52 | break; 53 | default: 54 | kind = NORMAL; 55 | } 56 | } 57 | else { 58 | is_static = false; 59 | is_const = false; 60 | is_abstract = false; 61 | is_async = false; 62 | //auto fnType = func.FfiCSignature(); 63 | //if (!fnType.IsRawNull()) { 64 | // auto& sig = dart::FunctionType::Handle(zone, fnType); 65 | // std::cout << "FFI: " << name << ", sig: " << sig.ToCString() << "\n"; 66 | //} 67 | } 68 | 69 | // the generated code can be checked from Assembler::MonomorphicCheckedEntryAOT() 70 | // Code.EntryPoint() in obfuscated app might be pointed at start of snapshot (wrong) 71 | const auto ep = func.entry_point() - lib_base; 72 | const auto& code = dart::Code::Handle(zone, func.CurrentCode()); 73 | payload_addr = code.PayloadStart(); 74 | if (payload_addr > 0) 75 | payload_addr -= lib_base; 76 | morphic_addr = code.MonomorphicEntryPoint(); 77 | if (morphic_addr > 0) 78 | morphic_addr -= lib_base; 79 | size = code.Size(); 80 | ep_addr = code.EntryPoint() - lib_base; 81 | if (ep != ep_addr) { 82 | ep_addr = ep; 83 | //std::cout << fmt::format("Fn: {}, payload: {:#x}, ep_addr: {:#x}, ep: {:#x}\n", name.c_str(), payload_addr, ep_addr, ep); 84 | } 85 | 86 | //if (ep_addr != payload_addr) { 87 | // std::cout << fmt::format("Fn: {}, payload: {:#x}, morphic: {:#x}, ep: {:#x}\n", name.c_str(), payload_addr, morphic_addr, ep_addr); 88 | //} 89 | 90 | // closure parent (what function use this closure) 91 | if (is_closure) { 92 | is_static = func.is_static(); 93 | auto parentPtr = func.parent_function(); 94 | if ((intptr_t)parentPtr != (intptr_t)dart::Function::null()) { 95 | //auto outmost_parent = func.GetOutermostFunction(); 96 | //auto& parentFn = dart::Function::Handle(zone, parentPtr); 97 | // Now, the parent function might be missing. 98 | // store parent as entry point first. it will be changed to pointer later. 99 | //parent = (DartFunction*)(parentFn.entry_point() - lib_base); 100 | parent = (DartFunction*)(intptr_t)parentPtr; 101 | } 102 | else { 103 | //std::cout << fmt::format("[?] closure {} ({:#x}) has null parent\n", name, ep_addr); 104 | } 105 | } 106 | 107 | // TODO: 108 | // more info: https://mrale.ph/dartvm/compiler/exceptions.html 109 | //auto& catchData = dart::TypedData::Handle(zone, code.catch_entry_moves_maps()); 110 | } 111 | 112 | // kind should be raw code 113 | DartFunction::DartFunction(DartClass& cls, const dart::Code& code) 114 | : DartFnBase(), cls(cls), parent(nullptr), ptr(dart::Function::null()), kind(NORMAL), 115 | is_native(true), is_closure(false), is_ffi(false), is_static(false), is_const(false), is_abstract(false), is_async(false) 116 | { 117 | payload_addr = code.PayloadStart(); 118 | if (payload_addr > 0) 119 | payload_addr -= lib_base; 120 | morphic_addr = code.MonomorphicEntryPoint(); 121 | if (morphic_addr > 0) 122 | morphic_addr -= lib_base; 123 | size = code.Size(); 124 | ep_addr = code.EntryPoint() - lib_base; 125 | name = "__unknown_function__"; 126 | } 127 | 128 | std::string DartFunction::FullName() const 129 | { 130 | auto& lib = cls.Library(); 131 | return "[" + lib.url + "] " + cls.Name() + "::" + name; 132 | } 133 | 134 | DartFunction* DartFunction::GetOutermostFunction() const 135 | { 136 | // Only closure should call this method 137 | if (!parent) 138 | return nullptr; 139 | 140 | DartFunction* topFn = parent; 141 | while (topFn->parent) { 142 | ASSERT(topFn->IsClosure()); 143 | topFn = topFn->parent; 144 | } 145 | return topFn; 146 | } 147 | 148 | void DartFunction::SetAnalyzedData(std::unique_ptr data) 149 | { 150 | // must never be called more than once 151 | ASSERT(!analyzedData); 152 | 153 | analyzedData = std::move(data); 154 | } 155 | 156 | static const auto BinaryOpNames = std::to_array({ "+", "-", "*", "/", "~/", "%", "&", "|" }); 157 | static bool isBinaryOpName(const std::string& name) 158 | { 159 | return std::find(BinaryOpNames.begin(), BinaryOpNames.end(), name) != BinaryOpNames.end(); 160 | } 161 | 162 | std::string DartFunction::ToCallStatement(const std::vector>& args) const 163 | { 164 | std::string callArgs = ""; 165 | std::string callFn = ""; 166 | if (IsStatic() || kind == CONSTRUCTOR) { 167 | if (!args.empty()) { 168 | callArgs = std::accumulate(args.begin() + 1, args.end(), args[0]->CallArgName(), 169 | [](std::string x, const std::shared_ptr y) { 170 | return x + ", " + y->CallArgName(); 171 | } 172 | ); 173 | } 174 | callFn = Name(); 175 | } 176 | else { 177 | ASSERT(!args.empty()); 178 | if (isBinaryOpName(name)) { 179 | ASSERT(args.size() == 2); 180 | return args[0]->CallArgName() + " " + name + " " + args[1]->CallArgName(); 181 | } 182 | else { 183 | if (args.size() > 1) { 184 | callArgs = std::accumulate(args.begin() + 2, args.end(), args[1]->CallArgName(), 185 | [](std::string x, const std::shared_ptr& y) { 186 | return x + ", " + y->CallArgName(); 187 | } 188 | ); 189 | } 190 | callFn = args[0]->CallArgName() + "." + Name(); 191 | } 192 | } 193 | return fmt::format("{}({})", callFn.c_str(), callArgs.c_str()); 194 | } 195 | 196 | void DartFunction::PrintHead(std::ostream& of) const 197 | { 198 | //of << fmt::format(" {} /* addr: {:#x}, size: {:#x} */\n", func.ToCString(), ep, code_size); 199 | auto zone = dart::Thread::Current()->zone(); 200 | auto& func = dart::Function::Handle(zone, ptr); 201 | 202 | // Note: Signature is not dropped in aot when any named parameter is required. (from Function::IsRequiredAt() body) 203 | const auto& sig = dart::FunctionType::Handle(zone, func.signature()); 204 | 205 | of << " "; // indentation 206 | if (is_closure) 207 | of << "[closure] "; 208 | 209 | if (is_ffi) { 210 | of << "[ffi] "; 211 | } 212 | else { 213 | if (is_const) 214 | of << "const "; 215 | else if (is_abstract) 216 | of << "abstract "; 217 | 218 | switch (kind) { 219 | case CONSTRUCTOR: 220 | if (is_static) 221 | of << "factory "; 222 | break; 223 | case SETTER: 224 | of << "set "; 225 | break; 226 | case GETTER: 227 | if (sig.IsNull()) 228 | of << "get "; 229 | break; 230 | default: 231 | if (is_static) 232 | of << "static "; 233 | break; 234 | } 235 | } 236 | 237 | if (sig.IsNull()) { 238 | of << "_ " << name << "(/* No info */)"; 239 | } 240 | else { 241 | dart::ZoneTextBuffer buffer(zone); 242 | const auto& result_type = dart::AbstractType::Handle(sig.result_type()); 243 | result_type.PrintName(dart::Object::kScrubbedName, &buffer); 244 | of << buffer.buffer() << " " << name; 245 | // function type paramaters 246 | const auto& type_params = dart::TypeParameters::Handle(zone, sig.type_parameters()); 247 | if (!type_params.IsNull()) { 248 | buffer.Clear(); 249 | type_params.Print(dart::Thread::Current(), zone, false, 0, dart::Object::kScrubbedName, &buffer); 250 | of << "<" << buffer.buffer() << ">"; 251 | } 252 | buffer.Clear(); 253 | sig.PrintParameters(dart::Thread::Current(), zone, dart::Object::kScrubbedName, &buffer); 254 | of << "(" << buffer.buffer() << ")"; 255 | } 256 | 257 | if (is_async) { 258 | of << " async"; 259 | } 260 | of << " {\n"; 261 | 262 | of << fmt::format(" // ** addr: {:#x}, size: {:#x}\n", ep_addr, size); 263 | } 264 | 265 | void DartFunction::PrintFoot(std::ostream& of) const 266 | { 267 | of << " }\n"; 268 | } 269 | -------------------------------------------------------------------------------- /blutter/src/DartFunction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "DartFnBase.h" 3 | #include "CodeAnalyzer.h" 4 | 5 | class DartClass; 6 | class DartApp; 7 | struct VarItem; 8 | 9 | struct FnParam { 10 | DartAbstractType* type{ nullptr }; 11 | std::string name; 12 | bool isRequired{ false }; 13 | 14 | explicit FnParam(DartAbstractType* type, std::string name, bool isRequired) 15 | : type(type), name(std::move(name)), isRequired(isRequired) {} 16 | }; 17 | 18 | struct DartFunctionSignature 19 | { 20 | DartAbstractType* ReturnType() const { return returnType; } 21 | 22 | int NumParam() const { return params.size(); } 23 | int NumOptionalParam() const { return numOptionalParam; } 24 | bool HasNamedParam() const { return hasNamedParam; } 25 | std::vector& Params() { return params; } 26 | FnParam& Param(int i) { return params[i]; } 27 | 28 | DartAbstractType* returnType; 29 | //typeParams; 30 | std::vector params; 31 | int numOptionalParam; 32 | bool hasNamedParam; 33 | }; 34 | 35 | class DartFunction : public DartFnBase 36 | { 37 | public: 38 | enum FunctionKind { 39 | NORMAL, 40 | CONSTRUCTOR, 41 | GETTER, 42 | SETTER, 43 | }; 44 | explicit DartFunction(DartClass& cls, const dart::FunctionPtr ptr); 45 | // for creating naked code (only used for obfuscated app) 46 | explicit DartFunction(DartClass& cls, const dart::Code& code); 47 | DartFunction() = delete; 48 | DartFunction(const DartFunction&) = delete; 49 | DartFunction(DartFunction&&) = delete; 50 | DartFunction& operator=(const DartFunction&) = delete; 51 | virtual ~DartFunction() {} 52 | 53 | DartClass& Class() const { return cls; } 54 | dart::FunctionPtr Ptr() const { return ptr; } 55 | //uint64_t Address() { return ep_addr; } 56 | //uint32_t Size() { return code_size; } 57 | FunctionKind Kind() const { return kind; } 58 | 59 | uint64_t PayloadAddress() const { return payload_addr; } 60 | uint64_t PayloadSize() const { return size; } 61 | uint64_t MonomorphicAddress() const { return morphic_addr; } 62 | bool HasMorphicCode() const { return morphic_addr != ep_addr; } 63 | 64 | virtual int64_t Size() const { return size > 0 ? size - (ep_addr - payload_addr) : 0; } 65 | virtual std::string FullName() const; 66 | 67 | DartFunction* GetOutermostFunction() const; 68 | 69 | bool IsNative() const { return is_native; } 70 | bool IsClosure() const { return is_closure; } 71 | bool IsFfi() const { return is_ffi; } 72 | bool IsStatic() const { return is_static; } 73 | bool IsConst() const { return is_const; } 74 | bool IsAbstract() const { return is_abstract; } 75 | bool IsAsync() const { return is_async; } 76 | 77 | DartFunctionSignature& Signature() { return signature; } 78 | int NumParam() const { return signature.NumParam(); } 79 | int FirstParamOffset() const { return NumParam() * sizeof(void*) + sizeof(void*); } 80 | int NumOptionalParam() const { return signature.NumOptionalParam(); } 81 | bool HasNamedParam() const { return signature.HasNamedParam(); } 82 | std::vector& Params() { return signature.Params(); } 83 | FnParam& Param(int i) { return signature.Param(i); } 84 | 85 | void SetAnalyzedData(std::unique_ptr data); 86 | AnalyzedFnData* GetAnalyzedData() { return analyzedData.get(); } 87 | 88 | std::string ToCallStatement(const std::vector>& args) const; 89 | void PrintHead(std::ostream& of) const; 90 | void PrintFoot(std::ostream& of) const; 91 | 92 | private: 93 | DartClass& cls; 94 | DartFunction* parent; // this value is nullptr for function. parent function/closure for a closure 95 | dart::FunctionPtr ptr; 96 | FunctionKind kind; 97 | bool is_native; 98 | bool is_closure; 99 | bool is_ffi; // if a function is FFI, no use of is_static, is_const, is_abstract 100 | bool is_static; 101 | bool is_const; 102 | bool is_abstract; 103 | bool is_async; 104 | 105 | uint64_t payload_addr; // the start of whole function data (most of them are same as entry point) 106 | uint64_t morphic_addr; // Monomorphic entry point (used for check class id before normal entry point) 107 | //uint32_t code_size; // code size 108 | 109 | DartFunctionSignature signature; 110 | std::unique_ptr analyzedData; 111 | 112 | friend class DartApp; 113 | }; 114 | 115 | -------------------------------------------------------------------------------- /blutter/src/DartLibrary.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "DartLibrary.h" 3 | #include "DartClass.h" 4 | #include 5 | 6 | DartLibrary::DartLibrary(const dart::Library& lib) : ptr(lib.ptr()), topClass(NULL) 7 | { 8 | auto& dtext = dart::String::Handle(); 9 | dtext = lib.name(); 10 | name = dtext.ToCString(); 11 | dtext = lib.url(); 12 | url = dtext.ToCString(); 13 | 14 | if (lib.is_dart_scheme()) 15 | isInternal = true; 16 | //else if (url.starts_with("package:flutter/src/") || url.starts_with("package:typed_data/src/") || url.starts_with("package:collection/src/")) 17 | // isInternal = true; 18 | else 19 | isInternal = false; 20 | 21 | // add all classes belong to this library 22 | auto& cls = dart::Class::Handle(); 23 | cls = lib.toplevel_class(); 24 | topClass = AddClass(cls); 25 | id = topClass->Id(); 26 | 27 | dart::DictionaryIterator iter(lib); 28 | while (iter.HasNext()) { 29 | auto objPtr = iter.GetNext(); 30 | // only 4 possible types but functions and fields are in top level class 31 | if (objPtr.IsClass()) { 32 | cls = dart::Class::RawCast(objPtr); 33 | AddClass(cls); 34 | } 35 | else if (objPtr.IsFunction()) { 36 | // TODO: check if top level class contain this function 37 | } 38 | else if (objPtr.IsField()) { 39 | // TODO: check if top level class contain this field 40 | } 41 | else if (objPtr.IsLibraryPrefix()) { 42 | throw std::runtime_error("library prefix in AOT"); 43 | } 44 | else { 45 | throw std::runtime_error("unknown type in library"); 46 | } 47 | } 48 | } 49 | 50 | DartLibrary::~DartLibrary() 51 | { 52 | for (auto cls : classes) { 53 | delete cls; 54 | } 55 | } 56 | 57 | std::string DartLibrary::GetName() 58 | { 59 | // name is empty for non-internal dart lib 60 | std::string out; 61 | if (url.starts_with("package:")) { 62 | //out = url.substr(8, url.find('/', 8) - 8); 63 | out = url.substr(8); 64 | } 65 | else if (url.starts_with("file:")) { 66 | auto offset = url.rfind('/'); 67 | offset = url.rfind('/', offset - 1); 68 | out = url.substr(offset + 1); 69 | } 70 | else { 71 | // expect "dart:*" 72 | out = url; 73 | out[4] = '_'; 74 | } 75 | 76 | if (out.ends_with(".dart")) 77 | out.erase(out.length() - 5); 78 | std::replace(out.begin(), out.end(), '/', '$'); 79 | 80 | return out; 81 | } 82 | 83 | DartClass* DartLibrary::AddClass(const dart::Class& cls) 84 | { 85 | auto dartCls = new DartClass(*this, cls); 86 | classes.push_back(dartCls); 87 | return dartCls; 88 | } 89 | 90 | std::string DartLibrary::CreatePath(const char* base_dir) 91 | { 92 | // create subdirectories for the library file 93 | std::string path = base_dir; 94 | path.push_back('/'); 95 | size_t start_pos = 0; 96 | if (url.starts_with("package:")) { 97 | start_pos = 8; // skip "package:" 98 | } 99 | else if (url.starts_with("file:///")) { 100 | start_pos = url.find("/.dart_tool/") + 1; 101 | } 102 | else if (url.starts_with("dart:")) { 103 | path.append("dart"); 104 | std::filesystem::create_directories(path); 105 | return path.append("/").append(&url[5]).append(".dart"); 106 | } 107 | else { 108 | // obfuscated 109 | ASSERT(url.find('/') == -1); 110 | return path.append(url).append(".dart"); 111 | } 112 | const char* lib_path = &url[start_pos]; // skip directory name 113 | const char* end = strrchr(lib_path, '/'); 114 | path.append(lib_path, end); 115 | std::filesystem::create_directories(path); 116 | path.append(end); 117 | return path; 118 | } 119 | 120 | void DartLibrary::PrintCommentInfo(std::ostream& of) 121 | { 122 | of << fmt::format("// lib: {}, url: {}\n", name.c_str(), url.c_str()); 123 | } 124 | -------------------------------------------------------------------------------- /blutter/src/DartLibrary.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | class DartClass; 5 | 6 | class DartLibrary 7 | { 8 | public: 9 | DartLibrary(const dart::Library& lib); 10 | // for creating non-existed library. needed for native classes 11 | DartLibrary(intptr_t nullId) : id((uint32_t)nullId), isInternal(true), ptr(nullptr), topClass(NULL) {} 12 | DartLibrary() = delete; 13 | DartLibrary(const DartLibrary&) = delete; 14 | DartLibrary(DartLibrary&&) = delete; 15 | DartLibrary& operator=(const DartLibrary&) = delete; 16 | ~DartLibrary(); 17 | 18 | bool operator==(const DartLibrary& rhs) { 19 | return name == rhs.name && url == rhs.url; 20 | } 21 | 22 | const std::string& Url() { return url; } 23 | std::string GetName(); 24 | DartClass* AddClass(const dart::Class& cls); 25 | 26 | std::string CreatePath(const char* base_dir); 27 | void PrintCommentInfo(std::ostream& of); 28 | 29 | uint32_t id; // it is array index. store it in object to get the index quicker 30 | bool isInternal; 31 | const dart::LibraryPtr ptr; 32 | std::string name; 33 | std::string url; 34 | std::vector classes; 35 | DartClass* topClass; 36 | // fields and functions are in topClass (named "::") 37 | //std::vector fields; 38 | //std::vector functions; 39 | }; 40 | 41 | -------------------------------------------------------------------------------- /blutter/src/DartLoader.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "DartLoader.h" 3 | #include 4 | #include 5 | 6 | // Note: most running dart VM code from runtime/bin/main.cc 7 | 8 | static void init_vm_flags() 9 | { 10 | // From flutter/engine/runtime/dart_vm.cc 11 | const char* options[] = { 12 | //"--ignore-unrecognized-flags", 13 | //"--enable_mirrors=false", 14 | "--precompilation", 15 | }; 16 | char* error = Dart_SetVMFlags(sizeof(options) / sizeof(*options), options); 17 | if (error) 18 | throw std::runtime_error(error); 19 | } 20 | 21 | static void init_dart(const uint8_t* vm_snapshot_data, const uint8_t* vm_snapshot_instructions) 22 | { 23 | char* error = NULL; 24 | 25 | Dart_InitializeParams init_params; 26 | memset(&init_params, 0, sizeof(init_params)); 27 | init_params.version = DART_INITIALIZE_PARAMS_CURRENT_VERSION; 28 | init_params.vm_snapshot_data = vm_snapshot_data; 29 | init_params.vm_snapshot_instructions = vm_snapshot_instructions; 30 | init_params.start_kernel_isolate = false; 31 | // other params are no needed if snapshot is not run 32 | error = Dart_Initialize(&init_params); 33 | if (error) { 34 | throw std::runtime_error(error); 35 | } 36 | } 37 | 38 | static Dart_Isolate load_isolate(const uint8_t* isolate_snapshot_data, const uint8_t* isolate_snapshot_instructions) 39 | { 40 | char* error = NULL; 41 | 42 | Dart_IsolateFlags flags; 43 | Dart_IsolateFlagsInitialize(&flags); 44 | flags.is_system_isolate = false; 45 | //flags.snapshot_is_dontneed_safe = true; // Dart <= 2.14 has no this field 46 | // dart 3 is always null safety 47 | // null safety is enabled by default on Flutter 2.0 with Dart 2.12 (since April 2021) 48 | auto pos = strstr((const char*)isolate_snapshot_data + 0x30, "null-safety"); 49 | if (pos == NULL) { 50 | // dart 3 is always null safety, it's ok to consider null-safety to be present, blutter isn't able to find it 51 | // in some of dev versions starting 3.5.0-xx-dev from snapshot data 52 | std::cout << "Cannot find null-safety text. Setting null_safety to true." << std::endl; 53 | flags.null_safety = true; 54 | } else { 55 | // "no-null-safety" is set when null safety is disabled. So check for space 56 | flags.null_safety = pos[-1] == ' '; 57 | } 58 | 59 | auto isolate = Dart_CreateIsolateGroup(nullptr, nullptr, isolate_snapshot_data, 60 | isolate_snapshot_instructions, &flags, 61 | nullptr, nullptr, &error); 62 | if (isolate == NULL) { 63 | throw std::runtime_error(error); 64 | } 65 | 66 | return isolate; 67 | } 68 | 69 | Dart_Isolate DartLoader::Load(LibAppInfo& libInfo) 70 | { 71 | init_vm_flags(); 72 | 73 | init_dart(libInfo.vm_snapshot_data, libInfo.vm_snapshot_instructions); 74 | 75 | auto isolate = load_isolate(libInfo.isolate_snapshot_data, libInfo.isolate_snapshot_instructions); 76 | 77 | return isolate; 78 | } 79 | 80 | template 81 | inline void ignore_result(const T& /* unused result */) {} 82 | 83 | void DartLoader::Unload() 84 | { 85 | if (Dart_CurrentIsolate() != nullptr) { 86 | Dart_ShutdownIsolate(); 87 | } 88 | ignore_result(Dart_Cleanup()); 89 | } 90 | -------------------------------------------------------------------------------- /blutter/src/DartLoader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ElfHelper.h" 3 | 4 | class DartLoader final 5 | { 6 | public: 7 | /*struct LoadInfo { 8 | void* lib; 9 | uint8_t* vm_snapshot_data; 10 | uint8_t* vm_snapshot_instructions; 11 | uint8_t* isolate_snapshot_data; 12 | uint8_t* isolate_snapshot_instructions; 13 | uintptr_t heap_base; 14 | 15 | intptr_t base() { return (intptr_t)lib; } 16 | uint32_t offset(intptr_t addr) { return (uint32_t)(addr - base()); } 17 | };*/ 18 | 19 | static Dart_Isolate Load(LibAppInfo& libInfo); 20 | static void Unload(); 21 | 22 | private: 23 | DartLoader() = delete; 24 | }; 25 | 26 | -------------------------------------------------------------------------------- /blutter/src/DartStub.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "DartStub.h" 3 | -------------------------------------------------------------------------------- /blutter/src/DartStub.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "DartFnBase.h" 4 | #include 5 | 6 | // forward declaration 7 | class DartAbstractType; 8 | 9 | class DartStub : public DartFnBase 10 | { 11 | public: 12 | enum Kind : int32_t { 13 | #define DO(member, name) name ## Stub, 14 | OBJECT_STORE_STUB_CODE_LIST(DO) 15 | #undef DO 16 | BuildNonGenericMethodExtractorStub, 17 | BuildGenericMethodExtractorStub, 18 | #define DO(name) name ## VMStub, 19 | VM_STUB_CODE_LIST(DO) 20 | #undef DO 21 | SharedStub, 22 | AllocateUserObjectStub, 23 | TypeCheckStub, 24 | UnknownStub, 25 | }; 26 | 27 | DartStub(const dart::CodePtr ptr, Kind kind, uint64_t addr, int64_t size, std::string name) : 28 | DartFnBase(addr, size, std::move(name)), ptr(ptr), kind(kind) {} 29 | DartStub() = delete; 30 | DartStub(const DartStub&) = default; 31 | DartStub(DartStub&&) = delete; 32 | DartStub& operator=(const DartStub&) = delete; 33 | virtual ~DartStub() {} 34 | 35 | virtual std::string FullName() const { return name + "Stub"; } 36 | virtual bool IsStub() const { return true; } 37 | 38 | // some stub might contain multiple of duplicated stubs 39 | // this functionality is needed only for DartStub (no subclasses) 40 | DartStub* Split(uint64_t another_ep_addr) { 41 | const auto updateSize = another_ep_addr - ep_addr; 42 | auto newStub = new DartStub(ptr, kind, another_ep_addr, size - updateSize, name); 43 | size = updateSize; 44 | return newStub; 45 | } 46 | 47 | const dart::CodePtr ptr; 48 | const Kind kind; 49 | }; 50 | 51 | // only for non-predefined class 52 | // these stubs just call AllocateObjectStub with their types 53 | // and call AllocateObjectParameterizedStub if there is a type parameter 54 | class DartAllocateStub : public DartStub { 55 | public: 56 | DartAllocateStub(const dart::CodePtr ptr, uint64_t addr, int64_t size, uint32_t cid, std::string name) : 57 | DartStub(ptr, AllocateUserObjectStub, addr, size, std::move(name)), cid(cid) {} 58 | DartAllocateStub() = delete; 59 | DartAllocateStub(const DartAllocateStub&) = delete; 60 | DartAllocateStub(DartAllocateStub&&) = delete; 61 | DartAllocateStub& operator=(const DartAllocateStub&) = delete; 62 | 63 | //virtual std::string Name() const { return "Allocate" + name + "Stub"; } 64 | virtual std::string FullName() const { return "Allocate" + name + "Stub"; } 65 | virtual uint32_t ReturnType() const { return cid; } 66 | 67 | private: 68 | const uint32_t cid; // class id 69 | }; 70 | 71 | // TypeTestStub 72 | class DartTypeStub : public DartStub { 73 | public: 74 | DartTypeStub(const dart::CodePtr ptr, const uint64_t addr, int64_t size, const DartAbstractType& abType, std::string name) : 75 | DartStub(ptr, TypeCheckStub, addr, size, std::move(name)), abType(abType) {} 76 | DartTypeStub() = delete; 77 | DartTypeStub(const DartTypeStub&) = delete; 78 | DartTypeStub(DartTypeStub&&) = delete; 79 | DartTypeStub& operator=(const DartTypeStub&) = delete; 80 | 81 | //virtual std::string Name() const { return "IsType_" + name + "_Stub"; } 82 | virtual std::string FullName() const { return "IsType_" + name + "_Stub"; } 83 | 84 | // With the Record type in Dart 3.0, Test stub can be Type or RecordType 85 | // So, we have to use AbstractType 86 | const DartAbstractType& abType; 87 | }; 88 | 89 | class DartNativeFn : public DartFnBase 90 | { 91 | public: 92 | DartNativeFn(uint64_t addr, int64_t size, std::string name) : 93 | DartFnBase(addr, size, std::move(name)) {} 94 | DartNativeFn() = delete; 95 | DartNativeFn(const DartNativeFn&) = delete; 96 | DartNativeFn(DartNativeFn&&) = delete; 97 | DartNativeFn& operator=(const DartNativeFn&) = delete; 98 | }; -------------------------------------------------------------------------------- /blutter/src/DartThreadInfo.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "DartThreadInfo.h" 3 | 4 | static std::unordered_map threadOffsetNames; 5 | static std::unordered_map leafFunctionMap; 6 | 7 | static void initThreadOffsetNames() 8 | { 9 | #define DEFINE_OFFSET_INIT(type_name, member_name, expr, default_init_value) \ 10 | threadOffsetNames[dart::Thread::member_name##offset()] = #member_name; 11 | CACHED_CONSTANTS_LIST(DEFINE_OFFSET_INIT); 12 | #undef DEFINE_OFFSET_INIT 13 | for (auto& it : threadOffsetNames) { 14 | it.second.pop_back(); // remove trailing '_' 15 | } 16 | 17 | #define DEFINE_OFFSET_INIT(name) \ 18 | threadOffsetNames[dart::Thread::name##_entry_point_offset()] = #name; 19 | RUNTIME_ENTRY_LIST(DEFINE_OFFSET_INIT); 20 | #undef DEFINE_OFFSET_INIT 21 | 22 | #define DEFINE_OFFSET_INIT(returntype, name, ...) \ 23 | threadOffsetNames[dart::Thread::name##_entry_point_offset()] = #name; 24 | LEAF_RUNTIME_ENTRY_LIST(DEFINE_OFFSET_INIT); 25 | #undef DEFINE_OFFSET_INIT 26 | 27 | #ifdef CACHED_FUNCTION_ENTRY_POINTS_LIST 28 | #define DEFINE_OFFSET_INIT(name) \ 29 | threadOffsetNames[dart::Thread::name##_entry_point_offset()] = #name; 30 | CACHED_FUNCTION_ENTRY_POINTS_LIST(DEFINE_OFFSET_INIT); 31 | #undef DEFINE_OFFSET_INIT 32 | #endif // CACHED_FUNCTION_ENTRY_POINTS_LIST 33 | 34 | // generate from "generate_thread_offsets_cpp.py runtime/vm/thread.h" 35 | threadOffsetNames[dart::Thread::stack_limit_offset()] = "stack_limit"; 36 | threadOffsetNames[dart::Thread::saved_stack_limit_offset()] = "saved_stack_limit"; 37 | threadOffsetNames[dart::Thread::saved_shadow_call_stack_offset()] = "saved_shadow_call_stack"; 38 | threadOffsetNames[dart::Thread::write_barrier_mask_offset()] = "write_barrier_mask"; 39 | #if defined(DART_COMPRESSED_POINTERS) 40 | threadOffsetNames[dart::Thread::heap_base_offset()] = "heap_base"; 41 | #endif 42 | threadOffsetNames[dart::Thread::stack_overflow_flags_offset()] = "stack_overflow_flags"; 43 | threadOffsetNames[dart::Thread::safepoint_state_offset()] = "safepoint_state"; 44 | //threadOffsetNames[dart::Thread::callback_code_offset()] = "ffi_callback_code"; // removed in Dart 3.1.0 45 | //threadOffsetNames[dart::Thread::callback_stack_return_offset()] = "ffi_callback_stack_return"; // removed in Dart 3.1.0 46 | threadOffsetNames[dart::Thread::exit_through_ffi_offset()] = "exit_through_ffi"; 47 | threadOffsetNames[dart::Thread::api_top_scope_offset()] = "api_top_scope"; 48 | //threadOffsetNames[dart::Thread::double_truncate_round_supported_offset()] = "double_truncate_round_supported"; 49 | //threadOffsetNames[dart::Thread::tsan_utils_offset()] = "tsan_utils"; 50 | threadOffsetNames[dart::Thread::isolate_offset()] = "isolate"; 51 | threadOffsetNames[dart::Thread::isolate_group_offset()] = "isolate_group"; 52 | threadOffsetNames[dart::Thread::field_table_values_offset()] = "field_table_values"; 53 | threadOffsetNames[dart::Thread::dart_stream_offset()] = "dart_stream"; 54 | //threadOffsetNames[dart::Thread::service_extension_stream_offset()] = "service_extension_stream"; 55 | threadOffsetNames[dart::Thread::store_buffer_block_offset()] = "store_buffer_block"; 56 | #ifdef OLD_MARKING_STACK_BLOCK 57 | threadOffsetNames[dart::Thread::old_marking_stack_block_offset()] = "old_marking_stack_block_offset"; 58 | threadOffsetNames[dart::Thread::new_marking_stack_block_offset()] = "new_marking_stack_block_offset"; 59 | #else 60 | threadOffsetNames[dart::Thread::marking_stack_block_offset()] = "marking_stack_block"; 61 | #endif 62 | threadOffsetNames[dart::Thread::top_exit_frame_info_offset()] = "top_exit_frame_info"; 63 | //threadOffsetNames[dart::Thread::heap_offset()] = "heap"; // removed in Dart 3.1.0 64 | threadOffsetNames[dart::Thread::top_offset()] = "top"; 65 | threadOffsetNames[dart::Thread::end_offset()] = "end"; 66 | threadOffsetNames[dart::Thread::vm_tag_offset()] = "vm_tag"; 67 | //threadOffsetNames[dart::Thread::unboxed_runtime_arg_offset()] = "unboxed_runtime_arg"; 68 | threadOffsetNames[dart::Thread::global_object_pool_offset()] = "global_object_pool"; 69 | threadOffsetNames[dart::Thread::dispatch_table_array_offset()] = "dispatch_table_array"; 70 | threadOffsetNames[dart::Thread::active_exception_offset()] = "active_exception"; 71 | threadOffsetNames[dart::Thread::active_stacktrace_offset()] = "active_stacktrace"; 72 | threadOffsetNames[dart::Thread::resume_pc_offset()] = "resume_pc"; 73 | threadOffsetNames[dart::Thread::execution_state_offset()] = "execution_state"; 74 | //threadOffsetNames[dart::Thread::next_task_id_offset()] = "next_task_id"; 75 | //threadOffsetNames[dart::Thread::random_offset()] = "random"; 76 | 77 | #define DEFINE_LEFT_FN_INFO(returntype, name, ...) \ 78 | leafFunctionMap.emplace(std::make_pair(dart::Thread::name##_entry_point_offset(), LeafFunctionInfo{#returntype, #__VA_ARGS__})); 79 | LEAF_RUNTIME_ENTRY_LIST(DEFINE_LEFT_FN_INFO); 80 | #undef DEFINE_LEFT_FN_INFO 81 | } 82 | 83 | const std::string& GetThreadOffsetName(intptr_t offset) 84 | { 85 | if (threadOffsetNames.empty()) 86 | initThreadOffsetNames(); 87 | return threadOffsetNames[offset]; 88 | } 89 | 90 | intptr_t GetThreadMaxOffset() 91 | { 92 | if (threadOffsetNames.empty()) 93 | initThreadOffsetNames(); 94 | using pair_type = decltype(threadOffsetNames)::value_type; 95 | auto it = std::max_element(threadOffsetNames.begin(), threadOffsetNames.end(), [](const pair_type& o1, const pair_type& o2) 96 | { 97 | return o1.first < o2.first; 98 | }); 99 | return it->first; 100 | } 101 | 102 | const std::unordered_map& GetThreadOffsetsMap() 103 | { 104 | if (threadOffsetNames.empty()) 105 | initThreadOffsetNames(); 106 | return threadOffsetNames; 107 | } 108 | 109 | const LeafFunctionInfo* GetThreadLeafFunction(intptr_t offset) 110 | { 111 | if (threadOffsetNames.empty()) 112 | initThreadOffsetNames(); 113 | auto it = leafFunctionMap.find(offset); 114 | return it == leafFunctionMap.end() ? nullptr : &it->second; 115 | } 116 | -------------------------------------------------------------------------------- /blutter/src/DartThreadInfo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | const std::string& GetThreadOffsetName(intptr_t offset); 4 | intptr_t GetThreadMaxOffset(); 5 | 6 | // use for dumping all thread offsets 7 | const std::unordered_map& GetThreadOffsetsMap(); 8 | 9 | struct LeafFunctionInfo { 10 | std::string returnType; 11 | std::string params; 12 | }; 13 | const LeafFunctionInfo* GetThreadLeafFunction(intptr_t offset); 14 | -------------------------------------------------------------------------------- /blutter/src/DartTypes.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "DartTypes.h" 3 | #include "DartClass.h" 4 | #include 5 | #include 6 | 7 | const DartTypeArguments DartTypeArguments::Null; 8 | 9 | std::string DartTypeArguments::SubvectorName(int from_index, int len) const 10 | { 11 | ASSERT(len > 0); 12 | std::string txt; 13 | txt.reserve(len * 32); 14 | txt += "<"; 15 | for (auto i = 0; i < len; i++) { 16 | if (i > 0) { 17 | txt += ", "; 18 | } 19 | const size_t pos = from_index + i; 20 | if (pos < args.size()) { 21 | txt += args[pos]->ToString(); 22 | } 23 | else { 24 | txt += "dynamic"; 25 | } 26 | } 27 | txt += ">"; 28 | return txt; 29 | } 30 | 31 | std::string DartType::ToString() const 32 | { 33 | return ToString(true); 34 | } 35 | 36 | std::string DartType::ToString(bool showTypeArgs) const 37 | { 38 | std::string txt = cls.Name(); 39 | if (showTypeArgs) { 40 | txt += args->ToString(); 41 | } 42 | if (IsNullable() && cls.Id() != dart::kDynamicCid) { 43 | txt += "?"; 44 | } 45 | return txt; 46 | } 47 | 48 | #ifdef HAS_RECORD_TYPE 49 | std::string DartRecordType::ToString() const 50 | { 51 | std::string txt = "("; 52 | const intptr_t num_positional_fields = fieldTypes.size() - fieldNames.size(); 53 | for (auto i = 0; i < fieldTypes.size(); i++) { 54 | if (i != 0) { 55 | txt += ", "; 56 | } 57 | if (i == num_positional_fields) { 58 | txt += '{'; 59 | } 60 | txt += fieldTypes[i]->ToString(); 61 | if (i >= num_positional_fields) { 62 | txt += ' '; 63 | txt += fieldNames[i - num_positional_fields]; 64 | } 65 | } 66 | if (num_positional_fields < fieldTypes.size()) { 67 | txt += '}'; 68 | } 69 | txt += ')'; 70 | if (IsNullable()) 71 | txt += '?'; 72 | return txt; 73 | } 74 | #endif 75 | 76 | #ifdef HAS_TYPE_REF 77 | std::string DartTypeRef::ToString() const 78 | { 79 | return type.ToString(false); 80 | } 81 | #endif 82 | 83 | std::string DartTypeParameter::ToString() const 84 | { 85 | // CanonicalName might not start from 0 86 | std::string txt; 87 | if (base != 0) 88 | txt += (isClassTypeParam ? "C" : "F") + std::to_string(base); 89 | txt += (isClassTypeParam ? "X" : "Y") + std::to_string(index - base); 90 | if (IsNullable()) { 91 | txt += "?"; 92 | } 93 | if (bound->IsType()) { 94 | auto& cls = bound->AsType()->Class(); 95 | if (cls.Id() != dart::kInstanceCid) { 96 | #ifdef HAS_TYPE_REF 97 | txt += " bound " + bound->ToString(); 98 | #else 99 | txt += " bound " + bound->AsType()->ToString(false); 100 | #endif 101 | } 102 | } 103 | return txt; 104 | } 105 | 106 | std::string DartFunctionType::ToString() const 107 | { 108 | std::string txt; 109 | if (IsNullable()) { 110 | txt += "("; 111 | } 112 | 113 | if (!typeParams.empty()) { 114 | txt += "<"; 115 | txt += std::accumulate(typeParams.begin() + 1, typeParams.end(), typeParams.front()->ToString(), 116 | [](std::string x, const DartTypeParameter* y) { 117 | return x + ", " + y->ToString(); 118 | } 119 | ); 120 | txt += ">"; 121 | } 122 | 123 | txt += "("; // open for function arguments 124 | if (!params.empty()) { 125 | auto first = params[0].type->ToString(); 126 | if (hasImplicitParam) 127 | first += " this"; 128 | txt += std::accumulate(params.begin() + 1, params.end(), first, 129 | [](std::string x, const Parameter& y) { 130 | return x + ", " + y.type->ToString(); 131 | } 132 | ); 133 | } 134 | if (!optionalParams.empty()) { 135 | if (!params.empty()) { 136 | txt += ", "; 137 | } 138 | if (hasNamedParam) { 139 | txt += std::accumulate(optionalParams.begin() + 1, optionalParams.end(), optionalParams[0].type->ToString() + " " + optionalParams[0].name, 140 | [](std::string x, const Parameter& y) { 141 | return x + ", " + y.type->ToString() + " " + y.name; 142 | } 143 | ); 144 | } 145 | else { 146 | txt += std::accumulate(optionalParams.begin() + 1, optionalParams.end(), optionalParams[0].type->ToString(), 147 | [](std::string x, const Parameter& y) { 148 | return x + ", " + y.type->ToString(); 149 | } 150 | ); 151 | } 152 | } 153 | txt += ") => " + resultType->ToString(); // close for function arguments and return type 154 | if (IsNullable()) { 155 | txt += ")?"; 156 | } 157 | return txt; 158 | } 159 | 160 | DartType* DartTypeDb::FindOrAdd(dart::TypePtr typePtr) 161 | { 162 | auto ptr = (intptr_t)typePtr; 163 | if (typesMap.contains(ptr)) { 164 | return typesMap[ptr]->AsType(); 165 | } 166 | 167 | const auto& type = dart::Type::Handle(typePtr); 168 | auto cid = type.type_class_id(); 169 | auto dartCls = classes[type.type_class_id()]; 170 | ASSERT(dartCls); 171 | // Add it to DB first. the type arguments might be many recursive calls 172 | auto dartType = new DartType(type.IsNullable(), *dartCls, &DartTypeArguments::Null); 173 | typesMap[ptr] = dartType; 174 | 175 | dartType->args = FindOrAdd(type.arguments()); 176 | 177 | auto& types = typesByCid[dartCls->Id()]; 178 | // can use pointer comparison because we create only one type args for one pointer 179 | // so different pointer means another type 180 | auto it = std::find(types.begin(), types.end(), dartType); 181 | if (it == types.end()) { 182 | types.push_back(dartType); 183 | } 184 | 185 | return dartType; 186 | } 187 | 188 | #ifdef HAS_RECORD_TYPE 189 | DartRecordType* DartTypeDb::FindOrAdd(dart::RecordTypePtr recordTypePtr) 190 | { 191 | auto ptr = (intptr_t)recordTypePtr; 192 | if (typesMap.contains(ptr)) { 193 | return typesMap[ptr]->AsRecordType(); 194 | } 195 | 196 | auto thread = dart::Thread::Current(); 197 | auto zone = thread->zone(); 198 | 199 | const auto& recordType = dart::RecordType::Handle(zone, recordTypePtr); 200 | 201 | std::vector fieldNames; 202 | const auto& field_names = dart::Array::Handle(zone, recordType.GetFieldNames(thread)); 203 | auto& name = dart::String::Handle(zone); 204 | for (intptr_t i = 0; i < field_names.Length(); i++) { 205 | name ^= field_names.At(i); 206 | fieldNames.push_back(name.ToCString()); 207 | } 208 | 209 | auto dartRecordType = new DartRecordType(recordType.IsNullable(), std::move(fieldNames)); 210 | typesMap[ptr] = dartRecordType; 211 | 212 | const auto num_fields = recordType.NumFields(); 213 | for (intptr_t i = 0; i < num_fields; i++) { 214 | auto abTypePtr = recordType.FieldTypeAt(i); 215 | dartRecordType->fieldTypes.push_back(FindOrAdd(abTypePtr)); 216 | } 217 | 218 | return dartRecordType; 219 | } 220 | #endif 221 | 222 | DartTypeParameter* DartTypeDb::FindOrAdd(dart::TypeParameterPtr typeParamPtr) 223 | { 224 | auto ptr = (intptr_t)typeParamPtr; 225 | if (typesMap.contains(ptr)) { 226 | return typesMap[ptr]->AsTypeParameter(); 227 | } 228 | 229 | const auto& typeParam = dart::TypeParameter::Handle(typeParamPtr); 230 | // Add it to DB first. the bound might be many recursive calls 231 | auto dartTypeParam = new DartTypeParameter(typeParam.IsNullable(), (uint16_t)typeParam.base(), (uint16_t)typeParam.index(), typeParam.IsClassTypeParameter()); 232 | typesMap[ptr] = dartTypeParam; 233 | 234 | // Removing TypeRef also replaces TypeParameter.bound with TypeParameter.owner 235 | #ifdef HAS_TYPE_REF 236 | dartTypeParam->bound = FindOrAdd(typeParam.bound()); 237 | #else 238 | // in Dart 3.5, owner.ptr() might be NULL (zero value) 239 | // code in TypeParameter::bound() is changed between Dart version, do NOT copy it to here 240 | // only add the edge case here 241 | if ((intptr_t)typeParamPtr.untag()->owner() == 0 || (intptr_t)typeParam.parameterized_class() == 0) 242 | dartTypeParam->bound = FindOrAdd(dart::Isolate::Current()->group()->object_store()->nullable_object_type()); 243 | else 244 | dartTypeParam->bound = FindOrAdd(typeParam.bound()); 245 | #endif 246 | 247 | return dartTypeParam; 248 | } 249 | 250 | DartFunctionType* DartTypeDb::FindOrAdd(dart::FunctionTypePtr fnTypePtr) 251 | { 252 | auto ptr = (intptr_t)fnTypePtr; 253 | if (typesMap.contains(ptr)) { 254 | return typesMap[ptr]->AsFunctionType(); 255 | } 256 | 257 | const auto& fnType = dart::FunctionType::Handle(fnTypePtr); 258 | 259 | // handle function type parameters 260 | std::vector typeParams; 261 | if (fnType.NumTypeParameters() != 0) { 262 | const auto& type_params = dart::TypeParameters::Handle(fnType.type_parameters()); 263 | RELEASE_ASSERT(!type_params.IsNull()); 264 | const intptr_t num_type_params = type_params.Length(); 265 | const intptr_t base = fnType.NumParentTypeArguments(); 266 | const bool kIsClassTypeParameter = false; 267 | for (intptr_t i = 0; i < num_type_params; i++) { 268 | auto dartTypeParam = new DartTypeParameter(false, (uint16_t)base, (uint16_t)(base + i), kIsClassTypeParameter); 269 | dartTypeParam->bound = FindOrAdd(type_params.BoundAt(i)); 270 | // Note: there might be defaults to 271 | 272 | typeParams.push_back(dartTypeParam); 273 | } 274 | } 275 | 276 | // Add it to DB first. the bound might be many recursive calls 277 | auto dartFnType = new DartFunctionType(fnType.IsNullable(), fnType.num_implicit_parameters() != 0, fnType.HasOptionalNamedParameters(), std::move(typeParams)); 278 | typesMap[ptr] = dartFnType; 279 | 280 | dartFnType->resultType = FindOrAdd(fnType.result_type()); 281 | 282 | const auto num_fixed_params = fnType.num_fixed_parameters(); 283 | dartFnType->params.reserve(num_fixed_params); 284 | for (auto i = 0; i < num_fixed_params; i++) { 285 | dartFnType->params.emplace_back("", FindOrAdd(fnType.ParameterTypeAt(i))); 286 | } 287 | 288 | const auto num_opt_params = fnType.NumOptionalParameters(); 289 | if (num_opt_params > 0) { 290 | dartFnType->optionalParams.reserve(num_opt_params); 291 | const auto num_params = num_fixed_params + num_opt_params; 292 | auto& name = dart::String::Handle(); 293 | const char* tmp = ""; 294 | for (auto i = num_fixed_params; i < num_params; i++) { 295 | //dartFnType->params[i].type = FindOrAdd(fnType.ParameterTypeAt(i)); 296 | //dartFnType->params[i].name = name.ToCString(); 297 | if (dartFnType->hasNamedParam) { 298 | name = fnType.ParameterNameAt(i); 299 | tmp = name.ToCString(); 300 | } 301 | dartFnType->optionalParams.emplace_back(tmp, FindOrAdd(fnType.ParameterTypeAt(i)), nullptr); 302 | } 303 | } 304 | 305 | return dartFnType; 306 | } 307 | 308 | DartAbstractType* DartTypeDb::FindOrAdd(dart::AbstractTypePtr abTypePtr) 309 | { 310 | switch (abTypePtr.GetClassId()) { 311 | case dart::kTypeCid: 312 | return FindOrAdd(dart::Type::RawCast(abTypePtr)); 313 | #ifdef HAS_RECORD_TYPE 314 | case dart::kRecordTypeCid: 315 | return FindOrAdd(dart::RecordType::RawCast(abTypePtr)); 316 | #endif 317 | #ifdef HAS_TYPE_REF 318 | case dart::kTypeRefCid: { 319 | auto typePtr = dart::TypeRef::RawCast(abTypePtr)->untag()->type(); 320 | ASSERT(typePtr.GetClassId() == dart::kTypeCid); 321 | return new DartTypeRef(*FindOrAdd(dart::Type::RawCast(typePtr))); 322 | } 323 | #endif 324 | case dart::kTypeParameterCid: 325 | return FindOrAdd(dart::TypeParameter::RawCast(abTypePtr)); 326 | case dart::kFunctionTypeCid: 327 | return FindOrAdd(dart::FunctionType::RawCast(abTypePtr)); 328 | } 329 | //return nullptr; 330 | FATAL("Invalid abstract type"); 331 | } 332 | 333 | const DartTypeArguments* DartTypeDb::FindOrAdd(dart::TypeArgumentsPtr typeArgsPtr) 334 | { 335 | if ((intptr_t)typeArgsPtr == (intptr_t)dart::Object::null()) { 336 | return &DartTypeArguments::Null; 337 | } 338 | 339 | auto ptr = (intptr_t)typeArgsPtr; 340 | if (typeArgsMap.contains(ptr)) { 341 | return typeArgsMap[ptr]; 342 | } 343 | 344 | auto& typeArgs = dart::TypeArguments::Handle(typeArgsPtr); 345 | const auto typeArgsLen = typeArgs.Length(); 346 | std::vector args(typeArgsLen); 347 | 348 | // Add it to DB first. the bound might be many recursive calls 349 | auto dartTypeArgs = new DartTypeArguments(std::move(args)); 350 | typeArgsMap[ptr] = dartTypeArgs; 351 | 352 | for (auto i = 0; i < typeArgsLen; i++) { 353 | const auto abTypePtr = typeArgs.TypeAt(i); 354 | dartTypeArgs->args[i] = FindOrAdd(abTypePtr); 355 | } 356 | 357 | return dartTypeArgs; 358 | } 359 | 360 | DartType* DartTypeDb::FindOrAdd(DartClass& dartCls, const dart::TypeArgumentsPtr typeArgsPtr) 361 | { 362 | auto args = FindOrAdd(typeArgsPtr); 363 | auto& types = typesByCid[dartCls.Id()]; 364 | // we want to find same type args 365 | // so different pointer means another type 366 | DartType* dartType; 367 | auto it = std::find_if(types.begin(), types.end(), [&args](const DartType* dtype) { 368 | return dtype->args == args; 369 | }); 370 | if (it == types.end()) { 371 | dartType = new DartType{ false, dartCls, args }; 372 | types.push_back(dartType); 373 | } 374 | else { 375 | dartType = *it; 376 | } 377 | 378 | return dartType; 379 | } 380 | 381 | DartType* DartTypeDb::FindOrAdd(DartClass& dartCls, const dart::Instance& inst) 382 | { 383 | if (dartCls.NumTypeParameters() == 0) { 384 | // this class cannot be parameterized 385 | return dartCls.DeclarationType(); 386 | } 387 | 388 | // the instance always has type arguments because the class can be parameterized 389 | return FindOrAdd(dartCls, inst.GetTypeArguments()); 390 | } 391 | 392 | 393 | DartType* DartTypeDb::FindOrAdd(uint32_t cid, const DartTypeArguments* typeArgs) 394 | { 395 | for (auto type : typesByCid[cid]) { 396 | if (type->args == typeArgs) 397 | return type; 398 | } 399 | auto dartType = new DartType{ false, *classes[cid], typeArgs }; 400 | typesByCid[cid].push_back(dartType); 401 | return dartType; 402 | } 403 | 404 | DartType* DartTypeDb::Get(uint32_t cid) 405 | { 406 | auto dartCls = classes[cid]; 407 | ASSERT(dartCls->NumTypeParameters() == 0); 408 | return dartCls->DeclarationType(); 409 | } 410 | -------------------------------------------------------------------------------- /blutter/src/DartTypes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // forward declaration 4 | class DartClass; 5 | class DartType; 6 | // RecordType is added in Dart 3.0 7 | #ifdef HAS_RECORD_TYPE 8 | class DartRecordType; 9 | #endif 10 | // TypeRef is removed in Dart 3.1 11 | #ifdef HAS_TYPE_REF 12 | class DartTypeRef; 13 | #endif 14 | class DartTypeParameter; 15 | class DartFunctionType; 16 | class DartTypeDb; 17 | 18 | class DartAbstractType { 19 | public: 20 | enum Kind : uint8_t { 21 | TypeParam = 1, 22 | Type, 23 | #ifdef HAS_RECORD_TYPE 24 | RecordType, 25 | #endif 26 | #ifdef HAS_TYPE_REF 27 | TypeRef, // it is ref to Type, use it when self reference 28 | #endif 29 | FunctionType, 30 | }; 31 | 32 | explicit DartAbstractType(Kind kind, bool nullable) : kind(kind), nullable(nullable) {} 33 | DartAbstractType() = delete; 34 | 35 | bool IsNullable() const { return nullable; } 36 | 37 | virtual std::string ToString() const = 0; 38 | 39 | bool IsType() const { return kind == Kind::Type; } 40 | bool IsTypeParameter() const { return kind == Kind::TypeParam; } 41 | 42 | DartType* AsType() { 43 | ASSERT(kind == Type); 44 | return reinterpret_cast(this); 45 | } 46 | #ifdef HAS_RECORD_TYPE 47 | DartRecordType* AsRecordType() { 48 | ASSERT(kind == RecordType); 49 | return reinterpret_cast(this); 50 | } 51 | #endif 52 | #ifdef HAS_TYPE_REF 53 | DartTypeRef* AsTypeRef() { 54 | ASSERT(kind == TypeRef); 55 | return reinterpret_cast(this); 56 | } 57 | #endif 58 | DartTypeParameter* AsTypeParameter() { 59 | ASSERT(kind == TypeParam); 60 | return reinterpret_cast(this); 61 | } 62 | DartFunctionType* AsFunctionType() { 63 | ASSERT(kind == FunctionType); 64 | return reinterpret_cast(this); 65 | } 66 | 67 | protected: 68 | Kind kind; 69 | bool nullable; 70 | }; 71 | 72 | class DartTypeArguments { 73 | public: 74 | explicit DartTypeArguments(std::vector args) : args(std::move(args)) {} 75 | explicit DartTypeArguments() {} 76 | 77 | std::string SubvectorName(int from_index, int len) const; 78 | std::string ToString() const { return args.empty() ? std::string() : SubvectorName(0, (int)args.size()); } 79 | size_t Length() const { return args.size(); } 80 | 81 | static const DartTypeArguments Null; 82 | 83 | protected: 84 | std::vector args; 85 | 86 | friend class DartTypeDb; 87 | }; 88 | 89 | class DartType : public DartAbstractType 90 | { 91 | public: 92 | DartType() = delete; 93 | const DartTypeArguments& Arguments() const { return *args; } 94 | const DartClass& Class() const { return cls; } 95 | 96 | virtual std::string ToString() const; 97 | std::string ToString(bool showTypeArgs) const; 98 | 99 | protected: 100 | explicit DartType(bool nullable, DartClass& cls, const DartTypeArguments* args) : DartAbstractType(Kind::Type, nullable), cls(cls), args(args) {} 101 | // incomplete initialization. we need it to prevent infinite loop when creating a new type 102 | //explicit DartType(bool nullable, DartClass& cls) : DartAbstractType(Kind::Type, nullable), cls(cls), args(nullptr) {} 103 | 104 | DartClass& cls; 105 | const DartTypeArguments* args; 106 | 107 | friend class DartTypeDb; 108 | friend class DartApp; 109 | }; 110 | 111 | #ifdef HAS_RECORD_TYPE 112 | class DartRecordType : public DartAbstractType 113 | { 114 | public: 115 | DartRecordType() = delete; 116 | 117 | virtual std::string ToString() const; 118 | 119 | protected: 120 | // incomplete initialization. we need it to prevent infinite loop when creating a new type 121 | explicit DartRecordType(bool nullable, std::vector fieldNames) : DartAbstractType(Kind::RecordType, nullable), fieldNames(std::move(fieldNames)) {} 122 | 123 | std::vector fieldTypes; 124 | std::vector fieldNames; 125 | 126 | friend class DartTypeDb; 127 | }; 128 | #endif 129 | 130 | #ifdef HAS_TYPE_REF 131 | class DartTypeRef : public DartAbstractType 132 | { 133 | public: 134 | DartTypeRef() = delete; 135 | 136 | virtual std::string ToString() const; 137 | 138 | protected: 139 | // incomplete initialization. we need it to prevent infinite loop when creating a new type 140 | explicit DartTypeRef(DartType& type) : DartAbstractType(Kind::TypeRef, false), type(type) {} 141 | 142 | DartType& type; 143 | 144 | friend class DartTypeDb; 145 | }; 146 | #endif 147 | 148 | class DartTypeParameter : public DartAbstractType { 149 | public: 150 | DartTypeParameter() = delete; 151 | 152 | virtual std::string ToString() const; 153 | 154 | protected: 155 | // incomplete initialization. we need it to prevent infinite loop when creating a new type 156 | explicit DartTypeParameter(bool nullable, uint16_t base, uint16_t index, bool isClassTypeParam) 157 | : DartAbstractType(Kind::TypeParam, nullable), base(base), index(index), isClassTypeParam(isClassTypeParam), bound(nullptr) {} 158 | 159 | // in UntaggedTypeParameter, base and index use uint16_t 160 | uint16_t base; 161 | uint16_t index; 162 | bool isClassTypeParam; 163 | DartAbstractType* bound; 164 | 165 | friend class DartTypeDb; 166 | }; 167 | 168 | class DartFunctionType : public DartAbstractType { 169 | public: 170 | DartFunctionType() = delete; 171 | 172 | virtual std::string ToString() const; 173 | 174 | // Note: positional parameter names are removed in AOT 175 | struct Parameter { 176 | Parameter(std::string name, DartAbstractType* type) : name(std::move(name)), type(type) {} 177 | 178 | std::string name; 179 | DartAbstractType* type; 180 | }; 181 | struct OptionalParameter : public Parameter { 182 | OptionalParameter(std::string name, DartAbstractType* type, void* defaultValue) : Parameter(std::move(name), type), defaultValue(defaultValue) {} 183 | 184 | // Note: parameter default value is compiled into ObjectPool and code 185 | void* defaultValue; 186 | }; 187 | 188 | protected: 189 | // incomplete initialization. we need it to prevent infinite loop when creating a new type 190 | explicit DartFunctionType(bool nullable, bool hasImplicitParam, bool hasNamedParam, std::vector typeParams) 191 | : DartAbstractType(Kind::FunctionType, nullable), hasImplicitParam(hasImplicitParam), hasNamedParam(hasNamedParam), resultType(nullptr), typeParams(std::move(typeParams)) {} 192 | 193 | bool hasImplicitParam; // this paramter for object method (so can be only 0 or 1) 194 | // Function parameter cannot contain both optional positional parameters and optional named parameters 195 | // if hasNamedParam is true, optionalParams are named pareters, else positional parameters 196 | bool hasNamedParam; 197 | 198 | std::vector typeParams; // function type parameters in "<>" 199 | DartAbstractType *resultType; // function return type 200 | 201 | std::vector params; // fixed function parameters 202 | std::vector optionalParams; // function parameters in "[]" or "{}" 203 | 204 | friend class DartTypeDb; 205 | }; 206 | 207 | class DartTypeDb { 208 | public: 209 | //~DartTypeDb(); 210 | 211 | DartType* Get(uint32_t cid); 212 | 213 | DartType* FindOrAdd(dart::TypePtr typePtr); 214 | #ifdef HAS_RECORD_TYPE 215 | DartRecordType* FindOrAdd(dart::RecordTypePtr recordTypePtr); 216 | #endif 217 | DartTypeParameter* FindOrAdd(dart::TypeParameterPtr typeParamPtr); 218 | DartFunctionType* FindOrAdd(dart::FunctionTypePtr fnTypePtr); 219 | DartAbstractType* FindOrAdd(dart::AbstractTypePtr abTypePtr); 220 | 221 | const DartTypeArguments* FindOrAdd(dart::TypeArgumentsPtr typeArgsPtr); 222 | 223 | DartType* FindOrAdd(DartClass& dartCls, const dart::TypeArgumentsPtr typeArgsPtr); 224 | DartType* FindOrAdd(DartClass& dartCls, const dart::Instance& inst); 225 | DartType* FindOrAdd(uint32_t cid, const DartTypeArguments* typeArgs); 226 | 227 | protected: 228 | DartTypeDb(std::vector& classes) : classes(classes) { typesByCid.resize(classes.size()); } 229 | 230 | std::unordered_map typesMap; // map dart ptr to the type 231 | std::vector> typesByCid; 232 | 233 | // Normally, type arguments are all read-only. no duplicated type arguments in Dart snapshot 234 | // cache it here for quick lookup 235 | std::unordered_map typeArgsMap; 236 | 237 | std::vector& classes; 238 | 239 | friend class DartApp; 240 | }; 241 | -------------------------------------------------------------------------------- /blutter/src/Disassembler.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Disassembler.h" 3 | 4 | AsmInstructions Disassembler::Disasm(const uint8_t* code, size_t code_size, uint64_t address, size_t max_count) 5 | { 6 | cs_insn* insns = NULL; 7 | size_t insn_cnt = 0; 8 | 9 | insn_cnt = cs_disasm(cshandle, code, code_size, address, max_count, &insns); 10 | 11 | return AsmInstructions(insns, insn_cnt); 12 | } -------------------------------------------------------------------------------- /blutter/src/Disassembler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #ifdef TARGET_ARCH_ARM64 5 | #include "Disassembler_arm64.h" 6 | #endif 7 | 8 | 9 | // master of disassmbled instructions from capstone 10 | // do not allow copy because this class must free the instructions 11 | class AsmInstructions { 12 | cs_insn* insns; 13 | size_t count; 14 | 15 | AsmInstructions(cs_insn* insns, size_t count) : insns(insns), count(count) {} 16 | public: 17 | AsmInstructions() = delete; 18 | AsmInstructions(const AsmInstructions&) = delete; 19 | AsmInstructions(AsmInstructions&& rhs) noexcept : insns(std::exchange(rhs.insns, nullptr)), count(std::exchange(rhs.count, 0)) {} 20 | AsmInstructions& operator=(const AsmInstructions&) = delete; 21 | ~AsmInstructions() { if (insns) cs_free(insns, count); } 22 | 23 | cs_insn* Insns() { return insns; } 24 | size_t Count() const { return count; } 25 | AsmInstruction First() { return AsmInstruction(insns); } 26 | AsmInstruction Last() { return AsmInstruction(&insns[count - 1]); } 27 | cs_insn* FirstPtr() { return insns; } 28 | cs_insn* LastPtr() { return &insns[count - 1]; } 29 | bool IsFirst(AsmInstruction& insn) { return insn.address() == insns->address; } 30 | 31 | AsmInstruction At(size_t i) { return AsmInstruction(insns + i); } 32 | cs_insn* Ptr(size_t i) { return &insns[i]; } 33 | size_t AtIndex(uint64_t addr) { 34 | ASSERT(addr > insns->address); 35 | // estimate index (normally 4 bytes per instruction for arm64) 36 | auto idx = (addr - insns->address) / 4; 37 | ASSERT(idx < count); 38 | while (idx < count && insns[idx].address < addr) 39 | ++idx; 40 | return idx; 41 | } 42 | AsmInstruction AtAddr(uint64_t addr) { 43 | ASSERT(addr > insns->address); 44 | // estimate index (normally 4 bytes per instruction for arm64) 45 | auto idx = (addr - insns->address) / 4; 46 | ASSERT(idx < count); 47 | auto insn = &insns[idx]; 48 | while (insn->address < addr) 49 | ++insn; 50 | ASSERT(insn->address == addr); 51 | return AsmInstruction(insn); 52 | } 53 | 54 | friend class Disassembler; 55 | friend class Instruction; 56 | }; 57 | 58 | // partial instructions from Instructions object. 59 | // this class object are safe to copy/move because there is no freeing when destructor is called 60 | class AsmBlock { 61 | cs_insn* insns; 62 | cs_insn* last_insn; 63 | 64 | public: 65 | explicit AsmBlock() : insns(nullptr), last_insn(nullptr) {} 66 | explicit AsmBlock(cs_insn* insns, cs_insn* last_insn) : insns(insns), last_insn(last_insn) {} 67 | bool isValid() { return insns != nullptr; } 68 | AsmInstruction first() { return AsmInstruction(insns); } 69 | AsmInstruction last() { return AsmInstruction(last_insn); } 70 | cs_insn* first_ptr() { return insns; } 71 | cs_insn* last_ptr() { return last_insn; } 72 | AsmInstruction at(size_t i) { return AsmInstruction(insns + i); } 73 | cs_insn* ptr(size_t i) { return &insns[i]; } 74 | bool isLast(cs_insn* insn) { return insn == last_insn; } 75 | bool isAfter(cs_insn* insn) { return insn->address > last_insn->address; } 76 | uint64_t Address() { return insns->address; } 77 | uint64_t AddressEnd() { return last_insn->address + last_insn->size; } 78 | 79 | friend class Instruction; 80 | }; 81 | 82 | class Disassembler 83 | { 84 | public: 85 | Disassembler(bool hasDetail = true); 86 | ~Disassembler() { cs_close(&cshandle); } 87 | Disassembler(const Disassembler&) = delete; 88 | Disassembler(Disassembler&&) = delete; 89 | Disassembler& operator=(const Disassembler&) = delete; 90 | 91 | AsmInstructions Disasm(const uint8_t* code, size_t code_size, uint64_t address, size_t max_count = 0); 92 | const char* GetRegName(arm64_reg reg) { return cs_reg_name(cshandle, reg); } 93 | 94 | private: 95 | csh cshandle; 96 | }; 97 | 98 | -------------------------------------------------------------------------------- /blutter/src/Disassembler_arm64.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Disassembler.h" 3 | 4 | namespace A64 { 5 | const char* Register::RegisterNames[] = { 6 | "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", 7 | "r10", "r11", "r12", "r13", "r14", "rSP", "r16", "r17", "r18", "r19", 8 | "r20", "r21", "rNULL", "r23", "r24", "r25", "rTHR", "rPP", "rHEAP", "rFP", 9 | "r30", "r31", 10 | "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", 11 | "d10", "d11", "d12", "d13", "d14", "d15", "d16", "d17", "d18", "d19", 12 | "d20", "d21", "d22", "d23", "d24", "d25", "d26", "d27", "d28", "d29", "d30", "d31", 13 | "rCSP", "rZR", "NZCV", 14 | }; 15 | } 16 | 17 | // singleton of capstone handle. use for global resolve register name of aarch64 18 | static csh g_cshandle; 19 | const char* GetCsRegisterName(arm64_reg reg) 20 | { 21 | if (g_cshandle == 0) 22 | cs_open(CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN, &g_cshandle); 23 | return cs_reg_name(g_cshandle, reg); 24 | } 25 | 26 | Disassembler::Disassembler(bool hasDetail) 27 | { 28 | if (cs_open(CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN, &cshandle) != CS_ERR_OK) 29 | throw std::runtime_error("Cannot open capstone engine"); 30 | 31 | if (hasDetail) 32 | cs_option(cshandle, CS_OPT_DETAIL, CS_OPT_ON); 33 | } 34 | -------------------------------------------------------------------------------- /blutter/src/ElfHelper.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "ElfHelper.h" 3 | PRAGMA_WARNING(push, 0) 4 | #include 5 | #if defined(DART_TARGET_OS_MACOS) 6 | // old dart version has no mach_o.h 7 | //#include 8 | #endif 9 | PRAGMA_WARNING(pop) 10 | #include 11 | #include 12 | #if defined(_WIN32) || defined(WIN32) 13 | #define WIN32_LEAN_AND_MEAN 14 | #include 15 | #else 16 | #include 17 | #include 18 | //#include 19 | #include 20 | #endif // #if defined(_WIN32) || defined(WIN32) 21 | 22 | struct ElfIdent { 23 | uint8_t ei_magic[4]; 24 | uint8_t ei_class; 25 | uint8_t ei_data; 26 | uint8_t ei_version; 27 | uint8_t ei_osabi; 28 | uint8_t ei_abiversion; 29 | uint8_t pad1[7]; 30 | }; 31 | 32 | using namespace dart::elf; 33 | 34 | #ifdef _WIN32 35 | static void* load_map_file(const char* path) 36 | { 37 | HANDLE hFile = CreateFileA(path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 38 | if (hFile == INVALID_HANDLE_VALUE) { 39 | printf("\nCannot find %s\n", path); 40 | return NULL; 41 | } 42 | 43 | // because Dart API requires only snapshot buffer addresses (no relative access across snapshot), 44 | // so we can just mapping a whole file and find address of snapshots 45 | HANDLE hMapFile = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); 46 | if (hMapFile == INVALID_HANDLE_VALUE) 47 | return NULL; 48 | 49 | // need RW because dart initialization need writing data in BSS 50 | void* mem = MapViewOfFile(hMapFile, FILE_MAP_COPY, 0, 0, 0); 51 | CloseHandle(hMapFile); 52 | 53 | CloseHandle(hFile); 54 | return mem; 55 | } 56 | #else 57 | static void* load_map_file(const char* path) 58 | { 59 | // need RW because dart initialization need writing data in BSS 60 | int fd = open(path, O_RDONLY); 61 | struct stat st; 62 | 63 | fstat(fd, &st); 64 | void* mem = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); 65 | 66 | close(fd); 67 | return mem; 68 | } 69 | #endif 70 | 71 | LibAppInfo ElfHelper::findSnapshots(const uint8_t* elf) 72 | { 73 | const auto* hdr = (const ElfHeader*)elf; 74 | if (hdr->section_table_entry_size != sizeof(SectionHeader)) 75 | throw std::invalid_argument("ELF: Invalid section entry size"); 76 | 77 | const auto* section = (SectionHeader*)(elf + hdr->section_table_offset); 78 | const auto sh_num = hdr->num_section_headers; 79 | 80 | // find .dynstr and .dynsym sections, so we can map the section names 81 | const char* dynstr = nullptr; 82 | const Symbol* dynsym = nullptr; 83 | const Symbol* dynsym_end = nullptr; 84 | for (uint16_t i = 0; i < sh_num; i++, section++) { 85 | if (section->type == SectionHeaderType::SHT_STRTAB && dynstr == nullptr) { 86 | // we want only .dynstr for .dynsym 87 | const char* strtab = (const char*)elf + section->file_offset; 88 | const char* last = strtab + section->file_size; 89 | const char* s_first = kVmSnapshotDataAsmSymbol; 90 | const char* s_last = s_first + strlen(kVmSnapshotDataAsmSymbol) + 1; 91 | //if (memmem(strtab, section->s_size, kVmSnapshotDataAsmSymbol, strlen(kVmSnapshotDataAsmSymbol))) { 92 | if (std::search(strtab, last, s_first, s_last) != last) { 93 | // found it 94 | dynstr = strtab; 95 | } 96 | } 97 | if (section->type == SectionHeaderType::SHT_DYNSYM) { 98 | if (section->entry_size != sizeof(Symbol)) 99 | throw std::invalid_argument("ELF: Invalid DYNSYM entry size"); 100 | dynsym = (Symbol*)(elf + section->file_offset); 101 | dynsym_end = (Symbol*)(elf + section->file_offset + section->file_size); 102 | } 103 | if (dynsym != nullptr && dynstr != nullptr) 104 | break; 105 | } 106 | 107 | // find the required symbol addresses 108 | const uint8_t* vm_snapshot_data = nullptr; 109 | const uint8_t* vm_snapshot_instructions = nullptr; 110 | const uint8_t* isolate_snapshot_data = nullptr; 111 | const uint8_t* isolate_snapshot_instructions = nullptr; 112 | for (; dynsym < dynsym_end; dynsym++) { 113 | if (dynsym->info == 0) 114 | continue; 115 | 116 | const char* name = dynstr + dynsym->name; 117 | // Note: sym_size is no needed for dart VM (its blob contains size) 118 | if (strcmp(name, kVmSnapshotDataAsmSymbol) == 0) { 119 | vm_snapshot_data = elf + dynsym->value; 120 | } 121 | else if (strcmp(name, kVmSnapshotInstructionsAsmSymbol) == 0) { 122 | vm_snapshot_instructions = elf + dynsym->value; 123 | } 124 | else if (strcmp(name, kIsolateSnapshotDataAsmSymbol) == 0) { 125 | isolate_snapshot_data = elf + dynsym->value; 126 | } 127 | else if (strcmp(name, kIsolateSnapshotInstructionsAsmSymbol) == 0) { 128 | isolate_snapshot_instructions = elf + dynsym->value; 129 | } 130 | } 131 | 132 | if (vm_snapshot_data == nullptr) 133 | throw std::invalid_argument("ELF: Cannot find Dart VM Snapshot Data"); 134 | if (vm_snapshot_instructions == nullptr) 135 | throw std::invalid_argument("ELF: Cannot find Dart VM Snapshot Instructions"); 136 | if (isolate_snapshot_data == nullptr) 137 | throw std::invalid_argument("ELF: Cannot find Dart Isolate Snapshot Data"); 138 | if (isolate_snapshot_instructions == nullptr) 139 | throw std::invalid_argument("ELF: Cannot find Dart Isolate Snapshot Instructions"); 140 | 141 | return LibAppInfo{ 142 | .lib = elf, 143 | .vm_snapshot_data = vm_snapshot_data, 144 | .vm_snapshot_instructions = vm_snapshot_instructions, 145 | .isolate_snapshot_data = isolate_snapshot_data, 146 | .isolate_snapshot_instructions = isolate_snapshot_instructions, 147 | }; 148 | } 149 | 150 | LibAppInfo ElfHelper::MapLibAppSo(const char* path) 151 | { 152 | void* lib = load_map_file(path); 153 | // quick and dirty parsing ELF to get symbol addresses 154 | uint8_t* elf = (uint8_t*)(lib); 155 | #if defined(DART_TARGET_OS_MACOS) 156 | // Note: only new dart version getting snapshots from load command 157 | // <=2.17, use EXPORT name 158 | // <= v2.18, load from sub SEGMENT_64, named "__CUSTOM" and section named "__dart_app_snap" 159 | // >= 2.19, LC_NOTE command is used 160 | auto header = (dart::mach_o::mach_header_64*)lib; 161 | switch (header->magic) { 162 | case dart::mach_o::MH_MAGIC: 163 | case dart::mach_o::MH_CIGAM: 164 | throw std::invalid_argument("Mach-O: Support only 64 bits"); 165 | case dart::mach_o::MH_CIGAM_64: 166 | throw std::invalid_argument("Mach-O: Expected a host endian header"); 167 | case dart::mach_o::MH_MAGIC_64: 168 | return size >= sizeof(mach_o::mach_header_64); 169 | default: 170 | throw std::invalid_argument("Mach-O: Invalid magic header"); 171 | } 172 | #else 173 | const auto* hdr = (ElfHeader*)elf; 174 | const auto* ident = (ElfIdent*)hdr->ident; 175 | if (memcmp(ident->ei_magic, "\x7f" "ELF", 4) != 0) 176 | throw std::invalid_argument("ELF: Invalid magic header"); // need ELF file 177 | if (ident->ei_data != 1) 178 | throw std::invalid_argument("ELF: Support only little endian"); // expect little-endian 179 | 180 | if (ident->ei_class != ELFCLASS64) { // 1 is 32 bits, 2 is 64 bits 181 | throw std::invalid_argument("ELF: Support only 64 bits"); // support only 64 bits 182 | } 183 | // expected e_machine 184 | // 3: x86, 0x28: ARM 185 | // 0x3e: x86-64, 0xB7: Aarch64 186 | // EM_386, EM_ARM, EM_X86_64, EM_AARCH64 187 | //hdr->e_machine; 188 | #endif 189 | 190 | return findSnapshots(elf); 191 | } 192 | -------------------------------------------------------------------------------- /blutter/src/ElfHelper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | struct LibAppInfo { 5 | const void* lib; 6 | const uint8_t* vm_snapshot_data; 7 | const uint8_t* vm_snapshot_instructions; 8 | const uint8_t* isolate_snapshot_data; 9 | const uint8_t* isolate_snapshot_instructions; 10 | }; 11 | 12 | class ElfHelper final 13 | { 14 | public: 15 | static LibAppInfo findSnapshots(const uint8_t* elf); 16 | static LibAppInfo MapLibAppSo(const char* path); 17 | 18 | private: 19 | ElfHelper() = delete; 20 | }; 21 | -------------------------------------------------------------------------------- /blutter/src/FridaWriter.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "FridaWriter.h" 3 | #include 4 | #include 5 | #include "Util.h" 6 | 7 | #ifndef FRIDA_TEMPLATE_DIR 8 | #define FRIDA_TEMPLATE_DIR "scripts" 9 | #endif 10 | 11 | void FridaWriter::Create(const char* filename) 12 | { 13 | std::filesystem::copy_file(FRIDA_TEMPLATE_DIR "/frida.template.js", filename, std::filesystem::copy_options::overwrite_existing); 14 | 15 | std::ofstream of(filename, std::ios_base::app); 16 | 17 | of << "const ClassIdTagPos = " << kUntaggedObjectClassIdTagPos << ";\n"; 18 | of << fmt::format("const ClassIdTagMask = {:#x};\n", (1 << dart::UntaggedObject::kClassIdTagSize) - 1); 19 | 20 | of << "const NumPredefinedCids = " << dart::kNumPredefinedCids << ";\n"; 21 | of << "const CidObject = " << dart::kInstanceCid << ";\n"; 22 | of << "const CidNull = " << dart::kNullCid << ";\n"; 23 | of << "const CidSmi = " << dart::kSmiCid << ";\n"; 24 | of << "const CidMint = " << dart::kMintCid << ";\n"; 25 | of << "const CidDouble = " << dart::kDoubleCid << ";\n"; 26 | of << "const CidBool = " << dart::kBoolCid << ";\n"; 27 | of << "const CidString = " << dart::kOneByteStringCid << ";\n"; 28 | of << "const CidTwoByteString = " << dart::kTwoByteStringCid << ";\n"; 29 | of << "const CidArray = " << dart::kArrayCid << ";\n"; 30 | of << "const CidGrowableArray = " << dart::kGrowableObjectArrayCid << ";\n"; 31 | of << "const CidSet = " << dart::kSetCid << ";\n"; 32 | of << "const CidMap = " << dart::kMapCid << ";\n"; 33 | of << "const CidClosure = " << dart::kClosureCid << ";\n"; 34 | of << "const CidUint8Array = " << dart::kTypedDataUint8ArrayCid << ";\n"; 35 | of << "const CidInt8Array = " << dart::kTypedDataInt8ArrayCid << ";\n"; 36 | of << "const CidUint16Array = " << dart::kTypedDataUint16ArrayCid << ";\n"; 37 | of << "const CidInt16Array = " << dart::kTypedDataInt16ArrayCid << ";\n"; 38 | of << "const CidUint32Array = " << dart::kTypedDataUint32ArrayCid << ";\n"; 39 | of << "const CidInt32Array = " << dart::kTypedDataInt32ArrayCid << ";\n"; 40 | of << "const CidUint64Array = " << dart::kTypedDataUint64ArrayCid << ";\n"; 41 | of << "const CidInt64Array = " << dart::kTypedDataInt64ArrayCid << ";\n"; 42 | 43 | of << "const Classes = [\n"; 44 | for (auto dartCls : app.classes) { 45 | if (!dartCls) { 46 | of << "null,\n"; 47 | continue; 48 | } 49 | 50 | if (dartCls->Id() < dart::kNumPredefinedCids) { 51 | switch (dartCls->Id()) { 52 | case dart::kBoolCid: 53 | of << "{id:" << dartCls->Id() << ","; 54 | of << "name:\"bool\","; 55 | // the bool in Dart use only 2 Immutable objects (true and false) 56 | //of << "tptr:" << (uint64_t)dart::Bool::True().ptr() - app.heap_base() << ","; 57 | //of << "fptr:" << (uint64_t)dart::Bool::False().ptr() - app.heap_base() << ","; 58 | // value_ offset in raw_object.h is inaccessible 59 | of << "valOffset:" << AOT_Instance_InstanceSize << "},\n"; 60 | break; 61 | case dart::kMintCid: 62 | of << "{id:" << dartCls->Id() << ","; 63 | of << "name:\"int\","; 64 | of << "valOffset:" << AOT_Mint_value_offset << "},\n"; 65 | break; 66 | case dart::kDoubleCid: 67 | of << "{id:" << dartCls->Id() << ","; 68 | of << "name:\"double\","; 69 | of << "valOffset:" << AOT_Double_value_offset << "},\n"; 70 | break; 71 | case dart::kOneByteStringCid: 72 | of << "{id:" << dartCls->Id() << ","; 73 | of << "name:\"String\","; 74 | of << "lenOffset:" << AOT_String_length_offset << ","; 75 | of << "dataOffset:" << AOT_OneByteString_data_offset << "},\n"; 76 | break; 77 | case dart::kTwoByteStringCid: 78 | of << "{id:" << dartCls->Id() << ","; 79 | of << "name:\"TwoByteString\","; 80 | of << "lenOffset:" << AOT_String_length_offset << ","; 81 | of << "dataOffset:" << AOT_TwoByteString_data_offset << "},\n"; 82 | break; 83 | case dart::kArrayCid: 84 | of << "{id:" << dartCls->Id() << ","; 85 | of << "name:\"List\","; 86 | //dart::Array::kBytesPerElement is same as a compressed pointer size 87 | of << "lenOffset:" << AOT_Array_length_offset << ","; 88 | of << "dataOffset:" << AOT_Array_data_offset << ","; 89 | of << "typeOffset:" << AOT_Array_type_arguments_offset << "},\n"; 90 | break; 91 | case dart::kGrowableObjectArrayCid: 92 | of << "{id:" << dartCls->Id() << ","; 93 | of << "name:\"GrowableList\","; 94 | of << "lenOffset:" << AOT_GrowableObjectArray_length_offset << ","; 95 | of << "dataOffset:" << AOT_GrowableObjectArray_data_offset << ","; 96 | of << "typeOffset:" << AOT_GrowableObjectArray_type_arguments_offset << "},\n"; 97 | break; 98 | case dart::kSetCid: 99 | of << "{id:" << dartCls->Id() << ","; 100 | of << "name:\"Set\","; 101 | of << "usedOffset:" << AOT_LinkedHashBase_used_data_offset << ","; 102 | of << "delOffset:" << AOT_LinkedHashBase_deleted_keys_offset << ","; 103 | of << "dataOffset:" << AOT_LinkedHashBase_data_offset << ","; 104 | of << "typeOffset:" << AOT_LinkedHashBase_type_arguments_offset << "},\n"; 105 | break; 106 | case dart::kMapCid: 107 | of << "{id:" << dartCls->Id() << ","; 108 | of << "name:\"Map\","; 109 | of << "usedOffset:" << AOT_LinkedHashBase_used_data_offset << ","; 110 | of << "delOffset:" << AOT_LinkedHashBase_deleted_keys_offset << ","; 111 | of << "dataOffset:" << AOT_LinkedHashBase_data_offset << ","; 112 | of << "typeOffset:" << AOT_LinkedHashBase_type_arguments_offset << "},\n"; 113 | break; 114 | case dart::kClosureCid: 115 | of << "{id:" << dartCls->Id() << ","; 116 | of << "name:\"Closure\","; 117 | of << "fnOffset:" << AOT_Closure_function_offset << ","; 118 | of << "contextOffset:" << AOT_Closure_context_offset << ","; 119 | of << "epOffset:" << AOT_Closure_entry_point_offset << "},\n"; 120 | break; 121 | case dart::kTypedDataUint8ArrayCid: 122 | case dart::kTypedDataUint16ArrayCid: 123 | case dart::kTypedDataUint32ArrayCid: 124 | case dart::kTypedDataUint64ArrayCid: 125 | case dart::kTypedDataInt8ArrayCid: 126 | case dart::kTypedDataInt16ArrayCid: 127 | case dart::kTypedDataInt32ArrayCid: 128 | case dart::kTypedDataInt64ArrayCid: 129 | of << "{id:" << dartCls->Id() << ","; 130 | of << "name:" << Util::Quote(dartCls->Name()) << ","; 131 | of << "lenOffset:" << AOT_TypedDataBase_length_offset << ","; 132 | // current version name is "AOT_TypedData_payload_offset" but old version name is "AOT_TypedData_data_offset" 133 | // function from UntaggedTypedData is always same 134 | of << "dataOffset:" << dart::UntaggedTypedData::payload_offset() << "},\n"; 135 | break; 136 | case dart::kInstanceCid: 137 | of << "{id:" << dartCls->Id() << ","; 138 | of << "name:\"Object\","; 139 | of << "size:" << AOT_Instance_InstanceSize << "},\n"; 140 | break; 141 | default: 142 | of << "{id:" << dartCls->Id() << ","; 143 | of << "name:" << Util::Quote(dartCls->Name()) << "},\n"; 144 | break; 145 | } 146 | } 147 | else { 148 | of << "{"; 149 | of << "id:" << dartCls->Id() << ","; 150 | of << "name:" << Util::Quote(dartCls->Name()) << ","; 151 | of << "fbitmap:" << dartCls->FieldBitmap() << ","; 152 | of << "sid:" << dartCls->Parent()->Id() << ","; 153 | of << "size:" << dartCls->Size() << ","; 154 | of << "argOffset:" << dartCls->TypeArgumentOffset();// << ","; 155 | of << "},\n"; 156 | } 157 | } 158 | of << "];\n"; 159 | } 160 | -------------------------------------------------------------------------------- /blutter/src/FridaWriter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "DartApp.h" 3 | 4 | class FridaWriter 5 | { 6 | public: 7 | FridaWriter(DartApp& app) : app(app) {}; 8 | 9 | void Create(const char* filename); 10 | 11 | private: 12 | DartApp& app; 13 | }; 14 | 15 | -------------------------------------------------------------------------------- /blutter/src/HtArrayIterator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class HtArrayIterator { 4 | public: 5 | explicit HtArrayIterator(const dart::Array& data_) : data(data_), max_idx(data_.Length() - 1), curr_idx(1) {} 6 | HtArrayIterator() = delete; 7 | 8 | uint32_t Size() const { 9 | const auto& num_used = dart::Smi::Cast(dart::Object::Handle(data.At(0))); 10 | #ifdef UNIFORM_INTEGER_ACCESS 11 | return (uint32_t)num_used.Value(); 12 | #else 13 | return (uint32_t)num_used.AsInt64Value(); 14 | #endif 15 | } 16 | 17 | bool MoveNext() { 18 | while (curr_idx < max_idx) { 19 | ++curr_idx; 20 | auto objPtr = data.At(curr_idx); 21 | if (objPtr.GetClassId() != dart::kSentinelCid) 22 | return true; 23 | } 24 | return false; 25 | } 26 | 27 | dart::ObjectPtr Current() const { 28 | return data.At(curr_idx); 29 | } 30 | 31 | intptr_t CurrentIndex() const { 32 | return curr_idx; 33 | } 34 | 35 | private: 36 | const dart::Array& data; 37 | const intptr_t max_idx; 38 | intptr_t curr_idx; 39 | }; 40 | -------------------------------------------------------------------------------- /blutter/src/Util.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Util.h" 3 | #include 4 | #include 5 | 6 | // only ascii and no \0 7 | static void unescape_char(std::string& s, char c) 8 | { 9 | switch (c) 10 | { 11 | case '\a': s += "\\a"; break; 12 | case '\b': s += "\\b"; break; 13 | case '\f': s += "\\f"; break; 14 | case '\n': s += "\\n"; break; 15 | case '\r': s += "\\r"; break; 16 | case '\t': s += "\\t"; break; 17 | case '\v': s += "\\v"; break; 18 | case '\\': s += "\\\\"; break; 19 | case '\'': s += "\\'"; break; 20 | case '\"': s += "\\\""; break; 21 | case '\?': s += "\\\?"; break; 22 | default: s += c; break; 23 | } 24 | } 25 | 26 | 27 | std::string Util::Unescape(const std::string& s) 28 | { 29 | std::string res; 30 | res.reserve(s.length() * 2); 31 | for (char c : s) { 32 | unescape_char(res, c); 33 | } 34 | return res; 35 | } 36 | 37 | std::string Util::Unescape(const char* s) 38 | { 39 | std::string res; 40 | res.reserve(strlen(s) * 2); 41 | while (char c = *s++) { 42 | unescape_char(res, c); 43 | } 44 | return res; 45 | } 46 | 47 | std::string Util::UnescapeWithQuote(const char* s) 48 | { 49 | std::string res; 50 | res.reserve(strlen(s) * 2); 51 | res += '"'; 52 | while (char c = *s++) { 53 | unescape_char(res, c); 54 | } 55 | res += '"'; 56 | return res; 57 | } 58 | 59 | std::string Util::Quote(const std::string& s) 60 | { 61 | std::ostringstream ss; 62 | ss << std::quoted(s); 63 | return ss.str(); 64 | } 65 | 66 | std::string Util::Unquote(const std::string& s) 67 | { 68 | std::string result; 69 | std::istringstream ss(s); 70 | ss >> std::quoted(result); 71 | return result; 72 | } -------------------------------------------------------------------------------- /blutter/src/Util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | class Util 3 | { 4 | public: 5 | static std::string Unescape(const std::string& s); 6 | static std::string Unescape(const char* s); 7 | static std::string UnescapeWithQuote(const char* s); 8 | static std::string Quote(const std::string& s); 9 | static std::string Unquote(const std::string& s); 10 | }; 11 | 12 | -------------------------------------------------------------------------------- /blutter/src/VarValue.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "VarValue.h" 3 | #include 4 | 5 | static_assert(sizeof(bool) == 1, "bool size is not 1 byte"); 6 | 7 | std::string VarStorage::Name() 8 | { 9 | switch (kind) { 10 | case Register: 11 | return reg.Name(); 12 | case Local: 13 | return fmt::format("local_{:x}", -offset); 14 | case Argument: 15 | return fmt::format("arg_{}", idx); 16 | case Static: 17 | return fmt::format("static_{:x}", offset); 18 | case Pool: 19 | return fmt::format("PP_{:x}", offset); 20 | case Thread: 21 | return fmt::format("THR_{:x}", offset); 22 | case SmallImm: 23 | return std::to_string(offset); 24 | case InInstruction: 25 | return "tmp"; 26 | default: 27 | // Immediate has no storage type 28 | FATAL("Unknown storage type"); 29 | } 30 | } 31 | 32 | void VarValue::SetIntType(ValueType tid) 33 | { 34 | // only 2 possibles containers are VarExpression and VarInteger 35 | if (RawTypeId() == VarValue::Expression) { 36 | reinterpret_cast(this)->SetType(tid); 37 | } 38 | else { 39 | // should be only VarInteger 40 | ASSERT(RawTypeId() == dart::kIntegerCid); 41 | reinterpret_cast(this)->intTypeId = tid; 42 | } 43 | } 44 | 45 | void VarValue::SetSmiIfInt() 46 | { 47 | if (RawTypeId() == VarValue::Expression) { 48 | reinterpret_cast(this)->SetType(dart::kSmiCid); 49 | } 50 | else if (RawTypeId() == dart::kIntegerCid) { 51 | // Note: should not convert from Mint 52 | reinterpret_cast(this)->intTypeId = dart::kSmiCid; 53 | } 54 | } 55 | 56 | std::string VarArray::ToString() 57 | { 58 | std::string out; 59 | if ((intptr_t)ptr == (intptr_t)dart::Object::null()) { 60 | // no data 61 | out = "List"; 62 | if (eleType) { 63 | out += "<" + eleType->ToString() + ">"; 64 | } 65 | out += "("; 66 | if (length != -1) { 67 | out += std::to_string(length); 68 | } 69 | out += ")"; 70 | } 71 | else { 72 | // has data (const array) 73 | const auto& arr = dart::Array::Handle(ptr); 74 | const auto arr_len = arr.Length(); 75 | //const auto& type_args = dart::TypeArguments::Handle(arr.GetTypeArguments()); 76 | std::ostringstream ss; 77 | ss << "const ["; 78 | if (arr_len > 0) { 79 | // in ImmutableList, only Dart type (native type is not used) 80 | const auto heap_base = dart::Thread::Current()->heap_base(); 81 | auto& obj = dart::Object::Handle(); 82 | auto arrPtr = dart::Array::DataOf(arr.ptr()); 83 | for (intptr_t i = 0; i < arr_len; i++) { 84 | if (i != 0) 85 | ss << ", "; 86 | 87 | if (arrPtr->IsHeapObject()) { 88 | obj = arrPtr->Decompress(heap_base); 89 | // TODO: better string representation 90 | ss << obj.ToCString(); 91 | } 92 | else { 93 | obj = arrPtr->DecompressSmi(); 94 | ss << std::hex << std::showbase << dart::Smi::Cast(obj).Value(); 95 | } 96 | arrPtr++; 97 | } 98 | } 99 | ss << "]"; 100 | out = ss.str(); 101 | } 102 | return out; 103 | } 104 | 105 | std::string VarItem::Name() 106 | { 107 | switch (storage.kind) { 108 | case VarStorage::Immediate: 109 | case VarStorage::Pool: 110 | return val->ToString(); 111 | default: 112 | return storage.Name(); 113 | } 114 | } 115 | 116 | std::string VarItem::CallArgName() 117 | { 118 | switch (storage.kind) { 119 | case VarStorage::Immediate: 120 | case VarStorage::Pool: 121 | case VarStorage::Register: 122 | return val->ToString(); 123 | default: 124 | return storage.Name(); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /blutter/src/VarValue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "DartClass.h" 3 | #include "DartStub.h" 4 | #include "Util.h" 5 | #include "Disassembler.h" 6 | 7 | struct VarStorage { 8 | enum Kind : int32_t { 9 | Expression = 0, 10 | Register, 11 | Local, 12 | Argument, // caller argument 13 | Static, // static variable 14 | Pool, // object in pool 15 | Thread, 16 | InInstruction, // temporary storage when one assembly instruction is splitted to multiple intermediate instruction 17 | Immediate, 18 | SmallImm, // array/object offset 19 | Call, // return value 20 | Field, // access field 21 | Uninit, 22 | }; 23 | VarStorage(A64::Register reg) : kind{ Register }, reg{ reg } {} 24 | VarStorage(Kind kind) : kind{ kind }, offset{ 0 } {} 25 | VarStorage(Kind kind, int val) : kind{ kind }, offset{ val } {} 26 | 27 | static VarStorage NewExpression() { return VarStorage(Expression); } 28 | static VarStorage NewRegister(A64::Register reg) { return VarStorage(reg); } 29 | static VarStorage NewLocal(int offset) { return VarStorage(Local, offset); } 30 | static VarStorage NewArgument(int idx) { return VarStorage(Argument, idx); } 31 | static VarStorage NewStatic(int offset) { return VarStorage(Static, offset); } 32 | static VarStorage NewPool(int offset) { return VarStorage(Pool, offset); } 33 | static VarStorage NewThread(int offset) { return VarStorage(Thread, offset); } 34 | static VarStorage NewImmediate() { return VarStorage(Immediate); } 35 | static VarStorage NewSmallImm(int val) { return VarStorage(SmallImm, val); } 36 | static VarStorage NewCall() { return VarStorage(Call); } 37 | static VarStorage NewUninit() { return VarStorage(Uninit); } 38 | 39 | bool operator==(A64::Register reg) const { return kind == Register && reg == this->reg; } 40 | bool IsImmediate() const { return kind == Immediate; } 41 | bool IsPredefinedValue() const { return kind == Immediate || kind == Pool; } 42 | 43 | std::string Name(); 44 | 45 | Kind kind; 46 | union { 47 | A64::Register reg; 48 | int offset; // offset of Local, Pool, Thread, Offset 49 | int idx; // index of Argument 50 | }; 51 | }; 52 | 53 | using ValueType = int32_t; 54 | 55 | struct VarInteger; 56 | struct VarParam; 57 | struct VarValue { 58 | // use Dart class id to determine what the variable type is 59 | // custom type use negative value 60 | enum CustomTypeId : int32_t { 61 | Expression = -1000, 62 | TaggedCid, 63 | NativeInt, 64 | NativeDouble, 65 | Parameter, // call parameter (argument) 66 | ArgsDesc, 67 | // it will a number of passing named parameter. no use after loading all named parameters but some function stores it into stack without use. 68 | // so, we need it to suppress an error about use without define. 69 | CurrNumNameParam, 70 | }; 71 | 72 | VarValue(ValueType typeId, bool hasValue = false) : typeId(typeId), hasValue(hasValue) {} 73 | //VarValue() : kind(Unknown), hasValue(false) {} 74 | virtual ~VarValue() {} 75 | //virtual std::string ToString() = 0; 76 | virtual std::string ToString() { return "unknown"; } 77 | bool HasValue() const { return hasValue; } 78 | virtual ValueType TypeId() { return typeId; } 79 | ValueType RawTypeId() const { return typeId; } 80 | 81 | void SetIntType(ValueType tid); 82 | void SetSmiIfInt(); 83 | VarInteger* AsInteger() { 84 | ASSERT(RawTypeId() == dart::kIntegerCid); 85 | return reinterpret_cast(this); 86 | } 87 | VarParam* AsParam() { 88 | ASSERT(typeId == Parameter); 89 | return reinterpret_cast(this); 90 | } 91 | 92 | ValueType typeId; 93 | bool hasValue; 94 | }; 95 | 96 | struct VarNull : public VarValue { 97 | explicit VarNull() : VarValue(dart::kNullCid, true) {} 98 | virtual std::string ToString() { return "Null"; } 99 | }; 100 | 101 | struct VarBoolean : public VarValue { 102 | explicit VarBoolean(bool val) : VarValue(dart::kBoolCid, true), val(val) {} 103 | explicit VarBoolean() : VarValue(dart::kBoolCid, false), val(false) {} 104 | virtual std::string ToString() { return val ? "true" : "false"; } 105 | 106 | bool val; 107 | }; 108 | 109 | struct VarInteger : public VarValue { 110 | // int type is same as type in VarValue 111 | // Note: VarInteger = unknown integer type (maybe native, smi, mint) 112 | explicit VarInteger(int64_t val, ValueType intTypeId = dart::kIntegerCid) : VarValue(dart::kIntegerCid, true), intTypeId(intTypeId), val(val) {} 113 | explicit VarInteger(ValueType intTypeId = dart::kIntegerCid) : VarValue(dart::kIntegerCid, false), intTypeId(intTypeId), val(0) {} 114 | virtual std::string ToString() { return std::to_string(Value()); } 115 | int64_t Value() const { return (intTypeId == dart::kSmiCid) ? val >> dart::kSmiTagSize : val; } 116 | 117 | ValueType intTypeId; 118 | int64_t val; 119 | }; 120 | 121 | struct VarDouble : public VarValue { 122 | explicit VarDouble(double val, ValueType doubleTypeId = dart::kDoubleCid) : VarValue(dart::kDoubleCid, true), doubleTypeId(doubleTypeId), val(val) {} 123 | explicit VarDouble(ValueType doubleTypeId = dart::kDoubleCid) : VarValue(dart::kDoubleCid, false), doubleTypeId(doubleTypeId), val(0.0) {} 124 | virtual std::string ToString() { return std::to_string(val); } 125 | 126 | ValueType doubleTypeId; 127 | double val; 128 | }; 129 | 130 | struct VarString : public VarValue { 131 | explicit VarString(std::string str) : VarValue(dart::kStringCid, true), str(std::move(str)) {} 132 | explicit VarString() : VarValue(dart::kStringCid, false) {} 133 | virtual std::string ToString() { return Util::UnescapeWithQuote(str.c_str()); } 134 | 135 | std::string str; 136 | }; 137 | 138 | struct VarFunctionCode : public VarValue { 139 | explicit VarFunctionCode(DartFnBase& fn) : VarValue(dart::kFunctionCid, true), fn(fn) {} 140 | virtual std::string ToString() { return fn.FullName(); } 141 | 142 | DartFnBase& fn; 143 | }; 144 | 145 | struct VarField : public VarValue { 146 | explicit VarField(DartField& field) : VarValue(dart::kFieldCid, true), field(field) {} 147 | virtual std::string ToString() { return field.Name(); } 148 | 149 | DartField& field; 150 | }; 151 | 152 | struct VarExpression : public VarValue { 153 | explicit VarExpression(std::string txt) : VarValue(Expression, false), txt(std::move(txt)), cid(dart::kIllegalCid) {} 154 | explicit VarExpression(std::string txt, ValueType cid) : VarValue(Expression, false), txt(std::move(txt)), cid(cid) {} 155 | virtual std::string ToString() { return txt; } 156 | void SetText(std::string txt) { this->txt = std::move(txt); } 157 | virtual ValueType TypeId() { return cid; } 158 | void SetType(ValueType cid) { this->cid = cid; } 159 | 160 | std::string txt; 161 | ValueType cid; 162 | }; 163 | 164 | struct VarArray : public VarValue { 165 | explicit VarArray(dart::ArrayPtr ptr) : VarValue(dart::kArrayCid, true), ptr(ptr), eleType(nullptr), length(-1) {} 166 | explicit VarArray(DartAbstractType* eleType, int length = -1) : VarValue(dart::kArrayCid, false), ptr(dart::Object::null()), eleType(eleType), length(length) {} 167 | explicit VarArray() : VarValue(dart::kArrayCid, false), ptr(dart::Object::null()), eleType(nullptr), length(-1) {} 168 | virtual std::string ToString(); 169 | int64_t DataOffset() { 170 | // TODO: typedArray has no type argument. so, offset is not the same 171 | return dart::Array::data_offset(); 172 | } 173 | int ElementSize() { 174 | // TODO: typedArray has fixed size 175 | return dart::kCompressedWordSize; 176 | } 177 | bool IsElementTypeInt() { 178 | //return eleType && eleType->Class().Name() == "int"; 179 | return eleType && eleType->AsType()->Class().Name() == "int"; 180 | } 181 | 182 | dart::ArrayPtr ptr; 183 | DartAbstractType* eleType; 184 | int length; // -1 for unknown or growable array 185 | }; 186 | 187 | // should growable array be treated as instance? 188 | // growable array is not Array 189 | // its data is a Fixed size Array (length of fixed size array is capacity) 190 | struct VarGrowableArray : public VarValue { 191 | explicit VarGrowableArray(DartAbstractType* eleType) : VarValue(dart::kGrowableObjectArrayCid, false), eleType(eleType) {} 192 | explicit VarGrowableArray() : VarValue(dart::kGrowableObjectArrayCid, false), eleType(nullptr) {} 193 | virtual std::string ToString() { return "GrowableArray"; } 194 | 195 | int ElementSize() { 196 | // TODO: typedArray has fixed size 197 | return dart::kCompressedWordSize; 198 | } 199 | bool IsElementTypeInt() { 200 | //return eleType && eleType->Class().Name() == "int"; 201 | return eleType && eleType->AsType()->Class().Name() == "int"; 202 | } 203 | 204 | int64_t LengthOffset() { 205 | return dart::GrowableObjectArray::length_offset(); 206 | } 207 | 208 | int64_t DataOffset() { 209 | return dart::GrowableObjectArray::data_offset(); 210 | } 211 | 212 | DartAbstractType* eleType; 213 | }; 214 | 215 | struct VarUnlinkedCall : public VarValue { 216 | explicit VarUnlinkedCall(DartStub& stub) : VarValue(dart::kUnlinkedCallCid, true), stub(stub) {} 217 | virtual std::string ToString() { return fmt::format("UnlinkedCall_{:#x}", stub.Address()); } 218 | 219 | DartStub& stub; 220 | }; 221 | 222 | // object instance 223 | struct VarInstance : public VarValue { 224 | explicit VarInstance(DartClass* cls) : VarValue(dart::kInstanceCid, true), cls(cls) {} 225 | explicit VarInstance() : VarValue(dart::kInstanceCid, false), cls(nullptr) {} 226 | virtual ValueType TypeId() { return cls->Id(); } 227 | virtual std::string ToString() { return fmt::format("Instance_{}", cls->Name()); } 228 | 229 | DartClass* cls; 230 | //TODO: TypeArguments; 231 | }; 232 | 233 | struct VarType : public VarValue { 234 | explicit VarType(const DartType& type) : VarValue(dart::kTypeCid, true), type(type) {} 235 | virtual std::string ToString() { return type.ToString(); } 236 | 237 | const DartType& type; 238 | }; 239 | 240 | #ifdef HAS_RECORD_TYPE 241 | struct VarRecordType : public VarValue { 242 | explicit VarRecordType(const DartRecordType& recordType) : VarValue(dart::kRecordTypeCid, true), recordType(recordType) {} 243 | virtual std::string ToString() { return recordType.ToString(); } 244 | 245 | const DartRecordType& recordType; 246 | }; 247 | #endif 248 | 249 | struct VarTypeParameter : public VarValue { 250 | explicit VarTypeParameter(const DartTypeParameter& typeParam) : VarValue(dart::kTypeParameterCid, true), typeParam(typeParam) {} 251 | virtual std::string ToString() { return typeParam.ToString(); } 252 | 253 | const DartTypeParameter& typeParam; 254 | }; 255 | 256 | struct VarFunctionType : public VarValue { 257 | explicit VarFunctionType(const DartFunctionType& fnType) : VarValue(dart::kFunctionTypeCid, true), fnType(fnType) {} 258 | virtual std::string ToString() { return fnType.ToString(); } 259 | 260 | const DartFunctionType& fnType; 261 | }; 262 | 263 | struct VarTypeArgument : public VarValue { 264 | explicit VarTypeArgument(const DartTypeArguments& typeArgs) : VarValue(dart::kTypeArgumentsCid, true), typeArgs(typeArgs) {} 265 | virtual std::string ToString() { return typeArgs.ToString(); } 266 | 267 | const DartTypeArguments& typeArgs; 268 | }; 269 | 270 | // uninitialized object in dart 271 | struct VarSentinel : public VarValue { 272 | explicit VarSentinel() : VarValue(dart::kSentinelCid, false) {} 273 | virtual std::string ToString() { return "Sentinel"; } 274 | }; 275 | 276 | struct VarSubtypeTestCache : public VarValue { 277 | explicit VarSubtypeTestCache() : VarValue(dart::kSubtypeTestCacheCid, false) {} 278 | virtual std::string ToString() { return "SubtypeTestCache"; } 279 | }; 280 | 281 | // A special integer type to represent class id 282 | // cid might be used tagged cid (SMI) 283 | struct VarCid : public VarValue { 284 | explicit VarCid(int cid, bool isSmi) : VarValue(dart::kClassCid, cid != 0), isSmi(isSmi), cid(cid) {} 285 | explicit VarCid() : VarValue(dart::kClassCid, false), isSmi(false), cid(0) {} 286 | virtual std::string ToString() { return isSmi ? fmt::format("TaggedCid_{}", cid >> dart::kSmiTagSize) : fmt::format("cid_{}", cid); } 287 | 288 | bool isSmi; 289 | int cid; 290 | }; 291 | 292 | struct VarParam : public VarValue { 293 | explicit VarParam(int idx) : VarValue(Parameter, false), idx(idx) {} 294 | int idx; 295 | }; 296 | 297 | struct VarItem { 298 | explicit VarItem() : storage(VarStorage::Uninit) {} 299 | explicit VarItem(VarStorage storage) : storage(storage) {} 300 | explicit VarItem(VarStorage storage, std::unique_ptr val) : storage(storage), val(std::move(val)) {} 301 | explicit VarItem(VarStorage storage, VarValue* val) : storage(storage), val(std::unique_ptr(val)) {} 302 | // register storage is common and also special type 303 | explicit VarItem(A64::Register reg, std::unique_ptr val) : storage(VarStorage(reg)), val(std::move(val)) {} 304 | explicit VarItem(A64::Register reg, VarValue* val) : storage(VarStorage(reg)), val(std::unique_ptr(val)) {} 305 | 306 | VarStorage Storage() const { return storage; } 307 | std::string StorageName() { return storage.Name(); } 308 | 309 | template ::value>> 310 | T* Get() const { return reinterpret_cast(val.get()); } 311 | VarValue* Value() const { return val.get(); } 312 | std::unique_ptr TakeValue() { return std::move(val); } 313 | std::string ValueString() const { return val ? val->ToString() : "BUG_NO_ASSIGN_VALUE"; } 314 | ValueType ValueTypeId() const { return val->RawTypeId(); } 315 | VarItem* MoveTo(VarStorage storage) { return new VarItem(storage, std::move(val)); } 316 | VarItem* MoveTo(A64::Register reg) { return new VarItem(VarStorage::NewRegister(reg), std::move(val)); } 317 | 318 | // TODO: more clever name or value when it is known type 319 | std::string Name(); 320 | std::string CallArgName(); 321 | 322 | VarStorage storage; 323 | //VarType type; 324 | std::unique_ptr val; 325 | }; 326 | -------------------------------------------------------------------------------- /blutter/src/il.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "il.h" 3 | #include "CodeAnalyzer.h" 4 | #include "DartThreadInfo.h" 5 | 6 | std::string SetupParametersInstr::ToString() 7 | { 8 | return "SetupParameters(" + params->ToString() + ")"; 9 | } 10 | 11 | std::string CallLeafRuntimeInstr::ToString() 12 | { 13 | const auto& name = GetThreadOffsetName(thrOffset); 14 | const auto info = GetThreadLeafFunction(thrOffset); 15 | return fmt::format("CallRuntime_{}({}) -> {}", name, info->params, info->returnType); 16 | } -------------------------------------------------------------------------------- /blutter/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "DartApp.h" 3 | #include "DartDumper.h" 4 | #include "CodeAnalyzer.h" 5 | #include "FridaWriter.h" 6 | #include "args.hxx" 7 | #include 8 | 9 | int main(int argc, char** argv) 10 | { 11 | args::ArgumentParser parser("B(l)utter - Reversing flutter application", ""); 12 | args::HelpFlag help(parser, "help", "Display this help menu", { 'h', "help" }); 13 | args::Group reqGrp(parser, "Required arguments", args::Group::Validators::All); 14 | args::ValueFlag infile(reqGrp, "infile", "libapp file", { 'i', "in" }); 15 | args::ValueFlag outdir(reqGrp, "outdir", "out path", { 'o', "out"}); 16 | 17 | try { 18 | parser.ParseCLI(argc, argv); 19 | 20 | auto& libappPath = args::get(infile); 21 | 22 | std::filesystem::path outDir{ args::get(outdir) }; 23 | std::error_code ec; 24 | if (!std::filesystem::create_directory(outDir, ec) && ec.value() != 0) { 25 | std::cerr << "Failed to create output directory: " << ec.message() << "\n"; 26 | return 1; 27 | } 28 | 29 | DartApp app{ libappPath.c_str() }; 30 | std::cout << fmt::format("libapp is loaded at {:#x}\n", app.base()); 31 | std::cout << fmt::format("Dart heap at {:#x}\n", app.heap_base()); 32 | 33 | app.EnterScope(); 34 | app.LoadInfo(); 35 | app.ExitScope(); 36 | 37 | app.EnterScope(); 38 | #ifndef NO_CODE_ANALYSIS 39 | std::cout << "Analyzing the application\n"; 40 | CodeAnalyzer analyzer{ app }; 41 | analyzer.AnalyzeAll(); 42 | #endif 43 | 44 | DartDumper dumper{ app }; 45 | std::cout << "Dumping Object Pool\n"; 46 | dumper.DumpObjectPool((outDir / "pp.txt").string().c_str()); 47 | dumper.DumpObjects((outDir / "objs.txt").string().c_str()); 48 | #ifndef NO_CODE_ANALYSIS 49 | std::cout << "Generating application assemblies\n"; 50 | #else 51 | std::cout << "Generating application functions in asm folder\n"; 52 | #endif 53 | dumper.DumpCode((outDir / "asm").string().c_str()); 54 | std::cout << "Generating radare2 script\n"; 55 | dumper.Dump4Radare2(outDir / "r2_script"); 56 | std::cout << "Generating IDA script\n"; 57 | dumper.Dump4Ida(outDir / "ida_script"); 58 | 59 | std::cout << "Generating Frida script\n"; 60 | FridaWriter fwriter{ app }; 61 | fwriter.Create((outDir / "blutter_frida.js").string().c_str()); 62 | 63 | app.ExitScope(); 64 | } 65 | catch (args::Help&) { 66 | std::cout << parser; 67 | return 0; 68 | } 69 | catch (args::ParseError& e) { 70 | std::cerr << e.what() << "\n"; 71 | std::cerr << parser; 72 | return 1; 73 | } 74 | catch (args::ValidationError& e) { 75 | std::cerr << e.what() << "\n"; 76 | std::cerr << parser; 77 | return 1; 78 | } 79 | catch (std::exception& e) { 80 | std::cerr << "exception: " << e.what() << "\n"; 81 | } 82 | 83 | return 0; 84 | } -------------------------------------------------------------------------------- /blutter/src/pch.cpp: -------------------------------------------------------------------------------- 1 | // pch.cpp: source file corresponding to the pre-compiled header 2 | 3 | #include "pch.h" 4 | 5 | // When you are using pre-compiled headers, this source file is necessary for compilation to succeed. 6 | -------------------------------------------------------------------------------- /blutter/src/pch.h: -------------------------------------------------------------------------------- 1 | // pch.h: This is a precompiled header file. 2 | // Files listed below are compiled only once, improving build performance for future builds. 3 | // This also affects IntelliSense performance, including code completion and many code browsing features. 4 | // However, files listed here are ALL re-compiled if any one of them is updated between builds. 5 | // Do not add files here that you will be updating frequently as this negates the performance advantage. 6 | 7 | #ifndef PCH_H 8 | #define PCH_H 9 | #define FMT_HEADER_ONLY 10 | // add headers that you want to pre-compile here 11 | #include 12 | #include "fmt/format.h" 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #if defined(_MSC_VER) 19 | # define PRAGMA_WARNING(...) __pragma(warning(__VA_ARGS__)) 20 | #else 21 | # define PRAGMA_WARNING(...) 22 | #endif 23 | 24 | PRAGMA_WARNING(push, 0) 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | //#include 31 | #include 32 | //#include 33 | //#include 34 | #include 35 | #include 36 | #include 37 | #include 38 | PRAGMA_WARNING(pop) 39 | 40 | #ifdef OLD_MAP_SET_NAME 41 | namespace dart { 42 | using Map = LinkedHashMap; 43 | using Set = LinkedHashSet; 44 | #ifdef OLD_MAP_NO_IMMUTABLE 45 | using ConstMap = LinkedHashMap; 46 | using ConstSet = LinkedHashSet; 47 | #else 48 | using ConstMap = ImmutableLinkedHashMap; 49 | using ConstSet = ImmutableLinkedHashSet; 50 | #endif 51 | 52 | enum ClassIdX : intptr_t { 53 | kMapCid = kLinkedHashMapCid, 54 | kSetCid = kLinkedHashSetCid, 55 | #ifdef OLD_MAP_NO_IMMUTABLE 56 | kConstMapCid = kLinkedHashMapCid, 57 | kConstSetCid = kLinkedHashSetCid, 58 | #else 59 | kConstMapCid = kImmutableLinkedHashMapCid, 60 | kConstSetCid = kImmutableLinkedHashSetCid, 61 | #endif 62 | }; 63 | }; 64 | #endif 65 | 66 | #ifdef NO_LAST_INTERNAL_ONLY_CID 67 | namespace dart { 68 | constexpr intptr_t kLastInternalOnlyCid = kUnwindErrorCid; 69 | }; 70 | #endif 71 | 72 | #if defined SEMIDBG && !defined DEBUG 73 | // the debug build configuration that use release configuration but no optimization 74 | // so only the executable are fully debuggable. but ASSERT is gone 75 | #undef ASSERT 76 | #define ASSERT(cond) RELEASE_ASSERT(cond) 77 | // Note: DEBUG_ASSERT requires dart vm is built with debug mode because it might access some field that exists only in DEBUG 78 | //#undef DEBUG_ASSERT 79 | //#define DEBUG_ASSERT(cond) RELEASE_ASSERT(cond) 80 | #endif // defined SEMIDBG && !defined DEBUG 81 | 82 | // https://github.com/dart-lang/sdk/commit/bf4bb953081f11fbec9a1c0ea08b428c744b369e 83 | // new async/await implementation in Dart (around version 2.16 or 2.17) 84 | // the result is generated code in async and prologue is changed drastically 85 | // now, analyzer can only work against new implementation 86 | #ifdef CACHED_FUNCTION_ENTRY_POINTS_LIST 87 | # define HAS_INIT_ASYNC 1 88 | #endif 89 | 90 | // InitLateStaticFieldStub is implemented around Dart 2.16. Before that, only InitStaticFieldStub 91 | #ifdef NO_INIT_LATE_STATIC_FIELD 92 | # define InitLateStaticFieldStub InitStaticFieldStub 93 | # define InitLateFinalStaticFieldStub InitStaticFieldStub 94 | #endif 95 | 96 | // https://github.com/dart-lang/sdk/commit/84fd647969f0d74ab63f0994d95b5fc26cac006a 97 | // refactor access to integer value to be same name "Value()". Basically, only dart::Mint is changed. 98 | // in dart 3.6, there are many internal changes (below). just use only one macro below for simplicity. 99 | // all of them are commited separately in dev only. so, it does not work only some dev version. 100 | // - Improve BitField API. UntaggedObject use BitField. kXXXPos and kXXXSize are changed to XXX. 101 | // Use XXX::shift() and XXX::bitsize() for Pos and Size respectively 102 | // https://github.com/dart-lang/sdk/commit/d3c165d7b52e48672224d0e46d2c74696fd89322 103 | // - Record::GetRecordType() with one argument 104 | // https://github.com/dart-lang/sdk/commit/ab19361e87a0248ade1e883f638542163f9100d1 105 | #ifdef UNIFORM_INTEGER_ACCESS 106 | # define MintValue(obj) obj.Value() 107 | constexpr intptr_t kUntaggedObjectClassIdTagPos = dart::UntaggedObject::ClassIdTag::shift(); 108 | # define DartGetRecordType(record) record.GetRecordType(dart::TypeVisibility::kUserVisibleType) 109 | #else 110 | # define MintValue(obj) obj.value() 111 | constexpr intptr_t kUntaggedObjectClassIdTagPos = dart::UntaggedObject::kClassIdTagPos; 112 | # define DartGetRecordType(record) record.GetRecordType() 113 | #endif 114 | 115 | #endif //PCH_H 116 | -------------------------------------------------------------------------------- /dartvm_fetch_build.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import stat 4 | import subprocess 5 | import sys 6 | 7 | # assume git and cmake (64 bits) command is in PATH 8 | GIT_CMD = "git" 9 | CMAKE_CMD = "cmake" 10 | NINJA_CMD = "ninja" 11 | 12 | SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) 13 | CMAKE_TEMPLATE_FILE = os.path.join(SCRIPT_DIR, "scripts", "CMakeLists.txt") 14 | CREATE_SRCLIST_FILE = os.path.join(SCRIPT_DIR, "scripts", "dartvm_create_srclist.py") 15 | MAKE_VERSION_FILE = os.path.join(SCRIPT_DIR, "scripts", "dartvm_make_version.py") 16 | 17 | SDK_DIR = os.path.join(SCRIPT_DIR, "dartsdk") 18 | BUILD_DIR = os.path.join(SCRIPT_DIR, "build") 19 | 20 | # DART_GIT_URL = 'https://dart.googlesource.com/sdk.git' 21 | DART_GIT_URL = "https://github.com/dart-lang/sdk.git" 22 | 23 | imp_replace_snippet = """import importlib.util 24 | import importlib.machinery 25 | 26 | def load_source(modname, filename): 27 | loader = importlib.machinery.SourceFileLoader(modname, filename) 28 | spec = importlib.util.spec_from_file_location(modname, filename, loader=loader) 29 | module = importlib.util.module_from_spec(spec) 30 | loader.exec_module(module) 31 | return module 32 | """ 33 | 34 | 35 | dart_versions = { 36 | "3.4": ["3.4.0", "3.4.1", "3.4.2", "3.4.3", "3.4.4"], 37 | "3.3": ["3.3.0", "3.3.1", "3.3.2", "3.3.3", "3.3.4"], 38 | "3.5": ["3.5.0", "3.5.1", "3.5.2", "3.5.3"], 39 | "3.0_aa": ["3.0.0", "3.0.1", "3.0.2"], 40 | "3.0_90": ["3.0.3", "3.0.4", "3.0.5", "3.0.6", "3.0.7"], 41 | "3.1": ["3.1.0", "3.1.1", "3.1.2", "3.1.3", "3.1.4", "3.1.5"], 42 | "3.2": ["3.2.0", "3.2.1", "3.2.2", "3.2.3", "3.2.4", "3.2.5", "3.2.6"], 43 | } 44 | 45 | 46 | class DartLibInfo: 47 | def __init__( 48 | self, 49 | version: str, 50 | os_name: str, 51 | arch: str, 52 | has_compressed_ptrs: bool | None = None, 53 | snapshot_hash: str | None = None, 54 | ): 55 | self.os_name = os_name 56 | self.arch = arch 57 | self.snapshot_hash = snapshot_hash 58 | if has_compressed_ptrs is None: 59 | # use same as flutter default configuration 60 | # TODO: old Dart version has no pointer compression 61 | self.has_compressed_ptrs = os_name != "ios" 62 | else: 63 | self.has_compressed_ptrs = has_compressed_ptrs 64 | 65 | if os.path.exists(os.path.join(SCRIPT_DIR, "bin")): 66 | file_name_version = [] 67 | suffixes = ["", "_no-analysis", "_ida-fcn", "_no-analysis_ida-fcn", "_no-compressed-ptrs", "_no-compressed-ptrs_no-analysis", "_no-compressed-ptrs_no-analysis_ida-fcn", "_no-compressed-ptrs_ida-fcn"] 68 | for file in os.listdir(os.path.join(SCRIPT_DIR, "bin")): 69 | for suffix in suffixes: 70 | if file.startswith("blutter_dartvm") and file.endswith(f"{os_name}_{arch}{suffix}"): 71 | file_name_version.append(file.split("_")[1].replace('dartvm', '')) 72 | 73 | for key, versions in dart_versions.items(): 74 | if version in versions and any(v in versions for v in file_name_version): 75 | matched_version = next(v for v in file_name_version if v in versions) 76 | self.lib_name = f"dartvm{matched_version}_{os_name}_{arch}" 77 | self.version = matched_version 78 | return 79 | 80 | self.version = version 81 | self.lib_name = f"dartvm{version}_{os_name}_{arch}" 82 | 83 | 84 | def checkout_dart(info: DartLibInfo): 85 | clonedir = os.path.join(SDK_DIR, "v" + info.version) 86 | 87 | # if no version file,assume previous clone is failed. delete the whole directory and try again. 88 | version_file = os.path.join(clonedir, "runtime", "vm", "version.cc") 89 | if os.path.exists(clonedir) and not os.path.exists(version_file): 90 | print("Delete incomplete clone directory " + clonedir) 91 | 92 | def remove_readonly(func, path, _): 93 | os.chmod(path, stat.S_IWRITE) 94 | func(path) 95 | 96 | shutil.rmtree(clonedir, onerror=remove_readonly) 97 | 98 | # clone Dart source code 99 | if not os.path.exists(clonedir): 100 | # minimum clone repository at the target branch 101 | subprocess.run( 102 | [ 103 | GIT_CMD, 104 | "-c", 105 | "advice.detachedHead=false", 106 | "clone", 107 | "-b", 108 | info.version, 109 | "--depth", 110 | "1", 111 | "--filter=blob:none", 112 | "--sparse", 113 | "--progress", 114 | DART_GIT_URL, 115 | clonedir, 116 | ], 117 | check=True, 118 | ) 119 | # checkout only needed sources (runtime and tools) 120 | # since Dart 3.3 "third_party/double-conversion" is moved to outside of "runtime" directory 121 | subprocess.run( 122 | [ 123 | GIT_CMD, 124 | "sparse-checkout", 125 | "set", 126 | "runtime", 127 | "tools", 128 | "third_party/double-conversion", 129 | ], 130 | cwd=clonedir, 131 | check=True, 132 | ) 133 | # delete some unnecessary files 134 | with os.scandir(clonedir) as it: 135 | for entry in it: 136 | if entry.is_file(): 137 | os.remove(entry.path) 138 | elif entry.is_dir() and entry.name == ".git": 139 | # should ".git" directory be removed? 140 | pass 141 | 142 | if info.snapshot_hash is None: 143 | # if running with Python 3.12, tools/utils.py should be patched to replace imp module with importlib 144 | # due to its remotion as stated in: https://docs.python.org/3.12/whatsnew/3.12.html#imp 145 | if sys.version_info[:2] >= (3, 12): 146 | utils_path = os.path.join(clonedir, "tools/utils.py") 147 | if os.path.exists(utils_path): 148 | with open(utils_path, "r+") as f: 149 | content = f.read() 150 | # the python 3.12 warnings are fixed in https://github.com/dart-lang/sdk/commit/a100968232c7492ab0de26897ff019e5784d4d38 151 | if r"match_against('^MAJOR (\d+)$', content)" in content: 152 | # old Dart version 153 | # replace invalid escape sequences strings with raw strings to avoid SyntaxWarning 154 | # in future Python versions this warning will raise an error instead of warning 155 | # as stated in: https://docs.python.org/3/whatsnew/3.12.html#other-language-changes 156 | content = ( 157 | content.replace(" ' awk ", " r' awk ") 158 | .replace("match_against('", "match_against(r'") 159 | .replace("re.search('", "re.search(r'") 160 | ) 161 | if "import imp\n" in content: 162 | content = content.replace( 163 | "import imp\n", imp_replace_snippet 164 | ).replace("imp.load_source", "load_source") 165 | f.seek(0) 166 | f.truncate() 167 | f.write(content) 168 | # make version 169 | subprocess.run( 170 | [ 171 | sys.executable, 172 | "tools/make_version.py", 173 | "--output", 174 | "runtime/vm/version.cc", 175 | "--input", 176 | "runtime/vm/version_in.cc", 177 | ], 178 | cwd=clonedir, 179 | check=True, 180 | ) 181 | else: 182 | subprocess.run( 183 | [sys.executable, MAKE_VERSION_FILE, clonedir, info.snapshot_hash], 184 | check=True, 185 | ) 186 | 187 | return clonedir 188 | 189 | 190 | def get_dartlib_name(ver: str, arch: str, os_name: str): 191 | return f"dartvm{ver}_{os_name}_{arch}" 192 | 193 | 194 | def cmake_dart(info: DartLibInfo, target_dir: str): 195 | # On windows, need developer command prompt for x64 (can check with "cl" command) 196 | # create dartsdk/vx.y.z/CMakefile.list 197 | with open(CMAKE_TEMPLATE_FILE, "r") as f: 198 | code = f.read() 199 | with open(os.path.join(target_dir, "CMakeLists.txt"), "w") as f: 200 | f.write(code.replace("VERSION_PLACE_HOLDER", info.version)) 201 | 202 | # create dartsdk/vx.y.z/Config.cmake.in 203 | with open(os.path.join(target_dir, "Config.cmake.in"), "w") as f: 204 | f.write("@PACKAGE_INIT@\n\n") 205 | f.write('include ( "${CMAKE_CURRENT_LIST_DIR}/dartvmTarget.cmake" )\n\n') 206 | 207 | # generate source list 208 | subprocess.run([sys.executable, CREATE_SRCLIST_FILE, target_dir], check=True) 209 | # cmake -GNinja -Bout3.0.3 -DCMAKE_BUILD_TYPE=Release 210 | # add -DTARGET_ARCH=x64 for analyzing x64 libapp.so 211 | # add -DTARGET_OS=ios for analyzing ios App 212 | # Note: pointer compression feature is set from Flutter and no one change it when building app. 213 | # so only one build of Dart runtime is enough 214 | builddir = os.path.join(BUILD_DIR, info.lib_name) 215 | subprocess.run( 216 | [ 217 | CMAKE_CMD, 218 | "-GNinja", 219 | "-B", 220 | builddir, 221 | f"-DTARGET_OS={info.os_name}", 222 | f"-DTARGET_ARCH={info.arch}", 223 | f"-DCOMPRESSED_PTRS={1 if info.has_compressed_ptrs else 0}", 224 | "-DCMAKE_BUILD_TYPE=Release", 225 | "--log-level=NOTICE", 226 | ], 227 | cwd=target_dir, 228 | check=True, 229 | ) 230 | 231 | # build and install dart vm library to packages directory 232 | subprocess.run([NINJA_CMD], cwd=builddir, check=True) 233 | subprocess.run([CMAKE_CMD, "--install", "."], cwd=builddir, check=True) 234 | 235 | 236 | def fetch_and_build(info: DartLibInfo): 237 | outdir = checkout_dart(info) 238 | cmake_dart(info, outdir) 239 | 240 | 241 | if __name__ == "__main__": 242 | ver = sys.argv[1] 243 | os_name = "android" if len(sys.argv) < 3 else sys.argv[2] 244 | arch = "arm64" if len(sys.argv) < 4 else sys.argv[3] 245 | snapshot_hash = None if len(sys.argv) < 5 else sys.argv[4] 246 | info = DartLibInfo(ver, os_name, arch, snapshot_hash=snapshot_hash) 247 | fetch_and_build(info) 248 | -------------------------------------------------------------------------------- /extract_dart_info.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os 3 | import re 4 | import requests 5 | import sys 6 | import zipfile 7 | import zlib 8 | from struct import unpack 9 | 10 | from elftools.elf.elffile import ELFFile 11 | from elftools.elf.enums import ENUM_E_MACHINE 12 | from elftools.elf.sections import SymbolTableSection 13 | 14 | # TODO: support both ELF and Mach-O file 15 | def extract_snapshot_hash_flags(libapp_file): 16 | with open(libapp_file, 'rb') as f: 17 | elf = ELFFile(f) 18 | # find "_kDartVmSnapshotData" symbol 19 | dynsym = elf.get_section_by_name('.dynsym') 20 | sym = dynsym.get_symbol_by_name('_kDartVmSnapshotData')[0] 21 | #section = elf.get_section(sym['st_shndx']) 22 | assert sym['st_size'] > 128 23 | f.seek(sym['st_value']+20) 24 | snapshot_hash = f.read(32).decode() 25 | data = f.read(256) # should be enough 26 | flags = data[:data.index(b'\0')].decode().strip().split(' ') 27 | 28 | return snapshot_hash, flags 29 | 30 | def extract_libflutter_info(libflutter_file): 31 | with open(libflutter_file, 'rb') as f: 32 | elf = ELFFile(f) 33 | if elf.header.e_machine == 'EM_AARCH64': # 183 34 | arch = 'arm64' 35 | elif elf.header.e_machine == 'EM_IA_64': # 50 36 | arch = 'x64' 37 | else: 38 | assert False, "Unsupport architecture: " + elf.header.e_machine 39 | 40 | section = elf.get_section_by_name('.rodata') 41 | data = section.data() 42 | 43 | sha_hashes = re.findall(b'\x00([a-f\\d]{40})(?=\x00)', data) 44 | #print(sha_hashes) 45 | # all possible engine ids 46 | engine_ids = [ h.decode() for h in sha_hashes ] 47 | assert len(engine_ids) == 2, f'found hashes {", ".join(engine_ids)}' 48 | 49 | # beta/dev version of flutter might not use stable dart version (we can get dart version from sdk with found engine_id) 50 | # support only stable 51 | epos = data.find(b' (stable) (') 52 | if epos == -1: 53 | dart_version = None 54 | else: 55 | pos = data.rfind(b'\x00', 0, epos) + 1 56 | dart_version = data[pos:epos].decode() 57 | 58 | return engine_ids, dart_version, arch, 'android' 59 | 60 | def get_dart_sdk_url_size(engine_ids): 61 | #url = f'https://storage.googleapis.com/dart-archive/channels/stable/release/3.0.3/sdk/dartsdk-windows-x64-release.zip' 62 | for engine_id in engine_ids: 63 | url = f'https://storage.googleapis.com/flutter_infra_release/flutter/{engine_id}/dart-sdk-windows-x64.zip' 64 | resp = requests.head(url) 65 | if resp.status_code == 200: 66 | sdk_size = int(resp.headers['Content-Length']) 67 | return engine_id, url, sdk_size 68 | 69 | return None, None, None 70 | 71 | def get_dart_commit(url): 72 | # in downloaded zip 73 | # * dart-sdk/revision - the dart commit id of https://github.com/dart-lang/sdk/ 74 | # * dart-sdk/version - the dart version 75 | # revision and version zip file records should be in first 4096 bytes 76 | # using stream in case a server does not support range 77 | commit_id = None 78 | dart_version = None 79 | fp = None 80 | with requests.get(url, headers={"Range": "bytes=0-4096"}, stream=True) as r: 81 | if r.status_code // 10 == 20: 82 | x = next(r.iter_content(chunk_size=4096)) 83 | fp = io.BytesIO(x) 84 | 85 | if fp is not None: 86 | while fp.tell() < 4096-30 and (commit_id is None or dart_version is None): 87 | #sig, ver, flags, compression, filetime, filedate, crc, compressSize, uncompressSize, filenameLen, extraLen = unpack(fp, ' 0: 92 | fp.seek(extraLen, io.SEEK_CUR) 93 | data = fp.read(compressSize) 94 | 95 | # expect compression method to be zipfile.ZIP_DEFLATED 96 | assert compMethod == zipfile.ZIP_DEFLATED, 'Unexpected compression method' 97 | if filename == b'dart-sdk/revision': 98 | commit_id = zlib.decompress(data, wbits=-zlib.MAX_WBITS).decode().strip() 99 | elif filename == b'dart-sdk/version': 100 | dart_version = zlib.decompress(data, wbits=-zlib.MAX_WBITS).decode().strip() 101 | 102 | # TODO: if no revision and version in first 4096 bytes, get the file location from the first zip dir entries at the end of file (less than 256KB) 103 | return commit_id, dart_version 104 | 105 | def extract_dart_info(libapp_file: str, libflutter_file: str): 106 | snapshot_hash, flags = extract_snapshot_hash_flags(libapp_file) 107 | #print('snapshot hash', snapshot_hash) 108 | #print(flags) 109 | 110 | engine_ids, dart_version, arch, os_name = extract_libflutter_info(libflutter_file) 111 | # print('possible engine ids', engine_ids) 112 | # print('dart version', dart_version) 113 | 114 | if dart_version is None: 115 | engine_id, sdk_url, sdk_size = get_dart_sdk_url_size(engine_ids) 116 | # print(engine_id) 117 | # print(sdk_url) 118 | # print(sdk_size) 119 | 120 | commit_id, dart_version = get_dart_commit(sdk_url) 121 | # print(commit_id) 122 | # print(dart_version) 123 | #assert dart_version == dart_version_sdk 124 | 125 | # TODO: os (android or ios) and architecture (arm64 or x64) 126 | return dart_version, snapshot_hash, flags, arch, os_name 127 | 128 | 129 | if __name__ == "__main__": 130 | libdir = sys.argv[1] 131 | libapp_file = os.path.join(libdir, 'libapp.so') 132 | libflutter_file = os.path.join(libdir, 'libflutter.so') 133 | 134 | print(extract_dart_info(libapp_file, libflutter_file)) 135 | -------------------------------------------------------------------------------- /scripts/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 1 2 | # this is CMakeLists.txt template file for building dart AOT VM 3 | cmake_minimum_required (VERSION 3.20) 4 | 5 | project(dartvmVERSION_PLACE_HOLDER) 6 | if (${PROJECT_NAME} MATCHES "_PLACE_HOLDER") 7 | message(FATAL_ERROR "Do NOT use this file directly") 8 | endif() 9 | if(${CMAKE_SIZEOF_VOID_P} STREQUAL 4) 10 | message(FATAL_ERROR "Only 64-bit compiler here") 11 | endif() 12 | 13 | set(CMAKE_INSTALL_PREFIX "${PROJECT_SOURCE_DIR}/../../packages" CACHE PATH "" FORCE) 14 | 15 | if (MSVC) 16 | set(ICU_ROOT "${PROJECT_SOURCE_DIR}/../../external/icu-windows") 17 | elseif (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") 18 | set(ICU_ROOT "$ENV{HOMEBREW_PREFIX}/opt/icu4c") 19 | endif() 20 | find_package(ICU REQUIRED uc) 21 | 22 | string(SUBSTRING ${PROJECT_NAME} 6 -1 DART_VERSION) 23 | 24 | if (NOT DEFINED TARGET_OS) 25 | set(TARGET_OS "android") 26 | endif() 27 | if (NOT "${TARGET_OS}" MATCHES "^(android|ios)$") 28 | message(FATAL_ERROR "Only android or ios platform") 29 | endif() 30 | 31 | if (NOT DEFINED TARGET_ARCH) 32 | set(TARGET_ARCH "arm64") 33 | endif() 34 | if (NOT "${TARGET_ARCH}" MATCHES "^(arm64|x64)$") 35 | message(FATAL_ERROR "Only arm64 or x64 architecture") 36 | endif() 37 | 38 | set(SRCDIR "runtime") 39 | set(LIBNAME "dartvm${DART_VERSION}_${TARGET_OS}_${TARGET_ARCH}") 40 | 41 | set(CMAKE_DEBUG_POSTFIX "_d") 42 | 43 | # Add source to this project's executable. 44 | include(sourcelist.cmake) 45 | add_library(${LIBNAME} STATIC ${SRCS}) 46 | 47 | # use C++17 because compilation with c++20 is slower 48 | set_target_properties(${LIBNAME} PROPERTIES 49 | CXX_STANDARD 17 50 | CXX_STANDARD_REQUIRED True 51 | CXX_VISIBILITY_PRESET hidden 52 | CXX_STANDARD_REQUIRED ON 53 | VISIBILITY_INLINES_HIDDEN ON 54 | POSITION_INDEPENDENT_CODE ON) 55 | 56 | # DART_TARGET_OS_ANDROID or DART_TARGET_OS_MACOS_IOS 57 | if (${TARGET_OS} STREQUAL "android") 58 | set(target_os "DART_TARGET_OS_ANDROID") 59 | # Note: DART_COMPRESSED_POINTERS should be from the binary 60 | #set(dart_pointers "DART_COMPRESSED_POINTERS") 61 | else() 62 | set(target_os "DART_TARGET_OS_MACOS_IOS") 63 | endif() 64 | # TARGET_ARCH_ARM64 or TARGET_ARCH_X64 65 | if (${TARGET_ARCH} STREQUAL "arm64") 66 | set(target_arch "TARGET_ARCH_ARM64") 67 | else() 68 | set(target_arch "TARGET_ARCH_X64") 69 | endif() 70 | if (${COMPRESSED_PTRS}) 71 | set(dart_pointers "DART_COMPRESSED_POINTERS") 72 | endif() 73 | 74 | # DART_TARGET_OS_WINDOWS_UWP can be used to disable unused features on Windows such as native symbols lookup, Certificate Store 75 | # we never this it because we just want to load snapshot 76 | # DART_SHARED_LIB U_ENABLE_DYLOAD=0 U_STATIC_IMPLEMENTATION -DFORCE_INCLUDE_DISASSEMBLER 77 | set(defines ${target_arch} ${target_os} 78 | NDEBUG DART_PRECOMPILED_RUNTIME 79 | ${dart_pointers} EXCLUDE_CFE_AND_KERNEL_PLATFORM 80 | PRODUCT U_USING_ICU_NAMESPACE=0 81 | _HAS_EXCEPTIONS=0 DART_TARGET_OS_WINDOWS_UWP 82 | ) 83 | # Dart header can detect DART_HOST_OS_xxx value 84 | 85 | if (MSVC) 86 | # remove compiler exception handler options 87 | string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) 88 | # remove compiler RTTI option 89 | string(REPLACE "/GR" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) 90 | set(cc_opts 91 | /Oy /GR- /EHs-c- 92 | ) 93 | else() 94 | set(cc_opts 95 | -O3 -fno-ident -fdata-sections -ffunction-sections 96 | -fno-omit-frame-pointer -fno-rtti -fno-exceptions 97 | #-Wall 98 | ) 99 | endif() 100 | 101 | target_compile_definitions(${LIBNAME} PUBLIC ${defines}) 102 | 103 | target_compile_options(${LIBNAME} PRIVATE ${cc_opts}) 104 | 105 | target_include_directories(${LIBNAME} PRIVATE "${SRCDIR}" "${ICU_INCLUDE_DIRS}") 106 | target_include_directories(${LIBNAME} INTERFACE 107 | $ 108 | $ 109 | ) 110 | 111 | if (MSVC) 112 | target_link_libraries(${LIBNAME} PUBLIC ${ICU_LIBRARIES}) 113 | else() 114 | target_link_libraries(${LIBNAME} PUBLIC dl pthread ${ICU_LIBRARIES}) 115 | endif() 116 | 117 | # install lib 118 | set(CMAKE_INSTALL_MESSAGE LAZY) 119 | # export name MUST be same as Config.cmake.in that is generated from dartvm_fetch_build.py 120 | set(EXPORT_NAME "dartvmTarget") 121 | 122 | install(TARGETS ${LIBNAME} 123 | EXPORT "${EXPORT_NAME}" 124 | DESTINATION lib 125 | ) 126 | 127 | # install include files (all target OS and architecture uses same header files) 128 | install(DIRECTORY "${SRCDIR}/" 129 | DESTINATION "include/${PROJECT_NAME}" 130 | FILES_MATCHING 131 | PATTERN "*.h" 132 | PATTERN "bin*" EXCLUDE 133 | PATTERN "docs*" EXCLUDE 134 | PATTERN "lib*" EXCLUDE 135 | PATTERN "observatory*" EXCLUDE 136 | PATTERN "tests*" EXCLUDE 137 | PATTERN "tools*" EXCLUDE 138 | PATTERN "third_party*" EXCLUDE 139 | ) 140 | 141 | # install project cmake 142 | 143 | message(STATUS ${CMAKE_BUILD_TYPE}) 144 | install(EXPORT "${EXPORT_NAME}" 145 | FILE "${EXPORT_NAME}.cmake" 146 | DESTINATION lib/cmake/${LIBNAME} 147 | ) 148 | 149 | 150 | include(CMakePackageConfigHelpers) 151 | # generate the config file that is includes the exports 152 | set(CONFIG_CMAKE "${CMAKE_CURRENT_BINARY_DIR}/${LIBNAME}Config.cmake") 153 | set(CONFIG_VERSION_CMAKE "${CMAKE_CURRENT_BINARY_DIR}/${LIBNAME}ConfigVersion.cmake") 154 | 155 | configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in 156 | "${CONFIG_CMAKE}" 157 | INSTALL_DESTINATION "lib/cmake/${LIBNAME}" 158 | NO_SET_AND_CHECK_MACRO 159 | NO_CHECK_REQUIRED_COMPONENTS_MACRO 160 | ) 161 | 162 | write_basic_package_version_file( 163 | "${CONFIG_VERSION_CMAKE}" 164 | VERSION "${DART_VERSION}" 165 | COMPATIBILITY ExactVersion 166 | ) 167 | 168 | install(FILES 169 | "${CONFIG_CMAKE}" 170 | "${CONFIG_VERSION_CMAKE}" 171 | DESTINATION "lib/cmake/${LIBNAME}" 172 | ) 173 | -------------------------------------------------------------------------------- /scripts/dartvm_create_srclist.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import glob 3 | import os 4 | import re 5 | import sys 6 | 7 | def extract_sources(gni_file): 8 | with open(gni_file, 'r') as f: 9 | data = f.read() 10 | 11 | objs = {} 12 | matches = re.findall(r'\s*(\w+?)\s*=\s*\[\s*([\"\w\-\.\/\,\s]+?),?\s*\]\s*', data) 13 | for name, names in matches: 14 | srcs = re.findall(r'\"([\w\-\.]+)\",?\s*', names) 15 | objs[name] = srcs 16 | 17 | return objs 18 | 19 | def get_src_files(path): 20 | name = os.path.split(path)[-1] 21 | gni_file = os.path.join(path, name+'_sources.gni') 22 | objs = extract_sources(gni_file) 23 | return objs[name+'_sources'] 24 | 25 | def get_default_src_files(gni_file): 26 | objs = extract_sources(gni_file) 27 | for key in objs.keys(): 28 | if key.endswith('_cc_files'): 29 | return objs[key] 30 | elif key.endswith('_sources'): 31 | return objs[key] 32 | 33 | def get_src_from_path(path): 34 | srcs = glob.glob(os.path.join(path, '*.cc')) 35 | return srcs 36 | 37 | # runtime directory 38 | BASEDIR = sys.argv[1] if len(sys.argv) > 1 else '.' 39 | os.chdir(BASEDIR) 40 | BASEDIR = '.' 41 | 42 | # check if BASEDIR contains runtime directory in case user input is dart sdk directory 43 | tmpdir = os.path.join(BASEDIR, 'runtime') 44 | if os.path.isdir(tmpdir): 45 | SDKDIR = BASEDIR 46 | BASEDIR = tmpdir 47 | else: 48 | SDKDIR = os.path.join(BASEDIR, '..') 49 | 50 | cc_srcs = [] 51 | hdrs = [] 52 | for path in ('vm', 'platform', 'vm/heap', 'vm/ffi'): 53 | if path[-1] == '/': 54 | path = path[:-1] 55 | path = os.path.join(BASEDIR, path) 56 | if not os.path.isdir(path): 57 | continue 58 | srcs = get_src_files(path) 59 | #cc_srcs.extend([ os.path.join(path, src) for src in srcs if src.endswith('.cc') ]) 60 | for src in srcs: 61 | cc_srcs.append(os.path.join(path, src)) 62 | if src.endswith('h'): 63 | hdrs.append(os.path.join(path, src)) 64 | 65 | # extra source files 66 | extra_files = ( 'vm/version.cc', 'vm/dart_api_impl.cc', 'vm/native_api_impl.cc', 67 | 'vm/compiler/runtime_api.cc', 'vm/compiler/jit/compiler.cc') 68 | for name in extra_files: 69 | cc_srcs.append(os.path.join(BASEDIR, name)) 70 | 71 | # extra public heaer 72 | hdrs.append(os.path.join(BASEDIR, 'vm/version.h')) 73 | 74 | # other libraries 75 | for lib in ('async', 'concurrent', 'core', 'developer', 'ffi', 'isolate', 'math', 'typed_data', 'vmservice', 'internal'): 76 | gni_file = os.path.join(BASEDIR, 'lib', lib+'_sources.gni') 77 | if os.path.isfile(gni_file): 78 | srcs = get_default_src_files(gni_file) 79 | cc_srcs.extend([ os.path.join(BASEDIR, 'lib', src) for src in srcs if src.endswith('.cc') ]) 80 | 81 | # other vm sources 82 | for lib in ['regexp']: 83 | gni_file = os.path.join(BASEDIR, 'vm', lib, lib+'_sources.gni') 84 | if os.path.isfile(gni_file): 85 | srcs = get_default_src_files(gni_file) 86 | cc_srcs.extend([ os.path.join(BASEDIR, 'vm', lib, src) for src in srcs if src.endswith('.cc') ]) 87 | 88 | 89 | double_conversion_dir = BASEDIR+'/third_party/double-conversion/src' 90 | if not os.path.isdir(double_conversion_dir): 91 | double_conversion_dir = SDKDIR+'/third_party/double-conversion/src' 92 | assert os.path.isdir(double_conversion_dir) 93 | cc_srcs.extend(get_src_from_path(double_conversion_dir)) 94 | 95 | #print('VMSRCS='+' '.join(cc_srcs)) 96 | #print(' '.join(cc_srcs)) 97 | #with open('sources.list', 'w') as f: 98 | # f.write('\n'.join(cc_srcs)) 99 | 100 | # CMake file need forward slash in path even in Windows 101 | if os.sep == '\\': 102 | cc_srcs = [ src.replace(os.sep, '/') for src in cc_srcs ] 103 | hdrs = [ src.replace(os.sep, '/') for src in hdrs ] 104 | 105 | with open('sourcelist.cmake', 'w') as f: 106 | f.write('set(SRCS \n ') 107 | f.write('\n '.join(cc_srcs)) 108 | f.write('\n)\n') 109 | 110 | #f.write('\n') 111 | #f.write('set(PUB_HDRS \n ') 112 | #f.write('\n '.join(hdrs)) 113 | #f.write('\n)\n') 114 | 115 | -------------------------------------------------------------------------------- /scripts/dartvm_make_version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # my own convert Dart version_in.cc to version.cc 3 | # to avoid the python version incompatible from running 'tools/make_version.py' 4 | # but this script required snapshot hash as input 5 | import os 6 | import sys 7 | import subprocess 8 | 9 | def extract_tools_version(version_file): 10 | vals = {} 11 | with open(version_file, 'r') as f: 12 | for line in f: 13 | if line.startswith('#'): 14 | continue 15 | line = line.strip() 16 | if not line: 17 | continue 18 | k, v = line.split(' ', 1) 19 | vals[k] = v 20 | 21 | return vals 22 | 23 | def get_short_git_hash(): 24 | return subprocess.run(['git', 'rev-parse', '--short=10', 'HEAD'], capture_output=True, check=True).stdout.decode().strip() 25 | 26 | def get_git_timestamp(): 27 | return subprocess.run(['git', 'log', '-n', '1', '--pretty=format:%cd'], capture_output=True, check=True).stdout.decode().strip() 28 | 29 | 30 | # sdk directory 31 | SDK_DIR = sys.argv[1] 32 | SNAPSHOT_HASH = sys.argv[2] 33 | os.chdir(SDK_DIR) 34 | SDK_DIR = '.' 35 | 36 | # extract info from 'tools/VERSION' 37 | tools_version_file = os.path.join(SDK_DIR, 'tools', 'VERSION') 38 | version_info = extract_tools_version(tools_version_file) 39 | 40 | version_info['SNAPSHOT_HASH'] = SNAPSHOT_HASH 41 | version_info['GIT_HASH'] = get_short_git_hash() 42 | version_info['COMMIT_TIME'] = get_git_timestamp() 43 | # not same as Dart tools/make_version.py, but it is ok because it is just for displaying 44 | version_info['VERSION_STR'] = f'{version_info["MAJOR"]}.{version_info["MINOR"]}.{version_info["PATCH"]}' 45 | 46 | version_in_file = os.path.join(SDK_DIR, 'runtime', 'vm', 'version_in.cc') 47 | version_out_file = os.path.join(SDK_DIR, 'runtime', 'vm', 'version.cc') 48 | 49 | with open(version_in_file, 'r') as f: 50 | code = f.read() 51 | 52 | for k, v in version_info.items(): 53 | code = code.replace('{{' + k + '}}', v) 54 | 55 | with open(version_out_file, 'w') as f: 56 | f.write(code) 57 | -------------------------------------------------------------------------------- /scripts/extract_libflutter_functions.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from struct import pack, unpack 3 | from capstone import * 4 | from elftools.elf.elffile import ELFFile 5 | 6 | # Note: current only support AArch64 architecture 7 | def extract_libflutter_functions(libflutter_file): 8 | with open(libflutter_file, 'rb') as f: 9 | elf = ELFFile(f) 10 | section = elf.get_section_by_name('.rodata') 11 | rodata = section.data() 12 | rohdr = section.header 13 | 14 | gerVersion_text_offset = rohdr.sh_addr + rodata.index(b'\x00Platform_GetVersion\x00') + 1 15 | print(f'Platform_GetVersion text offset: {gerVersion_text_offset:#x}') 16 | gerVersion_text_offset = pack(' rohdr.sh_addr 26 | offset = addr - rohdr.sh_addr 27 | assert offset < rohdr.sh_size 28 | epos = rodata.index(b'\x00', offset) 29 | return rodata[offset:epos].decode() 30 | 31 | while (True): 32 | # normally, compiler put the rela entry in order 33 | # one entry for function name, and the next one is function entry point 34 | rela_entry_offset -= 24 * 2 # 24 is rela entry size 35 | str_addr = unpack('> 1; // Dart store string length as Smi 59 | return ptr.add(cls.dataOffset).readUtf8String(len); 60 | } 61 | 62 | function getDartTwoByteString(ptr, cls) { 63 | const len = ptr.add(cls.lenOffset).readU32() >> 1; // Dart store string length as Smi 64 | return ptr.add(cls.dataOffset).readUtf16String(len); 65 | } 66 | 67 | function getDartArray(ptr, cls, depthLeft, glen = null) { 68 | // TODO: type arguments 69 | // Dart store array length as Smi 70 | const len = glen === null ? ptr.add(cls.lenOffset).readU32() >> 1 : glen; 71 | let vals = []; 72 | let dataPtr = ptr.add(cls.dataOffset); 73 | for (let i = 0; i < len; i++) { 74 | let dptr = dataPtr.add(i * CompressedWordSize).readPointer(); 75 | const [tptr, ocls, fieldValue] = getTaggedObjectValue(dptr, depthLeft - 1); 76 | if ([CidNull, CidSmi, CidMint, CidDouble, CidBool, CidString, CidTwoByteString].includes(ocls.id)) { 77 | vals.push(fieldValue); 78 | } 79 | else { 80 | const key = `${ocls.name}@${tptr.toString().slice(2)}`; 81 | vals.push({key: fieldValue}); 82 | } 83 | } 84 | return vals; 85 | } 86 | 87 | function getDartGrowableArray(ptr, cls, depthLeft) { 88 | // TODO: type arguments 89 | const len = ptr.add(cls.lenOffset).readU32() >> 1; // Dart store array length as Smi 90 | let arrPtr = ptr.add(cls.dataOffset); 91 | return getDartArray(arrPtr, Classes[CidArray], depthLeft, len); 92 | } 93 | 94 | function getDartTypedArrayValues(ptr, cls, elementSize, readValFn) { 95 | const len = ptr.add(cls.lenOffset).readU32() >> 1; 96 | let dataPtr = ptr.add(cls.dataOffset); 97 | let vals = []; 98 | for (let i = 0; i < len; i++) { 99 | let val = readValFn(dataPtr.add(i * elementSize)); 100 | vals.push(val); 101 | } 102 | return vals; 103 | } 104 | 105 | function isFieldNative(fieldBitmap, offset) { 106 | const idx = offset / CompressedWordSize; 107 | return (fieldBitmap & (1 << idx)) !== 0; 108 | } 109 | 110 | // tptr (tagged pointer) is only for tagged object (HeapBit in address except Smi) 111 | // return format: value 112 | function getObjectValue(ptr, cls, depthLeft = MaxDepth) { 113 | switch (cls.id) { 114 | case CidObject: 115 | console.error(`Object cid should not reach here`); 116 | return; 117 | case CidNull: 118 | return null; 119 | case CidBool: 120 | return getDartBool(ptr, cls); 121 | case CidString: 122 | return getDartString(ptr, cls); 123 | case CidTwoByteString: 124 | return getDartTwoByteString(ptr, cls); 125 | case CidMint: 126 | return getDartMint(ptr, cls); 127 | case CidDouble: 128 | return getDartDouble(ptr, cls); 129 | case CidArray: 130 | return getDartArray(ptr, cls, depthLeft); 131 | case CidGrowableArray: 132 | return getDartGrowableArray(ptr, cls, depthLeft); 133 | case CidUint8Array: 134 | return getDartTypedArrayValues(ptr, cls, 1, (p) => p.readU8()); 135 | case CidInt8Array: 136 | return getDartTypedArrayValues(ptr, cls, 1, (p) => p.readS8()); 137 | case CidUint16Array: 138 | return getDartTypedArrayValues(ptr, cls, 2, (p) => p.readU16()); 139 | case CidInt16Array: 140 | return getDartTypedArrayValues(ptr, cls, 2, (p) => p.readS16()); 141 | case CidUint32Array: 142 | return getDartTypedArrayValues(ptr, cls, 4, (p) => p.readU32()); 143 | case CidInt32Array: 144 | return getDartTypedArrayValues(ptr, cls, 4, (p) => p.readS32()); 145 | case CidUint64Array: 146 | return getDartTypedArrayValues(ptr, cls, 8, (p) => p.readU64()); 147 | case CidInt64Array: 148 | return getDartTypedArrayValues(ptr, cls, 8, (p) => p.readS64()); 149 | } 150 | 151 | if (cls.id < NumPredefinedCids) { 152 | const msg = `Unhandle class id: ${cls.id}, ${cls.name}`; 153 | console.log(msg); 154 | return msg; 155 | } 156 | 157 | if (depthLeft <= 0) { 158 | return 'no more recursive'; 159 | } 160 | 161 | // find parent tree 162 | let parents = []; 163 | let scls = Classes[cls.sid]; 164 | while (scls.id != CidObject) { 165 | parents.push(scls); 166 | scls = Classes[scls.sid]; 167 | } 168 | // get value from top parent to bottom parent 169 | let values = {}; 170 | while (parents.length > 0) { 171 | const sscls = scls; 172 | scls = parents.pop(); 173 | const parentValue = getInstanceValue(ptr, scls, sscls, depthLeft); 174 | values[`parent!${scls.name}`] = parentValue; 175 | } 176 | const myValue = getInstanceValue(ptr, cls, scls, depthLeft); 177 | Object.assign(values, myValue); 178 | return values; 179 | } 180 | 181 | function getInstanceValue(ptr, cls, scls, depthLeft = MaxDepth) { 182 | let values = {}; 183 | let offset = scls.size; 184 | while (offset < cls.size) { 185 | if (offset == cls.argOffset) { 186 | // TODO: type arguments 187 | offset += CompressedWordSize; 188 | } 189 | else if (isFieldNative(cls.fbitmap, offset)) { 190 | if (PointerCompressedEnabled && !isFieldNative(cls.fbitmap, offset + CompressedWordSize)) 191 | console.error("Native type but use only 4 bytes"); 192 | 193 | let val = ptr.add(offset).readU64(); 194 | // TODO: this might not work on javascript because all number are floating pointer (except BigInt) 195 | // it is rare to find integer that larger than 0x1000_0000_0000_0000 196 | // while double is very common because of exponent value 197 | // to know exact type (int or double), we have to check from register type in assembly 198 | if (val <= 0x1000000000000000n || val >= 0xffffffffffff0000n) { 199 | // it should be integer 200 | values[`off_${offset.toString(16)}`] = val; 201 | } 202 | else { 203 | val = ptr.add(offset).readDouble(); 204 | values[`off_${offset.toString(16)}`] = val; 205 | } 206 | offset += CompressedWordSize * 2; 207 | } 208 | else { 209 | // object 210 | let dptr = ptr.add(offset).readPointer(); 211 | const [tptr, ocls, fieldValue] = getTaggedObjectValue(dptr, depthLeft - 1); 212 | if (ocls.id === CidSmi) { 213 | values[`off_${offset.toString(16)}!Smi`] = fieldValue; 214 | } 215 | else if (ocls.id !== CidNull) { 216 | values[`off_${offset.toString(16)}!${ocls.name}@${tptr.toString().slice(2)}`] = fieldValue; 217 | } 218 | else if (ShowNullField) { 219 | values[`off_${offset.toString(16)}`] = fieldValue; 220 | } 221 | offset += CompressedWordSize; 222 | } 223 | } 224 | 225 | return values; 226 | } 227 | 228 | // tptr (tagged pointer) is only for tagged object (HeapBit in address except Smi) 229 | // return format: [tptr, cls, values] 230 | function getTaggedObjectValue(tptr, depthLeft = MaxDepth) { 231 | if (!isHeapObject(tptr)) { 232 | // smi 233 | // TODO: below support only compressed pointer (4 bytes) 234 | return [tptr, Classes[CidSmi], tptr.toInt32() >> 1]; 235 | } 236 | 237 | tptr = decompressPointer(tptr); 238 | let ptr = tptr.sub(1); 239 | const cls = Classes[getObjectCid(ptr)]; 240 | const values = getObjectValue(ptr, cls, depthLeft); 241 | return [tptr, cls, values]; 242 | } 243 | 244 | function getArg(context, idx) { 245 | // Note: argument pointer is never compressed 246 | let stack = context[StackReg]; 247 | return stack.add(8 * idx).readPointer(); 248 | } 249 | 250 | function isHeapObject(ptr) { 251 | return (ptr.toInt32() & 1) == 1; 252 | } 253 | 254 | function getObjectTag(ptr) { 255 | const tag = ptr.readU64(); 256 | const objSize = ((tag >> 8) & 0xf) * 8; 257 | const cid = (tag >> ClassIdTagPos) & ClassIdTagMask; 258 | //const hashId = (tag >> 32) & 0xffffffff; 259 | return [cid, objSize]; 260 | } 261 | 262 | function getObjectCid(ptr) { 263 | const tag = ptr.readU32(); 264 | return (tag >> ClassIdTagPos) & ClassIdTagMask; 265 | } 266 | 267 | function decompressPointer(dptr) { 268 | if (PointerCompressedEnabled) { 269 | if (HeapAddress === 0) 270 | console.error("Uninitialized HeapAddress"); 271 | return HeapAddress.add(dptr.toInt32()); 272 | } 273 | return dptr; 274 | } 275 | -------------------------------------------------------------------------------- /scripts/generate_thread_offsets_cpp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # This script parses the offset at runtime/vm/thread.h 3 | import sys 4 | import re 5 | 6 | hdr_file = sys.argv[1] 7 | with open(hdr_file, 'r') as f: 8 | content = f.read() 9 | 10 | 11 | names = re.findall(r'\sOFFSET_OF\(Thread, (\w+?)_\);', content) 12 | for name in names: 13 | if name.startswith('ffi_'): 14 | method = name[4:] 15 | elif name.startswith('thread_'): 16 | method = name[7:] 17 | name = method 18 | else: 19 | method = name 20 | print(f'threadOffsetNames[dart::Thread::{method}_offset()] = "{name}";') 21 | -------------------------------------------------------------------------------- /scripts/init_env_win.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Setup required libraries for compiling blutter 3 | # Note: for Windows only 4 | import io 5 | import os 6 | import requests 7 | import shutil 8 | import zipfile 9 | from pathlib import Path 10 | 11 | ICU_LIB_URL = 'https://github.com/unicode-org/icu/releases/download/release-73-2/icu4c-73_2-Win64-MSVC2019.zip' 12 | # Note: capstone 5 has no pre-built yet 13 | CAPSTONE_LIB_URL = 'https://github.com/capstone-engine/capstone/releases/download/4.0.2/capstone-4.0.2-win64.zip' 14 | 15 | SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) 16 | BIN_DIR = os.path.join(SCRIPT_DIR, '..', 'bin') 17 | EXTERNAL_DIR = os.path.join(SCRIPT_DIR, '..', 'external') 18 | ICU_WINDOWS_FILE = os.path.join(EXTERNAL_DIR, 'icu-windows.zip') 19 | ICU_WINDOWS_DIR = os.path.join(EXTERNAL_DIR, 'icu-windows') 20 | CAPSTONE_DIR = os.path.join(EXTERNAL_DIR, 'capstone') 21 | 22 | Path(BIN_DIR).mkdir(parents=True, exist_ok=True) 23 | Path(EXTERNAL_DIR).mkdir(parents=True, exist_ok=True) 24 | if os.path.exists(CAPSTONE_DIR): 25 | shutil.rmtree(CAPSTONE_DIR) 26 | 27 | # icu 28 | print('Downloading ICU library from ' + ICU_LIB_URL) 29 | r = requests.get(ICU_LIB_URL) 30 | print('Extracting ICU library') 31 | with zipfile.ZipFile(io.BytesIO(r.content)) as z: 32 | with z.open(z.namelist()[-1]) as zf, open(ICU_WINDOWS_FILE, 'wb') as f: 33 | shutil.copyfileobj(zf, f) 34 | 35 | with zipfile.ZipFile(ICU_WINDOWS_FILE) as z: 36 | z.extractall(ICU_WINDOWS_DIR) 37 | os.remove(ICU_WINDOWS_FILE) 38 | 39 | # capstone 40 | print('Downloading Capstone from ' + CAPSTONE_LIB_URL) 41 | r = requests.get(CAPSTONE_LIB_URL) 42 | print('Extracting Capstone library') 43 | with zipfile.ZipFile(io.BytesIO(r.content)) as z: 44 | capstone_zip_dir = z.namelist()[0].split('/', 1)[0] 45 | z.extractall(EXTERNAL_DIR) 46 | 47 | os.rename(os.path.join(EXTERNAL_DIR, capstone_zip_dir), CAPSTONE_DIR) 48 | 49 | # copy to bin (version of icu dll MUST be updated) 50 | print('Copying dlls to bin directory') 51 | shutil.copy(os.path.join(CAPSTONE_DIR, 'capstone.dll'), BIN_DIR) 52 | shutil.copy(os.path.join(ICU_WINDOWS_DIR, 'bin64', 'icudt73.dll'), BIN_DIR) 53 | shutil.copy(os.path.join(ICU_WINDOWS_DIR, 'bin64', 'icuuc73.dll'), BIN_DIR) 54 | 55 | print('Done') 56 | --------------------------------------------------------------------------------