c++ thread_local shared_ptr对象在析构时导致sigsegv

ldioqlga  于 2023-01-28  发布在  其他
关注(0)|答案(1)|浏览(239)

我有一个程序,它使用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时,包含线程local 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()做了什么指示新线程擦除存储?
  • 这种行为是否违反标准?我认为标准应该禁止在销毁前擦除内存。如果我说错了,请纠正我。
im9ewurl

im9ewurl1#

这是一个长期存在的、公开的、已知的mingw bug,请参见github上的相关分析和链接:https://github.com/msys2/MINGW-packages/issues/2519
是的,此violates the standard:它应该不会崩溃。基本上破坏的顺序是不正确的,正如你已经怀疑的那样。0xfeeefeeeHeapFree()用来标记释放内存的幻数。例如,参见this post
引用lhmouse的话:
所以这里有一个经验法则:对于MinGW目标,不要在GCC上使用thread_local。

相关问题