blob: fad839077f0f29f0175088ed4015796199b4e5ff [file] [log] [blame]
#!/usr/bin/python
#
# Copyright (C) 2023 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import argparse
import bisect
import os
import subprocess
import sys
PROT_READ = 1
PROT_WRITE = 2
PROT_EXEC = 4
class MapEntry:
start = -1
end = -1
prot = 0
offset = 0
file = ""
base_addr = -1
class TraceParser:
def __init__(self, tombstone, trace, symbols_dir):
self.maps = {}
self.tombstone = tombstone
self.trace = trace
self.symbols_dir = symbols_dir
def parse_prot(self, prot_str):
prot = 0
if prot_str[0] == "r":
prot |= PROT_READ
if prot_str[1] == "w":
prot |= PROT_WRITE
if prot_str[2] == "x":
prot |= PROT_EXEC
return prot
def parse_tombstone_map_entry(self, line, line_number):
if not line.startswith(" ") and not line.startswith("--->"):
raise Exception(
"Unexpected line (" + line_number + ") in maps section: " + line
)
if line.startswith("--->Fault address"):
return
line = line[3:] # throw away indent/prefix
entries = line.split(maxsplit=5)
addrs = entries[0].split("-")
map_entry = MapEntry()
map_entry.start = int(addrs[0].replace("'", ""), 16)
map_entry.end = int(addrs[1].replace("'", ""), 16)
map_entry.prot = self.parse_prot(entries[1])
map_entry.offset = int(entries[2], 16)
map_entry.size = int(entries[3], 16)
if len(entries) >= 5:
map_entry.file = entries[4]
# The default base address is start
map_entry.base_addr = map_entry.start
# Skip PROT_NONE mappings so they do not interfere with
# file mappings
if map_entry.prot == 0:
return
self.maps[map_entry.start] = map_entry
def read_maps_from_tombstone(self):
with open(self.tombstone, "r") as f:
maps_section_started = False
line_number = 0
for line in f:
line_number += 1
if maps_section_started:
# Maps section ends when we hit either '---------' or end of file
if line.startswith("---------"):
break
self.parse_tombstone_map_entry(line, line_number)
else:
maps_section_started = line.startswith("memory map")
def calculate_base_addr_for_map_entries(self):
# Ascending order of start_addr (key) is important here
last_file = None
current_base_addr = -1
for key in sorted(self.maps):
# For now we are assuming load_bias is 0, revisit once proved otherwise
# note that load_bias printed in tombstone is incorrect atm
map = self.maps[key]
if not map.file:
continue
# treat /memfd as if it was anon mapping
if map.file.startswith("/memfd:"):
continue
if map.file != last_file:
last_file = map.file
current_base_addr = map.start
map.base_addr = current_base_addr
def addr2line(self, address, file):
if not file:
print("error: no file")
return None
p = subprocess.run(
["addr2line", "-e", self.symbols_dir + file, hex(address)],
capture_output=True,
text=True,
)
if p.returncode != 0:
# print("error: ", p.stderr)
return None
return p.stdout.strip()
def symbolize_trace(self):
with open(self.trace, "r") as f:
sorted_start_addresses = sorted(self.maps.keys())
for line in f:
tokens = line.split(maxsplit=2)
if len(tokens) <= 2:
continue
msg = tokens[2]
if not msg.startswith("RunGeneratedCode @"):
continue
address = int(msg.split("@")[1].strip(), 16)
pos = bisect.bisect_right(sorted_start_addresses, address)
map = self.maps[sorted_start_addresses[pos]]
if address > map.end:
print("%x (not maped)" % address)
continue
relative_addr = address - map.base_addr
file_and_line = self.addr2line(relative_addr, map.file)
if file_and_line:
print(
"%x (%s+%x) %s" % (address, map.file, relative_addr, file_and_line)
)
else:
print("%x (%s+%x)" % (address, map.file, relative_addr))
def parse(self):
self.read_maps_from_tombstone()
self.calculate_base_addr_for_map_entries()
self.symbolize_trace()
def get_symbol_dir(args):
if args.symbols_dir:
return symbols_dir
product_out = os.environ.get("ANDROID_PRODUCT_OUT")
if not product_out:
raise Error(
"--symbols_dir is not set and unable to resolve ANDROID_PRODUCT_OUT via"
" environment variable"
)
return product_out + "/symbols"
def main():
argument_parser = argparse.ArgumentParser()
argument_parser.add_argument(
"trace", help="file containing berberis trace output"
)
# TODO(b/232598137): Make it possible to read maps from /proc/pid/maps format as an
# alternative option
argument_parser.add_argument(
"tombstone", help="Tombstone of the corresponding crash"
)
argument_parser.add_argument(
"--symbols_dir",
help="Symbols dir (default is '$ANDROID_PRODUCT_OUT/symbols')",
)
args = argument_parser.parse_args()
parser = TraceParser(args.tombstone, args.trace, get_symbol_dir(args))
parser.parse()
if __name__ == "__main__":
sys.exit(main())