gpt4 book ai didi

c++ - 使用 GCC 的函数检测,为什么使用 C++ STL 容器或流 I/O 会导致段错误?

转载 作者:塔克拉玛干 更新时间:2023-11-03 01:24:27 28 4
gpt4 key购买 nike

我最近了解到使用 GCC 的代码生成功能(特别是 -finstrument-functions 编译器标志)可以轻松地向我的程序添加检测。我认为它听起来很酷,并在以前的 C++ 项目中尝试过。在对我的补丁进行了几次修改之后,我发现每当我尝试使用 STL 容器或使用 C++ 流 I/O 打印到标准输出时,我的程序都会立即因段错误而崩溃。我的第一个想法是维护一个 std::listEvent 结构

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。

blog 中的示例这让我卷入了所有这些困惑并没有做任何疯狂的事情,它只是使用 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 回溯

方法一

#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&
...

方法二

#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 CPU
  • 8GB 内存

最佳答案

在检测函数中使用 cout 的问题在于,检测函数由 __libc_csu_init() 调用,这是运行时初始化的早期部分 - 之前全局 C++ 对象有机会被构造(事实上,我认为 __libc_csu_init() 负责启动这些构造函数 - 至少是间接地)。

所以 cout 还没有机会被构造出来,尝试使用它并不是很好......

这很可能是您在修复无限递归 (mentioned in Dave S' answer) 后尝试使用 std::List 时遇到的问题。

如果您愿意在初始化期间丢失一些检测,您可以这样做:

#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();
}

关于c++ - 使用 GCC 的函数检测,为什么使用 C++ STL 容器或流 I/O 会导致段错误?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12233416/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com