filesystem :: path被销毁时程序崩溃

如何解决filesystem :: path被销毁时程序崩溃

以下程序崩溃:

#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main()
{
    fs::path p1 = "/usr/lib/sendmail.cf";
 
    std::cout << "p1 = " << p1 << '\n';
}

编译:

$ g++ -std=c++17 pathExistsTest.cpp
$ ./a.out 
p1 = "/usr/lib/sendmail.cf"
[1]    35688 segmentation fault (core dumped)  ./a.out

在Ubuntu 20.04上测试,编译器为GCC 8.4.0。

Valgrind,这是剪切输出:

==30078==    by 0x4AE5034: QAbstractButton::mouseReleaseEvent(QMouseEvent*) (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.12.8)
==30078==    by 0x4A312B5: QWidget::event(QEvent*) (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.12.8)
==30078==  Address 0x2b is not stack'd,malloc'd or (recently) free'd
==30078== 
==30078== 
==30078== Process terminating with default action of signal 11 (SIGSEGV)
==30078==  Access not within mapped region at address 0x2B
==30078==    at 0x13AD9B: std::vector<std::filesystem::__cxx11::path::_Cmpt,std::allocator<std::filesystem::__cxx11::path::_Cmpt> >::~vector() (in /home/(me)/src/tomato/build-src-Desktop-Release/TomatoLauncher)

Full Output
我什至不知道为什么要调用向量dtor?我只创建一个path变量,没有创建vector<path>

解决方法

TL; DR

您正在使用GCC 8.4.0进行编译,因此需要明确链接到-lstdc++fs

因为您使用的是GCC 8.4.0,所以您使用的是GNU C ++标准库,也就是版本GCC 8.4.0的libstdc ++标头。但是您的系统(Ubuntu 20.04)仅包含来自GCC 9的libstdc++.so.6.0.28。如果您未明确链接到-lstdc++fs,则您不小心使用了GCC 9中的std::filesystem符号(通过libstdc++.so),而不是来自GCC 8(通过libstdc++fs.a)。

GCC 8和GCC 9具有不兼容的std::filesystem类型。更具体地说,它们的二进制布局是不同的。这基本上是一种非常隐蔽的违反ODR的行为。您的对象已分配给GCC 8布局,但使用GCC 9布局构造。然后,当您尝试销毁它时,析构函数将使用GCC 8布局并崩溃,因为数据不符合预期。


有两段代码使用path类型的不同,不兼容的布局。

第一段代码来自libstdc++.so.6.0.28:它包含path::_M_split_cmpts()的定义,通过内联构造函数path::path(string_type&&,format)进行调用。由于构造函数是内联的,因此构造函数本身的代码会生成到您的可执行文件中。因此,您的可执行文件包含对path::_M_split_cmpts的调用。

第二段代码在您自己的可执行文件中:它为内联(默认)析构函数path::~path()和其调用的内联函数生成指令;一直到std::filesystem::__cxx11::path::path<char [21],std::filesystem::__cxx11::path>(char const (&) [21],std::filesystem::__cxx11::path::path>(char const (&) [21],std::filesystem::__cxx11::path::format)


我们如何找到它?

使用调试器: 逐步执行ctor中的可疑功能可以发现:

0x5569716498ed <std::filesystem::__cxx11::path::path<char [21],std::filesystem::__cxx11::path::format)+112>       callq  0x5569716491e0 <_ZNSt10filesystem7__cxx114path14_M_split_cmptsEv@plt>

那是通过PLT进行的调用(因此,可能来自共享对象,并且绝对没有内联)。我们进入其中,并且:

(gdb) bt
#0  0x00007f102c60f260 in std::filesystem::__cxx11::path::_M_split_cmpts() () from /lib/x86_64-linux-gnu/libstdc++.so.6
#1  0x00005569716498ed in std::filesystem::__cxx11::path::path<char [21],std::filesystem::__cxx11::path> (this=0x7ffe1a07ad60,__source=...)
    at /usr/include/c++/8/bits/fs_path.h:185
