回顾多进程架构图2-2,RendererProcessHost 扮演了非常关键的角色,它主要负责管理和控制渲染进程。Chromium 采用多进程架构来提升浏览器的稳定性、安全性和响应速度。在这种架构中,主要涉及几种类型的进程,如浏览器进程、渲染进程、工具进程等。渲染进程的创建即是由RendererProcessHost 掌控的,了解了RendererProcessHost 的工作,那么也就知道应该在何处将命令行参数传递给渲染进程了。
如果现在向Chromium浏览器传递命令行参数,会发现只能在浏览器进程中获取,在渲染进程中获取不到对应内容。其中的原因很简单,从命令行工具那里得知,每个进程都有自己的命令行单例,在浏览器开启的时候传递的命令行参数,只有浏览器进程能够接收到,而渲染进程是不存在的,所以需要找到渲染进程被启动的地方,进行手动传递,而这个地方就是RendererProcessHost。
下面将深入探讨 Chromium 的 RenderProcessHostImpl::Init的源码,详细解释其作用和内部机制。这段源码是 Chromium 浏览器架构中负责初始化渲染进程主机实现的核心部分,理解这部分代码对于深入了解Chromium浏览器是如何管理渲染进程来说是非常重要的。通过逐步了解它的逻辑结构,我们可以为找到合适的命令行参数传递的切入点做准备。
首先需要明白的是,RenderProcessHostImpl::Init的主要目的是初始化一个渲染进程,包括配置进程启动的命令行参数、决定沙盒模式、创建 IPC 通道,并启动或重启渲染进程。这个方法还确保了渲染进程的正确配置和启动,即使在可能出现多次初始化调用的情况下也能正确处理。下边是它的源代码:
if (IsInitializedAndNotDead())
return true;
base::CommandLine::StringType renderer_prefix;
const base::CommandLine& browser_command_line =
*base::CommandLine::ForCurrentProcess();
renderer_prefix =browser_command_line.GetSwitchValueNative(
switches::kRendererCmdPrefix);
这个函数的源码中,首先检查了渲染进程主机RendererProcessHost是否已经被初始化且没有被关闭。如果已经初始化且没有被关闭,那么再次调用初始化函数就没有意义,所以直接返回 true,表示初始化成功。接着是我们熟悉的一些命令行代码,用来获取渲染器命令前缀,该命令行参数如下所示:
const char kRendererCmdPrefix[] = "renderer-cmd-prefix";
这个参数允许在渲染器进程启动时添加一些额外的命令或选项。比如可以设置这个参数为 "valgrind",这样在渲染器进程启动时,会在命令行前加上 "valgrind",这样就可以在渲染器进程中使用 Valgrind 工具进行内存调试了。或者也可以设置为 "xterm -e gdb --args",这样在渲染器进程启动时,会打开一个新的终端窗口,并在其中启动 GDB 调试器,以便可以对渲染器进程进行调试。
根据编译时的平台宏定义设置不同的标志。例如,在 Linux 或 Chrome OS 上,如果渲染器命令前缀为空,则设置为 CHILD_ALLOW_SELF,否则设置为 CHILD_NORMAL。具体代码如下:
int flags = 0;
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
flags = renderer_prefix.empty() ?
ChildProcessHost::CHILD_ALLOW_SELF : ChildProcessHost::CHILD_NORMAL;
#elif BUILDFLAG(IS_MAC)
flags = ChildProcessHost::CHILD_RENDERER;
#else
flags = ChildProcessHost::CHILD_NORMAL;
#endif
base::FilePath renderer_path = ChildProcessHost::GetChildPath(flags);
if (renderer_path.empty())
return false;
根据设置的标志获取渲染器的可执行文件路径,在Windows平台下,这个函数会去获取一个名为"--browser-subprocess-path"的开关,这个开关是执行渲染进程或者插件进程的可执行文件的路径,默认就是chrome.exe的路径。
接下来这段代码的主要目的是配置GPU相关的功能和磁盘缓存,以提高Chromium浏览器的性能或者实现其他相关的需求。它检查浏览器上下文是否处于“隐身模式”(OffTheRecord),以及当前进程是否禁用了GPU着色器磁盘缓存,如果都没有,就会配置GPU使用磁盘缓存来提高性能。具体代码如下:
gpu_client_->PreEstablishGpuChannel();
if (!GetBrowserContext()->IsOffTheRecord() &&
!base::CommandLine::ForCurr entProcess()
->HasSwitch(switches::kDisableGpuShaderDiskCache)) {
......
gpu_client_->SetDiskCacheHandle(handle); }
然后,调用 RenderProcessWillLaunch 方法,这个方法允许 Chromium 浏览器执行一些优先级高于 IPC 过滤器的初始化工作。代码如下:
GetContentClient()->browser()->RenderProcessWillLaunch(this);
...
GetRendererInterface()->InitializeRenderer( GetContentClient()->browser() ->GetUserAgentBasedOnPolicy(browser_context_),
GetContentClient()->browser()->GetUserAgentMetadata(),
storage_partition_impl_->cors_exempt_header_list(),
GetContentClient()->browser()->GetOriginTrialsSettings());
之后就调用了初始化渲染进程的方法,为渲染进程初始化了各类浏览器信息,比如 UserAgent和一些Client Hints信息,因此这些指纹信息可以在这里直接修改:
std::string ContentBrowserClient::GetUserAgentBasedOnPolicy(
content::BrowserContext* content) {
return GetUserAgent();
}
blink::UserAgentMetadata ContentBrowserClient::GetUserAgentMetadata() {
return blink::UserAgentMetadata();
}
最后一部分包含了一个条件分支,用于确定是在进程中运行渲染器还是作为单进程运行。如果在单进程中运行,则创建一个新的线程,并在该线程中运行初始化代码。这是为了避免在单进程模式下发生死锁,因为渲染器进程的原始线程在运行 WebKit 代码时可能会阻塞 UI 线程。具体代码如下:
if (run_renderer_in_process()) {
in_process_renderer_.reset(g_renderer_main_thread_factory(
InProcessChildThreadParams(GetIOThreadTaskRunner({}),
&mojo_invitation_),
base::checked_cast(id_)));
// 配置新的线程选项
base::Thread::Options options;
options.message_pump_type = base::MessagePumpType::UI;
OnProcessLaunched(); // 模拟进程启动后的回调
in_process_renderer_->StartWithOptions(std::move(options));
g_in_process_thread = in_process_renderer_.get();
channel_->Flush();
} else {
...
}
很明显,默认情况下渲染进程是以多进程的模式启动的,因此会走另一个分支。在这个分支中,由于可以启动新的进程,因此会先对命令行进行单独的初始化,其中存在一个函数AppendRendererCommandLine就是专门给渲染进程添加命令行参数的:
std::unique_ptr cmd_line =
std::make_unique(renderer_path);
if (!renderer_prefix.empty())
cmd_line->PrependWrapper(renderer_prefix);
AppendRendererCommandLine(cmd_line.get());
之后,针对不同操作系统,代码有条件编译。在Windows系统中,创建一个专门的沙箱启动委托,用于管理进程的安全环境。参数包括命令行对象和PDF渲染相关的安全选项。其他操作系统则使用通用的启动委托。代码如下:
std::unique_ptr sandbox_delegate;
sandbox_delegate = std::make_unique \
\
(*cmd_line, IsPdf(), IsPdf());
最后异步启动子进程,避免阻塞用户界面线程。Linux系统还会预加载特定的文件,优化V8引擎的启动性能。通过ChildProcessLauncher创建子进程,传递沙箱设置、命令行参数、进程标识、Mojo通信设置等。具体代码如下:
auto file_data = std::make_unique();
#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_MAC)
file_data->files_to_preload = GetV8SnapshotFilesToPreload();
#endif
child_process_launcher_ = std::make_unique
( std::move(sandbox_delegate), std::move(cmd_line), GetID(),
this, std::move(mojo_invitation_), base::BindRepeating
(&RenderProcessHostImpl::OnMojoError, id_),
std::move(file_data), metrics_memory_region_.Duplicate());
...
到此为止,RenderProcessHostImpl的初始化函数的源码就已经分析完毕,如果认真查看了该函数的初始化过程,不难发现AppendRendererCommandLine就是最佳的将传递给主进程的参数添加到渲染进程的地方。
在RenderProcessHostImpl::AppendRendererCommandLine这个函数当中,可以发现一开头的源码就比较熟悉:
void RenderProcessHostImpl::AppendRendererCommandLine(
base::CommandLine* command_line) {
command_line->AppendSwitchASCII(switches::kProcessType,
switches::kRendererProcess);
很明显这里是在给子进程添加进程类型,而这个正是我们在3.1.1中所看到的第一个命令行参数。因此,可以选择在这里为渲染进程添加传递给主进程的命令行参数。
首先需要在头文件中导入相关依赖,代码如下:
//ruyi
#include "base/json/json_reader.h"
#include "base/values.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
//ruyi end
接下来就是实际的命令行解析和添加了,代码如下:
//ruyi
const base::CommandLine* ruyi_command_line =
base::CommandLine::ForCurretProcess();
if (ruyi_command_line->HasSwitch(switches::kRuyi)) {
const std::string ruyi_fp =ruyi_command_line->
GetSwitchValueASCII(switches::kRuyi);
command_line->AppendSwitchASCII(switches::kRuyi, ruyi_fp);
}
//ruyi end
这里添加的代码很简单,首先获取当前主进程的命令行单例,接着判断是否存在之前定义的开关,如果存在,就把当前的这个开关的键值对添加到渲染进程的命令行参数上。打开Visual Studio,单击导航栏中的“调试”,在出现的菜单中继续单击“chrome调试属性”,在图3-3所示的位置添加指定switches开关的值。
接着启动调试,打开操作系统任务管理器,找到对应的Chromium进程,不出所料,自定义的命令行参数出现在了渲染进程之后,如图3-4所示。
到此为止,经过了整整一章的预备学习,终于在这一刻完成了从主进程到渲染进程的命令行参数传递。如今,每一个启动的渲染进程都将拥有自定义的命令行参数,而这些命令行参数在渲染进程的任何位置都是可以直接获取到的,后边只需要找到要修改的指纹信息,就可以轻松将指纹传递过去,也就完成了指纹传参。
本章节详细探讨了Chromium指纹浏览器开发过程中,渲染进程和参数传递的技术细节。Chromium浏览器为了在整个运行周期内保持固定的浏览器指纹,需要在启动阶段就固定这些指纹参数。利用多进程架构的特点,Chromium通过命令行传参、环境变量读取等多种方式来传递和固定指纹信息。
开发者可以在渲染进程启动前,通过修改或添加命令行参数来传递指纹信息,这些参数通过RenderProcessHostImpl::AppendRendererCommandLine函数添加到进程中。此外,通过控制进程命令行参数,可以在浏览器的不同模块间传递关键信息,保持数据的一致性和隐私性。总的来说,本书描述的技术手段不仅涉及底层的命令行工具使用,还包括了如JSON处理等高级特性,使得开发者可以更灵活地控制和扩展浏览器的功能,满足特定的业务需求。