diff --git a/esphome/analyze_memory.py b/esphome/analyze_memory.py index 91b3d3228b..45d73ae920 100644 --- a/esphome/analyze_memory.py +++ b/esphome/analyze_memory.py @@ -12,6 +12,397 @@ _LOGGER = logging.getLogger(__name__) # Pattern to extract ESPHome component namespaces dynamically ESPHOME_COMPONENT_PATTERN = re.compile(r"esphome::([a-zA-Z0-9_]+)::") +# Component identification rules +# Symbol patterns: patterns found in raw symbol names +SYMBOL_PATTERNS = { + "freertos": [ + "vTask", + "xTask", + "xQueue", + "pvPort", + "vPort", + "uxTask", + "pcTask", + "prvTimerTask", + "prvAddNewTaskToReadyList", + "pxReadyTasksLists", + ], + "xtensa": ["xt_", "_xt_"], + "heap": ["heap_", "multi_heap"], + "spi_flash": ["spi_flash"], + "rtc": ["rtc_"], + "gpio_driver": ["gpio_", "pins"], + "uart_driver": ["uart", "_uart", "UART"], + "timer": ["timer_", "esp_timer"], + "peripherals": ["periph_", "periman"], + "network_stack": [ + "vj_compress", + "raw_sendto", + "raw_input", + "etharp_", + "icmp_input", + "socket_ipv6", + "ip_napt", + ], + "ipv6_stack": ["nd6_", "ip6_", "mld6_"], + "wifi_stack": [ + "ieee80211", + "hostap", + "sta_", + "ap_", + "scan_", + "wifi_", + "wpa_", + "wps_", + "esp_wifi", + "cnx_", + "wpa3_", + "sae_", + "wDev_", + "ic_", + "mac_", + "esf_buf", + "gWpaSm", + "sm_WPA", + "eapol_", + "owe_", + ], + "bluetooth": ["bt_", "ble_", "l2c_", "gatt_", "gap_", "hci_", "BT_init"], + "wifi_bt_coex": ["coex"], + "bluetooth_rom": ["r_ble", "r_lld", "r_llc", "r_llm"], + "bluedroid_bt": ["bluedroid", "btc_", "bta_", "btm_", "btu_"], + "crypto_math": [ + "ecp_", + "bignum_", + "mpi_", + "sswu", + "modp", + "dragonfly_", + "gcm_mult", + "__multiply", + ], + "hw_crypto": ["esp_aes", "esp_sha", "esp_rsa", "esp_bignum", "esp_mpi"], + "libc": [ + "printf", + "scanf", + "malloc", + "free", + "memcpy", + "memset", + "strcpy", + "strlen", + "_dtoa", + "_fopen", + "__sfvwrite_r", + "qsort", + ], + "string_ops": ["strcmp", "strncmp", "strchr", "strstr", "strtok", "strdup"], + "memory_alloc": ["malloc", "calloc", "realloc", "free", "_sbrk"], + "file_io": ["fread", "fwrite", "fopen", "fclose", "fseek", "ftell", "fflush"], + "string_formatting": [ + "snprintf", + "vsnprintf", + "sprintf", + "vsprintf", + "sscanf", + "vsscanf", + ], + "cpp_anonymous": ["_GLOBAL__N_"], + "cpp_runtime": ["__cxx", "_ZN", "_ZL", "_ZSt", "__gxx_personality"], + "exception_handling": ["__cxa_", "_Unwind_", "__gcc_personality", "uw_frame_state"], + "static_init": ["_GLOBAL__sub_I_"], + "mdns_lib": ["mdns"], + "phy_radio": [ + "phy_", + "rf_", + "chip_", + "register_chipv7", + "pbus_", + "bb_", + "fe_", + "rfcal_", + "ram_rfcal", + "tx_pwctrl", + "rx_chan", + "set_rx_gain", + "set_chan", + "agc_reg", + "ram_txiq", + "ram_txdc", + "ram_gen_rx_gain", + ], + "wifi_phy_pp": ["pp_", "ppT", "ppR", "ppP", "ppInstall", "ppCalTxAMPDULength"], + "wifi_lmac": ["lmac"], + "wifi_device": ["wdev", "wDev_"], + "power_mgmt": [ + "pm_", + "sleep", + "rtc_sleep", + "light_sleep", + "deep_sleep", + "power_down", + "g_pm", + ], + "memory_mgmt": ["mem_", "memory_", "tlsf_", "memp_"], + "hal_layer": ["hal_"], + "clock_mgmt": [ + "clk_", + "clock_", + "rtc_clk", + "apb_", + "cpu_freq", + "setCpuFrequencyMhz", + ], + "cache_mgmt": ["cache"], + "flash_ops": ["flash", "image_load"], + "interrupt_handlers": [ + "isr", + "interrupt", + "intr_", + "exc_", + "exception", + "port_IntStack", + ], + "wrapper_functions": ["_wrapper"], + "error_handling": ["panic", "abort", "assert", "error_", "fault"], + "authentication": ["auth"], + "ppp_protocol": ["ppp", "ipcp_", "lcp_", "chap_"], + "dhcp": ["dhcp", "handle_dhcp"], + "ethernet_phy": [ + "emac_", + "eth_phy_", + "phy_tlk110", + "phy_lan87", + "phy_ip101", + "phy_rtl", + "phy_dp83", + "phy_ksz", + ], + "threading": ["pthread_", "thread_", "_task_"], + "pthread": ["pthread"], + "synchronization": ["mutex", "semaphore", "spinlock", "portMUX"], + "math_lib": [ + "sin", + "cos", + "tan", + "sqrt", + "pow", + "exp", + "log", + "atan", + "asin", + "acos", + "floor", + "ceil", + "fabs", + "round", + ], + "random": ["rand", "random", "rng_", "prng"], + "time_lib": [ + "time", + "clock", + "gettimeofday", + "settimeofday", + "localtime", + "gmtime", + "mktime", + "strftime", + ], + "console_io": ["console_", "uart_tx", "uart_rx", "puts", "putchar", "getchar"], + "rom_functions": ["r_", "rom_"], + "compiler_runtime": [ + "__divdi3", + "__udivdi3", + "__moddi3", + "__muldi3", + "__ashldi3", + "__ashrdi3", + "__lshrdi3", + "__cmpdi2", + "__fixdfdi", + "__floatdidf", + ], + "libgcc": ["libgcc", "_divdi3", "_udivdi3"], + "boot_startup": ["boot", "start_cpu", "call_start", "startup", "bootloader"], + "bootloader": ["bootloader_", "esp_bootloader"], + "app_framework": ["app_", "initArduino", "setup", "loop"], + "weak_symbols": ["__weak_"], + "compiler_builtins": ["__builtin_"], + "vfs": ["vfs_", "VFS"], + "esp32_sdk": ["esp32_", "esp32c", "esp32s"], + "usb": ["usb_", "USB", "cdc_", "CDC"], + "i2c_driver": ["i2c_", "I2C"], + "i2s_driver": ["i2s_", "I2S"], + "spi_driver": ["spi_", "SPI"], + "adc_driver": ["adc_", "ADC"], + "dac_driver": ["dac_", "DAC"], + "touch_driver": ["touch_", "TOUCH"], + "pwm_driver": ["pwm_", "PWM", "ledc_", "LEDC"], + "rmt_driver": ["rmt_", "RMT"], + "pcnt_driver": ["pcnt_", "PCNT"], + "can_driver": ["can_", "CAN", "twai_", "TWAI"], + "sdmmc_driver": ["sdmmc_", "SDMMC", "sdcard", "sd_card"], + "temp_sensor": ["temp_sensor", "tsens_"], + "watchdog": ["wdt_", "WDT", "watchdog"], + "brownout": ["brownout", "bod_"], + "ulp": ["ulp_", "ULP"], + "psram": ["psram", "PSRAM", "spiram", "SPIRAM"], + "efuse": ["efuse", "EFUSE"], + "partition": ["partition", "esp_partition"], + "esp_event": ["esp_event", "event_loop", "event_callback"], + "esp_console": ["esp_console", "console_"], + "chip_specific": ["chip_", "esp_chip"], + "esp_system_utils": ["esp_system", "esp_hw", "esp_clk", "esp_sleep"], + "ipc": ["esp_ipc", "ipc_"], + "wifi_config": [ + "g_cnxMgr", + "gChmCxt", + "g_ic", + "TxRxCxt", + "s_dp", + "s_ni", + "s_reg_dump", + "packet$", + "d_mult_table", + "K", + "fcstab", + ], + "smartconfig": ["sc_ack_send"], + "rc_calibration": ["rc_cal", "rcUpdate"], + "noise_floor": ["noise_check"], + "rf_calibration": [ + "set_rx_sense", + "set_rx_gain_cal", + "set_chan_dig_gain", + "tx_pwctrl_init_cal", + "rfcal_txiq", + "set_tx_gain_table", + "correct_rfpll_offset", + "pll_correct_dcap", + "txiq_cal_init", + "pwdet_sar", + "rx_11b_opt", + ], + "wifi_crypto": [ + "pk_use_ecparams", + "process_segments", + "ccmp_", + "rc4_", + "aria_", + "mgf_mask", + "dh_group", + ], + "radio_control": ["fsm_input", "fsm_sconfreq"], + "pbuf": [ + "pbuf_", + "ppSearchTxframe", + "ppMapWaitTxq", + "ppFillAMPDUBar", + "ppCheckTxConnTrafficIdle", + ], + "ppTask": ["ppCalTkipMic"], + "event_group": ["xEventGroup"], + "ringbuffer": ["xRingbuffer", "prvSend", "prvReceive", "prvCopy"], + "provisioning": ["prov_"], + "scan": ["gScanStruct"], + "port": ["xPort"], + "elf_loader": ["elf_add", "process_image", "read_encoded"], + "socket_api": [ + "sockets", + "netconn_", + "accept_function", + "recv_raw", + "socket_ipv4_multicast", + "socket_ipv6_multicast", + ], + "igmp": ["igmp_"], + "icmp6": ["icmp6_"], + "arp": ["arp_table"], + "ampdu": ["ampdu_", "rcAmpdu", "trc_onAmpduOp"], + "ieee802_11": ["ieee802_11_"], + "rate_control": ["rssi_margin", "rcGetSched", "get_rate_fcc_index"], + "nan": ["nan_dp_"], + "channel_mgmt": ["chm_init", "chm_set_current_channel"], + "trace": ["trc_init"], + "country_code": ["country_info"], + "multicore": ["do_multicore_settings"], + "Update_lib": ["Update"], + "stdio": [ + "__sf", + "__sflush_r", + "__srefill_r", + "_impure_data", + "_reclaim_reent", + "_open_r", + ], + "strncpy_ops": ["strncpy"], + "math_internal": ["__mdiff", "__lshift", "__mprec_tens", "quorem"], + "character_class": ["__chclass"], + "camellia": ["camellia_"], + "crypto_tables": ["FSb", "FSb2", "FSb3", "FSb4"], + "event_buffer": ["g_eb_list_desc", "eb_space"], + "base_node": ["base_node_"], + "file_descriptor": ["s_fd_table"], + "tx_delay": ["tx_delay_cfg"], + "deinit": ["deinit_functions"], + "lcp_echo": ["LcpEchoCheck"], + "raw_api": ["raw_bind", "raw_connect"], +} + +# Demangled patterns: patterns found in demangled C++ names +DEMANGLED_PATTERNS = { + "gpio_driver": ["GPIO"], + "uart_driver": ["UART"], + "network_stack": [ + "lwip", + "tcp", + "udp", + "ip4", + "ip6", + "dhcp", + "dns", + "netif", + "ethernet", + "ppp", + "slip", + ], + "wifi_stack": ["NetworkInterface"], + "nimble_bt": [ + "nimble", + "NimBLE", + "ble_hs", + "ble_gap", + "ble_gatt", + "ble_att", + "ble_l2cap", + "ble_sm", + ], + "crypto": ["mbedtls", "crypto", "sha", "aes", "rsa", "ecc", "tls", "ssl"], + "cpp_stdlib": ["std::", "__gnu_cxx::", "__cxxabiv"], + "static_init": ["__static_initialization"], + "rtti": ["__type_info", "__class_type_info"], + "web_server_lib": ["AsyncWebServer", "AsyncWebHandler", "WebServer"], + "async_tcp": ["AsyncClient", "AsyncServer"], + "mdns_lib": ["mdns"], + "json_lib": [ + "ArduinoJson", + "JsonDocument", + "JsonArray", + "JsonObject", + "deserialize", + "serialize", + ], + "http_lib": ["HTTP", "http_", "Request", "Response", "Uri", "WebSocket"], + "logging": ["log", "Log", "print", "Print", "diag_"], + "authentication": ["checkDigestAuthentication"], + "libgcc": ["libgcc"], + "esp_system": ["esp_", "ESP"], + "arduino": ["arduino"], + "nvs": ["nvs_"], + "filesystem": ["spiffs", "vfs"], + "libc": ["newlib"], +} + # Get the list of actual ESPHome components by scanning the components directory def get_esphome_components(): @@ -88,6 +479,7 @@ class MemoryAnalyzer: lambda: ComponentMemory("") ) self._demangle_cache: dict[str, str] = {} + self._uncategorized_symbols: list[tuple[str, str, int]] = [] def analyze(self) -> dict[str, ComponentMemory]: """Analyze the ELF file and return component memory usage.""" @@ -235,13 +627,17 @@ class MemoryAnalyzer: elif section_name == ".bss": comp_mem.bss_size += size + # Track uncategorized symbols + if component == "other" and size > 0: + demangled = self._demangle_symbol(symbol_name) + self._uncategorized_symbols.append((symbol_name, demangled, size)) + def _identify_component(self, symbol_name: str) -> str: """Identify which component a symbol belongs to.""" # Demangle C++ names if needed demangled = self._demangle_symbol(symbol_name) - # Check for ESPHome component namespaces dynamically - # Pattern: esphome::component_name:: (with trailing ::) + # Check for ESPHome component namespaces first match = ESPHOME_COMPONENT_PATTERN.search(demangled) if match: component_name = match.group(1) @@ -255,448 +651,64 @@ class MemoryAnalyzer: return "[esphome]core" # Check for esphome core namespace (no component namespace) - # This catches esphome::ClassName or esphome::function_name if "esphome::" in demangled: return "[esphome]core" - # Check for web server related code + # Check against symbol patterns + for component, patterns in SYMBOL_PATTERNS.items(): + if any(pattern in symbol_name for pattern in patterns): + return component + + # Check against demangled patterns + for component, patterns in DEMANGLED_PATTERNS.items(): + if any(pattern in demangled for pattern in patterns): + return component + + # Special cases that need more complex logic + + # ROM functions starting with r_ or rom_ + if symbol_name.startswith("r_") or symbol_name.startswith("rom_"): + return "rom_functions" + + # Math functions with short names + if len(symbol_name) < 20 and symbol_name in [ + "sin", + "cos", + "tan", + "sqrt", + "pow", + "exp", + "log", + "atan", + "asin", + "acos", + "floor", + "ceil", + "fabs", + "round", + ]: + return "math_lib" + + # Check if spi_flash vs spi_driver + if "spi_" in symbol_name or "SPI" in symbol_name: + if "spi_flash" in symbol_name: + return "spi_flash" + else: + return "spi_driver" + + # ESP OTA framework (exclude esphome OTA) if ( - "AsyncWebServer" in demangled - or "AsyncWebHandler" in demangled - or "WebServer" in demangled - ): - return "web_server_lib" - elif "AsyncClient" in demangled or "AsyncServer" in demangled: - return "async_tcp" - - # Check for FreeRTOS/ESP-IDF components - if any( - prefix in symbol_name - for prefix in [ - "vTask", - "xTask", - "xQueue", - "pvPort", - "vPort", - "uxTask", - "pcTask", - ] - ): - return "freertos" - elif "xt_" in symbol_name or "_xt_" in symbol_name: - return "xtensa" - elif "heap_" in symbol_name or "multi_heap" in demangled: - return "heap" - elif "spi_flash" in symbol_name: - return "spi_flash" - elif "rtc_" in symbol_name: - return "rtc" - elif "gpio_" in symbol_name or "GPIO" in demangled: - return "gpio_driver" - elif "uart_" in symbol_name or "UART" in demangled: - return "uart_driver" - elif "timer_" in symbol_name or "esp_timer" in symbol_name: - return "timer" - elif "periph_" in symbol_name: - return "peripherals" - - # C++ standard library - if any(ns in demangled for ns in ["std::", "__gnu_cxx::", "__cxxabiv"]): - return "cpp_stdlib" - elif "_GLOBAL__N_" in symbol_name: - return "cpp_anonymous" - - # Platform/system code - if "esp_" in demangled or "ESP" in demangled: - return "esp_system" - elif "app_" in symbol_name: - return "app_framework" - elif "arduino" in demangled.lower(): - return "arduino" - - # Network stack components - if any( - net in demangled - for net in [ - "lwip", - "tcp", - "udp", - "ip4", - "ip6", - "dhcp", - "dns", - "netif", - "ethernet", - "ppp", - "slip", - ] - ): - return "network_stack" - elif "vj_compress" in symbol_name: # Van Jacobson TCP compression - return "network_stack" - - # WiFi/802.11 stack - if any( - wifi in symbol_name - for wifi in [ - "ieee80211", - "hostap", - "sta_", - "ap_", - "scan_", - "wifi_", - "wpa_", - "wps_", - "esp_wifi", - ] - ): - return "wifi_stack" - elif "NetworkInterface" in demangled: - return "wifi_stack" - - # mDNS specific - if ( - "mdns" in symbol_name or "mdns" in demangled + "esp_ota" in symbol_name or "ota_" in symbol_name ) and "esphome" not in demangled: - return "mdns_lib" + return "esp_ota" - # Cryptography - if any( - crypto in demangled - for crypto in [ - "mbedtls", - "crypto", - "sha", - "aes", - "rsa", - "ecc", - "tls", - "ssl", - ] - ): - return "crypto" - - # C library functions - if any( - libc in symbol_name - for libc in [ - "printf", - "scanf", - "malloc", - "free", - "memcpy", - "memset", - "strcpy", - "strlen", - "_dtoa", - "_fopen", - ] - ): - return "libc" - elif symbol_name.startswith("_") and symbol_name[1:].replace("_r", "").replace( + # libc special printf variants + if symbol_name.startswith("_") and symbol_name[1:].replace("_r", "").replace( "v", "" ).replace("s", "") in ["printf", "fprintf", "sprintf", "scanf"]: return "libc" - # IPv6 specific - if "nd6_" in symbol_name or "ip6_" in symbol_name: - return "ipv6_stack" - - # Other system libraries - if "nvs_" in demangled: - return "nvs" - elif "spiffs" in demangled or "vfs" in demangled: - return "filesystem" - elif "newlib" in demangled: - return "libc" - elif ( - "libgcc" in demangled - or "_divdi3" in symbol_name - or "_udivdi3" in symbol_name - ): - return "libgcc" - - # Boot and startup - if any( - boot in symbol_name - for boot in ["boot", "start_cpu", "call_start", "startup", "bootloader"] - ): - return "boot_startup" - - # PHY/Radio layer - if any( - phy in symbol_name - for phy in [ - "phy_", - "rf_", - "chip_", - "register_chipv7", - "pbus_", - "bb_", - "fe_", - ] - ): - return "phy_radio" - elif any(pp in symbol_name for pp in ["pp_", "ppT", "ppR", "ppP", "ppInstall"]): - return "wifi_phy_pp" - elif "lmac" in symbol_name: - return "wifi_lmac" - elif "wdev" in symbol_name: - return "wifi_device" - - # Bluetooth/BLE - if any( - bt in symbol_name for bt in ["bt_", "ble_", "l2c_", "gatt_", "gap_", "hci_"] - ): - return "bluetooth" - elif "coex" in symbol_name: - return "wifi_bt_coex" - elif "r_" in symbol_name and any( - bt in symbol_name for bt in ["ble", "lld", "llc", "llm"] - ): - # ROM bluetooth functions - return "bluetooth_rom" - - # Power management - if any( - pm in symbol_name - for pm in [ - "pm_", - "sleep", - "rtc_sleep", - "light_sleep", - "deep_sleep", - "power_down", - ] - ): - return "power_mgmt" - - # Logging and diagnostics - if any(log in demangled for log in ["log", "Log", "print", "Print", "diag_"]): - return "logging" - - # Memory management - if any(mem in symbol_name for mem in ["mem_", "memory_", "tlsf_", "memp_"]): - return "memory_mgmt" - - # HAL (Hardware Abstraction Layer) - if "hal_" in symbol_name: - return "hal_layer" - - # Clock management - if any( - clk in symbol_name - for clk in ["clk_", "clock_", "rtc_clk", "apb_", "cpu_freq"] - ): - return "clock_mgmt" - - # Cache management - if "cache" in symbol_name: - return "cache_mgmt" - - # Flash operations - if "flash" in symbol_name and "spi" not in symbol_name: - return "flash_ops" - - # Interrupt/Exception handling - if any( - isr in symbol_name - for isr in ["isr", "interrupt", "intr_", "exc_", "exception"] - ): - return "interrupt_handlers" - elif "_wrapper" in symbol_name: - return "wrapper_functions" - - # Error handling - if any( - err in symbol_name - for err in ["panic", "abort", "assert", "error_", "fault"] - ): - return "error_handling" - - # ECC/Crypto math - if any( - ecc in symbol_name for ecc in ["ecp_", "bignum_", "mpi_", "sswu", "modp"] - ): - return "crypto_math" - - # Authentication - if "checkDigestAuthentication" in demangled or "auth" in symbol_name.lower(): - return "authentication" - - # PPP protocol - if any(ppp in symbol_name for ppp in ["ppp", "ipcp_", "lcp_", "chap_"]): - return "ppp_protocol" - - # DHCP - if "dhcp" in symbol_name or "handle_dhcp" in symbol_name: - return "dhcp" - - # JSON parsing - if any( - json in demangled - for json in [ - "ArduinoJson", - "JsonDocument", - "JsonArray", - "JsonObject", - "deserialize", - "serialize", - ] - ): - return "json_lib" - - # HTTP/Web related - if any( - http in demangled - for http in ["HTTP", "http_", "Request", "Response", "Uri", "WebSocket"] - ): - return "http_lib" - - # Ethernet PHY drivers - if any( - eth in symbol_name - for eth in [ - "emac_", - "eth_phy_", - "phy_tlk110", - "phy_lan87", - "phy_ip101", - "phy_rtl", - "phy_dp83", - "phy_ksz", - ] - ): - return "ethernet_phy" - - # Task/Thread management - if any(task in symbol_name for task in ["pthread_", "thread_", "_task_"]): - return "threading" - - # Mutex/Semaphore - if any( - sync in symbol_name - for sync in ["mutex", "semaphore", "spinlock", "portMUX"] - ): - return "synchronization" - - # String formatting - if any( - fmt in symbol_name - for fmt in [ - "snprintf", - "vsnprintf", - "sprintf", - "vsprintf", - "sscanf", - "vsscanf", - ] - ): - return "string_formatting" - - # Math functions - if ( - any( - math in symbol_name - for math in [ - "sin", - "cos", - "tan", - "sqrt", - "pow", - "exp", - "log", - "atan", - "asin", - "acos", - "floor", - "ceil", - "fabs", - "round", - ] - ) - and len(symbol_name) < 20 - ): - return "math_lib" - - # Random number generation - if any(rng in symbol_name for rng in ["rand", "random", "rng_", "prng"]): - return "random" - - # Time functions - if any( - time in symbol_name - for time in [ - "time", - "clock", - "gettimeofday", - "settimeofday", - "localtime", - "gmtime", - "mktime", - "strftime", - ] - ): - return "time_lib" - - # Console/UART output - if any( - console in symbol_name - for console in [ - "console_", - "uart_tx", - "uart_rx", - "puts", - "putchar", - "getchar", - ] - ): - return "console_io" - - # ROM functions - if symbol_name.startswith("r_") or symbol_name.startswith("rom_"): - return "rom_functions" - - # Compiler generated code - if any( - gen in symbol_name - for gen in [ - "__divdi3", - "__udivdi3", - "__moddi3", - "__muldi3", - "__ashldi3", - "__ashrdi3", - "__lshrdi3", - "__cmpdi2", - "__fixdfdi", - "__floatdidf", - ] - ): - return "compiler_runtime" - - # Exception handling - if any( - exc in symbol_name for exc in ["__cxa_", "_Unwind_", "__gcc_personality"] - ): - return "exception_handling" - - # RTTI (Run-Time Type Information) - if "__type_info" in demangled or "__class_type_info" in demangled: - return "rtti" - - # Static initializers - if "_GLOBAL__sub_I_" in symbol_name or "__static_initialization" in demangled: - return "static_init" - - # Weak symbols - if "__weak_" in symbol_name: - return "weak_symbols" - - # Compiler builtins - if "__builtin_" in symbol_name: - return "compiler_builtins" - + # Track uncategorized symbols for analysis return "other" def _batch_demangle_symbols(self, symbols: list[str]) -> None: @@ -760,7 +772,7 @@ class MemoryAnalyzer: # Main table lines.append( - f"{'Component':<28} | {'Flash (text)':<12} | {'Flash (data)':<12} | {'RAM (data)':<10} | {'RAM (bss)':<10} | {'Total Flash':<12} | {'Total RAM':<10}" + f"{'Component':<28} | {'Flash (text)':>12} | {'Flash (data)':>12} | {'RAM (data)':>10} | {'RAM (bss)':>10} | {'Total Flash':>12} | {'Total RAM':>10}" ) lines.append( "-" * 28 @@ -860,6 +872,39 @@ class MemoryAnalyzer: } return json.dumps(data, indent=2) + def dump_uncategorized_symbols(self, output_file: str | None = None) -> None: + """Dump uncategorized symbols for analysis.""" + # Sort by size descending + sorted_symbols = sorted( + self._uncategorized_symbols, key=lambda x: x[2], reverse=True + ) + + lines = ["Uncategorized Symbols Analysis", "=" * 80] + lines.append(f"Total uncategorized symbols: {len(sorted_symbols)}") + lines.append( + f"Total uncategorized size: {sum(s[2] for s in sorted_symbols):,} bytes" + ) + lines.append("") + lines.append(f"{'Size':>10} | {'Symbol':<60} | Demangled") + lines.append("-" * 10 + "-+-" + "-" * 60 + "-+-" + "-" * 40) + + for symbol, demangled, size in sorted_symbols[:100]: # Top 100 + if symbol != demangled: + lines.append(f"{size:>10,} | {symbol[:60]:<60} | {demangled[:100]}") + else: + lines.append(f"{size:>10,} | {symbol[:60]:<60} | [not demangled]") + + if len(sorted_symbols) > 100: + lines.append(f"\n... and {len(sorted_symbols) - 100} more symbols") + + content = "\n".join(lines) + + if output_file: + with open(output_file, "w") as f: + f.write(content) + else: + print(content) + def analyze_elf( elf_path: str,