1#!/usr/bin/env python3
2#
3# Copyright (c) 2020 Intel Corporation
4#
5# SPDX-License-Identifier: Apache-2.0
6
7import logging
8import struct
9
10# Note: keep sync with C code
11COREDUMP_HDR_ID = b'ZE'
12COREDUMP_HDR_VER = 2
13LOG_HDR_STRUCT = "<ccHHBBI"
14LOG_HDR_SIZE = struct.calcsize(LOG_HDR_STRUCT)
15
16COREDUMP_ARCH_HDR_ID = b'A'
17LOG_ARCH_HDR_STRUCT = "<cHH"
18LOG_ARCH_HDR_SIZE = struct.calcsize(LOG_ARCH_HDR_STRUCT)
19
20COREDUMP_THREADS_META_HDR_ID = b'T'
21LOG_THREADS_META_HDR_STRUCT = "<cHH"
22LOG_THREADS_META_HDR_SIZE = struct.calcsize(LOG_THREADS_META_HDR_STRUCT)
23
24COREDUMP_MEM_HDR_ID = b'M'
25COREDUMP_MEM_HDR_VER = 1
26LOG_MEM_HDR_STRUCT = "<cH"
27LOG_MEM_HDR_SIZE = struct.calcsize(LOG_MEM_HDR_STRUCT)
28
29
30logger = logging.getLogger("parser")
31
32
33def reason_string(reason):
34    # Keep sync with "enum k_fatal_error_reason"
35    ret = "(Unknown)"
36
37    if reason == 0:
38        ret = "K_ERR_CPU_EXCEPTION"
39    elif reason == 1:
40        ret = "K_ERR_SPURIOUS_IRQ"
41    elif reason == 2:
42        ret = "K_ERR_STACK_CHK_FAIL"
43    elif reason == 3:
44        ret = "K_ERR_KERNEL_OOPS"
45    elif reason == 4:
46        ret = "K_ERR_KERNEL_PANIC"
47
48    return ret
49
50
51class CoredumpLogFile:
52    """
53    Process the binary coredump file for register block
54    and memory blocks.
55    """
56
57    def __init__(self, logfile):
58        self.logfile = logfile
59        self.fd = None
60
61        self.log_hdr = None
62        self.arch_data = list()
63        self.memory_regions = list()
64        self.threads_metadata = {"hdr_ver": None, "data": None}
65
66    def open(self):
67        self.fd = open(self.logfile, "rb")
68
69    def close(self):
70        self.fd.close()
71
72    def get_arch_data(self):
73        return self.arch_data
74
75    def get_memory_regions(self):
76        return self.memory_regions
77
78    def get_threads_metadata(self):
79        return self.threads_metadata
80
81    def parse_arch_section(self):
82        hdr = self.fd.read(LOG_ARCH_HDR_SIZE)
83        _, hdr_ver, num_bytes = struct.unpack(LOG_ARCH_HDR_STRUCT, hdr)
84
85        arch_data = self.fd.read(num_bytes)
86
87        self.arch_data = {"hdr_ver": hdr_ver, "data": arch_data}
88
89        return True
90
91    def parse_threads_metadata_section(self):
92        hdr = self.fd.read(LOG_THREADS_META_HDR_SIZE)
93        _, hdr_ver, num_bytes = struct.unpack(LOG_THREADS_META_HDR_STRUCT, hdr)
94
95        data = self.fd.read(num_bytes)
96
97        self.threads_metadata = {"hdr_ver": hdr_ver, "data": data}
98
99        return True
100
101    def parse_memory_section(self):
102        hdr = self.fd.read(LOG_MEM_HDR_SIZE)
103        _, hdr_ver = struct.unpack(LOG_MEM_HDR_STRUCT, hdr)
104
105        if hdr_ver != COREDUMP_MEM_HDR_VER:
106            logger.error(f"Memory block version: {hdr_ver}, expected {COREDUMP_MEM_HDR_VER}!")
107            return False
108
109        # Figure out how to read the start and end addresses
110        ptr_fmt = None
111        if self.log_hdr["ptr_size"] == 64:
112            ptr_fmt = "QQ"
113        elif self.log_hdr["ptr_size"] == 32:
114            ptr_fmt = "II"
115        else:
116            return False
117
118        data = self.fd.read(struct.calcsize(ptr_fmt))
119        saddr, eaddr = struct.unpack(ptr_fmt, data)
120
121        size = eaddr - saddr
122
123        data = self.fd.read(size)
124
125        mem = {"start": saddr, "end": eaddr, "data": data}
126        self.memory_regions.append(mem)
127
128        logger.info(f"Memory: 0x{saddr:x} to 0x{eaddr:x} of size {size:d}")
129
130        return True
131
132    def parse(self):
133        if self.fd is None:
134            self.open()
135
136        hdr = self.fd.read(LOG_HDR_SIZE)
137        id1, id2, hdr_ver, tgt_code, ptr_size, flags, reason = struct.unpack(LOG_HDR_STRUCT, hdr)
138
139        if (id1 + id2) != COREDUMP_HDR_ID:
140            # ID in header does not match
141            logger.error("Log header ID not found...")
142            return False
143
144        if hdr_ver > COREDUMP_HDR_VER:
145            logger.error(f"Log version: {hdr_ver}, expected: {COREDUMP_HDR_VER}!")
146            return False
147
148        ptr_size = 2**ptr_size
149
150        self.log_hdr = {
151            "hdr_version": hdr_ver,
152            "tgt_code": tgt_code,
153            "ptr_size": ptr_size,
154            "flags": flags,
155            "reason": reason,
156        }
157
158        logger.info(f"Reason: {reason_string(reason)}")
159        logger.info(f"Pointer size {ptr_size}")
160
161        del id1, id2, hdr_ver, tgt_code, ptr_size, flags, reason
162
163        while True:
164            section_id = self.fd.read(1)
165            if not section_id:
166                # no more data to read
167                break
168
169            self.fd.seek(-1, 1)  # go back 1 byte
170            if section_id == COREDUMP_ARCH_HDR_ID:
171                if not self.parse_arch_section():
172                    logger.error("Cannot parse architecture section")
173                    return False
174            elif section_id == COREDUMP_THREADS_META_HDR_ID:
175                if not self.parse_threads_metadata_section():
176                    logger.error("Cannot parse threads metadata section")
177                    return False
178            elif section_id == COREDUMP_MEM_HDR_ID:
179                if not self.parse_memory_section():
180                    logger.error("Cannot parse memory section")
181                    return False
182            else:
183                # Unknown section in log file
184                logger.error(f"Unknown section in log file with ID {section_id}")
185                return False
186
187        return True
188