summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRicardo Quesada <ricardoq@google.com>2022-10-20 10:18:56 -0700
committerChromeos LUCI <chromeos-scoped@luci-project-accounts.iam.gserviceaccount.com>2022-10-21 16:36:24 +0000
commit6e31e1e27af695ea93e13983465dc7dbcd35c119 (patch)
tree0788462ba8ba045363450e59e46603d3dd9e196c
parent83d3e16ae92ad4725b4a88b5094a97d0bc617fbe (diff)
downloadchrome-ec-6e31e1e27af695ea93e13983465dc7dbcd35c119.tar.gz
util: Make it easier to add new arch in crash_analyzer
This CL changes crash_analyzer so that it is easier to add new supported architectures. Additionally it adds the following debugging features: - an optional parameter that allows to print the print of the crash report - print the "crash cause" in human-readable format BUG=None TEST=./crash_analyzer lite -n -m file.map -f /tmp/dumps BRANCH=None Change-Id: I45a9650b400d4be1d4a14459d107fa8729ef62d6 Signed-off-by: ricardoq@chromium.org Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/3967699 Reviewed-by: Jeremy Bettis <jbettis@chromium.org>
-rwxr-xr-xutil/crash_analyzer.py230
1 files changed, 157 insertions, 73 deletions
diff --git a/util/crash_analyzer.py b/util/crash_analyzer.py
index 50daa37150..3c0efb2778 100755
--- a/util/crash_analyzer.py
+++ b/util/crash_analyzer.py
@@ -10,9 +10,8 @@ import pathlib
import re
import sys
-# Regex tested here: https://regex101.com/r/K5S8cB/1
-# This Regex has only been tested in Cortex-M0+ crash reporter.
# TODO(b/253492108): Add regexp for missing architectures.
+# Regex tested here: https://regex101.com/r/K5S8cB/1
_REGEX_CORTEX_M0 = (
r"^Saved.*$\n=== .* EXCEPTION: (.*) ====== xPSR: (.*) ===$\n"
r"r0 :(.*) r1 :(.*) r2 :(.*) r3 :(.*)$\n"
@@ -22,10 +21,99 @@ _REGEX_CORTEX_M0 = (
r"\n"
r"^.*cfsr=(.*), shcsr=(.*), hfsr=(.*), dfsr=(.*), ipsr=(.*)$"
)
+
+
+# List of symbols. Each entry is tuple: address, name
_symbols = []
+# List of crashes. Each entry is dictionary.
+# Contains all crashes found.
_entries = []
+# This is function, and not a global dictionary, so that
+# we can reference the different functions without placing
+# the functions above the dictionary.
+def get_architectures() -> list:
+ """Returns a dictionary with the supported architectures"""
+
+ archs = {
+ "cm0": {
+ "regex": _REGEX_CORTEX_M0,
+ "parser": cm0_parse,
+ "extra_regs": [
+ "sp",
+ "lr",
+ "pc",
+ "cfsr",
+ "chcsr",
+ "hfsr",
+ "dfsr",
+ "ipsr",
+ ],
+ }
+ }
+ return archs
+
+
+def get_crash_cause(cause: int) -> str:
+ """Returns the cause of crash in human-readable format"""
+ causes = {
+ 0xDEAD6660: "div-by-0",
+ 0xDEAD6661: "stack-overflow",
+ 0xDEAD6662: "pd-crash",
+ 0xDEAD6663: "assert",
+ 0xDEAD6664: "watchdog",
+ 0xDEAD6665: "bad-rng",
+ 0xDEAD6666: "pmic-fault",
+ 0xDEAD6667: "exit",
+ }
+
+ if cause in causes:
+ return causes[cause]
+ return f"unknown-cause-{format(cause, '#x')}"
+
+
+def cm0_parse(match) -> dict:
+ """Regex parser for Cortex-M0+ architecture"""
+
+ # Expecting something like:
+ # Saved panic data: (NEW)
+ # === PROCESS EXCEPTION: ff ====== xPSR: ffffffff ===
+ # r0 : r1 : r2 : r3 :
+ # r4 :dead6664 r5 :10092632 r6 :00000000 r7 :00000000
+ # r8 :00000000 r9 :00000000 r10:00000000 r11:00000000
+ # r12: sp :00000000 lr : pc :
+ #
+ # cfsr=00000000, shcsr=00000000, hfsr=00000000, dfsr=00000000, ipsr=000000ff
+ regs = {}
+ values = []
+
+ for i in match.groups():
+ try:
+ val = int(i, 16)
+ except ValueError:
+ # Value might be empty, so we must handle the exception
+ val = -1
+ values.append(val)
+
+ regs["task"] = values[0]
+ regs["xPSR"] = values[1]
+ regs["regs"] = values[3:15]
+ regs["sp"] = values[15]
+ regs["lr"] = values[16]
+ regs["pc"] = values[17]
+ regs["cfsr"] = values[18]
+ regs["chcsr"] = values[19]
+ regs["hfsr"] = values[20]
+ regs["dfsr"] = values[21]
+ regs["ipsr"] = values[22]
+
+ regs["cause"] = get_crash_cause(values[6]) # r4
+ regs["symbol"] = get_symbol(values[7]) # r5
+
+ return regs
+
+
def read_map_file(map_file):
"""Reads the map file, and populates the _symbols list with the tuple address/name"""
lines = map_file.readlines()
@@ -116,56 +204,32 @@ def process_log_file(file_name: str) -> tuple:
)
-def process_crash_file(file_name: str) -> dict:
+def process_crash_file(filename: str) -> dict:
"""Process a single crash report, and convert it to a dictionary"""
- regs = {}
- with open(file_name, "r") as crash_file:
+
+ with open(filename, "r") as crash_file:
content = crash_file.read()
- # TODO(b/253492108): This is hardcoded to Cortex-M0+ crash reports.
- # New ones (Risc-V, NDS32, etc.) will be added on demand.
- #
- # Expecting something like:
- # Saved panic data: (NEW)
- # === PROCESS EXCEPTION: ff ====== xPSR: ffffffff ===
- # r0 : r1 : r2 : r3 :
- # r4 :dead6664 r5 :10092632 r6 :00000000 r7 :00000000
- # r8 :00000000 r9 :00000000 r10:00000000 r11:00000000
- # r12: sp :00000000 lr : pc :
- #
- # cfsr=00000000, shcsr=00000000, hfsr=00000000, dfsr=00000000, ipsr=000000ff
-
- match = re.match(_REGEX_CORTEX_M0, content, re.MULTILINE)
- values = []
- # Convert the values to numbers, invalid the invalid ones.
- # Cannot use list comprehension due to possible invalid values
- if match is not None:
- for i in match.groups():
- try:
- val = int(i, 16)
- except ValueError:
- # Value might be empty, so we must handle the exception
- val = -1
- values.append(val)
-
- regs["exp"] = values[0]
- regs["xPSR"] = values[1]
- regs["regs"] = values[2:15]
- regs["sp"] = values[15]
- regs["lr"] = values[16]
- regs["pc"] = values[17]
- regs["cfsr"] = values[18]
- regs["chcsr"] = values[19]
- regs["hfsr"] = values[20]
- regs["dfsr"] = values[21]
- regs["ipsr"] = values[22]
- regs["symbol"] = get_symbol(regs["regs"][5])
- return regs
+ for key, arch in get_architectures().items():
+ regex = arch["regex"]
+ match = re.match(regex, content, re.MULTILINE)
+ if match is None:
+ continue
+ entry = arch["parser"](match)
+
+ # Add "arch" entry since it is needed to process
+ # the "extra_regs", among other things.
+ entry["arch"] = key
+ return entry
+ return {}
-def process_crash_files(crash_folder):
+
+def process_crash_files(crash_folder: pathlib.Path) -> None:
"""Process the crash reports that are in the crash_folder"""
- processed = 0
+ total = 0
+ failed = 0
+ good = 0
for file in crash_folder.iterdir():
# .log and .upload_file_eccrash might not be in order.
# To avoid processing it more than once, only process the
@@ -179,56 +243,70 @@ def process_crash_files(crash_folder):
)
entry["ec_version"] = ec_ver
entry["bios_version"] = bios_ver
+ entry["filename"] = file.stem
if len(entry) != 0:
_entries.append(entry)
- processed += 1
- print(f"Processed: {processed}", file=sys.stderr)
+ good += 1
+ else:
+ failed += 1
+ total += 1
+ print(f"Total: {total}, OK: {good}, Failed: {failed}", file=sys.stderr)
-def cmd_report_lite(crash_folder):
+def cmd_report_lite(crash_folder: pathlib.Path, with_filename: bool) -> None:
"""Generates a 'lite' report that only contains a few fields"""
process_crash_files(crash_folder)
for entry in _entries:
print(
- f"Task: {format(entry['exp'],'#04x')} - "
- f"cause: {format(entry['regs'][4], '#x')} - "
+ f"Task: {format(entry['task'],'#04x')} - "
+ f"cause: {entry['cause']} - "
f"PC: {entry['symbol']} - "
f"{entry['ec_version']} - "
- f"{entry['bios_version']}"
+ f"{entry['bios_version']}",
+ end="",
)
+ if with_filename:
+ print(f" - {entry['filename']}", end="")
+
+ print()
-def cmd_report_full(crash_folder):
+
+def cmd_report_full(crash_folder: pathlib.Path, with_filename: bool) -> None:
"""Generates a full report in .cvs format"""
process_crash_files(crash_folder)
# Print header
- print(
- "Task,xPSR,r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,"
- "sp,lr,pc,cfsr,chcsr,hfsr,dfsr,ipsr,symbol,fw_version,bios_version"
- )
for entry in _entries:
print(
- f"{format(entry['exp'],'#04x')},{format(entry['xPSR'],'#x')}",
+ f"Task: {format(entry['task'],'#04x')} - "
+ f"cause: {entry['cause']} - "
+ f"PC: {entry['symbol']} - ",
end="",
)
- for i in range(12):
- print(f",{format(entry['regs'][i],'#x')}", end="")
+ # Print registers
+ hex_regs = [hex(x) for x in entry["regs"]]
+ print(f"{hex_regs} - ", end="")
+
+ # Print extra registers. Varies from architecture to architecture.
+ arch = get_architectures()[entry["arch"]]
+ for reg in arch["extra_regs"]:
+ print(f"{reg}: {format(entry[reg], '#x')} ", end="")
+
+ # Print EC & BIOS info
print(
- f",{format(entry['sp'],'#x')}"
- f",{format(entry['lr'],'#x')}"
- f",{format(entry['pc'],'#x')}"
- f",{format(entry['cfsr'],'#x')}"
- f",{format(entry['hfsr'],'#x')}"
- f",{format(entry['dfsr'],'#x')}"
- f",{format(entry['ipsr'],'#x')}"
- f",\"{(entry['symbol'])}\""
- f",\"{(entry['ec_version'])}\""
- f",\"{(entry['bios_version'])}\""
+ f"{entry['ec_version']} - {entry['bios_version']}",
+ end="",
)
+ # Finally the filename. Useful for debugging.
+ if with_filename:
+ print(f" - {entry['filename']}", end="")
+
+ print()
+
def main(argv):
"""Main entry point"""
@@ -276,12 +354,18 @@ crash_analyzer.py lite -m /tmp/rammus_193.map -f /tmp/dumps | sort | uniq -c | l
)
parser.add_argument(
"-f",
- "--crash_folder",
+ "--crash-folder",
type=pathlib.Path,
required=True,
help="Folder with the EC crash report files",
)
parser.add_argument(
+ "-n",
+ "--with-filename",
+ action="store_true",
+ help="Includes the filename in the report. Useful for debugging.",
+ )
+ parser.add_argument(
"command", choices=["lite", "full"], help="Command to run."
)
args = parser.parse_args(argv)
@@ -290,9 +374,9 @@ crash_analyzer.py lite -m /tmp/rammus_193.map -f /tmp/dumps | sort | uniq -c | l
read_map_file(args.map_file)
if args.command == "lite":
- cmd_report_lite(args.crash_folder)
+ cmd_report_lite(args.crash_folder, args.with_filename)
elif args.command == "full":
- cmd_report_full(args.crash_folder)
+ cmd_report_full(args.crash_folder, args.with_filename)
else:
print(f"Unsupported command: {args.command}")