Fix lives in: ESP-IDF heap component (components/heap/multi_heap.c)
What the workaround does
We provide an alignas(8)-guaranteed initial block when constructing the protobuf Arena inside OtlpHttpExporter::Export(). Because the initial_block is the first block the arena touches, TaggedAllocationPolicyPtr always operates on an 8-byte-aligned base, avoiding the misaligned pointer crash:
// src/workarounds/otlp_http_exporter_esp.cc (replaces otlp_http_exporter.cc
// via set_property(SOURCES))
alignas(8) char initial_buf[1024];
google::protobuf::ArenaOptions arena_options;
arena_options.initial_block = initial_buf;
arena_options.initial_block_size = sizeof(initial_buf);
arena_options.max_block_size = 65536;
google::protobuf::Arena arena{arena_options};
The replacement file is wired in through CMakeLists.txt using set_property(TARGET opentelemetry_http_client_curl PROPERTY SOURCES ...) so no submodule file is modified. This is a targeted fix: it guarantees 8-byte alignment for the single arena constructed by the exporter without changing global allocator behaviour.
Root cause
ESP-IDF's TLSF-backed heap uses a 4-byte alignment granularity on 32-bit Xtensa:
// components/heap/multi_heap.c
#define ALIGN(X) ((X) & ~(sizeof(void *)-1)) // sizeof(void*)==4 → 4-byte alignment
The C++ standard requires operator new to return memory aligned to
alignof(std::max_align_t). The ESP32-S3 Xtensa toolchain reports
alignof(std::max_align_t)==8 (empirically confirmed via logging at startup).
The heap's 4-byte granularity therefore violates the standard's alignment
guarantee: two out of eight consecutive operator new(256) calls returned a
4-byte-aligned (not 8-byte-aligned) address during testing.
The practical crash comes from protobuf's TaggedAllocationPolicyPtr
(google/protobuf/arena_allocation_policy.h). It stores three flag bits in the
low bits of an AllocationPolicy* pointer using kPtrMask = ~7, which requires
the stored pointer to be 8-byte aligned. The crash sequence when
ArenaOptions with non-default max_block_size are used:
InitializeWithPolicy places an AllocationPolicy at ptr() of the first
arena block. ptr() starts at block_base + kBlockHeaderSize (8). If
block_base is only 4-byte aligned, ptr() is also only 4-byte aligned.
alloc_policy_.set_policy(p) stores the 4-byte-aligned pointer p.
alloc_policy_.get() computes p & ~7 = p - 4, reading 4 bytes before
the AllocationPolicy struct.
get()->block_alloc therefore reads from p + 4, which is the
max_block_size field — 0x00010000 when max_block_size = 65536.
- When the arena needs a second block it calls
block_alloc(size), jumping to
address 0x00010000 → InstrFetchProhibited exception.
The OTLP HTTP exporter (otlp_http_exporter.cc) creates an arena with
max_block_size = 65536 on every Export() call, triggering the crash
consistently after the first span export.
Note that protobuf's existing ABSL_DCHECK_EQ(0u, ptr & 3) only verifies
4-byte alignment, masking the bug on platforms where the two overlap.
Proposed fix
ESP-IDF's ALIGN macro in components/heap/multi_heap.c should use
alignof(max_align_t) instead of sizeof(void*) as the alignment granularity
on targets where the two differ:
// components/heap/multi_heap.c
#include <stdalign.h>
#define ALIGN(X) ((X) & ~(alignof(max_align_t) - 1))
This is the conforming minimum: malloc and operator new must return memory
suitably aligned for any fundamental type. No existing upstream bug report was
found for this mismatch.
What we delete once fixed
src/workarounds/otlp_http_exporter_esp.cc
- The
set_property(TARGET opentelemetry_http_client_curl PROPERTY SOURCES ...) override in CMakeLists.txt
Fix lives in: ESP-IDF heap component (
components/heap/multi_heap.c)What the workaround does
We provide an
alignas(8)-guaranteed initial block when constructing the protobufArenainsideOtlpHttpExporter::Export(). Because theinitial_blockis the first block the arena touches,TaggedAllocationPolicyPtralways operates on an 8-byte-aligned base, avoiding the misaligned pointer crash:The replacement file is wired in through
CMakeLists.txtusingset_property(TARGET opentelemetry_http_client_curl PROPERTY SOURCES ...)so no submodule file is modified. This is a targeted fix: it guarantees 8-byte alignment for the single arena constructed by the exporter without changing global allocator behaviour.Root cause
ESP-IDF's TLSF-backed heap uses a 4-byte alignment granularity on 32-bit Xtensa:
The C++ standard requires
operator newto return memory aligned toalignof(std::max_align_t). The ESP32-S3 Xtensa toolchain reportsalignof(std::max_align_t)==8(empirically confirmed via logging at startup).The heap's 4-byte granularity therefore violates the standard's alignment
guarantee: two out of eight consecutive
operator new(256)calls returned a4-byte-aligned (not 8-byte-aligned) address during testing.
The practical crash comes from protobuf's
TaggedAllocationPolicyPtr(
google/protobuf/arena_allocation_policy.h). It stores three flag bits in thelow bits of an
AllocationPolicy*pointer usingkPtrMask = ~7, which requiresthe stored pointer to be 8-byte aligned. The crash sequence when
ArenaOptionswith non-defaultmax_block_sizeare used:InitializeWithPolicyplaces anAllocationPolicyatptr()of the firstarena block.
ptr()starts atblock_base + kBlockHeaderSize (8). Ifblock_baseis only 4-byte aligned,ptr()is also only 4-byte aligned.alloc_policy_.set_policy(p)stores the 4-byte-aligned pointerp.alloc_policy_.get()computesp & ~7 = p - 4, reading 4 bytes beforethe
AllocationPolicystruct.get()->block_alloctherefore reads fromp + 4, which is themax_block_sizefield —0x00010000whenmax_block_size = 65536.block_alloc(size), jumping toaddress
0x00010000→InstrFetchProhibitedexception.The OTLP HTTP exporter (
otlp_http_exporter.cc) creates an arena withmax_block_size = 65536on everyExport()call, triggering the crashconsistently after the first span export.
Note that protobuf's existing
ABSL_DCHECK_EQ(0u, ptr & 3)only verifies4-byte alignment, masking the bug on platforms where the two overlap.
Proposed fix
ESP-IDF's
ALIGNmacro incomponents/heap/multi_heap.cshould usealignof(max_align_t)instead ofsizeof(void*)as the alignment granularityon targets where the two differ:
This is the conforming minimum:
mallocandoperator newmust return memorysuitably aligned for any fundamental type. No existing upstream bug report was
found for this mismatch.
What we delete once fixed
src/workarounds/otlp_http_exporter_esp.ccset_property(TARGET opentelemetry_http_client_curl PROPERTY SOURCES ...)override inCMakeLists.txt