| // 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/dump_without_crashing.h" |
| #include "base/files/file_path.h" |
| #include "base/lazy_instance.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/metrics/histogram.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_util.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "build/build_config.h" |
| #include "components/tracing/tracing_switches.h" |
| #include "content/browser/histogram_message_filter.h" |
| #include "content/browser/loader/resource_message_filter.h" |
| #include "content/browser/memory/memory_message_filter.h" |
| #include "content/browser/profiler_message_filter.h" |
| #include "content/browser/tracing/trace_message_filter.h" |
| #include "content/common/child_process_host_impl.h" |
| #include "content/common/child_process_messages.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/content_switches.h" |
| #include "content/public/common/process_type.h" |
| #include "content/public/common/result_codes.h" |
| #include "ipc/attachment_broker.h" |
| #include "ipc/attachment_broker_privileged.h" |
| #include "third_party/mojo/src/mojo/edk/embedder/embedder.h" |
| |
| #if defined(OS_MACOSX) |
| #include "content/browser/mach_broker_mac.h" |
| #endif |
| |
| |
| #if defined(MOJO_SHELL_CLIENT) |
| #include "content/browser/mojo/mojo_shell_client_host.h" |
| #include "content/common/mojo/mojo_shell_connection_impl.h" |
| #endif |
| |
| namespace content { |
| namespace { |
| |
| static base::LazyInstance<BrowserChildProcessHostImpl::BrowserChildProcessList> |
| g_child_process_list = LAZY_INSTANCE_INITIALIZER; |
| |
| base::LazyInstance<base::ObserverList<BrowserChildProcessObserver>> |
| g_observers = LAZY_INSTANCE_INITIALIZER; |
| |
| void NotifyProcessLaunchedAndConnected(const ChildProcessData& data) { |
| FOR_EACH_OBSERVER(BrowserChildProcessObserver, g_observers.Get(), |
| BrowserChildProcessLaunchedAndConnected(data)); |
| } |
| |
| void NotifyProcessHostConnected(const ChildProcessData& data) { |
| FOR_EACH_OBSERVER(BrowserChildProcessObserver, g_observers.Get(), |
| BrowserChildProcessHostConnected(data)); |
| } |
| |
| void NotifyProcessHostDisconnected(const ChildProcessData& data) { |
| FOR_EACH_OBSERVER(BrowserChildProcessObserver, g_observers.Get(), |
| BrowserChildProcessHostDisconnected(data)); |
| } |
| |
| void NotifyProcessCrashed(const ChildProcessData& data, int exit_code) { |
| FOR_EACH_OBSERVER(BrowserChildProcessObserver, g_observers.Get(), |
| BrowserChildProcessCrashed(data, exit_code)); |
| } |
| |
| void NotifyProcessKilled(const ChildProcessData& data, int exit_code) { |
| FOR_EACH_OBSERVER(BrowserChildProcessObserver, g_observers.Get(), |
| BrowserChildProcessKilled(data, exit_code)); |
| } |
| |
| } // namespace |
| |
| BrowserChildProcessHost* BrowserChildProcessHost::Create( |
| content::ProcessType process_type, |
| BrowserChildProcessHostDelegate* delegate) { |
| return new BrowserChildProcessHostImpl(process_type, delegate); |
| } |
| |
| 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_observers.Get().AddObserver(observer); |
| } |
| |
| // static |
| void BrowserChildProcessHostImpl::RemoveObserver( |
| BrowserChildProcessObserver* observer) { |
| // TODO(phajdan.jr): Check thread after fixing http://crbug.com/167126. |
| g_observers.Get().RemoveObserver(observer); |
| } |
| |
| BrowserChildProcessHostImpl::BrowserChildProcessHostImpl( |
| content::ProcessType process_type, |
| BrowserChildProcessHostDelegate* delegate) |
| : data_(process_type), |
| delegate_(delegate), |
| power_monitor_message_broadcaster_(this), |
| is_channel_connected_(false) { |
| data_.id = ChildProcessHostImpl::GenerateChildProcessUniqueId(); |
| |
| #if USE_ATTACHMENT_BROKER |
| // Construct the privileged attachment broker early in the life cycle of a |
| // child process. This ensures that when a test is being run in one of the |
| // single process modes, the global attachment broker is the privileged |
| // attachment broker, rather than an unprivileged attachment broker. |
| #if defined(OS_MACOSX) && !defined(OS_IOS) |
| IPC::AttachmentBrokerPrivileged::CreateBrokerIfNeeded( |
| MachBroker::GetInstance()); |
| #else |
| IPC::AttachmentBrokerPrivileged::CreateBrokerIfNeeded(); |
| #endif // defined(OS_MACOSX) && !defined(OS_IOS) |
| #endif // USE_ATTACHMENT_BROKER |
| |
| child_process_host_.reset(ChildProcessHost::Create(this)); |
| AddFilter(new TraceMessageFilter(data_.id)); |
| AddFilter(new ProfilerMessageFilter(process_type)); |
| AddFilter(new HistogramMessageFilter); |
| AddFilter(new MemoryMessageFilter(this, process_type)); |
| |
| g_child_process_list.Get().push_back(this); |
| GetContentClient()->browser()->BrowserChildProcessHostCreated(this); |
| |
| power_monitor_message_broadcaster_.Init(); |
| } |
| |
| BrowserChildProcessHostImpl::~BrowserChildProcessHostImpl() { |
| g_child_process_list.Get().remove(this); |
| } |
| |
| // 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. |
| } |
| } |
| |
| void BrowserChildProcessHostImpl::Launch( |
| SandboxedProcessLauncherDelegate* delegate, |
| base::CommandLine* cmd_line, |
| bool terminate_on_shutdown) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| GetContentClient()->browser()->AppendExtraCommandLineSwitches( |
| cmd_line, data_.id); |
| |
| const base::CommandLine& browser_command_line = |
| *base::CommandLine::ForCurrentProcess(); |
| static const char* kForwardSwitches[] = { |
| switches::kDisableLogging, |
| switches::kEnableLogging, |
| switches::kIPCConnectionTimeout, |
| switches::kLoggingLevel, |
| switches::kTraceToConsole, |
| switches::kV, |
| switches::kVModule, |
| "use-new-edk", // TODO(use_chrome_edk): temporary. |
| }; |
| cmd_line->CopySwitchesFrom(browser_command_line, kForwardSwitches, |
| arraysize(kForwardSwitches)); |
| |
| child_process_.reset(new ChildProcessLauncher( |
| delegate, |
| cmd_line, |
| data_.id, |
| this, |
| 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(); |
| } |
| |
| void BrowserChildProcessHostImpl::SetName(const base::string16& name) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| data_.name = name; |
| } |
| |
| void BrowserChildProcessHostImpl::SetHandle(base::ProcessHandle handle) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| data_.handle = handle; |
| } |
| |
| ServiceRegistry* BrowserChildProcessHostImpl::GetServiceRegistry() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| return delegate_->GetServiceRegistry(); |
| } |
| |
| void BrowserChildProcessHostImpl::ForceShutdown() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| g_child_process_list.Get().remove(this); |
| child_process_host_->ForceShutdown(); |
| } |
| |
| void BrowserChildProcessHostImpl::SetBackgrounded(bool backgrounded) { |
| child_process_->SetProcessBackgrounded(backgrounded); |
| } |
| |
| void BrowserChildProcessHostImpl::AddFilter(BrowserMessageFilter* filter) { |
| child_process_host_->AddFilter(filter->GetFilter()); |
| } |
| |
| void BrowserChildProcessHostImpl::NotifyProcessInstanceCreated( |
| const ChildProcessData& data) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| FOR_EACH_OBSERVER(BrowserChildProcessObserver, g_observers.Get(), |
| BrowserChildProcessInstanceCreated(data)); |
| } |
| |
| void BrowserChildProcessHostImpl::HistogramBadMessageTerminated( |
| int process_type) { |
| UMA_HISTOGRAM_ENUMERATION("ChildProcess.BadMessgeTerminated", process_type, |
| PROCESS_TYPE_MAX); |
| } |
| |
| base::TerminationStatus BrowserChildProcessHostImpl::GetTerminationStatus( |
| bool known_dead, int* exit_code) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (!child_process_) // If the delegate doesn't use Launch() helper. |
| return base::GetTerminationStatus(data_.handle, exit_code); |
| return child_process_->GetChildTerminationStatus(known_dead, |
| exit_code); |
| } |
| |
| 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; |
| |
| #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::Bind(&NotifyProcessHostConnected, data_)); |
| |
| delegate_->OnChannelConnected(peer_pid); |
| |
| if (IsProcessLaunched()) { |
| BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| base::Bind(&NotifyProcessLaunchedAndConnected, |
| data_)); |
| } |
| } |
| |
| void BrowserChildProcessHostImpl::OnChannelError() { |
| delegate_->OnChannelError(); |
| } |
| |
| void BrowserChildProcessHostImpl::OnBadMessageReceived( |
| const IPC::Message& message) { |
| TerminateOnBadMessageReceived(message.type()); |
| } |
| |
| void BrowserChildProcessHostImpl::TerminateOnBadMessageReceived(uint32_t type) { |
| HistogramBadMessageTerminated(data_.process_type); |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kDisableKillAfterBadIPC)) { |
| return; |
| } |
| LOG(ERROR) << "Terminating child process for bad IPC message of type " |
| << type; |
| |
| // Create a memory dump. This will contain enough stack frames to work out |
| // what the bad message was. |
| base::debug::DumpWithoutCrashing(); |
| |
| child_process_->GetProcess().Terminate(RESULT_CODE_KILLED_BAD_MESSAGE, false); |
| } |
| |
| bool BrowserChildProcessHostImpl::CanShutdown() { |
| return delegate_->CanShutdown(); |
| } |
| |
| 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) { |
| int exit_code; |
| base::TerminationStatus status = GetTerminationStatus( |
| true /* known_dead */, &exit_code); |
| switch (status) { |
| case base::TERMINATION_STATUS_PROCESS_CRASHED: |
| case base::TERMINATION_STATUS_ABNORMAL_TERMINATION: { |
| delegate_->OnProcessCrashed(exit_code); |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(&NotifyProcessCrashed, data_, exit_code)); |
| UMA_HISTOGRAM_ENUMERATION("ChildProcess.Crashed2", |
| data_.process_type, |
| PROCESS_TYPE_MAX); |
| break; |
| } |
| #if defined(OS_ANDROID) |
| case base::TERMINATION_STATUS_OOM_PROTECTED: |
| #endif |
| #if defined(OS_CHROMEOS) |
| case base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM: |
| #endif |
| case base::TERMINATION_STATUS_PROCESS_WAS_KILLED: { |
| delegate_->OnProcessCrashed(exit_code); |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(&NotifyProcessKilled, data_, exit_code)); |
| // Report that this child process was killed. |
| UMA_HISTOGRAM_ENUMERATION("ChildProcess.Killed2", |
| data_.process_type, |
| PROCESS_TYPE_MAX); |
| break; |
| } |
| case base::TERMINATION_STATUS_STILL_RUNNING: { |
| UMA_HISTOGRAM_ENUMERATION("ChildProcess.DisconnectedAlive2", |
| data_.process_type, |
| PROCESS_TYPE_MAX); |
| } |
| default: |
| break; |
| } |
| UMA_HISTOGRAM_ENUMERATION("ChildProcess.Disconnected2", |
| data_.process_type, |
| PROCESS_TYPE_MAX); |
| #if defined(OS_CHROMEOS) |
| if (status == base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM) { |
| UMA_HISTOGRAM_ENUMERATION("ChildProcess.Killed2.OOM", |
| data_.process_type, |
| PROCESS_TYPE_MAX); |
| } |
| #endif |
| } |
| BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| base::Bind(&NotifyProcessHostDisconnected, data_)); |
| delete delegate_; // Will delete us |
| } |
| |
| bool BrowserChildProcessHostImpl::Send(IPC::Message* message) { |
| return child_process_host_->Send(message); |
| } |
| |
| void BrowserChildProcessHostImpl::OnProcessLaunchFailed() { |
| delegate_->OnProcessLaunchFailed(); |
| delete delegate_; // Will delete us |
| } |
| |
| void BrowserChildProcessHostImpl::OnProcessLaunched() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| const base::Process& process = child_process_->GetProcess(); |
| DCHECK(process.IsValid()); |
| |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch("use-new-edk")) { |
| mojo::embedder::ScopedPlatformHandle client_pipe; |
| #if defined(MOJO_SHELL_CLIENT) |
| if (IsRunningInMojoShell()) { |
| client_pipe = RegisterProcessWithBroker(process.Pid()); |
| } else |
| #endif |
| { |
| client_pipe = mojo::embedder::ChildProcessLaunched(process.Handle()); |
| } |
| Send(new ChildProcessMsg_SetMojoParentPipeHandle( |
| IPC::GetFileHandleForProcess( |
| #if defined(OS_WIN) |
| client_pipe.release().handle, |
| #else |
| client_pipe.release().fd, |
| #endif |
| process.Handle(), true))); |
| } |
| |
| #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_) { |
| BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| base::Bind(&NotifyProcessLaunchedAndConnected, |
| data_)); |
| } |
| } |
| |
| bool BrowserChildProcessHostImpl::IsProcessLaunched() const { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| return child_process_.get() && child_process_->GetProcess().IsValid(); |
| } |
| |
| #if defined(OS_WIN) |
| |
| void BrowserChildProcessHostImpl::OnObjectSignaled(HANDLE object) { |
| OnChildDisconnected(); |
| } |
| |
| #endif |
| |
| } // namespace content |