├── .gitignore ├── .gitmodules └── probe.py /.gitignore: -------------------------------------------------------------------------------- 1 | /cache/ 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "aosp/platform/art"] 2 | path = aosp/platform/art 3 | url = https://android.googlesource.com/platform/art 4 | [submodule "aosp/platform/system/core"] 5 | path = aosp/platform/system/core 6 | url = https://android.googlesource.com/platform/system/core 7 | [submodule "aosp/platform/external/gtest"] 8 | path = aosp/platform/external/gtest 9 | url = https://android.googlesource.com/platform/external/gtest 10 | -------------------------------------------------------------------------------- /probe.py: -------------------------------------------------------------------------------- 1 | # Note: This tool requires Python >= 3.7 2 | 3 | import collections 4 | import glob 5 | import os 6 | import json 7 | import re 8 | import subprocess 9 | import tempfile 10 | 11 | 12 | root_dir = os.path.dirname(os.path.abspath(__file__)) 13 | aosp_dir = os.path.join(root_dir, "aosp") 14 | cache_dir = os.path.join(root_dir, "cache") 15 | art_repo_dir = os.path.join(aosp_dir, "platform", "art") 16 | gtest_repo_dir = os.path.join(aosp_dir, "platform", "external", "gtest") 17 | 18 | host_triplets_generic = { 19 | "x86": "i686-linux-android", 20 | "x86_64": "x86_64-linux-android", 21 | "arm": "arm-linux-androideabi", 22 | "arm64": "aarch64-linux-android", 23 | } 24 | host_triplets_abi = { 25 | "x86": "i686-linux-android", 26 | "x86_64": "x86_64-linux-android", 27 | "arm": "armv7a-linux-androideabi", 28 | "arm64": "aarch64-linux-android", 29 | } 30 | api_levels = { 31 | "5.0": 21, 32 | "5.1": 22, 33 | "6.0": 23, 34 | "7.0": 24, 35 | "7.1": 25, 36 | "8.0": 26, 37 | "8.1": 27, 38 | "9.0": 28, 39 | "10.0": 29, 40 | } 41 | 42 | tag_pattern = re.compile(r"^android-(((\d)\.(\d))|(q)-)") 43 | section_data_pattern = re.compile(r"^\s+\d+\s+([\w\s]+)\s{2,}", re.MULTILINE) 44 | 45 | ignored_errors = [ 46 | "is not a member of", 47 | "has no member named", 48 | ] 49 | 50 | 51 | def main(): 52 | tags = compute_tags_affecting("runtime/mirror/art_method.h", "runtime/art_method.h") 53 | 54 | versions = [AndroidVersion.from_tag(tag) for tag in tags] 55 | 56 | result = collections.OrderedDict() 57 | for arch in ["arm", "x86", "arm64", "x86_64"]: 58 | for version in versions: 59 | size, access_flags = probe_offsets("runtime/mirror/art_method.h", "art::mirror::ArtMethod", ["access_flags_"], version, arch) 60 | if size < 0: 61 | size, access_flags = probe_offsets("runtime/art_method.h", "art::ArtMethod", ["access_flags_"], version, arch) 62 | 63 | key = "{}-{}".format(arch, version.api_level) 64 | value = "size={} access_flags={}".format(size, access_flags) 65 | 66 | print("// {} => {}".format(key, value)) 67 | 68 | entries = result.get(key, []) 69 | entries.append(value) 70 | entries = list(set(entries)) 71 | entries.sort() 72 | result[key] = entries 73 | 74 | print(json.dumps(result, indent=2)) 75 | 76 | def compute_tags_affecting(*paths): 77 | tags = [] 78 | for path in paths: 79 | tags += compute_tags_affecting_path(path) 80 | return list(dict.fromkeys(tags).keys()) 81 | 82 | def compute_tags_affecting_path(path): 83 | result = [] 84 | 85 | tags = filter(is_relevant_tag, run_in_art_repo("git", "tag", "--sort=committerdate").split("\n")) 86 | 87 | previous_tag = None 88 | for i, tag in enumerate(tags): 89 | if i == 0: 90 | result.append(tag) 91 | else: 92 | diff = run_in_art_repo("git", "diff", previous_tag, tag, "--", path) 93 | if len(diff) > 0: 94 | result.append(tag) 95 | 96 | previous_tag = tag 97 | 98 | return result 99 | 100 | def probe_offsets(header, class_name, field_names, version, arch): 101 | system_core_dir = get_aosp_checkout(["platform", "system", "core"], version) 102 | art_dir = get_aosp_checkout(["platform", "art"], version) 103 | 104 | header_path = os.path.join(art_dir, header) 105 | if not os.path.exists(header_path): 106 | return [-1] + [-1 for n in field_names] 107 | 108 | with tempfile.NamedTemporaryFile(prefix="probe", suffix=".cc", mode="w", encoding="utf-8") as probe_source: 109 | includes = [ 110 | "#include ", 111 | "#include ", 112 | "#include <{}>".format(header) 113 | ] 114 | 115 | queries = ["sizeof ({})".format(class_name)] 116 | queries += ["offsetof ({}, {})".format(class_name, field_name) for field_name in field_names] 117 | 118 | probe_source.write("""\ 119 | #include 120 | 121 | {includes} 122 | 123 | unsigned int values[] = 124 | {{ 125 | {queries} 126 | }}; 127 | """.format(includes="\n".join(includes), queries=",\n ".join(queries))) 128 | probe_source.flush() 129 | 130 | with open(header_path, "r", encoding="utf-8") as f: 131 | header_source = f.read() 132 | header_source = header_source.replace("protected:", "public:").replace("private:", "public:") 133 | with open(header_path, "w", encoding="utf-8") as f: 134 | f.write(header_source) 135 | 136 | if version.major >= 7: 137 | toolchain = get_toolchain(arch, "clang") 138 | system_core_includes = [ 139 | "-I", os.path.join(system_core_dir, "include"), 140 | "-I", os.path.join(system_core_dir, "base", "include"), 141 | ] 142 | else: 143 | toolchain = get_toolchain(arch, "gcc") 144 | system_core_includes = [ 145 | ] 146 | 147 | probe_obj = os.path.splitext(probe_source.name)[0] + ".o" 148 | try: 149 | result = subprocess.run([ 150 | toolchain.cxx 151 | ] + toolchain.cxxflags + [ 152 | "-DANDROID_SMP=1", 153 | "-DIMT_SIZE=64", 154 | "-DART_STACK_OVERFLOW_GAP_arm=8192", 155 | "-DART_STACK_OVERFLOW_GAP_arm64=8192", 156 | "-DART_STACK_OVERFLOW_GAP_mips=16384", 157 | "-DART_STACK_OVERFLOW_GAP_mips64=16384", 158 | "-DART_STACK_OVERFLOW_GAP_x86=8192", 159 | "-DART_STACK_OVERFLOW_GAP_x86_64=8192", 160 | "-Wno-invalid-offsetof", 161 | ] + system_core_includes + [ 162 | "-I", os.path.join(gtest_repo_dir, "include"), 163 | "-I", os.path.join(art_dir, "libartbase"), 164 | "-I", os.path.join(art_dir, "libdexfile"), 165 | "-I", os.path.join(art_dir, "runtime"), 166 | "-I", art_dir, 167 | probe_source.name, 168 | "-c", 169 | "-o", probe_obj, 170 | ], capture_output=True, encoding="utf-8") 171 | if result.returncode != 0: 172 | for e in ignored_errors: 173 | if e in result.stderr: 174 | return [-2] + [-2 for n in field_names] 175 | print(result.stderr) 176 | result.check_returncode() 177 | 178 | return parse_objdump_section_as_uint32_array(subprocess.run([ 179 | toolchain.objdump, 180 | "-sj", ".data", 181 | probe_obj 182 | ], check=True, capture_output=True, encoding="utf-8").stdout) 183 | finally: 184 | if os.path.exists(probe_obj): 185 | os.unlink(probe_obj) 186 | 187 | def is_relevant_tag(name): 188 | version = try_parse_tag(name) 189 | if version is None: 190 | return False 191 | major = version[0] 192 | return major >= 5 193 | 194 | def try_parse_tag(name): 195 | m = tag_pattern.match(name) 196 | if m is None: 197 | return None 198 | 199 | major = try_parse_version_component(m.group(3)) 200 | minor = try_parse_version_component(m.group(4)) 201 | codename = m.group(5) 202 | 203 | if major is None: 204 | assert codename == "q" 205 | major = 10 206 | minor = 0 207 | 208 | return (major, minor) 209 | 210 | def try_parse_version_component(component): 211 | if component is None: 212 | return None 213 | return int(component) 214 | 215 | def run_in_art_repo(*args): 216 | try: 217 | return subprocess.run(args, cwd=art_repo_dir, capture_output=True, encoding="utf-8", check=True).stdout.strip() 218 | except subprocess.CalledProcessError as e: 219 | print(e.stderr) 220 | raise 221 | 222 | def get_aosp_checkout(repo, version): 223 | tag = version.tag 224 | worktree_dir = os.path.join(cache_dir, tag, *repo) 225 | if not os.path.isdir(worktree_dir): 226 | repo_dir = os.path.join(aosp_dir, *repo) 227 | subprocess.run(["git", "worktree", "add", worktree_dir, tag], cwd=repo_dir, capture_output=True, check=True) 228 | return worktree_dir 229 | 230 | def get_toolchain(arch, flavor): 231 | host_triplet_generic = host_triplets_generic[arch] 232 | api_level = 21 if "64" in arch else 16 233 | 234 | if flavor == "clang": 235 | host_triplet_abi = host_triplets_abi[arch] + str(api_level) 236 | 237 | install_dir = glob.glob(os.path.join(os.environ["ANDROID_NDK_R21_ROOT"], "toolchains", "llvm", "prebuilt", "*"))[0] 238 | else: 239 | host_triplet_abi = host_triplet_generic 240 | 241 | install_dir = os.path.join(cache_dir, "toolchains", "-".join([arch, flavor])) 242 | if not os.path.isdir(install_dir): 243 | 244 | subprocess.run([ 245 | os.path.join(os.environ["ANDROID_NDK_R17B_ROOT"], "build", "tools", "make_standalone_toolchain.py"), 246 | "--arch", arch, 247 | "--api", str(api_level), 248 | "--stl", "gnustl", 249 | "--install-dir", install_dir 250 | ], capture_output=True, check=True) 251 | 252 | bin_dir = os.path.join(install_dir, "bin") 253 | 254 | cxx_name = "clang++" if flavor == "clang" else "g++" 255 | cxx = os.path.join(bin_dir, "-".join([host_triplet_abi, cxx_name])) 256 | cxxflags = [] 257 | objdump = os.path.join(bin_dir, "-".join([host_triplet_generic, "objdump"])) 258 | 259 | if flavor == "clang": 260 | cxxflags = [ 261 | "-std=c++2a", 262 | ] 263 | else: 264 | cxxflags = [ 265 | "-std=c++14", 266 | ] 267 | if arch == "arm": 268 | cxxflags += [ 269 | "-march=armv7-a", 270 | "-mthumb", 271 | ] 272 | if flavor == "clang": 273 | cxxflags += [ 274 | "-Wno-inconsistent-missing-override", 275 | ] 276 | 277 | return Toolchain(cxx, cxxflags, objdump) 278 | 279 | def parse_objdump_section_as_uint32_array(output): 280 | hex_bytes = "".join(section_data_pattern.findall(output)).replace(" ", "") 281 | raw_bytes = [int(hex_bytes[i:i + 2], 16) for i in range(0, len(hex_bytes), 2)] 282 | return [int.from_bytes(raw_bytes[i:i + 4], byteorder="little") for i in range(0, len(raw_bytes), 4)] 283 | 284 | 285 | class AndroidVersion(object): 286 | def __init__(self, tag, major, minor, api_level): 287 | self.tag = tag 288 | self.major = major 289 | self.minor = minor 290 | self.api_level = api_level 291 | 292 | @staticmethod 293 | def from_tag(tag): 294 | major, minor = try_parse_tag(tag) 295 | 296 | api_level = api_levels["{}.{}".format(major, minor)] 297 | 298 | return AndroidVersion(tag, major, minor, api_level) 299 | 300 | 301 | class Toolchain(object): 302 | def __init__(self, cxx, cxxflags, objdump): 303 | self.cxx = cxx 304 | self.cxxflags = cxxflags 305 | self.objdump = objdump 306 | 307 | 308 | main() 309 | --------------------------------------------------------------------------------