mirror of
https://github.com/esphome/esphome.git
synced 2026-05-21 13:24:09 +08:00
[esp32] Replace 512B stack buffer in printf wraps with picolibc cookie FILE (#16170)
This commit is contained in:
@@ -13,14 +13,21 @@
|
||||
* 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
|
||||
* unaffected; they use esp_rom_printf() which is a ROM function
|
||||
* and does not go through libc.
|
||||
*
|
||||
* These stubs redirect through vsnprintf() (which uses _svfprintf_r
|
||||
* already in the binary) and fwrite(), allowing the linker to
|
||||
* dead-code eliminate _vfprintf_r.
|
||||
* 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.
|
||||
*
|
||||
* Saves ~11 KB of flash.
|
||||
* 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.
|
||||
@@ -30,10 +37,55 @@
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
|
||||
#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 <cstddef>
|
||||
#include <type_traits>
|
||||
|
||||
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<CookieFile>::value, "CookieFile must be standard-layout");
|
||||
|
||||
int cookie_put(char c, FILE *stream) {
|
||||
auto *cookie = reinterpret_cast<CookieFile *>(stream);
|
||||
return fputc(static_cast<unsigned char>(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
|
||||
@@ -55,14 +107,18 @@ static int write_printf_buffer(FILE *stream, char *buf, int len) {
|
||||
return len;
|
||||
}
|
||||
|
||||
// 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];
|
||||
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);
|
||||
@@ -71,11 +127,6 @@ int __wrap_printf(const char *fmt, ...) {
|
||||
return len;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
int __wrap_fprintf(FILE *stream, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
|
||||
Reference in New Issue
Block a user