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