C++函数调用栈使用进阶

2021/7/20 14:06:21

本文主要是介绍C++函数调用栈使用进阶,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

函数调用栈高级用法

当程序运行异常退出时自动打印当前的函数调用栈,便于分析定位问题;
设计思路:

  1. 设计一个C++类 CallStack, 该类封装函数调用栈相关信息的搜集与组装;
  2. 设计一个C函数 callstack_dump(), 该接口创建一个函数调用栈类实例对象并输出函数调用栈;
  3. 设计一个信号处理函数 signal_SEGV_handler(int),该接口用于调用上一步的API;
  4. 使用上一步设计的函数,注册为信号处理函数;

模仿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;
}


这篇关于C++函数调用栈使用进阶的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程