|  | // 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/metrics/histogram.h" | 
|  | #include "base/stl_util.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "base/synchronization/waitable_event.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 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) { | 
|  | HistogramBadMessageTerminated(data_.process_type); | 
|  | if (base::CommandLine::ForCurrentProcess()->HasSwitch( | 
|  | switches::kDisableKillAfterBadIPC)) { | 
|  | return; | 
|  | } | 
|  | LOG(ERROR) << "Terminating child process for bad IPC message of type " | 
|  | << message.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 |