Skip to content

Conversation

@hushi55
Copy link
Contributor

@hushi55 hushi55 commented Dec 18, 2025

解决某些情况下,内存奔溃的问题
比如接收的数据在某个异步 task 中执行,数据又超过了 1500 字节

@78
Copy link
Owner

78 commented Dec 19, 2025

放在类成员会更好,在分配 tcp / udp 对象的时候就报内存不足,而不是在启动 task 后报错。

@hushi55
Copy link
Contributor Author

hushi55 commented Dec 19, 2025

放在类成员会更好,在分配 tcp / udp 对象的时候就报内存不足,而不是在启动 task 后报错。

done

帮忙重新 review 下

@78
Copy link
Owner

78 commented Dec 19, 2025

还是有点问题,从读取到申请的内存,到后面封装成 std::string,比之前多了一次内存复制操作。

@hushi55
Copy link
Contributor Author

hushi55 commented Dec 19, 2025

这里就是要多一次内存 copy,
不然在一些场景下就会 carsh,
之前 通过 resize 方式,内存碎片会比较严重

@hushi55
Copy link
Contributor Author

hushi55 commented Dec 19, 2025

解决某些情况下,内存奔溃的问题 比如接收的数据在某个异步 task 中执行,数据又超过了 1500 字节

不过这个库,使用一直没有问题
只有最近出现了一次 crash,奔溃的栈显示是在这个位置

@hushi55
Copy link
Contributor Author

hushi55 commented Jan 5, 2026

