繁体   English   中英

`std::call_once` 总是在 Windows 上的 Clang 12 上出现段错误(使用 libstdc++ 时)

[英]`std::call_once` always segfaults on Clang 12 on Windows (when using libstdc++)

我正在寻找一种解决方法,这可能涉及修补 libstdc++ 标头。 保留二进制兼容性是首选但不是强制性的,因为除了 libstdc++ 之外,我没有使用任何预编译的 C++ 代码。

我想保留std::call_once接口,因为我正在尝试编译使用 is 的第三方代码,我不想更改它。


这是我的代码:

#include <iostream>
#include <mutex>

int main()
{
    std::once_flag flag;
    std::call_once(flag, []{std::cout << "Once!\n";});
}

运行它会导致分段错误。

I cross-compile it to Windows from Ubuntu using Clang 12, using the standard library from MSYS2 GCC 10.2. 然后我用 Wine 测试结果(快速测试表明它也在 VM 上崩溃)。 But you should be able to reproduce the results by compiling on Windows natively (using the official Clang binaries + MSYS2 GCC, since MSYS2 doesn't have Clang 12 yet).

我这样编译它:

clang++-12 1.cpp --target=x86_64-w64-mingw32 --sysroot=/mingw64 -pthread -femulated-tls

如果我添加-g , GDB 显示如下:

Program received signal SIGSEGV, Segmentation fault.
0x00000001e014dc4a in ?? () from Z:\home\holyblackcat\Sandbox\2\libgcc_s_seh-1.dll
(gdb) bt
#0  0x00000001e014dc4a in ?? () from Z:\home\holyblackcat\Sandbox\2\libgcc_s_seh-1.dll
#1  0x00000000004015f3 in std::call_once<main::$_0> (__once=..., __f=...) at /mingw64/include/c++/10.2.0/mutex:721
#2  0x00000000004015b5 in main () at 1.cpp:8
(gdb) f 1
#1  0x00000000004015f3 in std::call_once<main::$_0> (__once=..., __f=...) at /mingw64/include/c++/10.2.0/mutex:721
721           __once_callable = std::__addressof(__callable);
(gdb) list
716           auto __callable = [&] {
717               std::__invoke(std::forward<_Callable>(__f),
718                             std::forward<_Args>(__args)...);
719           };
720     #ifdef _GLIBCXX_HAVE_TLS
721           __once_callable = std::__addressof(__callable);
722           __once_call = []{ (*(decltype(__callable)*)__once_callable)(); };
723     #else
724           unique_lock<mutex> __functor_lock(__get_once_mutex());
725           __once_functor = __callable;

Clang版本为:

# clang++-12 --version --target=x86_64-w64-mingw32 --sysroot=/mingw64 -pthread -femulated-tls
Ubuntu clang version 12.0.1-++20210423082613+072c90a863aa-1~exp1~20210423063319.76
Target: x86_64-w64-windows-gnu
Thread model: posix

GCC 版本(提供 libstdc++)为:

# g++ --version
g++.exe (Rev10, Built by MSYS2 project) 10.2.0

用这个 GCC (这是本机的,不是交叉编译的)编译代码,会产生一个工作代码。

这里发生了什么? 是否有任何解决方法,或者我必须降级到 Clang 11?


我报告了一个Clang 错误

这个错误看起来相关。


这是call_once的当前段错误实现,经过预处理:

struct once_flag
{
  private:
    typedef __gthread_once_t __native_type;
    __native_type _M_once = 0;

  public:
    constexpr once_flag() noexcept = default;
    once_flag(const once_flag &) = delete;
    once_flag &operator=(const once_flag &) = delete;
    template <typename _Callable, typename... _Args>
    friend void call_once(once_flag &__once, _Callable &&__f, _Args &&...__args);
};

extern __thread void *__once_callable;
extern __thread void (*__once_call)();
extern "C" void __once_proxy(void);

template <typename _Callable, typename... _Args>
void call_once(once_flag &__once, _Callable &&__f, _Args &&...__args)
{
    auto __callable = [&]
    {
        std::__invoke(std::forward<_Callable>(__f), std::forward<_Args>(__args)...);
    };
    __once_callable = std::__addressof(__callable);
    __once_call = []{(*(decltype(__callable) *)__once_callable)();};
    int __e = __gthread_once(&__once._M_once, &__once_proxy);
    if (__e)
        __throw_system_error(__e);
}

这已在 Clang 主干中修复, 提交0e4cf80 (感谢@mstorsjo 。)


在 Clang 更新到来之前,您可以修补 libstdc++ 标头作为解决方法。 下面的补丁将全局线程局部变量替换为static函数局部线程局部变量,不受该错误影响。

要应用,请将以下内容保存到patch.txt ,然后执行patch /mingw64/include/c++/10.3.0/mutex patch.txt

@@ -691,8 +691,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION

   /// @cond undocumented
 #ifdef _GLIBCXX_HAVE_TLS
-  extern __thread void* __once_callable;
-  extern __thread void (*__once_call)();
+  inline void *&__once_callable_get() {static __thread void *__ret; return __ret;}
+  inline void (*&__once_call_get())() {static __thread void (*__ret)(); return __ret;}
 #else
   extern function<void()> __once_functor;

@@ -703,7 +703,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   __get_once_mutex();
 #endif

-  extern "C" void __once_proxy(void);
+  extern "C" inline void __once_proxy_inline(void) {__once_call_get()();}
   /// @endcond

   /// Invoke a callable and synchronize with other calls using the same flag
@@ -718,15 +718,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
            std::forward<_Args>(__args)...);
       };
 #ifdef _GLIBCXX_HAVE_TLS
-      __once_callable = std::__addressof(__callable); // NOLINT: PR 82481
-      __once_call = []{ (*(decltype(__callable)*)__once_callable)(); };
+      __once_callable_get() = std::__addressof(__callable); // NOLINT: PR 82481
+      __once_call_get() = []{ (*(decltype(__callable)*)__once_callable_get())(); };
 #else
       unique_lock<mutex> __functor_lock(__get_once_mutex());
       __once_functor = __callable;
       __set_once_functor_lock_ptr(&__functor_lock);
 #endif

-      int __e = __gthread_once(&__once._M_once, &__once_proxy);
+      int __e = __gthread_once(&__once._M_once, &__once_proxy_inline);

 #ifndef _GLIBCXX_HAVE_TLS
       if (__functor_lock)

这是 GCC 10.2 的等效补丁(以上是 10.3):

@@ -691,8 +691,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
   /// @cond undocumented
 #ifdef _GLIBCXX_HAVE_TLS
-  extern __thread void* __once_callable;
-  extern __thread void (*__once_call)();
+  inline void *&__once_callable_get() {static __thread void *__ret; return __ret;}
+  inline void (*&__once_call_get())() {static __thread void (*__ret)(); return __ret;}
 #else
   extern function<void()> __once_functor;
 
@@ -703,7 +703,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   __get_once_mutex();
 #endif
 
-  extern "C" void __once_proxy(void);
+  extern "C" inline void __once_proxy_inline(void) {__once_call_get()();}
   /// @endcond
 
   /// Invoke a callable and synchronize with other calls using the same flag
@@ -718,15 +718,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
            std::forward<_Args>(__args)...);
       };
 #ifdef _GLIBCXX_HAVE_TLS
-      __once_callable = std::__addressof(__callable);
-      __once_call = []{ (*(decltype(__callable)*)__once_callable)(); };
+      __once_callable_get() = std::__addressof(__callable);
+      __once_call_get() = []{ (*(decltype(__callable)*)__once_callable_get())(); };
 #else
       unique_lock<mutex> __functor_lock(__get_once_mutex());
       __once_functor = __callable;
       __set_once_functor_lock_ptr(&__functor_lock);
 #endif
 
-      int __e = __gthread_once(&__once._M_once, &__once_proxy);
+      int __e = __gthread_once(&__once._M_once, &__once_proxy_inline);
 
 #ifndef _GLIBCXX_HAVE_TLS
       if (__functor_lock)
@@ -735,8 +735,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
 #ifdef __clang_analyzer__
       // PR libstdc++/82481
-      __once_callable = nullptr;
-      __once_call = nullptr;
+      __once_callable_get() = nullptr;
+      __once_call_get() = nullptr;
 #endif
 
       if (__e)

您可以解决此问题的一种方法是利用static变量自 C++11 以线程安全方式初始化的事实。 例子:

#include <iostream>

void test ()
{
    std::cout << "test\n";
    static bool once = [] { std::cout << "Once!\n"; return true; } ();
    (void) once;
}

int main()
{
    test ();
    test ();
}

Output:

test
Once!
test

如您所见,lambda 在 static 变量第一次进入 scope 时被调用(并且只会被调用一次)。

疯狂猜测:由于缺少InitializeCriticalSectionEx() ,它正在崩溃,这意味着您需要将 Windows VM 升级到 XP 版本,或者向编译器/链接器指定您的目标是 Windows 子系统 v5.02。 不需要补丁。 ;-)

编辑#1:

在黑暗中射击:尝试将-D_WIN32_WINNT=0x0502添加到您的编译中。 它可能会解决一些链接错误。 尽管混合和匹配libstd++版本可能永远不会在这里工作,并且确切的问题会因平台/编译器而异。

As another commenter noted, the initialization of static variables local to a function are now thread-safe in C++, and this seems to have forced some changes to the way TLS works on Windows, right down to the "subsystem" level. 在我们自己的构建中,我们将-Zc:threadSafeInit-/subsystem:windows,5.02 (x64) 或/subsystem:windows,5.01 (x86) 传递给 VC++,以便它在 XP 上工作。 但是,这些新奇的线程安全 static 本地变量不会发生。 我只能想象在 NT 加载程序是多线程的 Windows 10 上可能存在什么问题。

编辑#2:

最后一次窃取赏金的尝试,现在来自 OP 本人:我正在走这条路 我目前的理论是这个新的 clang 11 编译器中存在一个错误,导致它从错误的位置获取“已知” header <iostream> ,尽管--sysroot 也许只有在交叉编译时。

lstrand@styx:~/scratch/emulated-tls$ strace -o strace.out clang++-12 -E 1.exe 1.cpp --target=x86_64-w64-mingw32 --sysroot=/home/lstrand/scratch/emulated-tls/root > pp
clang: warning: 1.exe: 'linker' input unused [-Wunused-command-line-argument]
1.cpp:1:10: fatal error: 'iostream' file not found
#include <iostream>
         ^~~~~~~~~~
1 error generated.
lstrand@styx:~/scratch/emulated-tls$ grep iostream strace.out 
pread64(3, "#include <iostream>\n#include <mu"..., 134, 0) = 134
openat(AT_FDCWD, "/usr/lib/llvm-12/lib/clang/12.0.1/include/iostream", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "./iostream", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
write(2, "'iostream' file not found", 25) = 25
write(2, "#include <iostream>", 19)     = 19
lstrand@styx:~/scratch/emulated-tls$ 

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM