blob: 277c62f574698127ddb48078614f34636c481dce [file] [log] [blame] [edit]
#!/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