mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-08-20 14:39:01 +00:00
a4d91f12ef
* init * _
140 lines
5.0 KiB
Python
Executable File
140 lines
5.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
#
|
|
# copyleft 2026 zxkmm co author with AI
|
|
#
|
|
# This file is part of PortaPack.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2, or (at your option)
|
|
# any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; see the file COPYING. If not, write to
|
|
# the Free Software Foundation, Inc., 51 Franklin Street,
|
|
# Boston, MA 02110-1301, USA.
|
|
#
|
|
|
|
"""Check that every external app symbol landed in its own app's memory region.
|
|
|
|
The rules in external.ld match input *section names*, so a glob like
|
|
`*(*ui*external_app*level*)` also matches
|
|
|
|
.text._ZN2ui12external_app18waterfall_designer...12on_add_levelEv
|
|
^^^^^ contains "level"
|
|
|
|
Section assignment is first-match-wins, so a symbol whose mangled name merely
|
|
contains another app's name gets linked into that app's region. Only one
|
|
external app is resident at a time, so calling it jumps into unmapped memory
|
|
and hard faults.
|
|
|
|
export_external_apps.py already warns about *data* words that point at another
|
|
app, but a `bl` is PC-relative: the target never appears as a literal, so that
|
|
check cannot see it. This one works on symbol addresses instead and does.
|
|
|
|
Usage:
|
|
check_external_symbol_placement.py [build/firmware/application/application.elf]
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
|
DEFAULT_ELF = os.path.join(REPO, "build", "firmware", "application", "application.elf")
|
|
DEFAULT_LD = os.path.join(REPO, "firmware", "application", "external", "external.ld")
|
|
|
|
|
|
def find_toolchain():
|
|
for prefix in (os.environ.get("ARM_TOOLCHAIN"),
|
|
os.path.join(REPO, "armbin", "bin", "arm-none-eabi-"),
|
|
"arm-none-eabi-"):
|
|
if not prefix:
|
|
continue
|
|
try:
|
|
subprocess.run([prefix + "nm", "--version"], capture_output=True, check=True)
|
|
return prefix
|
|
except (OSError, subprocess.CalledProcessError):
|
|
continue
|
|
sys.exit("error: no arm-none-eabi toolchain found (set $ARM_TOOLCHAIN)")
|
|
|
|
|
|
def parse_regions(ld_path):
|
|
with open(ld_path) as f:
|
|
ld = f.read()
|
|
regions = {}
|
|
for m in re.finditer(r'ram_external_app_(\w+)\s+\(rwx\)\s*:\s*org\s*=\s*'
|
|
r'(0x[0-9A-Fa-f]+),\s*len\s*=\s*(\d+)k', ld):
|
|
regions[m.group(1)] = (int(m.group(2), 16), int(m.group(3)) * 1024)
|
|
return regions
|
|
|
|
|
|
def main():
|
|
elf = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_ELF
|
|
if not os.path.exists(elf):
|
|
print("skipping symbol placement check, no ELF at %s" % elf)
|
|
return 0
|
|
|
|
tc = find_toolchain()
|
|
regions = parse_regions(DEFAULT_LD)
|
|
if not regions:
|
|
print("skipping symbol placement check, could not parse external.ld")
|
|
return 0
|
|
|
|
def owner(addr):
|
|
for name, (base, size) in regions.items():
|
|
if base <= addr < base + size:
|
|
return name
|
|
return None
|
|
|
|
nm = subprocess.run([tc + "nm", "-C", "--defined-only", elf],
|
|
capture_output=True, text=True).stdout
|
|
|
|
misplaced = []
|
|
total = 0
|
|
for line in nm.splitlines():
|
|
parts = line.split(" ", 2)
|
|
if len(parts) < 3 or not re.fullmatch(r'[0-9a-f]{8}', parts[0]):
|
|
continue
|
|
addr, sym = int(parts[0], 16), parts[2]
|
|
if "_veneer" in sym:
|
|
continue
|
|
m = re.search(r'external_app::(\w+)::', sym)
|
|
if not m:
|
|
continue
|
|
ns, host = m.group(1), owner(addr)
|
|
total += 1
|
|
if host is None:
|
|
continue # inlined into main firmware, harmless
|
|
# Namespace and region name need not be identical: ert/ert_app,
|
|
# keeloqtx/ui_keeloqtx, secplustx/ui_secplustx. Substring either way is fine.
|
|
if ns not in host and host not in ns:
|
|
misplaced.append((ns, host, sym))
|
|
|
|
print("\nchecking placement of %d external app symbols across %d regions"
|
|
% (total, len(regions)))
|
|
|
|
if not misplaced:
|
|
print("all external app symbols are in their own app's region")
|
|
return 0
|
|
|
|
print("\nERROR: %d symbol(s) linked into the wrong app's region." % len(misplaced))
|
|
print("These will hard fault when called - the owning app is not resident.\n")
|
|
for ns, host, sym in misplaced:
|
|
print(" %s -> landed in '%s' region" % (ns, host))
|
|
print(" %s" % sym)
|
|
print("\nFix: make the rule in external.ld specific to the app's own sources, e.g.")
|
|
print(" */external/<app>/*(*ui*external_app*<app>*);")
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|