Chromium中的utility进程

有时候Chromium浏览器主进程需要做一些“危险”的事情,比如图片解码、文件解压缩。如果这些“危险”的操作发生了失败,会导致整个主进程发生异常崩溃,这是我们不愿意看到的。因此Chromium设计出了一个utility进程的机制。主进程临时需要做一些不方便的任务的情况下,可以启动一个utility进程来代替主进程执行,主进程与utility进程之间通过IPC消息来通信。我写一个收集电脑硬件信息的例子来介绍如何使用utility进程。 首先我们需要继承UtilityProcessHostClient创建一个自己的类HardwareProber。我们在HardwareProber里面处理与utility进程的IPC通信。在HardwareProbe里面我们会通过UtilityProcessHost::Create创建一个UtilityProcessHost,通过UtilityProcessHost的Send方法,我们可以向utility进程发送IPC消息。我们不用管理UtilityProcessHost的生命周期,所以持有UtilityProcessHost的WeakPtr即可。 UtilityProcessHost提供了一些接口: SetExposedDir。在沙箱里暴露一个utility进程可以操作的目录。 DisableSandbox。禁用utility进程的沙箱。 ElevatePrivileges。提权utility进程。 SetEnv。设置utility进程的环境变量。 通常需要执行某个任务,然后启动一个utility进程。当任务完成,utility进程就退出。有时候我们需要做很多同样的事情,如果频繁的启动和退出utility进程,效率比较低。因此UtilityProcessHost还有个Batch模式,就是在一个utility进程里处理多个相同的任务。一般utility进程退出都是utility进程自己主动调用content::UtilityThread::Get()->ReleaseProcessIfNeeded()。如果在Batch模式下,那边则由主进程来控制utility进程的退出,需要调用StartBatchMode和EndBatchMode。 UtilityProcessHostClient还具有RefCountedThreadSafe属性。它的生命周期是当utility进程退出后,IPC断开了BrowserChildProcessHostImpl来销毁。所以我们也不用管理UtilityProcessHostClient对象生命周期。 主进程里面HardwareProber的IPC消息首先会发到utility进程的ChromeContentUtilityClient里面来。我们可以在ChromeContentUtilityClient里面处理消息,也可以继承UtilityMessageHandler创建一个HardwareProberHandler类来处理IPC消息。 HardwareProberHandler... Read More | Share it now!

调试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!