win32线程栈溢出问题 (一)
一、什么是线程栈溢出
我们都知道,每一个win32线程都会开辟一个空间,用来临时存储线程执行时所调用的一系列函数的参数、返回地址和局部变量及其他上下文信息。这个空间就是线程的栈区。栈区的容量是有限的,在程序编译链接时,就固定下来了。通过VC++编译的程序,默认的栈区大小是1MB。当我们程序执行时,访问超过了这个空间的边界,就叫栈溢出,又叫Stack overflow。这时会产生代码为STATUS_STACK_OVERFLOW(0xC00000FD)的异常,从而导致程序崩溃。注意一定要与栈缓冲区溢出---STATUS_STACK_BUFFER_OVERRUN(0xC0000409)区别开来。
二、栈溢出的原因
栈溢出是用户模式线程可能会遇到的错误。 有三个可能的原因产生此错误:
线程使用为其保留的整个堆栈。这通常是由无限递归引起的。
线程无法扩展堆栈,因为页文件已最大化,因此无法提交其他页来扩展堆栈。
由于系统内使用以扩展页面文件的短时间内,线程不能扩展堆栈。
当一个线程上运行的函数分配的本地变量时,变量放线程的调用堆栈上。 函数所需的堆栈空间量可能大至所有本地变量的大小的总和。 但是,编译器通常会执行优化,降低函数所需的堆栈空间。 例如,如果两个变量,则在不同的作用域中,编译器可以为这两个这些变量使用相同的堆栈内存。 编译器还可能无法完全消除某些本地变量对计算进行优化。优化的量会影响应用在生成时的编译器设置。 例如,调试版本和发布版本具有不同的优化级别。 所需的调试版本中的函数的堆栈空间量可能会大于该发行版中的相同函数所需的堆栈空间量。
在我们编写代码时,如下情况通常引发栈溢出:
- 由于某些原因,导致函数递归深度很深或无穷递归
- 在栈里分配了很大的缓冲区
- 栈里的某缓冲区溢出
三、溢出代码举例
3.1、在栈里分配了很大的缓冲区导致溢出
代码如下:
#include "stdafx.h" int LargeBuffer(void)
{
char buf[1024 * 1024];
int a = 0;
int b = buf[2];
return a+b;
} int _tmain(int argc, _TCHAR* argv[])
{
int n=LargeBuffer();
printf("n=%d\n", n);
return 0;
}
由于在VS里默认线程栈空间为1MB,我们代码里在栈区分配了一个刚好1MB的数组,在加上其他函数入栈占用的空间,肯定就会超出1MB产生溢出
在VS2013里编译,Debug模式下调试运行,果然报错,跟我们前面预想的一样

我们注意到此时的调用栈如下:

看此时的栈,没又被破坏,最后停在了_chkstk()里,这是个什么样的函数呢。这个函数其实是VC++的crt函数,用来检测堆栈的。停在这里,说明这个函数检测到了这种情况栈溢出。我们看看它的代码,在VS里转到汇编模式下

在地址栏里输入上面错误提示框里地址0x00F21767,得到

_chkstk:
push ecx
cmp eax,1000h
lea ecx,[esp+]
jb lastpage
probepages:
sub ecx,1000h
sub eax,1000h
test dword ptr [ecx],eax
cmp eax,1000h
jae probepages (0040818c)
lastpage:
sub ecx,eax
mov eax,esp
test dword ptr [ecx],eax
mov esp,ecx
mov ecx,dword ptr [eax]
mov eax,dword ptr [eax+]
push eax
ret
大概流程
- chkstk例程是C编译器的助手例程。对于x86当局部变量超过4096字节;对于x64编译器,它分别是4K和8K。即, 当一个函数局部变量超过一个页面大小4k的时候, 编译器会自动插入这个函数。插入这个函数的位置在:
push ebp
mov ebp,esp
mov eax,1000D4h
call _chkstk() - 这个函数做什么?当函数为堆栈分配的页面不够时, (堆栈默认大小为1M), 堆栈需要更多的页面时调用这个函数。当堆栈使用大于分配的大小(默认1M)时,产生_XCPT_UNABLE_TO_GROW_STACK.
- 当调用这个函数时, 首先外面对eax进行赋值(已经分配的堆栈大小 + 即将分配给函数局部变量的堆栈大小)
- 调用 _chkstk() 首先,保持当期esp到eax中,然后开始判断:如果分配的大小大于一个页面, 到第5)步(大多都先第5)步);否则到第6)步。
- 当需要分配的大小大于一个页面, 则增加一个页面。“sub eax 1000h” 表示堆栈栈顶下移1000h,[由于堆栈是高地址(栈底部)->低地址(栈顶部)分布],所以堆栈扩大了1000h; “sub ecx 1000h”表示分配了1000h(1个页面)之后还需要多少空间;“test dword ptr [ecx],eax” 表示分配空间,这个时候之前只不过是分配虚存,内存没有 commit ,这个时候对这个内存地址进行读写操作都会引发一个 page fault 异常(_XCPT_GUARD_PAGE_VIOLATION), OS捕获这个异常,检查一定的条件,适合的时候就把这个内存页 commit 了,即分配了实际的物理内存。然后再次比较需要多少内存,如果还是超过1页(1000h), 则重复第5)步,否则到第6)步。
- 还需要分配的堆栈空间小于1页的时候,“sub ecx,eax” 堆栈继续扩大(扩大了剩余大小的空间);然后“mov esp,ecx”,保存到原来的esp;并且通过“test”为堆栈分配空间。
- 最后,esp的值不再是原来的值,堆栈的大小变成: 堆栈原来大小 + 局部变量需要的堆栈大小 + xx (push 用的一点堆栈的零头).
上面讲的是在vs里的debug模式下运行时的情况,那么在Release模式下是怎样的呢?我们转到Release模式下,编译运行,同样报错

