thread_local shared_ptr 对象在销毁时导致 sigsegv

Ant*_*res 11 c++ multithreading mingw mingw-w64 c++11

我有一个程序,用于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;
}
Run Code Online (Sandbox Code Playgroud)

如何编译:

g++ main.cpp -o build\main.exe -std=c++17
Run Code Online (Sandbox Code Playgroud)

编译器版本:

>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.
Run Code Online (Sandbox Code Playgroud)

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

我尝试调试发现,0xfeeefeee在调用RtlpWow64SetContextOnAmd64. 框架:

RtlpWow64SetContextOnAmd64 0x00007ffd8f4deb5f
RtlpWow64SetContextOnAmd64 0x00007ffd8f4de978
SbSelectProcedure 0x00007ffd8f4ae2e0
CloseHandle 0x00007ffd8ce3655b
pthread_create_wrapper 0x00007ffd73934bac
_beginthreadex 0x00007ffd8e9baf5a
_endthreadex 0x00007ffd8e9bb02c
BaseThreadInitThunk 0x00007ffd8ec87614
RtlUserThreadStart 0x00007ffd8f4c26a1
Run Code Online (Sandbox Code Playgroud)

大会:

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

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

我想知道:

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

Gug*_*ugi 3

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

是的,这违反了标准:它不应该崩溃。基本上,正如您已经怀疑的那样,破坏顺序是不正确的。这是用于标记已释放内存的0xfeeefeee幻数。HeapFree()参见例如这篇文章

引用lhmouse

因此,经验法则是:不要在 GCC 上为 MinGW 目标使用 thread_local。