最近在项目中,发现在使用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. dubbo入门示例

    前提准备: 在本次实验之前,需要准备一下几个包: Spring中的aop.beans.context.core.expression以及struts中的commons-logging.javassis ...

  2. 开源的运维机器人hubot原理

  3. 【VB6】打印机设置页边距

    Printer.ScaleLeft = - Printer.ScaleTop = - Printer.CurrentX = Printer.CurrentY = '一些打印操作 Printer.End ...

  4. How to pronounce symbols on keyboard

    Refefrence: http://answers.yahoo.com/question/index?qid=20100607151104AAtQxhc ~ “tilde” or “tweedle” ...

  5. c# 文件及目录操作类

    18位长度的计时周期数: DateTime.Now.Ticks.ToString() 多数是收集而来,加上测试感觉很不错,分享一下或许有些帮助吧: 引用: using System; using Sy ...

  6. JAVA Socket 编程学习笔记(一)

    1. Socket 通信简介及模型 Java Socket 可实现客户端--服务器间的双向实时通信.java.net包中定义的两个类socket和ServerSocket,分别用来实现双向连接的cli ...

  7. JVM 内存的那些事

    转自:http://blog.jobbole.com/104863/ 对于Java程序员你来说,在虚拟机内存管理的帮助下,不需要为每个new对象都匹配free操作,内存泄露和内存溢出等问题也不太容易出 ...

  8. 解决json日期格式问题的3种方法

    这篇文章主要介绍了解决json日期格式问题的3种方法 ,需要的朋友可以参考下 开发中有时候需要从服务器端返回json格式的数据,在后台代码中如果有DateTime类型的数据使用系统自带的工具类序列化后 ...

  9. 用VBS实现公司自动打卡

    公司每天要求早晚打卡,方式是登录某一个页面,然后点击上面的圈圈,如下图: 每天都要打开书签,登录这个页面,然后打卡...这么重复性的工作,怎么能是程序员天天干的事情呢(其实是我太懒了),那么就尝试让程 ...

  10. PHPSTORM 10.0.3 --PHP Interpreter is not configured

    PHP Interpreter is not configured Please configure PHP Interpreter to use built-in weberver