diff options
Diffstat (limited to 'util')
-rwxr-xr-x | util/build_with_clang.py | 10 | ||||
-rwxr-xr-x | util/clangd_config.py | 33 | ||||
-rwxr-xr-x | util/crash_analyzer.py | 339 | ||||
-rw-r--r-- | util/ec_panicinfo_fuzzer.cc | 22 | ||||
-rw-r--r-- | util/ectool.cc | 67 | ||||
-rw-r--r-- | util/stm32mon.cc | 1 |
6 files changed, 314 insertions, 158 deletions
diff --git a/util/build_with_clang.py b/util/build_with_clang.py index 4951c53f30..71957107a0 100755 --- a/util/build_with_clang.py +++ b/util/build_with_clang.py @@ -51,7 +51,7 @@ BOARDS_THAT_COMPILE_SUCCESSFULLY_WITH_CLANG = [ "max32660-eval", # Boards that use CHIP:=npcx # git grep --name-only 'CHIP:=npcx' | sed 's#^board/\(.*\)/build.mk#"\1",#' - # "adlrvpp_npcx", + "adlrvpp_npcx", "agah", "akemi", "aleena", @@ -75,7 +75,7 @@ BOARDS_THAT_COMPILE_SUCCESSFULLY_WITH_CLANG = [ "copano", "coral", "corori", - # "corori2", + "corori2", "cret", "crota", "dalboz", @@ -89,7 +89,7 @@ BOARDS_THAT_COMPILE_SUCCESSFULLY_WITH_CLANG = [ "eldrid", "elemi", "endeavour", - # "eve", + "eve", "ezkinil", "felwinter", "fizz", @@ -144,7 +144,7 @@ BOARDS_THAT_COMPILE_SUCCESSFULLY_WITH_CLANG = [ "pazquel", "phaser", "pompom", - # "poppy", + "poppy", "primus", "puff", "quackingstick", @@ -161,7 +161,7 @@ BOARDS_THAT_COMPILE_SUCCESSFULLY_WITH_CLANG = [ "treeya", "trembyle", "trogdor", - # "vell", + "vell", "vilboz", "voema", "volet", diff --git a/util/clangd_config.py b/util/clangd_config.py index 96bcb7ebf1..2553707c43 100755 --- a/util/clangd_config.py +++ b/util/clangd_config.py @@ -65,7 +65,38 @@ def ec_build(ec_root: Path, board: str, image: str) -> Optional[Path]: def zephyr_build(ec_root: Path, board: str, image: str) -> Optional[Path]: """Build the correct compile_commands.json for Zephyr board/image""" - raise NotImplementedError("Zephyr is currently unsupported.") + target = Path( + f"build/zephyr/{board}/build-{image.lower()}/compile_commands.json" + ) + cmd = ["zmake", "configure", board] + + print(" ".join(cmd)) + status = subprocess.run(cmd, check=False, cwd=ec_root) + + if status.returncode != 0: + return None + + # Replace /mnt/host/source with path of chromiumos outside chroot + default_chromiumos_path_outside_chroot = os.path.join( + Path.home(), "chromiumos" + ) + chromiumos_path_outside_chroot = os.environ.get( + "EXTERNAL_TRUNK_PATH", default_chromiumos_path_outside_chroot + ) + chromiumos_path_inside_chroot = "/mnt/host/source" + + print( + f"Replacing '{chromiumos_path_inside_chroot}' with " + + f"'{chromiumos_path_outside_chroot}' in file {target}" + ) + + target.write_text( + target.read_text().replace( + chromiumos_path_inside_chroot, chromiumos_path_outside_chroot + ) + ) + + return target def copy(ec_root: Path, target: Path) -> None: diff --git a/util/crash_analyzer.py b/util/crash_analyzer.py index 2e51511461..c2e07face2 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,152 @@ _REGEX_CORTEX_M0 = ( r"\n" r"^.*cfsr=(.*), shcsr=(.*), hfsr=(.*), dfsr=(.*), ipsr=(.*)$" ) + +# Regex tested here: https://regex101.com/r/FL7T0n/1 +_REGEX_NDS32 = ( + r"^Saved.*$\n===.*ITYPE=(.*) ===$\n" + r"R0 (.*) R1 (.*) R2 (.*) R3 (.*)$\n" + r"R4 (.*) R5 (.*) R6 (.*) R7 (.*)$\n" + r"R8 (.*) R9 (.*) R10 (.*) R15 (.*)$\n" + r"FP (.*) GP (.*) LP (.*) SP (.*)$\n" + r"IPC (.*) IPSW (.*)$\n" +) + + +# 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", + ], + }, + "nds32": { + "regex": _REGEX_NDS32, + "parser": nds32_parse, + "extra_regs": ["fp", "gp", "lp", "sp", "ipc", "ipsw"], + }, + } + 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 nds32_parse(match) -> dict: + """Regex parser for Andes (NDS32) architecture""" + + # Expecting something like: + # Saved panic data: (NEW) + # === EXCEP: ITYPE=0 === + # R0 00000000 R1 00000000 R2 00000000 R3 00000000 + # R4 00000000 R5 00000000 R6 dead6664 R7 00000000 + # R8 00000000 R9 00000000 R10 00000000 R15 00000000 + # FP 00000000 GP 00000000 LP 00000000 SP 00000000 + # IPC 00050d5e IPSW 00000 + # SWID of ITYPE: 0 + 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) + + # NDS32 is not reporting task info. + regs["task"] = -1 + regs["regs"] = values[1:13] + regs["fp"] = values[13] + regs["gp"] = values[14] + regs["lp"] = values[15] + regs["sp"] = values[16] + regs["ipc"] = values[17] + regs["ipsw"] = values[18] + + regs["cause"] = get_crash_cause(values[7]) # r6 + regs["symbol"] = get_symbol(regs["ipc"]) + 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() @@ -51,8 +192,8 @@ def get_symbol_bisec(addr: int, low: int, high: int) -> str: if _symbols[mid][0] <= addr < _symbols[mid + 1][0]: symbol = _symbols[mid][1] - # Start of a sequence of Thumb instructions. When this happens, query - # for the next address. + # Start of a sequence of Thumb instructions. When this happens, return + # the next address. if symbol == "$t": symbol = _symbols[mid + 1][1] return symbol @@ -73,8 +214,10 @@ def get_symbol(addr: int) -> str: return symbol -def process_log_file(file_name: str) -> str: - """Reads a .log file and extracts the FW version""" +def process_log_file(file_name: str) -> tuple: + """Reads a .log file and extracts the EC and BIOS versions""" + ec_ver = None + bios_ver = None try: with open(file_name, "r") as log_file: lines = log_file.readlines() @@ -84,64 +227,62 @@ def process_log_file(file_name: str) -> str: # vendor | Nuvoton # name | NPCX586G # fw_version | rammus_v2.0.460-d1d2aeb01f - if line.startswith("fw_version"): - _, value = line.split("|") - return value.strip() + # ... + # ===bios_info=== + # fwid = Google_Rammus.11275.193.0 # [RO/str] ... + # ro_fwid = Google_Rammus.11275.28.0 # [RO/str] ... + + # Get EC version. + # There could be more than one "fw_version". Only the first one + # corresponds to the EC version. + if line.startswith("fw_version") and ec_ver is None: + _, ec_ver = line.split("|") + ec_ver = ec_ver.strip(" \n") + + # Get BIOS version. + if line.startswith("fwid"): + _, value = line.split("=") + # Only get the first element. + bios_ver, _ = value.split("#") + bios_ver = bios_ver.strip() + + if ec_ver is not None and bios_ver is not None: + return ec_ver, bios_ver + except FileNotFoundError: - return ".log file not found" - return "unknown fw version" + return ".log file not found", "not found" + return ( + "unknown fw version" if ec_ver is None else ec_ver, + "unknown BIOS version" if bios_ver is None else bios_ver, + ) -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 @@ -150,65 +291,87 @@ def process_crash_files(crash_folder): continue entry = process_crash_file(file) if len(entry) != 0: - fw_ver = process_log_file(file.parent.joinpath(file.stem + ".log")) - entry["fw_version"] = fw_ver + ec_ver, bios_ver = process_log_file( + file.parent.joinpath(file.stem + ".log") + ) + 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"EC ver:{entry['fw_version']}" + f"{entry['ec_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" - ) 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['fw_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""" example_text = """Example: +# For further details see: go/cros-ec-crash-analyzer +# +# Summary: # 1st: # Collect the crash reports using this script: -# https://source.corp.google.com/piper///depot/google3/experimental/users/ricardoq/crashpad/main.py +# https://source.corp.google.com/piper///depot/google3/experimental/users/ricardoq/ec_crash_report_fetcher # MUST be run within a Google3 Workspace. E.g: -(google3) blaze run //experimental/users/ricardoq/crashpad:main -- --outdir=/tmp/dumps/ --limit=3000 --offset=15000 --hwclass=shyvana --milestone=105 +(google3) blaze run //experimental/users/ricardoq/ec_crash_report_fetcher:main -- --outdir=/tmp/dumps/ --limit=3000 --offset=15000 --hwclass=shyvana --milestone=105 # 2nd: # Assuming that you don't have the .map file of the EC image, you can download the EC image from LUCI @@ -244,12 +407,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) @@ -258,9 +427,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}") diff --git a/util/ec_panicinfo_fuzzer.cc b/util/ec_panicinfo_fuzzer.cc new file mode 100644 index 0000000000..95f1871837 --- /dev/null +++ b/util/ec_panicinfo_fuzzer.cc @@ -0,0 +1,22 @@ +/* Copyright 2022 The ChromiumOS Authors + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "ec_panicinfo.h" + +/* Fuzzing Build command: + * $ clang++ ec_panicinfo_fuzzer.cc ec_panicinfo.cc -g -fsanitize=address,fuzzer + * -o ec_panicinfo_fuzzer + * -I../include/ -I../chip/host/ -I../board/host/ -I../fuzz -I../test + * + * Run Fuzzing: + * $ ./ec_panicinfo_fuzzer -runs=5000 + */ + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, unsigned int size) +{ + parse_panic_info((const char *)data, size); + + return 0; +} diff --git a/util/ectool.cc b/util/ectool.cc index c116b35fcc..844fa15ac6 100644 --- a/util/ectool.cc +++ b/util/ectool.cc @@ -273,8 +273,6 @@ const char help_str[] = " Set 16 bit duty cycle of given PWM\n" " rand <num_bytes>\n" " generate <num_bytes> of random numbers\n" - " readtest <patternoffset> <size>\n" - " Reads a pattern from the EC via LPC\n" " reboot_ec <RO|RW|cold|hibernate|hibernate-clear-ap-off|disable-jump|cold-ap-off>" " [at-shutdown|switch-slot]\n" " Reboot EC to RO or RW\n" @@ -1228,70 +1226,6 @@ exit: return rv; } -int cmd_read_test(int argc, char *argv[]) -{ - struct ec_params_read_test p; - struct ec_response_read_test r; - int offset, size; - int errors = 0; - int rv; - int i; - char *e; - char *buf; - uint32_t *b; - - if (argc < 3) { - fprintf(stderr, "Usage: %s <pattern_offset> <size>\n", argv[0]); - return -1; - } - offset = strtol(argv[1], &e, 0); - size = strtol(argv[2], &e, 0); - if ((e && *e) || size <= 0 || size > MAX_FLASH_SIZE) { - fprintf(stderr, "Bad size.\n"); - return -1; - } - printf("Reading %d bytes with pattern offset 0x%x...\n", size, offset); - - buf = (char *)malloc(size); - if (!buf) { - fprintf(stderr, "Unable to allocate buffer.\n"); - return -1; - } - - /* Read data in chunks */ - for (i = 0; i < size; i += sizeof(r.data)) { - p.offset = offset + i / sizeof(uint32_t); - p.size = MIN(size - i, sizeof(r.data)); - rv = ec_command(EC_CMD_READ_TEST, 0, &p, sizeof(p), &r, - sizeof(r)); - if (rv < 0) { - fprintf(stderr, "Read error at offset %d\n", i); - free(buf); - return rv; - } - memcpy(buf + i, r.data, p.size); - } - - /* Check data */ - for (i = 0, b = (uint32_t *)buf; i < size / 4; i++, b++) { - if (*b != i + offset) { - printf("Mismatch at byte offset 0x%x: " - "expected 0x%08x, got 0x%08x\n", - (int)(i * sizeof(uint32_t)), i + offset, *b); - errors++; - } - } - - free(buf); - if (errors) { - printf("Found %d errors\n", errors); - return -1; - } - - printf("done.\n"); - return 0; -} - int cmd_reboot_ec(int argc, char *argv[]) { struct ec_params_reboot_ec p; @@ -11002,7 +10936,6 @@ const struct command commands[] = { { "pwmsetkblight", cmd_pwm_set_keyboard_backlight }, { "pwmsetduty", cmd_pwm_set_duty }, { "rand", cmd_rand }, - { "readtest", cmd_read_test }, { "reboot_ec", cmd_reboot_ec }, { "rgbkbd", cmd_rgbkbd }, { "rollbackinfo", cmd_rollback_info }, diff --git a/util/stm32mon.cc b/util/stm32mon.cc index 352158ae73..39837bd3dd 100644 --- a/util/stm32mon.cc +++ b/util/stm32mon.cc @@ -633,6 +633,7 @@ int wait_for_ack(int fd) } /* Do not break so that it can be handled as junk */ + __attribute__((fallthrough)); default: stat_resp[JUNK_IDX].event_count++; if (mode == MODE_SERIAL) |