zephyr/scripts/coredump/parser/log_parser.py
Daniel Leung 49206a86ff debug/coredump: add a primitive coredump mechanism
This adds a very primitive coredump mechanism under subsys/debug
where during fatal error, register and memory content can be
dumped to coredump backend. One such backend utilizing log
module for output is included. Once the coredump log is converted
to a binary file, it can be used with the ELF output file as
inputs to an overly simplified implementation of a GDB server.
This GDB server can be attached via the target remote command of
GDB and will be serving register and memory content. This allows
using GDB to examine stack and memory where the fatal error
occurred.

Signed-off-by: Daniel Leung <daniel.leung@intel.com>
2020-08-24 20:28:24 -04:00

168 lines
4.5 KiB
Python

#!/usr/bin/env python3
#
# Copyright (c) 2020 Intel Corporation
#
# SPDX-License-Identifier: Apache-2.0
import logging
import struct
# Note: keep sync with C code
Z_COREDUMP_HDR_ID = b'ZE'
Z_COREDUMP_HDR_VER = 1
LOG_HDR_STRUCT = "<ccHHBBI"
LOG_HDR_SIZE = struct.calcsize(LOG_HDR_STRUCT)
Z_COREDUMP_ARCH_HDR_ID = b'A'
LOG_ARCH_HDR_STRUCT = "<cHH"
LOG_ARCH_HDR_SIZE = struct.calcsize(LOG_ARCH_HDR_STRUCT)
Z_COREDUMP_MEM_HDR_ID = b'M'
Z_COREDUMP_MEM_HDR_VER = 1
LOG_MEM_HDR_STRUCT = "<cH"
LOG_MEM_HDR_SIZE = struct.calcsize(LOG_MEM_HDR_STRUCT)
logger = logging.getLogger("parser")
def reason_string(reason):
# Keep sync with "enum k_fatal_error_reason"
ret = "(Unknown)"
if reason == 0:
ret = "K_ERR_CPU_EXCEPTION"
elif reason == 1:
ret = "K_ERR_SPURIOUS_IRQ"
elif reason == 2:
ret = "K_ERR_STACK_CHK_FAIL"
elif reason == 3:
ret = "K_ERR_KERNEL_OOPS"
elif reason == 4:
ret = "K_ERR_KERNEL_PANIC"
return ret
class CoredumpLogFile:
"""
Process the binary coredump file for register block
and memory blocks.
"""
def __init__(self, logfile):
self.logfile = logfile
self.fd = None
self.log_hdr = None
self.arch_data = list()
self.memory_regions = list()
def open(self):
self.fd = open(self.logfile, "rb")
def close(self):
self.fd.close()
def get_arch_data(self):
return self.arch_data
def get_memory_regions(self):
return self.memory_regions
def parse_arch_section(self):
hdr = self.fd.read(LOG_ARCH_HDR_SIZE)
_, hdr_ver, num_bytes = struct.unpack(LOG_ARCH_HDR_STRUCT, hdr)
arch_data = self.fd.read(num_bytes)
self.arch_data = {"hdr_ver" : hdr_ver, "data" : arch_data}
return True
def parse_memory_section(self):
hdr = self.fd.read(LOG_MEM_HDR_SIZE)
_, hdr_ver = struct.unpack(LOG_MEM_HDR_STRUCT, hdr)
if hdr_ver != Z_COREDUMP_MEM_HDR_VER:
logger.error(f"Memory block version: {hdr_ver}, expected {Z_COREDUMP_MEM_HDR_VER}!")
return False
# Figure out how to read the start and end addresses
ptr_fmt = None
if self.log_hdr["ptr_size"] == 64:
ptr_fmt = "QQ"
elif self.log_hdr["ptr_size"] == 32:
ptr_fmt = "II"
else:
return False
data = self.fd.read(struct.calcsize(ptr_fmt))
saddr, eaddr = struct.unpack(ptr_fmt, data)
size = eaddr - saddr
data = self.fd.read(size)
mem = {"start": saddr, "end": eaddr, "data": data}
self.memory_regions.append(mem)
logger.info("Memory: 0x%x to 0x%x of size %d" %
(saddr, eaddr, size))
return True
def parse(self):
if self.fd is None:
self.open()
hdr = self.fd.read(LOG_HDR_SIZE)
id1, id2, hdr_ver, tgt_code, ptr_size, flags, reason = struct.unpack(LOG_HDR_STRUCT, hdr)
if (id1 + id2) != Z_COREDUMP_HDR_ID:
# ID in header does not match
logger.error("Log header ID not found...")
return False
if hdr_ver != Z_COREDUMP_HDR_VER:
logger.error(f"Log version: {hdr_ver}, expected: {Z_COREDUMP_HDR_VER}!")
return False
ptr_size = 2 ** ptr_size
self.log_hdr = {
"hdr_version": hdr_ver,
"tgt_code": tgt_code,
"ptr_size": ptr_size,
"flags": flags,
"reason": reason,
}
logger.info("Reason: {0}".format(reason_string(reason)))
logger.info(f"Pointer size {ptr_size}")
del id1, id2, hdr_ver, tgt_code, ptr_size, flags, reason
while True:
section_id = self.fd.read(1)
if not section_id:
# no more data to read
break
self.fd.seek(-1, 1) # go back 1 byte
if section_id == Z_COREDUMP_ARCH_HDR_ID:
if not self.parse_arch_section():
logger.error("Cannot parse architecture section")
return False
elif section_id == Z_COREDUMP_MEM_HDR_ID:
if not self.parse_memory_section():
logger.error("Cannot parse memory section")
return False
else:
# Unknown section in log file
logger.error(f"Unknown section in log file with ID {section_id}")
return False
return True