stackTrace

在muduo的CurrentThread.cc源文件中,有一个函数stackTrace,感觉很有意思,就研究了下是做什么的。

string stackTrace(bool demangle)
{
  string stack;
  const int max_frames = 200;
  void* frame[max_frames];
  int nptrs = ::backtrace(frame, max_frames);
  char** strings = ::backtrace_symbols(frame, nptrs);
  if (strings)
  {
    size_t len = 256;
    char* demangled = demangle ? static_cast<char*>(::malloc(len)) : nullptr;
    for (int i = 1; i < nptrs; ++i)  // skipping the 0-th, which is this function
    {
      if (demangle)
      {
        // https://panthema.net/2008/0901-stacktrace-demangled/
        // bin/exception_test(_ZN3Bar4testEv+0x79) [0x401909]
        char* left_par = nullptr;
        char* plus = nullptr;
        for (char* p = strings[i]; *p; ++p)
        {
          if (*p == '(')
            left_par = p;
          else if (*p == '+')
            plus = p;
        }

        if (left_par && plus)
        {
          *plus = '\0';
          int status = 0;
          char* ret = abi::__cxa_demangle(left_par+1, demangled, &len, &status);
          *plus = '+';
          if (status == 0)
          {
            demangled = ret;  // ret could be realloc()
            stack.append(strings[i], left_par+1);
            stack.append(demangled);
            stack.append(plus);
            stack.push_back('\n');
            continue;
          }
        }
      }
      // Fallback to mangled names
      stack.append(strings[i]);
      stack.push_back('\n');
    }
    free(demangled);
    free(strings);
  }
  return stack;
}

我并不打算详细的讲解函数的功能如何实现,因为我没有看,我只想说一下相关的接口、概念、以及实现的效果。

首先是两个函数backtrace和backtrace_symbols,在这篇博客中有说明和例子,https://www.cnblogs.com/fangyan5218/p/10686488.html。之后我会借用这里的例子,并做一些修改。例子如下:

#include <stdio.h>
#include <execinfo.h>
#include <unistd.h>
#include <stdlib.h>
#define BACKTRACE_SIZE 100

void print_backtrace()
{
    void* buffer[BACKTRACE_SIZE]={0};
    int pointer_num = backtrace(buffer, BACKTRACE_SIZE);
    char** string_buffer = backtrace_symbols(buffer, pointer_num);
    if(string_buffer == NULL)
    {
        printf("backtrace_symbols error");
        exit(-1);
    }
    
    printf("print backtrace begin\n");
    for(int i = 0; i < pointer_num; i++)
    {
        printf("%s\n", string_buffer[i]);
    }
    printf("print backtrace end\n");

    free(string_buffer);
    
    return;
}

void func(int num)
{
    if(num > 0)
    {
        func(--num);
    }
    else
    {
        print_backtrace();
    }
}

int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        printf("input param error");
        return -1;
    }
    
    int input_num = atoi(argv[1]);
    func(input_num);

    return 0;
}

我使用gcc命令编译了该程序,编译命令如下:

gcc backtrace.c -o backtrace -g -rdynamic -std=c99

运行结果如下:

print backtrace begin
./backtrace_c(print_backtrace+0x3a) [0x4008fe]
./backtrace_c(func+0x2b) [0x4009bf]
./backtrace_c(func+0x1f) [0x4009b3]
./backtrace_c(func+0x1f) [0x4009b3]
./backtrace_c(main+0x4e) [0x400a0f]
/lib64/libc.so.6(__libc_start_main+0xfd) [0x3cc8c1ed1d]
./backtrace_c() [0x400809]
print backtrace end

可以看到打印的堆栈信息,使用的函数名。但是如果我使用g++进行编译,修改编译命令为:

g++ backtrace.c -o backtrace -g -rdynamic

这时候执行后的打印信息变成了这样:

print backtrace begin
./backtrace_c(_Z15print_backtracev+0x3a) [0x4009ae]
./backtrace_c(_Z4funci+0x26) [0x400a6b]
./backtrace_c(_Z4funci+0x1f) [0x400a64]
./backtrace_c(_Z4funci+0x1f) [0x400a64]
./backtrace_c(main+0x4b) [0x400ab8]
/lib64/libc.so.6(__libc_start_main+0xfd) [0x3cc8c1ed1d]
./backtrace_c() [0x4008b9]
print backtrace end

可以看到函数的名字被修改了,这个修改就是mangle。而muduo库中stackTrace函数实现的就是demangle,将修改后的名字再改回去。

 将C++源程序标识符(original C++ source identifier)转换成C++ ABI标识符(C++ ABI identifier)
的过程称为mangle;相反的过程称为demangle。

现在我在源码中加入muduo库中的该函数,将代码修改为这样:

#include <stdio.h>
#include <cxxabi.h>
#include <execinfo.h>
#include <unistd.h>
#include <stdlib.h>
#include <string>
using namespace std;
#define BACKTRACE_SIZE 100

string stackTrace(bool demangle)
{
  string stack;
  const int max_frames = 200;
  void* frame[max_frames];
  int nptrs = ::backtrace(frame, max_frames);
  char** strings = ::backtrace_symbols(frame, nptrs);
  if (strings)
  {
    size_t len = 256;
    char* demangled = demangle ? static_cast<char*>(::malloc(len)) : NULL;
    for (int i = 1; i < nptrs; ++i)  // skipping the 0-th, which is this function
    {
      if (demangle)
      {
        // https://panthema.net/2008/0901-stacktrace-demangled/
        // bin/exception_test(_ZN3Bar4testEv+0x79) [0x401909]
        char* left_par = NULL;
        char* plus = NULL;
        for (char* p = strings[i]; *p; ++p)
        {
          if (*p == '(')
            left_par = p;
          else if (*p == '+')
            plus = p;
        }

        if (left_par && plus)
        {
          *plus = '\0';
          int status = 0;
          char* ret = abi::__cxa_demangle(left_par+1, demangled, &len, &status);
          *plus = '+';
          if (status == 0)
          {
            demangled = ret;  // ret could be realloc()
            stack.append(strings[i], left_par+1);
            stack.append(demangled);
            stack.append(plus);
            stack.push_back('\n');
            continue;
          }
        }
      }
      // Fallback to mangled names
      stack.append(strings[i]);
      stack.push_back('\n');
    }
    free(demangled);
    free(strings);
  }
  return stack;
}

void print_backtrace()
{
    // void* buffer[BACKTRACE_SIZE]={0};
    // int pointer_num = backtrace(buffer, BACKTRACE_SIZE);
    // char** string_buffer = backtrace_symbols(buffer, pointer_num);
    // if(string_buffer == NULL)
    // {
    //     printf("backtrace_symbols error");
    //     exit(-1);
    // }
    
    // printf("print backtrace begin\n");
    // for(int i = 0; i < pointer_num; i++)
    // {
    //     printf("%s\n", string_buffer[i]);
    // }
    // printf("print backtrace end\n");

    // free(string_buffer);
    
    // return;
    string s = stackTrace(1);
    printf("%s\n", s.c_str());
}


void func(int num)
{
    if(num > 0)
    {
        func(--num);
    }
    else
    {
        print_backtrace();
    }
}

int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        printf("input param error");
        return -1;
    }
    
    int input_num = atoi(argv[1]);
    func(input_num);

    return 0;
}

编译后执行的打印为:

./backtrace(print_backtrace()+0x1c) [0x401002]
./backtrace(func(int)+0x26) [0x401072]
./backtrace(func(int)+0x1f) [0x40106b]
./backtrace(func(int)+0x1f) [0x40106b]
./backtrace(main+0x4b) [0x4010bf]
/lib64/libc.so.6(__libc_start_main+0xfd) [0x3cc8c1ed1d]
./backtrace() [0x400cd9]

哈哈,可以看到之前被修改的名称改为了函数名加参数的格式,看起来方便多了,而且有一个工具c++filt可以将刚才被mangle的名字改回来,例如执行命令“c++filt  _Z4funci”,结果为“func(int)”,可以看到跟后面使用stackTrace函数打印的名称是一样的。

 

最后想把《程序员的自我修养--链接、装载与库》中的一段文字摘抄在这:

C++符号修饰

众所周知,强大而复杂的C++拥有类、继承、虚机制、重载、名称空间等这些特性,它们使得符号管理更为复杂。最简单的例子,两个相同名字的函数func(int)和func(double),尽管函数名相同,但是参数列表不同,这是C++里面函数重载的最简单的一种情况,那么编译器和链接器在链接过程中如何区分这两个函数呢?为了支持C++这些复杂的特性,人们发明了符号修饰(Name Decoration)和符号改编(Name Mangling)的机制,下面我们来看看C++的符号修饰机制。

首先出现的一个问题是C++允许多个不同参数类型的函数拥有一样的名字,就是所谓的函数重载;另外C++还在语言级别支持名称空间,即允许在不同的名称空间有多个同样名字的符号。比如清单3-4这段代码。

清单3-4  C++函数的名称修饰

int func(int);
float func(float);

class C {
    int func(int);
    class C2 {
        int func(int);
    };
};

namespace N {
    int func(int);
    class C {
        int func(int);
    };
}

这段代码中有6个同名函数叫func,只不过它们的返回类型和参数及所在的名称空间不同。我们引入一个术语叫做函数签名(Function Signature),函数签名包含了一个函数的信息,包括函数名、它的参数类型、它所在的类和名称空间及其他信息。函数签名用于识别不同的函数,就像签名用于识别不同的人一样,函数的名字只是函数签名的一部分。由于上面6个同名函数的参数类型及所处的类和名称空间不同,我们可以认为它们的函数签名不同。在编译器及链接器处理符号时,它们使用某种名称修饰的方法,使得每个函数签名对应一个修饰后名称(Decorated Name)。编译器在将C++源代码编译成目标文件时,会将函数和变量的名字进行修饰,形成符号名,也就是说,C++的源代码编译后的目标文件中所使用的符号名是相应的函数和变量的修饰后名称。C++编译器和链接器都使用符号来识别和处理函数和变量,所以对于不同函数签名的函数,即使函数名相同,编译器和链接器都认为它们是不同的函数。上面的6个函数签名在GCC编译器下,相对应的修饰后名称如表3-18所示。

表3-18

函数签名修饰后名称(符号名)
int func(int)_Z4funci
float func(float)_Z4funcf
int C::func(int)_ZN1C4funcEi
int C::C2::func(int)_ZN1C2C24funcEi
int N::func(int)_ZN1N4funcEi
int N::C::func(int)_ZN1N1C4funcEi

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值