如何确定C ++中详细的调用堆栈信息? [英] How do I determine detailed call-stack information in C++?

查看:101
本文介绍了如何确定C ++中详细的调用堆栈信息?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想更好地理解调用堆栈,为此,我想进一步使用它.

I’d like to understand the call-stack a bit better, and in an effort to do so, I’d like to work with it some more.

我将如何处理此信息?我不知道!虽然学习新事物很有趣,但这是我今天的新事物.

What will I do with this information? I have no idea! It’s fun to learn new things though, and this is my new thing for today.

我不喜欢程序中的调用栈是一个我一无所知的神秘实体.它有多大?我当前的调用堆栈上有多少可用内存? 在哪里?我不知道!而且,我想知道.

I don’t like the call-stack in my programs being this mystical entity that I know nothing about. How big is it? How much memory is available on my current call-stack? Where is it? I don't know! And, I would like to know.

我当前处理调用栈的方法是知道它,但是直到我遇到栈溢出错误时才有意识地与之交互. 那还不够好!

My current way to deal with the call-stack is to be aware of it, but to not knowingly interact with it until I hit a stack overflow error. That's just not good enough!

因此,在我的任务中,我想弄清楚如何执行以下操作:

So in my quest, I’d like to figure out how to do the following:

  1. 我想知道我正在操作的当前调用堆栈的总大小.

我想计算当前调用堆栈上可用的总可用内存.

我希望能够找到我的调用堆栈开始的地址.

声明可能看起来像这样,并且可以在各种平台中填写:

The declaration could look like this, and be filled in for various platforms:

class call_stack
{
    inline static void* base_address();
    inline static void* end_address();
    inline static std::size_t size();
    inline static std::size_t remaining();
};

如何在当前的台式机和移动平台上定义堆栈信息,如何访问?如何在编译时对此进行编码,或者在运行时确定它?

How is stack information defined on current desktop and mobile platforms, and how can I access it? How can I code this at compile time, or determine it at runtime?

对此进行回顾,使我想起了赫伯·萨特(Herb Sutter)谈及原子的话题.他说了一些类似的话:作为C ++程序员,我们喜欢按下红色的大按钮,说不要碰."

Looking into this reminds me of a talk that Herb Sutter gave about atomics. He said something along the lines of "as C++ programmers, we like to push the big red buttons that say don’t touch."

我意识到关于调用栈的很多问题,我相信我的既有意义又独特.

I realize there are many questions about the call-stack, and I believe that mine is both meaningful and unique.

关于调用栈主题的其他问题并没有像我在这里询问的那样广泛地询问调用栈.

推荐答案

您没有要求在标准c ++中完成任何操作,因为标准中未指定堆栈.

None of what you ask can be done in standard c++ since the stack is not specified in the standard.

您可以通过以汇编语言读取cpu寄存器来访问该信息.如何执行此操作取决于cpu体系结构,操作系统以及编译器使用的调用约定.查找所需信息的最佳位置是体系结构,操作系统等手册.平台也可能通过系统调用或虚拟文件系统提供信息.

You may be able to access that information by reading cpu registers in assembly language. How to do that depends on the cpu architecture, the OS and possibly the calling convention used by the compiler. Best place to find the information you're looking for is the manual for the architecture, OS, etc. The platfrom may also provide the information through system calls or virtual filesystems.

作为示例,下面是常见x86体系结构的维基百科页面的快速浏览

As an example, here's a quick look at the wikipedia page for the common x86 architecture

SP/ESP/RSP:堆栈顶部地址的堆栈指针.

SP/ESP/RSP: Stack pointer for top address of the stack.

BP/EBP/RBP:堆栈基指针,用于保存当前堆栈帧的地址.

BP/EBP/RBP: Stack base pointer for holding the address of the current stack frame.

您可以展开堆栈,并在第一个调用帧中找到堆栈顶部.再次介绍如何取消堆栈,具体取决于所使用的调用约定.用当前堆栈指针减去第一个堆栈帧的底部,将得出当前堆栈的大小.另外请记住,每个线程都有自己的调用堆栈,因此您必须从正确线程的堆栈底部减去.

You can unwind the stack and find find the top of the stack in the first call frame. How to unwind the stack is again, specific to calling convention used. Subtracting the base of the first stack frame with the current stack pointer would give you the current size of the stack. Also remember that each thread has their own call stack so you must subtract from the bottom of the stack of the correct thread.

但是请记住:

尽管主寄存器(指令指针除外)在指令集的32位和64位版本中是通用的",可用于任何事物...

Although the main registers (with the exception of the instruction pointer) are "general-purpose" in the 32-bit and 64-bit versions of the instruction set and can be used for anything...

在假定寄存器的用途之前,应先查看目标平台的手册.

You should check the manual of your target platform before assuming what the registers are used for.

获取剩余/总最大堆栈空间可能会有些棘手.在Linux中,堆栈大小通常在运行时受到限制.可以通过/proc/文件系统或使用系统调用来访问该限制.在Windows中,最大堆栈大小可以在链接时设置,并且应该可以在可执行文件头中访问.

Getting the remaining / total maximum stack space can be a bit trickier. In Linux the stack size is typically limited during runtime. The limit can be accessed through the /proc/ filesystem or using system calls. In Windows, the maximum stack size can be set at linktime and should be accessible in the executable file headers.

