zoukankan      html  css  js  c++  java
  • 更为复杂C程序的运行时结构

    运行环境 win 10 企业版 1809 17763.194,MinGW V3.14 32位,Bundled V3.13.2,Bundled GDB V8.2。

    在C语言中,栈的方向是从高地址向低地址延伸,而数组中数据在栈中的存储方向与此正好相反。字符串拷贝等数组操作是不对数据长度做审核的,如果实际的数据长度超过了栈中预留的空间,就会将栈中其他数据覆盖,这种现象被称为“栈溢出”。栈溢出可能导致一个不可预期的错误,也可能导致一个精心策划的执行流程发生改变。可见,是否能够对自己所写程序的运行时状态做到心中有数,是能否写出高质量、安全代码的前提保证。

    以上两节介绍的运行时结构都是由C程序所对应的指令,在内存中执行,驱动数据变化而产生的。C程序只有经过编译,才能生成目标代码。目标代码将与指令和全局数据一一对应。编译的最终目标就是能让C程序的设计意图体现在运行时结构中,这也使得编译的每个阶段的中心任务都要为形成运行时结构着想。下一节我们将概述编译的过程。

    1.2 更为复杂C程序的运行时结构(1)

    在实际编程过程中会遇到更为复杂的问题。要解决这样的问题,更加依赖对运行时结构的了解。下面我们来看一个比较复杂的案例,案例的两个程序分别如下:

    #include <stdio.h>
    #include <string.h>
    
    void fun1() {
        int m = 10;
        char num[4];
        strcpy(num, "bbbb");
    }
    
    void fun2() {
        printf("You were attacked!!!
    ");
    }
    
    int main() {
        fun1();
        printf("over");
        return 0;
    }
    #include <stdio.h>
    #include <string.h>
    
    void fun1() {
        int m = 10;
        char num[4];
        strcpy(num, "bbbbbbbbbbbbx12x13x40x00");
    }
    
    void fun2() {
        printf("You were attacked!!!
    ");
    }
    
    int main() {
        int address = (int) fun2;
        printf("%08x
    ", address);
    
        fun1();
    
        return 0;
    }

    这个案例中的两个程序在代码上只有微小的差别,但执行结果却不同,尤其是左边的程序,执行结果如下所示:

    这些字符显然是fun2函数被调用时才会输出的,但fun2这个函数在本程序中没有被调用过,这样的输出结果显得有些不可思议了,程序执行时到底发生了什么呢?下面我们一步一步地对比分析这个案例。我们先来看main函数调用fun1函数时的情景,fun1函数执行后的返回地址被压入栈中,跳转到fun1函数执行,此时两边程序的执行没有差异,情景如图1-29所示。

    之后保存了main函数栈底的地址值,ebp被腾出来,指向fun1函数的栈底,此时两边也没有差异。情景如图1-30所示。

    m入栈,初始化为10,为num数组开辟了栈空间,此时仍然没有差异,情景如图1-31所示。

    下面差异产生了。调用strcpy函数,执行的目的是把指定的字符串拷贝到num数组中,指定多少,拷贝多少。我们先来看右边的程序。该程序会把指定的字符串拷贝给num数组,其长度刚好填满num数组,情景如图1-32所示。

    再看左边程序,指定的字符串长度已经超出了num数组的长度,所以在拷贝的时候,会把栈中前面的数据覆盖掉,包括num的数组、main函数栈底地址值直至fun1函数执行后的返回地址,全部被覆盖,情景如图1-33所示。

    覆盖的结果使得fun1函数在返回并恢复现场时出现了问题。

    我们先来看右边的程序,跳转回main函数,正常恢复,情景如图1-34所示。

    再看左边的程序,栈底地址值被覆盖了,ebp会得到一个乱值,不再指向main函数的栈底,另外,由于fun1函数执行后返回地址已经被覆盖,而且覆盖的数值正好是fun2函数的起始地址,将这个数据传递给eip,那么eip自然跳转到fun2函数执行,相当于调用了fun2函数,也就输出了fun2函数的打印信息。同时,ebp成了乱值,程序最终将产生段错误,情景如图1-35所示。

  • 相关阅读:
    Spring Boot 使用 Dom4j XStream 操作 Xml
    Spring Boot 使用 JAX-WS 调用 WebService 服务
    Spring Boot 使用 CXF 调用 WebService 服务
    Spring Boot 开发 WebService 服务
    Spring Boot 中使用 HttpClient 进行 POST GET PUT DELETE
    Spring Boot Ftp Client 客户端示例支持断点续传
    Spring Boot 发送邮件
    Spring Boot 定时任务 Quartz 使用教程
    Spring Boot 缓存应用 Memcached 入门教程
    ThreadLocal,Java中特殊的线程绑定机制
  • 原文地址:https://www.cnblogs.com/Chary/p/10524801.html
Copyright © 2011-2022 走看看