以下程序崩溃:
#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。
瓦尔格林,这是切割输出:
==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)
全输出
我甚至不知道为什么调用向量 dtor?我只创建一个路径
变量,没有向量
TL;博士
您正在使用 GCC 8.4.0 进行编译,因此您需要针对 -lstdc fs
显式链接。
因为您使用的是GCC 8.4.0,所以您使用的GNU C标准库也称为libstdc头文件,用于版本GCC 8.4.0.但是您的系统(Ubuntu 20.04)只包含来自GCC 9的libstdc.so.6.0.28
。如果您没有显式链接到-lstdc-fs
,然后您意外地使用了来自GCC 9(通过libstdc.so
)的std::filesystem
symbol,而不是来自GCC 8(通过libstdc-fs.a)。
GCC 8和GCC 9具有不兼容的< code>std::filesystem
类型。更具体地说,它们的二进制布局是不同的。这基本上是一个非常隐蔽的ODR违规。您的对象是为GCC 8布局分配的,但却是使用GCC 9布局构造的。当您试图销毁它时,析构函数使用GCC 8布局并崩溃,因为数据不是它所期望的。
有两段代码使用path
类型的不同、不兼容的布局。
第一段代码来自libstdc.so.6.0.28:它包含
路径::_M_split_cmpts()
的定义,通过内联构造函数调用
第二段代码在您自己的可执行文件中:它为内联(默认)析构函数< code>path::~path()
以及它调用的内联函数生成指令;一直到< code > STD::file system::_ _ cxx 11::path::path
我们怎么才能找到这个?
使用调试器:单步调试ctor中的可疑函数揭示了:
0x5569716498ed <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)+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
的符号链接。
我们可以在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
function都来自GNU C Standard库(libstdc)。
它们怎么会不兼容呢?
为了回答这个问题,让我们看看确切的版本。我们正在使用GCC 8.4.0编译。它已经包含了包含路径,它们引用了Ubuntu 20.04的gcc-8包中提供的标准库标头。这些完全匹配,您必须更改默认设置,以使GCC使用不同的、不匹配的标准库标头。因此标头是GCC 8.4.0的标头。
共享对象libstdc.so
怎么样?根据ldd和调试器,我们使用libstdc.so.6.0.28
运行。根据libstdc ABI政策和指南,这是GCC
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-
因此,链接器被赋予GCC 9的
libstdc. so
,它不会从编译器(*)接收任何关于符号的版本信息。编译器只知道源代码,在这种情况下源代码不包含符号版本(GCC 8.4.0的文件系统标头)。然而,符号版本存在于ELF二进制libstdc. so
中。链接器看到编译器_ZNSt10filesystem7__cxx114path14_M_split_cmptsEv
请求的符号的GLIBCXX_3.4.26
并对此感到满意。这让您怀疑是否有一个链接器开关来告诉链接器“如果我请求未版本化的符号,请不要使用版本化的符号”。
(*)链接器不会从编译器接收关于该未解析符号的任何符号信息,因为编译器没有源代码中的此类信息。您可以将信息添加到源代码中。我不知道libstdc通常是怎么做的,也不知道它对头文件中符号版本的策略。对于
文件系统
,似乎根本没有这样做。
ELF符号版本控制机制通常应该防止这种不兼容:如果存在布局不兼容的更改,您可以创建一个具有相同名称但不同版本的新符号,并将其添加到
libstdc. so
,然后该版本包含旧版本和新版本。
根据
libstdc. so
编译的二进制文件指定它想要的符号版本,动态加载程序将未定义的符号与匹配名称和版本的符号正确解析。请注意,动态链接器不知道要搜索哪个共享库(在Windows/PE上,这是不同的)。任何“符号请求”只是一个未定义的符号,并且有一个完全独立的所需库列表,该列表将提供这些未定义的符号。但是二进制文件中没有映射哪个符号应该来自哪个库。
由于 ELF 符号版本控制机制允许向后兼容添加符号,因此我们可以为多个版本的编译器维护单个
libstdc .so
。这就是为什么你到处看到符号链接,导致所有指向同一个文件。后缀 .6.0.28
是另一种正交版本控制方案,它允许向后不兼容的更改:二进制文件可以指定它需要 libstdc .so.6
,并且可以为其他二进制文件添加不兼容的 libstdc .so.7
。
有趣的事实:如果您将您的库链接到一个纯GCC 8版本的< code>libstdc。因此
,您会看到一个链接器错误。链接一个共享库对二进制文件没有太大影响;然而,它可以修复未解析符号的符号版本,并可以在查看所有库后检查是否没有未解析的符号。我们可以看到,当您链接到< code>libstdc .so.6.0.28时,您的二进制文件实际上请求了< code > _ znst 10 file system 7 _ _ cxx 114 path 14 _ M _ split _ cmptsEv @ GLIBCXX _ 3 . 4 . 26 。
有趣的事实2:如果您针对纯GCC 8版本的< code>libstdc运行您的库。所以,您会收到一个动态链接器错误,因为它找不到< code > _ znst 10 file system 7 _ _ cxx 114 path 14 _ M _ split _ cmptsEv @ GLIBCXX _ 3 . 4 . 26 。
实际上应该发生什么?
你实际上应该链接到自由软件
。它还提供了_ZNSt10filesystem7__cxx114path14_M_split_cmptsEv
的定义,它不是一个符号链接,而是特定于这个海湾合作委员会的版本:/usr/lib/gcc/x86_64-linux-gnu/8/libstdc fs.a
。
当链接到< code>-lstdc fs时,它的符号直接包含在可执行文件中(因为它是一个静态库)。可执行文件中的符号优先于共享对象中的符号。因此,使用< code>libstdc fs.a中的< code > _ znst 10 file system 7 _ _ cxx 114 path 14 _ M _ split _ cmptsEv 。
< code>path中布局的不兼容性到底是什么?
GCC 9引入了一种不同的类型来保存路径的组件。使用clang-cc1-fdup-recet-layout
,我们可以在左侧看到偏移量,在右侧看到成员和类型名称:
一般合同条款8.4.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, struct std::char_traits<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, struct std::char_traits<char>, class std::allocator<char> >::pointer _M_p
8 | std::__cxx11::basic_string<char, struct std::char_traits<char>, class std::allocator<char> >::size_type _M_string_length
16 | union std::__cxx11::basic_string<char, struct std::char_traits<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, struct std::char_traits<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]
海湾合作委员会 9.3.0:
0 | class std::filesystem::__cxx11::path
0 | class std::__cxx11::basic_string<char> _M_pathname
0 | struct std::__cxx11::basic_string<char, struct std::char_traits<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, struct std::char_traits<char>, class std::allocator<char> >::pointer _M_p
8 | std::__cxx11::basic_string<char, struct std::char_traits<char>, class std::allocator<char> >::size_type _M_string_length
16 | union std::__cxx11::basic_string<char, struct std::char_traits<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, struct std::char_traits<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, struct std::filesystem::__cxx11::path::_List::_Impl *, false> (base)
32 | struct std::filesystem::__cxx11::path::_List::_Impl * _M_head_impl
| [sizeof=40, dsize=40, align=8,
| nvsize=40, nvalign=8]
区别在于路径:_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
您还可以在上面的记录转储中看到path::_List
的结构。它与GCC 8矢量非常不兼容。
请记住,我们正在通过GCC 9中的libstdc.so调用path::_M_split_cmpts
,并且我们正在这个数据成员的向量
destructor中崩溃。
这是从矢量
更改为_List
的提交:
commit 4f87bb8d6e8dec21a07f1fba641a78a127281349
Author: Jonathan Wakely <jwakely@redhat.com>
Date: Thu Dec 13 20:33:55 2018 +0000
PR libstdc++/71044 optimize std::filesystem::path construction
This new implementation has a smaller footprint than the previous
implementation, due to replacing std::vector<_Cmpt> with a custom pimpl
type that only needs a single pointer. The _M_type enumeration is also
combined with the pimpl type, by using a tagged pointer, reducing
sizeof(path) further still.
Construction and modification of paths is now done more efficiently, by
splitting the input into a stack-based buffer of string_view objects
instead of a dynamically-allocated vector containing strings. Once the
final size is known only a single allocation is needed to reserve space
for it. The append and concat operations no longer require constructing
temporary path objects, nor re-parsing the entire native pathname.
This results in algorithmic improvements to path construction, and
working with large paths is much faster.
问题内容: 我有一个用Restify和Mongoose在node.js中构建的REST服务,以及一个mongoDB,它的集合包含大约30.000个常规大小的文档。我的节点服务通过pmx和pm2运行。 昨天,节点突然开始通过消息“ MongoError:拓扑已被破坏”消除错误,仅此而已。我不知道这是什么意思,可能触发了什么。谷歌搜索时也没有太多发现。所以我想在这里问。 今天重新启动了节点服务后,错误
在我们的应用程序OneBusAway Android(Github上的开源)中,当用户拒绝特定的提醒通知时,我们需要收到通知,因此我们不会为同一事件发布另一个提醒通知(他们的总线到达前多长时间)。 我们通过在我们的应用程序中侦听来做到这一点,该注册为和。当用户拒绝通知时(通过将其滑动或点击通知窗口中的清除按钮),我们的应用程序应该会收到该。 通过测试,在Google Play上的当前版本(以及Gi
我知道: > 从位于类路径中的XML文件加载上下文定义,将上下文定义视为类路径资源。 从文件系统中的XML文件加载上下文定义。 从web应用程序中包含的XML文件加载上下文定义。
只有当我点击图标返回应用程序时,它才会被破坏。如果我打开正在运行的应用程序列表并从那里返回,它将正常恢复。我在onDestroy()函数上设置了一个断点,因为我找不到在它之前调用的任何代码。这是它调用的线程: 看起来它收到了一些消息,从某处破坏了我的活动。在循环器中有一个名为msg的变量。这就是它的价值: 有人知道这里发生了什么吗?如何使我的应用程序恢复正常,就像我从正在运行的应用程序列表中选择它
问题内容: 如何使用Java获取当前计算机的“程序文件”路径? 问题答案: 只需致电 请注意,它仅适用于Windows环境:-)