【问题标题】:thread_local shared_ptr object is causing sigsegv when destructingthread_local shared_ptr 对象在析构时导致 sigsegv
【发布时间】:2023-01-24 03:24:41
【问题描述】:

我有一个程序,它使用thread_local std::shared_ptr 来管理一些主要在线程本地访问的对象。但是,当线程被加入并且线程本地shared_ptr正在析构时,如果程序是由MinGW(Windows 10)编译的,则在调试时总是有SIGSEGV。这是重现错误的最少代码:

// main.cpp
#include <memory>
#include <thread>

void f() {
    thread_local std::shared_ptr<int> ptr = std::make_shared<int>(0);
}

int main() {
    std::thread th(f);
    th.join();
    return 0;
}

如何编译:

g++ main.cpp -o build\main.exe -std=c++17

编译器版本:

>g++ --version
g++ (x86_64-posix-seh-rev2, Built by MinGW-W64 project) 12.2.0
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

使用 gdb 运行时,当主线程等待 join() 时,它将在新线程中提供 SIGSEGV。它在 gcc、clang (Linux) 和 MSVC (Windows) 编译时工作正常。

我尝试调试发现,在调用RtlpWow64SetContextOnAmd64时,包含线程本地shared_ptr的一段连续内存被擦除为重复0xfeeefeee,然后销毁。框架:

RtlpWow64SetContextOnAmd64 0x00007ffd8f4deb5f
RtlpWow64SetContextOnAmd64 0x00007ffd8f4de978
SbSelectProcedure 0x00007ffd8f4ae2e0
CloseHandle 0x00007ffd8ce3655b
pthread_create_wrapper 0x00007ffd73934bac
_beginthreadex 0x00007ffd8e9baf5a
_endthreadex 0x00007ffd8e9bb02c
BaseThreadInitThunk 0x00007ffd8ec87614
RtlUserThreadStart 0x00007ffd8f4c26a1

大会:

...
mov    %rax,(%rdi)
movdqu %xmm0,(%rsi)               ; <------ erased here
call   0x7ffd8f491920             ; <ntdll!RtlReleaseSRWLockShared>
mov    $0x1,%r9d
mov    0x30(%rsp),%rbx
...

后来 shared_ptr 被破坏,当读取 0xfeeefeee 时有 SIGSEGV。

我想知道:

  • 为什么 MinGW(或 Windows 库?)在销毁前擦除线程本地存储?在我看来,擦除记忆应该只发生在破坏之后。我注意到如果join()被替换为detach(),程序正常退出。也许 join() 做了一些事情来指示新线程擦除存储?
  • 这样的行为是否违反标准?我认为标准应该禁止在销毁前擦除内存。如果我弄错了,请纠正我。

【问题讨论】:

  • thread_local 与局部变量混合使用是不常见的用例。我猜 ptrf 结束时被销毁了。我建议将变量移动到全局范围。另一方面,它隐含了一个静态局部变量:stackoverflow.com/a/22794640/6752050
  • 更喜欢使用 msys2 msys2.org
  • 在 godbolt.org 上尝试使用不同的编译器版本,也许如果你选择了一个较新的版本,那么你正在使用的版本就不见了。
  • 顺便说一句,我可以在 Win 11, g++ (Rev6, Built by MSYS2 project) 12.2.0 中重现这个问题。显示为Thread 5 received signal SIGSEGV, Segmentation fault. [Switching to Thread 15196.0x27dc] 0x00007ff7e54133f4 in std::_Sp_counted_base&lt;(__gnu_cxx::_Lock_policy)2&gt;::_M_release() () (gdb) x/i $rbp 0xfb957ff4b0: loopne 0xfb957ff4a6
  • @SolomonSlow 仅供参考“...如果 thread_local 是应用于块作用域变量的唯一存储类说明符,则还隐含了 static...”en.cppreference.com/w/cpp/language/storage_duration

标签: c++ multithreading c++11 mingw mingw-w64


【解决方案1】:

这是 mingw 中一个长期存在的、公开的和已知的错误,请参阅 github 上的分析和链接的相应问题:https://github.com/msys2/MINGW-packages/issues/2519

是的,这个violates the standard:它不应该崩溃。 正如您已经怀疑的那样,基本上销毁的顺序是不正确的。 0xfeeefeeeHeapFree() 用来标记已释放内存的幻数。参见例如this post

引用lhmouse

所以经验法则来了:不要在 GCC 上将 thread_local 用于 MinGW 目标。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-13
    • 1970-01-01
    • 1970-01-01
    • 2017-11-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多