首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >为什么MAP_GROWSDOWN映射没有增长?

为什么MAP_GROWSDOWN映射没有增长?
EN

Stack Overflow用户
提问于 2019-07-04 13:11:16
回答 3查看 1.4K关注 0票数 7

我试图创建MAP_GROWSDOWN映射,并期望它能够自动增长。如手册页所述:

MAP_GROWSDOWN 这个标志用于堆叠。它向内核虚拟内存系统表明,映射应该向下扩展到内存中。返回地址比在进程虚拟地址空间中实际创建的内存区域低一页。触摸映射下面的“守护”页面中的地址将导致映射由页面增长。这种增长可以重复,直到映射增长到下一个较低映射的高端的一个页面内,此时触摸“保护”页面将产生一个SIGSEGV信号。

因此,我编写了下面的示例来测试映射的增长:

代码语言:javascript
运行
复制
#ifndef _GNU_SOURCE
    #define _GNU_SOURCE
#endif
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <errno.h>
#include <sys/mman.h>
#include <stdio.h>

int main(void){
    char *mapped_ptr = mmap(NULL, 4096,
                            PROT_READ | PROT_WRITE,
                            MAP_ANONYMOUS | MAP_PRIVATE | MAP_STACK | MAP_GROWSDOWN,
                            -1, 0);
    if(mapped_ptr == MAP_FAILED){
        int error_code = errno;
        fprintf(stderr, "Cannot do MAP_FIXED mapping."
                        "Error code = %d, details = %s\n", error_code, strerror(error_code));
                        exit(EXIT_FAILURE);
    }
    volatile char *c_ptr_1 = mapped_ptr; //address returned by mmap
    *c_ptr_1 = 'a'; //fine

    volatile char *c_ptr_2 = mapped_ptr - 4095; //1 page below the guard
    *c_ptr_2 = 'b'; //crashes with SEGV
}

因此,我得到了SEGV,而不是增长映射。在这里生长是什么意思?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2019-07-04 13:32:50

取代:

代码语言:javascript
运行
复制
volatile char *c_ptr_1 = mapped_ptr - 4096; //1 page below

使用

代码语言:javascript
运行
复制
volatile char *c_ptr_1 = mapped_ptr;

因为:

返回地址比在进程虚拟地址空间中实际创建的内存区域低一页。触摸映射下方的“保护”页面中的地址将导致映射增长。

请注意,我测试了该解决方案,并且它在内核4.15.0-45-泛型上正常工作。

票数 3
EN

Stack Overflow用户

发布于 2019-07-07 10:05:12

首先,您不需要MAP_GROWSDOWN,这不是主线程堆栈的工作方式。用pmap分析进程的内存映射。[叠]没有使用它,而且几乎没有什么应该使用它。手册页上说它是“用于堆栈”的东西是错误的,应该修复。

我怀疑它可能是有问题的(因为没有人使用它,所以通常没有人关心,甚至注意到它是否坏了)。

如果我将mmap 调用更改为映射超过1页,则代码对我有用。具体来说,我尝试了在裸金属(Skylake)上运行Linux5.0.1 (Arch )的4096 * 100**.** 。

/proc/PID/smaps确实显示了一个gd标志。

然后(当单步执行asm时),maps条目实际上改变为一个较低的起始地址,但是相同的结束地址,所以当我以400 K的映射开始时,它实际上是向下增长的。这在返回地址之上提供了400 k的初始分配,当程序运行时,返回地址会增长到404 the。( _GROWSDOWN映射的大小不是增长限制之类的。)

https://bugs.centos.org/view.php?id=4767 可能是相关的;在CentOS 5.3和5.5内核版本之间发生了一些变化。和/或它与在VM (5.3)中工作,而不是在裸金属(5.5)上生长和故障有关。

我将C简化为使用ptr[-4095]等:

代码语言:javascript
运行
复制
int main(void){
    volatile char *ptr = mmap(NULL, 4096*100,
                            PROT_READ | PROT_WRITE,
                            MAP_ANONYMOUS | MAP_PRIVATE | MAP_STACK | MAP_GROWSDOWN,
                            -1, 0);
    if(ptr == MAP_FAILED){
        int error_code = errno;
        fprintf(stderr, "Cannot do MAP_FIXED mapping."
                        "Error code = %d, details = %s\n", error_code, strerror(error_code));
                        exit(EXIT_FAILURE);
    }

    ptr[0] = 'a';      //address returned by mmap
    ptr[-4095] = 'b';  // grow by 1 page
}

gcc -Og编译给出的asm是一个很好的单步。

顺便说一句,关于这面旗子已经从滑翔伞上移除的种种谣言显然是错误的。这个源代码是编译的,很明显内核也支持它,而不是默默地忽略它。(虽然我所看到的4096号而不是400kiB号的行为与被默默忽略的标志完全一致。然而,gd VmFlag仍然存在于smaps中,因此在那个阶段它不会被忽略。)

我检查了一下,在没有接近另一张地图的情况下,它还有增长的空间。所以,当GD映射只有1页时,IDK为什么没有增长。我试过几次,每次都有段错。对于更大的初始映射,它从不出错。

这两次都是使用存储到mmap返回值(映射本身的第一页),然后是在mmap返回值下面4095字节的存储。

票数 6
EN

Stack Overflow用户

发布于 2020-07-02 18:02:09

我知道OP已经接受了其中一个答案,但不幸的是,它并没有解释为什么MAP_GROWSDOWN有时会工作。由于这个堆栈溢出问题是搜索引擎中的第一个热门问题之一,让我为其他人添加我的答案。

MAP_GROWSDOWN的文档需要更新。特别是:

这种增长可以重复,直到映射增长到下一个较低映射的高端的一个页面内,此时触摸“保护”页面将产生SIGSEGV信号。

实际上,内核不允许MAP_GROWSDOWN映射比stack_guard_gap页面离前面的映射更近。默认值是256,但可以在内核命令行中重写它。由于您的代码没有为映射指定任何所需的地址,所以内核会自动选择一个地址,但是很可能在现有映射结束后256页内结束。

编辑

此外,v5.0之前的内核拒绝访问堆栈指针下面超过64k+256字节的地址。详情请参见这个内核提交

这个程序可以在x86上工作,即使使用的是5.0前内核:

代码语言:javascript
运行
复制
#include <sys/mman.h>
#include <stdint.h>
#include <stdio.h>

#define PAGE_SIZE   4096UL
#define GAP     512 * PAGE_SIZE

static void print_maps(void)
{
    FILE *f = fopen("/proc/self/maps", "r");
    if (f) {
        char buf[1024];
        size_t sz;
        while ( (sz = fread(buf, 1, sizeof buf, f)) > 0)
            fwrite(buf, 1, sz, stdout);
        fclose(f);
    }
}

int main()
{
    char *p;
    void *stack_ptr;

    /* Choose an address well below the default process stack. */
    asm volatile ("mov  %%rsp,%[sp]"
        : [sp] "=g" (stack_ptr));
    stack_ptr -= (intptr_t)stack_ptr & (PAGE_SIZE - 1);
    stack_ptr -= GAP;
    printf("Ask for a page at %p\n", stack_ptr);
    p = mmap(stack_ptr, PAGE_SIZE, PROT_READ | PROT_WRITE,
         MAP_PRIVATE | MAP_STACK | MAP_ANONYMOUS | MAP_GROWSDOWN,
         -1, 0);
    printf("Mapped at %p\n", p);
    print_maps();
    getchar();

    /* One page is already mapped: stack pointer does not matter. */
    *p = 'A';
    printf("Set content of that page to \"%s\"\n", p);
    print_maps();
    getchar();

    /* Expand down by one page. */
    asm volatile (
        "mov  %%rsp,%[sp]"  "\n\t"
        "mov  %[ptr],%%rsp" "\n\t"
        "movb $'B',-1(%%rsp)"   "\n\t"
        "mov  %[sp],%%rsp"
        : [sp] "+&g" (stack_ptr)
        : [ptr] "g" (p)
        : "memory");
    printf("Set end of guard page to \"%s\"\n", p - 1);
    print_maps();
    getchar();

    return 0;
}
票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/56888725

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档