#2  0x00005569716493fd in main () at blub.cpp:6

因此,我们可以看到它确实来自/lib/x86_64-linux-gnu/libstdc++.so.6,它是/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28的符号链接。

我们可以看到的dtor在OP的Valgrind输出中:

==30078== Invalid read of size 8
==30078==    at 0x13AD9B: std::vector<std::filesystem::__cxx11::path::_Cmpt,std::allocator<std::filesystem::__cxx11::path::_Cmpt> >::~vector() (in /home/(me)/src/tomato/build-src-Desktop-Release/TomatoLauncher)

它是内联的,因此在可执行文件中。


现在,真正有趣的部分是包含path的内联函数的标头和path::_M_split_cmpts函数的标头均来自GNU C ++标准库(libstdc ++)。

它们如何不兼容?

要回答这个问题,让我们看一下确切的版本。我们正在使用GCC 8.4.0进行编译。它包含在include路径中,它们引用Ubuntu 20.04的gcc-8软件包中随附的标准库头。它们完全匹配,并且您必须更改默认设置以使GCC使用不同的,不匹配的标准库头。因此,标题是GCC 8.4.0的标题。

共享对象libstdc++.so呢?我们正在根据libstdc++.so.6.0.28和调试器来运行ldd。根据{{​​3}},这是 GCC> = 9.3

libstdc ++。so.6.0.28确实包含_ZNSt10filesystem7__cxx114path14_M_split_cmptsEv的定义:

$ objdump -T /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28 | grep _ZNSt10filesystem7__cxx114path14_M_split_cmptsEv
000000000016a260 g    DF .text  00000000000005f3  GLIBCXX_3.4.26 _ZNSt10filesystem7__cxx114path14_M_split_cmptsEv

根据ABI文档,这是

GCC 9.1.0:GLIBCXX_3.4.26,CXXABI_1.3.12

所以这是GCC 8.4.0中没有的符号。


为什么编译器/链接器没有抱怨?

使用gcc-8进行编译时,为什么编译器或链接器不抱怨我们使用了GCC 9中的符号?

如果我们使用-v进行编译,则会看到链接器调用:

COLLECT_GCC_OPTIONS='-v' '-std=c++17' '-g' '-shared-libgcc' '-mtune=generic' '-march=x86-64'
/usr/lib/gcc/x86_64-linux-gnu/8/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/8/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/8/lto-wrapper -plugin-opt=-fresolution=/tmp/cceJgWPt.res -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -z now -z relro /usr/lib/gcc/x86_64-linux-gnu/8/../../../x86_64-linux-gnu/Scrt1.o /usr/lib/gcc/x86_64-linux-gnu/8/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/8/crtbeginS.o -L/usr/lib/gcc/x86_64-linux-gnu/8 -L/usr/lib/gcc/x86_64-linux-gnu/8/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/8/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/8/../../.. /tmp/ccTNph3u.o -lstdc++ -lm -lgcc_s -lgcc -lc -lgcc_s -lgcc /usr/lib/gcc/x86_64-linux-gnu/8/crtendS.o /usr/lib/gcc/x86_64-linux-gnu/8/../../../x86_64-linux-gnu/crtn.o                           COLLECT_GCC_OPTIONS='-v' '-std=c++17' '-g' '-shared-libgcc' '-mtune=generic' '-march=x86-64'

在这里,我们有-L/usr/lib/gcc/x86_64-linux-gnu/8和其他路径来查找标准库。在那里,我们找到libstdc++.so -> ../../../x86_64-linux-gnu/libstdc++.so.6,它最终指向/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28(!!!)。

因此,链接器具有GCC 9的libstdc++.so,并且不会从编译器(*)接收有关该符号的任何版本信息。编译器只知道源代码,在这种情况下,源代码不包含符号版本(GCC 8.4.0的文件系统头)。但是,符号版本存在于ELF二进制文件libstdc++.so中。链接器在GLIBCXX_3.4.26中看到编译器_ZNSt10filesystem7__cxx114path14_M_split_cmptsEv请求的符号,对此感到满意。让您想知道是否有一个链接器开关告诉链接器“如果我请求未版本化的符号,请不要使用版本化的符号”。

