最近在项目中,发现在使用Qt4.8.5 提供的QWebView与网页交互的时候,

m_pWebView->page()->mainFrame()->evaluateJavaScript(tmp);

QtWebKitd4.dll模块偶尔会出现崩溃,如图

中断查看调用堆栈(加载QtWebkitd4.pdb 才可看到正确的堆栈信息)

最后停止在 QT  StackBounds::checkConsistency。从堆栈类名跟函数名看出,可能是跟堆栈相关,尝试看看源文件,找到函数定义

函数很短,跟具体业务逻辑没什么关系,可以得出release模式函数直接返回,debug模式下,在栈上创建一个对象来获取此时栈指针大小,assert根据堆栈增长方向,

检查此时栈指针跟 m_origin 和m_bound的关系。 从名字推测是与栈基址跟栈边界比较。继续看代码,怎么给这两个赋值的。

在X86CPU配合MSVC编译器的平台下,栈基址 m_origin 通过FS寄存器中保存的 NT_TIB 线程信息块中得到当前线程的栈的基址,没有问题

那么栈边界呢m_bound ?

通过源码的注释,似乎想通过NT_TIB获得,但没这样做(后面验证,此方法得到的栈边界不可靠,只能获得已提交栈大小。qt5.4中提供新的方式获取,后面修改也是基于此)。

继续看看 QT是怎么做的。

转到函数定义:

QT把栈边界的大小固定为512kB,显然不适用所有平台,源码的注释也给出了说明 this code unsafely guesses stack sizes!,WINDOWS、WINCE等平台下,may be work wrong。

明知不可行但还是设置了固定值,而且是全平台通用的一个值,想想我们在以往的项目中是不是也做过类似的妥协呢?

可见实现Qt4.8.5时还是比较匆忙,并不是一个稳定的版本。

后面再看qt5.4时(中间哪个版本开始修改的,这个没有关注。。),全平台都是代码获取,感觉很可靠,至少是在window平台。(Qt团队效率还是可以的!)

问题原因大致定位了,但是是哪里导致栈空间被大量使用了呢?

猜测1:QtWebKit内部调用,消耗了大量栈空间。

验证: 1.1 新建一个工程,用QWebView加载网页 m_pWebView->load(QUrl("xxx"));

1.2 注册js调用对象 m_pWebView->page()->mainFrame()->addToJavaScriptWindowObject("servers", this);

1.3 声明接口供js调用

int JS2QT::MainCall(QString szCallOperate,QString szExternData)

{

….

m_pWebView->page()->mainFrame()->evaluateJavaScript(tmp);

}

1.4 在执行js之前查看堆栈ebp(栈基址) 与esp(栈顶指针)。 当前消耗 ebp-esp 才几k,属于正常现象。

1.5 执行js,正常。

猜测2:项目工程在进入QWebView调用之前=的调用链就已经消耗了大量栈空间。

验证:  在进入MainCall之前下断点,观察到进入MainCall之后, ebp-esp 瞬间消耗了 850KB以上的栈空间(默认1MB),

执行js,出现中断。850KB 早已超过512KB, debug模式下QtWebKit只要执行stackCheck,必然assert。

相同情况relese不会崩溃,正好验证了之前的源码在relese模式下不检查stack。

结果: 查看实际项目中MainCall的实现,该函数内部确实声明了大量的临时对象,消耗了大量的栈空间。

修改意见:

主工程(生成exe的工程)属性

QtWebKit

Debug

项目属性-》链接器-》系统 修改堆栈保留大小(推荐2097152)

其他默认0

  1. 参考qt5.4修正m_bound栈边界跟栈基址的获取,重新编译 QtWebKit.sln(附件替换qt4.8.5后重新编译WebKit.sln工程即可)
  2. 二进制编辑器 直接找到对应代码二进制,修改数字大小(适合本地使用)
  3. 增大m_bound 栈边界固定值,重新编译QtWebkit.sln 工程(不推荐)

Release

项目属性-》链接器-》系统 修改堆栈保留大小(推荐2097152)

