| // Copyright 2023 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include <stdint.h> | |
| #include <stdio.h> | |
| #include <string> | |
| #include <vector> | |
| #include <cstddef> | |
| #include <base/containers/span.h> | |
| #include <base/strings/strcat.h> | |
| #include <base/strings/stringprintf.h> | |
| #include "libec/ec_command.h" | |
| #include "libec/ec_panicinfo.h" | |
| namespace ec { | |
| namespace { | |
| static std::string PrintPanicReg(int regnum, const uint32_t* regs, int index) { | |
| static const char* const regname[] = { | |
| "r0 ", "r1 ", "r2 ", "r3 ", "r4 ", "r5 ", "r6 ", "r7 ", | |
| "r8 ", "r9 ", "r10", "r11", "r12", "sp ", "lr ", "pc "}; | |
| std::string ret; | |
| ret = base::StringPrintf("%s:", regname[regnum]); | |
| if (regs) | |
| base::StrAppend(&ret, {base::StringPrintf("%08x", regs[index])}); | |
| else | |
| base::StrAppend(&ret, {base::StringPrintf(" ")}); | |
| base::StrAppend(&ret, {(regnum & 3) == 3 ? "\n" : " "}); | |
| return ret; | |
| } | |
| static std::string PanicShowExtraCm(const struct panic_data* pdata) { | |
| enum { | |
| CPU_NVIC_CFSR_BFARVALID = BIT(15), | |
| CPU_NVIC_CFSR_MFARVALID = BIT(7), | |
| }; | |
| std::string ret; | |
| uint32_t bfar, mfar, cfsr, shcsr, hfsr, dfsr, ipsr; | |
| if (pdata->struct_version == 1) { | |
| bfar = pdata->cm_v1.bfar; | |
| mfar = pdata->cm_v1.mfar; | |
| cfsr = pdata->cm_v1.cfsr; | |
| shcsr = pdata->cm_v1.shcsr; | |
| hfsr = pdata->cm_v1.hfsr; | |
| dfsr = pdata->cm_v1.dfsr; | |
| ipsr = pdata->cm_v1.regs[CORTEX_PANIC_REGISTER_IPSR]; | |
| } else { | |
| bfar = pdata->cm.bfar; | |
| mfar = pdata->cm.mfar; | |
| cfsr = pdata->cm.cfsr; | |
| shcsr = pdata->cm.shcsr; | |
| hfsr = pdata->cm.hfsr; | |
| dfsr = pdata->cm.dfsr; | |
| ipsr = pdata->cm.regs[CORTEX_PANIC_REGISTER_IPSR]; | |
| } | |
| ret = base::StringPrintf("\n"); | |
| if (pdata->cm.cfsr & CPU_NVIC_CFSR_BFARVALID) | |
| base::StrAppend(&ret, {base::StringPrintf("bfar=%08x, ", bfar)}); | |
| if (pdata->cm.cfsr & CPU_NVIC_CFSR_MFARVALID) | |
| base::StrAppend(&ret, {base::StringPrintf("mfar=%08x, ", mfar)}); | |
| base::StrAppend( | |
| &ret, {base::StringPrintf("cfsr=%08x, ", cfsr), | |
| base::StringPrintf("shcsr=%08x, ", shcsr), | |
| base::StringPrintf("hfsr=%08x, ", hfsr), | |
| base::StringPrintf("dfsr=%08x, ", dfsr), | |
| base::StringPrintf("ipsr=%08x", ipsr), base::StringPrintf("\n")}); | |
| return ret; | |
| } | |
| static std::string ParsePanicInfoCm(const struct panic_data* pdata) { | |
| const uint32_t* lregs = pdata->cm.regs; | |
| const uint32_t* sregs = nullptr; | |
| enum { ORIG_UNKNOWN = 0, ORIG_PROCESS, ORIG_HANDLER } origin = ORIG_UNKNOWN; | |
| int i; | |
| const char* panic_origins[3] = {"", "PROCESS", "HANDLER"}; | |
| std::string ret; | |
| ret = base::StringPrintf( | |
| "Saved panic data: %02x%s\n", pdata->flags, | |
| (pdata->flags & PANIC_DATA_FLAG_OLD_HOSTCMD ? "" : " (NEW)")); | |
| if (pdata->struct_version == 2) | |
| origin = ((lregs[11] & 0xf) == 1 || (lregs[11] & 0xf) == 9) ? ORIG_HANDLER | |
| : ORIG_PROCESS; | |
| /* | |
| * In pdata struct, 'regs', which is allocated before 'frame', has | |
| * one less elements in version 1. Choose the data according to the version. | |
| */ | |
| if (pdata->flags & PANIC_DATA_FLAG_FRAME_VALID) | |
| sregs = pdata->struct_version == 1 ? pdata->cm_v1.frame : pdata->cm.frame; | |
| base::StrAppend(&ret, {base::StringPrintf( | |
| "=== %s EXCEPTION: %02x ====== xPSR: %08x ===\n", | |
| panic_origins[origin], lregs[1] & 0xff, | |
| sregs ? sregs[7] : -1)}); | |
| for (i = 0; i < 4; ++i) | |
| base::StrAppend(&ret, {PrintPanicReg(i, sregs, i)}); | |
| for (i = 4; i < 10; ++i) | |
| base::StrAppend(&ret, {PrintPanicReg(i, lregs, i - 1)}); | |
| base::StrAppend(&ret, | |
| {PrintPanicReg(10, lregs, 9), PrintPanicReg(11, lregs, 10), | |
| PrintPanicReg(12, sregs, 4), | |
| PrintPanicReg(13, lregs, origin == ORIG_HANDLER ? 2 : 0), | |
| PrintPanicReg(14, sregs, 5), PrintPanicReg(15, sregs, 6), | |
| PanicShowExtraCm(pdata)}); | |
| return ret; | |
| } | |
| static std::string ParsePanicInfoNds32(const struct panic_data* pdata) { | |
| const uint32_t* regs = pdata->nds_n8.regs; | |
| uint32_t itype = pdata->nds_n8.itype; | |
| uint32_t ipc = pdata->nds_n8.ipc; | |
| uint32_t ipsw = pdata->nds_n8.ipsw; | |
| std::string ret; | |
| ret = base::StringPrintf( | |
| "Saved panic data: %02x%s\n", pdata->flags, | |
| (pdata->flags & PANIC_DATA_FLAG_OLD_HOSTCMD ? "" : " (NEW)")); | |
| base::StrAppend( | |
| &ret, | |
| {base::StringPrintf("=== EXCEP: ITYPE=%x ===\n", itype), | |
| base::StringPrintf("R0 %08x R1 %08x R2 %08x R3 %08x\n", regs[0], | |
| regs[1], regs[2], regs[3]), | |
| base::StringPrintf("R4 %08x R5 %08x R6 %08x R7 %08x\n", regs[4], | |
| regs[5], regs[6], regs[7]), | |
| base::StringPrintf("R8 %08x R9 %08x R10 %08x R15 %08x\n", regs[8], | |
| regs[9], regs[10], regs[11]), | |
| base::StringPrintf("FP %08x GP %08x LP %08x SP %08x\n", regs[12], | |
| regs[13], regs[14], regs[15]), | |
| base::StringPrintf("IPC %08x IPSW %05x\n", ipc, ipsw), | |
| base::StringPrintf("SWID of ITYPE: %x\n", ((itype >> 16) & 0x7fff))}); | |
| return ret; | |
| } | |
| static std::string ParsePanicInfoRv32i(const struct panic_data* pdata) { | |
| const uint32_t* regs; | |
| uint32_t mcause, mepc; | |
| regs = reinterpret_cast<const uint32_t*>(pdata->riscv.regs); | |
| mcause = pdata->riscv.mcause; | |
| mepc = pdata->riscv.mepc; | |
| std::string ret; | |
| ret = base::StringPrintf("=== EXCEPTION: MCAUSE=%x ===\n", mcause); | |
| base::StrAppend(&ret, | |
| {base::StringPrintf("S11 %08x S10 %08x S9 %08x S8 %08x\n", | |
| regs[0], regs[1], regs[2], regs[3]), | |
| base::StringPrintf("S7 %08x S6 %08x S5 %08x S4 %08x\n", | |
| regs[4], regs[5], regs[6], regs[7]), | |
| base::StringPrintf("S3 %08x S2 %08x S1 %08x S0 %08x\n", | |
| regs[8], regs[9], regs[10], regs[11]), | |
| base::StringPrintf("T6 %08x T5 %08x T4 %08x T3 %08x\n", | |
| regs[12], regs[13], regs[14], regs[15]), | |
| base::StringPrintf("T2 %08x T1 %08x T0 %08x A7 %08x\n", | |
| regs[16], regs[17], regs[18], regs[19]), | |
| base::StringPrintf("A6 %08x A5 %08x A4 %08x A3 %08x\n", | |
| regs[20], regs[21], regs[22], regs[23]), | |
| base::StringPrintf("A2 %08x A1 %08x A0 %08x TP %08x\n", | |
| regs[24], regs[25], regs[26], regs[27]), | |
| base::StringPrintf("GP %08x RA %08x SP %08x MEPC %08x\n", | |
| regs[28], regs[29], regs[30], mepc)}); | |
| return ret; | |
| } | |
| } // namespace | |
| base::expected<std::vector<uint8_t>, std::string> GetPanicInput( | |
| size_t max_size) { | |
| size_t size = 0; | |
| size_t read; | |
| std::vector<uint8_t> data(max_size); | |
| while (1) { | |
| read = fread(&data[size], 1, max_size - size, stdin); | |
| if (read < 0) { | |
| return base::unexpected("Cannot read panicinfo from stdin."); | |
| } | |
| if (read == 0) | |
| break; | |
| size += read; | |
| if (size >= max_size) { | |
| return base::unexpected("Too much panicinfo data in stdin."); | |
| } | |
| } | |
| data.resize(size); | |
| return data; | |
| } | |
| base::expected<std::string, std::string> ParsePanicInfo( | |
| base::span<const uint8_t> data) { | |
| size_t size = data.size(); | |
| /* Size of the panic information "header". */ | |
| const size_t header_size = 4; | |
| /* Size of the panic information "trailer" (struct_size and magic). */ | |
| const size_t trailer_size = | |
| sizeof(struct panic_data) - offsetof(struct panic_data, struct_size); | |
| struct panic_data pdata = {0}; | |
| size_t copy_size; | |
| std::string warning; | |
| if (size < (header_size + trailer_size)) { | |
| return base::unexpected( | |
| base::StringPrintf("ERROR: Panic data too short (%zd).\n", size)); | |
| } | |
| if (size > sizeof(pdata)) { | |
| warning = base::StringPrintf("WARNING: Panic data too large (%zd > %zd)\n", | |
| size, sizeof(pdata)); | |
| copy_size = sizeof(pdata); | |
| } else { | |
| copy_size = size; | |
| } | |
| /* Copy the data into pdata, as the struct size may have changed. */ | |
| memcpy(&pdata, data.data(), copy_size); | |
| /* Then copy the trailer in position. */ | |
| memcpy(reinterpret_cast<uint8_t*>(&pdata) + | |
| (sizeof(struct panic_data) - trailer_size), | |
| data.last(trailer_size).data(), trailer_size); | |
| /* | |
| * We only understand panic data with version in [1, 2]. Error on invalid | |
| * versions. | |
| */ | |
| if (pdata.struct_version > 2 || pdata.struct_version == 0) | |
| return base::unexpected( | |
| warning + | |
| base::StringPrintf("ERROR: Unknown panic data version (%d).\n", | |
| pdata.struct_version)); | |
| if (pdata.reserved != 0) | |
| return base::unexpected( | |
| warning + base::StringPrintf("ERROR: Panic reserve is not 0 (%d).\n", | |
| pdata.reserved)); | |
| /* Validate flag is within BIT(0) to BIT(6) inclusive. */ | |
| if (pdata.flags >> 7) | |
| return base::unexpected( | |
| warning + | |
| base::StringPrintf("ERROR: Incorrect flag (%d).\n", pdata.flags)); | |
| /* | |
| * Validate magic number. This is unlikely to happen but we should investigate | |
| * the mismatching in crash reports. | |
| */ | |
| if (pdata.magic != PANIC_DATA_MAGIC) | |
| base::StrAppend( | |
| &warning, {base::StringPrintf("WARNING: Incorrect panic magic (%d).\n", | |
| pdata.magic)}); | |
| /* | |
| * The size mismatching is unlikely to happen but we should investiage this | |
| * case in crash reports. | |
| */ | |
| if (pdata.struct_size != size) | |
| base::StrAppend( | |
| &warning, {base::StringPrintf( | |
| "WARNING: Panic struct size inconsistent (%u vs %zd).\n", | |
| pdata.struct_size, size)}); | |
| std::string ret; | |
| switch (pdata.arch) { | |
| case PANIC_ARCH_CORTEX_M: | |
| return base::ok(warning + ParsePanicInfoCm(&pdata)); | |
| case PANIC_ARCH_NDS32_N8: | |
| return base::ok(warning + ParsePanicInfoNds32(&pdata)); | |
| case PANIC_ARCH_RISCV_RV32I: | |
| return base::ok(warning + ParsePanicInfoRv32i(&pdata)); | |
| default: | |
| return base::unexpected( | |
| warning + base::StringPrintf("ERROR: Unknown architecture (%d).\n", | |
| pdata.arch)); | |
| } | |
| } | |
| } // namespace ec |