(*)链接器不会从编译器接收有关该未解析符号的任何符号信息,因为编译器从源代码中没有此类信息。您libstdc++ ABI Policy and Guidelines。我不知道libstdc ++通常如何执行它-或它对头文件中的符号版本的策略。看来filesystem根本没有完成。

ELF符号版本控制机制通常应避免这种不兼容性:如果存在与布局不兼容的更改,请创建一个名称相同但版本不同的新符号,并将其添加到{{ 1}},然后同时包含旧版本和新版本。

针对libstdc++.so编译的二进制文件指定了所需的符号版本,动态加载程序会根据名称和版本匹配的符号正确解析未定义的符号。请注意,动态链接器不知道要搜索哪个共享库(在Windows / PE上,这是不同的)。任何“符号请求”都只是一个未定义的符号,并且有一个完全独立的必需库列表,这些库将提供那些未定义的符号。但是二进制文件中没有映射,哪个符号应该来自哪个库。

由于ELF符号版本控制机制允许向后兼容符号添加,因此我们可以为多个版本的编译器维护一个 libstdc++.so。这就是为什么您到处都看到符号链接,导致所有链接都指向同一文件的原因。后缀libstdc++.so是另一种正交版本控制方案,它允许向后-不兼容更改:您可以指定二进制文件.6.0.28,也可以添加不兼容的libstdc++.so.6其他二进制文件。

有趣的事实:如果您将库与纯libstdc++.so.7的GCC 8版本链接,您将看到can add info to your source code。链接到共享库对二进制文件没有多大作用;但是,它确实修复了未解析符号的符号版本,并且可以在查看所有库之后检查是否没有剩余未解析符号。我们可以看到,当您将二进制文件链接到libstdc++.so时,您的二进制文件实际上会请求_ZNSt10filesystem7__cxx114path14_M_split_cmptsEv@GLIBCXX_3.4.26

有趣的事实2 :如果您对纯libstdc++.so.6.0.28的GCC 8版本运行库,则会收到动态链接器错误,因为它找不到libstdc++.so


实际应该发生什么?

您实际上应该链接到_ZNSt10filesystem7__cxx114path14_M_split_cmptsEv@GLIBCXX_3.4.26。它还提供了libstdc++fs.a的定义,它不是符号链接,而是特定于以下GCC版本:_ZNSt10filesystem7__cxx114path14_M_split_cmptsEv

针对/usr/lib/gcc/x86_64-linux-gnu/8/libstdc++fs.a进行链接时,会直接将其符号包含在可执行文件中(因为它是静态库)。可执行文件中的符号优先于共享库中的符号。因此,将使用-lstdc++fs中的_ZNSt10filesystem7__cxx114path14_M_split_cmptsEv


libstdc++fs.a中的布局实际上是什么不兼容?

GCC 9引入了另一种类型来保存路径的组成部分。使用path,我们可以在左侧看到偏移量,并在右侧看到成员和类型名称:

GCC 8.4.0:

clang++ -cc1 -fdump-record-layouts

GCC 9.3.0:

 0 | class std::filesystem::__cxx11::path
 0 |   class std::__cxx11::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > _M_pathname
 0 |     struct std::__cxx11::basic_string<char,class std::allocator<char> >::_Alloc_hider _M_dataplus
 0 |       class std::allocator<char> (base) (empty)
 0 |         class __gnu_cxx::new_allocator<char> (base) (empty)
 0 |       std::__cxx11::basic_string<char,class std::allocator<char> >::pointer _M_p
 8 |     std::__cxx11::basic_string<char,class std::allocator<char> >::size_type _M_string_length
