函数调用栈高级用法
当程序运行异常退出时自动打印当前的函数调用栈,便于分析定位问题;
设计思路:
- 设计一个C++类 CallStack, 该类封装函数调用栈相关信息的搜集与组装;
- 设计一个C函数 callstack_dump(), 该接口创建一个函数调用栈类实例对象并输出函数调用栈;
- 设计一个信号处理函数 signal_SEGV_handler(int),该接口用于调用上一步的API;
- 使用上一步设计的函数,注册为信号处理函数;
模仿android封装一个callstack类
CallStack头文件
/*****************************************
* Copyright (C) 2021 * Ltd. All rights reserved.
*
* File name : CallStack.hpp
* Created date: 2021-07-15 14:25:42
* Description :
*
*******************************************/
#ifndef __CALLSTACK_H__
#define __CALLSTACK_H__
#include <iostream>
#include <stdint.h>
#include <sys/types.h>
namespace DebugTrace {
class CallStack
{
public:
enum {
MAX_DEPTH = 31
};
CallStack();
CallStack(const CallStack& rhs);
~CallStack();
CallStack& operator = (const CallStack& rhs);
bool operator == (const CallStack& rhs) const;
bool operator != (const CallStack& rhs) const;
bool operator < (const CallStack& rhs) const;
bool operator >= (const CallStack& rhs) const;
bool operator > (const CallStack& rhs) const;
bool operator <= (const CallStack& rhs) const;
const void* operator [] (int index) const;
void clear();
void update(int32_t ignoreDepth=0, int32_t maxDepth=MAX_DEPTH);
// Dump a stack trace to the log
void dump(const char* prefix = 0) const;
std::string toString(const char* prefix = 0) const;
size_t size() const { return mCount; }
private:
std::string toStringSingleLevel(const char* prefix, int32_t level) const;
size_t mCount;
void * mStack[MAX_DEPTH];
}; // class CallStack
}; // namespace DebugTrace
#endif //__CALLSTACK_H__
CallStack实现
/*****************************************
* Copyright (C) 2021 * Ltd. All rights reserved.
* File name : CallStack.cpp
* Created date: 2021-07-15 14:28:35
*******************************************/
#include <iostream>
#include <mutex>
#include <string>
#include <vector>
#include <map>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <execinfo.h>
#include "CallStack.hpp"
#if HAVE_DLADDR
#include <dlfcn.h>
#endif
#if HAVE_CXXABI
#include <cxxabi.h>
#endif
namespace DebugTrace {
typedef struct {
size_t count;
size_t ignore;
const void** addrs;
} stack_crawl_state_t;
/*****************************************************************************/
static const char *lookup_symbol(const void* addr, void **offset, char* name, size_t bufSize)
{
#if HAVE_DLADDR
Dl_info info;
void *dli_saddr;
if (dladdr(addr, &info)) {
strncpy(name, info.dli_fname, bufSize);
dli_saddr = info.dli_saddr;
*offset = info.dli_saddr;
if ((unsigned long long)addr >= (unsigned long long)dli_saddr) {
*offset = (void *)((unsigned long long)addr - (unsigned long long)dli_saddr);
}
return info.dli_sname;
}
#endif
return NULL;
}
static int32_t linux_gcc_demangler(const char *mangled_name, char *unmangled_name, size_t buffersize)
{
size_t out_len = 0;
#if HAVE_CXXABI
int status = 0;
char *demangled = abi::__cxa_demangle(mangled_name, 0, &out_len, &status);
if (status == 0) {
// OK
if (out_len < buffersize) {
memcpy(unmangled_name, demangled, out_len);
} else {
out_len = 0;
}
free(demangled);
} else {
out_len = 0;
}
#endif
return out_len;
}
/*****************************************************************************/
class MapInfo
{
struct Maps_Info_St {
struct Maps_Info_St *next;
uint64_t start;
uint64_t end;
char name[];
}; //struct Maps_Info_St
const char *map_to_name(uint64_t pc, const char* def, uint64_t* start) {
Maps_Info_St* mi = getMapInfoList();
while (mi) {
if ((pc >= mi->start) && (pc < mi->end)) {
if (start) {
*start = mi->start;
}
return mi->name;
}
mi = mi->next;
}
if (start) {
*start = 0;
}
return def;
}
Maps_Info_St *parse_maps_line(char *line) {
Maps_Info_St *mi = NULL;
unsigned long long int start = 0;
unsigned long long int end = 0;
char name[128] = { 0 };
char permissions[5];
int len = strlen(line);
if (len < 50) return NULL;
//7fbec608b000-7fbec608c000 r-xp 00000000 08:11 11142411 /home/user/toolkit/c++/callstack/lib/libdebug/libdebug.so
//6f000000-6f01e000 rwxp 00000000 00:0c 16389419 /system/lib/libcomposer.so
if (sscanf(line, "%llx-%llx %4s %*x %*x:%*x %*d %127s",
&start, &end, permissions, name) != 4) {
return NULL;
}
if (permissions[2] != 'x') return NULL;
// printf("len:%d permissions[2]:%c %s", len, permissions[2], line);
mi = (Maps_Info_St*)malloc(sizeof(Maps_Info_St) + strlen(name));
if (mi == NULL) return NULL;
// mi->start = strtoull(line, 0, 16);
// mi->end = strtoull(line + 9, 0, 16);
// strcpy(mi->name, line + 49);
mi->start = (uint64_t)start;
mi->end = (uint64_t)end;
strcpy(mi->name, name);
// printf("0x%lx-0x%lx %s\n", mi->start, mi->end, mi->name);
mi->next = 0;
return mi;
}
Maps_Info_St* getMapInfoList() {
//Mutex::Autolock _l(mLock);
if (mMIlist != NULL) {
return mMIlist;
}
char data[1024] = { 0 };
FILE *fp = NULL;
sprintf(data, "/proc/%d/maps", getpid());
fp = fopen(data, "r");
if (fp) {
while (fgets(data, 1024, fp)) {
Maps_Info_St *mi = parse_maps_line(data);
if (mi) {
mi->next = mMIlist;
mMIlist = mi;
}
}
fclose(fp);
}
return mMIlist;
}
Maps_Info_St* mMIlist;
//Mutex mLock;
static MapInfo sMapInfo;
public:
MapInfo()
: mMIlist(0)
{
}
~MapInfo()
{
while (mMIlist) {
Maps_Info_St *next = mMIlist->next;
free(mMIlist);
mMIlist = next;
}
}
static const char *mapAddressToName(const void* pc, const char* def,
void const** start)
{
uint64_t s;
char const* name = sMapInfo.map_to_name(uint64_t(uintptr_t(pc)), def, &s);
if (start) {
*start = (void*)s;
}
return name;
}
}; //class MapInfo
/*****************************************************************************/
MapInfo MapInfo::sMapInfo;
/*****************************************************************************/
CallStack::CallStack()
: mCount(0)
{
}
CallStack::CallStack(const CallStack& rhs)
: mCount(rhs.mCount)
{
if (mCount) {
memcpy(mStack, rhs.mStack, mCount*sizeof(void*));
}
}
CallStack::~CallStack()
{
}
CallStack& CallStack::operator = (const CallStack& rhs)
{
mCount = rhs.mCount;
if (mCount) {
memcpy(mStack, rhs.mStack, mCount*sizeof(void*));
}
return *this;
}
bool CallStack::operator == (const CallStack& rhs) const
{
if (mCount != rhs.mCount) {
return false;
}
return !mCount || (memcmp(mStack, rhs.mStack, mCount*sizeof(void*)) == 0);
}
bool CallStack::operator != (const CallStack& rhs) const
{
return !operator == (rhs);
}
bool CallStack::operator < (const CallStack& rhs) const
{
if (mCount != rhs.mCount) {
return mCount < rhs.mCount;
}
return memcmp(mStack, rhs.mStack, mCount*sizeof(void*)) < 0;
}
bool CallStack::operator >= (const CallStack& rhs) const
{
return !operator < (rhs);
}
bool CallStack::operator > (const CallStack& rhs) const
{
if (mCount != rhs.mCount) {
return mCount > rhs.mCount;
}
return memcmp(mStack, rhs.mStack, mCount*sizeof(void*)) > 0;
}
bool CallStack::operator <= (const CallStack& rhs) const
{
return !operator > (rhs);
}
const void* CallStack::operator [] (int index) const
{
if (index >= int(mCount)) {
return 0;
}
return mStack[index];
}
void CallStack::clear()
{
mCount = 0;
}
void CallStack::update(int32_t ignoreDepth, int32_t maxDepth)
{
if (maxDepth > MAX_DEPTH) {
maxDepth = MAX_DEPTH;
}
if ((ignoreDepth < 0)) {
ignoreDepth = 0;
}
if ((ignoreDepth > maxDepth)) {
ignoreDepth = 0;
maxDepth = 0;
return;
}
mCount = backtrace(mStack, maxDepth);
mCount -= ignoreDepth;
int32_t i = 0;
for (i = 0; i <= mCount; i++) {
mStack[i] = mStack[i+ignoreDepth];
}
mStack[i] = 0;
}
// Return the stack frame name on the designated level
std::string CallStack::toStringSingleLevel(const char* prefix, int32_t level) const
{
std::string res;
char namebuf[1024];
char tmp[256];
char tmp1[32];
char tmp2[32];
void *offs;
const void* ip = mStack[level];
if (!ip) return res;
if (prefix) res.append(prefix);
snprintf(tmp1, 32, "#%02d ", level);
res.append(tmp1);
// printf("ip: 0x%llx\n", (unsigned long long)ip);
const char* name = lookup_symbol(ip, &offs, namebuf, sizeof(namebuf));
if (name) {
if (linux_gcc_demangler(name, tmp, 256) != 0) {
name = tmp;
}
snprintf(tmp1, 32, "pc %p ", ip);
snprintf(tmp2, 32, "+%p)", offs);
res.append(tmp1); //pc
res.append(namebuf); //object name
res.append(" (");
res.append(name); //symbol name
res.append(tmp2); //offs
} else {
void const* start = 0;
name = MapInfo::mapAddressToName(ip, "<unknown>", &start);
snprintf(tmp, 256, "pc 0x%08lx %s",
long(uintptr_t(ip)-uintptr_t(start)), name);
res.append(tmp);
}
res.append("\n");
return res;
}
// Dump a stack trace to the log
void CallStack::dump(const char* prefix) const
{
/*
* Sending a single long log may be truncated since the stack levels can
* get very deep. So we request function names of each frame individually.
*/
for (int i = 0; i < int(mCount); i++) {
printf("%s", toStringSingleLevel(prefix, i).c_str());
}
}
// Return a string (possibly very long) containing the complete stack trace
std::string CallStack::toString(const char* prefix) const
{
std::string res;
for (int i = 0; i < int(mCount); i++) {
res.append(toStringSingleLevel(prefix, i).c_str());
}
return res;
}
/*****************************************************************************/
}; // namespace DebugTrace
封装一个C函数
封装一个C接口 void callstack_dump(const char *prefix);
其中prefix用于指定每行输出的前缀;
此外还定义了一个 size_t callstack_strings(const char *prefix, char *const buf, const size_t max_size);
将输出信息放入用户传入的参数buf中, buf的大小为 max_size;
callstack_dump头文件
/*****************************************
* Copyright (C) 2021 * Ltd. All rights reserved.
*
* File name : libcallstack.h
* Created date: 2021-07-16 09:10:18
* Description :
*
*******************************************/
//#pragma once
#ifndef __LIBCALLSTACK_H__
#define __LIBCALLSTACK_H__
#ifdef __cplusplus
extern "C"
{
#endif
void callstack_dump(const char *prefix);
size_t callstack_strings(const char *prefix, char *const buf, const size_t max_size);
#ifdef __cplusplus
}
#endif
#endif //__LIBCALLSTACK_H__
callstack_dump实现
/*****************************************
* Copyright (C) 2021 * Ltd. All rights reserved.
*
* File name : libcallstack.cpp
* Created date: 2021-07-15 17:14:30
* Description :
*
*******************************************/
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include "CallStack.hpp"
#include "libcallstack.h"
void callstack_dump(const char *prefix) {
DebugTrace::CallStack cs;
cs.update(2, 32);
cs.dump(prefix);
}
size_t callstack_strings(const char *prefix, char *const buf, const size_t max_size) {
std::string res;
DebugTrace::CallStack cs;
cs.update(2, 32);
res.assign(cs.toString(prefix).c_str(), max_size);
std::copy(res.begin(), res.end(), buf);
return res.size();
}
注意编译的时候注意定义
-DHAVE_CXXABI -DHAVE_DLADDR
并使用-ldl
选项
给一个Makefile的例子
####################################################
# Created date: 2021-07-16 09:00:12
####################################################
#target: prerequisites
# command
DST = libcallstack.so
SRC = CallStack.cpp libcallstack.cpp
OBJS = $(patsubst %.cpp, %.o, $(SRC))
%.o: %.cpp
$(XX) -o $@ -c -fPIC $< $(CXXFLAGS)
XX = g++
CXXFLAGS = -DHAVE_CXXABI -DHAVE_DLADDR
all: $(DST)
$(DST): $(OBJS)
$(XX) -o $@ -shared $^ -ldl -rdynamic
.PHONY: clean
clean:
-rm -v $(DST)
-rm -v $(OBJS)
应用 callstack_dump()
在应用的地方定义一个信号处理函数
void signal_SEGV_handler(int signo) {
std::cout << "signal_SEGV_handler received signal: " << signo << std::endl;
callstack_dump("CALLSTACK_TEST_CPP: ");
/* reset signal handle to default */
signal(signo, SIG_DFL);
/* will receive SIGSEGV again and exit app */
}
注册该信号处理函数
/* register handler of signal SIGSEGV */
signal(SIGSEGV, signal_SEGV_handler);
当应用异常崩溃的时候会收到信号SIGSEGV,然后会调用信号处理函数并打印调用栈
实例
比如程序异常crash并打印如下调用栈
$ ./callstack_test
signal_SEGV_handler received signal: 11
CALLSTACK_TEST_CPP: #00 pc 0x55563954ae02 ./callstack_test (signal_SEGV_handler(int)+0x52)
CALLSTACK_TEST_CPP: #01 pc 0x0003f040 /lib/x86_64-linux-gnu/libc-2.27.so
CALLSTACK_TEST_CPP: #02 pc 0x7f635454d795 ./lib/libdebug/libdebug.so (libdebugfunccrash+0x1b)
CALLSTACK_TEST_CPP: #03 pc 0x7f635454d7b3 ./lib/libdebug/libdebug.so (libdebugfunc5+0x15)
CALLSTACK_TEST_CPP: #04 pc 0x7f635454d7cb ./lib/libdebug/libdebug.so (libdebugfunc4+0x15)
CALLSTACK_TEST_CPP: #05 pc 0x7f635454d7e3 ./lib/libdebug/libdebug.so (libdebugfunc3+0x15)
CALLSTACK_TEST_CPP: #06 pc 0x7f635454d7fb ./lib/libdebug/libdebug.so (libdebugfunc2+0x15)
CALLSTACK_TEST_CPP: #07 pc 0x7f635454d813 ./lib/libdebug/libdebug.so (libdebugfunc1+0x15)
CALLSTACK_TEST_CPP: #08 pc 0x7f635454d82b ./lib/libdebug/libdebug.so (libdebugfunc+0x15)
CALLSTACK_TEST_CPP: #09 pc 0x55563954ad67 ./callstack_test (func6(int, int)+0x1d)
CALLSTACK_TEST_CPP: #10 pc 0x55563954ad7d ./callstack_test (func5()+0x13)
CALLSTACK_TEST_CPP: #11 pc 0x55563954ad89 ./callstack_test (func4()+0x9)
CALLSTACK_TEST_CPP: #12 pc 0x55563954ad95 ./callstack_test (func3()+0x9)
CALLSTACK_TEST_CPP: #13 pc 0x55563954ada1 ./callstack_test (func2()+0x9)
CALLSTACK_TEST_CPP: #14 pc 0x55563954adad ./callstack_test (func1()+0x9)
CALLSTACK_TEST_CPP: #15 pc 0x55563954ae31 ./callstack_test (main+0x1d)
CALLSTACK_TEST_CPP: #16 pc 0x7f6353befbf7 /lib/x86_64-linux-gnu/libc.so.6 (__libc_start_main+0xe7)
CALLSTACK_TEST_CPP: #17 pc 0x55563954ac6a ./callstack_test (_start+0x2a)
Segmentation fault (core dumped)
可知问题出在函数调用 libdebugfunccrash 里面;
通过查看 libdebugfunccrash 的代码发现如下问题:
int libdebugfunccrash(int num) {
(void)num;
char *buff = NULL;
buff[1] = buff[1];// will crash here
}
应用相关参考代码
/*****************************************
* Copyright (C) 2021 * Ltd. All rights reserved.
* File name : libdebug.c
*******************************************/
#include <stdio.h>
#include <stdlib.h>
#include "libdebug.h"
int libdebugfunccrash(int num) {
(void)num;
char *buff = NULL;
buff[1] = buff[1];// will crash here
}
int libdebugfunc5(int num) { libdebugfunccrash(num); }
int libdebugfunc4(int num) { libdebugfunc5(num); }
int libdebugfunc3(int num) { libdebugfunc4(num); }
int libdebugfunc2(int num) { libdebugfunc3(num); }
int libdebugfunc1(int num) { libdebugfunc2(num); }
int libdebugfunc(int num) { libdebugfunc1(num); }
注册信号处理函数参考代码
/*****************************************
* Copyright (C) 2021 * Ltd. All rights reserved.
*
* File name : callstack_test.cpp
* Created date: 2021-07-15 17:14:30
* Description :
*
*******************************************/
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <unistd.h>
#include <signal.h>
#include "libcallstack.h"
#include "libdebug.h"
void func6(int num1, int num2) {
libdebugfunc(num1 + num2);
}
void func5(void) { func6(0, 0); }
void func4(void) { func5(); }
void func3(void) { func4(); }
void func2(void) { func3(); }
void func1(void) { func2(); }
void signal_SEGV_handler(int signo) {
std::cout << "signal_SEGV_handler received signal: " << signo << std::endl;
#if 0
char buf[2048+1] = { 0 };
size_t len = 0;
len = callstack_strings("CALLSTACK_TEST_CPP: ", buf, sizeof(buf)-1);
if (len) {
std::cout << buf << std::flush;
}
#else
callstack_dump("CALLSTACK_TEST_CPP: ");
#endif
/* reset signal handle to default */
signal(signo, SIG_DFL);
/* will receive SIGSEGV again and exit app */
}
int main() {
/* register handler of signal SIGSEGV */
signal(SIGSEGV, signal_SEGV_handler);
func1();
return 0;
}