Files
ESP32_COMM/tools/parse_map_static_mem.py
T
小龙虾 00b716c8db refactor(debug): 重构调试日志环形缓冲区管理
- 将日志环形缓冲区从静态数组改为动态堆分配
- 实现启动时大容量缓冲区和运行时小容量缓冲区的切换机制
- 添加日志环满时优先保留错误和警告信息的清理策略
- 优化内存使用,在日志为空时避免不必要的内存分配
- 移除调试日志中的冗余字段显示

feat(api): 更新CAN接口为批量发送模式

- 将 `/can/send` 接口替换为 `/can/send_batch`,支持1到N帧的批量发送
- 更新API文档中的接口描述和使用示例
- 修改请求参数格式为 `{"ch":0,"frames":[{...}]}` 返回格式为 `{"status":"ok","results":[...]}`
- 单帧发送也需要通过send_batch接口实现

feat(monitor): 新增GD32 ADC调试反馈处理功能

- 添加gd32_adc_feedback组件用于解析0xC4类型的SPI反馈
- 实现ADC数据解析包括温度、电压等参数的计算和转换
- 在can_feedback_task中集成ADC反馈分发逻辑
- 添加线程安全的数据获取和年龄检查接口

refactor(config): 优化任务栈大小配置并调整OTA块大小

- 重新评估各任务栈使用情况,优化HTTP、WiFi、SPI等工作任务的栈大小
- 将HTTP服务器栈从8KB调整至7KB,WiFi OTA任务栈从4KB调整至3KB
- OTA块大小从2KB调整回4KB以提升传输吞吐性能
- 重新整理配置文件结构,移动WiFi模式配置位置
2026-07-20 03:10:42 +08:00

227 lines
7.2 KiB
Python

