从何处开始进程的内存空间以及它在何处结束?

时间:2010-04-09 00:27:39

标签: c++ memory pointers

在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

6 个答案:

答案 0 :(得分:7)

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

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

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

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

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

答案 1 :(得分:2)

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

void* 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;
    // ... 
    // ... do lots of computation
    // ... a  function called here can do something similar, and
    // ... attempt to print out from ptr_to_start_of_stack to its own
    // ... approximate start of stack
    // ... 
    return 0;
}

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

那就是说,这是一个非常糟糕的主意。它不仅依赖于平台,而且您可以从中获取的信息实际上没有良好的日志记录那么有用。我建议你熟悉Log4Cxx

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

答案 2 :(得分:2)

概述

你想要做的事情是绝对可能的,甚至还有工具可以提供帮助,但是你需要做的工作比我想象的要多。

在您的情况下,您对“变量所在的位置”特别感兴趣。 Windows上的system heap API将为您提供难以置信的帮助。该引用非常好,虽然它不会是一个连续的区域,但API会告诉您变量的位置。

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

处理内存布局

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

  1. 编译代码(只读),
  2. 堆栈数据(局部变量),
  3. 静态全球(例如来自共享库或您的程序)和
  4. 动态堆数据(来自mallocnew的所有内容)。
  5. 合理转储内存的关键是能够分辨哪个地址属于哪个系列。这是你的主要工作,当你倾销程序时。其中一些,你可以通过读取函数(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 :(得分:1)

AFAIK,这取决于操作系统,你应该看看,例如记忆分割。

答案 4 :(得分:1)

假设您正在使用主流操作系统。您需要操作系统的帮助才能找出虚拟内存空间中哪些地址映射了页面。例如,在Windows上,您将使用VirtualQueryEx()。你将获得的内存转储可能大到2千兆字节,你不太可能发现任何可识别的东西。

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

答案 5 :(得分:0)

你不能,至少不能携带。你也不能做出很多假设。

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

但是对于现代系统,在通用情况下,数据和代码所在位置和位置并不完全取决于您。

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

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

在较高级别,您的程序分为3个部分:代码,数据和堆栈。操作系统将这些放置在它认为合适的位置,内存管理器控制您可以看到内容的位置和位置。

有各种各样的东西可以淹没这些水域。

然而

如果你想。

您可以尝试在代码中使用“标记”。例如,将一个函数放在文件的开头,名为“startHere()”,然后在函数结尾处调用一个名为“endHere()”的函数。如果你很幸运,对于单个文件程序,你会在“startHere”和“endHere”的函数指针之间连续得到一些代码。

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