(base) m4:mm-esp32-voice hushi.hs$ xtensa-esp32-elf-addr2line -pfiaC -e build/mm-audio.elf 0x4038fb52
0x4038fb52: insert_free_block at /Users/hushi.hs/Documents/WorkSpace/esp/esp-idf/components/heap/tlsf/tlsf_control_functions.h:406 (discriminator 3)
(inlined by) block_insert at /Users/hushi.hs/Documents/WorkSpace/esp/esp-idf/components/heap/tlsf/tlsf_control_functions.h:430 (discriminator 3)
(inlined by) tlsf_free at /Users/hushi.hs/Documents/WorkSpace/esp/esp-idf/components/heap/tlsf/tlsf.c:634 (discriminator 3)
(base) m4:mm-esp32-voice hushi.hs$ xtensa-esp32-elf-addr2line -pfiaC -e build/mm-audio.elf 0x4038f9c2
0x4038f9c2: multi_heap_free_impl at /Users/hushi.hs/Documents/WorkSpace/esp/esp-idf/components/heap/multi_heap.c:240
(inlined by) multi_heap_free_impl at /Users/hushi.hs/Documents/WorkSpace/esp/esp-idf/components/heap/multi_heap.c:229
(base) m4:mm-esp32-voice hushi.hs$ xtensa-esp32-elf-addr2line -pfiaC -e build/mm-audio.elf 0x4037a13b
0x4037a13b: heap_caps_free at /Users/hushi.hs/Documents/WorkSpace/esp/esp-idf/components/heap/heap_caps_base.c:86
(base) m4:mm-esp32-voice hushi.hs$ xtensa-esp32-elf-addr2line -pfiaC -e build/mm-audio.elf 0x40390de1
0x40390de1: free at /Users/hushi.hs/Documents/WorkSpace/esp/esp-idf/components/newlib/src/heap.c:34
(base) m4:mm-esp32-voice hushi.hs$ xtensa-esp32-elf-addr2line -pfiaC -e build/mm-audio.elf 0x421a67dd
0x421a67dd: operator delete(void*) at /builds/idf/crosstool-NG/.build/xtensa-esp-elf/src/gcc/libstdc++-v3/libsupc++/del_op.cc:49
(base) m4:mm-esp32-voice hushi.hs$ xtensa-esp32-elf-addr2line -pfiaC -e build/mm-audio.elf 0x4218cd49
0x4218cd49: operator delete(void*, unsigned int) at /builds/idf/crosstool-NG/.build/xtensa-esp-elf/src/gcc/libstdc++-v3/libsupc++/del_ops.cc:33
(base) m4:mm-esp32-voice hushi.hs$ xtensa-esp32-elf-addr2line -pfiaC -e build/mm-audio.elf 0x420890bf
0x420890bf: std::__new_allocator::deallocate(char*, unsigned int) at /Users/hushi.hs/.espressif/tools/xtensa-esp-elf/esp-14.2.0_20251107/xtensa-esp-elf/xtensa-esp-elf/include/c++/14.2.0/bits/new_allocator.h:172
(inlined by) std::allocator::deallocate(char*, unsigned int) at /Users/hushi.hs/.espressif/tools/xtensa-esp-elf/esp-14.2.0_20251107/xtensa-esp-elf/xtensa-esp-elf/include/c++/14.2.0/bits/allocator.h:208
(inlined by) std::allocator_traits<std::allocator >::deallocate(std::allocator&, char*, unsigned int) at /Users/hushi.hs/.espressif/tools/xtensa-esp-elf/esp-14.2.0_20251107/xtensa-esp-elf/xtensa-esp-elf/include/c++/14.2.0/bits/alloc_traits.h:513
(inlined by) std::_Vector_base<char, std::allocator >::_M_deallocate(char*, unsigned int) at /Users/hushi.hs/.espressif/tools/xtensa-esp-elf/esp-14.2.0_20251107/xtensa-esp-elf/xtensa-esp-elf/include/c++/14.2.0/bits/stl_vector.h:389
(inlined by) std::_Vector_base<char, std::allocator >::~_Vector_base() at /Users/hushi.hs/.espressif/tools/xtensa-esp-elf/esp-14.2.0_20251107/xtensa-esp-elf/xtensa-esp-elf/include/c++/14.2.0/bits/stl_vector.h:368
(base) m4:mm-esp32-voice hushi.hs$ xtensa-esp32-elf-addr2line -pfiaC -e build/mm-audio.elf 0x4208a1a5
0x4208a1a5: std::vector<char, std::allocator >::~vector() at /Users/hushi.hs/.espressif/tools/xtensa-esp-elf/esp-14.2.0_20251107/xtensa-esp-elf/xtensa-esp-elf/include/c++/14.2.0/bits/stl_vector.h:738
(inlined by) WebSocket::OnTcpData(std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&) at /Users/hushi.hs/Documents/WorkSpace/Firmware/mm-esp32-voice/managed_components/78__esp-ml307/src/web_socket.cc:404
(base) m4:mm-esp32-voice hushi.hs$ xtensa-esp32-elf-addr2line -pfiaC -e build/mm-audio.elf 0x4208a47d
xtensa-esp32-elf-addr2line -pfiaC -e build/mm-audio.elf 0x42088762
xtensa-esp32-elf-addr2line -pfiaC -e build/mm-audio.elf 0x4208876d
0x4208a47d: std::_Function_handler<void (std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&), WebSocket::Connect(char const*)::{lambda(std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&)#1}>::_M_invoke(std::_Any_data const&, std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&) at /Users/hushi.hs/Documents/WorkSpace/Firmware/mm-esp32-voice/managed_components/78__esp-ml307/src/web_socket.cc:159
(inlined by) __invoke_impl<void, WebSocket::Connect(char const*)::<lambda(const std::string&)>&, const std::__cxx11::basic_string<char, std::char_traits, std::allocator >&> at /Users/hushi.hs/.espressif/tools/xtensa-esp-elf/esp-14.2.0_20251107/xtensa-esp-elf/xtensa-esp-elf/include/c++/14.2.0/bits/invoke.h:61
(inlined by) __invoke_r<void, WebSocket::Connect(char const*)::<lambda(const std::string&)>&, const std::__cxx11::basic_string<char, std::char_traits, std::allocator >&> at /Users/hushi.hs/.espressif/tools/xtensa-esp-elf/esp-14.2.0_20251107/xtensa-esp-elf/xtensa-esp-elf/include/c++/14.2.0/bits/invoke.h:111
(inlined by) _M_invoke at /Users/hushi.hs/.espressif/tools/xtensa-esp-elf/esp-14.2.0_20251107/xtensa-esp-elf/xtensa-esp-elf/include/c++/14.2.0/bits/std_function.h:290
(base) m4:mm-esp32-voice hushi.hs$ xtensa-esp32-elf-addr2line -pfiaC -e build/mm-audio.elf 0x42088762
0x42088762: std::function<void (std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&)>::operator()(std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&) const at /Users/hushi.hs/.espressif/tools/xtensa-esp-elf/esp-14.2.0_20251107/xtensa-esp-elf/xtensa-esp-elf/include/c++/14.2.0/bits/std_function.h:591
(inlined by) EspTcp::ReceiveTask() at /Users/hushi.hs/Documents/WorkSpace/Firmware/mm-esp32-voice/managed_components/78__esp-ml307/src/esp/esp_tcp.cc:146
(base) m4:mm-esp32-voice hushi.hs$ xtensa-esp32-elf-addr2line -pfiaC -e build/mm-audio.elf 0x4208876d
0x4208876d: EspTcp::Connect(std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&, int)::{lambda(void*)#1}::_FUN(void*) at /Users/hushi.hs/Documents/WorkSpace/Firmware/mm-esp32-voice/managed_components/78__esp-ml307/src/esp/esp_tcp.cc:66
(inlined by) _FUN at /Users/hushi.hs/Documents/WorkSpace/Firmware/mm-esp32-voice/managed_components/78__esp-ml307/src/esp/esp_tcp.cc:69
(base) m4:mm-esp32-voice hushi.hs$

@hushi55
Copy link
Contributor Author

hushi55 commented Jan 5, 2026

提供最近奔溃的一个堆栈

@78
Copy link
Owner

78 commented Jan 5, 2026

我让 Cursor 根据你提供的出错日志,修复了导致崩溃的问题,稍后提交一个PR。

[13 tools called]

基于您提供的堆栈信息,我分析了 components/esp-ml307 组件中的 WebSocket 实现,发现导致崩溃(Heap Corruption/堆破坏)的几个核心原因,并已针对性地进行了修复。

崩溃原因分析

  1. 符号位扩展导致巨大的内存分配(主因)
    在解析 WebSocket 帧长度时,原始代码使用了 const char* 来读取缓冲区数据。由于 ESP32 上 char 默认是有符号的,当数据的高位为 1 时(例如 0x80),在进行位移和或运算时会发生符号位扩展。

    • 例子:如果长度字段的第一个字节是 0x80,代码执行 (buffer[offset] << 8) 时,0x80 会先被提升为 0xFFFFFF80,导致最终计算出的 payload_length 变成一个天文数字(接近 2^64)。
    • 后果:接下来的 std::vector<char> payload(payload_length); 会尝试申请巨大的内存,直接导致堆内存耗尽或破坏,最终在 payload 析构时(web_socket.cc:404)触发 ~vector() 崩溃。
  2. 静态变量竞争导致堆破坏
    OnTcpData 函数中,处理分片消息使用了 static 类型的变量(current_message, is_fragmented, is_binary)。

    • 后果:如果系统中有多个 WebSocket 连接,或者 OnTcpData 被并发调用,所有连接都会共享同一个静态缓冲区。一个连接的数据会插入到另一个连接的缓冲区中,且由于 std::vector 不是线程安全的,并发操作会直接破坏堆管理器的元数据。
  3. 频繁创建线程导致资源耗尽
    原始代码在每次收到 Ping 帧时都会使用 std::thread(...).detach() 创建一个新线程来回复 Pong

    • 后果:在高频率 Ping 的情况下,会迅速耗尽系统的任务句柄和堆内存。

已完成的修改

  1. 修复长度计算逻辑

    • 将解析缓冲区统一转换为 const uint8_t*,消除位运算时的符号位扩展风险。
    • 使用了显式的 static_cast<uint64_t> 确保大长度帧(126/127模式)计算正确。
  2. 消除静态变量

    • current_message_is_fragmented_is_binary_static 局部变量改为 WebSocket 类的成员变量。
    • 确保每个 WebSocket 实例拥有独立的接收上下文,从根本上解决了多实例并发下的内存破坏问题。
  3. 优化 Ping/Pong 回复

    • 移除了 std::thread,直接在当前任务中调用 SendControlFrame
    • 由于 SendControlFrame 内部已有 mutex 保护且 Pong 数据极小,这样做既安全又高效。

代码修复详情

  • 修改了 components/esp-ml307/include/web_socket.h:增加了必要的成员变量和头文件包含。
  • 修改了 components/esp-ml307/src/web_socket.cc:重构了 OnTcpData 的帧解析逻辑。

这些改动解决了堆内存非法增长和多线程竞态的问题,应该能解决您遇到的运行错误。建议您重新编译并观察运行情况。

@hushi55
Copy link
Contributor Author

hushi55 commented Jan 5, 2026

ok
我用你最新的 版本,看看是不是问题解决了

不得不说 Cursor 比我用的 Copilot 要强哈
看来得换到这个 Cursor 的订阅上

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.

2 participants