可以看到,跟Debug模式一样。也就是说,如果是这种是在栈区分配了超大缓冲区导致异常,基本都会被_chkstk()检测出来。
针对这种情况,通常我们可以用下面两种方式来解决:
- 修改线程栈区大小,重新编译程序。这种方法只能对我们确定要使用多少栈区的情况下有用,要不然,不能彻底解决。
在VS里可以在这里设置一个比现在大的栈区
- 更好的方式是把这种需要的大缓冲区搬到进程的堆里区申请使用。
win32线程栈溢出问题 (一)的更多相关文章
- win32线程栈溢出问题 (二)
3.2.函数递归调用引发的栈溢出 写一段最简单的无穷递归代码,如下: #include "stdafx.h" void f(void) { f(); } int _tmain(in ...
- win32线程池代码(WinApi/C++)
win32线程池代码(WinApi/C++) 健壮, 高效,易用,易于扩, 可用于任何C++编译器 //说明, 这段代码我用了很久, 我删除了自动调整规模的代码(因为他还不成熟)/********** ...
- win32线程
win32线程 一丶什么是线程 在windows中常听到的就是线程.多线程.啊什么的. 这里介绍一下什么是线程. 1.线程是附属在进程中的一个执行实体.简而言之就是执行代码的. 2.每个进程至少有一个 ...
- Win32线程——优先权
<Win32多线程程序设计>–Jim Beveridge & Robert Wiener Win32 优先权是以数值表现的,并以进程的“优先权类别(priority class)” ...
- Win32 线程同步
Win32 线程同步 ## Win32线程同步 ### 1. 原子锁 ### 2. 临界区 {全局变量} CRITICAL_SECTION CS = {0}; // 定义并初始化临界区结构体变量 {线 ...
- C++ win32线程数上限
hThread = CreateThread( NULL, 0, WorkerFunction, &threadParm, 0, &dwThreadID ); 这样的创建方法 ...
- Win32线程——等待另一个线程结束
转载: https://blog.csdn.net/yss28/article/details/53646627 <Win32多线程程序设计>–Jim Beveridge & Ro ...
- win32 线程通信初步
// 线程通信机制.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #define NUM_THREADS 10 #include < ...
- Win32线程安全问题.同步函数
线程安全问题.同步函数 一丶简介什么是线程安全 通过上面几讲.我们知道了线程怎么创建.线程切换的原理(CONTEXT结构) 每个线程在切换的时候都有自己的堆栈. 但是这样会有安全问题. 为什么? 我 ...
随机推荐
- ThreadPoolExecutor使用错误导致死锁
背景 10月2号凌晨12:08收到报警,所有请求失败,处于完全不可用状态 应用服务器共四台resin,resin之前由四台nginx做负载均衡 服务器现象及故障恢复步骤 登入服务器,观察resin进程 ...
- Python之TensorFlow的模型训练保存与加载-3
一.TensorFlow的模型保存和加载,使我们在训练和使用时的一种常用方式.我们把训练好的模型通过二次加载训练,或者独立加载模型训练.这基本上都是比较常用的方式. 二.模型的保存与加载类型有2种 1 ...
- docker 入坑1
本文是记录一下学习docker的过程,希望可以帮助到入门的朋友. 系统:ubuntu16.04 docker:18.09 打开官网:https://docs.docker.com/install/li ...
- MySQL的ERROR 1205错误分析
一.错误发生及原因猜测 1.错误发生 在删除 t_user 表的一条数据时,Navicat 发生长时间的无响应,然后弹出一个对话框,提示:ERROR 1205: Lock wait timeout e ...
- Hadoop之HDFS读写流程
hadoophdfs 1. HDFS写流程 2. HDFS写流程 1. HDFS写流程 HDFS写流程 副本存放策略: 上传的数据块后,触发一个新的线程,进行存放. 第一个副本:与client最近的机 ...
- 6.transform?animation?区别?animation-duration【CSS】
1.Transform:它和width.left一样,定义了元素很多静态样式实现变形.旋转.缩放.移位及透视等功能,通过一系列功能的组合我们可以实现很炫酷的静态效果(非动画). 2.Animatio ...
- MySQL比较时间(datetime)大小
获取时间返回的秒数:strtotime('2019-05-10 00:00:00') 遇到一个业务功能需要将当前时间与数据库中的会议开始.结束时间进行比较,记录一下,方便下次用. 用unix_time ...
- Air for ANE:打包注意的地方
来源:http://blog.csdn.net/hero82748274/article/details/8631982 今天遇到了一个打包ANE 文件的问题,导致花费了几个小时查找,最后师弟的一句话 ...
- jenkins rpm卸载
rpm卸载 1.rpm -e jenkins rpm -ql jenkins 检查是否卸载成功 2.彻底删除残留文件:find / -iname jenkins | xargs -n 1000 rm ...
- spark 机器学习 随机森林 实现(二)
通过天气,温度,风速3个特征,建立随机森林,判断特征的优先级结果 天气 温度 风速结果(0否,1是)天气(0晴天,1阴天,2下雨)温度(0热,1舒适,2冷)风速(0没风,1微风,2大风)1 1:0 2 ...