是否可以识别一个地址引用是否属于进程地址space中的static/heap/stack

Is it possible to identify whether an address reference belongs to static/heap/stack in the process address space

我们有一种机制可以监控捕获引用地址的加载和存储指令。我想对地址进行分类,它们是属于堆栈,堆还是分配静态变量的区域。有没有办法以编程方式进行这种分类?

我最初的想法是在进程启动后立即执行带有小内存请求(1?)的 malloc() 运行ning 以便我可以捕获 "base address"(或启动地址)堆。这样,我就可以区分那些静态分配的变量和其他变量。对于那些不属于静态区域的引用(那些是堆和堆栈),我该如何区分它们?

一些小测试表明下面的简单代码(运行 in Linux 3.18/x86-64 compiled with gcc 4.8.4)

#include <stdio.h>
#include <stdlib.h>

int x;

int foo (void)
{
    int s;
    int *h = malloc (sizeof(int));

    printf ("x = %p, *s = %p, h = %p\n", &x, &s, h);
}

int main (int argc, char *argv[])
{
    foo();
    return 0;
}

显示了地址的一些随机化 space (不是在静态变量中,而是在其余部分——堆和堆栈中)这可能会增加一些不确定性,但也许是一种找到这些区域的限制的方法地址 space.

我想为了获得正确的结果,您应该在 Linux 上解析 /proc/<pid>/maps 文件。示例内容:

# cat maps
00400000-00407000 r-xp 00000000 fc:02 1837717           /sbin/getty
00606000-00607000 r--p 00006000 fc:02 1837717           /sbin/getty
00607000-00608000 rw-p 00007000 fc:02 1837717           /sbin/getty
00608000-0060a000 rw-p 00000000 00:00 0 
0252e000-0254f000 rw-p 00000000 00:00 0                 [heap]
7f3ca601f000-7f3ca6833000 r--p 00000000 fc:02 2105304   /usr/lib/locale/locale-archive
...
7f3ca7656000-7f3ca7657000 r--p 00022000 fc:02 1711858   /lib/x86_64-linux-gnu/ld-2.19.so
7f3ca7657000-7f3ca7658000 rw-p 00023000 fc:02 1711858   /lib/x86_64-linux-gnu/ld-2.19.so
7f3ca7658000-7f3ca7659000 rw-p 00000000 00:00 0 
7fffbbcf2000-7fffbbd13000 rw-p 00000000 00:00 0         [stack]
7fffbbdfc000-7fffbbdfe000 r-xp 00000000 00:00 0         [vdso]
7fffbbdfe000-7fffbbe00000 r--p 00000000 00:00 0         [vvar]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

参考proc(5).

对此没有标准的 C API,这意味着所有可能的解决方案都将基于特定于平台的 hack。此外,此答案仅限于单线程应用程序。

  1. 如何识别栈地址?

堆栈是一个连续的内存区域。因此,您只需要知道两个数字:栈顶和栈底。栈顶基本上受限于当前函数的栈帧。但是,由于无法从 C 代码访问当前堆栈帧的大小,因此很难判断当前帧的确切结束位置。这里的技巧是从当前调用另一个函数,并使用被调用函数堆栈帧中的地址作为 stack_top.

的边界值

学习栈底更简单——它的值在程序执行期间保持不变,并以入口函数的栈帧为界(main()在C程序中)。因此,在 main() 函数中取一些局部变量的地址是一个足够的近似值。

还有一点需要注意的是x86栈是向后增长的,这意味着栈顶的地址比栈底的地址小。这段代码总结起来:

void *stack_bottom;

bool IS_IN_STACK(void *x) __attribute__((noinline));
bool IS_IN_STACK(void *x) {
    void *stack_top = &stack_top;
    return x <= stack_bottom && x >= stack_top;
}

int main (int argc, char *argv[]) {
   int x;
   stack_bottom = &x;
   ...
  1. 如何识别静态变量的地址?

这里的逻辑更简单。静态变量分配在以固定的、特定于平台的地址开始的内存区域中。通常这个区域先于内存中的所有其他区域。因此,唯一需要学习的是这个静态内存区域的 end 地址。

幸运的是,GCC 链接器 provides symbols endedataetext 表示 .bss.data.text段分别。静态变量分配在 .bss.data 段中,因此此检查在大多数平台上应该足够了:

#define IS_STATIC(x) ((void*)(x) <= (void*)&end || (void*)(x) <= (void*)&edata)

此宏同时检查 edataend,以避免假设 .bss.data 中的哪一个在内存中排在第一位。

  1. 堆地址。​​

堆变量通常直接分配在 .data.bss 区域地址之后的地址中。但是,有时堆地址可能属于不连续的内存范围。因此,您在这里可以做的最好的事情是阅读 Linux 进程文件以找出其他答案中建议的内存映射。或者,只需检查 IS_IN_STACKIS_STATIC return 是否都为 false.

使用这些宏的完整程序:

int x;
extern int end, edata;

void *stack_bottom;

bool IS_IN_STACK(void *x) __attribute__((noinline));
bool IS_IN_STACK(void *x) {
    void *stack_top = &stack_top;
    return x <= stack_bottom && x >= stack_top;
}

#define IS_STATIC(x) ((void*)(x) <= (void*)&end || (void*)(x) <= (void*)&edata)

int foo (void)
{
    int s;
    int *h = malloc (sizeof(int));

    printf ("x = %p, *s = %p, h = %p\n", &x, &s, h);
    // prints 0 1 0
    printf ("%d %d %d\n", IS_IN_STACK(&x), IS_IN_STACK(&s), IS_IN_STACK(h));
    // prints 1 0 0
    printf ("%d %d %d\n", IS_STATIC(&x), IS_STATIC(&s), IS_STATIC(h));
}

int main (int argc, char *argv[])
{
    int x;
    stack_bottom = &x;
    foo();
    return 0;
}