使用 GCC 的函数检测,为什么使用 C++ STL 容器或流 I/O 会导致段错误?
Using GCC's function instrumentation, why does using C++ STL containers or stream I/O cause a segfault?
我最近读到了关于使用GCC的代码生成功能(特别是-finstrument-functions编译器标志(轻松地向我的程序添加检测的信息。我觉得这听起来很酷,并在之前的C++项目中进行了尝试。在我的补丁进行了几次修订后,我发现每当我尝试使用 STL 容器或使用C++流 I/O 打印到 stdout 时,我的程序都会立即崩溃并出现段错误。我的第一个想法是维护Event
结构的std::list
typedef struct
{
unsigned char event_code;
intptr_t func_addr;
intptr_t caller_addr;
pthread_t thread_id;
timespec ts;
}Event;
list<Event> events;
这将在程序终止时写入文件。GDB 告诉我,当我尝试向列表中添加Event
时,调用events.push_back(ev)
本身会发起检测调用。这并不令人惊讶,在我考虑了一会儿之后是有道理的,所以计划2。
博客中让我卷入所有这些混乱的例子并没有做任何疯狂的事情,它只是使用 fprintf()
将字符串写入文件。我不认为使用C++的基于流的I/O而不是旧的(f)printf()
会有任何害处,但事实证明这种假设是错误的。这一次,GDB报告了一个看起来相当正常的下降到标准库,而不是一个近乎无限的死亡螺旋......然后是段错误。
一个简短的例子
#include <list>
#include <iostream>
#include <stdio.h>
using namespace std;
extern "C" __attribute__ ((no_instrument_function)) void __cyg_profile_func_enter(void*, void*);
list<string> text;
extern "C" void __cyg_profile_func_enter(void* /* unused */, void* /* unused */)
{
// Method 1
text.push_back("NOPE");
// Method 2
cout << "This explodes" << endl;
// Method 3
printf("This works!");
}
示例 GDB 回溯
方法 1
#0 _int_malloc (av=0x7ffff7380720, bytes=29) at malloc.c:3570
#1 0x00007ffff704ca45 in __GI___libc_malloc (bytes=29) at malloc.c:2924
#2 0x00007ffff7652ded in operator new(unsigned long) ()
from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3 0x00007ffff763ba89 in std::string::_Rep::_S_create(unsigned long, unsigned long, std::allocator<char> const&) () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4 0x00007ffff763d495 in char* std::string::_S_construct<char const*>(char const*, char const*, std::allocator<char> const&, std::forward_iterator_tag) () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5 0x00007ffff763d5e3 in std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&) () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6 0x00000000004028c1 in __cyg_profile_func_enter () at src/instrumentation.cpp:82
#7 0x0000000000402c6f in std::move<std::string&> (__t=...) at /usr/include/c++/4.6/bits/move.h:82
#8 0x0000000000402af5 in std::list<std::string, std::allocator<std::string> >::push_back(std::string&&) (this=0x6055c0, __x=...) at /usr/include/c++/4.6/bits/stl_list.h:993
#9 0x00000000004028d2 in __cyg_profile_func_enter () at src/instrumentation.cpp:82
#10 0x0000000000402c6f in std::move<std::string&> (__t=...) at /usr/include/c++/4.6/bits/move.h:82
#11 0x0000000000402af5 in std::list<std::string, std::allocator<std::string> >::push_back(std::string&&) (this=0x6055c0, __x=...) at /usr/include/c++/4.6/bits/stl_list.h:993
#12 0x00000000004028d2 in __cyg_profile_func_enter () at src/instrumentation.cpp:82
#13 0x0000000000402c6f in std::move<std::string&> (__t=...) at /usr/include/c++/4.6/bits/move.h:82
#14 0x0000000000402af5 in std::list<std::string, std::allocator<std::string> >::push_back(std::string&
...
方法 2
#0 0x00007ffff76307d1 in std::ostream::sentry::sentry(std::ostream&) ()
from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#1 0x00007ffff7630ee9 in std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long) ()
from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#2 0x00007ffff76312ef in std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*) ()
from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3 0x000000000040251e in __cyg_profile_func_enter () at src/instrumentation.cpp:81
#4 0x000000000040216d in _GLOBAL__sub_I__ZN8GLWindow7attribsE () at src/glwindow.cpp:164
#5 0x0000000000402f2d in __libc_csu_init ()
#6 0x00007ffff6feb700 in __libc_start_main (main=0x402cac <main()>, argc=1, ubp_av=0x7fffffffe268,
init=0x402ed0 <__libc_csu_init>, fini=<optimized out>, rtld_fini=<optimized out>,
stack_end=0x7fffffffe258) at libc-start.c:185
#7 0x0000000000401589 in _start ()
环境:
- Ubuntu Linux 12.04 (x64(
- 海湾合作委员会 4.6.3
- 英特尔 3750K 处理器
- 8GB 内存
在检测函数中使用cout
的问题在于,__libc_csu_init()
正在调用检测函数,这是运行时初始化的早期部分 - 在全局C++对象有机会被构造之前(事实上,我认为__libc_csu_init()
负责启动这些构造函数 - 至少是间接的(。
所以cout
还没有机会被建造,试图使用它效果不是很好......
这很可能是您在修复无限递归后尝试使用std::List
时遇到的问题(在Dave S的回答中提到(。
如果愿意在初始化期间丢失一些检测,可以执行以下操作:
#include <iostream>
#include <stdio.h>
int initialization_complete = 0;
using namespace std;
extern "C" __attribute__ ((no_instrument_function)) void __cyg_profile_func_enter(void*, void*);
extern "C" void __cyg_profile_func_enter(void* /* unused */, void* /* unused */)
{
if (!initialization_complete) return;
// Method 2
cout << "This explodes" << endl;
// Method 3
printf("This works! ");
}
void foo()
{
cout << "foo()" << endl;
}
int main()
{
initialization_complete = 1;
foo();
}
第一种情况似乎是无限循环,导致堆栈溢出。 这可能是因为 std::list 是一个模板,它的代码是作为你使用它的翻译单元的一部分生成的。 这会导致它也被检测。 所以你调用push_back,它调用处理程序,它调用push_back,...
第二个,如果我不得不猜测,可能是相似的,尽管很难说。
解决方案是单独编译检测函数,而不使用 -finstrument-函数。 请注意,示例博客单独编译了 trace.c,没有选项。
- 编写代码时C++出现错误:错误 1 错误 C2601:'circle':本地函数定义是非法的
- 如何摆脱C ++中的分段错误错误?
- Clang 8 带有静态 constexpr 和数组的链接器错误 - 错误是什么以及如何解决它?
- 为什么每当我尝试运行此链接列表删除功能时都会收到分段错误错误?
- 如何解决分段错误错误C++
- 作为参数模板的模板类:MSVC 错误 - 错误 C2977:模板参数过多 (C++98)
- 安卓工作室 |CPP 文件错误错误: 位图库中对"AndroidBitmap_unlockPixels"的未定义引用
- 卷曲给出分段错误错误
- 无法访问 Arduino 结构字段。错误"退出状态 1。xxxx 不命名类型"
- 错误错误 C2872:"布尔值":kinect.h 的不明确符号
- C++打印模板容器错误(错误:"运算符<<"的不明确重载)理解?
- 结构的分割错误错误
- 为什么此代码返回分段错误错误?
- 错误错误:无法编译内置功能
- 分段错误错误C++
- C++ 1Z 错误:错误:演绎指南中声明中的显式限定
- 使用对数据类型的向量的哈希表中的分段错误错误
- 为什么此代码会导致分段错误错误
- JNA结构字段值错误
- C++段故障错误