如何以编程方式获取Linux进程的堆栈开始和结束地址?

时间:2014-04-23 11:54:56

标签: c linux linux-kernel

对于单线程程序,我想检查给定的虚拟地址是否在进程的堆栈中。我想在用C语言编写的过程中做到这一点。

我正在考虑阅读/proc/self/maps以找到标记为[stack]的行,以获取进程堆栈的起始和结束地址。考虑到这个解决方案,我想到了以下问题:

  1. /proc/self/maps显示我的特定进程的堆栈为132k,堆栈的最大大小(ulimit -s)在我的系统上为8兆。 Linux如何知道由于我们高于堆栈限制而发生的给定页面错误属于堆栈(并且堆栈必须更大)而不是我们到达进程的另一个内存区域?

  2. Linux会缩小堆栈吗?换句话说,例如,当从深层函数调用返回时,操作系统是否会减少与堆栈对应的虚拟内存区域?

  3. 操作系统最初为堆栈分配了多少虚拟空间?

  4. 我的解决方案是否正确,还有其他更清洁的方法吗?

2 个答案:

答案 0 :(得分:5)

许多堆栈设置细节取决于您正在运行的架构,可执行格式和各种内核配置选项(堆栈指针随机化,i386的4GB地址空间等)。

当进程执行时,内核选择默认堆栈顶部(例如,在传统的i386上,它是0xc0000000,即虚拟用户模式区域的末尾)地址空间)。

可执行格式的类型(ELF vs a.out等)理论上可以改变初始堆栈顶部。然后完成任何额外的堆栈随机化和任何其他修正(例如,当使用时,vdso [系统调用跳板]区域通常放在这里)。现在你有一个实际的初始堆栈顶部。

内核现在为进程分配构造参数和环境向量所需的任何空间,初始化堆栈指针,创建初始寄存器值,并启动进程。我相信这为(3)提供了答案:即内核只分配足够的空间来包含参数和环境向量,其他页面按需分配。

其他答案,尽我所知:

(1)当进程尝试将数据存储在堆栈区域当前底部下方的区域中时,会生成页面错误。内核错误处理程序确定进程中下一个填充的虚拟内存区域的位置。虚拟地址空间开始。然后它会查看哪种类型的区域。如果它是"长大"区域(至少在x86上,所有堆栈区域应标记为grow-down),如果进程'故障时的堆栈指针(ESP / RSP)值小于该区域的底部,如果该过程没有超过ulimit -s设置,则该区域的新大小不会与另一个区域发生碰撞,然后它被认为是增加堆栈的有效尝试,并且分配了额外的页面以满足该过程。

(2)不是100%肯定,但我认为没有任何缩小堆叠区域的尝试。据推测,正常的LRU页面扫描将被执行,如果他们真的没有被重新使用,那么现在未使用的区域可以用于寻呼到交换区域。

(4)你的计划对我来说似乎是合理的:/ proc / NN / maps应该整个地获得堆栈区域的起始和结束地址。我认为这将是你的筹码量最大的筹码。当前实际工作堆栈区域OTOH应该位于当前堆栈指针和区域末尾之间(通常没有任何东西应该使用堆栈区域下面堆栈指针)

答案 1 :(得分:4)

我的答案仅适用于内核3.12.23的x64上的linux。它可能适用于或不适用于其他版本或体系结构。

(1)+(2)我在这里不确定,但我相信这是吉尔·汉密尔顿之前所说的。

(3)您可以在/ proc / pid / maps中查看金额(如果您定位调用流程,则可以在/ proc / self / maps中查看)。然而,并非所有这些实际上都可用作应用程序的堆栈。参数 - (argv [])和环境向量(__environ [])通常会占用该区域底部(最高地址)的相当大的空间。

实际找到内核指定为" stack"的区域。对于您的应用程序,您可以查看/ proc / self / stat。它的值记录在here。正如您所看到的,有一个" startstack"的字段。与映射区域的大小一起,您可以计算当前保留的堆栈数量。与" kstkesp"一起,您可以确定可用堆栈空间的数量或实际使用的堆栈空间(请记住,您的线程执行的任何操作很可能会更改这些值)。

另请注意,这仅适用于进程主线程!其他线程不会得到标签" [堆栈]"映射,但要么使用匿名映射,要么甚至可能最终在堆上。 (使用pthreads API查找这些值,或者记住线程main函数中的stack-start)。

(4)如(3)所述,你的解决方案基本上没问题,但并不完全准确。