scripts: check_init_priorities: use the Zephyr executable file

Rework check_init_priorities to use the main executable file instead of
the individual object files for discovering the devices.

This should make the script more robust in case of stale files in the
build directory, and also makes it work with LTO object files.

Additionally, keep track of the detected init calls, and add a handy
"-i" option to produce a human readable print of the initcalls in the
call sequence, which can be useful for debugging initialization problems
due to odd SYS_INIT and DEVICE interactions.

Signed-off-by: Fabio Baltieri <fabiobaltieri@google.com>
This commit is contained in:
Fabio Baltieri 2023-09-08 18:10:13 +00:00 committed by Fabio Baltieri
commit 5212a4c619
6 changed files with 352 additions and 269 deletions

View file

@ -1811,8 +1811,7 @@ if(CONFIG_CHECK_INIT_PRIORITIES)
list(APPEND list(APPEND
post_build_commands post_build_commands
COMMAND ${PYTHON_EXECUTABLE} ${ZEPHYR_BASE}/scripts/build/check_init_priorities.py COMMAND ${PYTHON_EXECUTABLE} ${ZEPHYR_BASE}/scripts/build/check_init_priorities.py
--build-dir ${PROJECT_BINARY_DIR}/.. --elf-file=${ZEPHYR_BINARY_DIR}/${KERNEL_ELF_NAME}
--edt-pickle ${EDT_PICKLE}
${fail_on_warning} ${fail_on_warning}
) )
endif() endif()

View file

@ -747,6 +747,7 @@ config BUILD_OUTPUT_STRIP_PATHS
config CHECK_INIT_PRIORITIES config CHECK_INIT_PRIORITIES
bool "Build time initialization priorities check" bool "Build time initialization priorities check"
default y default y
depends on !NATIVE_LIBRARY
help help
Check the build for initialization priority issues by comparing the Check the build for initialization priority issues by comparing the
initialization priority in the build with the device dependency initialization priority in the build with the device dependency

View file

@ -6,14 +6,16 @@
""" """
Checks the initialization priorities Checks the initialization priorities
This script parses the object files in the specified build directory, creates a This script parses a Zephyr executable file, creates a list of known devices
list of known devices and their effective initialization priorities and and their effective initialization priorities and compares that with the device
compares that with the device dependencies inferred from the devicetree dependencies inferred from the devicetree hierarchy.
hierarchy.
This can be used to detect devices that are initialized in the incorrect order, This can be used to detect devices that are initialized in the incorrect order,
but also devices that are initialized at the same priority but depends on each but also devices that are initialized at the same priority but depends on each
other, which can potentially break if the linking order is changed. other, which can potentially break if the linking order is changed.
Optionally, it can also produce a human readable list of the initialization
calls for the various init levels.
""" """
import argparse import argparse
@ -24,7 +26,6 @@ import pickle
import sys import sys
from elftools.elf.elffile import ELFFile from elftools.elf.elffile import ELFFile
from elftools.elf.relocation import RelocationSection
from elftools.elf.sections import SymbolTableSection from elftools.elf.sections import SymbolTableSection
# This is needed to load edt.pickle files. # This is needed to load edt.pickle files.
@ -32,24 +33,14 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..",
"dts", "python-devicetree", "src")) "dts", "python-devicetree", "src"))
from devicetree import edtlib # pylint: disable=unused-import from devicetree import edtlib # pylint: disable=unused-import
# Prefix used for relocation sections containing initialization data, as in
# sequence of "struct init_entry".
_INIT_SECTION_PREFIX = (".rel.z_init_", ".rela.z_init_")
# Prefix used for "struct device" reference initialized based on devicetree # Prefix used for "struct device" reference initialized based on devicetree
# entries with a known ordinal. # entries with a known ordinal.
_DEVICE_ORD_PREFIX = "__device_dts_ord_" _DEVICE_ORD_PREFIX = "__device_dts_ord_"
# File name suffix for object files to be scanned.
_OBJ_FILE_SUFFIX = ".c.obj"
# Defined init level in order of priority. # Defined init level in order of priority.
_DEVICE_INIT_LEVELS = ["EARLY", "PRE_KERNEL_1", "PRE_KERNEL_2", "POST_KERNEL", _DEVICE_INIT_LEVELS = ["EARLY", "PRE_KERNEL_1", "PRE_KERNEL_2", "POST_KERNEL",
"APPLICATION", "SMP"] "APPLICATION", "SMP"]
# File name to check for detecting and skiping nested build directories.
_BUILD_DIR_DETECT_FILE = "CMakeCache.txt"
# List of compatibles for node where the initialization priority should be the # List of compatibles for node where the initialization priority should be the
# opposite of the device tree inferred dependency. # opposite of the device tree inferred dependency.
_INVERTED_PRIORITY_COMPATIBLES = frozenset() _INVERTED_PRIORITY_COMPATIBLES = frozenset()
@ -65,34 +56,28 @@ _IGNORE_COMPATIBLES = frozenset([
class Priority: class Priority:
"""Parses and holds a device initialization priority. """Parses and holds a device initialization priority.
Parses an ELF section name for the corresponding initialization level and
priority, for example ".rel.z_init_PRE_KERNEL_155_" for "PRE_KERNEL_1 55".
The object can be used for comparing levels with one another. The object can be used for comparing levels with one another.
Attributes: Attributes:
name: the section name name: the section name
""" """
def __init__(self, name): def __init__(self, level, priority):
for idx, level in enumerate(_DEVICE_INIT_LEVELS): for idx, level_name in enumerate(_DEVICE_INIT_LEVELS):
if level in name: if level_name == level:
_, priority_str = name.strip("_").split(level)
priority, sub_priority = priority_str.split("_")
self._level = idx self._level = idx
self._priority = int(priority) self._priority = priority
self._sub_priority = int(sub_priority)
# Tuples compare elementwise in order # Tuples compare elementwise in order
self._level_priority = (self._level, self._priority, self._sub_priority) self._level_priority = (self._level, self._priority)
return return
raise ValueError("Unknown level in %s" % name) raise ValueError("Unknown level in %s" % level)
def __repr__(self): def __repr__(self):
return "<%s %s %d %d>" % (self.__class__.__name__, return "<%s %s %d>" % (self.__class__.__name__,
_DEVICE_INIT_LEVELS[self._level], self._priority, self._sub_priority) _DEVICE_INIT_LEVELS[self._level], self._priority)
def __str__(self): def __str__(self):
return "%s %d %d" % (_DEVICE_INIT_LEVELS[self._level], self._priority, self._sub_priority) return "%s %d" % (_DEVICE_INIT_LEVELS[self._level], self._priority)
def __lt__(self, other): def __lt__(self, other):
return self._level_priority < other._level_priority return self._level_priority < other._level_priority
@ -104,15 +89,15 @@ class Priority:
return self._level_priority return self._level_priority
class ZephyrObjectFile: class ZephyrInitLevels:
"""Load an object file and finds the device defined within it. """Load an executable file and find the initialization calls and devices.
Load an object file and scans the relocation sections looking for the known Load a Zephyr executable file and scan for the list of initialization calls
ones containing initialization callbacks. Then finds what device ordinals and defined devices.
are being initialized at which priority and stores the list internally.
A dictionary of {ordinal: Priority} is available in the defined_devices The list of devices is available in the "devices" class variable in the
class variable. {ordinal: Priority} format, the list of initilevels is in the "initlevels"
class variables in the {"level name": ["call", ...]} format.
Attributes: Attributes:
file_path: path of the file to be loaded. file_path: path of the file to be loaded.
@ -120,26 +105,49 @@ class ZephyrObjectFile:
def __init__(self, file_path): def __init__(self, file_path):
self.file_path = file_path self.file_path = file_path
self._elf = ELFFile(open(file_path, "rb")) self._elf = ELFFile(open(file_path, "rb"))
self._load_symbols() self._load_objects()
self._find_defined_devices() self._load_level_addr()
self._process_initlevels()
def _load_symbols(self): def _load_objects(self):
"""Initialize the symbols table.""" """Initialize the object table."""
self._symbols = {} self._objects = {}
for section in self._elf.iter_sections(): for section in self._elf.iter_sections():
if not isinstance(section, SymbolTableSection): if not isinstance(section, SymbolTableSection):
continue continue
for num, sym in enumerate(section.iter_symbols()): for sym in section.iter_symbols():
if sym.name: if (sym.name and
self._symbols[num] = sym.name sym.entry.st_size > 0 and
sym.entry.st_info.type in ["STT_OBJECT", "STT_FUNC"]):
self._objects[sym.entry.st_value] = (
sym.name, sym.entry.st_size, sym.entry.st_shndx)
def _device_ord_from_rel(self, rel): def _load_level_addr(self):
"""Find a device ordinal from a device symbol name.""" """Find the address associated with known init levels."""
sym_id = rel["r_info_sym"] self._init_level_addr = {}
sym_name = self._symbols.get(sym_id, None)
for section in self._elf.iter_sections():
if not isinstance(section, SymbolTableSection):
continue
for sym in section.iter_symbols():
for level in _DEVICE_INIT_LEVELS:
name = f"__init_{level}_start"
if sym.name == name:
self._init_level_addr[level] = sym.entry.st_value
elif sym.name == "__init_end":
self._init_level_end = sym.entry.st_value
if len(self._init_level_addr) != len(_DEVICE_INIT_LEVELS):
raise ValueError(f"Missing init symbols, found: {self._init_level_addr}")
if not self._init_level_end:
raise ValueError(f"Missing init section end symbol")
def _device_ord_from_name(self, sym_name):
"""Find a device ordinal from a symbol name."""
if not sym_name: if not sym_name:
return None return None
@ -149,35 +157,67 @@ class ZephyrObjectFile:
_, device_ord = sym_name.split(_DEVICE_ORD_PREFIX) _, device_ord = sym_name.split(_DEVICE_ORD_PREFIX)
return int(device_ord) return int(device_ord)
def _find_defined_devices(self): def _object_name(self, addr):
"""Find the device structures defined in the object file.""" if not addr:
self.defined_devices = {} return "NULL"
elif addr in self._objects:
return self._objects[addr][0]
else:
return "unknown"
for section in self._elf.iter_sections(): def _initlevel_pointer(self, addr, idx, shidx):
if not isinstance(section, RelocationSection): elfclass = self._elf.elfclass
continue if elfclass == 32:
ptrsize = 4
elif elfclass == 64:
ptrsize = 8
else:
ValueError(f"Unknown pointer size for ELF class f{elfclass}")
if not section.name.startswith(_INIT_SECTION_PREFIX): section = self._elf.get_section(shidx)
continue start = section.header.sh_addr
data = section.data()
prio = Priority(section.name) offset = addr - start
for rel in section.iter_relocations(): start = offset + ptrsize * idx
device_ord = self._device_ord_from_rel(rel) stop = offset + ptrsize * (idx + 1)
if not device_ord:
continue
if device_ord in self.defined_devices: return int.from_bytes(data[start:stop], byteorder="little")
raise ValueError(
f"Device {device_ord} already defined, stale "
"object files in the build directory? "
"Try running a clean build.")
self.defined_devices[device_ord] = prio def _process_initlevels(self):
"""Process the init level and find the init functions and devices."""
self.devices = {}
self.initlevels = {}
def __repr__(self): for i, level in enumerate(_DEVICE_INIT_LEVELS):
return (f"<{self.__class__.__name__} {self.file_path} " start = self._init_level_addr[level]
f"defined_devices: {self.defined_devices}>") if i + 1 == len(_DEVICE_INIT_LEVELS):
stop = self._init_level_end
else:
stop = self._init_level_addr[_DEVICE_INIT_LEVELS[i + 1]]
self.initlevels[level] = []
priority = 0
addr = start
while addr < stop:
if addr not in self._objects:
raise ValueError(f"no symbol at addr {addr:08x}")
obj, size, shidx = self._objects[addr]
arg0_name = self._object_name(self._initlevel_pointer(addr, 0, shidx))
arg1_name = self._object_name(self._initlevel_pointer(addr, 1, shidx))
self.initlevels[level].append(f"{obj}: {arg0_name}({arg1_name})")
ordinal = self._device_ord_from_name(arg1_name)
if ordinal:
prio = Priority(level, priority)
self.devices[ordinal] = prio
addr += size
priority += 1
class Validator(): class Validator():
"""Validates the initialization priorities. """Validates the initialization priorities.
@ -187,52 +227,26 @@ class Validator():
dependency list and log any found priority issue. dependency list and log any found priority issue.
Attributes: Attributes:
build_dir: the build directory to scan elf_file_path: path of the ELF file
edt_pickle_path: path of the EDT pickle file edt_pickle: name of the EDT pickle file
log: a logging.Logger object log: a logging.Logger object
""" """
def __init__(self, build_dir, edt_pickle_path, log): def __init__(self, elf_file_path, edt_pickle, log):
self.log = log self.log = log
edtser = pathlib.Path(build_dir, edt_pickle_path) edt_pickle_path = pathlib.Path(
with open(edtser, "rb") as f: pathlib.Path(elf_file_path).parent,
edt_pickle)
with open(edt_pickle_path, "rb") as f:
edt = pickle.load(f) edt = pickle.load(f)
self._ord2node = edt.dep_ord2node self._ord2node = edt.dep_ord2node
self._objs = [] self._obj = ZephyrInitLevels(elf_file_path)
for file in self._find_build_objfiles(build_dir, is_root=True):
obj = ZephyrObjectFile(file)
if obj.defined_devices:
self._objs.append(obj)
for dev, prio in obj.defined_devices.items():
dev_path = self._ord2node[dev].path
self.log.debug(f"{file}: {dev_path} {prio}")
self._dev_priorities = {}
for obj in self._objs:
for dev, prio in obj.defined_devices.items():
if dev in self._dev_priorities:
dev_path = self._ord2node[dev].path
raise ValueError(
f"ERROR: device {dev} ({dev_path}) already defined")
self._dev_priorities[dev] = prio
self.warnings = 0 self.warnings = 0
self.errors = 0 self.errors = 0
def _find_build_objfiles(self, build_dir, is_root=False):
"""Find all project object files, skip sub-build directories."""
if not is_root and pathlib.Path(build_dir, _BUILD_DIR_DETECT_FILE).exists():
return
for file in pathlib.Path(build_dir).iterdir():
if file.is_file() and file.name.endswith(_OBJ_FILE_SUFFIX):
yield file
if file.is_dir():
for file in self._find_build_objfiles(file.resolve()):
yield file
def _check_dep(self, dev_ord, dep_ord): def _check_dep(self, dev_ord, dep_ord):
"""Validate the priority between two devices.""" """Validate the priority between two devices."""
if dev_ord == dep_ord: if dev_ord == dep_ord:
@ -254,8 +268,8 @@ class Validator():
self.log.info(f"Swapped priority: {dev_compat}, {dep_compat}") self.log.info(f"Swapped priority: {dev_compat}, {dep_compat}")
dev_ord, dep_ord = dep_ord, dev_ord dev_ord, dep_ord = dep_ord, dev_ord
dev_prio = self._dev_priorities.get(dev_ord, None) dev_prio = self._obj.devices.get(dev_ord, None)
dep_prio = self._dev_priorities.get(dep_ord, None) dep_prio = self._obj.devices.get(dep_ord, None)
if not dev_prio or not dep_prio: if not dev_prio or not dep_prio:
return return
@ -286,10 +300,16 @@ class Validator():
def check_edt(self): def check_edt(self):
"""Scan through all known devices and validate the init priorities.""" """Scan through all known devices and validate the init priorities."""
for dev_ord in self._dev_priorities: for dev_ord in self._obj.devices:
dev = self._ord2node[dev_ord] dev = self._ord2node[dev_ord]
self._check_edt_r(dev_ord, dev) self._check_edt_r(dev_ord, dev)
def print_initlevels(self):
for level, calls in self._obj.initlevels.items():
print(level)
for call in calls:
print(f" {call}")
def _parse_args(argv): def _parse_args(argv):
"""Parse the command line arguments.""" """Parse the command line arguments."""
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
@ -297,8 +317,8 @@ def _parse_args(argv):
formatter_class=argparse.RawDescriptionHelpFormatter, formatter_class=argparse.RawDescriptionHelpFormatter,
allow_abbrev=False) allow_abbrev=False)
parser.add_argument("-d", "--build-dir", default="build", parser.add_argument("-f", "--elf-file", default=pathlib.Path("build", "zephyr", "zephyr.elf"),
help="build directory to use") help="ELF file to use")
parser.add_argument("-v", "--verbose", action="count", parser.add_argument("-v", "--verbose", action="count",
help=("enable verbose output, can be used multiple times " help=("enable verbose output, can be used multiple times "
"to increase verbosity level")) "to increase verbosity level"))
@ -308,8 +328,10 @@ def _parse_args(argv):
help="always exit with a return code of 0, used for testing") help="always exit with a return code of 0, used for testing")
parser.add_argument("-o", "--output", parser.add_argument("-o", "--output",
help="write the output to a file in addition to stdout") help="write the output to a file in addition to stdout")
parser.add_argument("--edt-pickle", default=pathlib.Path("zephyr", "edt.pickle"), parser.add_argument("-i", "--initlevels", action="store_true",
help="path to read the pickled edtlib.EDT object from", help="print the initlevel functions instead of checking the device dependencies")
parser.add_argument("--edt-pickle", default=pathlib.Path("edt.pickle"),
help="name of the the pickled edtlib.EDT file",
type=pathlib.Path) type=pathlib.Path)
return parser.parse_args(argv) return parser.parse_args(argv)
@ -323,7 +345,7 @@ def _init_log(verbose, output):
log.addHandler(console) log.addHandler(console)
if output: if output:
file = logging.FileHandler(output) file = logging.FileHandler(output, mode="w")
file.setFormatter(logging.Formatter("%(levelname)s: %(message)s")) file.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
log.addHandler(file) log.addHandler(file)
@ -341,9 +363,12 @@ def main(argv=None):
log = _init_log(args.verbose, args.output) log = _init_log(args.verbose, args.output)
log.info(f"check_init_priorities build_dir: {args.build_dir}") log.info(f"check_init_priorities: {args.elf_file}")
validator = Validator(args.build_dir, args.edt_pickle, log) validator = Validator(args.elf_file, args.edt_pickle, log)
if args.initlevels:
validator.print_initlevels()
else:
validator.check_edt() validator.check_edt()
if args.always_succeed: if args.always_succeed:

View file

@ -10,7 +10,7 @@ import mock
import pathlib import pathlib
import unittest import unittest
from elftools.elf.relocation import RelocationSection from elftools.elf.relocation import Section
from elftools.elf.sections import SymbolTableSection from elftools.elf.sections import SymbolTableSection
import check_init_priorities import check_init_priorities
@ -19,191 +19,245 @@ class TestPriority(unittest.TestCase):
"""Tests for the Priority class.""" """Tests for the Priority class."""
def test_priority_parsing(self): def test_priority_parsing(self):
prio1 = check_init_priorities.Priority(".rel.z_init_POST_KERNEL12_0_") prio1 = check_init_priorities.Priority("POST_KERNEL", 12)
self.assertEqual(prio1._level_priority, (3, 12, 0)) self.assertEqual(prio1._level_priority, (3, 12))
prio2 = check_init_priorities.Priority("noisenoise_POST_KERNEL99_00023_") prio1 = check_init_priorities.Priority("APPLICATION", 9999)
self.assertEqual(prio2._level_priority, (3, 99, 23)) self.assertEqual(prio1._level_priority, (4, 9999))
prio3 = check_init_priorities.Priority("_PRE_KERNEL_10_99999_")
self.assertEqual(prio3._level_priority, (1, 0, 99999))
prio4 = check_init_priorities.Priority("_PRE_KERNEL_110_00001_")
self.assertEqual(prio4._level_priority, (1, 10, 1))
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
check_init_priorities.Priority("i-am-not-a-priority") check_init_priorities.Priority("i-am-not-a-priority", 0)
check_init_priorities.Priority("_DOESNOTEXIST0_") check_init_priorities.Priority("_DOESNOTEXIST0_", 0)
check_init_priorities.Priority(".rel.z_init_POST_KERNEL12_blah")
check_init_priorities.Priority(".rel.z_init_2_")
check_init_priorities.Priority(".rel.z_init_POST_KERNEL1_")
def test_priority_levels(self): def test_priority_levels(self):
prios = [ prios = [
check_init_priorities.Priority(".rel.z_init_EARLY0_0_"), check_init_priorities.Priority("EARLY", 0),
check_init_priorities.Priority(".rel.z_init_EARLY1_0_"), check_init_priorities.Priority("EARLY", 1),
check_init_priorities.Priority(".rel.z_init_EARLY11_0_"), check_init_priorities.Priority("PRE_KERNEL_1", 0),
check_init_priorities.Priority(".rel.z_init_PRE_KERNEL_10_0_"), check_init_priorities.Priority("PRE_KERNEL_1", 1),
check_init_priorities.Priority(".rel.z_init_PRE_KERNEL_11_0_"), check_init_priorities.Priority("PRE_KERNEL_2", 0),
check_init_priorities.Priority(".rel.z_init_PRE_KERNEL_111_0_"), check_init_priorities.Priority("PRE_KERNEL_2", 1),
check_init_priorities.Priority(".rel.z_init_PRE_KERNEL_111_1_"), check_init_priorities.Priority("POST_KERNEL", 0),
check_init_priorities.Priority(".rel.z_init_PRE_KERNEL_111_00002_"), check_init_priorities.Priority("POST_KERNEL", 1),
check_init_priorities.Priority(".rel.z_init_PRE_KERNEL_111_00010_"), check_init_priorities.Priority("APPLICATION", 0),
check_init_priorities.Priority(".rel.z_init_PRE_KERNEL_20_0_"), check_init_priorities.Priority("APPLICATION", 1),
check_init_priorities.Priority(".rel.z_init_PRE_KERNEL_21_0_"), check_init_priorities.Priority("SMP", 0),
check_init_priorities.Priority(".rel.z_init_PRE_KERNEL_211_0_"), check_init_priorities.Priority("SMP", 1),
check_init_priorities.Priority(".rel.z_init_POST_KERNEL0_0_"),
check_init_priorities.Priority(".rel.z_init_POST_KERNEL1_0_"),
check_init_priorities.Priority(".rel.z_init_POST_KERNEL11_0_"),
check_init_priorities.Priority(".rel.z_init_APPLICATION0_0_"),
check_init_priorities.Priority(".rel.z_init_APPLICATION1_0_"),
check_init_priorities.Priority(".rel.z_init_APPLICATION11_0_"),
check_init_priorities.Priority(".rel.z_init_SMP0_0_"),
check_init_priorities.Priority(".rel.z_init_SMP1_0_"),
check_init_priorities.Priority(".rel.z_init_SMP11_0_"),
] ]
self.assertListEqual(prios, sorted(prios)) self.assertListEqual(prios, sorted(prios))
def test_priority_strings(self): def test_priority_strings(self):
prio = check_init_priorities.Priority(".rel.z_init_POST_KERNEL12_00023_") prio = check_init_priorities.Priority("POST_KERNEL", 12)
self.assertEqual(str(prio), "POST_KERNEL 12 23") self.assertEqual(str(prio), "POST_KERNEL 12")
self.assertEqual(repr(prio), "<Priority POST_KERNEL 12 23>") self.assertEqual(repr(prio), "<Priority POST_KERNEL 12>")
class testZephyrObjectFile(unittest.TestCase): class testZephyrInitLevels(unittest.TestCase):
"""Tests for the ZephyrObjectFile class.""" """Tests for the ZephyrInitLevels class."""
@mock.patch("check_init_priorities.ZephyrObjectFile.__init__", return_value=None) @mock.patch("check_init_priorities.ZephyrInitLevels.__init__", return_value=None)
def test_load_symbols(self, mock_zofinit): def test_load_objects(self, mock_zilinit):
mock_elf = mock.Mock() mock_elf = mock.Mock()
sts = mock.Mock(spec=SymbolTableSection) sts = mock.Mock(spec=SymbolTableSection)
rel = mock.Mock(spec=RelocationSection) rel = mock.Mock(spec=Section)
mock_elf.iter_sections.return_value = [sts, rel] mock_elf.iter_sections.return_value = [sts, rel]
s0 = mock.Mock() s0 = mock.Mock()
s0.name = "a" s0.name = "a"
s0.entry.st_info.type = "STT_OBJECT"
s0.entry.st_size = 4
s0.entry.st_value = 0xaa
s0.entry.st_shndx = 1
s1 = mock.Mock() s1 = mock.Mock()
s1.name = None s1.name = None
s2 = mock.Mock() s2 = mock.Mock()
s2.name = "b" s2.name = "b"
s2.entry.st_info.type = "STT_FUNC"
s2.entry.st_size = 8
s2.entry.st_value = 0xbb
s2.entry.st_shndx = 2
sts.iter_symbols.return_value = [s0, s1, s2] sts.iter_symbols.return_value = [s0, s1, s2]
obj = check_init_priorities.ZephyrObjectFile("") obj = check_init_priorities.ZephyrInitLevels("")
obj._elf = mock_elf obj._elf = mock_elf
obj._load_symbols() obj._load_objects()
self.assertDictEqual(obj._symbols, {0: "a", 2: "b"}) self.assertDictEqual(obj._objects, {0xaa: ("a", 4, 1), 0xbb: ("b", 8, 2)})
@mock.patch("check_init_priorities.Priority") @mock.patch("check_init_priorities.ZephyrInitLevels.__init__", return_value=None)
@mock.patch("check_init_priorities.ZephyrObjectFile._device_ord_from_rel") def test_load_level_addr(self, mock_zilinit):
@mock.patch("check_init_priorities.ZephyrObjectFile.__init__", return_value=None)
def test_find_defined_devices(self, mock_zofinit, mock_dofr, mock_prio):
mock_elf = mock.Mock() mock_elf = mock.Mock()
sts = mock.Mock(spec=SymbolTableSection) sts = mock.Mock(spec=SymbolTableSection)
rel1 = mock.Mock(spec=RelocationSection) rel = mock.Mock(spec=Section)
rel1.name = ".rel.z_init_SOMETHING" mock_elf.iter_sections.return_value = [sts, rel]
rel2 = mock.Mock(spec=RelocationSection)
rel2.name = ".rel.something_else"
mock_elf.iter_sections.return_value = [sts, rel1, rel2]
r0 = mock.Mock() s0 = mock.Mock()
rel1.iter_relocations.return_value = [r0] s0.name = "__init_EARLY_start"
s0.entry.st_value = 0x00
mock_dofr.return_value = 123 s1 = mock.Mock()
s1.name = "__init_PRE_KERNEL_1_start"
s1.entry.st_value = 0x11
r0_prio = mock.Mock() s2 = mock.Mock()
mock_prio.return_value = r0_prio s2.name = "__init_PRE_KERNEL_2_start"
s2.entry.st_value = 0x22
obj = check_init_priorities.ZephyrObjectFile("") s3 = mock.Mock()
s3.name = "__init_POST_KERNEL_start"
s3.entry.st_value = 0x33
s4 = mock.Mock()
s4.name = "__init_APPLICATION_start"
s4.entry.st_value = 0x44
s5 = mock.Mock()
s5.name = "__init_SMP_start"
s5.entry.st_value = 0x55
s6 = mock.Mock()
s6.name = "__init_end"
s6.entry.st_value = 0x66
sts.iter_symbols.return_value = [s0, s1, s2, s3, s4, s5, s6]
obj = check_init_priorities.ZephyrInitLevels("")
obj._elf = mock_elf obj._elf = mock_elf
obj._find_defined_devices() obj._load_level_addr()
self.assertDictEqual(obj.defined_devices, {123: r0_prio}) self.assertDictEqual(obj._init_level_addr, {
mock_dofr.assert_called_once_with(r0) "EARLY": 0x00,
mock_prio.assert_called_once_with(rel1.name) "PRE_KERNEL_1": 0x11,
"PRE_KERNEL_2": 0x22,
"POST_KERNEL": 0x33,
"APPLICATION": 0x44,
"SMP": 0x55,
})
self.assertEqual(obj._init_level_end, 0x66)
@mock.patch("check_init_priorities.ZephyrObjectFile.__init__", return_value=None) @mock.patch("check_init_priorities.ZephyrInitLevels.__init__", return_value=None)
def test_device_ord_from_rel(self, mock_zofinit): def test_device_ord_from_name(self, mock_zilinit):
obj = check_init_priorities.ZephyrObjectFile("") obj = check_init_priorities.ZephyrInitLevels("")
obj._symbols = { self.assertEqual(obj._device_ord_from_name(None), None)
1: "blah", self.assertEqual(obj._device_ord_from_name("hey, hi!"), None)
2: "__device_dts_ord_123", self.assertEqual(obj._device_ord_from_name("__device_dts_ord_123"), 123)
@mock.patch("check_init_priorities.ZephyrInitLevels.__init__", return_value=None)
def test_object_name(self, mock_zilinit):
obj = check_init_priorities.ZephyrInitLevels("")
obj._objects = {0x123: ("name", 4)}
self.assertEqual(obj._object_name(0), "NULL")
self.assertEqual(obj._object_name(73), "unknown")
self.assertEqual(obj._object_name(0x123), "name")
@mock.patch("check_init_priorities.ZephyrInitLevels.__init__", return_value=None)
def test_initlevel_pointer_32(self, mock_zilinit):
obj = check_init_priorities.ZephyrInitLevels("")
obj._elf = mock.Mock()
obj._elf.elfclass = 32
mock_section = mock.Mock()
obj._elf.get_section.return_value = mock_section
mock_section.header.sh_addr = 0x100
mock_section.data.return_value = (b"\x01\x00\x00\x00"
b"\x02\x00\x00\x00"
b"\x03\x00\x00\x00")
self.assertEqual(obj._initlevel_pointer(0x100, 0, 0), 1)
self.assertEqual(obj._initlevel_pointer(0x100, 1, 0), 2)
self.assertEqual(obj._initlevel_pointer(0x104, 0, 0), 2)
self.assertEqual(obj._initlevel_pointer(0x104, 1, 0), 3)
@mock.patch("check_init_priorities.ZephyrInitLevels.__init__", return_value=None)
def test_initlevel_pointer_64(self, mock_zilinit):
obj = check_init_priorities.ZephyrInitLevels("")
obj._elf = mock.Mock()
obj._elf.elfclass = 64
mock_section = mock.Mock()
obj._elf.get_section.return_value = mock_section
mock_section.header.sh_addr = 0x100
mock_section.data.return_value = (b"\x01\x00\x00\x00\x00\x00\x00\x00"
b"\x02\x00\x00\x00\x00\x00\x00\x00"
b"\x03\x00\x00\x00\x00\x00\x00\x00")
self.assertEqual(obj._initlevel_pointer(0x100, 0, 0), 1)
self.assertEqual(obj._initlevel_pointer(0x100, 1, 0), 2)
self.assertEqual(obj._initlevel_pointer(0x108, 0, 0), 2)
self.assertEqual(obj._initlevel_pointer(0x108, 1, 0), 3)
@mock.patch("check_init_priorities.ZephyrInitLevels._object_name")
@mock.patch("check_init_priorities.ZephyrInitLevels._initlevel_pointer")
@mock.patch("check_init_priorities.ZephyrInitLevels.__init__", return_value=None)
def test_process_initlevels(self, mock_zilinit, mock_ip, mock_on):
obj = check_init_priorities.ZephyrInitLevels("")
obj._init_level_addr = {
"EARLY": 0x00,
"PRE_KERNEL_1": 0x00,
"PRE_KERNEL_2": 0x00,
"POST_KERNEL": 0x08,
"APPLICATION": 0x0c,
"SMP": 0x0c,
}
obj._init_level_end = 0x0c
obj._objects = {
0x00: ("a", 4, 0),
0x04: ("b", 4, 0),
0x08: ("c", 4, 0),
} }
self.assertEqual(obj._device_ord_from_rel({"r_info_sym": 0}), None) mock_ip.side_effect = lambda *args: args
self.assertEqual(obj._device_ord_from_rel({"r_info_sym": 1}), None)
self.assertEqual(obj._device_ord_from_rel({"r_info_sym": 2}), 123) def mock_obj_name(*args):
if args[0] == (0, 0, 0):
return "i0"
elif args[0] == (0, 1, 0):
return "__device_dts_ord_11"
elif args[0] == (4, 0, 0):
return "i1"
elif args[0] == (4, 1, 0):
return "__device_dts_ord_22"
return f"name_{args[0][0]}_{args[0][1]}"
mock_on.side_effect = mock_obj_name
obj._process_initlevels()
self.assertDictEqual(obj.initlevels, {
"EARLY": [],
"PRE_KERNEL_1": [],
"PRE_KERNEL_2": ["a: i0(__device_dts_ord_11)", "b: i1(__device_dts_ord_22)"],
"POST_KERNEL": ["c: name_8_0(name_8_1)"],
"APPLICATION": [],
"SMP": [],
})
self.assertDictEqual(obj.devices, {
11: check_init_priorities.Priority("PRE_KERNEL_2", 0),
22: check_init_priorities.Priority("PRE_KERNEL_2", 1),
})
class testValidator(unittest.TestCase): class testValidator(unittest.TestCase):
"""Tests for the Validator class.""" """Tests for the Validator class."""
@mock.patch("check_init_priorities.ZephyrObjectFile") @mock.patch("check_init_priorities.ZephyrInitLevels")
@mock.patch("check_init_priorities.Validator._find_build_objfiles")
@mock.patch("pickle.load") @mock.patch("pickle.load")
def test_initialize(self, mock_pl, mock_fbo, mock_zof): def test_initialize(self, mock_pl, mock_zil):
mock_fbo.return_value = ["filepath"]
mock_log = mock.Mock() mock_log = mock.Mock()
mock_prio = mock.Mock() mock_prio = mock.Mock()
mock_obj = mock.Mock() mock_obj = mock.Mock()
mock_obj.defined_devices = {123: mock_prio} mock_obj.defined_devices = {123: mock_prio}
mock_zof.return_value = mock_obj mock_zil.return_value = mock_obj
with mock.patch("builtins.open", mock.mock_open()) as mock_open: with mock.patch("builtins.open", mock.mock_open()) as mock_open:
validator = check_init_priorities.Validator("path", "pickle", mock_log) validator = check_init_priorities.Validator("path", "pickle", mock_log)
self.assertListEqual(validator._objs, [mock_obj]) self.assertEqual(validator._obj, mock_obj)
self.assertDictEqual(validator._dev_priorities, {123: mock_prio}) mock_zil.assert_called_once_with("path")
mock_fbo.assert_called_once_with("path", is_root=True) mock_open.assert_called_once_with(pathlib.Path("pickle"), "rb")
mock_zof.assert_called_once_with("filepath")
mock_open.assert_called_once_with(pathlib.Path("path/pickle"), "rb")
@mock.patch("pathlib.Path")
@mock.patch("check_init_priorities.Validator.__init__", return_value=None)
def test_find_build_objfiles(self, mock_vinit, mock_path):
mock_log = mock.Mock()
validator = check_init_priorities.Validator("", "", mock_log)
mock_file = mock.Mock()
mock_file.is_file.return_value = True
mock_file.is_dir.return_value = False
mock_file.file.name = "filename.c.obj"
mock_dir = mock.Mock()
mock_dir.is_file.return_value = False
mock_dir.is_dir.return_value = True
mock_dir.resolve.return_value = "subdir"
mock_dir.iterdir.return_value = []
mock_nosettingsfile = mock.Mock()
mock_nosettingsfile.exists.return_value = False
mock_path_root = mock.Mock()
mock_path_root.iterdir.return_value = [mock_file, mock_dir]
def mock_path_stubs(*args):
if args == ("root",):
return mock_path_root
elif args == ("subdir",):
return mock_dir
elif args == ("subdir", "CMakeCache.txt"):
return mock_nosettingsfile
raise ValueError
mock_path.side_effect = mock_path_stubs
ret = list(validator._find_build_objfiles("root", is_root=True))
self.assertListEqual(ret, [mock_file])
mock_nosettingsfile.exists.assert_called_once_with()
self.assertListEqual(mock_path.call_args_list, [
mock.call("root"),
mock.call("subdir", "CMakeCache.txt"),
mock.call("subdir")
])
@mock.patch("check_init_priorities.Validator.__init__", return_value=None) @mock.patch("check_init_priorities.Validator.__init__", return_value=None)
def test_check_dep_same_node(self, mock_vinit): def test_check_dep_same_node(self, mock_vinit):
@ -220,15 +274,16 @@ class testValidator(unittest.TestCase):
def test_check_dep_no_prio(self, mock_vinit): def test_check_dep_no_prio(self, mock_vinit):
validator = check_init_priorities.Validator("", "", None) validator = check_init_priorities.Validator("", "", None)
validator.log = mock.Mock() validator.log = mock.Mock()
validator._obj = mock.Mock()
validator._ord2node = {1: mock.Mock(), 2: mock.Mock()} validator._ord2node = {1: mock.Mock(), 2: mock.Mock()}
validator._ord2node[1]._binding = None validator._ord2node[1]._binding = None
validator._ord2node[2]._binding = None validator._ord2node[2]._binding = None
validator._dev_priorities = {1: 10} validator._obj.devices = {1: 10}
validator._check_dep(1, 2) validator._check_dep(1, 2)
validator._dev_priorities = {2: 20} validator._obj.devices = {2: 20}
validator._check_dep(1, 2) validator._check_dep(1, 2)
self.assertFalse(validator.log.info.called) self.assertFalse(validator.log.info.called)
@ -239,6 +294,7 @@ class testValidator(unittest.TestCase):
def test_check(self, mock_vinit): def test_check(self, mock_vinit):
validator = check_init_priorities.Validator("", "", None) validator = check_init_priorities.Validator("", "", None)
validator.log = mock.Mock() validator.log = mock.Mock()
validator._obj = mock.Mock()
validator.warnings = 0 validator.warnings = 0
validator.errors = 0 validator.errors = 0
@ -250,7 +306,7 @@ class testValidator(unittest.TestCase):
validator._ord2node[3]._binding = None validator._ord2node[3]._binding = None
validator._ord2node[3].path = "/3" validator._ord2node[3].path = "/3"
validator._dev_priorities = {1: 10, 2: 10, 3: 20} validator._obj.devices = {1: 10, 2: 10, 3: 20}
validator._check_dep(3, 1) validator._check_dep(3, 1)
validator._check_dep(2, 1) validator._check_dep(2, 1)
@ -266,6 +322,7 @@ class testValidator(unittest.TestCase):
def test_check_swapped(self, mock_vinit): def test_check_swapped(self, mock_vinit):
validator = check_init_priorities.Validator("", "", None) validator = check_init_priorities.Validator("", "", None)
validator.log = mock.Mock() validator.log = mock.Mock()
validator._obj = mock.Mock()
validator.warnings = 0 validator.warnings = 0
validator.errors = 0 validator.errors = 0
@ -279,7 +336,7 @@ class testValidator(unittest.TestCase):
validator._ord2node[3]._binding.compatible = "compat-3" validator._ord2node[3]._binding.compatible = "compat-3"
validator._ord2node[3].path = "/3" validator._ord2node[3].path = "/3"
validator._dev_priorities = {1: 20, 3: 10} validator._obj.devices = {1: 20, 3: 10}
validator._check_dep(3, 1) validator._check_dep(3, 1)
@ -296,6 +353,7 @@ class testValidator(unittest.TestCase):
def test_check_ignored(self, mock_vinit): def test_check_ignored(self, mock_vinit):
validator = check_init_priorities.Validator("", "", None) validator = check_init_priorities.Validator("", "", None)
validator.log = mock.Mock() validator.log = mock.Mock()
validator._obj = mock.Mock()
validator.warnings = 0 validator.warnings = 0
validator.errors = 0 validator.errors = 0
@ -309,7 +367,7 @@ class testValidator(unittest.TestCase):
validator._ord2node[3]._binding.compatible = "compat-3" validator._ord2node[3]._binding.compatible = "compat-3"
validator._ord2node[3].path = "/3" validator._ord2node[3].path = "/3"
validator._dev_priorities = {1: 20, 3: 10} validator._obj.devices = {1: 20, 3: 10}
validator._check_dep(3, 1) validator._check_dep(3, 1)
@ -360,7 +418,8 @@ class testValidator(unittest.TestCase):
def test_check_edt(self, mock_vinit, mock_cer): def test_check_edt(self, mock_vinit, mock_cer):
validator = check_init_priorities.Validator("", "", None) validator = check_init_priorities.Validator("", "", None)
validator._ord2node = {1: mock.Mock(), 2: mock.Mock(), 3: mock.Mock()} validator._ord2node = {1: mock.Mock(), 2: mock.Mock(), 3: mock.Mock()}
validator._dev_priorities = {1: 10, 2: 10, 3: 20} validator._obj = mock.Mock()
validator._obj.devices = {1: 10, 2: 10, 3: 20}
validator.check_edt() validator.check_edt()

View file

@ -5,21 +5,20 @@ find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
set(output_file ${PROJECT_BINARY_DIR}/check_init_priorities_output.txt) set(output_file ${PROJECT_BINARY_DIR}/check_init_priorities_output.txt)
add_custom_target( add_custom_command(
check_init_priorities_output
COMMENT "Running check_init_priorities.py" COMMENT "Running check_init_priorities.py"
OUTPUT ${output_file}
DEPENDS ${BYPRODUCT_KERNEL_ELF_NAME}
COMMAND ${PYTHON_EXECUTABLE} ${ZEPHYR_BASE}/scripts/build/check_init_priorities.py COMMAND ${PYTHON_EXECUTABLE} ${ZEPHYR_BASE}/scripts/build/check_init_priorities.py
--verbose --verbose
--build-dir ${PROJECT_BINARY_DIR}/.. --elf-file=${ZEPHYR_BINARY_DIR}/${KERNEL_ELF_NAME}
--output ${output_file} --output ${output_file}
--always-succeed --always-succeed
COMMAND ${PYTHON_EXECUTABLE} ${APPLICATION_SOURCE_DIR}/validate_check_init_priorities_output.py COMMAND ${PYTHON_EXECUTABLE} ${APPLICATION_SOURCE_DIR}/validate_check_init_priorities_output.py
${output_file} ${output_file}
DEPENDS zephyr_pre0
) )
if (TARGET zephyr_pre1)
add_dependencies(zephyr_pre1 check_init_priorities_output) add_custom_target(check_init_priorities_output ALL DEPENDS ${output_file})
endif()
project(check_init_priorities) project(check_init_priorities)

View file

@ -8,12 +8,12 @@
import sys import sys
REFERENCE_OUTPUT = [ REFERENCE_OUTPUT = [
"ERROR: /i2c@11112222/test-i2c-dev@10 PRE_KERNEL_1 49 31 < /gpio@ffff PRE_KERNEL_1 50 29", "ERROR: /i2c@11112222/test-i2c-dev@10 PRE_KERNEL_1 0 < /gpio@ffff PRE_KERNEL_1 1",
"ERROR: /i2c@11112222/test-i2c-dev@10 PRE_KERNEL_1 49 31 < /i2c@11112222 PRE_KERNEL_1 50 30", "ERROR: /i2c@11112222/test-i2c-dev@10 PRE_KERNEL_1 0 < /i2c@11112222 PRE_KERNEL_1 2",
"INFO: /i2c@11112222/test-i2c-dev@11 PRE_KERNEL_1 50 32 > /gpio@ffff PRE_KERNEL_1 50 29", "INFO: /i2c@11112222/test-i2c-dev@11 PRE_KERNEL_1 3 > /gpio@ffff PRE_KERNEL_1 1",
"INFO: /i2c@11112222/test-i2c-dev@11 PRE_KERNEL_1 50 32 > /i2c@11112222 PRE_KERNEL_1 50 30", "INFO: /i2c@11112222/test-i2c-dev@11 PRE_KERNEL_1 3 > /i2c@11112222 PRE_KERNEL_1 2",
"INFO: /i2c@11112222/test-i2c-dev@12 PRE_KERNEL_1 51 33 > /gpio@ffff PRE_KERNEL_1 50 29", "INFO: /i2c@11112222/test-i2c-dev@12 PRE_KERNEL_1 4 > /gpio@ffff PRE_KERNEL_1 1",
"INFO: /i2c@11112222/test-i2c-dev@12 PRE_KERNEL_1 51 33 > /i2c@11112222 PRE_KERNEL_1 50 30" "INFO: /i2c@11112222/test-i2c-dev@12 PRE_KERNEL_1 4 > /i2c@11112222 PRE_KERNEL_1 2",
] ]
if len(sys.argv) != 2: if len(sys.argv) != 2:
@ -23,7 +23,7 @@ if len(sys.argv) != 2:
output = [] output = []
with open(sys.argv[1], "r") as file: with open(sys.argv[1], "r") as file:
for line in file: for line in file:
if line.startswith("INFO: check_init_priorities build_dir:"): if line.startswith("INFO: check_init_priorities"):
continue continue
output.append(line.strip()) output.append(line.strip())