mirror of
https://github.com/esphome/esphome.git
synced 2026-03-04 03:38:20 -07:00
[esp32] Wrap printf/vprintf/fprintf to eliminate _vfprintf_r (~11 KB flash)
ESP-IDF SDK components reference fprintf(), printf(), and vprintf() which pull in newlib's _vfprintf_r (~11 KB). This is a separate implementation from _svfprintf_r (used by snprintf/vsnprintf) that handles FILE* stream I/O with buffering and locking. ESPHome replaces the ESP-IDF log handler via esp_log_set_vprintf_(), so the SDK's vprintf() path is dead code at runtime. The fprintf() and printf() calls in SDK components are only in debug/assert paths that are either GC'd or never called. These linker --wrap stubs redirect through vsnprintf() + fwrite(), allowing the linker to dead-code eliminate _vfprintf_r. An escape hatch is provided via enable_full_printf: true in the esp32 advanced config section for external components that need full FILE*-based fprintf.
This commit is contained in:
@@ -952,6 +952,7 @@ CONF_HEAP_IN_IRAM = "heap_in_iram"
|
||||
CONF_LOOP_TASK_STACK_SIZE = "loop_task_stack_size"
|
||||
CONF_USE_FULL_CERTIFICATE_BUNDLE = "use_full_certificate_bundle"
|
||||
CONF_DISABLE_DEBUG_STUBS = "disable_debug_stubs"
|
||||
CONF_ENABLE_FULL_PRINTF = "enable_full_printf"
|
||||
CONF_DISABLE_OCD_AWARE = "disable_ocd_aware"
|
||||
CONF_DISABLE_USB_SERIAL_JTAG_SECONDARY = "disable_usb_serial_jtag_secondary"
|
||||
CONF_DISABLE_DEV_NULL_VFS = "disable_dev_null_vfs"
|
||||
@@ -1126,6 +1127,7 @@ FRAMEWORK_SCHEMA = cv.Schema(
|
||||
cv.Optional(
|
||||
CONF_INCLUDE_BUILTIN_IDF_COMPONENTS, default=[]
|
||||
): cv.ensure_list(cv.string_strict),
|
||||
cv.Optional(CONF_ENABLE_FULL_PRINTF, default=False): cv.boolean,
|
||||
cv.Optional(CONF_DISABLE_DEBUG_STUBS, default=True): cv.boolean,
|
||||
cv.Optional(CONF_DISABLE_OCD_AWARE, default=True): cv.boolean,
|
||||
cv.Optional(
|
||||
@@ -1469,6 +1471,14 @@ async def to_code(config):
|
||||
"_ZSt25__throw_bad_function_callv",
|
||||
]:
|
||||
cg.add_build_flag(f"-Wl,--wrap={mangled}")
|
||||
|
||||
# Wrap FILE*-based printf functions to eliminate newlib's _vfprintf_r
|
||||
# (~11 KB). See printf_stubs.cpp for implementation.
|
||||
if conf[CONF_ADVANCED][CONF_ENABLE_FULL_PRINTF]:
|
||||
cg.add_define("USE_FULL_PRINTF")
|
||||
else:
|
||||
for symbol in ["vprintf", "printf", "fprintf"]:
|
||||
cg.add_build_flag(f"-Wl,--wrap={symbol}")
|
||||
else:
|
||||
cg.add_build_flag("-DUSE_ARDUINO")
|
||||
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO")
|
||||
|
||||
79
esphome/components/esp32/printf_stubs.cpp
Normal file
79
esphome/components/esp32/printf_stubs.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Linker wrap stubs for FILE*-based printf functions.
|
||||
*
|
||||
* ESP-IDF SDK components (gpio driver, ringbuf, log_write) reference
|
||||
* fprintf(), printf(), and vprintf() which pull in newlib's _vfprintf_r
|
||||
* (~11 KB). This is a separate implementation from _svfprintf_r (used by
|
||||
* snprintf/vsnprintf) that handles FILE* stream I/O with buffering and
|
||||
* locking.
|
||||
*
|
||||
* ESPHome replaces the ESP-IDF log handler via esp_log_set_vprintf_(),
|
||||
* so the SDK's vprintf() path is dead code at runtime. The fprintf()
|
||||
* and printf() calls in SDK components are only in debug/assert paths
|
||||
* (gpio_dump_io_configuration, ringbuf diagnostics) that are either
|
||||
* GC'd or never called.
|
||||
*
|
||||
* These stubs redirect through vsnprintf() (which uses _svfprintf_r
|
||||
* already in the binary) and fwrite(), allowing the linker to
|
||||
* dead-code eliminate _vfprintf_r.
|
||||
*
|
||||
* Saves ~11 KB of flash.
|
||||
*
|
||||
* To disable these wraps, set enable_full_printf: true in the esp32
|
||||
* advanced config section.
|
||||
*/
|
||||
|
||||
#if defined(USE_ESP_IDF) && !defined(USE_FULL_PRINTF)
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
|
||||
#include "esp_system.h"
|
||||
|
||||
static constexpr size_t PRINTF_BUFFER_SIZE = 512;
|
||||
|
||||
// NOLINTBEGIN(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
|
||||
extern "C" {
|
||||
|
||||
int __wrap_vprintf(const char *fmt, va_list ap) {
|
||||
char buf[PRINTF_BUFFER_SIZE];
|
||||
int len = vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||
if (len < 0) {
|
||||
return len;
|
||||
}
|
||||
if (static_cast<size_t>(len) >= sizeof(buf)) {
|
||||
// Output was truncated — this should not happen in normal operation.
|
||||
// Abort to make the issue visible rather than silently losing output.
|
||||
esp_system_abort("printf buffer overflow; set enable_full_printf: true in esp32 advanced config");
|
||||
}
|
||||
fwrite(buf, 1, len, stdout);
|
||||
return len;
|
||||
}
|
||||
|
||||
int __wrap_printf(const char *fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
int len = __wrap_vprintf(fmt, ap);
|
||||
va_end(ap);
|
||||
return len;
|
||||
}
|
||||
|
||||
int __wrap_fprintf(FILE *stream, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
char buf[PRINTF_BUFFER_SIZE];
|
||||
int len = vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||
va_end(ap);
|
||||
if (len < 0) {
|
||||
return len;
|
||||
}
|
||||
if (static_cast<size_t>(len) >= sizeof(buf)) {
|
||||
esp_system_abort("fprintf buffer overflow; set enable_full_printf: true in esp32 advanced config");
|
||||
}
|
||||
fwrite(buf, 1, len, stream);
|
||||
return len;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
// NOLINTEND(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
|
||||
|
||||
#endif // USE_ESP_IDF && !USE_FULL_PRINTF
|
||||
Reference in New Issue
Block a user