12.Chromium指纹浏览器开发教程之RendererProcessHost传递

回顾多进程架构图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开关的值。

12.Chromium指纹浏览器开发教程之RendererProcessHost传递_第1张图片

接着启动调试,打开操作系统任务管理器,找到对应的Chromium进程,不出所料,自定义的命令行参数出现在了渲染进程之后,如图3-4所示。

12.Chromium指纹浏览器开发教程之RendererProcessHost传递_第2张图片

到此为止,经过了整整一章的预备学习,终于在这一刻完成了从主进程到渲染进程的命令行参数传递。如今,每一个启动的渲染进程都将拥有自定义的命令行参数,而这些命令行参数在渲染进程的任何位置都是可以直接获取到的,后边只需要找到要修改的指纹信息,就可以轻松将指纹传递过去,也就完成了指纹传参。

本章节详细探讨了Chromium指纹浏览器开发过程中,渲染进程和参数传递的技术细节。Chromium浏览器为了在整个运行周期内保持固定的浏览器指纹,需要在启动阶段就固定这些指纹参数。利用多进程架构的特点,Chromium通过命令行传参、环境变量读取等多种方式来传递和固定指纹信息。

开发者可以在渲染进程启动前,通过修改或添加命令行参数来传递指纹信息,这些参数通过RenderProcessHostImpl::AppendRendererCommandLine函数添加到进程中。此外,通过控制进程命令行参数,可以在浏览器的不同模块间传递关键信息,保持数据的一致性和隐私性。总的来说,本书描述的技术手段不仅涉及底层的命令行工具使用,还包括了如JSON处理等高级特性,使得开发者可以更灵活地控制和扩展浏览器的功能,满足特定的业务需求。

你可能感兴趣的:(chrome,chromium,指纹浏览器,浏览器开发)