其他默认0

Release模式下QtWebKit不对堆栈使用做检查。一旦发生栈空间不够,直接崩溃。

Q&A

  1. 同样操作为什么debug模式必崩溃,但是release模式不会崩溃?

    答:因为QtWebkit StackBounds类负责做栈边界检查的时候,认为栈的大小固定在512kB,而主线程的默认栈 1MB,当主线程使用超过512kB的栈空间时,QtWebkit必崩,

    但在release模式下,QtWebkit不做栈检查,只要主线程使用栈不超过1MB,程序就不会崩溃。

  1. 为什么跟js做一些交互的时候,程序会崩溃?

    答:使用QWebView内核,与js交互都是通过我们项目中的 xxx::MainCall 完成分发的,MainCall中声明的各种数组消耗了大量的栈空间,

    目前来看已使用850kB左右,此时函数调用继续发生,堆栈进一步被消耗,当某些操作需要消耗大一点栈空间的时候,此时就会发生崩溃,而如果崩溃在

    Vs编译的库(不主动做栈检查,不主动产生中断),会友好提示 stackOverflow,崩溃在其他库(不主动做栈检查,不主动产生中断),就会显得莫名其妙了吧。

!!隐藏的问题,虽然扩大默认栈大小,可以解决问题,但是,改变默认栈大小带来的问题?

  1. 如果最后我们的工程生成的是 xxx.exe 以进程提供服务,那么我们设置的默认堆栈大小会起到作用。
  2. 如果我们工程生成的是 xxx.dll 或 xxx.ocx。我们的服务是被IE(其他进程)加载,主线程的堆栈是由加载进程决定的,我们工程设置的大堆栈将不起作用。(解决方法:修改IE默认堆栈大小字段,利用PE工具很方便)

总之,问题的根源在于,一个函数中大量使用堆栈资源,势必不是良好的程序设计风格,就目前及以后会出现的问题,提两点自己的建议

  1. 一个函数不要太长,应按照实际业务分发处理,多加些函数负责不同的操作;同时一个函数内部不要消耗太多的栈空间,这样有可能导致后的函数调用时,stackOverflow。
  2. 使用标准库容器来管理大量临时对象(容器对象在栈上分配空间,容器中的内容在堆上分配,堆的释放由标准库负责,有一定的可靠性).

附:手动修改QtWebKitd4.dll文件,改变QtWebkit 设置的固定栈大小。

下断点观察 m_bound的指令地址

指令地址 0x10EC3636  查看模块加载地址:0x10000000  则文件偏移地址 0x00EC3626

用二进制编辑器打开QtWebKit4d.dll (debug才需修改) 找到0x00EC3626 或直接搜内容 2D00000800

2D  00 00 08 00 对应汇编指令 sub eax 80000h     注意为小端字节序

保存即可。

替换QtWebkitd4.dll 断点查看

修改成功

