【问题标题】:Wrong line numbers from addr2line来自 addr2line 的错误行号
【发布时间】:2012-07-19 17:45:04
【问题描述】:

我尝试在 C++ 程序的回溯中找到调用的确切行。现在我正在使用这些行(来自 backtrace 的手册页)来获取跟踪:

  void *bt_buffer[1000];
  char **bt_strings;
  int bt_nptrs = backtrace(bt_buffer, 1000);
  bt_strings = backtrace_symbols(bt_buffer, bt_nptrs);

在 bt_strings 中,我找到格式为

的行
./prog() [0x402e42]

现在我获取地址(十六进制字符串)并将其提供给 addr2line。这有时会导致明显错误的行号。互联网搜索让我找到了这个post,其中显示

readelf -wl ./prog

表示真正行在哪里,或者更确切地说,符号移动到当前行的行数。

编辑: 当我使用 -g -O0 编译时会发生这种情况,即明确地没有优化。编译器是gcc 4.6.3 还有我想念的另一个编译器标志吗?

我的问题如下:我需要自动执行此操作。我需要我的程序创建回溯(完成),提取文件(完成)和行号(失败)。

我当然可以调用readelf 并解析输出,但这并不合适,因为输出因符号而异,具体取决于具体发生的情况。有时符号的地址在一行中,而关于行偏移的信息在下一行...

总结一下:

是否有一种优雅的方法可以在运行时从程序内部获取回溯中函数调用的确切行号?

编辑:示例代码:

#define UNW_LOCAL_ONLY
#include <libunwind.h>
#include <execinfo.h>
#include <iostream>
#include <stdlib.h>

void show_backtrace()
{
  // get current address
  void* p = __builtin_return_address(0);
  std::cout << std::hex << p << std::endl;

  // get callee addresses
  p = __builtin_return_address(1);
  std::cout << std::hex << p << std::endl;

  p = __builtin_return_address(2);
  std::cout << std::hex << p << std::endl;
}

void show_backtrace2()
{
  void *array[10];
  size_t size;
  char **strings;
  int i;

  size = backtrace (array, 10);
  strings = backtrace_symbols ((void *const *)array, size);

  for (i = 0; i < size; i++)
    {
      std::cout << strings[i] << std::endl;
    }

  free (strings);
}

void show_backtrace3 (void)
{
  char name[256];
  unw_cursor_t cursor; unw_context_t uc;
  unw_word_t ip, sp, offp;

  unw_getcontext (&uc);
  unw_init_local (&cursor, &uc);

  while (unw_step(&cursor) > 0)
    {
      char file[256];
      int line = 0;

      name[0] = '\0';
      unw_get_proc_name (&cursor, name, 256, &offp);
      unw_get_reg (&cursor, UNW_REG_IP, &ip);
      unw_get_reg (&cursor, UNW_REG_SP, &sp);

      std::cout << std:: hex << name << " ip = " << (long) ip 
                << " , sp = " << (long) sp << std::endl;
    }
}

void dummy_function2()
{
  show_backtrace();
  show_backtrace2();
  show_backtrace3();
}

void dummy_function1()
{ 
  dummy_function2 (); 
} // line 73

int main(int argc, char **argv)
{
  dummy_function1 ();
  return 0;
}

编译运行:

g++ test_unwind.cc -g -O0 -lunwind  && ./a.out

输出:

0x400edb
0x400ef0
0x400f06
./a.out() [0x400cfb]
./a.out() [0x400ee0]
./a.out() [0x400ef0]
./a.out() [0x400f06]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xed) [0x7f2f044ae76d]
./a.out() [0x400b79]
_Z15dummy_function2v ip = 400ee5 , sp = 7fffdb564580
_Z15dummy_function1v ip = 400ef0 , sp = 7fffdb564590
main ip = 400f06 , sp = 7fffdb5645a0
__libc_start_main ip = 7f2f044ae76d , sp = 7fffdb5645c0
_start ip = 400b79 , sp = 7fffdb564680

测试例如0x400ef0 与 addr2line 产量

/path/to/code/test_unwind.cc:73

