Skip to content

Instantly share code, notes, and snippets.

@obfusk
Last active January 13, 2023 12:42
Show Gist options
  • Select an option

  • Save obfusk/98249517a329dc33911b71ed344e7db3 to your computer and use it in GitHub Desktop.

Select an option

Save obfusk/98249517a329dc33911b71ed344e7db3 to your computer and use it in GitHub Desktop.
#!/usr/bin/python3
# encoding: utf-8
# SPDX-FileCopyrightText: 2023 FC Stegerman <[email protected]>
# SPDX-License-Identifier: GPL-3.0-or-later
import struct
import zlib
from typing import Any, BinaryIO, Tuple
# https://android.googlesource.com/platform/tools/base
# profgen/profgen/src/main/kotlin/com/android/tools/profgen/ArtProfileSerializer.kt
PROF_MAGIC = b"pro\x00"
PROFM_MAGIC = b"prm\x00"
PROF_001_N = b"001\x00"
PROF_005_O = b"005\x00"
PROF_009_O_MR1 = b"009\x00"
PROF_010_P = b"010\x00"
PROF_015_S = b"015\x00"
PROFM_001_N = b"001\x00"
PROFM_002 = b"002\x00"
INLINE_CACHE_MISSING_TYPES_ENCODING = 6
INLINE_CACHE_MEGAMORPHIC_ENCODING = 7
class Error(RuntimeError):
pass
# FIXME
def dump_prof_profm(file: str, verbose: bool = False) -> None:
with open(file, "rb") as fh:
magic = fh.read(4)
version = fh.read(4)
if magic == PROF_MAGIC:
if version == PROF_010_P:
print("prof version=010 P")
dump_prof_010_p(fh, verbose)
else:
raise Error(f"Unsupported prof version {version!r}")
elif magic == PROFM_MAGIC:
if version == PROFM_002:
print("profm version=002")
dump_profm_002(fh, verbose)
else:
raise Error(f"Unsupported profm version {version!r}")
else:
raise Error(f"Unsupported magic {magic!r}")
def dump_prof_010_p(fh: BinaryIO, verbose: bool) -> None:
num_dex_files, uncompressed_data_size, compressed_data_size = \
struct.unpack("<BII", fh.read(9))
print(f"num_dex_files={num_dex_files}")
if verbose:
print(f"uncompressed_data_size={uncompressed_data_size}")
print(f"compressed_data_size={compressed_data_size}")
data = zlib.decompress(fh.read(compressed_data_size))
# FIXME
if fh.read():
raise Error("Expected EOF")
if len(data) != uncompressed_data_size:
raise Error("Uncompressed data size does not match")
info = []
for i in range(num_dex_files):
print(f"dex_data_header {i}")
profile_key_size, num_type_ids, hot_method_region_size, \
dex_checksum, num_method_ids, data = _unpack("<HHIII", data)
profile_key, data = _split(data, profile_key_size)
info.append((num_type_ids, hot_method_region_size, num_method_ids))
print(f" profile_key={profile_key.decode()}")
print(f" num_type_ids={num_type_ids}")
if verbose:
print(f" hot_method_region_size={hot_method_region_size}")
print(f" dex_checksum=0x{dex_checksum:x}")
print(f" num_method_ids={num_method_ids}")
for i in range(num_dex_files):
print(f"dex_data {i}")
num_type_ids, hot_method_region_size, num_method_ids = info[i]
region, data = _split(data, hot_method_region_size)
num_hot_method_ids = 0
mi_delta = 0
while region:
num_hot_method_ids += 1
method_id, num_inline_caches, region = _unpack("<HH", region)
method_id += mi_delta
mi_delta = method_id
if verbose:
print(f" method_id={method_id}")
print(f" num_inline_caches={num_inline_caches}")
# skip inline caches
_skip_inline_caches(PROF_010_P, region, num_inline_caches)
print(f" num_hot_method_ids={num_hot_method_ids}")
ti_delta = 0
for _ in range(num_type_ids):
type_id, data = _unpack("<H", data)
type_id += ti_delta
ti_delta = type_id
if verbose:
print(f" type_id={type_id}")
# FIXME: skip bitmap
bitmap_size = _bitmap_storage_size(num_method_ids)
_bitmap, data = _split(data, bitmap_size)
print(f" bitmap_size={bitmap_size}")
if data:
raise Error("Expected end of data")
def dump_profm_002(fh: BinaryIO, verbose: bool) -> None:
num_dex_files, uncompressed_data_size, compressed_data_size = \
struct.unpack("<HII", fh.read(10))
print(f"num_dex_files={num_dex_files}")
if verbose:
print(f"uncompressed_data_size={uncompressed_data_size}")
print(f"compressed_data_size={compressed_data_size}")
data = zlib.decompress(fh.read(compressed_data_size))
# FIXME
if fh.read():
raise Error("Expected EOF")
if len(data) != uncompressed_data_size:
raise Error("Uncompressed data size does not match")
for _ in range(num_dex_files):
profile_idx, profile_key_size, data = _unpack("<HH", data)
profile_key, data = _split(data, profile_key_size)
print(f"profile_idx={profile_idx}")
print(f" profile_key={profile_key.decode()}")
num_type_ids, num_class_ids, data = _unpack("<IH", data)
print(f" num_type_ids={num_type_ids}")
print(f" num_class_ids={num_class_ids}")
ci_delta = 0
for _ in range(num_class_ids):
class_id, data = _unpack("<H", data)
class_id += ci_delta
ci_delta = class_id
if verbose:
print(f" class_id={class_id}")
if data:
raise Error("Expected end of data")
def _skip_inline_caches(version: bytes, region: bytes, num_inline_caches: int) -> bytes:
if version <= PROF_010_P:
for _ in range(num_inline_caches):
_dex_pc, dex_map_size, region = _unpack("<HB", region)
if dex_map_size in (INLINE_CACHE_MISSING_TYPES_ENCODING,
INLINE_CACHE_MEGAMORPHIC_ENCODING):
continue
for _ in range(dex_map_size):
_dex_profile_idx, num_classes, region = _unpack("<BB", region)
_, region = _split(region, 2 * num_classes)
return region
else:
raise Error(f"Unsupported version {version!r}")
def _bitmap_storage_size(num_method_ids: int) -> int:
byte, bits = 8, num_method_ids * 2
return (bits + byte - 1 & -byte) // byte
def _unpack(fmt: str, data: bytes) -> Any:
size = fmt.count("B") + 2 * fmt.count("H") + 4 * fmt.count("I")
return struct.unpack(fmt, data[:size]) + (data[size:],)
def _split(data: bytes, size: int) -> Tuple[bytes, bytes]:
return data[:size], data[size:]
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(prog="dump-baseline.py")
parser.add_argument("-v", "--verbose", action="store_true")
parser.add_argument("file", metavar="FILE")
args = parser.parse_args()
dump_prof_profm(args.file, args.verbose)
# vim: set tw=80 sw=4 sts=4 et fdm=marker :
#!/usr/bin/python3
# encoding: utf-8
# SPDX-FileCopyrightText: 2023 FC Stegerman <[email protected]>
# SPDX-License-Identifier: GPL-3.0-or-later
import struct
import zlib
from typing import Any, BinaryIO, Tuple
# https://android.googlesource.com/platform/tools/base
# profgen/profgen/src/main/kotlin/com/android/tools/profgen/ArtProfileSerializer.kt
PROF_MAGIC = b"pro\x00"
PROFM_MAGIC = b"prm\x00"
PROF_001_N = b"001\x00"
PROF_005_O = b"005\x00"
PROF_009_O_MR1 = b"009\x00"
PROF_010_P = b"010\x00"
PROF_015_S = b"015\x00"
PROFM_001_N = b"001\x00"
PROFM_002 = b"002\x00"
class Error(RuntimeError):
pass
# FIXME
def sort_prof_profm(input_file: str, output_file: str) -> None:
with open(input_file, "rb") as fhi:
magic = fhi.read(4)
version = fhi.read(4)
if magic == PROF_MAGIC:
raise Error(f"Unsupported prof version {version!r}")
elif magic == PROFM_MAGIC:
if version == PROFM_002:
with open(output_file, "wb") as fho:
fho.write(PROFM_MAGIC + PROFM_002)
fho.write(sort_profm_002(fhi))
else:
raise Error(f"Unsupported profm version {version!r}")
else:
raise Error(f"Unsupported magic {magic!r}")
def sort_profm_002(fh: BinaryIO) -> bytes:
num_dex_files, uncompressed_data_size, compressed_data_size = \
struct.unpack("<HII", fh.read(10))
data = zlib.decompress(fh.read(compressed_data_size))
profiles = []
if fh.read():
raise Error("Expected EOF")
if len(data) != uncompressed_data_size:
raise Error("Uncompressed data size does not match")
for _ in range(num_dex_files):
profile = data[:4]
profile_idx, profile_key_size, data = _unpack("<HH", data)
profile_key, data = _split(data, profile_key_size)
profile += profile_key + data[:6]
num_type_ids, num_class_ids, data = _unpack("<IH", data)
class_ids, data = _split(data, num_class_ids * 2)
profile += class_ids
profiles.append((profile_key, profile))
if data:
raise Error("Expected end of data")
srtd = b"".join(int.to_bytes(i, 2, "little") + p[1][2:]
for i, p in enumerate(sorted(profiles)))
cdata = zlib.compress(srtd, 1)
hdr = struct.pack("<HII", num_dex_files, uncompressed_data_size, len(cdata))
return hdr + cdata
def _unpack(fmt: str, data: bytes) -> Any:
size = fmt.count("B") + 2 * fmt.count("H") + 4 * fmt.count("I")
return struct.unpack(fmt, data[:size]) + (data[size:],)
def _split(data: bytes, size: int) -> Tuple[bytes, bytes]:
return data[:size], data[size:]
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(prog="sort-baseline.py")
parser.add_argument("input_file", metavar="INPUT_FILE")
parser.add_argument("output_file", metavar="OUTPUT_FILE")
args = parser.parse_args()
sort_prof_profm(args.input_file, args.output_file)
# vim: set tw=80 sw=4 sts=4 et fdm=marker :
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment