Files
kaptan/firmware/scripts/test-analyze-fw.py
T

137 lines
4.7 KiB
Python

#!/usr/bin/env python3
"""Synthetic regression tests for strict firmware-memory validation."""
import importlib.util
import json
import unittest
def load_analyzer_module():
spec = importlib.util.spec_from_file_location("analyze_fw", "scripts/analyze-fw.py")
assert spec is not None and spec.loader is not None
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
ANALYZER = load_analyzer_module()
LAYOUT = [("FLASH", 0x08000000, 0x100), ("RAM", 0x20000000, 0x80)]
class FirmwareMemoryValidationTests(unittest.TestCase):
def test_elf_layout_uses_alloc_flag_and_only_load_segments(self):
document = [
{
"Sections": [
{
"Section": {
"Name": {"Name": ".text"},
"Size": 8,
"Address": 0x08000000,
"Flags": {"Flags": [{"Name": "SHF_ALLOC"}]},
}
},
{
"Section": {
"Name": {"Name": ".debug_info"},
"Size": 100,
"Address": 0,
"Flags": {"Flags": []},
}
},
],
"ProgramHeaders": [
{
"ProgramHeader": {
"Type": {"Name": "PT_LOAD"},
"VirtualAddress": 0x08000000,
"PhysicalAddress": 0x08000000,
"FileSize": 8,
"MemSize": 8,
}
},
{
"ProgramHeader": {
"Type": {"Name": "PT_ARM_EXIDX"},
"VirtualAddress": 0,
"PhysicalAddress": 0,
"FileSize": 0,
"MemSize": 0,
}
},
],
}
]
sections, segments = ANALYZER.elf_layout(json.dumps(document))
self.assertEqual(
sections,
[
ANALYZER.Section(".text", 8, 0x08000000, True),
ANALYZER.Section(".debug_info", 100, 0, False),
],
)
self.assertEqual(
segments,
[ANALYZER.LoadSegment(0x08000000, 0x08000000, 8, 8)],
)
def test_accepts_allocated_memory_and_ignores_debug_sections(self):
sections = [
ANALYZER.Section(".text", 0x80, 0x08000000, True),
ANALYZER.Section(".bss", 0x40, 0x20000000, True),
ANALYZER.Section(".debug_info", 0x1000, 0, False),
ANALYZER.Section(".empty", 0, 0xDEADBEEF, True),
]
segments = [
ANALYZER.LoadSegment(0x08000000, 0x08000000, 0x80, 0x80),
ANALYZER.LoadSegment(0x20000000, 0x20000000, 0, 0x40),
]
self.assertEqual(ANALYZER.validation_errors(LAYOUT, sections, segments), [])
def test_rejects_allocated_sections_outside_or_crossing_a_region(self):
outside = [ANALYZER.Section(".text", 4, 0x08000200, True)]
crossing = [ANALYZER.Section(".text", 2, 0x080000FF, True)]
self.assertIn(
"allocated section .text",
ANALYZER.validation_errors(LAYOUT, outside, [])[0],
)
self.assertIn(
"allocated section .text",
ANALYZER.validation_errors(LAYOUT, crossing, [])[0],
)
def test_usage_counts_data_load_image_without_double_counting_text(self):
sections = [
ANALYZER.Section(".text", 0x40, 0x08000000, True),
ANALYZER.Section(".data", 0x10, 0x20000000, True),
]
segments = [
ANALYZER.LoadSegment(0x08000000, 0x08000000, 0x40, 0x40),
ANALYZER.LoadSegment(0x20000000, 0x08000040, 0x10, 0x10),
]
self.assertEqual(
ANALYZER.region_usage("FLASH", LAYOUT, sections, segments), 0x50
)
self.assertEqual(
ANALYZER.region_usage("RAM", LAYOUT, sections, segments), 0x10
)
def test_rejects_loadable_virtual_and_physical_spans_outside_memory(self):
segments = [
ANALYZER.LoadSegment(0x09000000, 0x08000000, 4, 4),
ANALYZER.LoadSegment(0x20000000, 0x09000000, 4, 4),
]
errors = ANALYZER.validation_errors(LAYOUT, [], segments)
self.assertTrue(any("virtual span" in error for error in errors))
self.assertTrue(any("physical span" in error for error in errors))
if __name__ == "__main__":
unittest.main()