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
10import elftools
11from elftools.elf.elffile import ELFFile
12from enum import IntEnum
13
14
15# ELF section flags
16SHF_WRITE = 0x1
17SHF_ALLOC = 0x2
18SHF_EXEC = 0x4
19SHF_WRITE_ALLOC = SHF_WRITE | SHF_ALLOC
20SHF_ALLOC_EXEC = SHF_ALLOC | SHF_EXEC
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 self.has_kernel_thread_info() and thread_info_offset_index <= ThreadInfoOffset.THREAD_INFO_OFFSET_T_ARC_RELINQUISH_CAUSE:
82            return self.kernel_thread_info_offsets[thread_info_offset_index]
83        else:
84            return None
85
86    def parse(self):
87        if self.fd is None:
88            self.open()
89
90        kernel_thread_info_offsets_segment = None
91        kernel_thread_info_num_offsets_segment = None
92        _kernel_thread_info_offsets = None
93        _kernel_thread_info_num_offsets = None
94        _kernel_thread_info_size_t_size = None
95
96        for section in self.elf.iter_sections():
97            # Find symbols for _kernel_thread_info data
98            if isinstance(section, elftools.elf.sections.SymbolTableSection):
99                _kernel_thread_info_offsets = section.get_symbol_by_name("_kernel_thread_info_offsets")
100                _kernel_thread_info_num_offsets = section.get_symbol_by_name("_kernel_thread_info_num_offsets")
101                _kernel_thread_info_size_t_size = section.get_symbol_by_name("_kernel_thread_info_size_t_size")
102
103            # REALLY NEED to match exact type as all other sections
104            # (debug, text, etc.) are descendants where
105            # isinstance() would match.
106            if type(section) is not elftools.elf.sections.Section: # pylint: disable=unidiomatic-typecheck
107                continue
108
109            size = section['sh_size']
110            flags = section['sh_flags']
111            sec_start = section['sh_addr']
112            sec_end = sec_start + size - 1
113
114            store = False
115            sect_desc = "?"
116
117            if section['sh_type'] == 'SHT_PROGBITS':
118                if (flags & SHF_ALLOC_EXEC) == SHF_ALLOC_EXEC:
119                    # Text section
120                    store = True
121                    sect_desc = "text"
122                elif (flags & SHF_WRITE_ALLOC) == SHF_WRITE_ALLOC:
123                    # Data section
124                    #
125                    # Running app changes the content so no need
126                    # to store
127                    pass
128                elif (flags & SHF_ALLOC) == SHF_ALLOC:
129                    # Read only data section
130                    store = True
131                    sect_desc = "read-only data"
132
133            if store:
134                mem_region = {"start": sec_start, "end": sec_end, "data": section.data()}
135                logger.info("ELF Section: 0x%x to 0x%x of size %d (%s)" %
136                            (mem_region["start"],
137                             mem_region["end"],
138                             len(mem_region["data"]),
139                             sect_desc))
140
141                self.memory_regions.append(mem_region)
142
143        if _kernel_thread_info_size_t_size is not None and \
144           _kernel_thread_info_num_offsets is not None and \
145           _kernel_thread_info_offsets is not None:
146            for seg in self.elf.iter_segments():
147                if seg.header['p_type'] != 'PT_LOAD':
148                    continue
149
150                # Store segment of kernel_thread_info_offsets
151                info_offsets_symbol = _kernel_thread_info_offsets[0]
152                if info_offsets_symbol['st_value'] >= seg['p_vaddr'] and info_offsets_symbol['st_value'] < seg['p_vaddr'] + seg['p_filesz']:
153                    kernel_thread_info_offsets_segment = seg
154
155                # Store segment of kernel_thread_info_num_offsets
156                num_offsets_symbol = _kernel_thread_info_num_offsets[0]
157                if num_offsets_symbol['st_value'] >= seg['p_vaddr'] and num_offsets_symbol['st_value'] < seg['p_vaddr'] + seg['p_filesz']:
158                    kernel_thread_info_num_offsets_segment = seg
159
160                # Read and store size_t size
161                size_t_size_symbol = _kernel_thread_info_size_t_size[0]
162                if size_t_size_symbol['st_value'] >= seg['p_vaddr'] and size_t_size_symbol['st_value'] < seg['p_vaddr'] + seg['p_filesz']:
163                    offset = size_t_size_symbol['st_value'] - seg['p_vaddr'] + seg['p_offset']
164                    self.elf.stream.seek(offset)
165                    self.kernel_thread_info_size_t_size = struct.unpack('B', self.elf.stream.read(size_t_size_symbol['st_size']))[0]
166
167            struct_format = "I"
168            if self.kernel_thread_info_size_t_size == 8:
169                struct_format = "Q"
170
171            # Read and store count of offset values
172            num_offsets_symbol = _kernel_thread_info_num_offsets[0]
173            offset = num_offsets_symbol['st_value'] - kernel_thread_info_num_offsets_segment['p_vaddr'] + kernel_thread_info_num_offsets_segment['p_offset']
174            self.elf.stream.seek(offset)
175            self.kernel_thread_info_num_offsets = struct.unpack(struct_format, self.elf.stream.read(num_offsets_symbol['st_size']))[0]
176
177            array_format = ""
178            for _ in range(self.kernel_thread_info_num_offsets):
179                array_format = array_format + struct_format
180
181            # Read and store array of offset values
182            info_offsets_symbol = _kernel_thread_info_offsets[0]
183            offset = info_offsets_symbol['st_value'] - kernel_thread_info_offsets_segment['p_vaddr'] + kernel_thread_info_offsets_segment['p_offset']
184            self.elf.stream.seek(offset)
185            self.kernel_thread_info_offsets = struct.unpack(array_format, self.elf.stream.read(info_offsets_symbol['st_size']))
186
187        return True
188