mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-01 12:29:04 +00:00
Fix waterfall designer crash and also some linker script fixes (#3281)
* init * _
This commit is contained in:
+139
@@ -0,0 +1,139 @@
|
||||
#!/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())
|
||||
Executable
+187
@@ -0,0 +1,187 @@
|
||||
#!/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.
|
||||
#
|
||||
|
||||
"""Translate a guru meditation pc/lr into a symbol, source line and disassembly.
|
||||
|
||||
Plain `arm-none-eabi-gdb -ex "x/3i 0xADDR" application.elf` only works for
|
||||
addresses in the main M0 firmware. External apps are linked at a placeholder
|
||||
0xADxxxxxx address but *run* from ~0x1008xxxx, so the guru shows a runtime
|
||||
address that does not exist in the ELF. This script does the translation:
|
||||
|
||||
link_addr = section_vma + (runtime_addr - memory_location)
|
||||
|
||||
where memory_location is the first word of the app's .ppma header.
|
||||
|
||||
Usage:
|
||||
guru_lookup.py 0x10085CF9 0x0FF36284
|
||||
guru_lookup.py --app waterfall_designer 0x10085CF9
|
||||
guru_lookup.py --baseband adsbrx 0x10081234
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
|
||||
|
||||
def find_toolchain(explicit):
|
||||
"""Locate the arm-none-eabi- prefix: --toolchain, $ARM_TOOLCHAIN, ./armbin/bin, PATH."""
|
||||
candidates = []
|
||||
if explicit:
|
||||
candidates.append(explicit)
|
||||
if os.environ.get("ARM_TOOLCHAIN"):
|
||||
candidates.append(os.environ["ARM_TOOLCHAIN"])
|
||||
candidates.append(os.path.join(REPO, "armbin", "bin", "arm-none-eabi-"))
|
||||
candidates.append("arm-none-eabi-")
|
||||
|
||||
for prefix in candidates:
|
||||
try:
|
||||
subprocess.run([prefix + "addr2line", "--version"],
|
||||
capture_output=True, check=True)
|
||||
return prefix
|
||||
except (OSError, subprocess.CalledProcessError):
|
||||
continue
|
||||
sys.exit("error: no arm-none-eabi toolchain found (try --toolchain /path/to/arm-none-eabi-)")
|
||||
|
||||
|
||||
def run(*cmd):
|
||||
return subprocess.run(cmd, capture_output=True, text=True).stdout
|
||||
|
||||
|
||||
def section_vmas(tc, elf):
|
||||
"""section name -> (vma, size) for every .external_app_* section."""
|
||||
out = run(tc + "objdump", "-h", elf)
|
||||
vmas = {}
|
||||
for m in re.finditer(r'\.external_app_(\S+)\s+([0-9a-f]{8})\s+([0-9a-f]{8})', out):
|
||||
vmas[m.group(1)] = (int(m.group(3), 16), int(m.group(2), 16))
|
||||
return vmas
|
||||
|
||||
|
||||
def app_load_addresses(build_dir):
|
||||
"""app name -> memory_location, read from the first word of each .ppma."""
|
||||
app_dir = os.path.join(build_dir, "firmware", "application")
|
||||
loads = {}
|
||||
if not os.path.isdir(app_dir):
|
||||
return loads
|
||||
for name in os.listdir(app_dir):
|
||||
if not name.endswith(".ppma"):
|
||||
continue
|
||||
path = os.path.join(app_dir, name)
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
(mem,) = struct.unpack("<I", f.read(4))
|
||||
loads[name[:-5]] = mem
|
||||
except (OSError, struct.error):
|
||||
pass
|
||||
return loads
|
||||
|
||||
|
||||
def describe(tc, elf, addr, label=""):
|
||||
"""Print addr2line (with inline frames) plus a short disassembly window."""
|
||||
a = "0x%08x" % addr
|
||||
src = run(tc + "addr2line", "-f", "-C", "-i", "-e", elf, a).strip()
|
||||
print(" %s%s" % (label, a))
|
||||
for i, line in enumerate(src.splitlines()):
|
||||
print(" %s %s" % ("in" if i % 2 == 0 else " at", line))
|
||||
|
||||
dis = run(tc + "objdump", "-d", "--start-address=0x%x" % (addr - 8),
|
||||
"--stop-address=0x%x" % (addr + 8), elf)
|
||||
body = [l for l in dis.splitlines() if re.match(r'\s*[0-9a-f]+:\t', l)]
|
||||
if body:
|
||||
print(" --")
|
||||
for l in body:
|
||||
here = re.match(r'\s*0*%x:' % addr, l)
|
||||
print(" %s%s" % (" >> " if here else " ", l.strip()))
|
||||
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser(description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
p.add_argument("addresses", nargs="+", help="pc / lr values from the guru screen")
|
||||
p.add_argument("--build", default=os.path.join(REPO, "build"), help="build directory")
|
||||
p.add_argument("--app", help="external app name, if auto-detection is ambiguous")
|
||||
p.add_argument("--baseband", help="resolve against firmware/baseband/baseband_<name>.elf")
|
||||
p.add_argument("--toolchain", help="arm-none-eabi- prefix")
|
||||
args = p.parse_args()
|
||||
|
||||
tc = find_toolchain(args.toolchain)
|
||||
app_elf = os.path.join(args.build, "firmware", "application", "application.elf")
|
||||
|
||||
if args.baseband:
|
||||
elf = os.path.join(args.build, "firmware", "baseband",
|
||||
"baseband_%s.elf" % args.baseband)
|
||||
if not os.path.exists(elf):
|
||||
sys.exit("error: no such baseband image: " + elf)
|
||||
for a in args.addresses:
|
||||
addr = int(a, 16) & ~1
|
||||
print("\n%s (M4 baseband: %s)" % (a, args.baseband))
|
||||
describe(tc, elf, addr)
|
||||
return
|
||||
|
||||
if not os.path.exists(app_elf):
|
||||
sys.exit("error: %s not found (pass --build)" % app_elf)
|
||||
|
||||
vmas = section_vmas(tc, app_elf)
|
||||
loads = app_load_addresses(args.build)
|
||||
|
||||
for a in args.addresses:
|
||||
addr = int(a, 16) & ~1 # drop the Thumb bit
|
||||
print("\n=== %s ===" % a)
|
||||
|
||||
# Main M0 firmware is linked at 0 (SPIFI shadow), so it resolves directly.
|
||||
if addr < 0x00100000:
|
||||
print(" main M0 firmware")
|
||||
describe(tc, app_elf, addr)
|
||||
continue
|
||||
|
||||
# Otherwise it is an external app running from local SRAM. Every app has
|
||||
# its own memory_location, so several can plausibly contain the address;
|
||||
# list each candidate and let the caller pick the app they actually ran.
|
||||
names = [args.app] if args.app else sorted(loads)
|
||||
hits = 0
|
||||
for name in names:
|
||||
if name not in loads or name not in vmas:
|
||||
continue
|
||||
off = addr - loads[name]
|
||||
vma, size = vmas[name]
|
||||
if not (0 <= off < size):
|
||||
continue
|
||||
hits += 1
|
||||
print(" candidate: %s load=0x%08X offset=0x%X" % (name, loads[name], off))
|
||||
describe(tc, app_elf, vma + off, "link ")
|
||||
|
||||
if hits == 0:
|
||||
print(" no external app contains this address.")
|
||||
print(" If the guru header said M4, re-run with --baseband <image>.")
|
||||
print(" A wild pc with a sane lr usually means an indirect call through")
|
||||
print(" a bad pointer - look up the lr instead, that is the caller.")
|
||||
elif hits > 1 and not args.app:
|
||||
print(" (%d candidates - narrow it with --app <name>)" % hits)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+214
@@ -0,0 +1,214 @@
|
||||
#!/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.
|
||||
#
|
||||
|
||||
"""Report per-function stack frame sizes for an ELF or object file.
|
||||
|
||||
The M0 process stack is 4 kB total (__process_stack_size__ in LPC43xx_M0.ld),
|
||||
shared by the whole UI thread including external apps. A guru that says
|
||||
"Stack Overflow" is a hard fault where get_free_stack_space() < 16, i.e. the
|
||||
4 kB really was consumed - it is not a heap problem.
|
||||
|
||||
Watch out for anything holding a File: _FS_TINY is 0 and _MAX_SS is 512 in
|
||||
ffconf.h, so every FIL carries a 512 byte sector cache and a File local costs
|
||||
~560 bytes of stack. copy_file() holds two of them plus a 512 byte block
|
||||
buffer, ~1.8 kB in one frame.
|
||||
|
||||
Thumb-1 cannot encode `sub sp, #imm` above 508 bytes, so large frames appear as
|
||||
a negative constant loaded from the literal pool:
|
||||
|
||||
ldr r4, [pc, #728] ; (2e0 <...>)
|
||||
add sp, r4 ; r4 = 0xfffffcfc = -772
|
||||
|
||||
Naive greps for `sub sp` miss exactly the frames that matter. This resolves the
|
||||
literal.
|
||||
|
||||
Usage:
|
||||
stack_usage.py build/firmware/application/application.elf # top 40
|
||||
stack_usage.py <file> --min 512 # everything >= 512
|
||||
stack_usage.py <file> --grep copy_file # filter by name
|
||||
stack_usage.py <file> --chain main run_external_app copy_file # sum a call chain
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
M0_STACK_BYTES = 4096
|
||||
|
||||
REG_ORDER = ['r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6', 'r7',
|
||||
'r8', 'r9', 'sl', 'fp', 'ip', 'sp', 'lr', 'pc']
|
||||
|
||||
|
||||
def find_toolchain(explicit):
|
||||
for prefix in (explicit, 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 + "objdump", "--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 push_bytes(reglist):
|
||||
n = 0
|
||||
for part in reglist.split(','):
|
||||
part = part.strip()
|
||||
if '-' in part:
|
||||
a, b = part.split('-')
|
||||
if a in REG_ORDER and b in REG_ORDER:
|
||||
n += REG_ORDER.index(b) - REG_ORDER.index(a) + 1
|
||||
else:
|
||||
n += 2
|
||||
else:
|
||||
n += 1
|
||||
return n * 4
|
||||
|
||||
|
||||
def frame_sizes(tc, path):
|
||||
"""function name (mangled) -> stack bytes reserved in its prologue."""
|
||||
out = subprocess.run([tc + "objdump", "-d", path],
|
||||
capture_output=True, text=True).stdout
|
||||
|
||||
bodies, cur = {}, None
|
||||
for line in out.splitlines():
|
||||
m = re.match(r'^[0-9a-f]+ <(.+)>:', line)
|
||||
if m:
|
||||
cur = m.group(1)
|
||||
bodies[cur] = []
|
||||
elif cur is not None:
|
||||
bodies[cur].append(line)
|
||||
|
||||
sizes = {}
|
||||
for func, body in bodies.items():
|
||||
# Literal pool words, so `add sp, rN` can be resolved.
|
||||
literals = {}
|
||||
for line in body:
|
||||
m = re.match(r'\s*([0-9a-f]+):\s+([0-9a-f]{8})\s+\.word\s+0x([0-9a-f]+)', line)
|
||||
if m:
|
||||
literals[int(m.group(1), 16)] = int(m.group(3), 16)
|
||||
|
||||
total, regval = 0, {}
|
||||
for line in body:
|
||||
m = re.search(r'ldr\s+(\w+), \[pc, #\d+\].*;\s*\(([0-9a-f]+)', line)
|
||||
if m:
|
||||
regval[m.group(1)] = literals.get(int(m.group(2), 16))
|
||||
|
||||
m = re.search(r'\bsub(?:\.w)?\s+sp, (?:sp, )?#(\d+)', line)
|
||||
if m:
|
||||
total += int(m.group(1))
|
||||
|
||||
# Thumb-1 large frame: add sp, rN where rN holds a negative literal.
|
||||
# Bound it: the same register may instead hold a data address, and
|
||||
# external app addresses (0xADxxxxxx) also look "negative" here.
|
||||
m = re.search(r'\badd\s+sp, (r\d+|sl|fp|ip)\b', line)
|
||||
if m:
|
||||
v = regval.get(m.group(1))
|
||||
if v and v > 0x80000000:
|
||||
adjust = 0x100000000 - v
|
||||
if adjust <= 2 * M0_STACK_BYTES:
|
||||
total += adjust
|
||||
|
||||
m = re.search(r'\bpush(?:\.w)?\s+\{(.+)\}', line)
|
||||
if m:
|
||||
total += push_bytes(m.group(1))
|
||||
|
||||
sizes[func] = total
|
||||
return sizes
|
||||
|
||||
|
||||
def demangle(tc, names):
|
||||
if not names:
|
||||
return []
|
||||
out = subprocess.run([tc + "c++filt"], input="\n".join(names),
|
||||
capture_output=True, text=True).stdout
|
||||
return out.splitlines()
|
||||
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser(description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
p.add_argument("path", help="ELF or .obj to analyse")
|
||||
p.add_argument("--min", type=int, default=0, help="only show frames >= this many bytes")
|
||||
p.add_argument("--top", type=int, default=40, help="max rows to print (0 = all)")
|
||||
p.add_argument("--grep", help="only functions whose demangled name matches this regex")
|
||||
p.add_argument("--chain", nargs="+",
|
||||
help="sum the frames of these functions (substring match) as a call chain")
|
||||
p.add_argument("--toolchain", help="arm-none-eabi- prefix")
|
||||
args = p.parse_args()
|
||||
|
||||
if not os.path.exists(args.path):
|
||||
sys.exit("error: no such file: " + args.path)
|
||||
|
||||
tc = find_toolchain(args.toolchain)
|
||||
sizes = frame_sizes(tc, args.path)
|
||||
names = list(sizes)
|
||||
pretty = dict(zip(names, demangle(tc, names)))
|
||||
|
||||
if args.chain:
|
||||
print("\ncall chain, M0 process stack is %d bytes\n" % M0_STACK_BYTES)
|
||||
total = 0
|
||||
for want in args.chain:
|
||||
hit = None
|
||||
for n in names:
|
||||
if want in pretty[n] or want in n:
|
||||
if hit is None or sizes[n] > sizes[hit]:
|
||||
hit = n
|
||||
if hit is None:
|
||||
print(" %6s %s (not found)" % ("?", want))
|
||||
continue
|
||||
total += sizes[hit]
|
||||
print(" %6d %s" % (sizes[hit], pretty[hit][:96]))
|
||||
pct = 100.0 * total / M0_STACK_BYTES
|
||||
print(" " + "-" * 60)
|
||||
print(" %6d total (%.0f%% of the 4 kB stack, %d bytes free)"
|
||||
% (total, pct, M0_STACK_BYTES - total))
|
||||
if total > M0_STACK_BYTES:
|
||||
print("\n OVERFLOWS - this chain cannot fit.")
|
||||
elif pct > 75:
|
||||
print("\n Tight. Anything the callees add on top may fault.")
|
||||
return
|
||||
|
||||
rows = [(v, pretty[k]) for k, v in sizes.items() if v >= args.min]
|
||||
if args.grep:
|
||||
rx = re.compile(args.grep)
|
||||
rows = [r for r in rows if rx.search(r[1])]
|
||||
rows.sort(key=lambda r: -r[0])
|
||||
if args.top:
|
||||
rows = rows[:args.top]
|
||||
|
||||
print("\n%6s %s" % ("bytes", "function"))
|
||||
for v, n in rows:
|
||||
flag = " <-- over half the stack" if v > M0_STACK_BYTES // 2 else ""
|
||||
print("%6d %s%s" % (v, n[:100], flag))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user