ESPHome 2026.5.0b1
Loading...
Searching...
No Matches
printf_stubs.cpp
Go to the documentation of this file.
1/*
2 * Linker wrap stubs for FILE*-based printf functions (newlib only).
3 *
4 * ESP-IDF SDK components (gpio driver, ringbuf, log_write) reference
5 * fprintf(), printf(), vprintf(), and vfprintf(), which on newlib pull
6 * in _vfprintf_r (~11 KB) — a separate implementation from the one used
7 * by snprintf/vsnprintf that handles FILE* stream I/O with buffering.
8 *
9 * ESPHome replaces the ESP-IDF log handler via esp_log_set_vprintf_(),
10 * so the SDK's vprintf() path is dead code at runtime. The fprintf()
11 * and printf() calls in SDK components are only in debug/assert paths
12 * (gpio_dump_io_configuration, ringbuf diagnostics) that are either
13 * GC'd or never called. Crash backtraces and panic output are
14 * unaffected; they use esp_rom_printf() which is a ROM function and
15 * does not go through libc.
16 *
17 * This wrap is newlib-only. On picolibc, vsnprintf is implemented as
18 * vfprintf into a string-output FILE, so vfprintf is unconditionally
19 * linked in by any caller of snprintf/vsnprintf and the wrap can never
20 * elide it — it just adds shim cost. Codegen forces USE_FULL_PRINTF
21 * on picolibc builds (IDF 6.0+ on all variants) so this file compiles
22 * to nothing there; the #error below catches a desynchronised gate.
23 *
24 * Saves ~11 KB of flash on newlib.
25 *
26 * To disable this wrap on newlib, set enable_full_printf: true in the
27 * esp32 advanced config section.
28 */
29
30#if defined(USE_ESP_IDF) && !defined(USE_FULL_PRINTF)
31
32#ifdef __PICOLIBC__
33#error "printf wrap is net-negative on picolibc; codegen should set USE_FULL_PRINTF"
34#endif
35
36#include <cstdarg>
37#include <cstdio>
38
39#include "esp_system.h"
40
41namespace esphome::esp32 {}
42
43// NOLINTBEGIN(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
44extern "C" {
45
46static constexpr size_t PRINTF_BUFFER_SIZE = 512;
47
48// These stubs are essentially dead code at runtime — ESPHome replaces the
49// ESP-IDF log handler, and the SDK's printf/fprintf calls only exist in
50// debug/assert paths that are never reached in normal operation.
51// The buffer overflow check is purely defensive and should never trigger.
52static int write_printf_buffer(FILE *stream, char *buf, int len) {
53 if (len < 0) {
54 return len;
55 }
56 size_t write_len = len;
57 if (write_len >= PRINTF_BUFFER_SIZE) {
58 fwrite(buf, 1, PRINTF_BUFFER_SIZE - 1, stream);
59 esp_system_abort("printf buffer overflow; set enable_full_printf: true in esp32 framework advanced config");
60 }
61 if (fwrite(buf, 1, write_len, stream) < write_len || ferror(stream)) {
62 return -1;
63 }
64 return len;
65}
66
67int __wrap_vprintf(const char *fmt, va_list ap) {
68 char buf[PRINTF_BUFFER_SIZE];
69 return write_printf_buffer(stdout, buf, vsnprintf(buf, sizeof(buf), fmt, ap));
70}
71
72int __wrap_vfprintf(FILE *stream, const char *fmt, va_list ap) {
73 char buf[PRINTF_BUFFER_SIZE];
74 return write_printf_buffer(stream, buf, vsnprintf(buf, sizeof(buf), fmt, ap));
75}
76
77int __wrap_printf(const char *fmt, ...) {
78 va_list ap;
79 va_start(ap, fmt);
80 int len = __wrap_vprintf(fmt, ap);
81 va_end(ap);
82 return len;
83}
84
85int __wrap_fprintf(FILE *stream, const char *fmt, ...) {
86 va_list ap;
87 va_start(ap, fmt);
88 int len = __wrap_vfprintf(stream, fmt, ap);
89 va_end(ap);
90 return len;
91}
92
93} // extern "C"
94// NOLINTEND(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
95
96#endif // USE_ESP_IDF && !USE_FULL_PRINTF
int __wrap_fprintf(FILE *stream, const char *fmt,...)
int __wrap_printf(const char *fmt,...)
int __wrap_vprintf(const char *fmt, va_list ap)
int __wrap_vfprintf(FILE *stream, const char *fmt, va_list ap)
uint32_t len