/* * Linker wrap stubs for FILE*-based printf functions. * * ESP-IDF SDK components (gpio driver, ringbuf, log_write) reference * fprintf(), printf(), vprintf(), and vfprintf() which pull in the full * printf implementation (~11 KB on newlib's _vfprintf_r, ~2.8 KB on * picolibc's vfprintf). This is a separate implementation from the one * 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. Crash backtraces and panic output are * unaffected; they use esp_rom_printf() which is a ROM function * and does not go through libc. * * On picolibc (default for IDF >= 5 on RISC-V, IDF >= 6 everywhere) we * route output through a stack-allocated cookie FILE that forwards each * byte to the real target stream via fputc(). Picolibc's tinystdio * vfprintf walks the FILE::put callback one character at a time, so this * costs ~32 bytes of stack for the cookie struct vs. a 512-byte format * buffer. The buffered path overflows the loopTask stack on IDF 6. * * On newlib (IDF <= 5 on Xtensa) we keep the original snprintf-then-fwrite * path because that loopTask stack budget has plenty of headroom for the * 512-byte buffer; the picolibc-only crash above does not affect it. * * Saves ~11 KB of flash on newlib, ~2.8 KB on picolibc. * * 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 #include #ifndef __PICOLIBC__ #include "esp_system.h" #endif namespace esphome::esp32 {} // NOLINTBEGIN(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming) extern "C" { #ifdef __PICOLIBC__ #include #include extern int __real_vfprintf(FILE *stream, const char *fmt, va_list ap); namespace { struct CookieFile { FILE base; FILE *target; }; // cookie_put() recovers CookieFile* from FILE* via reinterpret_cast, which is // only well-defined when FILE is the first member at offset 0 and CookieFile // is standard-layout. static_assert(offsetof(CookieFile, base) == 0, "FILE must be the first member of CookieFile"); static_assert(std::is_standard_layout::value, "CookieFile must be standard-layout"); int cookie_put(char c, FILE *stream) { auto *cookie = reinterpret_cast(stream); return fputc(static_cast(c), cookie->target); } const FILE COOKIE_FILE_TEMPLATE = FDEV_SETUP_STREAM(cookie_put, nullptr, nullptr, _FDEV_SETUP_WRITE); } // namespace int __wrap_vfprintf(FILE *stream, const char *fmt, va_list ap) { CookieFile cookie; cookie.base = COOKIE_FILE_TEMPLATE; cookie.target = stream; return __real_vfprintf(&cookie.base, fmt, ap); } int __wrap_vprintf(const char *fmt, va_list ap) { return __wrap_vfprintf(stdout, fmt, ap); } #else // !__PICOLIBC__ static constexpr size_t PRINTF_BUFFER_SIZE = 512; // These stubs are essentially dead code at runtime — ESPHome replaces the // ESP-IDF log handler, and the SDK's printf/fprintf calls only exist in // debug/assert paths that are never reached in normal operation. // The buffer overflow check is purely defensive and should never trigger. static int write_printf_buffer(FILE *stream, char *buf, int len) { if (len < 0) { return len; } size_t write_len = len; if (write_len >= PRINTF_BUFFER_SIZE) { fwrite(buf, 1, PRINTF_BUFFER_SIZE - 1, stream); esp_system_abort("printf buffer overflow; set enable_full_printf: true in esp32 framework advanced config"); } if (fwrite(buf, 1, write_len, stream) < write_len || ferror(stream)) { return -1; } return len; } int __wrap_vprintf(const char *fmt, va_list ap) { char buf[PRINTF_BUFFER_SIZE]; return write_printf_buffer(stdout, buf, vsnprintf(buf, sizeof(buf), fmt, ap)); } int __wrap_vfprintf(FILE *stream, const char *fmt, va_list ap) { char buf[PRINTF_BUFFER_SIZE]; return write_printf_buffer(stream, buf, vsnprintf(buf, sizeof(buf), fmt, ap)); } #endif // __PICOLIBC__ 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); int len = __wrap_vfprintf(stream, fmt, ap); va_end(ap); return len; } } // extern "C" // NOLINTEND(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming) #endif // USE_ESP_IDF && !USE_FULL_PRINTF