Fix: ESP-IDF 5.x / ESPHome 2026.5+ compatibility (Boot crash-loops, LoadProhibited & aborts) - #42
Open
attixray wants to merge 3 commits into
Open
Fix: ESP-IDF 5.x / ESPHome 2026.5+ compatibility (Boot crash-loops, LoadProhibited & aborts)#42attixray wants to merge 3 commits into
attixray wants to merge 3 commits into
Conversation
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
marked this pull request as ready for review
June 3, 2026 21:33
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
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.
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().
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).