process_watcher_win.cc (8819B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. 4 // Use of this source code is governed by a BSD-style license that can be 5 // found in the LICENSE file. 6 7 #include "chrome/common/process_watcher.h" 8 9 #include <algorithm> 10 #include <processthreadsapi.h> 11 #include <synchapi.h> 12 #include "base/message_loop.h" 13 #include "base/object_watcher.h" 14 #include "prenv.h" 15 16 // Maximum amount of time (in milliseconds) to wait for the process to exit. 17 static constexpr int kWaitInterval = 2000; 18 19 // This is somewhat arbitrary, but based on Try run results. When 20 // changing this, be aware of toolkit.asyncshutdown.crash_timeout 21 // (currently 60s), after which the parent process will be killed. 22 #ifdef MOZ_CODE_COVERAGE 23 // Child processes seem to take longer to shut down on ccov builds, at 24 // least in the wdspec tests; ~20s has been observed, and we'll spam 25 // false positives unless this is increased. 26 static constexpr DWORD kShutdownWaitMs = 80000; 27 #elif defined(MOZ_ASAN) || defined(MOZ_TSAN) 28 // Sanitizers also slow things down in some cases; see bug 1806224. 29 static constexpr DWORD kShutdownWaitMs = 40000; 30 #else 31 static constexpr DWORD kShutdownWaitMs = 20000; 32 #endif 33 34 namespace { 35 36 static bool IsProcessDead(base::ProcessHandle process) { 37 return WaitForSingleObject(process, 0) == WAIT_OBJECT_0; 38 } 39 40 class ChildReaper : public mozilla::Runnable, 41 public base::ObjectWatcher::Delegate, 42 public MessageLoop::DestructionObserver { 43 public: 44 explicit ChildReaper(base::ProcessHandle process, bool force) 45 : mozilla::Runnable("ChildReaper"), process_(process), force_(force) { 46 watcher_.StartWatching(process_, this); 47 } 48 49 virtual ~ChildReaper() { 50 if (process_) { 51 KillProcess(); 52 DCHECK(!process_) << "Make sure to close the handle."; 53 } 54 } 55 56 // MessageLoop::DestructionObserver ----------------------------------------- 57 58 virtual void WillDestroyCurrentMessageLoop() { 59 MOZ_ASSERT(!force_); 60 if (process_) { 61 // Exception for the fake hang tests in ipc/glue/test/browser 62 if (!PR_GetEnv("MOZ_TEST_CHILD_EXIT_HANG")) { 63 CrashProcessIfHanging(); 64 } 65 WaitForSingleObject(process_, INFINITE); 66 base::CloseProcessHandle(process_); 67 process_ = 0; 68 69 MessageLoop::current()->RemoveDestructionObserver(this); 70 delete this; 71 } 72 } 73 74 // Task --------------------------------------------------------------------- 75 76 NS_IMETHOD Run() override { 77 MOZ_ASSERT(force_); 78 if (process_) { 79 KillProcess(); 80 } 81 return NS_OK; 82 } 83 84 // MessageLoop::Watcher ----------------------------------------------------- 85 86 virtual void OnObjectSignaled(HANDLE object) { 87 // When we're called from KillProcess, the ObjectWatcher may still be 88 // watching. the process handle, so make sure it has stopped. 89 watcher_.StopWatching(); 90 91 base::CloseProcessHandle(process_); 92 process_ = 0; 93 94 if (!force_) { 95 MessageLoop::current()->RemoveDestructionObserver(this); 96 delete this; 97 } 98 } 99 100 private: 101 void KillProcess() { 102 MOZ_ASSERT(force_); 103 104 // OK, time to get frisky. We don't actually care when the process 105 // terminates. We just care that it eventually terminates, and that's what 106 // TerminateProcess should do for us. Don't check for the result code since 107 // it fails quite often. This should be investigated eventually. 108 TerminateProcess(process_, base::PROCESS_END_PROCESS_WAS_HUNG); 109 110 // Now, just cleanup as if the process exited normally. 111 OnObjectSignaled(process_); 112 } 113 114 void CrashProcessIfHanging() { 115 if (IsProcessDead(process_)) { 116 return; 117 } 118 DWORD pid = GetProcessId(process_); 119 DCHECK(pid != 0); 120 121 // If child processes seems to be hanging on shutdown, wait for a 122 // reasonable time. The wait is global instead of per-process 123 // because the child processes should be shutting down in 124 // parallel, and also we're potentially racing global timeouts 125 // like nsTerminator. (The counter doesn't need to be atomic; 126 // this is always called on the I/O thread.) 127 static DWORD sWaitMs = kShutdownWaitMs; 128 if (sWaitMs > 0) { 129 CHROMIUM_LOG(WARNING) 130 << "Process " << pid 131 << " may be hanging at shutdown; will wait for up to " << sWaitMs 132 << "ms"; 133 } 134 const auto beforeWait = mozilla::TimeStamp::NowLoRes(); 135 const DWORD waitStatus = WaitForSingleObject(process_, sWaitMs); 136 137 const double elapsed = 138 (mozilla::TimeStamp::NowLoRes() - beforeWait).ToMilliseconds(); 139 sWaitMs -= static_cast<DWORD>( 140 std::clamp(elapsed, 0.0, static_cast<double>(sWaitMs))); 141 142 switch (waitStatus) { 143 case WAIT_TIMEOUT: 144 // The process is still running. 145 break; 146 case WAIT_OBJECT_0: 147 // The process exited. 148 return; 149 case WAIT_FAILED: 150 CHROMIUM_LOG(ERROR) << "Waiting for process " << pid 151 << " failed; error " << GetLastError(); 152 DCHECK(false) << "WaitForSingleObject failed"; 153 // Process status unclear; assume it's gone. 154 return; 155 default: 156 DCHECK(false) << "WaitForSingleObject returned " << waitStatus; 157 // Again, not clear what's happening so avoid touching the process 158 return; 159 } 160 161 // We want TreeHerder to flag this log line as an error, so that 162 // this is more obviously a deliberate crash; "fatal error" is one 163 // of the strings it looks for. 164 CHROMIUM_LOG(ERROR) 165 << "Process " << pid 166 << " hanging at shutdown; attempting crash report (fatal error)"; 167 168 // We're going to use CreateRemoteThread to call DbgBreakPoint in 169 // the target process; it's in a "known DLL" so it should be at 170 // the same address in all processes. (Normal libraries, like 171 // xul.dll, are usually at the same address but can be relocated 172 // in case of conflict.) 173 // 174 // DbgBreakPoint doesn't take an argument, so we can give it an 175 // arbitrary value to make it easier to identify these crash 176 // reports. (reinterpret_cast isn't constexpr, so this is 177 // declared as an integer and cast to the required type later.) 178 // The primary use case for all of this is in CI, where we'll also 179 // have log messages, but if these crashes end up in Socorro in 180 // significant numbers then we'll be able to look for this value. 181 static constexpr uint64_t kIpcMagic = 0x43504900435049; 182 183 const HMODULE ntdll = GetModuleHandleW(L"ntdll.dll"); 184 if (!ntdll) { 185 CHROMIUM_LOG(ERROR) << "couldn't find ntdll.dll: error " 186 << GetLastError(); 187 return; 188 } 189 const auto dbgBreak = reinterpret_cast<LPTHREAD_START_ROUTINE>( 190 GetProcAddress(ntdll, "DbgBreakPoint")); 191 if (!dbgBreak) { 192 CHROMIUM_LOG(ERROR) << "couldn't find DbgBreakPoint: error " 193 << GetLastError(); 194 return; 195 } 196 197 const DWORD rights = PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | 198 PROCESS_VM_OPERATION | PROCESS_VM_WRITE | 199 PROCESS_VM_READ; 200 HANDLE process_priv = nullptr; 201 if (!DuplicateHandle(GetCurrentProcess(), process_, GetCurrentProcess(), 202 &process_priv, rights, /* inherit */ FALSE, 203 /* options */ 0)) { 204 const auto error = GetLastError(); 205 CHROMIUM_LOG(ERROR) << "OpenProcess: error " << error; 206 } else { 207 DCHECK(process_priv); 208 HANDLE thread = 209 CreateRemoteThread(process_priv, /* sec attr */ nullptr, 210 /* stack */ 0, dbgBreak, (LPVOID)kIpcMagic, 211 /* flags */ 0, nullptr); 212 if (!thread) { 213 const auto error = GetLastError(); 214 CHROMIUM_LOG(ERROR) << "CreateRemoteThread: error " << error; 215 } else { 216 CloseHandle(thread); 217 } 218 CloseHandle(process_priv); 219 } 220 } 221 222 // The process that we are watching. 223 base::ProcessHandle process_; 224 225 base::ObjectWatcher watcher_; 226 227 bool force_; 228 229 DISALLOW_EVIL_CONSTRUCTORS(ChildReaper); 230 }; 231 232 } // namespace 233 234 // static 235 void ProcessWatcher::EnsureProcessTerminated(base::ProcessHandle process, 236 bool force) { 237 DCHECK(process != GetCurrentProcess()); 238 239 // If already signaled, then we are done! 240 if (IsProcessDead(process)) { 241 base::CloseProcessHandle(process); 242 return; 243 } 244 245 MessageLoopForIO* loop = MessageLoopForIO::current(); 246 if (force) { 247 RefPtr<mozilla::Runnable> task = new ChildReaper(process, force); 248 loop->PostDelayedTask(task.forget(), kWaitInterval); 249 } else { 250 loop->AddDestructionObserver(new ChildReaper(process, force)); 251 } 252 }