mirror of
https://github.com/esphome/esphome.git
synced 2025-10-25 21:23:53 +01:00
tweak
This commit is contained in:
@@ -7,6 +7,7 @@ import logging
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .const import (
|
||||
CORE_SUBCATEGORY_PATTERNS,
|
||||
@@ -22,9 +23,65 @@ from .helpers import (
|
||||
parse_symbol_line,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from esphome.platformio_api import IDEData
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_toolchain_for_platform(platform: str) -> tuple[str | None, str | None]:
|
||||
"""Get objdump and readelf paths for a given platform.
|
||||
|
||||
This function auto-detects the correct toolchain based on the platform name,
|
||||
using the same detection logic as PlatformIO's IDEData class.
|
||||
|
||||
Args:
|
||||
platform: Platform name (e.g., "esp8266-ard", "esp32-idf", "esp32-c3-idf")
|
||||
|
||||
Returns:
|
||||
Tuple of (objdump_path, readelf_path) or (None, None) if not found/supported
|
||||
"""
|
||||
home = Path.home()
|
||||
platformio_packages = home / ".platformio" / "packages"
|
||||
|
||||
# Map platform to toolchain and prefix (same logic as PlatformIO uses)
|
||||
toolchain = None
|
||||
prefix = None
|
||||
|
||||
if "esp8266" in platform:
|
||||
toolchain = "toolchain-xtensa"
|
||||
prefix = "xtensa-lx106-elf"
|
||||
elif "esp32-c" in platform or "esp32-h" in platform or "esp32-p4" in platform:
|
||||
# RISC-V variants (C2, C3, C5, C6, H2, P4)
|
||||
toolchain = "toolchain-riscv32-esp"
|
||||
prefix = "riscv32-esp-elf"
|
||||
elif "esp32" in platform:
|
||||
# Xtensa variants (original, S2, S3)
|
||||
toolchain = "toolchain-xtensa-esp-elf"
|
||||
if "s2" in platform:
|
||||
prefix = "xtensa-esp32s2-elf"
|
||||
elif "s3" in platform:
|
||||
prefix = "xtensa-esp32s3-elf"
|
||||
else:
|
||||
prefix = "xtensa-esp32-elf"
|
||||
else:
|
||||
# Other platforms (RP2040, LibreTiny, etc.) - not supported for ELF analysis
|
||||
_LOGGER.debug("Platform %s not supported for ELF analysis", platform)
|
||||
return None, None
|
||||
|
||||
# Construct paths (same pattern as IDEData.objdump_path/readelf_path)
|
||||
toolchain_path = platformio_packages / toolchain / "bin"
|
||||
objdump_path = toolchain_path / f"{prefix}-objdump"
|
||||
readelf_path = toolchain_path / f"{prefix}-readelf"
|
||||
|
||||
if objdump_path.exists() and readelf_path.exists():
|
||||
_LOGGER.debug("Found %s toolchain: %s", platform, prefix)
|
||||
return str(objdump_path), str(readelf_path)
|
||||
|
||||
_LOGGER.warning("Toolchain not found at %s", toolchain_path)
|
||||
return None, None
|
||||
|
||||
|
||||
@dataclass
|
||||
class MemorySection:
|
||||
"""Represents a memory section with its symbols."""
|
||||
@@ -67,11 +124,27 @@ class MemoryAnalyzer:
|
||||
objdump_path: str | None = None,
|
||||
readelf_path: str | None = None,
|
||||
external_components: set[str] | None = None,
|
||||
idedata: "IDEData | None" = None,
|
||||
):
|
||||
"""Initialize memory analyzer.
|
||||
|
||||
Args:
|
||||
elf_path: Path to ELF file to analyze
|
||||
objdump_path: Path to objdump binary (auto-detected from idedata if not provided)
|
||||
readelf_path: Path to readelf binary (auto-detected from idedata if not provided)
|
||||
external_components: Set of external component names
|
||||
idedata: Optional PlatformIO IDEData object to auto-detect toolchain paths
|
||||
"""
|
||||
self.elf_path = Path(elf_path)
|
||||
if not self.elf_path.exists():
|
||||
raise FileNotFoundError(f"ELF file not found: {elf_path}")
|
||||
|
||||
# Auto-detect toolchain paths from idedata if not provided
|
||||
if idedata is not None and (objdump_path is None or readelf_path is None):
|
||||
objdump_path = objdump_path or idedata.objdump_path
|
||||
readelf_path = readelf_path or idedata.readelf_path
|
||||
_LOGGER.debug("Using toolchain paths from PlatformIO idedata")
|
||||
|
||||
self.objdump_path = objdump_path or "objdump"
|
||||
self.readelf_path = readelf_path or "readelf"
|
||||
self.external_components = external_components or set()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""CLI interface for memory analysis with report generation."""
|
||||
|
||||
from collections import defaultdict
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from . import MemoryAnalyzer
|
||||
@@ -313,51 +312,91 @@ def analyze_elf(
|
||||
def main():
|
||||
"""CLI entrypoint for memory analysis."""
|
||||
if len(sys.argv) < 2:
|
||||
print(
|
||||
"Usage: python -m esphome.analyze_memory <elf_file> [objdump_path] [readelf_path]"
|
||||
)
|
||||
print("\nIf objdump/readelf paths are not provided, you must specify them.")
|
||||
print("\nExample for ESP8266:")
|
||||
print(" python -m esphome.analyze_memory firmware.elf \\")
|
||||
print(
|
||||
" ~/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-objdump \\"
|
||||
)
|
||||
print(
|
||||
" ~/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-readelf"
|
||||
)
|
||||
print("\nExample for ESP32:")
|
||||
print(" python -m esphome.analyze_memory firmware.elf \\")
|
||||
print(
|
||||
" ~/.platformio/packages/toolchain-xtensa-esp-elf/bin/xtensa-esp32-elf-objdump \\"
|
||||
)
|
||||
print(
|
||||
" ~/.platformio/packages/toolchain-xtensa-esp-elf/bin/xtensa-esp32-elf-readelf"
|
||||
)
|
||||
print("\nExample for ESP32-C3 (RISC-V):")
|
||||
print(" python -m esphome.analyze_memory firmware.elf \\")
|
||||
print(
|
||||
" ~/.platformio/packages/toolchain-riscv32-esp/bin/riscv32-esp-elf-objdump \\"
|
||||
)
|
||||
print(
|
||||
" ~/.platformio/packages/toolchain-riscv32-esp/bin/riscv32-esp-elf-readelf"
|
||||
)
|
||||
print("Usage: python -m esphome.analyze_memory <build_directory>")
|
||||
print("\nAnalyze memory usage from an ESPHome build directory.")
|
||||
print("The build directory should contain firmware.elf and idedata will be")
|
||||
print("loaded from ~/.esphome/.internal/idedata/<device>.json")
|
||||
print("\nExamples:")
|
||||
print(" python -m esphome.analyze_memory ~/.esphome/build/my-device")
|
||||
print(" python -m esphome.analyze_memory .esphome/build/my-device")
|
||||
print(" python -m esphome.analyze_memory my-device # Short form")
|
||||
sys.exit(1)
|
||||
|
||||
elf_file = sys.argv[1]
|
||||
objdump_path = sys.argv[2] if len(sys.argv) > 2 else None
|
||||
readelf_path = sys.argv[3] if len(sys.argv) > 3 else None
|
||||
build_dir = sys.argv[1]
|
||||
|
||||
# Load build directory
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from esphome.platformio_api import IDEData
|
||||
|
||||
build_path = Path(build_dir)
|
||||
|
||||
# If no path separator in name, assume it's a device name
|
||||
if "/" not in build_dir and not build_path.is_dir():
|
||||
# Try current directory first
|
||||
cwd_path = Path.cwd() / ".esphome" / "build" / build_dir
|
||||
if cwd_path.is_dir():
|
||||
build_path = cwd_path
|
||||
print(f"Using build directory: {build_path}", file=sys.stderr)
|
||||
else:
|
||||
# Fall back to home directory
|
||||
build_path = Path.home() / ".esphome" / "build" / build_dir
|
||||
print(f"Using build directory: {build_path}", file=sys.stderr)
|
||||
|
||||
if not build_path.is_dir():
|
||||
print(f"Error: {build_path} is not a directory", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Find firmware.elf
|
||||
elf_file = None
|
||||
for elf_candidate in [
|
||||
build_path / "firmware.elf",
|
||||
build_path / ".pioenvs" / build_path.name / "firmware.elf",
|
||||
]:
|
||||
if elf_candidate.exists():
|
||||
elf_file = str(elf_candidate)
|
||||
break
|
||||
|
||||
if not elf_file:
|
||||
print(f"Error: firmware.elf not found in {build_dir}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Find idedata.json - check current directory first, then home
|
||||
device_name = build_path.name
|
||||
idedata_candidates = [
|
||||
Path.cwd() / ".esphome" / "idedata" / f"{device_name}.json",
|
||||
Path.home() / ".esphome" / "idedata" / f"{device_name}.json",
|
||||
]
|
||||
|
||||
idedata = None
|
||||
for idedata_path in idedata_candidates:
|
||||
if idedata_path.exists():
|
||||
try:
|
||||
with open(idedata_path, encoding="utf-8") as f:
|
||||
raw_data = json.load(f)
|
||||
idedata = IDEData(raw_data)
|
||||
print(f"Loaded idedata from: {idedata_path}", file=sys.stderr)
|
||||
break
|
||||
except (json.JSONDecodeError, OSError) as e:
|
||||
print(f"Warning: Failed to load idedata: {e}", file=sys.stderr)
|
||||
|
||||
if not idedata:
|
||||
print(
|
||||
f"Warning: idedata not found (searched {idedata_candidates[0]} and {idedata_candidates[1]})",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
try:
|
||||
report = analyze_elf(elf_file, objdump_path, readelf_path)
|
||||
analyzer = MemoryAnalyzerCLI(elf_file, idedata=idedata)
|
||||
analyzer.analyze()
|
||||
report = analyzer.generate_report()
|
||||
print(report)
|
||||
except (subprocess.CalledProcessError, FileNotFoundError, OSError) as e:
|
||||
except Exception as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
if "readelf" in str(e) or "objdump" in str(e):
|
||||
print(
|
||||
"\nHint: You need to specify the toolchain-specific tools.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
print("See usage above for examples.", file=sys.stderr)
|
||||
import traceback
|
||||
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
|
||||
@@ -412,10 +412,8 @@ def analyze_memory_usage(config: dict[str, Any]) -> None:
|
||||
|
||||
idedata = get_idedata(config)
|
||||
|
||||
# Get paths to tools
|
||||
# Get ELF path
|
||||
elf_path = idedata.firmware_elf_path
|
||||
objdump_path = idedata.objdump_path
|
||||
readelf_path = idedata.readelf_path
|
||||
|
||||
# Debug logging
|
||||
_LOGGER.debug("ELF path from idedata: %s", elf_path)
|
||||
@@ -457,7 +455,10 @@ def analyze_memory_usage(config: dict[str, Any]) -> None:
|
||||
_LOGGER.debug("Detected external components: %s", external_components)
|
||||
|
||||
# Create analyzer and run analysis
|
||||
analyzer = MemoryAnalyzer(elf_path, objdump_path, readelf_path, external_components)
|
||||
# Pass idedata to auto-detect toolchain paths
|
||||
analyzer = MemoryAnalyzer(
|
||||
elf_path, external_components=external_components, idedata=idedata
|
||||
)
|
||||
analyzer.analyze()
|
||||
|
||||
# Generate and print report
|
||||
|
||||
Reference in New Issue
Block a user