Dump分析系列-1:一个dump引发的Qt Webkit分析
《一个dump引发的Qt Webkit分析》 第一篇
花了点时间分析了下dump,先从LOLTheme的代码来说,打开游戏广场会调用OnOpenPlazzUrl,关闭则调用OnClosePlazzUserWnd,plazz_user_wnd_内部包含一个QWebView
QT会依次析构ChildWidget,不难理解。
下面开始精确定位错误的根源,期间需要阅读5.51 qtwebkit,主要是WebCore的代码。 从异常来看,访问了空指针,此时ecx为0,ub向上反汇编看下调用过程。
根据符号找到对应源码:qtwebkit\source\webkit\qt\webcoresupport\texturemapperlayerclientqt.cpp @ 74
QWebFrameAdapter* m_frame是其成员指针变量,即是eax(mov eax,dword ptr [esi]),从TextureMapperLayerClientQt类的内存布局可知:
//this(从函数头的mov esi,ecx可知,编译器的惯用方式),因此eax是m_frame 6226086d 8b06 mov eax,dword ptr [esi]
//eax是m_frame(QWebFrameAdapter),该类含有虚函数,因此[eax]是其虚函数表,eax+4则是第一个成员变量,即是pageAdapter 6226086f 8b4004 mov eax,dword ptr [eax+4]
//分析方式同上,ecx是client成员变量 62260872 8b4810 mov ecx,dword ptr [eax+10h]
//client是Qt的一个模板类实例(QScopedPointer参考c++的RAII),QScopedPointer client,如下图,重载了->操作符,且内联函数,编译后,[ecx]即是client的QWebPageClient实例。 62260875 8b01 mov eax,dword ptr [ecx]
分析到这里,应该比较清楚了,ecx为什么是nullptr,从QScopedPointer可知,无非两种情况,调用reset函数(默认参数)、take函数把实例转移出去。
这里是分析的第一阶段,直接从异常点入手,跟踪代码,找到异常的最直接的原因。
下面开始第二阶段的分析,还是从qtwebkit代码入手,QWebView析构会delete d(QWebViewPrivate):
从源码可知,在析构QWebPage *page之前,会调用page->d->client.take(),从第一阶段分析可知,这里会将实例转移出去,并置nullptr,然而这里调用者并没有使用转移出来的实例,“因此感觉QT这里貌似存在内存泄露”。 再通过Windbg验证一下这个client是否是同一个对象。
再切换到StackFrame 38,查看 38 0014cd28 6fc7b44f Qt5WebKitWidgets!QWebViewPrivate::detachCurrentPage+0x70 [e:\qt5vs2010full2\qtwebkit\source\webkit\qt\widgetapi\qwebview.cpp @ 237]
两者地址一致,并且QWebPagePrivate其实继承于QWebPageAdapter,因此这个client就是异常的时候的client。
至此,第二阶段分析完毕。
分析第三阶段(最后阶段): 关键问题是detachCurrentPage调用了client.take置空之后,为何还会用到?
这里会停掉所有Loader和渲染,如果有页面元素还在pending渲染状态,那么就强制停掉,调用一些通知函数,但后面还会访问WebPageClient。
怎么解决: 可以尝试在PlazzUserWnd::~PlazzUserWnd()析构的时候,调用QWebView::stop停掉页面加载之后,再析构掉QWebView。 周一的时候来论证下是否可行。。
后话:
1、Qt5.51的webkit的渲染代码来至KDE的KHTML,感觉有些地方处理有点费解,比如上面的take函数。
2、Qt5.6开始webkit的渲染代码全部采用chromium,用的Google从Webkit而来,后面自己维护开发的Blink,感觉这个版本应该要好不少,或许也没有上面的问题,从代码目录结构也可看到。
来至stackoverflow: