muduo_base 03 (Exception)

本文源码基于muduo v2.0.2分析

UML

Exception

继承自std::exception。如何使用该类可以看Exception_test.cc文件。 函数重点关注如何打印堆栈信息

backtrace打印堆栈

Exception::Exception(string msg)
  : message_(std::move(msg)),
    stack_(CurrentThread::stackTrace(/*demangle=*/false))
{
}

调用Exception的构造函数时会打印当前的堆栈信息。

CurrentThread::stackTrace(bool)

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;
    //下面这块是由于编译器会将函数名字转换,通过abi::__cxa_demangle给转换回来的过程。具体见下文
    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

 #include <execinfo.h>

int backtrace(void **buffer, int size);

char **backtrace_symbols(void *const *buffer, int size);
void backtrace_symbols_fd(void *const *buffer, int size, int fd);

backtrace返回调用者的堆栈信息,指向buffer指针数组。每个buffer的成员是void*类型,为栈帧的地址。size为地址的最大个数,如果超过了该值,则保留最近的size个栈帧,其余截断。函数返回值为地址个数。

backtrace_symbols()将地址转换成描述地址符号的string数组。该函数内部调用了malloc并返回,所以使用完毕需要free掉返回值。函数调用失败返回NULL。

backtrace_symbols_fd同backtrace_symbols,只不过将结果写入了fd中。

__cxa_demangle

至此,虽然已经通过backtrace_symbols将地址转换成可阅读的符号,但是因为编译器会对C++的符号名称进行转换,打印出的信息仍然是不可阅读的。

下面是截取的一段exception_test输出。

//转换后的堆栈信息
Stack inside std::bind:
./exception_test(Bar::callback()+0x12) [0x401f62]
./exception_test(foo()+0x13f) [0x401ebf]
./exception_test(main+0xb) [0x401c9b]
/usr/lib64/libc.so.6(__libc_start_main+0xf5) [0x7f546b93b555]
./exception_test() [0x401ccf]

//未转换的打印
reason: oops
stack trace:
./exception_test(_ZN5muduo9ExceptionC1ENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE+0x50) [0x4021c0]
./exception_test() [0x401b7c]
./exception_test(main+0xb) [0x401c9b]
/usr/lib64/libc.so.6(__libc_start_main+0xf5) [0x7f546b93b555]
./exception_test() [0x401ccf]

可以看到我们并不知道_ZN5muduo9ExceptionC1ENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE+0x50代表了什么。通过__cxa_demangle,可以还原编译器对符号的转换。

/*
Parameters:
mangled_name 	A NUL-terminated character string containing the name to be demangled.
output_buffer 	A region of memory, allocated with malloc, of *length bytes, into which the demangled name is stored. If output_buffer is not long enough, it is expanded using realloc. output_buffer may instead be NULL; in that case, the demangled name is placed in a region of memory allocated with malloc.
length 	If length is non-NULL, the length of the buffer containing the demangled name is placed in *length.
status 	*status is set to one of the following values:
0: The demangling operation succeeded.
-1: A memory allocation failiure occurred.
-2: mangled_name is not a valid name under the C++ ABI mangling rules.
-3: One of the arguments is invalid.
Returns:
A pointer to the start of the NUL-terminated demangled name, or NULL if the demangling fails. The caller is responsible for deallocating this memory using free.
*/
char* abi::__cxa_demangle	(	const char * 	mangled_name,
                            char * 	output_buffer,
                            size_t * 	length,
                            int * 	status	 
                            )	

Reference

abi Namespace Reference

C++ Code Snippet - Print Stack Backtrace Programmatically with Demangled Function Names