当程序运行异常退出时自动打印当前的函数调用栈,便于分析定位问题;
设计思路:
/***************************************** * 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__
/***************************************** * 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接口 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)
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; }