build(f722): centralize linker layout
This commit is contained in:
1 parent
387fdaad23
commit
ceb2134392
8 files changed
+358
-77
No files matched your search
@@ -1,9 +1,11 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
println!("cargo:rerun-if-changed=build.rs");
|
println!("cargo:rerun-if-changed=build.rs");
|
||||||
println!("cargo:rerun-if-changed=memory.x");
|
let layout = std::path::Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap())
|
||||||
println!("cargo:rustc-link-arg=-Tlink.x");
|
.join("../../platforms/stm32f7/link/f722");
|
||||||
println!(
|
println!(
|
||||||
"cargo:rustc-link-search={}",
|
"cargo:rerun-if-changed={}",
|
||||||
std::env::var("CARGO_MANIFEST_DIR").expect("Cargo must set CARGO_MANIFEST_DIR")
|
layout.join("memory.x").display()
|
||||||
);
|
);
|
||||||
|
println!("cargo:rustc-link-arg=-Tlink.x");
|
||||||
|
println!("cargo:rustc-link-search={}", layout.display());
|
||||||
}
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
MEMORY
|
|
||||||
{
|
|
||||||
FLASH : ORIGIN = 0x08000000, LENGTH = 512K
|
|
||||||
/*
|
|
||||||
* STM32F722 RAM totals 256 KiB:
|
|
||||||
* - DTCM: 64 KiB at 0x2000_0000
|
|
||||||
* - SRAM1: 176 KiB at 0x2001_0000
|
|
||||||
* - SRAM2: 16 KiB at 0x2003_C000
|
|
||||||
*
|
|
||||||
* SRAM1 and SRAM2 are contiguous and are both accessible by DMA1 and DMA2.
|
|
||||||
* They form one 192 KiB default RAM region from 0x2001_0000 through
|
|
||||||
* 0x2003_FFFF. DTCM is intentionally kept separate for a future explicit
|
|
||||||
* placement policy rather than mixing CPU-critical and general allocations.
|
|
||||||
*
|
|
||||||
* TODO: Utilize DTCM.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
RAM : ORIGIN = 0x20010000, LENGTH = 192K
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
println!("cargo:rerun-if-changed=build.rs");
|
println!("cargo:rerun-if-changed=build.rs");
|
||||||
println!("cargo:rerun-if-changed=memory.x");
|
let layout = std::path::Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap())
|
||||||
println!("cargo:rustc-link-arg=-Tlink.x");
|
.join("../../platforms/stm32f7/link/f722");
|
||||||
println!(
|
println!(
|
||||||
"cargo:rustc-link-search={}",
|
"cargo:rerun-if-changed={}",
|
||||||
std::env::var("CARGO_MANIFEST_DIR").expect("Cargo must set CARGO_MANIFEST_DIR")
|
layout.join("memory.x").display()
|
||||||
);
|
);
|
||||||
|
println!("cargo:rustc-link-arg=-Tlink.x");
|
||||||
|
println!("cargo:rustc-link-search={}", layout.display());
|
||||||
}
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
MEMORY
|
|
||||||
{
|
|
||||||
FLASH : ORIGIN = 0x08000000, LENGTH = 512K
|
|
||||||
/*
|
|
||||||
* STM32F722 RAM totals 256 KiB:
|
|
||||||
* - DTCM: 64 KiB at 0x2000_0000
|
|
||||||
* - SRAM1: 176 KiB at 0x2001_0000
|
|
||||||
* - SRAM2: 16 KiB at 0x2003_C000
|
|
||||||
*
|
|
||||||
* SRAM1 and SRAM2 are contiguous and are both accessible by DMA1 and DMA2.
|
|
||||||
* They form one 192 KiB default RAM region from 0x2001_0000 through
|
|
||||||
* 0x2003_FFFF. DTCM is intentionally kept separate for a future explicit
|
|
||||||
* placement policy rather than mixing CPU-critical and general allocations.
|
|
||||||
*
|
|
||||||
* TODO: Utilize DTCM.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
RAM : ORIGIN = 0x20010000, LENGTH = 192K
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* STM32F72xx flash sectors 0-5 form the 256 KiB application region.
|
||||||
|
* Sectors 6 and 7 are two independently erasable 128 KiB configuration
|
||||||
|
* journal slots at 0x0804_0000 and 0x0806_0000. They are deliberately not
|
||||||
|
* linker regions, so application sections cannot overlap persistent data.
|
||||||
|
*/
|
||||||
|
FLASH : ORIGIN = 0x08000000, LENGTH = 256K
|
||||||
|
|
||||||
|
/*
|
||||||
|
* STM32F722 RAM totals 256 KiB:
|
||||||
|
* - DTCM: 64 KiB at 0x2000_0000
|
||||||
|
* - SRAM1: 176 KiB at 0x2001_0000
|
||||||
|
* - SRAM2: 16 KiB at 0x2003_C000
|
||||||
|
*
|
||||||
|
* SRAM1 and SRAM2 are contiguous and DMA-accessible. DTCM remains separate
|
||||||
|
* for a future explicit placement policy.
|
||||||
|
*/
|
||||||
|
RAM : ORIGIN = 0x20010000, LENGTH = 192K
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Absolute symbols are the single source of truth for the Rust NOR adapter. */
|
||||||
|
__kaptan_config_slot0_start = 0x08040000;
|
||||||
|
__kaptan_config_slot1_start = 0x08060000;
|
||||||
|
__kaptan_config_journal_end = 0x08080000;
|
||||||
|
|
||||||
|
ASSERT(ORIGIN(FLASH) + LENGTH(FLASH) == __kaptan_config_slot0_start,
|
||||||
|
"application FLASH must end at configuration slot 0")
|
||||||
|
ASSERT(__kaptan_config_slot1_start - __kaptan_config_slot0_start == 128K,
|
||||||
|
"configuration slot 0 must be one F722 128 KiB sector")
|
||||||
|
ASSERT(__kaptan_config_journal_end - __kaptan_config_slot1_start == 128K,
|
||||||
|
"configuration slot 1 must be one F722 128 KiB sector")
|
||||||
+166
-23
@@ -1,11 +1,13 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Show flash, static RAM and ELF-section use for embedded firmware.
|
"""Validate and show flash, static RAM and ELF-section use.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
scripts/analyze-fw.py <firmware.elf> <memory.x>
|
scripts/analyze-fw.py <firmware.elf> <memory.x>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
import re
|
import re
|
||||||
|
from collections import namedtuple
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
@@ -17,6 +19,11 @@ REGION = re.compile(
|
|||||||
re.MULTILINE,
|
re.MULTILINE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Section = namedtuple("Section", "name size address allocated")
|
||||||
|
LoadSegment = namedtuple(
|
||||||
|
"LoadSegment", "virtual_address physical_address file_size memory_size"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def number(text):
|
def number(text):
|
||||||
"""Parse linker numbers such as 0x2001_0000, 176K, and 1M."""
|
"""Parse linker numbers such as 0x2001_0000, 176K, and 1M."""
|
||||||
@@ -30,18 +37,18 @@ def run(command):
|
|||||||
return subprocess.run(command, check=True, text=True, capture_output=True).stdout
|
return subprocess.run(command, check=True, text=True, capture_output=True).stdout
|
||||||
|
|
||||||
|
|
||||||
def llvm_size():
|
def llvm_tool(name):
|
||||||
"""Find the llvm-size installed by the active Rust toolchain."""
|
"""Find one LLVM binary installed by the active Rust toolchain."""
|
||||||
sysroot = run(["rustc", "--print", "sysroot"]).strip()
|
sysroot = run(["rustc", "--print", "sysroot"]).strip()
|
||||||
host = next(
|
host = next(
|
||||||
line.split(": ", 1)[1]
|
line.split(": ", 1)[1]
|
||||||
for line in run(["rustc", "-vV"]).splitlines()
|
for line in run(["rustc", "-vV"]).splitlines()
|
||||||
if line.startswith("host: ")
|
if line.startswith("host: ")
|
||||||
)
|
)
|
||||||
tool = Path(sysroot) / "lib" / "rustlib" / host / "bin" / "llvm-size"
|
tool = Path(sysroot) / "lib" / "rustlib" / host / "bin" / name
|
||||||
if not tool.is_file():
|
if not tool.is_file():
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"llvm-size is unavailable; install it with: rustup component add llvm-tools-preview"
|
f"{name} is unavailable; install it with: rustup component add llvm-tools-preview"
|
||||||
)
|
)
|
||||||
return tool
|
return tool
|
||||||
|
|
||||||
@@ -54,14 +61,39 @@ def regions(memory_x):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def sections(output):
|
def elf_layout(output):
|
||||||
"""Return llvm-size sections as (name, bytes, address)."""
|
"""Return allocated-section and load-segment metadata from llvm-readobj JSON."""
|
||||||
result = []
|
documents = json.loads(output)
|
||||||
for line in output.splitlines():
|
if len(documents) != 1:
|
||||||
fields = line.split()
|
raise ValueError("llvm-readobj returned no unique ELF document")
|
||||||
if len(fields) == 3 and fields[0].startswith("."):
|
document = documents[0]
|
||||||
result.append((fields[0], int(fields[1], 0), int(fields[2], 0)))
|
|
||||||
return result
|
section_list = []
|
||||||
|
for wrapped in document.get("Sections", []):
|
||||||
|
section = wrapped["Section"]
|
||||||
|
flags = {flag["Name"] for flag in section["Flags"]["Flags"]}
|
||||||
|
section_list.append(
|
||||||
|
Section(
|
||||||
|
section["Name"]["Name"],
|
||||||
|
section["Size"],
|
||||||
|
section["Address"],
|
||||||
|
"SHF_ALLOC" in flags,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
load_segments = []
|
||||||
|
for wrapped in document.get("ProgramHeaders", []):
|
||||||
|
segment = wrapped["ProgramHeader"]
|
||||||
|
if segment["Type"]["Name"] == "PT_LOAD":
|
||||||
|
load_segments.append(
|
||||||
|
LoadSegment(
|
||||||
|
segment["VirtualAddress"],
|
||||||
|
segment["PhysicalAddress"],
|
||||||
|
segment["FileSize"],
|
||||||
|
segment["MemSize"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return section_list, load_segments
|
||||||
|
|
||||||
|
|
||||||
def region_for(address, layout):
|
def region_for(address, layout):
|
||||||
@@ -72,6 +104,88 @@ def region_for(address, layout):
|
|||||||
return "outside"
|
return "outside"
|
||||||
|
|
||||||
|
|
||||||
|
def region_for_span(address, size, layout):
|
||||||
|
"""Return the one linker region containing the complete nonempty span."""
|
||||||
|
end = address + size
|
||||||
|
for name, origin, length in layout:
|
||||||
|
if origin <= address and end <= origin + length:
|
||||||
|
return name
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def merged_span_size(spans):
|
||||||
|
"""Return bytes covered by the union of half-open address spans."""
|
||||||
|
merged = []
|
||||||
|
for start, end in sorted(spans):
|
||||||
|
if not merged or start > merged[-1][1]:
|
||||||
|
merged.append([start, end])
|
||||||
|
else:
|
||||||
|
merged[-1][1] = max(merged[-1][1], end)
|
||||||
|
return sum(end - start for start, end in merged)
|
||||||
|
|
||||||
|
|
||||||
|
def region_usage(name, layout, section_list, load_segments):
|
||||||
|
"""Return runtime or load-image bytes occupying one linker region.
|
||||||
|
|
||||||
|
Allocated-section VMAs account for runtime RAM, while PT_LOAD physical file
|
||||||
|
spans account for initializers stored in flash (notably a future `.data`).
|
||||||
|
Taking their union avoids counting ordinary flash sections twice.
|
||||||
|
"""
|
||||||
|
spans = [
|
||||||
|
(section.address, section.address + section.size)
|
||||||
|
for section in section_list
|
||||||
|
if section.allocated
|
||||||
|
and section.size
|
||||||
|
and region_for_span(section.address, section.size, layout) == name
|
||||||
|
]
|
||||||
|
spans.extend(
|
||||||
|
(
|
||||||
|
segment.physical_address,
|
||||||
|
segment.physical_address + segment.file_size,
|
||||||
|
)
|
||||||
|
for segment in load_segments
|
||||||
|
if segment.file_size
|
||||||
|
and region_for_span(segment.physical_address, segment.file_size, layout) == name
|
||||||
|
)
|
||||||
|
return merged_span_size(spans)
|
||||||
|
|
||||||
|
|
||||||
|
def validation_errors(layout, section_list, load_segments):
|
||||||
|
"""Return every placement error that makes a raw firmware image unsafe."""
|
||||||
|
errors = []
|
||||||
|
for section in section_list:
|
||||||
|
if not section.allocated or section.size == 0:
|
||||||
|
continue
|
||||||
|
region = region_for_span(section.address, section.size, layout)
|
||||||
|
if region is None:
|
||||||
|
errors.append(
|
||||||
|
f"allocated section {section.name} spans "
|
||||||
|
f"0x{section.address:08X}..0x{section.address + section.size:08X} "
|
||||||
|
"outside declared memory"
|
||||||
|
)
|
||||||
|
|
||||||
|
for index, segment in enumerate(load_segments):
|
||||||
|
if segment.memory_size and region_for_span(
|
||||||
|
segment.virtual_address, segment.memory_size, layout
|
||||||
|
) is None:
|
||||||
|
errors.append(
|
||||||
|
f"PT_LOAD[{index}] virtual span "
|
||||||
|
f"0x{segment.virtual_address:08X}.."
|
||||||
|
f"0x{segment.virtual_address + segment.memory_size:08X} "
|
||||||
|
"is outside declared memory"
|
||||||
|
)
|
||||||
|
if segment.file_size and region_for_span(
|
||||||
|
segment.physical_address, segment.file_size, layout
|
||||||
|
) is None:
|
||||||
|
errors.append(
|
||||||
|
f"PT_LOAD[{index}] physical span "
|
||||||
|
f"0x{segment.physical_address:08X}.."
|
||||||
|
f"0x{segment.physical_address + segment.file_size:08X} "
|
||||||
|
"is outside declared memory"
|
||||||
|
)
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
if len(sys.argv) != 3:
|
if len(sys.argv) != 3:
|
||||||
raise ValueError(f"usage: {sys.argv[0]} <firmware.elf> <memory.x>")
|
raise ValueError(f"usage: {sys.argv[0]} <firmware.elf> <memory.x>")
|
||||||
@@ -86,20 +200,29 @@ def main():
|
|||||||
layout = regions(memory_x)
|
layout = regions(memory_x)
|
||||||
if not layout:
|
if not layout:
|
||||||
raise ValueError(f"no MEMORY regions found in: {memory_x}")
|
raise ValueError(f"no MEMORY regions found in: {memory_x}")
|
||||||
section_list = sections(run([str(llvm_size()), "-A", str(elf)]))
|
readobj = llvm_tool("llvm-readobj")
|
||||||
|
section_list, load_segments = elf_layout(
|
||||||
|
run(
|
||||||
|
[
|
||||||
|
str(readobj),
|
||||||
|
"--sections",
|
||||||
|
"--program-headers",
|
||||||
|
"--elf-output-style=JSON",
|
||||||
|
str(elf),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
if not section_list:
|
if not section_list:
|
||||||
raise ValueError(f"no sections found in: {elf}")
|
raise ValueError(f"no sections found in: {elf}")
|
||||||
|
if not load_segments:
|
||||||
|
raise ValueError(f"no loadable segments found in: {elf}")
|
||||||
|
|
||||||
print(f"\nFirmware: {elf}")
|
print(f"\nFirmware: {elf}")
|
||||||
print(f"ELF file size: {elf.stat().st_size:,} bytes")
|
print(f"ELF file size: {elf.stat().st_size:,} bytes")
|
||||||
print("\nLinker regions:")
|
print("\nLinker regions:")
|
||||||
print(" Region Origin Used / Capacity Remaining")
|
print(" Region Origin Used / Capacity Remaining")
|
||||||
for name, origin, length in layout:
|
for name, origin, length in layout:
|
||||||
used = sum(
|
used = region_usage(name, layout, section_list, load_segments)
|
||||||
size
|
|
||||||
for _, size, address in section_list
|
|
||||||
if size and origin <= address < origin + length
|
|
||||||
)
|
|
||||||
remaining = length - used
|
remaining = length - used
|
||||||
print(
|
print(
|
||||||
f" {name:<12} 0x{origin:08X} {used / 1024:>7.2f} KiB /"
|
f" {name:<12} 0x{origin:08X} {used / 1024:>7.2f} KiB /"
|
||||||
@@ -109,14 +232,34 @@ def main():
|
|||||||
|
|
||||||
print("\nELF sections:")
|
print("\nELF sections:")
|
||||||
print(" Region Address Size Section")
|
print(" Region Address Size Section")
|
||||||
for name, size, address in sorted(section_list, key=lambda section: section[2]):
|
for section in sorted(section_list, key=lambda item: item.address):
|
||||||
if size:
|
if section.size:
|
||||||
print(f" {region_for(address, layout):<12} 0x{address:08X} {size:>10,} {name}")
|
location = (
|
||||||
|
region_for(section.address, layout)
|
||||||
|
if section.allocated
|
||||||
|
else "non-alloc"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f" {location:<12} "
|
||||||
|
f"0x{section.address:08X} {section.size:>10,} {section.name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
errors = validation_errors(layout, section_list, load_segments)
|
||||||
|
if errors:
|
||||||
|
raise ValueError("firmware memory validation failed:\n " + "\n ".join(errors))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
main()
|
main()
|
||||||
except (IndexError, OSError, RuntimeError, ValueError, subprocess.CalledProcessError) as error:
|
except (
|
||||||
|
IndexError,
|
||||||
|
KeyError,
|
||||||
|
OSError,
|
||||||
|
RuntimeError,
|
||||||
|
TypeError,
|
||||||
|
ValueError,
|
||||||
|
subprocess.CalledProcessError,
|
||||||
|
) as error:
|
||||||
print(f"error: {error}", file=sys.stderr)
|
print(f"error: {error}", file=sys.stderr)
|
||||||
raise SystemExit(1) from error
|
raise SystemExit(1) from error
|
||||||
@@ -1,18 +1,16 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
if [[ $# -ne 1 ]]; then
|
if [[ $# -lt 1 || $# -gt 2 ]]; then
|
||||||
printf 'Usage: %s <firmware.elf>\n' "$0" >&2
|
printf 'Usage: %s <firmware.elf> [memory.x]\n' "$0" >&2
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
readonly input_elf="$1"
|
readonly input_elf="$1"
|
||||||
readonly firmware_name="$(basename "${input_elf%.elf}")"
|
|
||||||
readonly board_name="${firmware_name#kaptan-}"
|
|
||||||
readonly output_image="${input_elf%.elf}.bin"
|
readonly output_image="${input_elf%.elf}.bin"
|
||||||
readonly script_directory="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
readonly script_directory="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
readonly workspace_root="$(cd -- "$script_directory/.." && pwd)"
|
readonly workspace_root="$(cd -- "$script_directory/.." && pwd)"
|
||||||
readonly memory_x="$workspace_root/boards/$board_name/memory.x"
|
readonly memory_x="${2:-$workspace_root/platforms/stm32f7/link/f722/memory.x}"
|
||||||
readonly host="$(rustc -vV | sed -n 's/^host: //p')"
|
readonly host="$(rustc -vV | sed -n 's/^host: //p')"
|
||||||
readonly llvm_objcopy="$(rustc --print sysroot)/lib/rustlib/$host/bin/llvm-objcopy"
|
readonly llvm_objcopy="$(rustc --print sysroot)/lib/rustlib/$host/bin/llvm-objcopy"
|
||||||
|
|
||||||
@@ -22,7 +20,7 @@ if [[ ! -f "$input_elf" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -f "$memory_x" ]]; then
|
if [[ ! -f "$memory_x" ]]; then
|
||||||
printf 'cannot find the linker layout for board %q: %s\n' "$board_name" "$memory_x" >&2
|
printf 'cannot find linker layout: %s\n' "$memory_x" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -31,6 +29,11 @@ if [[ ! -x "$llvm_objcopy" ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
"$llvm_objcopy" -O binary "$input_elf" "$output_image"
|
|
||||||
printf 'DFU image: %s\n' "$output_image"
|
|
||||||
"$script_directory/analyze-fw.py" "$input_elf" "$memory_x"
|
"$script_directory/analyze-fw.py" "$input_elf" "$memory_x"
|
||||||
|
|
||||||
|
temporary_image="$(mktemp "${output_image}.tmp.XXXXXX")"
|
||||||
|
trap 'rm -f -- "$temporary_image"' EXIT
|
||||||
|
"$llvm_objcopy" -O binary "$input_elf" "$temporary_image"
|
||||||
|
mv -f -- "$temporary_image" "$output_image"
|
||||||
|
trap - EXIT
|
||||||
|
printf 'DFU image: %s\n' "$output_image"
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
#!/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()
|
||||||
Reference in new issue
Block a user