前言
尽管Chromium 是一个开源项目,但其架构和内容异常复杂。直接深入研究就像看天书一样,尤其对于那些对C++代码不太熟悉的前端程序员来说。然而,借助我们在大学里学到的一些C++知识和通用的程序调试技巧,我们有机会从程序的启动阶段开始,逐步、有条不紊地理解Chromium 的整体结构。这包括深入了解其实现原理,如多进程架构 、网络解析 、以及blink排版布局引擎 等等。通过这样的学习过程,我们能够真正理解从输入URL到页面展示的整体流程 。在着手这些内容之前,我们首先需要学习如何使用visual studio 2022。
Chromium 编译
在之前的文章里(在Mac上编译属于你自己的Chromium),我介绍了如何在Mac编译Chromium。为了方便,这次我们选择在Windows上进行编译。编译方法在网上已经有比较全的攻略了,经过本人实验可行。具体可参考以下几篇文章(至少预留
100G
的编译空间):
- win10 下载 Chromium 源码并编译(版本 103.0.5060.66)
- windows下载,编译,运行chromium源码遇到的问题
- 编译chromium时下载gn.exe时出错的解决方案
- Chromium Windows 编译官方文档
我们以第一篇
为主,主要为以下几步:
- 安装 Visual Studio 2022 Professional
- 安装 Windows 11 SDK
- 安装 depot_tools
- depot_tools 初始化(
gclient
) - 拉取代码(
fetch --no-history chromium
) - 同步第三方依赖(
gclient sync
) - 编译调试版浏览器
- 编译正式版浏览器
- 程序的断点调试
注意事项
在执行任何命令之前,如
gclient
,记得先设置cmd
的代理:
shell
set HTTP_PROXY=http://127.0.0.1:8888 // 改为你的代理地址
set HTTPS_PROXY=http://127.0.0.1:8888
同时需要设置
NO_AUTH_BOTO_CONFIG
,否则可能导致gn.exe
下载失败
- 随便找个地方创建一个文本文件,比如 C:\boto.cfg,写入以下文本内容:(注意端口号换成你的代理端口)
text
[Boto]
proxy=127.0.0.1
proxy_port=1080
- 设置环境变量,把这个文件添加到系统环境变量中:
拉取代码这一步,可不必按照
第一篇
,而是参考第二篇
文章:
shell
fetch chromium //下载代码
fetch --no-history chromium //只下载最新版本
其他步骤如
"编译配置"
等,结合第一、二篇文章
处理即可,最后编译完成后可以得到如下界面:(编译大概需要10小时)
Visual Studio 2022 调试指南
以管理员身份运行开始菜单 Visual Studio 2022 文件夹的 "x64 Native Tools Command Prompt for VS 2022" 程序:
执行以下命令(如果要调试多进程则把--single-process
删除):
shell
devenv /DEBUGEXE D:\src\chromium\src\out\Default\chrome.exe --single-process
功能界面
使用快捷键ctrl + o
打开源码目录,选择源码文件,我这里以Chromium
在Windows
上的入口启动文件chromium\src\chrome\app\chrome_exe_main_win.cc
为例。
断点
我们在401
行处打上断点
(鼠标点击行标左边空白区域即可)。此函数位于main
函数中,是Chromium
启动关键路径中的重要一环。
单击启动
(或按F5
):
程序会在我们设置的断点处暂停
:
局部变量
左下角这部分是函数运行时局部变量
的值,除了基础的布尔值和数字之外,还包括指针地址等,具有一定的参考性,但是我们在前期不必过多关注这块儿内容。
调用堆栈
右下角的区域是函数执行时的调用堆栈
,箭头所指部分为当前正在执行的代码行,也就是我们打断点的地方。
我们可以也可以切换左下角
的选项卡,查看我们标记的所有断点:
进/线程树
刚启动的时候是看不到并行堆栈的,也就是所有线程组成的线程树
。我们需要点击调用堆栈中的查看所有线程
按钮:
此时就会弹出线程树窗口,我们可以通过这颗树,更好的分析程序启动后所拉起的所有进/线程
,对程序有更好的把控和分析。其中通过箭头来表示父子进/线程
关系。
其中每一个框都是一个线程从开始创建
后执行过的关键函数的调用栈,且Chromium中的函数名都非常语义化
,这很有利于我们之后的分析。
可以根据个人喜好把窗口放到你喜欢的位置。
Tips: 在Chromium windows实现中:
- 通过
CreateThread
函数创建线程。- 通过
CreateProcess
或者ShellExecuteEx
函数创建子进程。
多进程调试
Chromium是多进程架构,但是visual studio 2022默认只能调试程序的主进程,无法调试主进程创建的子进程,这时候可以通过插件来实现这部分功能,主要步骤如下:
- 安装 Visual Studio 扩展 Microsoft Child Process Debugging Power Tool 2022(安装时记得退出Visual Studio)
- 配置插件:
将Enable child process debugging
打上勾并保存即可:
3. 重新启动调试,查看当前运行的进程ID:
或者打开一个单独的窗口查看:
这些进程都叫相同的名字chrome.exe
,不好分辨,但我们可以一直调试下去,最终可以打开Chromium的任务管理器查看Chromium中启动的进程,与我们在进程tab
中的id进行比较,就可以找到对应的渲染进程 、GPU进程 、插件进程 、网络进程等。
调试技巧
基础技能
- :点击后执行到下一个断点处。
- :点击后结束调试。
- : 快速定位到当前正在执行的语句。
- :进入当前函数调用栈。
- :逐语句,不进入函数,一条代码一条代码的执行。
- :跳出当前函数调用栈。
关键路径分析
先凭直觉每隔一大段距离
找到一个你认为是关键函数调用
的地方打一个断点,多打几个,然后从头执行。因为每一次断点执行,线程树中都会多很多的函数调用,这些调用在不同的线程中,形成不同的调用栈。我们可以从根出发,分析这其中的关键路径。
线程树调用堆栈分析
通过分析前后两个断点之间拉起的线程、进程,对线程中的堆栈函数进行分析,找出关键函数并跳转,打上断点。假如我们通过跳着打断点,最终捕捉到了线程栈中渲染进程 、Gpu进程 、以及其他辅助进程的启动过程:
在Chromium浏览器的源代码中,
content
目录下有一些主要的入口点,其中包括RendererMain
、GpuMain
和UtilityMain
。这些入口点负责启动Chromium浏览器的不同组件。以下是它们的简要描述:
- RendererMain: - 位于
content/renderer/
目录。 - 负责启动渲染进程。 - 渲染进程负责处理网页内容、JavaScript 执行等,每个标签页通常对应一个渲染进程。- GpuMain: - 位于
content/gpu/
目录。 - 负责启动GPU进程。 - GPU进程处理与图形渲染相关的任务,包括加速绘图、WebGL等。- UtilityMain: - 位于
content/utility/
目录。 - 负责启动一些辅助进程,通常用于处理一些特殊任务,如插件进程等。
那么我们就可以分别对这三个进程打断点,如:
由于Chromium是多进程,且启动后,就进入各种子进程的创建过程中,那么我们刚才打的这三个断点就很利于我们分析Chromium主子进程的启动过程。
ChatGPT
- 不懂的地方可以交给GPT来分析,如 :
chromium中有很多看不懂的C++语法,皆可扔给GPT,让他帮你解答。
- 结合你自己的疑惑给他提问,如 :
chromium源代码的main函数在哪儿,也就是启动的入口函数在哪儿?
他的回答很具有参考意义,虽然在Windows平台上实际的main函数在chromium\src\chrome\app\chrome_exe_main_win.cc
的wWinMain
中,但也十分接近我们想要的结果了。
因此,结合GPT发挥自己的想象力!
最后
诚以此文献给所有对Chromium饱有热情的同学,希望通过此文能够帮助大家更好的学习如何调试Chromium,让阅读Chromium源码不再是那么遥不可及。