zoukankan      html  css  js  c++  java
  • 深入理解任务堆栈

    本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/gumbour/archive/2009/08/30/4500424.aspx

    先来看这一个小函数,猜猜他的运行结果(VC6环境)?

    #include <stdio.h>

    void  b()
    {
        int data[10];
        printf("helloworld!/r/n");
        data[11]-=5;
    }

    int main()
    {
        b();
        return 0;
    }

    堆栈溢出,肯定不正常,马上有人叫起来了。

    没错, 那么结果是什么呢,为什么会不停打印helloworld呢,我们将用堆栈揭开他的奥秘。

    且看main函数汇编代码。  

    很简单,  L12  调用b函数,   L13对返回值赋0.

    这里有个很关键的东东: call

    call包含2部分操作,call的下一条指令地址入栈,跳转,也就是从效果来说,包含push  0040108D 和 jmp  00401005两条操作。 假如,你打开内存窗口,你会看到,堆栈里已经有0040108D 这个值了。

    10:   int main()
    11:   {
             ...........
    12:       b();
    00401088   call        @ILT+0(b) (00401005)
    13:       return 0;
    0040108D   xor         eax,eax
    14:   }

    再来看函数b

    当你把  printf("helloworld!/r/n"); 替换为 printf("%08x!/r/n",data[11]);时,你会发现,程序在不停的打印0040108D!, 显而易见,你修改的data[11]其实就是函数b的返回值地址,而data[11] -= 5;更是巧妙的利用 call    00401005 这条指令正好是5个字节的特点,将返回地址正好修改到了 0040108D ,也就是说函数返回时会再次调用函数b。每次b()都会把返回值改为b返回的地址,导致b()被不停的调用。

    为什么data[11]正好是函数的返回值呢,让我们来看堆栈和任务有和关系
     

        任务(线程)都有一个堆栈,任务创建时创建,任务撤销时撤销。 任务的创建本质上包含2点。

        1  任务资源的分配(任务TCB和任务堆栈),很多嵌入式操作系统把TCB和堆栈是分配在一起的,比如Vxworks操作系统,其任务ID,堆栈基地址,TCB指针其实指向同一块内存。 创建任务时要指定任务大小,分配堆栈空间其实是一个特殊的malloc函数,他从堆栈空间分配,而不是从系统空间分配内存。任务堆栈windows下默认比较大,嵌入式OS则比较小,经常64k左右。 而局部变量就保存在堆栈中,当访问局部变量越界时,就发生了我们常说的"堆栈被踩了",堆栈被踩得话后果严重,轻则导致某次运行结果不对(这种问题很难定位),重则导致程序崩溃,例如把上面程序改为data[11]-=4,则程序直接崩溃。

        2  任务的初始化,包含2部分,任务TCB的初始化,并且把TCB和操作系统关联。

            TCB中包含任务的很多东西,   比如任务拥有的信号量的链表,文件描述符的链表,CPU寄存器的值(任务切换时用的),任务优先级,堆栈地址,任务名称等等,这些都需要初始化。初始化完成之后,操作系统会把这个任务TCB假如调度队列,如果加入调度队列时任务状态是就绪,那么当他拿到CPU时就可以直接运行了。

        堆栈中包含任务的栈帧,也就是说在函数调用链(A call B,B call C,C call D,D call E),那么堆栈中,ABCDE函数分别对应自己的一段栈帧。以E为例  E的栈帧包含A函数的传入参数,函数返回值,局部变量和临时保存的寄存器值。

         函数栈帧在主调函数和被掉函数中分配,在函数返回时释放,这就是为什么局部变量地址在函数返回后其值可能失效。

         例如 下面代码FuncB分配的函数栈帧在FuncB执行完后又被分配给FuncC,FuncC中很可能会踩到FuncB曾经的局部变量。

          FuncA{

             FuncB();

             FuncC();

          }

        任务(线程)的栈以及上面函数b的栈为下图。

        *debug版本的函数b其实除了data[10],还在局部变量位置分配了一部分内存用来做调试,不过我们不用关系他。

        *为什么是data[11],而不是data[10]/data[12]或者其他? x86下编译器函数入口一般会有2条指令。

          push  ebp

          move ebp,esp

          其实就是将ebp作为帧指针来用(函数帧即为栈中一个函数所拥有的一段内存)。

          而这样就可以在函数中采用ebp-XXX表示局部变量,用ebp+XXX来表示传入参数。 函数中经常会有一些push操作,

          采用esp对局部变量和参数寻址远不如用ebp来的省事了,因为esp是经常变化的,而ebp是相对横的的。

        

    本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/gumbour/archive/2009/08/30/4500424.aspx

  • 相关阅读:
    QPushbutton样式
    qt调动DLL
    QLabel设置行间距
    Qt 5.2.1 applications (32 bit) in CentOS (64 bit with gcc 4.8.2)
    qt设置阴影效果
    ubuntu命令
    How to configure NFS on Linux
    gsoap生成onvif c++源码
    QUrl乱码问题
    QTableWidget自适应标题(铺满、可调节)
  • 原文地址:https://www.cnblogs.com/yanglin1228/p/4812075.html
Copyright © 2011-2022 走看看