blob: 3fc3aa2f44c794a7be390082a8eb0653f85b3d11 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// 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 "base/base_switches.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/persistent_histogram_allocator.h"
#include "base/metrics/persistent_memory_allocator.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "components/tracing/common/trace_startup_config.h"
#include "components/tracing/common/tracing_switches.h"
#include "content/browser/bad_message.h"
#include "content/browser/browser_main_loop.h"
#include "content/browser/histogram_controller.h"
#include "content/browser/loader/resource_message_filter.h"
#include "content/browser/service_manager/service_manager_context.h"
#include "content/browser/tracing/trace_message_filter.h"
#include "content/common/child_process_host_impl.h"
#include "content/common/service_manager/child_connection.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_thread.h"
#include "content/public/browser/child_process_data.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/common/connection_filter.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 "content/public/common/service_manager_connection.h"
#include "mojo/public/cpp/system/platform_handle.h"
#include "services/service_manager/embedder/switches.h"
#if defined(OS_MACOSX)
#include "content/browser/mach_broker_mac.h"
#endif
namespace content {
namespace {
static base::LazyInstance<
BrowserChildProcessHostImpl::BrowserChildProcessList>::DestructorAtExit
g_child_process_list = LAZY_INSTANCE_INITIALIZER;
base::LazyInstance<base::ObserverList<BrowserChildProcessObserver>>::
DestructorAtExit g_browser_child_process_observers =
LAZY_INSTANCE_INITIALIZER;
void NotifyProcessLaunchedAndConnected(const ChildProcessData& data) {
for (auto& observer : g_browser_child_process_observers.Get())
observer.BrowserChildProcessLaunchedAndConnected(data);
}
void NotifyProcessHostConnected(const ChildProcessData& data) {
for (auto& observer : g_browser_child_process_observers.Get())
observer.BrowserChildProcessHostConnected(data);
}
void NotifyProcessHostDisconnected(const ChildProcessData& data) {
for (auto& observer : g_browser_child_process_observers.Get())
observer.BrowserChildProcessHostDisconnected(data);
}
#if !defined(OS_ANDROID)
void NotifyProcessCrashed(const ChildProcessData& data,
const ChildProcessTerminationInfo& info) {
for (auto& observer : g_browser_child_process_observers.Get())
observer.BrowserChildProcessCrashed(data, info);
}
#endif
void NotifyProcessKilled(const ChildProcessData& data,
const ChildProcessTerminationInfo& info) {
for (auto& observer : g_browser_child_process_observers.Get())
observer.BrowserChildProcessKilled(data, info);
}
} // namespace
BrowserChildProcessHost* BrowserChildProcessHost::Create(
content::ProcessType process_type,
BrowserChildProcessHostDelegate* delegate) {
return Create(process_type, delegate, std::string());
}
BrowserChildProcessHost* BrowserChildProcessHost::Create(
content::ProcessType process_type,
BrowserChildProcessHostDelegate* delegate,
const std::string& service_name) {
return new BrowserChildProcessHostImpl(process_type, delegate, service_name);
}
BrowserChildProcessHost* BrowserChildProcessHost::FromID(int child_process_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
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 defined(OS_MACOSX)
base::PortProvider* BrowserChildProcessHost::GetPortProvider() {
return MachBroker::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) {
// TODO(phajdan.jr): Check thread after fixing http://crbug.com/167126.
g_browser_child_process_observers.Get().RemoveObserver(observer);
}
BrowserChildProcessHostImpl::BrowserChildProcessHostImpl(
content::ProcessType process_type,
BrowserChildProcessHostDelegate* delegate,
const std::string& service_name)
: data_(process_type),
delegate_(delegate),
channel_(nullptr),
is_channel_connected_(false),
notify_child_disconnected_(false),
weak_factory_(this) {
data_.id = ChildProcessHostImpl::GenerateChildProcessUniqueId();
child_process_host_.reset(ChildProcessHost::Create(this));
AddFilter(new TraceMessageFilter(data_.id));
g_child_process_list.Get().push_back(this);
GetContentClient()->browser()->BrowserChildProcessHostCreated(this);
if (!service_name.empty()) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
service_manager::Identity child_identity(
service_name, service_manager::mojom::kInheritUserID,
base::StringPrintf("%d", data_.id));
child_connection_.reset(
new ChildConnection(child_identity, &mojo_invitation_,
ServiceManagerContext::GetConnectorForIOThread(),
base::ThreadTaskRunnerHandle::Get()));
data_.metrics_name = service_name;
}
// Create a persistent memory segment for subprocess histograms.
CreateMetricsAllocator();
}
BrowserChildProcessHostImpl::~BrowserChildProcessHostImpl() {
g_child_process_list.Get().remove(this);
if (notify_child_disconnected_) {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&NotifyProcessHostDisconnected, data_));
}
}
// static
void BrowserChildProcessHostImpl::TerminateAll() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// Make a copy since the BrowserChildProcessHost dtor mutates the original
// list.
BrowserChildProcessList copy = g_child_process_list.Get();
for (BrowserChildProcessList::iterator it = copy.begin();
it != copy.end(); ++it) {
delete (*it)->delegate(); // ~*HostDelegate deletes *HostImpl.
}
}
// static
void BrowserChildProcessHostImpl::CopyFeatureAndFieldTrialFlags(
base::CommandLine* cmd_line) {
// If we run base::FieldTrials, we want to pass to their state to the
// child process so that it can act in accordance with each state.
base::FieldTrialList::CopyFieldTrialStateToFlags(
switches::kFieldTrialHandle, switches::kEnableFeatures,
switches::kDisableFeatures, cmd_line);
}
// static
void BrowserChildProcessHostImpl::CopyTraceStartupFlags(
base::CommandLine* cmd_line) {
if (tracing::TraceStartupConfig::GetInstance()->IsEnabled()) {
const auto trace_config =
tracing::TraceStartupConfig::GetInstance()->GetTraceConfig();
if (!trace_config.IsArgumentFilterEnabled()) {
// The only trace option that we can pass through switches is the record
// mode. Other trace options should have the default value.
//
// TODO(chiniforooshan): Add other trace options to switches if, for
// example, they are used in a telemetry test that needs startup trace
// events from renderer processes.
cmd_line->AppendSwitchASCII(switches::kTraceStartup,
trace_config.ToCategoryFilterString());
cmd_line->AppendSwitchASCII(
switches::kTraceStartupRecordMode,
base::trace_event::TraceConfig::TraceRecordModeToStr(
trace_config.GetTraceRecordMode()));
}
}
}
void BrowserChildProcessHostImpl::Launch(
std::unique_ptr<SandboxedProcessLauncherDelegate> delegate,
std::unique_ptr<base::CommandLine> cmd_line,
bool terminate_on_shutdown) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
GetContentClient()->browser()->AppendExtraCommandLineSwitches(cmd_line.get(),
data_.id);
const base::CommandLine& browser_command_line =
*base::CommandLine::ForCurrentProcess();
static const char* const kForwardSwitches[] = {
service_manager::switches::kDisableInProcessStackTraces,
switches::kDisableBackgroundTasks,
switches::kDisableLogging,
switches::kEnableLogging,
switches::kIPCConnectionTimeout,
switches::kLoggingLevel,
switches::kTraceToConsole,
switches::kV,
switches::kVModule,
};
cmd_line->CopySwitchesFrom(browser_command_line, kForwardSwitches,
arraysize(kForwardSwitches));
if (child_connection_) {
cmd_line->AppendSwitchASCII(
service_manager::switches::kServiceRequestChannelToken,
child_connection_->service_token());
}
// All processes should have a non-empty metrics name.
DCHECK(!data_.metrics_name.empty());
notify_child_disconnected_ = true;
child_process_.reset(new ChildProcessLauncher(
std::move(delegate), std::move(cmd_line), data_.id, this,
std::move(mojo_invitation_),
base::Bind(&BrowserChildProcessHostImpl::OnMojoError,
weak_factory_.GetWeakPtr(),
base::ThreadTaskRunnerHandle::Get()),
terminate_on_shutdown));
}
const ChildProcessData& BrowserChildProcessHostImpl::GetData() const {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
return data_;
}
ChildProcessHost* BrowserChildProcessHostImpl::GetHost() const {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
return child_process_host_.get();
}
const base::Process& BrowserChildProcessHostImpl::GetProcess() const {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(child_process_.get())
<< "Requesting a child process handle before launching.";
DCHECK(child_process_->GetProcess().IsValid())
<< "Requesting a child process handle before launch has completed OK.";
return child_process_->GetProcess();
}
std::unique_ptr<base::SharedPersistentMemoryAllocator>
BrowserChildProcessHostImpl::TakeMetricsAllocator() {
return std::move(metrics_allocator_);
}
void BrowserChildProcessHostImpl::SetName(const base::string16& name) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
data_.name = name;
}
void BrowserChildProcessHostImpl::SetMetricsName(
const std::string& metrics_name) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
data_.metrics_name = metrics_name;
}
void BrowserChildProcessHostImpl::SetHandle(base::ProcessHandle handle) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
data_.handle = handle;
}
service_manager::mojom::ServiceRequest
BrowserChildProcessHostImpl::TakeInProcessServiceRequest() {
auto invitation = std::move(mojo_invitation_);
return service_manager::mojom::ServiceRequest(
invitation.ExtractMessagePipe(child_connection_->service_token()));
}
void BrowserChildProcessHostImpl::ForceShutdown() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
g_child_process_list.Get().remove(this);
child_process_host_->ForceShutdown();
}
void BrowserChildProcessHostImpl::AddFilter(BrowserMessageFilter* filter) {
child_process_host_->AddFilter(filter->GetFilter());
}
void BrowserChildProcessHostImpl::BindInterface(
const std::string& interface_name,
mojo::ScopedMessagePipeHandle interface_pipe) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!child_connection_)
return;
child_connection_->BindInterface(interface_name, std::move(interface_pipe));
}
void BrowserChildProcessHostImpl::HistogramBadMessageTerminated(
ProcessType process_type) {
UMA_HISTOGRAM_ENUMERATION("ChildProcess.BadMessgeTerminated", process_type,
PROCESS_TYPE_MAX);
}
ChildProcessTerminationInfo BrowserChildProcessHostImpl::GetTerminationInfo(
bool known_dead) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!child_process_) {
// If the delegate doesn't use Launch() helper.
ChildProcessTerminationInfo info;
info.status = base::GetTerminationStatus(data_.handle, &info.exit_code);
return info;
}
return child_process_->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::IO);
is_channel_connected_ = true;
notify_child_disconnected_ = true;
#if defined(OS_WIN)
// From this point onward, the exit of the child process is detected by an
// error on the IPC channel.
early_exit_watcher_.StopWatching();
#endif
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::BindOnce(&NotifyProcessHostConnected, data_));
delegate_->OnChannelConnected(peer_pid);
if (IsProcessLaunched()) {
ShareMetricsAllocatorToProcess();
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&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 += std::to_string(message.type());
} else {
log_message += "unknown";
}
TerminateOnBadMessageReceived(log_message);
}
void BrowserChildProcessHostImpl::TerminateOnBadMessageReceived(
const std::string& error) {
HistogramBadMessageTerminated(static_cast<ProcessType>(data_.process_type));
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableKillAfterBadIPC)) {
return;
}
LOG(ERROR) << "Terminating child process for bad IPC message: " << error;
// Create a memory dump. This will contain enough stack frames to work out
// what the bad message was.
base::debug::DumpWithoutCrashing();
child_process_->Terminate(RESULT_CODE_KILLED_BAD_MESSAGE);
}
void BrowserChildProcessHostImpl::OnChannelInitialized(IPC::Channel* channel) {
channel_ = channel;
}
void BrowserChildProcessHostImpl::OnChildDisconnected() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
#if defined(OS_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_.get() || data_.handle) {
ChildProcessTerminationInfo info =
GetTerminationInfo(true /* known_dead */);
#if defined(OS_ANDROID)
delegate_->OnProcessCrashed(info.exit_code);
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::BindOnce(&NotifyProcessKilled, data_, info));
#else // OS_ANDROID
switch (info.status) {
case base::TERMINATION_STATUS_PROCESS_CRASHED:
case base::TERMINATION_STATUS_ABNORMAL_TERMINATION: {
delegate_->OnProcessCrashed(info.exit_code);
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&NotifyProcessCrashed, data_, info));
UMA_HISTOGRAM_ENUMERATION("ChildProcess.Crashed2",
static_cast<ProcessType>(data_.process_type),
PROCESS_TYPE_MAX);
break;
}
#if defined(OS_CHROMEOS)
case base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM:
#endif
case base::TERMINATION_STATUS_PROCESS_WAS_KILLED: {
delegate_->OnProcessCrashed(info.exit_code);
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&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;
}
default:
break;
}
#endif // OS_ANDROID
UMA_HISTOGRAM_ENUMERATION("ChildProcess.Disconnected2",
static_cast<ProcessType>(data_.process_type),
PROCESS_TYPE_MAX);
#if defined(OS_CHROMEOS)
if (info.status == base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM) {
UMA_HISTOGRAM_ENUMERATION("ChildProcess.Killed2.OOM",
static_cast<ProcessType>(data_.process_type),
PROCESS_TYPE_MAX);
}
#endif
}
channel_ = nullptr;
delete delegate_; // Will delete us
}
bool BrowserChildProcessHostImpl::Send(IPC::Message* message) {
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(bcwhite): Remove this once persistence is always enabled.
if (!base::GlobalHistogramAllocator::Get())
return;
// Determine the correct parameters based on the process type.
size_t memory_size;
base::StringPiece metrics_name;
switch (data_.process_type) {
case PROCESS_TYPE_UTILITY:
// This needs to be larger for the network service.
memory_size = 256 << 10; // 256 KiB
metrics_name = "UtilityMetrics";
break;
case PROCESS_TYPE_ZYGOTE:
memory_size = 64 << 10; // 64 KiB
metrics_name = "ZygoteMetrics";
break;
case PROCESS_TYPE_SANDBOX_HELPER:
memory_size = 64 << 10; // 64 KiB
metrics_name = "SandboxHelperMetrics";
break;
case PROCESS_TYPE_GPU:
memory_size = 64 << 10; // 64 KiB
metrics_name = "GpuMetrics";
break;
case PROCESS_TYPE_PPAPI_PLUGIN:
memory_size = 64 << 10; // 64 KiB
metrics_name = "PpapiPluginMetrics";
break;
case PROCESS_TYPE_PPAPI_BROKER:
memory_size = 64 << 10; // 64 KiB
metrics_name = "PpapiBrokerMetrics";
break;
default:
// Report new processes. "Custom" ones are renumbered to 1000+ so that
// they won't conflict with any standard ones in the future.
int process_type = data_.process_type;
if (process_type >= PROCESS_TYPE_CONTENT_END)
process_type += 1000 - PROCESS_TYPE_CONTENT_END;
base::UmaHistogramSparse(
"UMA.SubprocessMetricsProvider.UntrackedProcesses", process_type);
return;
}
// Create the shared memory segment and attach an allocator to it.
// Mapping the memory shouldn't fail but be safe if it does; everything
// will continue to work but just as if persistence weren't available.
std::unique_ptr<base::SharedMemory> shm(new base::SharedMemory());
if (!shm->CreateAndMapAnonymous(memory_size))
return;
metrics_allocator_.reset(new base::SharedPersistentMemoryAllocator(
std::move(shm), static_cast<uint64_t>(data_.id), metrics_name,
/*readonly=*/false));
}
void BrowserChildProcessHostImpl::ShareMetricsAllocatorToProcess() {
if (metrics_allocator_) {
HistogramController::GetInstance()->SetHistogramMemory<ChildProcessHost>(
GetHost(),
mojo::WrapSharedMemoryHandle(
metrics_allocator_->shared_memory()->handle().Duplicate(),
metrics_allocator_->shared_memory()->mapped_size(),
mojo::UnwrappedSharedMemoryHandleProtection::kReadWrite));
} else {
HistogramController::GetInstance()->SetHistogramMemory<ChildProcessHost>(
GetHost(), mojo::ScopedSharedBufferHandle());
}
}
void BrowserChildProcessHostImpl::OnProcessLaunchFailed(int error_code) {
delegate_->OnProcessLaunchFailed(error_code);
notify_child_disconnected_ = false;
delete delegate_; // Will delete us
}
void BrowserChildProcessHostImpl::OnProcessLaunched() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
const base::Process& process = child_process_->GetProcess();
DCHECK(process.IsValid());
if (child_connection_)
child_connection_->SetProcessHandle(process.Handle());
#if defined(OS_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 detecter by an error on the
// IPC channel thereafter.
DCHECK(!early_exit_watcher_.GetWatchedObject());
early_exit_watcher_.StartWatchingOnce(process.Handle(), this);
#endif
// TODO(rvargas) crbug.com/417532: Don't store a handle.
data_.handle = process.Handle();
delegate_->OnProcessLaunched();
if (is_channel_connected_) {
ShareMetricsAllocatorToProcess();
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&NotifyProcessLaunchedAndConnected, data_));
}
}
bool BrowserChildProcessHostImpl::IsProcessLaunched() const {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
return child_process_.get() && child_process_->GetProcess().IsValid();
}
// static
void BrowserChildProcessHostImpl::OnMojoError(
base::WeakPtr<BrowserChildProcessHostImpl> process,
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
const std::string& error) {
if (!task_runner->BelongsToCurrentThread()) {
task_runner->PostTask(
FROM_HERE, base::BindOnce(&BrowserChildProcessHostImpl::OnMojoError,
process, task_runner, error));
return;
}
if (!process)
return;
HistogramBadMessageTerminated(
static_cast<ProcessType>(process->data_.process_type));
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableKillAfterBadIPC)) {
return;
}
LOG(ERROR) << "Terminating child process for bad Mojo message: " << 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.
base::debug::ScopedCrashKeyString scoped_error_key(
bad_message::GetMojoErrorCrashKey(), error);
base::debug::DumpWithoutCrashing();
process->child_process_->Terminate(RESULT_CODE_KILLED_BAD_MESSAGE);
}
#if defined(OS_WIN)
void BrowserChildProcessHostImpl::OnObjectSignaled(HANDLE object) {
OnChildDisconnected();
}
#endif
} // namespace content