Skip to content

Fix: ESP-IDF 5.x / ESPHome 2026.5+ compatibility (Boot crash-loops, LoadProhibited & aborts) - #42

Open
attixray wants to merge 3 commits into
fsievers22:masterfrom
attixray:nullsafe-fix
Open

Fix: ESP-IDF 5.x / ESPHome 2026.5+ compatibility (Boot crash-loops, LoadProhibited & aborts)#42
attixray wants to merge 3 commits into
fsievers22:masterfrom
attixray:nullsafe-fix

Conversation

@attixray

@attixray attixray commented Jun 3, 2026

Copy link
Copy Markdown

After updating to ESPHome 2026.5+ (which migrates the underlying framework to ESP-IDF 5.x), devices using ble_client_hid suffered from continuous boot crash-loops followed by OTA rollbacks.

This PR resolves three distinct C++ runtime crashes introduced by the new framework's stricter memory constraints, changed NimBLE behavior, and altered BLE discovery timings.

Root Causes & Fixes in this PR:

  1. Boot-time abort() during static initialization (OOM)

Issue: Serial backtraces showed an abort() at ~583ms, before any ESPHome log output. The global USAGE_PAGES nested std::map was doing deep recursive _Rb_tree copies during __libc_init_array because the mapped types were const, forbidding move semantics. On ESP-IDF 5.x with larger configs, this allocation threw a std::bad_alloc during static init, leading to std::terminate -> abort().

Fix: Built USAGE_PAGES lazily. Exposed a get_usage_pages() function returning a function-local static map, so it constructs once on first use at runtime. Removed const from mapped types to allow move semantics, eliminating redundant deep copies.

  1. HID Report-Map Parser abort() (Padding/Truncation)

Issue: ESP-IDF 5.x can return a truncated or padded HID report map. The parser decremented report_map_size by an item data length without bounds checking, causing a uint16_t underflow. It then looked up input_reports.at(report_id) on garbage data. With -fno-exceptions, std::out_of_range triggers std::terminate and a hardware abort.

Fix: Added truncation/padding guards to stop parsing if an item claims more data bytes than remain. Replaced throwing .at() calls with safe find()/emplace in the INPUT handler and HIDReportMap::parse().

  1. LoadProhibited panics due to BLE discovery timing

Issue: ESPHome 2026.5 changed BLE discovery timings. Missing characteristics or unread handles (where map::operator[] silently inserts a nullptr) caused null-dereferences because get_characteristic() results were not guarded.

Fix: Made configure_hid_client completely null-safe. Used find() in parse_characteristic_data, guarded the PnP ID block (ensuring length >= 7) and Report Reference descriptors. Added WARN logs to skip gracefully instead of panicking.

(Note: The root cause debugging and code fixes in these commits were co-authored with Claude Opus 4.8).

attixray and others added 3 commits June 1, 2026 22:16
The component dereferenced get_characteristic() results and
handles_to_read[handle] entries without null/existence checks. Under
ESPHome 2026.5's changed BLE discovery timing a missing characteristic
(or an unread handle, where map::operator[] silently inserts a nullptr)
caused a LoadProhibited panic at boot -> watchdog reset -> OTA rollback.

- parse_characteristic_data: use find() and verify stored data != nullptr
- PnP ID block: guard pnp_id_char and its read data (len >= 7), skip if absent
- HID Report Map: guard char + read data, abort cleanly (no crash) if missing
- Report Reference descriptor: guard read data before indexing value_[0]
- Add WARN logs naming what is missing, and a dump_config build marker

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Real cause of the boot crash-loop (serial backtrace shows abort was called,
with recursive frames in the parser): an uncaught C++ exception. ESP-IDF 5.x
can return a truncated/padded HID report map; the parser then:

- decremented report_map_size (uint16_t) by an item data length without a
  bounds check, underflowing and over-reading the heap, and
- looked up input_reports.at(report_id) on the garbage, throwing
  std::out_of_range (or runaway new, throwing std::bad_alloc). With
  -fno-exceptions that becomes std::terminate then abort, so the device
  resets before boot is marked good and the bootloader rolls back.

Fixes:
- parse_report_map_data / esp_logd_report_map: stop parsing if an item claims
  more data bytes than remain (truncation/padding guard)
- replace throwing .at() with find()/emplace in the INPUT handler and in
  HIDReportMap::parse()

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Serial backtrace showed abort() at ~583ms, before any ESPHome log output,
looping every boot - i.e. during C++ static initialization (__libc_init_array),
not in the BLE/parse path. The culprit is the global USAGE_PAGES nested
std::map: the UsagePage ctor took the inner map by value and copied it again
into a const member, and the outer map's mapped type was const UsagePage,
which forbids moves and forces deep recursive _Rb_tree copies (the repeated
frames in the backtrace). On ESP-IDF 5.x / ESPHome 2026.5 with a larger config
the allocation throws during static init -> std::terminate -> abort -> reset
before boot is marked good -> rollback / boot loop.

Fix:
- expose get_usage_pages() returning a function-local static map, so it is
  constructed once on first use at runtime instead of during static init
- UsagePage moves the inner map into its member and the member/mapped types
  are no longer const, removing the redundant deep copies
- ble_client_hid.cpp uses get_usage_pages() instead of the global

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@attixray attixray changed the title Fix: Boot crash / abort() in hid_parser under ESP-IDF 5.x (ESPHome 2026.5+) Fix: ESP-IDF 5.x / ESPHome 2026.5+ compatibility (Boot crash-loops, LoadProhibited & aborts) Jun 3, 2026
@attixray
attixray marked this pull request as ready for review June 3, 2026 21:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant