调试Utility进程崩溃

M55内核升级中发现Utility进程崩溃很多。以前Utility进程的崩溃只占到总崩溃量1%不到,现在却增加到了10%以上。 Utility进程崩溃的堆栈都很类似,如下: 我们看0号线程,也就是主线程。这个调用栈没有wMain函数,却有exit函数。可以看出来程序崩溃在进程退出的时候。最后的我们的代码崩溃帧是在BrokerServicesBase::~BrokerServicesBase里面,其代码如下: 我认为0号线程是正常的。我们再看1号线程。1号线程是程序发生异常,生成dump的线程。可以看到是ntdll!TppWaiterpThread引发了ntdll!KiUserExceptionDispatcher。好奇怪,异常竟然来自系统模块。我们看下Windbg的dump分析: 可以看到ExceptionCode的值为c000070a,查一下ntstatus.h头文件,值c000070a表示STATUS_THREADPOOL_HANDLE_EXCEPTION,意思是线程池等待的句柄异常,可以看到句柄是0000012c。 然后用!handle命令行查看一下进程当前拥有的句柄情况: 可以看到进程句柄表里面并没有0000012c句柄。因此我猜测可能是因为0000012c句柄已经关闭了,而TppWaiterpThread还在使用0000012c这个无效的句柄导致的异常。 因此我想收集一下句柄分配和释放的信息,然后根据dump查看一下无效的句柄是怎么分配和释放的。幸好chromium里面有一个ActiveVerifier机制,ActiveVerifier是用来跟踪句柄使用情况的,它跟踪了一部分句柄创建的和Hook了CloseHandle函数。在StartTracking和CloseHandle里面加一个我自己写的LogHandle函数,这个函数会把句柄分配和释放的StackTrace跟进句柄值记录到环境变量中。 因为Utility进程创建和释放的句柄并不多,所以把这个调试信息放在环境变量中是方便和合适的。 灰度出去,收集崩溃信息。这次果然收集到了有用的信息,先看dump分析: 这次异常的句柄值是00000198,再看看环境变量中的调试信息: 00000198果然被CloseHandle了。然后根据后面的地址查看关闭的调用栈: 从代码里面可以看到00000198句柄已经随着ChildProcess的析构而关闭变成无效了,其他地方继续使用这个句柄是有问题的。 00000198句柄是ChildProcess的shutdown_event_成员变量。这个shutdown_event_会被ChildThreadImpl传递给其IPC::SyncChannel的channel_成员。而SyncChannel的shutdown_watcher_会调用系统的RegisterWaitForSingleObject去利用线程池去等待00000198句柄对应Event。 所以问题的根源是ChildProcess的析构函数CloseHandle了shutdown_event_,但是SyncChannel里面的shutdown_watcher_通过RegisterWaitForSingleObject继续等待shutdown_event_句柄。MSDN里RegisterWaitForSingleObjec介绍说:If... Read More | Share it now!

Chromium架构之启动

总结一下Chromium浏览器从wWinMain到浏览器循环开始的这段逻辑。 wWinMain Win32程序的入口都是wWinMain,Chromium浏览器也不例外。 初始化Crashpad 一开始就调用SignalInitializeCrashReporting去初始化Crashpad崩溃报告系统。这里会创建一个ChromeCrashReporterClient的对象,用来处理crash相关的逻辑。初始化完成之后就会启动一个crashpad-handler的进程。其他浏览器进程崩溃后,这个crashpad-handler进程就会为它们生成dump。 fast... Read More | Share it now!

创建新的Chromium extension api

不同编程语言的开发效率是不一样的,比如c++可以实现一些很底层的功能,但是开发速度比较慢,对开发者的要求也很高。而javascript开发速度很快,编程的抽象层次很高,无法调用一些系统底层的api。于是我们想到可以把c++与javascript结合起来,c++给javascript提供一些底层的api接口,javascript调用c++的接口并专注于业务开发,这样岂不两全其美。 事实上真有这样做的,比如Chromium浏览器,浏览器本身是用c++编写的,提供了很多extension... Read More | Share it now!

Chrome extension的类型

Chrome中的extension概念跟我们一般想的不一样,除了一般的extension,比如广告过过滤extension是属于extension,其实Chrome中的theme也是属于extension。另外随着extension系统的发展,类型的定义也会发生改变。 Extension类型的定义 我们可以在代码extensions\common\manifest.h中查看Manifest::Type的定义: 此外我们可也以从代码extensions\common\api\management.json中可以看到ExtensionType的定义: 这两处对于extension类型的定义有些差异,management.json定义渐渐废弃,以manifest.h中的定义为准。 Extension类型的判断 判断一个extension类型是看extensions\common\extension.h中的判断逻辑: Extension最后还是调用Manifest中的逻辑: 在Manifest构造函数中: 可以看到extension类型的判断逻辑: theme 类型为Manifest::Type::TYPE_THEME的extension就是一个theme(主题),它可以改变浏览器的外观。theme通常不会包含有html和js等代码。具体可以看看这里的介绍https://developer.chrome.com/extensions/themes。 shared_module shared_module类型很少见。 app app包含三种类型,platform_app、hosted_app、legacy_packaged_app。2016年Chrome宣布将不再支持app类型了。app与extension最大的差别是app可以具有独立于浏览器的窗口界面。app相比extension可以获取到更多的权限,能够实现更加强大的功能。 extension 我们在Chrome网上应用店里面下载到扩展程序都是属于extension,这也是最常见的,如下图: ... Read More | Share it now!

Chrome的差量升级Courgette

Windows客户端做程序的升级是一件比较复杂的事情。不像移动端,有专门的移动商店用来安装和更新程序,Windows客户端程序升级需要自己制作安装包,放到服务器上供用户下载,后续还需要提供升级服务。由于用户操作不可预知性和用户环境的复杂,安装跟升级都会发生各种异常而失败。迄今为止Windows上也没有一个好的程序安装、升级、卸载框架,这不得不说是Windows开发的悲哀。 而且目前程序的安装包越来越大了,动辄几十兆,用户下载安装包需要很长的时间,这也是导致安装升级成功率低的一个原因。所以,安装升级包越小越好。对于这个问题,有两个解决思路: 程序功能模块化,把代码编译到不同的dll里面。如果某个模块有修改,就只需要升级相应的dll就行了。 找出旧版本和要升级的新版本文件的差异,把差异文件发给客户端。客户端根据旧版本和差异文件还原出新版本。 对于第一种方案,优点是实现起来比较简单,我之前的老东家360很多产品就是用的这种方案。这个方案的缺点也很多: 把功能模块化,这个升级粒度依然有点大,有些dll可能只是修改了几行代码就需要升级这个几兆的dll文件。 如果底层模块的接口改了,那么调用它的上层模块都需要修改,这就导致升级会涉及到很多文件。 如果模块代码划分不清或者有一些隐式的逻辑依赖,这种dll升级可能引入难以觉察的bug,甚至会导致程序崩溃。 第二种方案已经有了bsdiff了。但是对于Widnows客户端程序,依然存在很大的挑战。因为C++这种需要编译的代码,充满了内部地址引用,一些指令直接包含其他指令的地址或者位移,几行代码的修改就会导致许多地址值的改变,结果导致最终生成的二进制文件差异很大。 Google的Chrome开发团队特地为C++这种需要代码编译的程序开发了一种叫做Courgette的差异算法。原理是先把可执行的二进制程序进行反汇编,找到那些内部的地址指针,把它们替换成一个符号,然后再用常规的办法生成差异文件。 下面是常规的bsdiff升级: 这个是Courgette升级: 不得不佩服,这是个绝妙的思路。没有对C++、编译器、汇编、PE文件格式深厚了解的人,是无法设计出如此精妙的方案。 Chrome团队还有一个数据的对比: Full... Read More | Share it now!

Chromium UI框架view

如果把widget当作画板,那么view就相当于铺在画板上的画纸。widget上面不会画什么东西,我们的界面元素都是画在view上,然后view又盖在widget上面。 在non_client_view.h的注视里面可以看到以下的内容: 可以看到最底层是Widget,然后RootView盖在Widget上面。RootView包含了NonClientView,而NonClientView又包含NonClientFrameView和ClientView。View不仅可以显示元素,而且可以当作容器包含其他的View。所有的View形成了一个树状的层次结构,低底层的根是RootView。 RootView附着在一个Widget上面,Widget从操作系统里面接受事件,转换成与View兼容的事件,然后再传递给RootView。RootView则按照树状层次结构一层层的传递事件。 听起来还是很抽象,我们可以实际动手试一试,我们利用views::PrintViewHierarchy(this)打印出RootView的View... Read More | Share it now!

Chromium预置搜索引擎

Chromium是Google主导一个开源浏览器项目,而Google是以搜索引擎起家的,因此Chromium里面搜索是个很重要的功能,给它增加了很多特性。我写篇博客介绍一下Chromium里面的搜索功能。 预置的搜索引擎 在浏览器的地址栏里面输入chrome://settings/searchEngines就可以管理搜索引擎。里面有一些预置的搜索引擎,你可以删除它们也可以添加一些你自定义的搜索引擎:如下图所示: 预置的搜索引擎是由Chromium代码prepopulated_engines.json定义的,这个文件的注释值得一读。文件其实就是包含了json格式的一些数据,在编译的时候会动态生成prepopulated_engine.h/cc文件。值得注意的是:每次修改过prepopulated_engines.json文件里面的数据都需要自增int_variables.kCurrentDataVersion的值,否则你的修改可能不会生效。原因是浏览器运行之后会把预置搜索引擎的数据保存在Web... Read More | Share it now!

踏破铁鞋无觅处

最近学习Chromium的UI渲染,于是编译一下ui/views/examples的例子工程来看看。运行views_examples_exe,发现界面是上下颠倒显示。 一开始以为是例子工程可能好久没有维护了,在代码升级的过程中出了偏差,导致界面颠倒显示。于是自己动手写了一个例子工程,也是颠倒显示。看了下aura_demo工程界面的坐标,显示也是颠倒。我意识到这不是Aura层的问题,可能是CC模块通过GPU硬件加速渲染出来的结果本身就是颠倒的。 我家虚拟机里有Chromium... Read More | Share it now!

Chrome HTTP Transport Security

传统的HTTP连接很容易被中间人攻击。比如当网络流量经过网络运营商的时候,运营商为了私利可能会在HTTP的数据中插入它自己的广告链接,也可能会把HTTP请求重定向到别的网址。随着现代浏览器对HTTPS的支持越来越普遍,很多重要的网站例如www.baidu.com把HTTP协议升级成HTTPS,保证网站的内容在传输的过程中不被篡改。然而大部分普通用户还是习惯在地址栏里输入HTTP网址来访问网站,互联网中也有很多的链接还是HTTP。为了保证兼容性,网站的后台可以把HTTP请求重定向到HTTPS,但是这有两个问题。一是客户端浏览器发起请求还是HTTP,这个过程可能会被中间人攻击,二是网站服务端每次都把HTTP重定向到HTTPS会增大服务器的负担。 HTTP... Read More | Share it now!