对于单线程程序,我想检查一个给定的虚拟地址是否在进程的堆栈中。 我想在C写的过程中这样做。
我正在考虑读/proc/self/maps
来find标有[stack]的行来获取我的进程堆栈的起始和结束地址。 考虑到这个解决scheme,我带来了以下问题:
/proc/self/maps
显示我的特定进程的一个132k的堆栈和最大的堆栈大小(ulimit -s)在我的系统上是8兆。 Linux如何知道给定的页面错误发生,因为我们超过了堆栈限制属于堆栈(而且堆栈必须变大),而不是我们到达另一个进程内存区域?
Linux收缩堆栈吗? 换句话说,从深层函数调用返回时,操作系统是否减less了堆栈对应的虚拟内存区域?
操作系统最初为堆栈分配了多less虚拟空间?
我的解决scheme是否正确,还有其他更干净的方法吗?
许多堆栈设置细节取决于你正在运行的体系结构,可执行格式和各种内核配置选项(堆栈指针随机化,i386的4GB地址空间等)。
在执行进程的时候,内核选择一个默认的栈顶(例如,在传统的i386 arch上,它是0xc0000000,即虚拟地址空间的用户模式区的末尾)。
可执行格式(ELF vs a.out等)的类型理论上可以改变初始栈顶。 然后完成任何额外的堆栈随机化和任何其他修正(例如,使用时,vdso [系统调用跳板]区域通常放在这里)。 现在你有一个实际的最初的堆栈。
内核现在分配任何需要的空间来为进程构造参数和环境向量等等,初始化堆栈指针,创建初始寄存器值,并启动进程。 我相信这为(3)提供了答案:即内核只分配足够的空间来容纳参数和环境向量,其他页面按需分配。
其他答案,我可以告诉:
(1)当一个进程试图将数据存储在当前堆栈区域底部以下的区域时,会产生页面错误。 内核故障处理程序确定进程虚拟地址空间内的下一个填充的虚拟内存区域开始的位置。 然后看看是什么类型的区域。 如果它是一个“增长”的区域(至少在x86上,所有堆栈区域都应该标记为增长),并且如果进程的堆栈指针(ESP / RSP)值在发生故障时小于底部该区域,如果进程没有超过ulimit -s设置,并且该区域的新大小不会与另一个区域相冲突,则认为这是一个有效的增长堆栈的尝试,并且分配额外的页面以满足这个过程。
(2)不是100%确定,但我不认为有任何缩小堆栈区域的尝试。 假定正常的LRU页面扫描将被执行,使得现在未被使用的区域候选者可以向换出区域寻呼,如果它们真的不被重新使用的话。
(4)你的计划对我来说似乎是合理的:/ proc / NN / maps应该获得整个堆栈区域的起始和结束地址。 我想这将是你的筹码量最大的一次。 当前的实际工作堆栈区OTOH应该驻留在当前的堆栈指针和区域的结尾之间(通常堆栈指针下面的堆栈区域不应该使用)。
我的答案是只用于内核3.12.23的x64上的Linux。 它可能会或可能不适用于其他版本或体系结构。
(1)+(2)在这里我不确定,但我相信就像吉姆·汉密尔顿以前说的那样。
(3)您可以在/ proc / pid / maps(或/ proc / self / maps中查看调用进程的目标地址)中的数量。 然而,并不是所有的东西都可以用作应用程序的堆栈。 参数(argv [])和环境向量(__environ [])通常会在该区域的底部(最高地址)消耗相当多的空间。
要真正找到内核为您的应用程序指定为“堆栈”的区域,您可以查看/ proc / self / stat。 其值在这里记录 。 正如你所看到的,有一个“初始堆栈”的字段。 与映射区域的大小一起,可以计算当前保留的堆栈数量。 与“kstkesp”一起,您可以确定可用堆栈空间的数量或实际使用的堆栈空间(请记住,线程完成的任何操作都可能会更改这些值)。
另外请注意,这只适用于进程主线程! 其他的线程不会得到一个标签映射,而是使用匿名映射,或者甚至最终堆在堆上。 (使用pthreads API来查找这些值,或记住线程主函数中的堆栈启动)。
(4)如(3)中所解释的,你的解决方案大多是好的,但不完全准确。