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
9from enum import IntEnum
10
11import elftools
12from elftools.elf.elffile import ELFFile
13
14# ELF section flags
15SHF_WRITE = 0x1
16SHF_ALLOC = 0x2
17SHF_EXEC = 0x4
18SHF_WRITE_ALLOC = SHF_WRITE | SHF_ALLOC
19SHF_ALLOC_EXEC = SHF_ALLOC | SHF_EXEC
20
21
22# Must match enum in thread_info.c
23class ThreadInfoOffset(IntEnum):
24    THREAD_INFO_OFFSET_VERSION = 0
25    THREAD_INFO_OFFSET_K_CURR_THREAD = 1
26    THREAD_INFO_OFFSET_K_THREADS = 2
27    THREAD_INFO_OFFSET_T_ENTRY = 3
28    THREAD_INFO_OFFSET_T_NEXT_THREAD = 4
29    THREAD_INFO_OFFSET_T_STATE = 5
30    THREAD_INFO_OFFSET_T_USER_OPTIONS = 6
31    THREAD_INFO_OFFSET_T_PRIO = 7
32    THREAD_INFO_OFFSET_T_STACK_PTR = 8
33    THREAD_INFO_OFFSET_T_NAME = 9
34    THREAD_INFO_OFFSET_T_ARCH = 10
35    THREAD_INFO_OFFSET_T_PREEMPT_FLOAT = 11
36    THREAD_INFO_OFFSET_T_COOP_FLOAT = 12
37    THREAD_INFO_OFFSET_T_ARM_EXC_RETURN = 13
38    THREAD_INFO_OFFSET_T_ARC_RELINQUISH_CAUSE = 14
39
40    def __int__(self):
41        return self.value
42
43
44logger = logging.getLogger("parser")
45
46
47class CoredumpElfFile:
48    """
49    Class to parse ELF file for memory content in various sections.
50    There are read-only sections (e.g. text and rodata) where
51    the memory content does not need to be dumped via coredump
52    and can be retrieved from the ELF file.
53    """
54
55    def __init__(self, elffile):
56        self.elffile = elffile
57        self.fd = None
58        self.elf = None
59        self.memory_regions = list()
60        self.kernel_thread_info_offsets = None
61        self.kernel_thread_info_num_offsets = None
62        self.kernel_thread_info_size_t_size = None
63
64    def open(self):
65        self.fd = open(self.elffile, "rb")
66        self.elf = ELFFile(self.fd)
67
68    def close(self):
69        self.fd.close()
70
71    def get_memory_regions(self):
72        return self.memory_regions
73
74    def get_kernel_thread_info_size_t_size(self):
75        return self.kernel_thread_info_size_t_size
76
77    def has_kernel_thread_info(self):
78        return self.kernel_thread_info_offsets is not None
79
80    def get_kernel_thread_info_offset(self, thread_info_offset_index):
81        if (
82            self.has_kernel_thread_info()
83            and thread_info_offset_index
84            <= ThreadInfoOffset.THREAD_INFO_OFFSET_T_ARC_RELINQUISH_CAUSE
85        ):
86            return self.kernel_thread_info_offsets[thread_info_offset_index]
87        else:
88            return None
89
90    def parse(self):
91        if self.fd is None:
92            self.open()
93
94        kernel_thread_info_offsets_segment = None
95        kernel_thread_info_num_offsets_segment = None
96        _kernel_thread_info_offsets = None
97        _kernel_thread_info_num_offsets = None
98        _kernel_thread_info_size_t_size = None
99
100        for section in self.elf.iter_sections():
101            # Find symbols for _kernel_thread_info data
102            if isinstance(section, elftools.elf.sections.SymbolTableSection):
103                _kernel_thread_info_offsets = section.get_symbol_by_name(
104                    "_kernel_thread_info_offsets"
105                )
106                _kernel_thread_info_num_offsets = section.get_symbol_by_name(
107                    "_kernel_thread_info_num_offsets"
108                )
109                _kernel_thread_info_size_t_size = section.get_symbol_by_name(
110                    "_kernel_thread_info_size_t_size"
111                )
112
113            # REALLY NEED to match exact type as all other sections
114            # (debug, text, etc.) are descendants where
115            # isinstance() would match.
116            if type(section) is not elftools.elf.sections.Section:  # pylint: disable=unidiomatic-typecheck
117                continue
118
119            size = section['sh_size']
120            flags = section['sh_flags']
121            sec_start = section['sh_addr']
122            sec_end = sec_start + size - 1
123
124            store = False
125            sect_desc = "?"
126
127            if section['sh_type'] == 'SHT_PROGBITS':
128                if (flags & SHF_ALLOC_EXEC) == SHF_ALLOC_EXEC:
129                    # Text section
130                    store = True
131                    sect_desc = "text"
132                elif (flags & SHF_WRITE_ALLOC) == SHF_WRITE_ALLOC:
133                    # Data section
134                    #
135                    # Running app changes the content so no need
136                    # to store
137                    pass
138                elif (flags & SHF_ALLOC) == SHF_ALLOC:
139                    # Read only data section
140                    store = True
141                    sect_desc = "read-only data"
142
143            if store:
144                mem_region = {"start": sec_start, "end": sec_end, "data": section.data()}
145                logger.info(
146                    f'ELF Section: 0x{mem_region["start"]:x} to 0x{mem_region["end"]:x} '
147                    f'of size {mem_region["data"]:d} ({sect_desc:s})'
148                )
149
150                self.memory_regions.append(mem_region)
151
152        if (
153            _kernel_thread_info_size_t_size is not None
154            and _kernel_thread_info_num_offsets is not None
155            and _kernel_thread_info_offsets is not None
156        ):
157            for seg in self.elf.iter_segments():
158                if seg.header['p_type'] != 'PT_LOAD':
159                    continue
160
161                # Store segment of kernel_thread_info_offsets
162                info_offsets_symbol = _kernel_thread_info_offsets[0]
163                if (
164                    info_offsets_symbol['st_value'] >= seg['p_vaddr']
165                    and info_offsets_symbol['st_value'] < seg['p_vaddr'] + seg['p_filesz']
166                ):
167                    kernel_thread_info_offsets_segment = seg
168
169                # Store segment of kernel_thread_info_num_offsets
170                num_offsets_symbol = _kernel_thread_info_num_offsets[0]
171                if (
172                    num_offsets_symbol['st_value'] >= seg['p_vaddr']
173                    and num_offsets_symbol['st_value'] < seg['p_vaddr'] + seg['p_filesz']
174                ):
175                    kernel_thread_info_num_offsets_segment = seg
176
177                # Read and store size_t size
178                size_t_size_symbol = _kernel_thread_info_size_t_size[0]
179                if (
180                    size_t_size_symbol['st_value'] >= seg['p_vaddr']
181                    and size_t_size_symbol['st_value'] < seg['p_vaddr'] + seg['p_filesz']
182                ):
183                    offset = size_t_size_symbol['st_value'] - seg['p_vaddr'] + seg['p_offset']
184                    self.elf.stream.seek(offset)
185                    self.kernel_thread_info_size_t_size = struct.unpack(
186                        'B', self.elf.stream.read(size_t_size_symbol['st_size'])
187                    )[0]
188
189            struct_format = "I"
190            if self.kernel_thread_info_size_t_size == 8:
191                struct_format = "Q"
192
193            # Read and store count of offset values
194            num_offsets_symbol = _kernel_thread_info_num_offsets[0]
195            offset = (
196                num_offsets_symbol['st_value']
197                - kernel_thread_info_num_offsets_segment['p_vaddr']
198                + kernel_thread_info_num_offsets_segment['p_offset']
199            )
200            self.elf.stream.seek(offset)
201            self.kernel_thread_info_num_offsets = struct.unpack(
202                struct_format, self.elf.stream.read(num_offsets_symbol['st_size'])
203            )[0]
204
205            array_format = ""
206            for _ in range(self.kernel_thread_info_num_offsets):
207                array_format = array_format + struct_format
208
209            # Read and store array of offset values
210            info_offsets_symbol = _kernel_thread_info_offsets[0]
211            offset = (
212                info_offsets_symbol['st_value']
213                - kernel_thread_info_offsets_segment['p_vaddr']
214                + kernel_thread_info_offsets_segment['p_offset']
215            )
216            self.elf.stream.seek(offset)
217            self.kernel_thread_info_offsets = struct.unpack(
218                array_format, self.elf.stream.read(info_offsets_symbol['st_size'])
219            )
220
221        return True
222