【问题标题】:From where starts the process' memory space and where does it end?进程的内存空间从哪里开始,又从哪里结束?
【发布时间】:2010-04-09 00:27:39
【问题描述】:

在 Windows 平台上,我试图从变量所在的应用程序中转储内存。函数如下:


void MyDump(const void *m, unsigned int n)
{
        const unsigned char *p = reinterpret_cast<const unsigned char *>(m);
        char buffer[16];
        unsigned int mod = 0;

        for (unsigned int i = 0; i < n; ++i, ++mod) {
                if (mod % 16 == 0) {
                        mod = 0;

                        std::cout << " | ";

                        for (unsigned short j = 0; j < 16; ++j) {
                                switch (buffer[j]) {
                                        case 0xa:
                                        case 0xb:
                                        case 0xd:
                                        case 0xe:
                                        case 0xf:
                                                std::cout << " ";

                                                break;

                                        default: std::cout << buffer[j];
                                }
                        }

                        std::cout << "\n0x" << std::setfill('0') << std::setw(8) << std::hex << (long)i << " | ";
                 }

                buffer[i % 16] = p[i];

                std::cout << std::setw(2) << std::hex << static_cast<unsigned int>(p[i]) << " ";

                if (i % 4 == 0 && i != 1)
                        std::cout << " ";
        }
}

现在,我如何知道我的进程内存空间从哪个地址开始,所有变量都存储在哪里?我现在该怎么做,该区域有多长?

例如:


MyDump(0x0000 /* <-- Starts from here? */, 0x1000 /* <-- This much? */);

最好的问候,
nhaa123

【问题讨论】:

  • 在什么样的电脑和操作系统上?

标签: c++ memory pointers


【解决方案1】:

这个问题的简短回答是你不能以这种方式解决这个问题。进程在内存中的布局方式在很大程度上取决于编译器和操作系统,并且不容易确定所有代码和变量的位置。要准确完整地找到所有变量,您需要自己编写调试器的大部分内容(或从真实调试器的代码中借用它们)。

但是,您或许可以稍微缩小您的问题范围。如果你真正想要的只是一个堆栈跟踪,那么生成它们并不难:How can one grab a stack trace in C?

或者,如果您想检查堆栈本身,很容易获得指向当前堆栈顶部的指针(只需声明一个局部变量,然后获取它的地址)。获取堆栈底部的最简单方法是在 main 中声明一个变量,将其地址存储在一个全局变量中,然后将该地址用作“底部”(这很简单,但并不真正“干净”)。

获得堆的图片要困难得多,因为您需要广泛了解堆的内部工作原理才能知道当前分配了哪些部分。由于堆的大小基本上是“无限的”,因此如果您只打印所有数据,甚至是未使用的部分,那么要打印的数据就相当多。我不知道如何做到这一点,我强烈建议您不要浪费时间尝试。

获取静态全局变量的图片并不像堆那么糟糕,但也很困难。这些存在于可执行文件的数据段中,除非您想对可执行格式进行一些汇编和解析,否则也请避免这样做。

