我尝试创建MAP_GROWSDOWN
映射,并以其期望自动增长。如手动页面中指定:
map_growsdown
此标志用于堆栈。它指示内核虚拟内存系统,映射应向下扩展 记忆。返回地址比内存区域低一个页面 实际上是在该过程的虚拟地址空间中创建的。 触摸映射下方的"警卫"页面中的地址将导致通过页面生长的映射。可以重复这种增长,直到 映射生长到下一个较低的高端的一页之内 映射,此时触摸"警卫"页面的映射将导致
SIGSEGV
信号。
所以我写了以下示例来测试映射的生长:
#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 = %sn", 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
而不是生长映射。在这里成长是什么意思?
首先,您不需要MAP_GROWSDOWN
,而不是主线程堆栈的工作方式。用PMAP分析过程的内存映射。[堆栈]什么都没有使用,几乎什么都不应使用使用它。人页面上的东西说它"用于堆栈"是错误的,应修复。
我怀疑它可能是越来越小的(因为没有什么使用它,所以通常没有人在乎,甚至注意到它是否破裂。(
(如果将 ,然后(单步进ASM时( https://bugs.centos.org/view.php?id=4767可能是相关的; 在CentOS 5.3和5.5中的内核版本之间发生了变化。和/或它与在VM中工作有关(5.3(与不在裸机上生长和过错有关(5.5(。 我简化了C使用 用 顺便说一句,关于旗帜已从GLIBC删除的各种谣言显然是错误的。该来源确实编译了,很明显,它也得到了内核的支持,而不是默默地忽略。(尽管我看到的行为大小为4096而不是400KiB完全一致,但标志被忽略。但是mmap
调用更改为1页以上,则您的代码对我有用。具体来说,我尝试了 4096 * 100
。/proc/PID/smaps
确实显示了gd
标志。maps
条目实际上确实更改为较低的启动地址,但端地址相同,因此当我从400K映射开始时,它实际上会向下增长。这给出了400K初始分配上面的返回地址,该返回地址在程序运行时生长为404KIB。( _GROWSDOWN
映射的大小是不是增长限制或类似的东西。(ptr[-4095]
等: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 = %sn", 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对单步的效果很好。gd
VMFLAG仍然存在于smaps
中,因此在该阶段不忽略它。(
我检查了一下,有空间可以生长,而无需接近另一个映射。因此,当GD映射仅1页时,为什么它没有增长。我尝试了几次,每次都会遇到。使用较大的初始映射,它永远不会错。
两次都在商店到MMAP返回值(正确的映射的第一页(,然后是该商店4095字节以下。
我知道OP已经接受了其中一个答案,但不幸的是,它并不能解释为什么MAP_GROWSDOWN
有时似乎有时起作用。由于此堆栈溢出问题是搜索引擎中的最早打击,因此让我为他人添加答案。
MAP_GROWSDOWN
的文档需要更新。特别是:
可以重复此增长,直到映射生长到页面内下一个较低映射的高端,此时接触"守卫"页面将导致sigsegv信号。
实际上,内核不允许 MAP_GROWSDOWN
映射比远离前面映射的stack_guard_gap
页更近。默认值为256,但可以在内核命令行上覆盖。由于您的代码没有为映射指定任何所需的地址,因此内核会自动选择一个地址,但很可能从现有映射的末尾最终进入256页。
编辑:
此外,v5.0之前的内核拒绝访问堆栈指针下方超过64K 256个字节的地址。请参阅此内核提交详细信息。
即使在5.0前内核中,此程序也可以在x86上工作:
#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 %pn", 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 %pn", 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]" "nt"
"mov %[ptr],%%rsp" "nt"
"movb $'B',-1(%%rsp)" "nt"
"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;
}
替换:
volatile char *c_ptr_1 = mapped_ptr - 4096; //1 page below
volatile char *c_ptr_1 = mapped_ptr;
因为:
返回地址比该过程的虚拟地址空间中实际创建的内存区域低一个页面。触摸映射下方的"警卫"页面中的地址通过页面。
请注意,我测试了该解决方案,并且在内核4.15.0-45代时可以按预期工作。