代码之家  ›  专栏  ›  技术社区  ›  KPexEA

如何检测C/C++程序中可能的/潜在的堆栈溢出问题?

  •  37
  • KPexEA  · 技术社区  · 16 年前

    有没有一个标准的方法来查看你的应用程序有多少堆栈空间,以及在运行期间堆栈使用的最高水印是什么?

    同样在实际溢出的可怕情况下会发生什么?

    它会崩溃,触发异常还是信号?在所有系统和编译器上是否都有标准或不同?

    我在找Windows、Linux和Macintosh。

    9 回复  |  直到 13 年前
        1
  •  15
  •   jussij    16 年前

    窗户 堆栈溢出 例外 将生成。

    以下Windows代码说明了这一点:

    #include <stdio.h>
    #include <windows.h>
    
    void StackOverFlow()
    {
      CONTEXT context;
    
      // we are interested control registers
      context.ContextFlags = CONTEXT_CONTROL;
    
      // get the details
      GetThreadContext(GetCurrentThread(), &context);
    
      // print the stack pointer
      printf("Esp: %X\n", context.Esp);
    
      // this will eventually overflow the stack
      StackOverFlow();
    }
    
    DWORD ExceptionFilter(EXCEPTION_POINTERS *pointers, DWORD dwException)
    {
      return EXCEPTION_EXECUTE_HANDLER;
    }
    
    void main()
    {
      CONTEXT context;
    
      // we are interested control registers
      context.ContextFlags = CONTEXT_CONTROL;
    
      // get the details
      GetThreadContext(GetCurrentThread(), &context);
    
      // print the stack pointer
      printf("Esp: %X\n", context.Esp);
    
      __try
      {
        // cause a stack overflow
        StackOverFlow();
      }
      __except(ExceptionFilter(GetExceptionInformation(), GetExceptionCode()))
      {
        printf("\n****** ExceptionFilter fired ******\n");
      }
    }
    

    运行此exe时,将生成以下输出:

    Esp: 12FC4C
    Esp: 12F96C
    Esp: 12F68C
    .....
    Esp: 33D8C
    Esp: 33AAC
    Esp: 337CC
    
    ****** ExceptionFilter fired ******
    
        2
  •  12
  •   adl    13 年前

    在linux上,如果您的代码试图写过堆栈,就会出现分段错误。

    堆栈的大小是进程之间继承的属性。如果可以在shell中使用如下命令读取或修改它 ulimit -s sh , ksh , zsh limit stacksize ( tcsh , ZSH )

    从程序中,可以使用

    #include <sys/resource.h>
    #include <stdio.h>
    
    struct rlimit l;
    getrlimit(RLIMIT_STACK, &l);
    printf("stack_size = %d\n", l.rlim_cur);
    

    我不知道有什么标准方法可以得到可用堆栈的大小。

    堆栈开始于 argc 接下来是 argv 一份环境的副本,然后是你的变量。但是,因为内核可以随机化堆栈开始的位置,并且上面可以有一些虚拟值 氩弧焊 ,假设你有 l.rlim_cur 以下可用字节数 &argc .

    检索堆栈确切位置的一种方法是查看文件 /proc/1234/maps (何处) 1234 是程序的进程ID)。一旦知道了这些界限,就可以通过查看最新局部变量的地址来计算堆栈的使用量。

        3
  •  10
  •   Kknd    16 年前

    gcc在“unsafe”函数调用的返回地址和普通变量之间放置了一个额外的内存块,例如(在本例中,函数是void test(){char a[10];b[20]}:

    call stack:
    -----------
    return address
    dummy
    char b[10]
    char a[20]
    

    如果函数在指针“a”中写入36个字节,则溢出将“损坏”返回地址(可能存在安全漏洞)。但它也会改变“dummy”的值,即指针和返回地址之间的值,因此程序将崩溃并发出警告(您可以使用-fno堆栈保护程序禁用它)

        4
  •  7
  •   Norman Ramsey    15 年前

    在Linux上,GNU libsigsegv library 包括功能 stackoverflow_install_handler ,它可以检测(在某些情况下有助于恢复)堆栈溢出。

        5
  •  6
  •   deemok    16 年前

    在Windows上,堆栈(针对特定线程)按需增长,直到达到创建之前为此线程指定的堆栈大小。

    按需增长是使用保护页推动的,因为最初只有一个堆栈片段可用,然后是一个保护页,当它被击中时,将触发一个异常-这个异常是特殊的,并由系统为您处理-处理会增加可用的堆栈空间(也会检查是否已达到限制!)然后重试读取操作。

    一旦达到限制,就不再增长,这将导致堆栈溢出异常。 当前堆栈基和限制存储在线程环境块中,在名为 _NT_TIB (线程信息块)。 如果您手头有一个调试器,您将看到:

    0:000> dt ntdll!_teb @$teb nttib.
       +0x000 NtTib  : 
          +0x000 ExceptionList : 0x0012e030 _EXCEPTION_REGISTRATION_RECORD
          +0x004 StackBase : 0x00130000 
          +0x008 StackLimit : 0x0011e000 
          +0x00c SubSystemTib : (null) 
          +0x010 FiberData : 0x00001e00 
          +0x010 Version : 0x1e00
          +0x014 ArbitraryUserPointer : (null) 
          +0x018 Self   : 0x7ffdf000 _NT_TIB
    

    stackLimit属性将按需更新。 如果检查此内存块上的属性,将看到类似的内容:

    0:000> !address 0x0011e000 
        00030000 : 0011e000 - 00012000
                        Type     00020000 MEM_PRIVATE
                        Protect  00000004 PAGE_READWRITE
                        State    00001000 MEM_COMMIT
                        Usage    RegionUsageStack
                        Pid.Tid  abc.560
    

    检查旁边的页面会显示guard属性:

    0:000> !address 0x0011e000-1000
        00030000 : 0011d000 - 00001000
                        Type     00020000 MEM_PRIVATE
                        Protect  00000104 PAGE_READWRITE | PAGE_GUARD
                        State    00001000 MEM_COMMIT
                        Usage    RegionUsageStack
                        Pid.Tid  abc.560
    

    希望有帮助。

        6
  •  4
  •   Rob Walker    16 年前

    堆栈溢出可能是要处理的最糟糕的异常类型——因为异常处理程序必须处理最小数量的堆栈(通常为此只保留一个页面)。

    有关处理此类异常的困难的有趣讨论,请参阅以下博客文章: 1 2 来自chris brumme,他从.net的角度关注这个问题,特别是托管clr。

        7
  •  1
  •   Shyam Verma    16 年前

    如果您在Linux上,我建议您使用备用信号堆栈。

    1. 在这种情况下,所有信号都将在备用堆栈上处理。
    2. 如果发生堆栈溢出,系统生成一个segv信号,这可以通过备用堆栈处理。
    3. 如果你不使用它…然后您可能无法处理该信号,并且您的程序可能会在没有任何处理/错误报告的情况下崩溃。
        8
  •  1
  •   Richard    13 年前

    可以在visual studio中使用editbin来更改堆栈大小。这些信息可以在 msdn.microsoft.com/en-us/library/35yc2tc3.aspx .

        9
  •  0
  •   dmityugov    16 年前

    一些编译器支持stackAvail()函数,该函数返回堆栈的剩余可用空间量。在调用程序中需要大量堆栈空间的函数之前,可以使用此函数来确定调用它们是否安全