Qt4.8.5 QtWebKit QWebView 用户栈检查崩溃问题的思考的更多相关文章

  1. qt4.8.5 qtwebkit 静态编译 版本

    2013年就编译好了,qtwebkit是最不好编译的了,尤其是静态编译,这儿分享给大家 估计总有人会用得到... 静态库下载地址:http://yunpan.cn/cyyNqrApbVDwq  提取码 ...

  2. Qt4 QWebView的使用例子

    最近项目中使用QT4框架开发PC端软件,所以耐着性子学习了一下QT相关的东西. 下面是QT4中QWebView的使用方法,觉得蛮方便的. 我使用的开发环境是:Win7+Qt 4.8.5开发库+qtcr ...

  3. QT4项目升级到QT5遇到的问题和解决方法

    QT4升级到QT5改动: PC部分: [改QTDIR变量] 在工程根目录下找到.user文件, 如InnoTabPlugin.vcxproj.user 修改指向你的QT5根目录: <Proper ...

  4. QT项目升级(QT4.6.3到QT5.2)时,遇到的问题和解决方法

    QT4升级到QT5修改: PC部分: [改QTDIR变量] 在project根文件夹下找到.user文件, 如InnoTabPlugin.vcxproj.user 改动指向你的QT5根文件夹: < ...

  5. 【Qt开发】QT4 升级到 QT5 改动

    QT4 升级到 QT5 改动: PC部分: [改 QTDIR 变量] 在工程根目录下找到 .user 文件 ,  如 InnoTabPlugin.vcxproj.user 修改指向你的 QT5 根目录 ...

  6. 使用 PyQt 转换网页到 PDF(使用QtWebKit加载完毕后,打印整个窗口就行了,真简单!)

    import sys try: from PyQt4 import QtWebKit from PyQt4.QtCore import QUrl from PyQt4.QtGui import QAp ...

  7. 如何使用Microsoft的驱动程序验证程序解释无法分析的崩溃转储文件

    这篇文章解释了如何使用驱动程序验证工具来分析崩溃转储文件. 使用Microsoft驱动程序验证工具 如果您曾经使用Windows的调试工具来分析崩溃转储,那么毫无疑问,您已经使用WinDbg打开了一个 ...

  8. Python各种花式截图工具,截到你手软

    前言: 最近,项目中遇到了一个关于实现通过给定URL,实现对网页屏幕进行截图的一个功能,前面代码中已经用python的第三方库实现了截图功能,但在上线以后出现了一些bug,所以就改bug的任务就落在了 ...

  9. Linux2.6.11版本:classic RCU的实现

    转载自:http://www.wowotech.net/kernel_synchronization/linux2-6-11-RCU.html 一.前言 无论你愿意或者不愿意,linux kernel ...

随机推荐

  1. DIY的.net正则表达式工具

    基本包括了常用的正则表达式测试工作. 对应.net Framework 2.0版本 VB.NET编写 百度网盘下载:http://pan.baidu.com/s/1eQAHnlo 包含源码. 在下一个 ...

  2. java工作流软件发送邮件的方案

    利用javamail的功能将发送邮件的功能集成到java工作流系统中.javamail包提供有发送邮件的方法,设置发送人地址,收件人地址,抄送,主题,邮件服务器地址,认证用户等信息,再调用javama ...

  3. 说说无耻的商河水木清华开发商2013"交房

    说说无耻的水木清华开发商2013"交房" 我买的是22号楼,合同里写的是2011年6月30号前交房.4月28我手机响了,电话那边说是水木清华的,29号交房.说交房通知书已经EMS发 ...

  4. 来自 Thoughtram 的 Angular 2 系列资料

    Angular 2 已经正式 Release 了,Thoughtram 已经发布了一系列的文档,对 Angular 2 的各个方面进行深入的阐释和说明. 我计划逐渐将这个系列翻译出来,以便对大家学习 ...

  5. Android的各种Drawable 讲解 大全

    Android把可绘制的对象抽象为Drawable,不同的图形图像资源就代表着不同的drawable类型.Android FrameWork提供了一些具体的Drawable实现,通常在代码中都不会直接 ...

  6. Delegate, Method as Parameter.

    代理, 将方法作为另一方法的参数. 类似C里面的函数指针. using System; using System.Windows.Forms; using System.Threading; name ...

  7. C#编写WIN32系统托盘程序

    基本功能概述: 程序运行后驻留系统托盘,左键呼出,右键退出.后续可加右键菜单. 注册系统案件WIN+F10,呼出程序. 重写系统消息,最小化和关闭按钮隐藏程序 using System; using ...

  8. Ninject之旅之四:Ninject模块

    摘要 随着应用程序的增长,注册的服务列表跟着变长,管理这个列表将变得困难.Ninject模块是一个好的将我们的类型绑定分离到不同的绑定组的方式,它很容易地将分组组织到不同的文件中.将一个类变成一个Ni ...

  9. Java并发编程:Callable、Future和FutureTask

    作者:海子 出处:http://www.cnblogs.com/dolphin0520/ 本博客中未标明转载的文章归作者海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置 ...

  10. 实数---Currency讲解

      Currency 实际上是 Int64 的变体,Int64/10000 就是实际的值