有本事就出来,没本事就当鳖!

假设让我回答关于进程栈,线程栈的问题,仅仅要问题不笼统,仅仅要问题明白。我会一五一十地回答,正确率上九成,然而,可悲的是,问题往往他妈的都不是非常明白,因此,游戏到此结束。!

艹。可是假设给我一个问的机会。我会问以下一个问题,记住。使出你拉屎的劲来回答(该问题足够糙。不必太当回事,重要的东西在以下-):

UNIX/Linux的stack在大多数平台是向下扩展的(注意,我已经告诉事实了。我并没有问...是怎样扩展的,这是能够背诵下来并朗读出来的),在一个运行流调用了一个函数A,而该函数A在stack上分配了一个大数组导致了stack扩展(注意,这又是一段陈述。我还没有给出问题),然后A返回了,UNIX/Linux理应回收调A里面大数组分配stack空间-由于它再也没实用了。可是它并没有这么做(这里可能是一个陷阱,真的是UNIX/Linux理应这么做却没有做,还是说我仅仅是在逗你玩...不确定,但陈述就是如此)。(注意,我的问题来了),请问,UNIX/Linux为什么这么做???!!!

       凡是回答操作系统这么规定的之类的答案,一律零分!况且。你能证明我说的一定对吗?万一我是逗你玩呢?操作系统能规定一个错的东西吗?或者我能够继续问为什么这么规定,直到像我初中的历史老师被我问到朝我眼角猛打一拳那样。假设我能找回点贱人所谓的快感,那么来打吧!问题就是这样,无论这个问题是一个伪命题还是你有自己的想法,能说5分钟的,我认为也够能够了。该题目的答题要求例如以下:
时间限制:5分钟。

作答方式:全口述。不能绘图,不能打手势...语言含糊不清的。表达能力不好的,算错误。
答题建议:假设你对OS虚拟内存管理以及Linux的VMA实现细节没有相当深入的理解,请不要推測答案。请直接回答“不知道”,然后看完此文。

.................................
5分钟过去。我要发布一点我的想法了。
       首先,这个问题在本身看来。有问题。由于尽管Linux理应这么做,但它:
第一,它不一定能做到;
第二,它根本没有必要做。
那么论据是什么?凭什么这样说?

积极论点

不是必需这样做。运行流还会调用别的函数或者再次调用A。频繁回收栈损耗性能。

消极论点

非常难或者不能做到。

stack操作是处理器控制的,和OS内核地址空间管理机制之间没有同步机制。一个函数调用结束后。CPU自己主动处理stack寄存器的收缩,弹出栈帧。然而它无法通知OS内存管理系统去更新进程地址空间的映射关系。

怎样处理stack所在地址空间区域的争议

stack会一直扩展到碰到异常的地址B,B可能是一个readonly的地址或者是一个保护空洞。在向下扩展stack情况下。假设地址B偏上,会导致stack空间变小,假设偏下,一旦函数局部变量差点儿占满了stack底到B的空间,mmap尽管也能unmap掉这段区域然后remap,然而这会使数据混乱,造成严重问题。

拍脑袋的结论

mmap或者brk期间,比較stack顶部与esp寄存器,若小于则回收(等于是正常的。大于是不可能的)。

Linux真实的做法

Linux没有推断什么esp寄存器,Linux的原则非常easy,仅仅要一个地址处在一个vma范围内或者处在stack可扩展的范围内,且拥有权限的。它就是能够訪问,内核是无论这个VMA是属于stack还是heap或者别的什么。详细由应用程序自己控制。也就是说,你全然能够写一段代码。把地址空间中所有能够写的区域所有清零,这全然有可能。缓冲区溢出可能是一种蓄意的破坏,然而程序猿偶然的错误也会造成破坏,尽管他们大多数都不知道错误是怎样发生的。我不想用文字长篇大论Linux是怎样管理VMA的,你知道这个应该是一个前提,你必须知道这个。

我用一段代码以及两个图示来展示Linux系统内核是怎样管理stack附近的地址空间映射的,而且在第二张图中给出,假设你非要蓄意破坏。会造成什么问题。

也就是说。一旦发生莫名奇异的错误,你必须能从细节上理解这个错误是怎样发生的。

演示代码

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/types.h>
  4. #include <unistd.h>
  5. #include <sys/mman.h>
  6.  
  7. #define LARGE 70000000
  8. #define PAGESIZE 4096
  9.  
  10. // 该函数什么都不做。仅仅为了把stack向下扩展
  11. // 请注意用ulimit将stack限制大小去掉。这样会更easy说明问题
  12. void call()
  13. {
  14. int i;
  15. char a[LARGE];
  16.  
  17. // 请相信,一定是在中间的赋值中触发segfault,由于两边的元素要么处在stack/fixmap vma,
  18. // 要么处在游离的,及其孤独的,被fixmap给截断了的vma中。因此以下的赋值不会引发段错误:
  19. // a[0] = 1;
  20. // a[LARGE-1] = 1;
  21. for (i = 0; i < LARGE; i++) {
  22. a[i] = 1;
  23. }
  24. }
  25.  
  26. int main(int argc, char **argv)
  27. {
  28. int i;
  29. char *p_map, *p_base, *p_base2;
  30.  
  31. printf("%d\ninit state\n", getpid());
  32.  
  33. // 获取stack的大致地址,而且PAGESIZE对齐。
  34.  
  35. p_base = (char *)&i;
  36. p_base2 = (char *)(((unsigned long)p_base) & ~4095);
  37.  
  38. // 获取pagesize对齐的用来fixmap的地址,该地址起点在当前stack的以下。
  39. p_base2 = (char *)((unsigned long)p_base2 - (unsigned long)36*PAGESIZE);
  40. getchar();
  41.  
  42. // 调用fixmap,显然。假设你细致在getchar期间分析了/proc/xx/maps文件而且
  43. // 得到了上述的那些magic number,以下的mmap无论怎样是会成功的!
  44. p_map = (char *)mmap((void*)p_base2, PAGESIZE*3, PROT_READ | PROT_WRITE,
  45. MAP_FIXED |MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  46. if (p_map == MAP_FAILED) {
  47. printf("failed 1\n");
  48. } else {
  49. printf("before unmap fixmap around stack\n");
  50. getchar();
  51. // 成功了就释放掉它,此时的地址空间恢复成mmap之前的状况
  52. munmap(p_map, PAGESIZE*3);
  53. }
  54.  
  55. printf("after unmap fixmap around stack\n");
  56. getchar();
  57.  
  58. call();
  59.  
  60. printf("after extend stack[first]\n");
  61. getchar();
  62.  
  63. // 依旧调用之前的那个一模一样的mmap进行fixmap,由于调用了call,stack
  64. // 空间已经扩展到了这个fixmap的fixaddress,非常遗憾,成功了。然而它将stack vma
  65. // 一刀切成了两段。无论怎样。訪问还是能够进行的。
  66. p_map = (char *)mmap((void*)p_base2, PAGESIZE*3, PROT_READ | PROT_WRITE,
  67. MAP_FIXED |MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  68. if (p_map == MAP_FAILED) {
  69. printf("failed 2\n");
  70. }
  71.  
  72. printf("after second fixmap around stack at the same address\n");
  73. getchar();
  74.  
  75. // 这里更狠。部分unmap了上面那个fixmap vma,留下一个空洞。
  76. munmap(p_map, PAGESIZE);
  77.  
  78. printf("after unmap fixmap around stack incompletely\n");
  79. getchar();
  80.  
  81. // 当空洞被touch的时候,不会引发stack extend!而是直接segfault!
  82.  
  83. 爆炸!
  84.  
  85. call();
  86.  
  87. // 永远不会到达这里!
  88. printf("after extend stack[second]\n");
  89. getchar();
  90.  
  91. return 0;
  92. }

针对上述代码的图解

以下一幅图展示一直到出事之前,该进程的stack附近的地址空间映射区域是怎么演化的:

以下一幅图展示出事的过程以及这个事故的原因:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZG9nMjUw/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />

測试方式

假设你认为图是我自己画出来的,那么肯定有一个疑问。我是基于什么画出来的,其实,我并非通过看代码画出来的。我是通过不断查看procfs的maps文件实时了解该进程地址空间的细节,将其转换成了上面的图示,为了让我有机会到还有一个终端去查maps文件,我在代码中添加了getchar调用。每次查看完maps文件。我会拍一下键盘的回车键。我的測试例如以下:

代码编译后的进程输出

root@abcd:~# ./a.out
7846
init state

before unmap fixmap around stack

after unmap fixmap around stack

after extend stack[first]

after second fixmap around stack at the same address

after unmap fixmap around stack incompletely

段错误 (core dumped)

以下是查看每一步进程maps文件的输出:

root@abcd:~# cat /proc/`ps -e|grep a.out|awk '{print $1}'`/maps |tail -n 6|head -n 4
2b2c01483000-2b2c01487000 r--p 00157000 fe:00 387296                     /lib/libc-2.11.2.so
2b2c01487000-2b2c01488000 rw-p 0015b000 fe:00 387296                     /lib/libc-2.11.2.so
2b2c01488000-2b2c0148f000 rw-p 00000000 00:00 0
7fff6236a000-7fff6237f000 rw-p 00000000 00:00 0                          [stack]
root@abcd:~# cat /proc/`ps -e|grep a.out|awk '{print $1}'`/maps |tail -n 6|head -n 4
2b2c01487000-2b2c01488000 rw-p 0015b000 fe:00 387296                     /lib/libc-2.11.2.so
2b2c01488000-2b2c0148f000 rw-p 00000000 00:00 0
7fff62359000-7fff6235c000 rw-p 00000000 00:00 0
7fff6236a000-7fff6237f000 rw-p 00000000 00:00 0                          [stack]
root@abcd:~# cat /proc/`ps -e|grep a.out|awk '{print $1}'`/maps |tail -n 6|head -n 4
2b2c01483000-2b2c01487000 r--p 00157000 fe:00 387296                     /lib/libc-2.11.2.so
2b2c01487000-2b2c01488000 rw-p 0015b000 fe:00 387296                     /lib/libc-2.11.2.so
2b2c01488000-2b2c0148f000 rw-p 00000000 00:00 0
7fff6236a000-7fff6237f000 rw-p 00000000 00:00 0                          [stack]
root@abcd:~# cat /proc/`ps -e|grep a.out|awk '{print $1}'`/maps |tail -n 6|head -n 4
2b2c01483000-2b2c01487000 r--p 00157000 fe:00 387296                     /lib/libc-2.11.2.so
2b2c01487000-2b2c01488000 rw-p 0015b000 fe:00 387296                     /lib/libc-2.11.2.so
2b2c01488000-2b2c0148f000 rw-p 00000000 00:00 0
7fff5e0bb000-7fff6237f000 rw-p 00000000 00:00 0                          [stack]
root@abcd:~# cat /proc/`ps -e|grep a.out|awk '{print $1}'`/maps |tail -n 6|head -n 4
2b2c01488000-2b2c0148f000 rw-p 00000000 00:00 0
7fff5e0bb000-7fff62359000 rw-p 00000000 00:00 0
7fff62359000-7fff6235c000 rw-p 00000000 00:00 0
7fff6235d000-7fff6237f000 rw-p 00000000 00:00 0                          [stack]
root@abcd:~# cat /proc/`ps -e|grep a.out|awk '{print $1}'`/maps |tail -n 6|head -n 4
2b2c01488000-2b2c0148f000 rw-p 00000000 00:00 0
7fff5e0bb000-7fff62359000 rw-p 00000000 00:00 0
7fff6235a000-7fff6235c000 rw-p 00000000 00:00 0
7fff6235d000-7fff6237f000 rw-p 00000000 00:00 0                          [stack]
我怕上面的文字信息太乱,格式在不同浏览器会有问题。我还特意截了一张图:

有什么用

你能够用这样的方式彻底限制一个进程的stack的大小。越界了不是报错,而是segfault。然后你能够signal捕获这个segfault,在里面把那个未全然unmap的fixmap vma以及那个可怜且孤独的残缺的stack vma给彻底unmap掉。只是这确实没什么好玩的。

有什么用呢?它的作用就是让你更加深入理解Linux对虚拟地址空间的管理方式。

小Tips

本文不涉及线程栈,可是倒也不难,线程栈一般在heap区或者中间的大块mmap区动态分配,mmap的时候给它一个MAP_GROWSDOWN标志就能够了。关于它的管理方式。没啥区别。核心问题在于。缺页异常处理程序是怎么识别到一个缺页是一个vma内部的缺页(结果就是调页),还是vma外部的缺页。在后一种情况下。缺页处理逻辑还要进一步识别是stack的缺页(结果就是extend stack然后调页),还是非stack缺页(结果就是segfault...)。


       Linux的stack除非遇到本文所述的这样的方式的挤兑收缩,它是永远扩展的。假设你想阅读Linux的内核代码。那么也须要理解以下的事实:
1.find_vma函数能找到vma仅仅有一个限制,即输入地址仅仅要小于查找vma的end就可以。并非非常多人想象的那样输入地址必须处在查找vma的start和end之间;
2.find_vma函数之所以实现得如此incompletely。是由于为了简化缺页中断的处理,同一时候也是为了提供一种更加统一的方式同一时候处理upgrows和downgrows的vma。

后话

尽管这个问题问得有点乱,可是假设能找上述回答连续扯5分钟的,应该是真行!只是我不知道怎样的语言表达能力能够不用图解和代码把上面的每个细节说清楚...总之,我认为我的这个题目是一个好题目。

能够建议给看到此文的人,把它做面试题吧。凡是发现不了题目问题的以及说不出所以然的。一律不要。这真是一道好測试题啊。它是如此之好,以至于我还想再出几道比它更好的。

请使劲回答一个关于UNIX/Linux自己主动扩展stack的问题的更多相关文章

  1. 来自Unix/Linux的编程启发录

    本篇文章已授权微信公众号 guolin_blog (郭霖)独家公布重点内容 2017年第一篇文章,祝各位好友新年快乐. 年前因为不小心坐到了自己左手大拇指导致轻微的骨裂,没有按时更新,实在是羞愧.今年 ...

  2. 《UNIX/Linux网络日志分析与流量监控》新书发布

    本书从UNIX/Linux系统的原始日志(Raw Log)采集与分析讲起,逐步深入到日志审计与计算机取证环节.书中提供了多个案例,每个案例都以一种生动的记事手法讲述了网络遭到入侵之后,管理人员开展系统 ...

  3. 28个Unix/Linux的命令行神器_转

    28个Unix/Linux的命令行神器 下面是Kristóf Kovács收集的28个Unix/Linux下的28个命令行下的工具,有一些是大家熟悉的,有一些是非常有用的,有一些是不为人知的.这些工具 ...

  4. UNIX/Linux下C语言的学习路线

    一.工具篇 “公欲善其事,必先利其器”.编程是一门实践性很强的工作,在你以后的学习或工作中,你将常常会与以下工具打交道, 下面列出学习C语言编程常常用到的软件和工具. 1.操作系统    在UNIX或 ...

  5. 如何使用Unix/Linux grep命令——磨刀不误砍柴工系列

     http://man.linuxde.net/grep ---------------------------------------------------- 如何使用Unix/Linux gre ...

  6. 28个Unix/Linux的命令行神器

    下面是Kristóf Kovács收集的28个Unix/Linux下的28个命令行下的工具(原文链接),有一些是大家熟悉的,有一些是非常有用的,有一些是不为人知的.这些工具都非常不错,希望每个人都知道 ...

  7. Unix/Linux运维首选工具Xmanager Enterprise 3.0的使用教程

    管理Uinx和Linux服务器的兄弟们应该很熟悉Xmanager,一个窗口可以同时控制上百台Linux和Unix服务器,功能非常强大!^_^请看: manager是一个简单易用的高性能的运行在Wind ...

  8. 28 个 Unix/Linux 的命令行神器

    28 个 Unix/Linux 的命令行神器   下面是Kristóf Kovács收集的28个Unix/Linux下的28个命令行下的工具(原文链接),有一些是大家熟悉的,有一些是非常有用的,有一些 ...

  9. Unix/Linux环境C编程入门教程(32) 环境变量那些事儿

    1. getenv() putenv()setenv()函数介绍 getenv(取得环境变量内容) 相关函数 putenv,setenv,unsetenv 表头文件 #include<stdli ...

随机推荐

  1. 负载均衡-lvs

    常用的负载均衡技术比较DNS 轮询DNS本身的机制不再赘述,这里主要看一看基于DNS的负载均衡,其大致原理很清楚,DNS系统本身支持同一个域名映射到多个ip (A记录),例如 这样每次向DNS系统询问 ...

  2. msiexec

    msiexec: runCmd = new String[]{ "msiexec", "/i", exeName, "/quiet", &q ...

  3. vsphere平台windows虚拟机克隆的小插曲(无法登陆系统)

    问题: 1.克隆完windows虚拟化后输入法乱码. 2.开启远程的情况下远程登录输入正确的密码也无法登录. 解决: 1.更改管理员用户密码(不输入原win7密码更改win7密码). 2.重新启用管理 ...

  4. Assembly.Load 详解(c#)

    我们在使用C# 语言的Assembly.Load 来加载托管程序集并使用反射功能时,一般需要先通过Assembly.Load(), Assembly.LoadFrom() 等方法将目标托管程序集加载到 ...

  5. 在navicat中如何新建连接数据库

    前几天给大家分享了如何安装Navicat,没有来得及上车的小伙伴可以戳这篇文章:手把手教你安装Navicat——靠谱的Navicat安装教程.今天给大家分享一下Navicat的简单使用教程,具体的教程 ...

  6. shell脚本的if判断语句

    if条件判断语句 if (表达式) #if ( Variable in Array ) 语句1 else 语句2 fi 1.测试数字大小 #!/bin/sh NUM=100 if (( $NUM &g ...

  7. TI Code Composer Studio MSP430系列驱动源代码

    一.参考TI官网驱动源代码 安装打开Code Composer Studio,如下图所示,最近在调试MSP430G2533的AD,需要参考官网的底层驱动配置. 从Code Composer Studi ...

  8. 【Uva 1336】Fixing the Great Wall

    [Link]: [Description] 给你长城上的n个修补点,然后你的位置为x; 你需要依次去这n个点,然后把它们全部修好. 但是修的前后顺序不一样的话,花费不一样. 如果立即把第i个点修好的话 ...

  9. (七十一)关于UITableView退出崩溃的问题和滚动究竟部的方法

    [TableView退出崩溃的问题] 近期在使用TableView时偶然发如今TableView中数据较多时,假设在滚动过程中退出TableView到上一界面.会引起程序的崩溃.经过网上查阅和思考我发 ...

  10. javafx DragDropped file

    public class EffectTest extends Application { @Override public void start(Stage primaryStage) { Grou ...