使用pmap分析进程的内存映射. [堆]

我试图了解堆栈在Linux中是如何工作的.我阅读了有关堆栈和进程初始化的AMD64 ABI部分,并且不清楚堆栈应该如何映射.这是相关的引用(3.4.1):

Stack State

This section describes the machine state that exec (BA_OS) creates for
new processes.

It is unspecified whether the data and stack segments are initially
mapped with execute permissions or not. Applications which need to
execute code on the stack or data segments should take proper
precautions, e.g., by calling mprotect().

因此,我可以从上面的引文中推断出堆栈已被映射(如果使用PROT_EXEC来创建映射,则未指定).映射也是由exec创建的.

问题是“主线程”的堆栈是否使用MAP_GROWSDOWN | MAP_STACK映射,甚至可能通过sbrk?

看看pmap -x< pid>堆栈标有[stack] as

00007ffc04c78000     132      12      12 rw---   [ stack ]

创建映射为

mmap(NULL, 4096,
     PROT_READ | PROT_WRITE,
     MAP_ANONYMOUS | MAP_PRIVATE | MAP_STACK,
     -1, 0);

只需创建匿名映射,如pmap -x< pid>中所示.如

00007fb6e42fa000       4       0       0 rw---   [ anon ]

解决方法:

I can deduce from the quotes above that the stack is mapped

字面意思就是分配内存.即,存在从那些虚拟地址到物理页面的逻辑映射.我们知道这一点是因为您可以在_start中使用push或call指令而无需从用户空间进行系统调用来分配堆栈.

事实上,x86-64 System V ABI指定argc,argv和envp在进程启动时位于堆栈中.

The question is whether the “main thread”‘s stack uses MAP_GROWSDOWN | MAP_STACK mapping or maybe even via sbrk?

ELF二进制加载器为主线程的堆栈设置_GROWSDOWN标志,但不为MAP_STACK标志设置.这是内核中的代码,它不会通过常规的mmap系统调用接口.

(用户空间中没有任何内容使用mmap(MAP_GROWSDOWN),所以通常主线程堆栈是唯一在内核中具有VM_GROWSDOWN标志的映射.)

用于堆栈的虚拟内存ise(VMA)的标志的内部名称称为VM_GROWSDOWN.如果您感兴趣,以下是用于主线程堆栈的所有标志:VM_GROWSDOWN,VM_READ,VM_WRITE,VM_MAYREAD,VM_MAYWRITE和VM_MAYEXEC.另外,如果指定ELF二进制文件具有可执行堆栈(例如,通过用gcc -z execstack编译),则还使用VM_EXEC标志.请注意,在支持向上增长的堆栈的体系结构上,如果内核是使用CONFIG_STACK_GROWSUP定义编译的,则使用VM_GROWSUP而不是VM_GROWSDOWN.可以在here中找到在Linux内核中指定这些标志的代码行.

/proc/…/maps和pmap不使用VM_GROWSDOWN – 它们依赖于地址比较.因此,他们可能无法准确确定主线程堆栈占用的虚拟地址空间的确切范围(请参阅example).另一方面,/ proc /… / smaps查找VM_GROWSDOWN标志,并将具有此标志的每个内存区域标记为gd. (虽然它似乎忽略了VM_GROWSUP.)

所有这些工具/文件都忽略MAP_STACK标志.事实上,整个Linux内核忽略了这个标志(这可能是程序加载器没有设置它的原因.)用户空间只传递它以便在将来验证的情况下内核确实要特别开始处理线程堆栈分配.

sbrk在这里毫无意义;堆栈与“break”不相邻,并且brk堆无论如何都朝向堆栈向上增长. Linux使堆栈非常接近虚拟地址空间的顶部.所以当然主堆栈不能分配(内核相当于)sbrk.

不,没有使用MAP_GROWSDOWN,甚至没有使用辅助线程堆栈,因为它通常不能安全使用.

将MAP_GROWSDOWN称为“用于堆栈”的mmap(2)手册页是可笑的过时和误导.请参阅How to mmap the stack for the clone() system call on linux?.作为Ulrich Drepper explained in 2008,使用MAP_GROWSDOWN的代码通常会被破坏,建议从Linux mmap和glibc头中删除该标志. (这显然没有发生,但是pthreads从那时起就没有使用它,如果有的话.)

MAP_GROWSDOWN为内核中的映射设置VM_GROWSDOWN标志.主线程也使用该标志来启用增长机制,因此线程堆栈可能能够以与主堆栈相同的方式增长:如果堆栈指针低于页面错误位置,则任意远(直到ulimit -s?) . (Linux不需要“堆栈探测器”来触摸大型多页堆栈阵列或alloca的每个页面.)

(线程堆栈在前面完全分配;只有物理页面的正常延迟分配才能支持虚拟分配,避免浪费线程堆栈的空间.)

MAP_GROWSDOWN映射也可以增长mmap手册页描述的方式:访问最低映射页面下方的“保护页面”也会触发增长,即使它低于红色区域的底部.

但主线程的堆栈具有mmap(MAP_GROWSDOWN)无法获得的魔力.它将增长空间保留为ulimit -s,以防止随机选择mmap地址,从而创建阻止增长的障碍.这种魔法仅适用于内核程序加载器,它在execve()期间映射主线程的堆栈,从而使mmap(NULL,…)随机阻止未来的堆栈增长.

mmap(MAP_FIXED)仍然可以为主堆栈创建一个包版广告,但是如果你使用MAP_FIXED,则100%负责不破坏任何内容. (Unlimited stack cannot grow beyond the initial 132KiB if MAP_FIXED involved?). MAP_FIXED将替换现有的映射和​​保留,但其他任何东西都会将主线程的堆栈增长空间视为保留; (我认为这是真的;值得尝试使用MAP_FIXED_NOREPLACE或仅使用非NULL提示地址)

看到

> How is Stack memory allocated when using ‘push’ or ‘sub’ x86 instructions?
> Why does this code crash with address randomization on?

pthread_create不会将MAP_GROWSDOWN用于线程堆栈,其他任何人都不应该使用它.一般不要使用.默认情况下,Linux pthreads为线程堆栈分配完整大小.这会花费虚拟地址空间,但(直到它实际触及)不是物理页面.

Why is MAP_GROWSDOWN mapping does not grow?评论中的不一致结果(有些人发现它有效,有些人在触摸返回值和下面的页面时发现它仍然是段错误)听起来像https://bugs.centos.org/view.php?id=4767 – MAP_GROWSDOWN甚至可能在标准主堆栈VM_GROWSDOWN映射的方式之外是错误的用过的.

上一篇:Python – 使用请求直接下载文件到内存


下一篇:c-是否可以将现有缓冲区映射到新文件?