【讨论】:

    【解决方案2】:

    概述

    您尝试做的事情绝对是可能的,甚至还有一些工具可以提供帮助,但您必须做的工作量超出我的预期。

    就您而言,您对“变量所在的位置”特别感兴趣。 Windows 上的system heap API 将为您提供难以置信的帮助。参考真的很好,虽然它不是一个连续的区域,但 API 会告诉你变量在哪里。

    不过,一般来说,如果您对内存的布局一无所知,您将不得不对进程的整个地址空间进行扫描。如果你只想要数据,你也必须对其进行一些过滤,因为代码和堆栈废话也在那里。最后,为了避免在转储地址空间时出现段错误,您可能需要添加一个段错误信号处理程序,以便在转储时跳过未映射的内存。

    进程内存布局

    在一个正在运行的进程中,您将拥有多个不相交的内存以打印出来。它们将包括:

    1. 编译后的代码(只读),
    2. 堆栈数据(局部变量),
    3. 静态全局变量(例如来自共享库或在您的程序中),以及
    4. 动态堆数据(来自mallocnew 的所有数据)。

    合理转储内存的关键是能够判断哪个地址范围属于哪个系列。当您转储程序时,这是您的主要工作。其中一些,您可以通过读取函数 (1) 和变量 (2、3 和 4) 的地址来完成,但如果您想打印更多内容,则需要一些帮助。

    为此,我们有...

    有用的工具

    与其盲目地从 0 到 2^64 搜索地址空间(我们都知道,这个地址空间非常庞大),不如使用操作系统和编译器开发工具来缩小搜索范围。有人需要这些工具,也许比你更需要;这只是找到它们的问题。以下是我知道的一些。

    免责声明:我不知道其中许多与 Windows 等效的东西,但我确信它们存在于某个地方。

    我已经提到过 Windows system heap API。这对您来说是最好的情况。在这种情况下你能找到的东西越多,你的转储就越准确和容易。确实,操作系统和 C 运行时对您的程序了解很多。这是提取信息的问题。

    在 Linux 上,可通过 /proc/pid/maps 等实用程序访问内存类型 1 和 3。在 /proc/pid/maps 中,您可以看到为库和程序代码保留的地址空间范围。您还可以看到保护位;例如,只读范围可能是代码,而不是数据。

    对于 Windows 提示,Mark Russinovich 提供了written some articles,了解如何了解 Windows 进程的地址空间以及不同内容的存储位置。我想他可能会有一些好的指示。

    【讨论】:

      【解决方案3】:

      嗯,你不能,不是真的......至少不能以便携的方式。对于堆栈,您可以执行以下操作:

      无效* ptr_to_start_of_stack = 0; int main(int argc, char* argv[]) { int item_at_approximately_start_of_stack; ptr_to_start_of_stack = &item_at_approximately_start_of_stack; // ... // ... 进行大量计算 // ... 这里调用的函数可以做类似的事情,并且 // ... 尝试从 ptr_to_start_of_stack 打印到它自己的 // ... 堆栈的近似开始 // ... 返回0; }

      在尝试查看堆的范围方面,在许多系统上,您可以使用sbrk() 函数(特别是sbrk(0))来获取指向堆开始的指针(通常,它向上增长从地址空间的末尾开始,而堆栈通常从地址空间的开头向下增长。

      也就是说,这是一个非常糟糕的主意。它不仅依赖于平台,而且您从中获得的信息确实不如良好的日志记录有用。我建议你熟悉Log4Cxx

      除了使用调试器(如 GDB)之外,良好的日志记录实践确实是最好的方法。尝试通过查看完整的内存转储来调试程序就像大海捞针一样,因此它确实没有您想象的那么有用。在逻辑上记录问题所在的位置会更有帮助。

      【讨论】:

        【解决方案4】:

        AFAIK,这取决于操作系统,您应该查看例如内存分割。

        【讨论】:

          【解决方案5】:

          假设您在主流操作系统上运行。您需要操作系统的帮助来找出您的虚拟内存空间中的哪些地址已映射页面。例如,在 Windows 上,您将使用 VirtualQueryEx()。您将获得的内存转储可能高达 2 GB,您不太可能很快发现任何可识别的内容。

          您的调试器已经允许您检查任意地址的内存。

          【讨论】:

            【解决方案6】:

            你不能,至少不能便携。而且你也不能做出很多假设。

            除非你在 CP/M 或 MS-DOS 上运行它。

            但在现代系统中,数据和代码的位置和方式在一般情况下并不完全取决于您。

            您可以玩链接器游戏等,以便更好地控制可执行文件的内存映射,但您无法控制您可能加载的任何共享库等。

            例如,无法保证您的任何代码甚至位于连续空间中。虚拟内存和加载器可以将代码放置在它想要的位置。也不能保证您的数据在您的代码附近。事实上,无法保证您甚至可以读取代码所在的内存空间。 (执行,是的。阅读,也许不是。)

            在高层次上,您的程序分为 3 个部分:代码、数据和堆栈。操作系统将它们放置在它认为合适的位置,而内存管理器控制您可以查看的内容和位置。

            有各种各样的东西可以使这些水域变得浑浊。

            但是。

            如果你愿意。

            您可以尝试在代码中添加“标记”。例如,在文件的开头放置一个名为“startHere()”的函数,然后在末尾放置一个名为“endHere()”的函数。如果幸运的话,对于单个文件程序,“startHere”和“endHere”的函数指针之间会有连续的代码块。

            静态数据也是如此。如果您对此感兴趣,可以尝试相同的概念。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2023-04-10
              • 1970-01-01
              • 2011-09-19
              • 1970-01-01
              • 1970-01-01
              • 2010-11-05
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多