mirror of
https://github.com/esphome/esphome.git
synced 2026-02-09 01:01:56 +00:00
Compare commits
2 Commits
cswitch_sd
...
web_server
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67fbd31e46 | ||
|
|
7b40e8afcb |
@@ -397,38 +397,47 @@ class MemoryAnalyzer:
|
||||
return pioenvs_dir
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _parse_nm_cswtch_output(
|
||||
output: str,
|
||||
base_dir: Path | None,
|
||||
cswtch_map: dict[str, list[tuple[str, int]]],
|
||||
) -> None:
|
||||
"""Parse nm output for CSWTCH symbols and add to cswtch_map.
|
||||
def _scan_cswtch_in_objects(
|
||||
self, obj_dir: Path
|
||||
) -> dict[str, list[tuple[str, int]]]:
|
||||
"""Scan object files for CSWTCH symbols using a single nm invocation.
|
||||
|
||||
Handles both ``.o`` files and ``.a`` archives.
|
||||
|
||||
nm output formats::
|
||||
|
||||
.o files: /path/file.o:hex_addr hex_size type name
|
||||
.a files: /path/lib.a:member.o:hex_addr hex_size type name
|
||||
|
||||
For ``.o`` files, paths are made relative to *base_dir* when possible.
|
||||
For ``.a`` archives (detected by ``:`` in the file portion), paths are
|
||||
formatted as ``archive_stem/member.o`` (e.g. ``liblwip2-536-feat/lwip-esp.o``).
|
||||
Uses ``nm --print-file-name -S`` on all ``.o`` files at once.
|
||||
Output format: ``/path/to/file.o:address size type name``
|
||||
|
||||
Args:
|
||||
output: Raw stdout from ``nm --print-file-name -S``.
|
||||
base_dir: Base directory for computing relative paths of ``.o`` files.
|
||||
Pass ``None`` when scanning archives outside the build tree.
|
||||
cswtch_map: Dict to populate, mapping ``"CSWTCH$N:size"`` to source list.
|
||||
obj_dir: Directory containing object files (.pioenvs/<env>/)
|
||||
|
||||
Returns:
|
||||
Dict mapping "CSWTCH$NNN:size" to list of (source_file, size) tuples.
|
||||
"""
|
||||
for line in output.splitlines():
|
||||
cswtch_map: dict[str, list[tuple[str, int]]] = defaultdict(list)
|
||||
|
||||
if not self.nm_path:
|
||||
return cswtch_map
|
||||
|
||||
# Find all .o files recursively, sorted for deterministic output
|
||||
obj_files = sorted(obj_dir.rglob("*.o"))
|
||||
if not obj_files:
|
||||
return cswtch_map
|
||||
|
||||
_LOGGER.debug("Scanning %d object files for CSWTCH symbols", len(obj_files))
|
||||
|
||||
# Single nm call with --print-file-name for all object files
|
||||
result = run_tool(
|
||||
[self.nm_path, "--print-file-name", "-S"] + [str(f) for f in obj_files],
|
||||
timeout=30,
|
||||
)
|
||||
if result is None or result.returncode != 0:
|
||||
return cswtch_map
|
||||
|
||||
for line in result.stdout.splitlines():
|
||||
if "CSWTCH$" not in line:
|
||||
continue
|
||||
|
||||
# Split on last ":" that precedes a hex address.
|
||||
# For .o: "filepath.o" : "hex_addr hex_size type name"
|
||||
# For .a: "filepath.a:member.o" : "hex_addr hex_size type name"
|
||||
# nm --print-file-name format: filepath:hex_addr hex_size type name
|
||||
# We split from the right: find the last colon followed by hex digits.
|
||||
parts_after_colon = line.rsplit(":", 1)
|
||||
if len(parts_after_colon) != 2:
|
||||
continue
|
||||
@@ -448,89 +457,16 @@ class MemoryAnalyzer:
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
# Determine readable source path
|
||||
# Use ".a:" to detect archive format (not bare ":" which matches
|
||||
# Windows drive letters like "C:\...\file.o").
|
||||
if ".a:" in file_path:
|
||||
# Archive format: "archive.a:member.o" → "archive_stem/member.o"
|
||||
archive_part, member = file_path.rsplit(":", 1)
|
||||
archive_name = Path(archive_part).stem
|
||||
rel_path = f"{archive_name}/{member}"
|
||||
elif base_dir is not None:
|
||||
try:
|
||||
rel_path = str(Path(file_path).relative_to(base_dir))
|
||||
except ValueError:
|
||||
rel_path = file_path
|
||||
else:
|
||||
# Get relative path from obj_dir for readability
|
||||
try:
|
||||
rel_path = str(Path(file_path).relative_to(obj_dir))
|
||||
except ValueError:
|
||||
rel_path = file_path
|
||||
|
||||
key = f"{sym_name}:{size}"
|
||||
cswtch_map[key].append((rel_path, size))
|
||||
|
||||
def _run_nm_cswtch_scan(
|
||||
self,
|
||||
files: list[Path],
|
||||
base_dir: Path | None,
|
||||
cswtch_map: dict[str, list[tuple[str, int]]],
|
||||
) -> None:
|
||||
"""Run nm on *files* and add any CSWTCH symbols to *cswtch_map*.
|
||||
|
||||
Args:
|
||||
files: Object (``.o``) or archive (``.a``) files to scan.
|
||||
base_dir: Base directory for relative path computation (see
|
||||
:meth:`_parse_nm_cswtch_output`).
|
||||
cswtch_map: Dict to populate with results.
|
||||
"""
|
||||
if not self.nm_path or not files:
|
||||
return
|
||||
|
||||
_LOGGER.debug("Scanning %d files for CSWTCH symbols", len(files))
|
||||
|
||||
result = run_tool(
|
||||
[self.nm_path, "--print-file-name", "-S"] + [str(f) for f in files],
|
||||
timeout=30,
|
||||
)
|
||||
if result is None or result.returncode != 0:
|
||||
_LOGGER.debug(
|
||||
"nm failed or timed out scanning %d files for CSWTCH symbols",
|
||||
len(files),
|
||||
)
|
||||
return
|
||||
|
||||
self._parse_nm_cswtch_output(result.stdout, base_dir, cswtch_map)
|
||||
|
||||
def _scan_cswtch_in_sdk_archives(
|
||||
self, cswtch_map: dict[str, list[tuple[str, int]]]
|
||||
) -> None:
|
||||
"""Scan SDK library archives (.a) for CSWTCH symbols.
|
||||
|
||||
Prebuilt SDK libraries (e.g. lwip, bearssl) are not compiled from source,
|
||||
so their CSWTCH symbols only exist inside ``.a`` archives. Results are
|
||||
merged into *cswtch_map* for keys not already found in ``.o`` files.
|
||||
|
||||
The same source file (e.g. ``lwip-esp.o``) often appears in multiple
|
||||
library variants (``liblwip2-536.a``, ``liblwip2-1460-feat.a``, etc.),
|
||||
so results are deduplicated by member name.
|
||||
"""
|
||||
sdk_dirs = self._find_sdk_library_dirs()
|
||||
if not sdk_dirs:
|
||||
return
|
||||
|
||||
sdk_archives = sorted(a for sdk_dir in sdk_dirs for a in sdk_dir.glob("*.a"))
|
||||
|
||||
sdk_map: dict[str, list[tuple[str, int]]] = defaultdict(list)
|
||||
self._run_nm_cswtch_scan(sdk_archives, None, sdk_map)
|
||||
|
||||
# Merge SDK results, deduplicating by member name.
|
||||
for key, sources in sdk_map.items():
|
||||
if key in cswtch_map:
|
||||
continue
|
||||
seen: dict[str, tuple[str, int]] = {}
|
||||
for path, sz in sources:
|
||||
member = Path(path).name
|
||||
if member not in seen:
|
||||
seen[member] = (path, sz)
|
||||
cswtch_map[key] = list(seen.values())
|
||||
return cswtch_map
|
||||
|
||||
def _source_file_to_component(self, source_file: str) -> str:
|
||||
"""Map a source object file path to its component name.
|
||||
@@ -569,25 +505,17 @@ class MemoryAnalyzer:
|
||||
|
||||
CSWTCH symbols are compiler-generated lookup tables for switch statements.
|
||||
They are local symbols, so the same name can appear in different object files.
|
||||
This method scans .o files and SDK archives to attribute them to their
|
||||
source components.
|
||||
This method scans .o files to attribute them to their source components.
|
||||
"""
|
||||
obj_dir = self._find_object_files_dir()
|
||||
if obj_dir is None:
|
||||
_LOGGER.debug("No object files directory found, skipping CSWTCH analysis")
|
||||
return
|
||||
|
||||
# Scan build-dir object files for CSWTCH symbols
|
||||
cswtch_map: dict[str, list[tuple[str, int]]] = defaultdict(list)
|
||||
self._run_nm_cswtch_scan(sorted(obj_dir.rglob("*.o")), obj_dir, cswtch_map)
|
||||
|
||||
# Also scan SDK library archives (.a) for CSWTCH symbols.
|
||||
# Prebuilt SDK libraries (e.g. lwip, bearssl) are not compiled from source
|
||||
# so their symbols only exist inside .a archives, not as loose .o files.
|
||||
self._scan_cswtch_in_sdk_archives(cswtch_map)
|
||||
|
||||
# Scan object files for CSWTCH symbols
|
||||
cswtch_map = self._scan_cswtch_in_objects(obj_dir)
|
||||
if not cswtch_map:
|
||||
_LOGGER.debug("No CSWTCH symbols found in object files or SDK archives")
|
||||
_LOGGER.debug("No CSWTCH symbols found in object files")
|
||||
return
|
||||
|
||||
# Collect CSWTCH symbols from the ELF (already parsed in sections)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
namespace esphome::epaper_spi {
|
||||
|
||||
class EPaperSpectraE6 : public EPaperBase {
|
||||
class EPaperSpectraE6 final : public EPaperBase {
|
||||
public:
|
||||
EPaperSpectraE6(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
|
||||
size_t init_sequence_length)
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace esphome::epaper_spi {
|
||||
/**
|
||||
* An epaper display that needs LUTs to be sent to it.
|
||||
*/
|
||||
class EpaperWaveshare : public EPaperMono {
|
||||
class EpaperWaveshare final : public EPaperMono {
|
||||
public:
|
||||
EpaperWaveshare(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
|
||||
size_t init_sequence_length, const uint8_t *lut, size_t lut_length, const uint8_t *partial_lut,
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
#include <cstring>
|
||||
#include "multipart_parser.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace web_server_idf {
|
||||
namespace esphome::web_server_idf {
|
||||
|
||||
static const char *const TAG = "multipart";
|
||||
|
||||
@@ -249,6 +248,5 @@ std::string str_trim(const std::string &str) {
|
||||
return str.substr(start, end - start + 1);
|
||||
}
|
||||
|
||||
} // namespace web_server_idf
|
||||
} // namespace esphome
|
||||
} // namespace esphome::web_server_idf
|
||||
#endif // defined(USE_ESP32) && defined(USE_WEBSERVER_OTA)
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace esphome {
|
||||
namespace web_server_idf {
|
||||
namespace esphome::web_server_idf {
|
||||
|
||||
// Wrapper around zorxx/multipart-parser for ESP-IDF OTA uploads
|
||||
class MultipartReader {
|
||||
@@ -81,6 +80,5 @@ bool parse_multipart_boundary(const char *content_type, const char **boundary_st
|
||||
// Trim whitespace from both ends of a string
|
||||
std::string str_trim(const std::string &str);
|
||||
|
||||
} // namespace web_server_idf
|
||||
} // namespace esphome
|
||||
} // namespace esphome::web_server_idf
|
||||
#endif // defined(USE_ESP32) && defined(USE_WEBSERVER_OTA)
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace web_server_idf {
|
||||
namespace esphome::web_server_idf {
|
||||
|
||||
static const char *const TAG = "web_server_idf_utils";
|
||||
|
||||
@@ -119,6 +118,5 @@ const char *stristr(const char *haystack, const char *needle) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace web_server_idf
|
||||
} // namespace esphome
|
||||
} // namespace esphome::web_server_idf
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
#include <string>
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace web_server_idf {
|
||||
namespace esphome::web_server_idf {
|
||||
|
||||
/// Decode URL-encoded string in-place (e.g., %20 -> space, + -> space)
|
||||
/// Returns the new length of the decoded string
|
||||
@@ -29,6 +28,5 @@ bool str_ncmp_ci(const char *s1, const char *s2, size_t n);
|
||||
// Case-insensitive string search (like strstr but case-insensitive)
|
||||
const char *stristr(const char *haystack, const char *needle);
|
||||
|
||||
} // namespace web_server_idf
|
||||
} // namespace esphome
|
||||
} // namespace esphome::web_server_idf
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -30,8 +30,7 @@
|
||||
#include <cerrno>
|
||||
#include <sys/socket.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace web_server_idf {
|
||||
namespace esphome::web_server_idf {
|
||||
|
||||
#ifndef HTTPD_409
|
||||
#define HTTPD_409 "409 Conflict"
|
||||
@@ -897,7 +896,6 @@ esp_err_t AsyncWebServer::handle_multipart_upload_(httpd_req_t *r, const char *c
|
||||
}
|
||||
#endif // USE_WEBSERVER_OTA
|
||||
|
||||
} // namespace web_server_idf
|
||||
} // namespace esphome
|
||||
} // namespace esphome::web_server_idf
|
||||
|
||||
#endif // !defined(USE_ESP32)
|
||||
|
||||
Reference in New Issue
Block a user