16 |     union std::__cxx11::basic_string<char,class std::allocator<char> >::(anonymous at /usr/include/c++/8/bits/basic_string.h:160:7) 
16 |       char [16] _M_local_buf
16 |       std::__cxx11::basic_string<char,class std::allocator<char> >::size_type _M_allocated_capacity
32 |   class std::vector<struct std::filesystem::__cxx11::path::_Cmpt,class std::allocator<struct std::filesystem::__cxx11::path::_Cmpt> > _M_cmpts
32 |     struct std::_Vector_base<struct std::filesystem::__cxx11::path::_Cmpt,class std::allocator<struct std::filesystem::__cxx11::path::_Cmpt> > (base)
32 |       struct std::_Vector_base<struct std::filesystem::__cxx11::path::_Cmpt,class std::allocator<struct std::filesystem::__cxx11::path::_Cmpt> >::_Vector_impl _M_impl
32 |         class std::allocator<struct std::filesystem::__cxx11::path::_Cmpt> (base) (empty)
32 |           class __gnu_cxx::new_allocator<struct std::filesystem::__cxx11::path::_Cmpt> (base) (empty)
32 |         std::_Vector_base<struct std::filesystem::__cxx11::path::_Cmpt,class std::allocator<struct std::filesystem::__cxx11::path::_Cmpt> >::pointer _M_start
40 |         std::_Vector_base<struct std::filesystem::__cxx11::path::_Cmpt,class std::allocator<struct std::filesystem::__cxx11::path::_Cmpt> >::pointer _M_finish
48 |         std::_Vector_base<struct std::filesystem::__cxx11::path::_Cmpt,class std::allocator<struct std::filesystem::__cxx11::path::_Cmpt> >::pointer _M_end_of_storage
56 |   enum std::filesystem::__cxx11::path::_Type _M_type
   | [sizeof=64,dsize=57,align=8,|  nvsize=57,nvalign=8]

区别在于 0 | class std::filesystem::__cxx11::path 0 | class std::__cxx11::basic_string<char> _M_pathname 0 | struct std::__cxx11::basic_string<char,class std::allocator<char> >::(anonymous at /usr/include/c++/9/bits/basic_string.h:171:7) 16 | char [16] _M_local_buf 16 | std::__cxx11::basic_string<char,class std::allocator<char> >::size_type _M_allocated_capacity 32 | struct std::filesystem::__cxx11::path::_List _M_cmpts 32 | class std::unique_ptr<struct std::filesystem::__cxx11::path::_List::_Impl,struct std::filesystem::__cxx11::path::_List::_Impl_deleter> _M_impl 32 | class std::__uniq_ptr_impl<struct std::filesystem::__cxx11::path::_List::_Impl,struct std::filesystem::__cxx11::path::_List::_Impl_deleter> _M_t 32 | class std::tuple<struct std::filesystem::__cxx11::path::_List::_Impl *,struct std::filesystem::__cxx11::path::_List::_Impl_deleter> _M_t 32 | struct std::_Tuple_impl<0,struct std::filesystem::__cxx11::path::_List::_Impl *,struct std::filesystem::__cxx11::path::_List::_Impl_deleter> (base) 32 | struct std::_Tuple_impl<1,struct std::filesystem::__cxx11::path::_List::_Impl_deleter> (base) (empty) 32 | struct std::_Head_base<1,struct std::filesystem::__cxx11::path::_List::_Impl_deleter,true> (base) (empty) 32 | struct std::filesystem::__cxx11::path::_List::_Impl_deleter (base) (empty) 32 | struct std::_Head_base<0,false> (base) 32 | struct std::filesystem::__cxx11::path::_List::_Impl * _M_head_impl | [sizeof=40,dsize=40,| nvsize=40,nvalign=8]

path::_M_cmpts

您还可以在上面的记录转储中看到// GCC 8 class std::vector< struct std::filesystem::__cxx11::path::_Cmpt,class std::allocator<struct std::filesystem::__cxx11::path::_Cmpt> > _M_cmpts // GCC 9 struct std::filesystem::__cxx11::path::_List _M_cmpts 的结构。它与GCC 8 path::_List完全不兼容。

请记住,我们正在通过GCC 9中的libstdc ++。so调用vector,并且我们正在为此path::_M_split_cmpts数据成员的vector析构函数中崩溃。

这是从_M_cmptsvector的提交:

_List

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-