下面是在Linux上运行的示例程序.我从/proc/<pid>/stat读取了堆栈的开始.我还提供了一个展开示例,为此,我使用了一个库,该库抽象了所有OS/体系结构特定的汇编代码.堆栈一直一直展开到main之前的初始化代码,并说明了该堆栈使用的堆栈空间.

Below is an example program that works on Linux. I read the start of stack from /proc/<pid>/stat. I also provide an example for unwinding and for that I use a library that abstracts away all the OS / architecture specific assembly code. The stack is unwound all the way up to the initialization code before main and the stack space used by that is accounted for.

我使用SP寄存器而不是BP在fisrt调用框架中获取堆栈的底部,因为在某些体系结构中不存在BP,并且在我的平台上,初始化框架中的BP为零.这意味着底部与第一个调用帧的大小无关,因此仅是一个近似值.在 coliru 上看到它,不幸的是,拒绝访问/proc/<pid>/stat.

I use SP register instead of BP to get the bottom of the stack in the fisrt call frame because BP does not exist in some architectures and on my platform it was zero in the initialization frames. That means that the bottom is off by the size of the first call frame and is therefore just an approximation. See it on coliru, unfortunately access to /proc/<pid>/stat is denied there.

#include <iostream>
using namespace std;

#include <fstream>
#include <sstream>
#include <unistd.h>
// read bottom of stack from /proc/<pid>/stat
unsigned long bottom_of_stack() {
    unsigned long bottom = 0;
    ostringstream path;
    path << "/proc/" << getpid() << "/stat";
    ifstream stat(path.str());
    // possibly not the best way to parse /proc/pid/stat
    string ignore;
    if(stat.is_open()) {
        // startstack is the 28th field
        for(int i = 1; i < 28; i++)
            getline(stat, ignore, ' ');
        stat >> bottom;
    }
    return bottom;
}

#include <sys/resource.h>
rlim_t get_max_stack_size() {
    rlimit limits;
    getrlimit(RLIMIT_STACK, &limits);
    return limits.rlim_cur;
}

#define UNW_LOCAL_ONLY
#include <libunwind.h>

// using global variables for conciseness
unw_cursor_t frame_cursor;
unw_context_t unwind_context;

// approximate bottom of stack using SP register and unwinding
unw_word_t appr_bottom_of_stack() {
    unw_word_t bottom;
    unw_getcontext(&unwind_context);
    unw_init_local(&frame_cursor, &unwind_context);
    do {
        unw_get_reg(&frame_cursor, UNW_REG_SP, &bottom);
    } while(unw_step(&frame_cursor) > 0);
    return bottom;
}

// must not inline since that would change behaviour
unw_word_t __attribute__((noinline)) current_sp() {
    unw_word_t sp;
    unw_getcontext(&unwind_context);
    unw_init_local(&frame_cursor, &unwind_context);
    unw_step(&frame_cursor); // step to frame before this function
    unw_get_reg(&frame_cursor, UNW_REG_SP, &sp);
    return sp;
}

// a little helper for absolute delta of unsigned integers
#include <algorithm>
template<class UI>
UI abs_udelta(UI left, UI right) {
    return max(left,right) - min(left,right);
}

unsigned long global_bottom;
rlim_t global_max;

// a test function to grow the call stack
int recurse(int index) {
    if(index < 2 ) {
        auto stack_size = abs_udelta(current_sp(), global_bottom);
        cout << "Current stack size: " << stack_size
             << "\tStack left: " << global_max - stack_size << '\n';
        return index;
    }
    return recurse(index - 1) + recurse(index - 2); // do the fibonacci
}

int main() {
    global_max = get_max_stack_size();
    global_bottom = bottom_of_stack();
    auto appr_bottom = appr_bottom_of_stack();
    cout << "Maximum stack size: "
         << global_max << '\n';
    cout << "Approximate bottom of the stack by unwinding: "
         << (void*)appr_bottom << '\n';
    if(global_bottom > 0) {
        cout << "Bottom of the stack in /proc/<pid>/stat: "
             << (void*)global_bottom << '\n';
        cout << "Approximation error: "
             << abs_udelta(global_bottom, appr_bottom) << '\n';
    } else {
        global_bottom = appr_bottom;
        cout << "Could not parse /proc/<pid>/stat" << '\n';
    }
    // use the result so call won't get optimized out
    cout << "Result of recursion: " << recurse(6);
}

输出:

Maximum stack size: 8388608
Approximate bottom of the stack by unwinding: 0x7fff64570af8
Bottom of the stack in /proc/<pid>/stat: 0x7fff64570b00
Approximation error: 8
Current stack size: 640 Stack left: 8387968
Current stack size: 640 Stack left: 8387968
Current stack size: 576 Stack left: 8388032
Current stack size: 576 Stack left: 8388032
Current stack size: 576 Stack left: 8388032
Current stack size: 576 Stack left: 8388032
Current stack size: 576 Stack left: 8388032
Current stack size: 512 Stack left: 8388096
Current stack size: 576 Stack left: 8388032
Current stack size: 576 Stack left: 8388032
Current stack size: 512 Stack left: 8388096
Current stack size: 512 Stack left: 8388096
Current stack size: 512 Stack left: 8388096
Result of recursion: 8

这篇关于如何确定C ++中详细的调用堆栈信息?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