|  | // Copyright 2012 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "content/browser/browser_child_process_host_impl.h" | 
|  |  | 
|  | #include <memory> | 
|  |  | 
|  | #include "base/base_switches.h" | 
|  | #include "base/clang_profiling_buildflags.h" | 
|  | #include "base/command_line.h" | 
|  | #include "base/debug/dump_without_crashing.h" | 
|  | #include "base/feature_list.h" | 
|  | #include "base/files/file_path.h" | 
|  | #include "base/functional/bind.h" | 
|  | #include "base/lazy_instance.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/metrics/histogram_functions.h" | 
|  | #include "base/metrics/histogram_macros.h" | 
|  | #include "base/metrics/histogram_shared_memory.h" | 
|  | #include "base/metrics/persistent_histogram_allocator.h" | 
|  | #include "base/metrics/persistent_memory_allocator.h" | 
|  | #include "base/observer_list.h" | 
|  | #include "base/strings/string_number_conversions.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "base/synchronization/waitable_event.h" | 
|  | #include "base/task/single_thread_task_runner.h" | 
|  | #include "base/token.h" | 
|  | #include "base/trace_event/memory_dump_manager.h" | 
|  | #include "build/build_config.h" | 
|  | #include "components/metrics/histogram_controller.h" | 
|  | #include "components/tracing/common/tracing_switches.h" | 
|  | #include "content/browser/browser_main_loop.h" | 
|  | #include "content/browser/child_process_host_impl.h" | 
|  | #include "content/browser/metrics/histogram_shared_memory_config.h" | 
|  | #include "content/browser/renderer_host/spare_render_process_host_manager_impl.h" | 
|  | #include "content/browser/tracing/background_tracing_manager_impl.h" | 
|  | #include "content/public/browser/browser_child_process_host_delegate.h" | 
|  | #include "content/public/browser/browser_child_process_observer.h" | 
|  | #include "content/public/browser/browser_task_traits.h" | 
|  | #include "content/public/browser/browser_thread.h" | 
|  | #include "content/public/browser/child_process_data.h" | 
|  | #include "content/public/browser/content_browser_client.h" | 
|  | #include "content/public/browser/resource_coordinator_service.h" | 
|  | #include "content/public/common/content_client.h" | 
|  | #include "content/public/common/content_features.h" | 
|  | #include "content/public/common/content_switches.h" | 
|  | #include "content/public/common/process_type.h" | 
|  | #include "content/public/common/result_codes.h" | 
|  | #include "content/public/common/sandboxed_process_launcher_delegate.h" | 
|  | #include "mojo/public/cpp/bindings/scoped_message_error_crash_key.h" | 
|  | #include "mojo/public/cpp/system/platform_handle.h" | 
|  | #include "services/tracing/public/cpp/trace_startup.h" | 
|  | #include "services/tracing/public/cpp/trace_startup_config.h" | 
|  |  | 
|  | #if BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_IOS_TVOS) | 
|  | #include "content/browser/child_process_task_port_provider_mac.h" | 
|  | #endif | 
|  |  | 
|  | #if BUILDFLAG(IS_MAC) | 
|  | #include "content/browser/sandbox_support_impl.h" | 
|  | #include "content/common/sandbox_support.mojom.h" | 
|  | #endif | 
|  |  | 
|  | #if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_ANDROID) | 
|  | #include "services/tracing/public/cpp/system_tracing_service.h" | 
|  | #endif | 
|  |  | 
|  | #if BUILDFLAG(IS_WIN) | 
|  | #include "content/browser/renderer_host/dwrite_font_proxy_impl_win.h" | 
|  | #include "content/public/common/font_cache_dispatcher_win.h" | 
|  | #include "content/public/common/font_cache_win.mojom.h" | 
|  | #endif | 
|  |  | 
|  | #if BUILDFLAG(CLANG_PROFILING_INSIDE_SANDBOX) | 
|  | #include "content/public/common/profiling_utils.h" | 
|  | #endif | 
|  |  | 
|  | #if BUILDFLAG(CONTENT_ENABLE_LEGACY_IPC) | 
|  | #include "content/public/browser/browser_message_filter.h" | 
|  | #endif | 
|  |  | 
|  | namespace content { | 
|  | namespace { | 
|  |  | 
|  | static base::LazyInstance< | 
|  | BrowserChildProcessHostImpl::BrowserChildProcessList>::DestructorAtExit | 
|  | g_child_process_list = LAZY_INSTANCE_INITIALIZER; | 
|  |  | 
|  | base::LazyInstance<base::ObserverList<BrowserChildProcessObserver>::Unchecked>:: | 
|  | DestructorAtExit g_browser_child_process_observers = | 
|  | LAZY_INSTANCE_INITIALIZER; | 
|  |  | 
|  | void NotifyProcessLaunchedAndConnected(const ChildProcessData& data) { | 
|  | // Assert that the process is valid, as guaranteed in a comment on the | 
|  | // declaration of `BrowserChildProcessLaunchedAndConnected()`. | 
|  | CHECK(data.GetProcess().IsValid()); | 
|  |  | 
|  | for (auto& observer : g_browser_child_process_observers.Get()) | 
|  | observer.BrowserChildProcessLaunchedAndConnected(data); | 
|  | } | 
|  |  | 
|  | void NotifyProcessKilled(const ChildProcessData& data, | 
|  | const ChildProcessTerminationInfo& info) { | 
|  | for (auto& observer : g_browser_child_process_observers.Get()) | 
|  | observer.BrowserChildProcessKilled(data, info); | 
|  | } | 
|  |  | 
|  | memory_instrumentation::mojom::ProcessType GetCoordinatorClientProcessType( | 
|  | ProcessType process_type) { | 
|  | switch (process_type) { | 
|  | case PROCESS_TYPE_RENDERER: | 
|  | return memory_instrumentation::mojom::ProcessType::RENDERER; | 
|  | case PROCESS_TYPE_UTILITY: | 
|  | return memory_instrumentation::mojom::ProcessType::UTILITY; | 
|  | case PROCESS_TYPE_GPU: | 
|  | return memory_instrumentation::mojom::ProcessType::GPU; | 
|  | default: | 
|  | NOTREACHED(); | 
|  | } | 
|  | } | 
|  | void BindTracedProcessFromUIThread( | 
|  | base::WeakPtr<BrowserChildProcessHostImpl> weak_host, | 
|  | mojo::PendingReceiver<tracing::mojom::TracedProcess> receiver) { | 
|  | if (!weak_host) | 
|  | return; | 
|  |  | 
|  | weak_host->GetHost()->BindReceiver(std::move(receiver)); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | // static | 
|  | std::unique_ptr<BrowserChildProcessHost> BrowserChildProcessHost::Create( | 
|  | content::ProcessType process_type, | 
|  | BrowserChildProcessHostDelegate* delegate, | 
|  | ChildProcessHost::IpcMode ipc_mode) { | 
|  | return std::make_unique<BrowserChildProcessHostImpl>(process_type, delegate, | 
|  | ipc_mode); | 
|  | } | 
|  |  | 
|  | BrowserChildProcessHost* BrowserChildProcessHost::FromID(int child_process_id) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | BrowserChildProcessHostImpl::BrowserChildProcessList* process_list = | 
|  | g_child_process_list.Pointer(); | 
|  | for (BrowserChildProcessHostImpl* host : *process_list) { | 
|  | if (host->GetData().id == child_process_id) | 
|  | return host; | 
|  | } | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | #if BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_IOS_TVOS) | 
|  | base::PortProvider* BrowserChildProcessHost::GetPortProvider() { | 
|  | return ChildProcessTaskPortProvider::GetInstance(); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | // static | 
|  | BrowserChildProcessHostImpl::BrowserChildProcessList* | 
|  | BrowserChildProcessHostImpl::GetIterator() { | 
|  | return g_child_process_list.Pointer(); | 
|  | } | 
|  |  | 
|  | // static | 
|  | void BrowserChildProcessHostImpl::AddObserver( | 
|  | BrowserChildProcessObserver* observer) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | g_browser_child_process_observers.Get().AddObserver(observer); | 
|  | } | 
|  |  | 
|  | // static | 
|  | void BrowserChildProcessHostImpl::RemoveObserver( | 
|  | BrowserChildProcessObserver* observer) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | g_browser_child_process_observers.Get().RemoveObserver(observer); | 
|  | } | 
|  |  | 
|  | BrowserChildProcessHostImpl::BrowserChildProcessHostImpl( | 
|  | content::ProcessType process_type, | 
|  | BrowserChildProcessHostDelegate* delegate, | 
|  | ChildProcessHost::IpcMode ipc_mode) | 
|  | : data_(process_type, ChildProcessHostImpl::GenerateChildProcessUniqueId()), | 
|  | delegate_(delegate) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  |  | 
|  | // Create a persistent memory segment for subprocess histograms. | 
|  | CreateMetricsAllocator(); | 
|  |  | 
|  | child_process_host_ = ChildProcessHost::Create(this, ipc_mode); | 
|  |  | 
|  | g_child_process_list.Get().push_back(this); | 
|  | GetContentClient()->browser()->BrowserChildProcessHostCreated(this); | 
|  | GetContentClient()->browser()->ExposeInterfacesToChild(&binder_map_); | 
|  | } | 
|  |  | 
|  | BrowserChildProcessHostImpl::~BrowserChildProcessHostImpl() { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  |  | 
|  | g_child_process_list.Get().remove(this); | 
|  |  | 
|  | // Skip sending the disconnected notification if the connected notification | 
|  | // was never sent. The only exception here is when the main browser process | 
|  | // hosts the child, since InProcessUtilityThreadHelper still depends on this | 
|  | // behavior to know when the utility service was shut down. | 
|  | if (!launched_and_connected_ && !in_process_) | 
|  | return; | 
|  |  | 
|  | if (launched_and_connected_ && !exited_abnormally_) { | 
|  | for (auto& observer : g_browser_child_process_observers.Get()) { | 
|  | observer.BrowserChildProcessExitedNormally(data_, | 
|  | GetTerminationInfo(false)); | 
|  | } | 
|  | } | 
|  |  | 
|  | for (auto& observer : g_browser_child_process_observers.Get()) | 
|  | observer.BrowserChildProcessHostDisconnected(data_); | 
|  | } | 
|  |  | 
|  | // static | 
|  | void BrowserChildProcessHostImpl::TerminateAll() { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | // Make a copy since the BrowserChildProcessHost dtor mutates the original | 
|  | // list. | 
|  | BrowserChildProcessList copy = g_child_process_list.Get(); | 
|  | for (auto it = copy.begin(); it != copy.end(); ++it) { | 
|  | delete (*it)->delegate();  // ~*HostDelegate deletes *HostImpl. | 
|  | } | 
|  | } | 
|  |  | 
|  | void BrowserChildProcessHostImpl::Launch( | 
|  | std::unique_ptr<SandboxedProcessLauncherDelegate> delegate, | 
|  | std::unique_ptr<base::CommandLine> cmd_line, | 
|  | bool terminate_on_shutdown) { | 
|  | LaunchWithFileData( | 
|  | std::move(delegate), std::move(cmd_line), | 
|  | /*file_data=*/std::make_unique<ChildProcessLauncherFileData>(), | 
|  | terminate_on_shutdown); | 
|  | } | 
|  |  | 
|  | const ChildProcessData& BrowserChildProcessHostImpl::GetData() { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | return data_; | 
|  | } | 
|  |  | 
|  | ChildProcessHost* BrowserChildProcessHostImpl::GetHost() { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | return child_process_host_.get(); | 
|  | } | 
|  |  | 
|  | const base::Process& BrowserChildProcessHostImpl::GetProcess() { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | return data_.GetProcess(); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<base::PersistentMemoryAllocator> | 
|  | BrowserChildProcessHostImpl::TakeMetricsAllocator() { | 
|  | return std::move(metrics_allocator_); | 
|  | } | 
|  |  | 
|  | void BrowserChildProcessHostImpl::SetName(const std::u16string& name) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | data_.name = name; | 
|  | } | 
|  |  | 
|  | void BrowserChildProcessHostImpl::SetMetricsName( | 
|  | const std::string& metrics_name) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | data_.metrics_name = metrics_name; | 
|  | } | 
|  |  | 
|  | void BrowserChildProcessHostImpl::SetProcess(base::Process process) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | DCHECK(!in_process_); | 
|  |  | 
|  | // Only NaClProcessHost uses SetProcess(), and it always involve a legacy IPC | 
|  | // channel. The channel is never connected at the time of the call, so | 
|  | // NotifyProcessLaunchedAndConnected() never has to be invoked here. | 
|  | DCHECK(has_legacy_ipc_channel_ && !is_channel_connected_); | 
|  |  | 
|  | DCHECK(!process.is_current()); | 
|  | data_.SetProcess(std::move(process)); | 
|  | } | 
|  |  | 
|  | void BrowserChildProcessHostImpl::ForceShutdown() { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | g_child_process_list.Get().remove(this); | 
|  | child_process_host_->ForceShutdown(); | 
|  | } | 
|  |  | 
|  | #if BUILDFLAG(CONTENT_ENABLE_LEGACY_IPC) | 
|  | void BrowserChildProcessHostImpl::AddFilter(BrowserMessageFilter* filter) { | 
|  | child_process_host_->AddFilter(filter->GetFilter()); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | void BrowserChildProcessHostImpl::LaunchWithFileData( | 
|  | std::unique_ptr<SandboxedProcessLauncherDelegate> delegate, | 
|  | std::unique_ptr<base::CommandLine> cmd_line, | 
|  | std::unique_ptr<ChildProcessLauncherFileData> file_data, | 
|  | bool terminate_on_shutdown) { | 
|  | GetContentClient()->browser()->AppendExtraCommandLineSwitches(cmd_line.get(), | 
|  | data_.id); | 
|  | LaunchWithoutExtraCommandLineSwitches( | 
|  | std::move(delegate), std::move(cmd_line), std::move(file_data), | 
|  | terminate_on_shutdown); | 
|  | } | 
|  |  | 
|  | void BrowserChildProcessHostImpl::LaunchWithoutExtraCommandLineSwitches( | 
|  | std::unique_ptr<SandboxedProcessLauncherDelegate> delegate, | 
|  | std::unique_ptr<base::CommandLine> cmd_line, | 
|  | std::unique_ptr<ChildProcessLauncherFileData> file_data, | 
|  | bool terminate_on_shutdown) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | DCHECK(!in_process_); | 
|  |  | 
|  | const base::CommandLine& browser_command_line = | 
|  | *base::CommandLine::ForCurrentProcess(); | 
|  | static const char* const kForwardSwitches[] = { | 
|  | switches::kDisableInProcessStackTraces, | 
|  | switches::kDisableBestEffortTasks, | 
|  | switches::kIPCConnectionTimeout, | 
|  | switches::kLogBestEffortTasks, | 
|  | switches::kPerfettoDisableInterning, | 
|  | switches::kTraceToConsole, | 
|  | }; | 
|  | cmd_line->CopySwitchesFrom(browser_command_line, kForwardSwitches); | 
|  |  | 
|  | // All processes should have a non-empty metrics name. | 
|  | if (data_.metrics_name.empty()) | 
|  | data_.metrics_name = GetProcessTypeNameInEnglish(data_.process_type); | 
|  |  | 
|  | data_.sandbox_type = delegate->GetSandboxType(); | 
|  |  | 
|  | // Note that if this host has a legacy IPC Channel, we don't dispatch any | 
|  | // connection status notifications until we observe OnChannelConnected(). | 
|  | #if BUILDFLAG(CLANG_PROFILING_INSIDE_SANDBOX) | 
|  | bool is_elevated = false; | 
|  | #if BUILDFLAG(IS_WIN) | 
|  | is_elevated = (delegate->GetSandboxType() == | 
|  | sandbox::mojom::Sandbox::kNoSandboxAndElevatedPrivileges); | 
|  | #endif | 
|  | if (!is_elevated) | 
|  | child_process_host_->SetProfilingFile(OpenProfilingFile()); | 
|  | #endif | 
|  |  | 
|  | tracing_config_memory_region_ = | 
|  | MakeRefCounted<base::RefCountedData<base::ReadOnlySharedMemoryRegion>>( | 
|  | tracing::CreateTracingConfigSharedMemory()); | 
|  | tracing_output_memory_region_ = | 
|  | tracing_config_memory_region_->data.IsValid() | 
|  | ? MakeRefCounted< | 
|  | base::RefCountedData<base::UnsafeSharedMemoryRegion>>( | 
|  | tracing::CreateTracingOutputSharedMemory()) | 
|  | : nullptr; | 
|  |  | 
|  | child_process_launcher_ = std::make_unique<ChildProcessLauncher>( | 
|  | std::move(delegate), std::move(cmd_line), data_.id, this, | 
|  | std::move(*child_process_host_->GetMojoInvitation()), | 
|  | base::BindRepeating(&BrowserChildProcessHostImpl::OnMojoError, | 
|  | weak_factory_.GetWeakPtr(), | 
|  | base::SingleThreadTaskRunner::GetCurrentDefault()), | 
|  | std::move(file_data), | 
|  | base::HistogramSharedMemory::PassOnCommandLineIsEnabled( | 
|  | data_.process_type) | 
|  | ? metrics_shared_region_ | 
|  | : nullptr, | 
|  | tracing_config_memory_region_, tracing_output_memory_region_, | 
|  | terminate_on_shutdown); | 
|  | ShareMetricsAllocatorToProcess(); | 
|  |  | 
|  | if (!has_legacy_ipc_channel_) | 
|  | OnProcessConnected(); | 
|  | } | 
|  |  | 
|  | #if !BUILDFLAG(IS_ANDROID) | 
|  | void BrowserChildProcessHostImpl::SetProcessPriority( | 
|  | base::Process::Priority priority) { | 
|  | DCHECK(child_process_launcher_); | 
|  | DCHECK(!child_process_launcher_->IsStarting()); | 
|  | child_process_launcher_->SetProcessPriority(priority); | 
|  | } | 
|  | #endif  // !BUILDFLAG(IS_ANDROID) | 
|  |  | 
|  | #if BUILDFLAG(IS_ANDROID) | 
|  | void BrowserChildProcessHostImpl::EnableWarmUpConnection() { | 
|  | can_use_warm_up_connection_ = true; | 
|  | } | 
|  |  | 
|  | void BrowserChildProcessHostImpl::DumpProcessStack() { | 
|  | if (!child_process_launcher_) { | 
|  | return; | 
|  | } | 
|  | child_process_launcher_->DumpProcessStack(); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | ChildProcessTerminationInfo BrowserChildProcessHostImpl::GetTerminationInfo( | 
|  | bool known_dead) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | if (!child_process_launcher_) { | 
|  | // If the delegate doesn't use Launch() helper. | 
|  | ChildProcessTerminationInfo info; | 
|  | // TODO(crbug.com/40255458): iOS is single process mode for now. | 
|  | #if !BUILDFLAG(IS_IOS) | 
|  | info.status = base::GetTerminationStatus(data_.GetProcess().Handle(), | 
|  | &info.exit_code); | 
|  | #endif | 
|  | return info; | 
|  | } | 
|  | return child_process_launcher_->GetChildTerminationInfo(known_dead); | 
|  | } | 
|  |  | 
|  | bool BrowserChildProcessHostImpl::OnMessageReceived( | 
|  | const IPC::Message& message) { | 
|  | return delegate_->OnMessageReceived(message); | 
|  | } | 
|  |  | 
|  | void BrowserChildProcessHostImpl::OnChannelConnected(int32_t peer_pid) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  |  | 
|  | DCHECK(has_legacy_ipc_channel_); | 
|  | is_channel_connected_ = true; | 
|  |  | 
|  | delegate_->OnChannelConnected(peer_pid); | 
|  |  | 
|  | OnProcessConnected(); | 
|  | } | 
|  |  | 
|  | void BrowserChildProcessHostImpl::OnProcessConnected() { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | #if BUILDFLAG(IS_WIN) | 
|  | // From this point onward, the exit of the child process is detected by an | 
|  | // error on the IPC channel or ChildProcessHost pipe. | 
|  | early_exit_watcher_.StopWatching(); | 
|  | #endif | 
|  |  | 
|  | if (IsProcessLaunched()) { | 
|  | launched_and_connected_ = true; | 
|  | NotifyProcessLaunchedAndConnected(data_); | 
|  | } | 
|  | } | 
|  |  | 
|  | void BrowserChildProcessHostImpl::OnChannelError() { | 
|  | delegate_->OnChannelError(); | 
|  | } | 
|  |  | 
|  | void BrowserChildProcessHostImpl::OnBadMessageReceived( | 
|  | const IPC::Message& message) { | 
|  | std::string log_message = "Bad message received of type: "; | 
|  | if (message.IsValid()) { | 
|  | log_message += base::NumberToString(message.type()); | 
|  | } else { | 
|  | log_message += "unknown"; | 
|  | } | 
|  | TerminateOnBadMessageReceived(log_message); | 
|  | } | 
|  |  | 
|  | void BrowserChildProcessHostImpl::BindChildHistogramFetcherFactory( | 
|  | mojo::PendingReceiver<metrics::mojom::ChildHistogramFetcherFactory> | 
|  | factory) { | 
|  | GetHost()->BindReceiver(std::move(factory)); | 
|  | } | 
|  |  | 
|  | void BrowserChildProcessHostImpl::TerminateOnBadMessageReceived( | 
|  | const std::string& error) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  |  | 
|  | // Create a memory dump. This will contain enough stack frames to work out | 
|  | // what the bad message was. | 
|  | base::debug::DumpWithoutCrashing(); | 
|  |  | 
|  | TerminateProcessForBadMessage(weak_factory_.GetWeakPtr(), error); | 
|  | } | 
|  |  | 
|  | void BrowserChildProcessHostImpl::OnChannelInitialized(IPC::Channel* channel) { | 
|  | has_legacy_ipc_channel_ = true; | 
|  |  | 
|  | // When using a legacy IPC Channel, we defer any notifications until the | 
|  | // Channel handshake is complete. See OnChannelConnected(). | 
|  | is_channel_connected_ = false; | 
|  | } | 
|  |  | 
|  | void BrowserChildProcessHostImpl::OnChildDisconnected() { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  |  | 
|  | tracing_registration_.reset(); | 
|  |  | 
|  | #if BUILDFLAG(IS_WIN) | 
|  | // OnChildDisconnected may be called without OnChannelConnected, so stop the | 
|  | // early exit watcher so GetTerminationStatus can close the process handle. | 
|  | early_exit_watcher_.StopWatching(); | 
|  | #endif | 
|  |  | 
|  | if (child_process_launcher_.get() || IsProcessLaunched()) { | 
|  | ChildProcessTerminationInfo info = | 
|  | GetTerminationInfo(true /* known_dead */); | 
|  | #if BUILDFLAG(IS_ANDROID) | 
|  | info.has_spare_renderer = | 
|  | SpareRenderProcessHostManagerImpl::Get().HasSpareRenderer(); | 
|  | exited_abnormally_ = true; | 
|  | // Do not treat clean_exit, ie when child process exited due to quitting | 
|  | // its main loop, as a crash. | 
|  | if (!info.clean_exit) { | 
|  | delegate_->OnProcessCrashed(info.exit_code); | 
|  | } | 
|  | NotifyProcessKilled(data_, info); | 
|  | #else  // BUILDFLAG(IS_ANDROID) | 
|  | switch (info.status) { | 
|  | case base::TERMINATION_STATUS_PROCESS_CRASHED: | 
|  | case base::TERMINATION_STATUS_ABNORMAL_TERMINATION: { | 
|  | exited_abnormally_ = true; | 
|  | delegate_->OnProcessCrashed(info.exit_code); | 
|  | for (auto& observer : g_browser_child_process_observers.Get()) | 
|  | observer.BrowserChildProcessCrashed(data_, info); | 
|  | UMA_HISTOGRAM_ENUMERATION("ChildProcess.Crashed2", | 
|  | static_cast<ProcessType>(data_.process_type), | 
|  | PROCESS_TYPE_MAX); | 
|  | break; | 
|  | } | 
|  | #if BUILDFLAG(IS_CHROMEOS) | 
|  | case base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM: | 
|  | #endif | 
|  | case base::TERMINATION_STATUS_PROCESS_WAS_KILLED: { | 
|  | exited_abnormally_ = true; | 
|  | delegate_->OnProcessCrashed(info.exit_code); | 
|  | NotifyProcessKilled(data_, info); | 
|  | // Report that this child process was killed. | 
|  | UMA_HISTOGRAM_ENUMERATION("ChildProcess.Killed2", | 
|  | static_cast<ProcessType>(data_.process_type), | 
|  | PROCESS_TYPE_MAX); | 
|  | break; | 
|  | } | 
|  | case base::TERMINATION_STATUS_STILL_RUNNING: { | 
|  | UMA_HISTOGRAM_ENUMERATION("ChildProcess.DisconnectedAlive2", | 
|  | static_cast<ProcessType>(data_.process_type), | 
|  | PROCESS_TYPE_MAX); | 
|  | break; | 
|  | } | 
|  | case base::TERMINATION_STATUS_LAUNCH_FAILED: { | 
|  | // This is handled in OnProcessLaunchFailed. | 
|  | NOTREACHED(); | 
|  | } | 
|  | case base::TERMINATION_STATUS_NORMAL_TERMINATION: { | 
|  | // TODO(wfh): This should not be hit but is sometimes. Investigate. | 
|  | break; | 
|  | } | 
|  | case base::TERMINATION_STATUS_OOM: { | 
|  | // TODO(wfh): Decide to what to do with OOMs here. | 
|  | break; | 
|  | } | 
|  | #if BUILDFLAG(IS_WIN) | 
|  | case base::TERMINATION_STATUS_INTEGRITY_FAILURE: { | 
|  | // TODO(wfh): Decide to what to do with CIG failures here. | 
|  | break; | 
|  | } | 
|  | #endif  // BUILDFLAG(IS_WIN) | 
|  | case base::TERMINATION_STATUS_MAX_ENUM: { | 
|  | NOTREACHED(); | 
|  | } | 
|  | } | 
|  | #endif  // BUILDFLAG(IS_ANDROID) | 
|  | } | 
|  | delete delegate_;  // Will delete us | 
|  | } | 
|  |  | 
|  | bool BrowserChildProcessHostImpl::Send(IPC::Message* message) { | 
|  | DCHECK(has_legacy_ipc_channel_); | 
|  | return child_process_host_->Send(message); | 
|  | } | 
|  |  | 
|  | void BrowserChildProcessHostImpl::CreateMetricsAllocator() { | 
|  | // Create a persistent memory segment for subprocess histograms only if | 
|  | // they're active in the browser. | 
|  | // TODO(crbug.com/40818143): Remove this. | 
|  | if (!base::GlobalHistogramAllocator::Get()) { | 
|  | DVLOG(1) << "GlobalHistogramAllocator not configured"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | // This class is not expected to be used for renderer child processes. | 
|  | // TODO(crbug.com/40109064): CHECK, once proven that this scenario does not | 
|  | // occur in the wild, else remove dump and just return early if disproven. | 
|  | if (data_.process_type == PROCESS_TYPE_RENDERER) { | 
|  | base::debug::DumpWithoutCrashing(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Get the shared memory configuration for this process type, if any, | 
|  | auto shared_memory_config = | 
|  | GetHistogramSharedMemoryConfig(data_.process_type); | 
|  | if (!shared_memory_config.has_value()) { | 
|  | DVLOG(1) << "No histogram shared memory configured: " << "pid=" << data_.id | 
|  | << "; process_type='" | 
|  | << GetProcessTypeNameInEnglish(data_.process_type) << "'"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Create the shared memory region and histogram allocator. | 
|  | auto shared_memory = base::HistogramSharedMemory::Create( | 
|  | data_.id, shared_memory_config.value()); | 
|  |  | 
|  | if (!shared_memory.has_value()) { | 
|  | DVLOG(1) << "Failed to create histogram shared memory for pid=" << data_.id | 
|  | << "; process_type='" | 
|  | << GetProcessTypeNameInEnglish(data_.process_type) << "'"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | DVLOG(1) << "Createdhistogram shared memory for pid=" << data_.id | 
|  | << "; process_type='" | 
|  | << GetProcessTypeNameInEnglish(data_.process_type) << "'"; | 
|  |  | 
|  | metrics_shared_region_ = | 
|  | MakeRefCounted<base::RefCountedData<base::UnsafeSharedMemoryRegion>>( | 
|  | std::move(shared_memory->region)); | 
|  | metrics_allocator_ = std::move(shared_memory->allocator); | 
|  | } | 
|  |  | 
|  | void BrowserChildProcessHostImpl::ShareMetricsAllocatorToProcess() { | 
|  | // Only get histograms from content process types; skip "embedder" process | 
|  | // types. | 
|  | const bool is_content_process = | 
|  | (data_.process_type < PROCESS_TYPE_CONTENT_END); | 
|  |  | 
|  | // Get histogram data from content processes; exchange pings with embedder | 
|  | // processes. | 
|  | const auto histogram_mode = | 
|  | is_content_process | 
|  | ? metrics::HistogramController::ChildProcessMode::kGetHistogramData | 
|  | : metrics::HistogramController::ChildProcessMode::kPingOnly; | 
|  |  | 
|  | // If this is a content process, but passing the shared memory region on the | 
|  | // command line is NOT enabled for this process type, then we pass the region | 
|  | // via the child's HistogramController, below; otherwise, we give the | 
|  | // HistogramController a default (invalid) region. | 
|  | // TODO(crbug.com/40818143): simplify to always pass an empty region or to | 
|  | // elide that param once passing the region via the command line is fully | 
|  | // launched for all content process types. | 
|  | auto memory_region = | 
|  | is_content_process && metrics_shared_region_ && | 
|  | !base::HistogramSharedMemory::PassOnCommandLineIsEnabled( | 
|  | data_.process_type) | 
|  | ? std::move(metrics_shared_region_->data) | 
|  | : base::UnsafeSharedMemoryRegion(); | 
|  |  | 
|  | // Pass the shared memory region to use for future histogram transmission | 
|  | // (an invalid region if the region was already passed via the command line) | 
|  | // and ask the child to transmit any early histograms that did not get stored | 
|  | // in shared memory. This happens exactly once for each child process. | 
|  | metrics::HistogramController::GetInstance()->SetHistogramMemory( | 
|  | this, std::move(memory_region), histogram_mode); | 
|  |  | 
|  | // At this point the shared memory region has either been shared via command | 
|  | // line, or it has been given (moved) to the histogram controller. The child | 
|  | // process host no longer needs to track it. We can safely release the host's | 
|  | // reference. | 
|  | metrics_shared_region_.reset(); | 
|  | } | 
|  |  | 
|  | void BrowserChildProcessHostImpl::OnProcessLaunchFailed(int error_code) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | delegate_->OnProcessLaunchFailed(error_code); | 
|  | ChildProcessTerminationInfo info = | 
|  | child_process_launcher_->GetChildTerminationInfo(/*known_dead=*/true); | 
|  | #if BUILDFLAG(IS_ANDROID) | 
|  | info.has_spare_renderer = | 
|  | SpareRenderProcessHostManagerImpl::Get().HasSpareRenderer(); | 
|  | #endif | 
|  | DCHECK_EQ(info.status, base::TERMINATION_STATUS_LAUNCH_FAILED); | 
|  |  | 
|  | for (auto& observer : g_browser_child_process_observers.Get()) | 
|  | observer.BrowserChildProcessLaunchFailed(data_, info); | 
|  | delete delegate_;  // Will delete us | 
|  | } | 
|  |  | 
|  | #if BUILDFLAG(IS_ANDROID) | 
|  | bool BrowserChildProcessHostImpl::CanUseWarmUpConnection() { | 
|  | return can_use_warm_up_connection_; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | void BrowserChildProcessHostImpl::OnProcessLaunched() { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  |  | 
|  | const base::Process& process = child_process_launcher_->GetProcess(); | 
|  | DCHECK(process.IsValid()); | 
|  |  | 
|  | #if BUILDFLAG(IS_MAC) | 
|  | ChildProcessTaskPortProvider::GetInstance()->OnChildProcessLaunched( | 
|  | process.Pid(), | 
|  | static_cast<ChildProcessHostImpl*>(child_process_host_.get()) | 
|  | ->child_process()); | 
|  | #endif | 
|  |  | 
|  | #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) | 
|  | child_thread_type_switcher_.SetPid(process.Pid()); | 
|  | #endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) | 
|  |  | 
|  | #if BUILDFLAG(IS_WIN) | 
|  | // Start a WaitableEventWatcher that will invoke OnProcessExitedEarly if the | 
|  | // child process exits. This watcher is stopped once the IPC channel is | 
|  | // connected and the exit of the child process is detected by an error on the | 
|  | // IPC channel thereafter. | 
|  | DCHECK(!early_exit_watcher_.GetWatchedObject()); | 
|  | early_exit_watcher_.StartWatchingOnce(process.Handle(), this); | 
|  | #endif | 
|  |  | 
|  | DCHECK(!process.is_current()); | 
|  | data_.SetProcess(process.Duplicate()); | 
|  | delegate_->OnProcessLaunched(); | 
|  |  | 
|  | if (is_channel_connected_) { | 
|  | launched_and_connected_ = true; | 
|  | NotifyProcessLaunchedAndConnected(data_); | 
|  | } | 
|  |  | 
|  | #if BUILDFLAG(IS_CHROMEOS) | 
|  | // In ChromeOS, there are still child processes of NaCl modules, and they | 
|  | // don't contribute to tracing actually. So do not register those clients | 
|  | // to the tracing service. See https://crbug.com/1101468. | 
|  | if (data_.process_type >= PROCESS_TYPE_CONTENT_END) | 
|  | return; | 
|  | #endif | 
|  |  | 
|  | tracing_registration_ = TracingServiceController::Get().RegisterClient( | 
|  | process.Pid(), base::BindRepeating(&BindTracedProcessFromUIThread, | 
|  | weak_factory_.GetWeakPtr())); | 
|  | BackgroundTracingManagerImpl::ActivateForProcess( | 
|  | GetData().id, | 
|  | static_cast<ChildProcessHostImpl*>(GetHost())->child_process()); | 
|  |  | 
|  | #if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_ANDROID) | 
|  | system_tracing_service_ = std::make_unique<tracing::SystemTracingService>(); | 
|  | child_process()->EnableSystemTracingService( | 
|  | system_tracing_service_->BindAndPassPendingRemote()); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | void BrowserChildProcessHostImpl::RegisterCoordinatorClient( | 
|  | mojo::PendingReceiver<memory_instrumentation::mojom::Coordinator> receiver, | 
|  | mojo::PendingRemote<memory_instrumentation::mojom::ClientProcess> | 
|  | client_process) { | 
|  | // Intentionally disallow non-browser processes from getting a Coordinator. | 
|  | receiver.reset(); | 
|  |  | 
|  | // The child process may have already terminated by the time this message is | 
|  | // dispatched. We do nothing in that case. | 
|  | if (!IsProcessLaunched()) | 
|  | return; | 
|  |  | 
|  | base::trace_event::MemoryDumpManager::GetInstance() | 
|  | ->GetDumpThreadTaskRunner() | 
|  | ->PostTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce( | 
|  | [](mojo::PendingReceiver< | 
|  | memory_instrumentation::mojom::Coordinator> receiver, | 
|  | mojo::PendingRemote< | 
|  | memory_instrumentation::mojom::ClientProcess> | 
|  | client_process, | 
|  | memory_instrumentation::mojom::ProcessType process_type, | 
|  | base::ProcessId process_id, | 
|  | std::optional<std::string> service_name) { | 
|  | GetMemoryInstrumentationRegistry()->RegisterClientProcess( | 
|  | std::move(receiver), std::move(client_process), | 
|  | process_type, process_id, std::move(service_name)); | 
|  | }, | 
|  | std::move(receiver), std::move(client_process), | 
|  | GetCoordinatorClientProcessType( | 
|  | static_cast<ProcessType>(data_.process_type)), | 
|  | child_process_launcher_->GetProcess().Pid(), | 
|  | delegate_->GetServiceName())); | 
|  | } | 
|  |  | 
|  | bool BrowserChildProcessHostImpl::IsProcessLaunched() const { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | return data_.GetProcess().IsValid(); | 
|  | } | 
|  |  | 
|  | // static | 
|  | void BrowserChildProcessHostImpl::OnMojoError( | 
|  | base::WeakPtr<BrowserChildProcessHostImpl> process, | 
|  | scoped_refptr<base::SingleThreadTaskRunner> task_runner, | 
|  | const std::string& error) { | 
|  | // Create a memory dump with the error message captured in a crash key value. | 
|  | // This will make it easy to determine details about what interface call | 
|  | // failed. | 
|  | // | 
|  | // It is important to call DumpWithoutCrashing synchronously - this will help | 
|  | // to preserve the callstack and the crash keys present when the bad mojo | 
|  | // message was received. | 
|  | mojo::debug::ScopedMessageErrorCrashKey scoped_error_key(error); | 
|  | base::debug::DumpWithoutCrashing(); | 
|  |  | 
|  | if (task_runner->BelongsToCurrentThread()) { | 
|  | TerminateProcessForBadMessage(process, error); | 
|  | } else { | 
|  | task_runner->PostTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce( | 
|  | &BrowserChildProcessHostImpl::TerminateProcessForBadMessage, | 
|  | process, error)); | 
|  | } | 
|  | } | 
|  |  | 
|  | // static | 
|  | void BrowserChildProcessHostImpl::TerminateProcessForBadMessage( | 
|  | base::WeakPtr<BrowserChildProcessHostImpl> process, | 
|  | const std::string& error) { | 
|  | if (!process) | 
|  | return; | 
|  | if (base::CommandLine::ForCurrentProcess()->HasSwitch( | 
|  | switches::kDisableKillAfterBadIPC)) { | 
|  | return; | 
|  | } | 
|  | DVLOG(1) << "Terminating child process for bad message: " << error; | 
|  | process->child_process_launcher_->Terminate(RESULT_CODE_KILLED_BAD_MESSAGE); | 
|  | } | 
|  |  | 
|  | #if BUILDFLAG(IS_WIN) | 
|  |  | 
|  | void BrowserChildProcessHostImpl::OnObjectSignaled(HANDLE object) { | 
|  | OnChildDisconnected(); | 
|  | } | 
|  |  | 
|  | #endif | 
|  |  | 
|  | }  // namespace content |