zephyr/scripts/build/elf_parser.py
Jordan Yates 60aa9e1857 scripts: build: elf_parser: fix relocatable data offsets
Based on investigations with relocatable `.elf` files, the symbol table
data in relocatable files is not shifted by the section address. Remove
the shift from these files to ensure that data can be pulled from the
symbol table.

Signed-off-by: Jordan Yates <jordan.yates@data61.csiro.au>
2023-06-02 18:53:06 -04:00

282 lines
9.7 KiB
Python

#!/usr/bin/env python3
#
# Copyright (c) 2022, CSIRO
#
# SPDX-License-Identifier: Apache-2.0
import struct
import sys
from packaging import version
import elftools
from elftools.elf.elffile import ELFFile
from elftools.elf.sections import SymbolTableSection
if version.parse(elftools.__version__) < version.parse('0.24'):
sys.exit("pyelftools is out of date, need version 0.24 or later")
class _Symbol:
"""
Parent class for objects derived from an elf symbol.
"""
def __init__(self, elf, sym):
self.elf = elf
self.sym = sym
self.data = self.elf.symbol_data(sym)
def _data_native_read(self, offset):
(format, size) = self.elf.native_struct_format
return struct.unpack(format, self.data[offset:offset + size])[0]
class DevicePM(_Symbol):
"""
Represents information about device PM capabilities.
"""
required_ld_consts = [
"_PM_DEVICE_STRUCT_FLAGS_OFFSET",
"_PM_DEVICE_FLAG_PD"
]
def __init__(self, elf, sym):
super().__init__(elf, sym)
self.flags = self._data_native_read(self.elf.ld_consts['_PM_DEVICE_STRUCT_FLAGS_OFFSET'])
@property
def is_power_domain(self):
return self.flags & (1 << self.elf.ld_consts["_PM_DEVICE_FLAG_PD"])
class DeviceOrdinals(_Symbol):
"""
Represents information about device dependencies.
"""
DEVICE_HANDLE_SEP = -32768
DEVICE_HANDLE_ENDS = 32767
DEVICE_HANDLE_NULL = 0
def __init__(self, elf, sym):
super().__init__(elf, sym)
format = "<" if self.elf.little_endian else ">"
format += "{:d}h".format(len(self.data) // 2)
self._ordinals = struct.unpack(format, self.data)
self._ordinals_split = []
# Split ordinals on DEVICE_HANDLE_SEP
prev = 1
for idx, val in enumerate(self._ordinals, 1):
if val == self.DEVICE_HANDLE_SEP:
self._ordinals_split.append(self._ordinals[prev:idx-1])
prev = idx
self._ordinals_split.append(self._ordinals[prev:])
@property
def self_ordinal(self):
return self._ordinals[0]
@property
def ordinals(self):
return self._ordinals_split
class Device(_Symbol):
"""
Represents information about a device object and its references to other objects.
"""
required_ld_consts = [
"_DEVICE_STRUCT_HANDLES_OFFSET",
"_DEVICE_STRUCT_PM_OFFSET"
]
def __init__(self, elf, sym):
super().__init__(elf, sym)
self.edt_node = None
self.handle = None
self.ordinals = None
self.pm = None
# Devicetree dependencies, injected dependencies, supported devices
self.devs_depends_on = set()
self.devs_depends_on_injected = set()
self.devs_supports = set()
# Point to the handles instance associated with the device;
# assigned by correlating the device struct handles pointer
# value with the addr of a Handles instance.
ordinal_offset = self.elf.ld_consts['_DEVICE_STRUCT_HANDLES_OFFSET']
self.obj_ordinals = self._data_native_read(ordinal_offset)
self.obj_pm = None
if '_DEVICE_STRUCT_PM_OFFSET' in self.elf.ld_consts:
pm_offset = self.elf.ld_consts['_DEVICE_STRUCT_PM_OFFSET']
self.obj_pm = self._data_native_read(pm_offset)
@property
def ordinal(self):
return self.ordinals.self_ordinal
class ZephyrElf:
"""
Represents information about devices in an elf file.
"""
def __init__(self, kernel, edt, device_start_symbol):
self.elf = ELFFile(open(kernel, "rb"))
self.relocatable = self.elf['e_type'] == 'ET_REL'
self.edt = edt
self.devices = []
self.ld_consts = self._symbols_find_value(set([device_start_symbol, *Device.required_ld_consts, *DevicePM.required_ld_consts]))
self._device_parse_and_link()
@property
def little_endian(self):
"""
True if the elf file is for a little-endian architecture.
"""
return self.elf.little_endian
@property
def native_struct_format(self):
"""
Get the struct format specifier and byte size of the native machine type.
"""
format = "<" if self.little_endian else ">"
if self.elf.elfclass == 32:
format += "I"
size = 4
else:
format += "Q"
size = 8
return (format, size)
def symbol_data(self, sym):
"""
Retrieve the raw bytes associated with a symbol from the elf file.
"""
# Symbol data parameters
addr = sym.entry.st_value
length = sym.entry.st_size
# Section associated with the symbol
section = self.elf.get_section(sym.entry['st_shndx'])
data = section.data()
# Relocatable data does not appear to be shifted
offset = addr - (0 if self.relocatable else section['sh_addr'])
# Validate data extraction
assert offset + length <= len(data)
# Extract symbol bytes from section
return bytes(data[offset:offset + length])
def _symbols_find_value(self, names):
symbols = {}
for section in self.elf.iter_sections():
if isinstance(section, SymbolTableSection):
for sym in section.iter_symbols():
if sym.name in names:
symbols[sym.name] = sym.entry.st_value
return symbols
def _object_find_named(self, prefix, cb):
for section in self.elf.iter_sections():
if isinstance(section, SymbolTableSection):
for sym in section.iter_symbols():
if sym.entry.st_info.type != 'STT_OBJECT':
continue
if sym.name.startswith(prefix):
cb(sym)
def _link_devices(self, devices):
# Compute the dependency graph induced from the full graph restricted to the
# the nodes that exist in the application. Note that the edges in the
# induced graph correspond to paths in the full graph.
root = self.edt.dep_ord2node[0]
for ord, dev in devices.items():
n = self.edt.dep_ord2node[ord]
deps = set(n.depends_on)
while len(deps) > 0:
dn = deps.pop()
if dn.dep_ordinal in devices:
# this is used
dev.devs_depends_on.add(devices[dn.dep_ordinal])
elif dn != root:
# forward the dependency up one level
for ddn in dn.depends_on:
deps.add(ddn)
sups = set(n.required_by)
while len(sups) > 0:
sn = sups.pop()
if sn.dep_ordinal in devices:
dev.devs_supports.add(devices[sn.dep_ordinal])
else:
# forward the support down one level
for ssn in sn.required_by:
sups.add(ssn)
def _link_injected(self, devices):
for dev in devices.values():
injected = dev.ordinals.ordinals[1]
for inj in injected:
if inj in devices:
dev.devs_depends_on_injected.add(devices[inj])
devices[inj].devs_supports.add(dev)
def _device_parse_and_link(self):
# Find all PM structs
pm_structs = {}
def _on_pm(sym):
pm_structs[sym.entry.st_value] = DevicePM(self, sym)
self._object_find_named('__pm_device_', _on_pm)
# Find all ordinal arrays
ordinal_arrays = {}
def _on_ordinal(sym):
ordinal_arrays[sym.entry.st_value] = DeviceOrdinals(self, sym)
self._object_find_named('__devicehdl_', _on_ordinal)
# Find all device structs
def _on_device(sym):
self.devices.append(Device(self, sym))
self._object_find_named('__device_', _on_device)
# Sort the device array by address for handle calculation
self.devices = sorted(self.devices, key = lambda k: k.sym.entry.st_value)
# Assign handles to the devices
for idx, dev in enumerate(self.devices):
dev.handle = 1 + idx
# Link devices structs with PM and ordinals
for dev in self.devices:
if dev.obj_pm in pm_structs:
dev.pm = pm_structs[dev.obj_pm]
if dev.obj_ordinals in ordinal_arrays:
dev.ordinals = ordinal_arrays[dev.obj_ordinals]
if dev.ordinal != DeviceOrdinals.DEVICE_HANDLE_NULL:
dev.edt_node = self.edt.dep_ord2node[dev.ordinal]
# Create mapping of ordinals to devices
devices_by_ord = {d.ordinal: d for d in self.devices if d.edt_node}
# Link devices to each other based on the EDT tree
self._link_devices(devices_by_ord)
# Link injected devices to each other
self._link_injected(devices_by_ord)
def device_dependency_graph(self, title, comment):
"""
Construct a graphviz Digraph of the relationships between devices.
"""
import graphviz
dot = graphviz.Digraph(title, comment=comment)
# Split iteration so nodes and edges are grouped in source
for dev in self.devices:
if dev.ordinal == DeviceOrdinals.DEVICE_HANDLE_NULL:
text = '{:s}\\nHandle: {:d}'.format(dev.sym.name, dev.handle)
else:
n = self.edt.dep_ord2node[dev.ordinal]
text = '{:s}\\nOrdinal: {:d} | Handle: {:d}\\n{:s}'.format(
n.name, dev.ordinal, dev.handle, n.path
)
dot.node(str(dev.ordinal), text)
for dev in self.devices:
for sup in dev.devs_supports:
dot.edge(str(dev.ordinal), str(sup.ordinal))
return dot