最近在项目中,发现在使用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. EJB 教程推荐

    EJB教程 EJB概述 EJB创建应用 EJB无状态Bean EJB有状态会话Bean EJB持久性 EJB消息驱动Bean EJB注解 EJB回调 EJB定时器服务 EJB依赖注入 EJB拦截器 E ...

  2. Modelsim-altera 仿真 顶层原理图设计的FPGA

    我的原理图采用的是bdf的顶层原理图的设计,仿真工具用的是modelsim-altera,调用仿真后的错误提示: # ** Error: (vsim-3033) C:/Users/lenovo/Des ...

  3. window.returnValue跨域传值问题[转]

    主页面用window.showModalDialog的时候,如果直接打开其它系统的页面,这时候别人的页面在window.returnValue=1;这样返回值的时候,主页面是取不到返回值的,原因就是因 ...

  4. 查看SQLServer最耗资源时间的SQL语句

    执行最慢的SQL语句 SELECT (total_elapsed_time / execution_count)/1000 N'平均时间ms' ,total_elapsed_time/1000 N'总 ...

  5. Eclipse导入Tomcat源码(转)

    想要研究下Tomcat的体系结构或者源码,最好将Tomcat的源码导入到ide中,编写实例进行代码跟踪(debug). 这里参考了网上一些资料,将自己操作过程记个流水账. 准备: 1.Tomcat源码 ...

  6. JAVA 多线程和并发学习笔记(四)

    1. 多进程 实现并发最直接的方式是在操作系统级别使用进程,进程是运行在它自己的地址空间内的自包容的程序.多任务操作系统可以通过周期性地将CPU从一个进程切换到另一个进程,来实现同时运行多个进程. 尽 ...

  7. SQLServer查询所有库表结构信息

    1.查询数据库中的所有数据库名: SELECT Name FROM Master..SysDatabases ORDER BY Name 2.查询某个数据库中所有的表名: SELECT Name FR ...

  8. Centos Cacti 0.8.8g

    一.Cacti简介1. cacti是用php语言实现的一个软件,它的主要功能是用snmp服务获取数据,然后用rrdtool储存和更新数据,当用户需要查看数据的时候用rrdtool生成图表呈现给用户.因 ...

  9. 解决linux yum无法安装mysql

    yum源中默认好像是没有mysql的.为了解决这个问题,我们要先下载mysql的repo源. 1. 下载mysql的repo源 wget http://repo.mysql.com/mysql-com ...

  10. springboot一个service内组件的加载顺序

    先加载自身构造器,所以在构造器中初始化时若使用需要注入的(即@Autowired注解的)组件相关的方法,则会报null: 然后加载注入的组件即@Autowired 最后加载@PostConstruct ...