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

时间:2012-09-02 04:30:39

标签: c++ c gcc instrumentation

我最近阅读了关于使用GCC的代码生成功能(特别是-finstrument-functions编译器标志)来轻松地将检测添加到我的程序中。我认为这听起来很酷,并且在以前的C ++项目中尝试过。经过我的补丁的几次修改后,我发现每当我尝试使用STL容器或使用C ++流I / O打印到stdout时,我的程序会立即崩溃并发生段错误。我的第一个想法是维持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 Backtrace

方法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)
  • GCC 4.6.3
  • Intel 3750K CPU
  • 8GB RAM

2 个答案:

答案 0 :(得分:6)

在instrumentation函数中使用cout的问题是,__libc_csu_init()调用了instrumentation函数,这是运行时初始化的一个非常早期的部分 - 在全局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();
}

答案 1 :(得分:2)

第一种情况似乎是无限循环,导致堆栈溢出。这可能是因为std :: list是一个模板,它的代码是作为您正在使用它的翻译单元的一部分生成的。这导致它也得到了检测。所以你调用push_back,它调用处理程序,调用push_back,...

第二个,如果我不得不猜测,可能是相似的,但它更难说。

解决方案是单独编译检测功能,而不使用-finstrument-functions。注意,示例博客单独编译了trace.c,没有选项。