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