#!/usr/bin/env python3
"""Print ESP32-C3 static memory breakdown from build map + ELF (nm).
Usage (from repo or any cwd):
python tools/parse_map_static_mem.py
python tools/parse_map_static_mem.py --elf build/ESP32_COMM.elf
"""
from __future__ import annotations
import argparse
import re
import shutil
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_MAP = ROOT / "build" / "ESP32_COMM.map"
DEFAULT_ELF = ROOT / "build" / "ESP32_COMM.elf"
# ESP32-C3 DRAM / IRAM windows used by app (after 16KB i-cache)
DRAM_LO, DRAM_HI = 0x3FC80000, 0x3FCE0000
IRAM_LO, IRAM_HI = 0x40380000, 0x403E0000
def demangle(name: str) -> str:
"""Lightweight Itanium demangle for common ESP app symbols."""
# _ZL13s_status_json -> s_status_json
m = re.match(r"^_ZL\d+(.+)$", name)
if m:
return m.group(1)
# _ZZ20can_feedback_handler...E15s_feedback_json
m = re.search(r"E\d+([A-Za-z_][A-Za-z0-9_]*)$", name)
if m and name.startswith("_ZZ"):
return m.group(1) + " (fn-static)"
# _ZN12_GLOBAL__N_1L6s_workE
m = re.match(r"^_ZN\d+_GLOBAL__N_\d+L\d+(.+)E$", name)
if m:
return m.group(1) + " (anon)"
# _ZZN12Esp32OtaCore23computeShaFromPartitionEvE14s_sha_read_buf
m = re.search(r"E\d+([A-Za-z_][A-Za-z0-9_]*)$", name)
if m and name.startswith("_ZZN"):
return m.group(1) + " (method-static)"
# singleton: _ZZN8DebugLog8instanceEvE4inst
m = re.match(r"^_ZZN(\d+)(.+)$", name)
if m and "instanceEvE" in name:
# extract class-ish
body = name
cm = re.search(r"_ZZN\d+([A-Za-z0-9_]+)\d+instance", body)
if cm:
return cm.group(1) + "::instance"
return "singleton inst"
return name
def find_nm() -> str | None:
which = shutil.which("riscv32-esp-elf-nm")
if which:
return which
roots = [
Path(r"C:\Espressif\tools\riscv32-esp-elf"),
Path(r"F:\software\.espressif\tools\riscv32-esp-elf"),
]
for root in roots:
if not root.exists():
continue
matches = sorted(root.glob("**/riscv32-esp-elf-nm.exe"))
if matches:
return str(matches[-1])
return None
def parse_syms(map_text: str) -> dict[str, int]:
out = {}
for name in (
"_iram_start",
"_iram_end",
"_data_start",
"_data_end",
"_bss_start",
"_bss_end",
"_heap_start",
):
m = re.search(rf"0x([0-9a-fA-F]+)\s+{name}\s*=", map_text)
if m:
out[name] = int(m.group(1), 16)
return out
def run_nm(nm: str, elf: Path) -> list[tuple[int, int, str, str]]:
"""Return list of (addr, size, type, name)."""
# -S size, --size-sort, -t x hex
r = subprocess.run(
[nm, "-S", "--size-sort", "-t", "x", str(elf)],
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
check=False,
)
rows = []
for line in r.stdout.splitlines():
parts = line.split()
if len(parts) < 4:
continue
try:
addr = int(parts[0], 16)
size = int(parts[1], 16)
except ValueError:
continue
typ = parts[2]
name = parts[3]
rows.append((addr, size, typ, name))
return rows
def top_in_range(
rows: list[tuple[int, int, str, str]],
lo: int,
hi: int,
types: set[str],
n: int = 20,
min_size: int = 64,
) -> list[tuple[int, str, str]]:
picked = []
for addr, size, typ, name in rows:
if typ not in types:
continue
if size < min_size:
continue
if not (lo <= addr < hi):
continue
# skip linker bookkeeping extremes
if name.startswith("_") and name.endswith(("_start", "_end", "_length")):
if size > 0x10000:
continue
picked.append((size, typ, name))
picked.sort(key=lambda x: -x[0])
return picked[:n]
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--map", type=Path, default=DEFAULT_MAP)
ap.add_argument("--elf", type=Path, default=DEFAULT_ELF)
ap.add_argument("-n", type=int, default=18, help="top N symbols per section")
args = ap.parse_args()
if not args.map.exists():
print(f"map not found: {args.map}", file=sys.stderr)
return 1
text = args.map.read_text(encoding="utf-8", errors="replace")
syms = parse_syms(text)
def span(a: str, b: str) -> int:
if a not in syms or b not in syms:
return 0
return max(0, syms[b] - syms[a])
iram = span("_iram_start", "_iram_end")
data = span("_data_start", "_data_end")
bss = span("_bss_start", "_bss_end")
heap_start = syms.get("_heap_start", 0)
static_to_heap = max(0, heap_start - DRAM_LO) if heap_start else iram + data + bss
print("=== Linker section totals (authoritative) ===")
print(f" IRAM : {iram:7d} ({iram/1024:6.1f} KB) code in RAM (WiFi/BT/ISR/tlsf…)")
print(f" .data : {data:7d} ({data/1024:6.1f} KB) initialized globals/singletons")
print(f" .bss : {bss:7d} ({bss/1024:6.1f} KB) zeroed static buffers")
print(f" static→heap : {static_to_heap:7d} ({static_to_heap/1024:6.1f} KB)")
if heap_start:
print(f" _heap_start : 0x{heap_start:08x}")
room = max(0, DRAM_HI - heap_start)
print(f" DRAM window after heap_start (to SOC_DRAM_HIGH): {room} ({room/1024:.1f} KB)")
print(f" sum check IRAM+data+bss = {iram+data+bss} (should ≈ static→heap on C3)")
nm = find_nm()
if not args.elf.exists():
print(f"\nELF not found: {args.elf} (skip symbol tops)")
return 0
if not nm:
print("\nriscv32-esp-elf-nm not found (skip symbol tops)")
return 0
print(f"\nusing nm: {nm}")
rows = run_nm(nm, args.elf)
sections = [
("IRAM code (largest functions)", IRAM_LO, IRAM_HI, set("tT")),
(".data (largest objects)", DRAM_LO, DRAM_HI, set("dD")),
(".bss (largest objects)", DRAM_LO, DRAM_HI, set("bB")),
]
for title, lo, hi, types in sections:
print(f"\n=== Top {args.n} {title} ===")
tops = top_in_range(rows, lo, hi, types, n=args.n)
# for .bss prefer heap_start range if known
if "b" in {t.lower() for t in types} and heap_start:
bss_lo = syms.get("_bss_start", DRAM_LO)
bss_hi = heap_start
tops = top_in_range(rows, bss_lo, bss_hi, types, n=args.n)
if "d" in {t.lower() for t in types} and "_data_start" in syms:
tops = top_in_range(
rows,
syms["_data_start"],
syms.get("_data_end", DRAM_HI),
types,
n=args.n,
)
total = sum(s for s, _, _ in tops)
for size, typ, name in tops:
print(
f" {size:6d} {size/1024:5.1f}KB {typ} "
f"{demangle(name):40s} {name}"
)
print(f" (listed sum {total} / {total/1024:.1f} KB)")
print(
"\nTip: runtime /system/status memory.{iram,data,bss} matches linker totals; "
"symbol tops are build-time only."
)
return 0
if __name__ == "__main__":
raise SystemExit(main())