#!/usr/bin/env python3 # # Copyright (c) 2020 Intel Corporation # # SPDX-License-Identifier: Apache-2.0 import logging import struct import elftools from elftools.elf.elffile import ELFFile from enum import IntEnum # ELF section flags SHF_WRITE = 0x1 SHF_ALLOC = 0x2 SHF_EXEC = 0x4 SHF_WRITE_ALLOC = SHF_WRITE | SHF_ALLOC SHF_ALLOC_EXEC = SHF_ALLOC | SHF_EXEC # Must match enum in thread_info.c class ThreadInfoOffset(IntEnum): THREAD_INFO_OFFSET_VERSION = 0 THREAD_INFO_OFFSET_K_CURR_THREAD = 1 THREAD_INFO_OFFSET_K_THREADS = 2 THREAD_INFO_OFFSET_T_ENTRY = 3 THREAD_INFO_OFFSET_T_NEXT_THREAD = 4 THREAD_INFO_OFFSET_T_STATE = 5 THREAD_INFO_OFFSET_T_USER_OPTIONS = 6 THREAD_INFO_OFFSET_T_PRIO = 7 THREAD_INFO_OFFSET_T_STACK_PTR = 8 THREAD_INFO_OFFSET_T_NAME = 9 THREAD_INFO_OFFSET_T_ARCH = 10 THREAD_INFO_OFFSET_T_PREEMPT_FLOAT = 11 THREAD_INFO_OFFSET_T_COOP_FLOAT = 12 THREAD_INFO_OFFSET_T_ARM_EXC_RETURN = 13 THREAD_INFO_OFFSET_T_ARC_RELINQUISH_CAUSE = 14 def __int__(self): return self.value logger = logging.getLogger("parser") class CoredumpElfFile(): """ Class to parse ELF file for memory content in various sections. There are read-only sections (e.g. text and rodata) where the memory content does not need to be dumped via coredump and can be retrieved from the ELF file. """ def __init__(self, elffile): self.elffile = elffile self.fd = None self.elf = None self.memory_regions = list() self.kernel_thread_info_offsets = None self.kernel_thread_info_num_offsets = None self.kernel_thread_info_size_t_size = None def open(self): self.fd = open(self.elffile, "rb") self.elf = ELFFile(self.fd) def close(self): self.fd.close() def get_memory_regions(self): return self.memory_regions def get_kernel_thread_info_size_t_size(self): return self.kernel_thread_info_size_t_size def has_kernel_thread_info(self): return self.kernel_thread_info_offsets is not None def get_kernel_thread_info_offset(self, thread_info_offset_index): if self.has_kernel_thread_info() and thread_info_offset_index <= ThreadInfoOffset.THREAD_INFO_OFFSET_T_ARC_RELINQUISH_CAUSE: return self.kernel_thread_info_offsets[thread_info_offset_index] else: return None def parse(self): if self.fd is None: self.open() kernel_thread_info_offsets_segment = None kernel_thread_info_num_offsets_segment = None _kernel_thread_info_offsets = None _kernel_thread_info_num_offsets = None _kernel_thread_info_size_t_size = None for section in self.elf.iter_sections(): # Find symbols for _kernel_thread_info data if isinstance(section, elftools.elf.sections.SymbolTableSection): _kernel_thread_info_offsets = section.get_symbol_by_name("_kernel_thread_info_offsets") _kernel_thread_info_num_offsets = section.get_symbol_by_name("_kernel_thread_info_num_offsets") _kernel_thread_info_size_t_size = section.get_symbol_by_name("_kernel_thread_info_size_t_size") # REALLY NEED to match exact type as all other sections # (debug, text, etc.) are descendants where # isinstance() would match. if type(section) is not elftools.elf.sections.Section: # pylint: disable=unidiomatic-typecheck continue size = section['sh_size'] flags = section['sh_flags'] sec_start = section['sh_addr'] sec_end = sec_start + size - 1 store = False sect_desc = "?" if section['sh_type'] == 'SHT_PROGBITS': if (flags & SHF_ALLOC_EXEC) == SHF_ALLOC_EXEC: # Text section store = True sect_desc = "text" elif (flags & SHF_WRITE_ALLOC) == SHF_WRITE_ALLOC: # Data section # # Running app changes the content so no need # to store pass elif (flags & SHF_ALLOC) == SHF_ALLOC: # Read only data section store = True sect_desc = "read-only data" if store: mem_region = {"start": sec_start, "end": sec_end, "data": section.data()} logger.info("ELF Section: 0x%x to 0x%x of size %d (%s)" % (mem_region["start"], mem_region["end"], len(mem_region["data"]), sect_desc)) self.memory_regions.append(mem_region) if _kernel_thread_info_size_t_size is not None and \ _kernel_thread_info_num_offsets is not None and \ _kernel_thread_info_offsets is not None: for seg in self.elf.iter_segments(): if seg.header['p_type'] != 'PT_LOAD': continue # Store segment of kernel_thread_info_offsets info_offsets_symbol = _kernel_thread_info_offsets[0] if info_offsets_symbol['st_value'] >= seg['p_vaddr'] and info_offsets_symbol['st_value'] < seg['p_vaddr'] + seg['p_filesz']: kernel_thread_info_offsets_segment = seg # Store segment of kernel_thread_info_num_offsets num_offsets_symbol = _kernel_thread_info_num_offsets[0] if num_offsets_symbol['st_value'] >= seg['p_vaddr'] and num_offsets_symbol['st_value'] < seg['p_vaddr'] + seg['p_filesz']: kernel_thread_info_num_offsets_segment = seg # Read and store size_t size size_t_size_symbol = _kernel_thread_info_size_t_size[0] if size_t_size_symbol['st_value'] >= seg['p_vaddr'] and size_t_size_symbol['st_value'] < seg['p_vaddr'] + seg['p_filesz']: offset = size_t_size_symbol['st_value'] - seg['p_vaddr'] + seg['p_offset'] self.elf.stream.seek(offset) self.kernel_thread_info_size_t_size = struct.unpack('B', self.elf.stream.read(size_t_size_symbol['st_size']))[0] struct_format = "I" if self.kernel_thread_info_size_t_size == 8: struct_format = "Q" # Read and store count of offset values num_offsets_symbol = _kernel_thread_info_num_offsets[0] offset = num_offsets_symbol['st_value'] - kernel_thread_info_num_offsets_segment['p_vaddr'] + kernel_thread_info_num_offsets_segment['p_offset'] self.elf.stream.seek(offset) self.kernel_thread_info_num_offsets = struct.unpack(struct_format, self.elf.stream.read(num_offsets_symbol['st_size']))[0] array_format = "" for _ in range(self.kernel_thread_info_num_offsets): array_format = array_format + struct_format # Read and store array of offset values info_offsets_symbol = _kernel_thread_info_offsets[0] offset = info_offsets_symbol['st_value'] - kernel_thread_info_offsets_segment['p_vaddr'] + kernel_thread_info_offsets_segment['p_offset'] self.elf.stream.seek(offset) self.kernel_thread_info_offsets = struct.unpack(array_format, self.elf.stream.read(info_offsets_symbol['st_size'])) return True