0f3a6ff5b5
This prints the error string defined on previous lines instead of raising a python exception. Change-Id: I953516bcd1530a9bf4c7ca14cd39c95a700fa06c
376 linhas
14 KiB
Python
Arquivo Executável
376 linhas
14 KiB
Python
Arquivo Executável
#!/usr/bin/env python
|
|
|
|
# Copyright 2016 The Fuchsia Authors
|
|
#
|
|
# Use of this source code is governed by a MIT-style
|
|
# license that can be found in the LICENSE file or at
|
|
# https://opensource.org/licenses/MIT
|
|
|
|
"""
|
|
|
|
This tool will symbolize a crash from Magenta's crash logger, adding
|
|
function names and, if available, source code locations (filenames and
|
|
line numbers from debug info).
|
|
|
|
Example usage #1:
|
|
./scripts/run-magenta -a x86-64 | ./scripts/symbolize devmgr.elf --build-dir=build-magenta-pc-x86-64
|
|
|
|
Example usage #2:
|
|
./scripts/symbolize devmgr.elf --build-dir=build-magenta-pc-x86-64
|
|
<copy and paste output from Magenta>
|
|
|
|
Example usage #3 (for magenta kernel output):
|
|
./scripts/symbolize --build-dir=build-magenta-pc-x86-64
|
|
<copy and paste output from Magenta>
|
|
|
|
|
|
"""
|
|
|
|
import argparse
|
|
import errno
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
|
|
PREBUILTS_BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(SCRIPT_DIR), "prebuilt",
|
|
"downloads"))
|
|
GCC_VERSION = '6.3.0'
|
|
name_to_full_path = {}
|
|
|
|
|
|
def find_func(find_args, dirname, names):
|
|
if find_args["path"] != "": # found something!
|
|
return
|
|
if dirname.find("sysroot") != -1:
|
|
return
|
|
for name in names:
|
|
if name == find_args["name"]:
|
|
find_args["path"] = dirname
|
|
return
|
|
|
|
|
|
def find_file_in_build_dir(name, build_dirs):
|
|
find_args = {"name": name, "path": ""}
|
|
for location in build_dirs:
|
|
os.path.walk(location, find_func, find_args)
|
|
if find_args["path"] != "":
|
|
return os.path.abspath(os.path.join(find_args["path"], name))
|
|
return None
|
|
|
|
|
|
def buildid_to_full_path(buildid, build_dirs):
|
|
for build_dir in build_dirs:
|
|
id_file_path = os.path.join(build_dir, "ids.txt")
|
|
if os.path.exists(id_file_path):
|
|
with open(id_file_path) as id_file:
|
|
for line in id_file:
|
|
id, path = line.split()
|
|
if id == buildid:
|
|
return path
|
|
return None
|
|
|
|
|
|
def find_file_in_boot_manifest(boot_app_name, build_dirs):
|
|
manifest_path = find_file_in_build_dir("bootfs.manifest", build_dirs)
|
|
if manifest_path:
|
|
with open(manifest_path) as manifest_file:
|
|
for line in manifest_file:
|
|
out_path, in_path = line.rstrip().split("=")
|
|
if out_path == boot_app_name:
|
|
if in_path.endswith(".strip"):
|
|
in_path = in_path[:-len(".strip")]
|
|
return in_path
|
|
return None
|
|
|
|
|
|
def find_dso_full_path_uncached(dso, exe_name, name_to_buildid, build_dirs):
|
|
if dso in name_to_buildid:
|
|
found_path = buildid_to_full_path(name_to_buildid[dso], build_dirs)
|
|
if found_path:
|
|
return found_path
|
|
|
|
# The name 'app' indicates the real app name is unknown.
|
|
# If the process has a name property that will be printed, but
|
|
# it has a max of 32 characters so it may be insufficient.
|
|
# Crashlogger prefixes such names with "app:" for our benefit.
|
|
if dso == "app" or dso.startswith("app:"):
|
|
# If an executable was passed on the command-line, try using that
|
|
if exe_name:
|
|
found_path = find_file_in_build_dir(exe_name, build_dirs)
|
|
if found_path:
|
|
return found_path
|
|
|
|
# If this looks like a program in boot fs, consult the manifest
|
|
if dso.startswith("app:/boot/"):
|
|
boot_app_name = dso[len("app:/boot/"):]
|
|
found_path = find_file_in_boot_manifest(boot_app_name, build_dirs)
|
|
if found_path:
|
|
return found_path
|
|
return None
|
|
|
|
# First, try an exact match for the filename
|
|
found_path = find_file_in_build_dir(dso, build_dirs)
|
|
if not found_path:
|
|
# If that fails, and this file doesn't end with .so, try the executable
|
|
# name
|
|
if not dso.endswith(".so"):
|
|
found_path = find_file_in_build_dir(exe_name, build_dirs)
|
|
if not found_path:
|
|
# If that still fails and this looks like an absolute path, try the
|
|
# last path component
|
|
if dso.startswith("/"):
|
|
short_name = dso[dso.rfind("/"):]
|
|
found_path = find_file_in_build_dir(short_name, build_dirs)
|
|
return found_path
|
|
|
|
|
|
def find_dso_full_path(dso, exe_name, name_to_buildid, build_dirs):
|
|
if dso in name_to_full_path:
|
|
return name_to_full_path[dso]
|
|
found_path = find_dso_full_path_uncached(dso, exe_name, name_to_buildid, build_dirs)
|
|
if found_path:
|
|
name_to_full_path[dso] = found_path
|
|
return found_path
|
|
|
|
|
|
def tool_path(arch, tool):
|
|
if sys.platform.startswith("linux"):
|
|
platform = "Linux"
|
|
elif sys.platform.startswith("darwin"):
|
|
platform = "Darwin"
|
|
else:
|
|
raise Exception("Unsupported platform!")
|
|
return ("%s/%s-elf-%s-%s-x86_64/bin/%s-elf-%s" %
|
|
(PREBUILTS_BASE_DIR, arch, GCC_VERSION, platform, arch, tool))
|
|
|
|
|
|
def run_tool(arch, tool, *args):
|
|
cmd = [tool_path(arch, tool)] + list(args)
|
|
try:
|
|
output = subprocess.check_output(cmd)
|
|
except Exception as e:
|
|
print "Calling %s failed: command %s error %s" % (tool, cmd, e)
|
|
return False
|
|
return output
|
|
|
|
# On BSD platforms there are cases where writing to stdout can return EAGAIN.
|
|
# In that event, retry the line again. This only manifests itself when piping
|
|
# qemu's stdout directly to this script.
|
|
def writelines(lines):
|
|
for line in lines:
|
|
writeline(line)
|
|
|
|
|
|
def writeline(line):
|
|
while True:
|
|
try:
|
|
sys.stdout.write(line)
|
|
except IOError as e:
|
|
if e.errno == errno.EAGAIN:
|
|
continue
|
|
break
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description=__doc__,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
parser.add_argument("--file", "-f", nargs="?", type=argparse.FileType("r"),
|
|
default=sys.stdin,
|
|
help="File to read from, stdin by default")
|
|
parser.add_argument("--build-dir", "-b", nargs="*",
|
|
help="List of additional build directories to search")
|
|
parser.add_argument("--disassemble", "-d", action="store_true",
|
|
help="Show disassembly of each function")
|
|
parser.add_argument("--stack_size", "-s", type=int,
|
|
default=256*1024,
|
|
help="Change the assumed size of the stack (e.g. use 1048576 for ftl or mtl default thread size")
|
|
parser.add_argument("--echo", dest="echo", action="store_true",
|
|
help="Echo lines of input (on by default)")
|
|
parser.add_argument("--no-echo", dest="echo", action="store_false",
|
|
help="Don't echo lines of input")
|
|
parser.add_argument("app", nargs="?", help="Name of primary application")
|
|
parser.set_defaults(echo=True)
|
|
args = parser.parse_args()
|
|
|
|
magenta_build_dir = os.path.join(
|
|
os.path.dirname(SCRIPT_DIR), "build-magenta-pc-x86-64")
|
|
if not os.path.exists(magenta_build_dir):
|
|
magenta_build_dir = os.path.join(
|
|
os.path.dirname(SCRIPT_DIR), os.pardir, "out", "build-magenta", "build-magenta-pc-x86-64")
|
|
build_dirs = [magenta_build_dir]
|
|
if args.build_dir:
|
|
build_dirs = args.build_dir + build_dirs
|
|
else:
|
|
fuchsia_build_dir = os.path.abspath(os.path.join(
|
|
os.path.dirname(SCRIPT_DIR), os.pardir, "out", "debug-x86-64"))
|
|
build_dirs.insert(0, fuchsia_build_dir)
|
|
|
|
# Parsing vars
|
|
arch = "x86_64"
|
|
name_to_buildid = {}
|
|
processed_lines = []
|
|
prev_sp = None
|
|
prev_frame_num = None
|
|
frame_sizes = []
|
|
total_stack_size = 0
|
|
|
|
# Regex for parsing
|
|
# Either nothing, or something like "[00007.268] 00304.00325> "
|
|
full_prefix = "^(|\[\d+\.\d+\] \d+\.\d+> )"
|
|
btre = re.compile(full_prefix + "bt#(\d+):")
|
|
bt_with_offset_re = re.compile(full_prefix +
|
|
"bt#(\d+): pc 0x[0-9a-f]+ sp (0x[0-9a-f]+) \(([^,]+),(0x[0-9a-f]+)\)$")
|
|
bt_end_re = re.compile(full_prefix + "bt#(\d+): end")
|
|
arch_re = re.compile(full_prefix + "arch: ([\\S]+)$")
|
|
build_id_re = re.compile(full_prefix +
|
|
"dso: id=([0-9a-z]+) base=(0x[0-9a-f]+) name=(.+)$")
|
|
disasm_re = re.compile("^ *(0x[0-9a-f]+)( .+)$")
|
|
|
|
# Magenta backtraces
|
|
magenta_crash_re = re.compile("^MAGENTA KERNEL PANIC$")
|
|
# TODO(cja): Add ARM to the regex
|
|
magenta_pc_re = re.compile("RIP: (0x[0-9a-z]+)")
|
|
magenta_bt_re = re.compile(full_prefix +
|
|
"bt#(\d+): (\dx[0-9a-fA-F]+)$")
|
|
magenta_elf_path = ''
|
|
magenta_bt = False
|
|
magenta_pc = ''
|
|
|
|
while True:
|
|
line = args.file.readline()
|
|
if args.echo and not args.file.isatty():
|
|
writeline(line)
|
|
end_of_file = (line == '')
|
|
# Strip any trailing carriage return character ("\r"), since
|
|
# these appear in the serial console output from QEMU.
|
|
line = line.rstrip()
|
|
if bt_end_re.match(line):
|
|
if len(processed_lines) != 0:
|
|
writeline("\nstart of symbolized stack:\n")
|
|
writelines(processed_lines)
|
|
writeline("end of symbolized stack\n")
|
|
|
|
if total_stack_size > args.stack_size - 8*1024:
|
|
if total_stack_size >= args.stack_size:
|
|
message = "Overflowed stack"
|
|
else:
|
|
message = "Potentially overflowed stack"
|
|
writeline("WARNING: %s (total usage: %d, stack size: %d)\n" % (message, total_stack_size, args.stack_size))
|
|
for frame, size in frame_sizes:
|
|
writeline("#%s: %d bytes\n" % (frame, size))
|
|
# Since we don't have the last stack frame, leave some leeway
|
|
# when warning about stackoverflow
|
|
processed_lines = []
|
|
frames_sizes = []
|
|
name_to_buildid = {}
|
|
total_stack_size = 0
|
|
prev_sp = None
|
|
magenta_bt = False
|
|
if end_of_file:
|
|
break
|
|
m = arch_re.match(line)
|
|
if m:
|
|
arch = m.group(2)
|
|
continue
|
|
m = build_id_re.match(line)
|
|
if m:
|
|
buildid = m.group(2)
|
|
bias = int(m.group(3), 16)
|
|
name = m.group(4)
|
|
name_to_buildid[name] = buildid
|
|
continue
|
|
m = btre.match(line)
|
|
if m and not magenta_bt:
|
|
frame_num = m.group(2)
|
|
m = bt_with_offset_re.match(line)
|
|
if m:
|
|
sp = int(m.group(3), 16)
|
|
if prev_sp is not None:
|
|
frame_size = sp - prev_sp
|
|
total_stack_size += frame_size
|
|
frame_sizes.append((prev_frame_num, frame_size))
|
|
prev_sp = sp
|
|
prev_frame_num = frame_num
|
|
|
|
dso = m.group(4)
|
|
off = m.group(5)
|
|
dso_full_path = find_dso_full_path(
|
|
dso, args.app, name_to_buildid, build_dirs)
|
|
if dso_full_path:
|
|
addr2line_output = run_tool(arch, "addr2line", "-Cipfe",
|
|
dso_full_path, off)
|
|
if addr2line_output:
|
|
processed_lines.append(
|
|
"#%s: %s" % (frame_num, addr2line_output))
|
|
if args.disassemble:
|
|
pc = int(off, 16)
|
|
disassembly = run_tool(
|
|
arch, "gdb", "--nx", "--batch", "-ex",
|
|
"disassemble %#x" % pc, dso_full_path)
|
|
if disassembly:
|
|
for line in disassembly.splitlines():
|
|
m = disasm_re.match(line)
|
|
if m:
|
|
timestamp, addr, rest = m.groups()
|
|
addr = int(addr, 16)
|
|
if addr == pc:
|
|
prefix = "=> "
|
|
else:
|
|
prefix = " "
|
|
line = "%s%#.16x%s" % (prefix, bias + addr, rest)
|
|
processed_lines.append(line + "\n")
|
|
continue
|
|
|
|
# fallthru print if we don't recognize the format of the backtrace entry
|
|
processed_lines.append("#%s: (unknown)\n" % (frame_num,))
|
|
|
|
# Magenta Specific Handling
|
|
if magenta_crash_re.search(line):
|
|
magenta_elf_path = find_file_in_build_dir("magenta.elf", build_dirs)
|
|
if magenta_elf_path:
|
|
magenta_bt = True
|
|
else:
|
|
sys.stderr.write("Symbolize could not find the magenta elf binary. Perhaps you need to build "
|
|
"magenta or specify the build directory with -b?\n")
|
|
m = magenta_pc_re.search(line)
|
|
if m:
|
|
magenta_pc = m.group(1)
|
|
m = magenta_bt_re.match(line)
|
|
if m and magenta_bt:
|
|
frame_num = m.group(2)
|
|
addr = m.group(3)
|
|
# If we saw the instruction pointer for the fault/panic then use it once
|
|
if magenta_pc:
|
|
prefix = " pc: %s => " % magenta_pc
|
|
a2l_out = run_tool(arch, "addr2line", "-Cpife", magenta_elf_path,
|
|
magenta_pc)
|
|
processed_lines.append(prefix +
|
|
a2l_out.replace("(inlined", (" " * len(prefix)) + "(inlined"))
|
|
magenta_pc = None
|
|
|
|
|
|
prefix = "bt#%s: %s => " % (frame_num, addr)
|
|
# Subtract 1 to convert from a return address to a call site
|
|
# address. (To be more exact, this converts to an address that
|
|
# is within the call site instruction.) This adjustment gives
|
|
# more correct results in the presence of inlining and
|
|
# 'noreturn' functions. (See MG-842.)
|
|
call_addr = "0x%x" % (int(addr, 16) - 1)
|
|
a2l_out = run_tool(arch, "addr2line", "-Cpife", magenta_elf_path,
|
|
call_addr)
|
|
# In the case of inlined methods, it is more readable if the
|
|
# inlined lines are aligned to be to the right of "=>".
|
|
processed_lines.append(prefix +
|
|
a2l_out.replace("(inlined", (" " * len(prefix)) + "(inlined"))
|
|
continue
|
|
|
|
# Exit if we've reached the end of stdin
|
|
if end_of_file:
|
|
break
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|