forked from worawit/blutter
-
Notifications
You must be signed in to change notification settings - Fork 14
/
extract_dart_info.py
134 lines (112 loc) · 5.3 KB
/
extract_dart_info.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import io
import os
import re
import requests
import sys
import zipfile
import zlib
from struct import unpack
from elftools.elf.elffile import ELFFile
from elftools.elf.enums import ENUM_E_MACHINE
from elftools.elf.sections import SymbolTableSection
# TODO: support both ELF and Mach-O file
def extract_snapshot_hash_flags(libapp_file):
with open(libapp_file, 'rb') as f:
elf = ELFFile(f)
# find "_kDartVmSnapshotData" symbol
dynsym = elf.get_section_by_name('.dynsym')
sym = dynsym.get_symbol_by_name('_kDartVmSnapshotData')[0]
#section = elf.get_section(sym['st_shndx'])
assert sym['st_size'] > 128
f.seek(sym['st_value']+20)
snapshot_hash = f.read(32).decode()
data = f.read(256) # should be enough
flags = data[:data.index(b'\0')].decode().strip().split(' ')
return snapshot_hash, flags
def extract_libflutter_info(libflutter_file):
with open(libflutter_file, 'rb') as f:
elf = ELFFile(f)
if elf.header.e_machine == 'EM_AARCH64': # 183
arch = 'arm64'
elif elf.header.e_machine == 'EM_IA_64': # 50
arch = 'x64'
else:
assert False, "Unsupport architecture: " + elf.header.e_machine
section = elf.get_section_by_name('.rodata')
data = section.data()
sha_hashes = re.findall(b'\x00([a-f\\d]{40})(?=\x00)', data)
#print(sha_hashes)
# all possible engine ids
engine_ids = [ h.decode() for h in sha_hashes ]
assert len(engine_ids) == 2, f'found hashes {", ".join(engine_ids)}'
# beta/dev version of flutter might not use stable dart version (we can get dart version from sdk with found engine_id)
# support only stable
epos = data.find(b' (stable) (')
if epos == -1:
dart_version = None
else:
pos = data.rfind(b'\x00', 0, epos) + 1
dart_version = data[pos:epos].decode()
return engine_ids, dart_version, arch, 'android'
def get_dart_sdk_url_size(engine_ids):
#url = f'https://storage.googleapis.com/dart-archive/channels/stable/release/3.0.3/sdk/dartsdk-windows-x64-release.zip'
for engine_id in engine_ids:
url = f'https://storage.googleapis.com/flutter_infra_release/flutter/{engine_id}/dart-sdk-windows-x64.zip'
resp = requests.head(url)
if resp.status_code == 200:
sdk_size = int(resp.headers['Content-Length'])
return engine_id, url, sdk_size
return None, None, None
def get_dart_commit(url):
# in downloaded zip
# * dart-sdk/revision - the dart commit id of https://github.com/dart-lang/sdk/
# * dart-sdk/version - the dart version
# revision and version zip file records should be in first 4096 bytes
# using stream in case a server does not support range
commit_id = None
dart_version = None
fp = None
with requests.get(url, headers={"Range": "bytes=0-4096"}, stream=True) as r:
if r.status_code // 10 == 20:
x = next(r.iter_content(chunk_size=4096))
fp = io.BytesIO(x)
if fp is not None:
while fp.tell() < 4096-30 and (commit_id is None or dart_version is None):
#sig, ver, flags, compression, filetime, filedate, crc, compressSize, uncompressSize, filenameLen, extraLen = unpack(fp, '<IHHHHHIIIHH')
_, _, _, compMethod, _, _, _, compressSize, _, filenameLen, extraLen = unpack('<IHHHHHIIIHH', fp.read(30))
filename = fp.read(filenameLen)
#print(filename)
if extraLen > 0:
fp.seek(extraLen, io.SEEK_CUR)
data = fp.read(compressSize)
# expect compression method to be zipfile.ZIP_DEFLATED
assert compMethod == zipfile.ZIP_DEFLATED, 'Unexpected compression method'
if filename == b'dart-sdk/revision':
commit_id = zlib.decompress(data, wbits=-zlib.MAX_WBITS).decode().strip()
elif filename == b'dart-sdk/version':
dart_version = zlib.decompress(data, wbits=-zlib.MAX_WBITS).decode().strip()
# 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)
return commit_id, dart_version
def extract_dart_info(libapp_file: str, libflutter_file: str):
snapshot_hash, flags = extract_snapshot_hash_flags(libapp_file)
#print('snapshot hash', snapshot_hash)
#print(flags)
engine_ids, dart_version, arch, os_name = extract_libflutter_info(libflutter_file)
# print('possible engine ids', engine_ids)
# print('dart version', dart_version)
if dart_version is None:
engine_id, sdk_url, sdk_size = get_dart_sdk_url_size(engine_ids)
# print(engine_id)
# print(sdk_url)
# print(sdk_size)
commit_id, dart_version = get_dart_commit(sdk_url)
# print(commit_id)
# print(dart_version)
#assert dart_version == dart_version_sdk
# TODO: os (android or ios) and architecture (arm64 or x64)
return dart_version, snapshot_hash, flags, arch, os_name
if __name__ == "__main__":
libdir = sys.argv[1]
libapp_file = os.path.join(libdir, 'libapp.so')
libflutter_file = os.path.join(libdir, 'libflutter.so')
print(extract_dart_info(libapp_file, libflutter_file))