这是正确的文件,但行号错误。在现实生活中的应用程序中,行号可以前后相差很多行。

edit:-S编译显示相关部分是:

.LCFI34:
  .cfi_def_cfa_register 6
  .loc 2 72 0
  call  _Z15dummy_function2v
  .loc 2 73 0
  popq  %rbp

addr2line等所显示的是返回地址,如call后面的行所示。我想获得“入口”行,即之前显示的内容!

【问题讨论】:

  • 在没有优化的情况下编译会出错吗?
  • 尝试gdb执行可执行文件并使用l *0x&lt;address&gt;。它给出了正确的地址,还是也给出了与addr2line相同的地址?
  • 也许是一个愚蠢的评论,但是.. 第 73 行不是实际的返回地址吗? IE。通话结束后返回哪里?换句话说就是压栈的地址?
  • 你是否也试过用汇编输出-S编译它,看看什么汇编代码对应什么行?这帮助我解决了类似的问题(在 Windows 中)。
  • 这里也需要这个,我偶然发现了这个问题。我只需要不时手动使用它,但是通过在将地址提供给 addr2line 之前从地址中减去 1 得到了很好的结果,这似乎使它达到了调用指令。您是否找到了可能也想在这里发布的解决方案?

标签: c++ c callstack backtrace binutils


【解决方案1】:

你当然可以!我知道一个使用libunwind 的示例实现。见这篇博文:http://blog.bigpixel.ro/stack-unwinding-stack-trace-with-gcc/

归结为这段代码(字面上从文章中复制):

void show_backtrace (void)
{
    char name[256];
    unw_cursor_t cursor; unw_context_t uc;
    unw_word_t ip, sp, offp;

    unw_getcontext(&uc);
    unw_init_local(&cursor, &uc);

    while (unw_step(&cursor) > 0)
    {
        char file[256];
        int line = 0;

        name[0] = '\0';
        unw_get_proc_name(&cursor, name, 256, &offp);
        unw_get_reg(&cursor, UNW_REG_IP, &ip);
        unw_get_reg(&cursor, UNW_REG_SP, &sp);

        //printf ("%s ip = %lx, sp = %lx\n", name, (long) ip, (long) sp);
        getFileAndLine((long)ip, file, 256, &line);
        printf("%s in file %s line %d\n", name, file, line);
    }
}

【讨论】:

  • 我很高兴一到办公室就尝试一下!
  • 我试过了,发现在getFileAndLine 中,他们还使用popen 调用addr2line。 libunwind 很有趣(为此 +1),但具有与回溯类似的功能。结果是相同的偏移行号。
  • 在您的问题中您说您正在使用-g 进行编译,也许您应该改用-ggdb
  • 感谢您的提示。我尝试了所有可用的调试符号标志,结果是一样的。
  • 有什么方法可以在不使用 popen 和 addr2line 实用程序的情况下获取文件和行号?
【解决方案2】:

这是因为在大多数架构上,程序计数器 points to an instruction after the current executing instruction。您仍然可以使用 addr2line,但为 x86 添加 -1 或为 aarch32 添加 -8 以获取源代码行的实际地址。向后 CPP 使用相同的想法。 https://github.com/bombela/backward-cpp/blob/master/backward.hpp(搜索 ip_before_instruction)。我发现唯一不减去的时间是地址是发生崩溃的地方。这是有道理的,因为在调用错误处理程序(例如,为页面错误提供服务)之后,它可以继续执行错误指令。

【讨论】:

  • 伟大的总结!谢谢
【解决方案3】:

你试过了吗

__LINE__

它是一个预处理器符号,但你可以编译它。

【讨论】:

  • 我也想过__FILE____LINE__,但我想从foo()调用的函数中获取foo()的信息。而且我无法更改 foo() 的调用方式。
猜你喜欢
  • 2011-09-17
  • 2011-08-23
  • 2019-01-24
  • 1970-01-01
  • 2013-08-05
  • 1970-01-01
  • 2010-12-14
  • 2013-07-05
  • 1970-01-01
相关资源
最近更新 更多