OLD | NEW |
1 // Copyright 2015 The Crashpad Authors. All rights reserved. | 1 // Copyright 2015 The Crashpad Authors. All rights reserved. |
2 // | 2 // |
3 // Licensed under the Apache License, Version 2.0 (the "License"); | 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
4 // you may not use this file except in compliance with the License. | 4 // you may not use this file except in compliance with the License. |
5 // You may obtain a copy of the License at | 5 // You may obtain a copy of the License at |
6 // | 6 // |
7 // http://www.apache.org/licenses/LICENSE-2.0 | 7 // http://www.apache.org/licenses/LICENSE-2.0 |
8 // | 8 // |
9 // Unless required by applicable law or agreed to in writing, software | 9 // Unless required by applicable law or agreed to in writing, software |
10 // distributed under the License is distributed on an "AS IS" BASIS, | 10 // distributed under the License is distributed on an "AS IS" BASIS, |
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 // See the License for the specific language governing permissions and | 12 // See the License for the specific language governing permissions and |
13 // limitations under the License. | 13 // limitations under the License. |
14 | 14 |
15 #include "client/crashpad_client.h" | 15 #include "client/crashpad_client.h" |
16 | 16 |
17 #include <windows.h> | 17 #include <windows.h> |
18 #include <stdint.h> | 18 #include <stdint.h> |
19 #include <string.h> | 19 #include <string.h> |
20 | 20 |
21 #include <memory> | 21 #include <memory> |
22 | 22 |
23 #include "base/atomicops.h" | 23 #include "base/atomicops.h" |
24 #include "base/logging.h" | 24 #include "base/logging.h" |
| 25 #include "base/macros.h" |
25 #include "base/scoped_generic.h" | 26 #include "base/scoped_generic.h" |
26 #include "base/strings/string16.h" | 27 #include "base/strings/string16.h" |
27 #include "base/strings/stringprintf.h" | 28 #include "base/strings/stringprintf.h" |
28 #include "base/strings/utf_string_conversions.h" | 29 #include "base/strings/utf_string_conversions.h" |
29 #include "base/synchronization/lock.h" | 30 #include "base/synchronization/lock.h" |
30 #include "util/file/file_io.h" | 31 #include "util/file/file_io.h" |
| 32 #include "util/misc/random_string.h" |
31 #include "util/win/address_types.h" | 33 #include "util/win/address_types.h" |
32 #include "util/win/command_line.h" | 34 #include "util/win/command_line.h" |
33 #include "util/win/critical_section_with_debug_info.h" | 35 #include "util/win/critical_section_with_debug_info.h" |
34 #include "util/win/get_function.h" | 36 #include "util/win/get_function.h" |
35 #include "util/win/handle.h" | 37 #include "util/win/handle.h" |
| 38 #include "util/win/initial_client_data.h" |
36 #include "util/win/nt_internals.h" | 39 #include "util/win/nt_internals.h" |
37 #include "util/win/ntstatus_logging.h" | 40 #include "util/win/ntstatus_logging.h" |
38 #include "util/win/process_info.h" | 41 #include "util/win/process_info.h" |
39 #include "util/win/registration_protocol_win.h" | 42 #include "util/win/registration_protocol_win.h" |
40 #include "util/win/scoped_handle.h" | |
41 #include "util/win/scoped_process_suspend.h" | 43 #include "util/win/scoped_process_suspend.h" |
42 #include "util/win/termination_codes.h" | 44 #include "util/win/termination_codes.h" |
| 45 #include "util/win/xp_compat.h" |
| 46 |
| 47 namespace crashpad { |
43 | 48 |
44 namespace { | 49 namespace { |
45 | 50 |
46 // This handle is never closed. This is used to signal to the server that a dump | 51 // This handle is never closed. This is used to signal to the server that a dump |
47 // should be taken in the event of a crash. | 52 // should be taken in the event of a crash. |
48 HANDLE g_signal_exception = INVALID_HANDLE_VALUE; | 53 HANDLE g_signal_exception = INVALID_HANDLE_VALUE; |
49 | 54 |
50 // Where we store the exception information that the crash handler reads. | 55 // Where we store the exception information that the crash handler reads. |
51 crashpad::ExceptionInformation g_crash_exception_information; | 56 ExceptionInformation g_crash_exception_information; |
52 | 57 |
53 // These handles are never closed. g_signal_non_crash_dump is used to signal to | 58 // These handles are never closed. g_signal_non_crash_dump is used to signal to |
54 // the server to take a dump (not due to an exception), and the server will | 59 // the server to take a dump (not due to an exception), and the server will |
55 // signal g_non_crash_dump_done when the dump is completed. | 60 // signal g_non_crash_dump_done when the dump is completed. |
56 HANDLE g_signal_non_crash_dump = INVALID_HANDLE_VALUE; | 61 HANDLE g_signal_non_crash_dump = INVALID_HANDLE_VALUE; |
57 HANDLE g_non_crash_dump_done = INVALID_HANDLE_VALUE; | 62 HANDLE g_non_crash_dump_done = INVALID_HANDLE_VALUE; |
58 | 63 |
59 // Guards multiple simultaneous calls to DumpWithoutCrash(). This is leaked. | 64 // Guards multiple simultaneous calls to DumpWithoutCrash(). This is leaked. |
60 base::Lock* g_non_crash_dump_lock; | 65 base::Lock* g_non_crash_dump_lock; |
61 | 66 |
62 // Where we store a pointer to the context information when taking a non-crash | 67 // Where we store a pointer to the context information when taking a non-crash |
63 // dump. | 68 // dump. |
64 crashpad::ExceptionInformation g_non_crash_exception_information; | 69 ExceptionInformation g_non_crash_exception_information; |
| 70 |
| 71 enum class StartupState : int { |
| 72 kNotReady = 0, // This must be value 0 because it is the initial value of a |
| 73 // global AtomicWord. |
| 74 kSucceeded = 1, // The CreateProcess() for the handler succeeded. |
| 75 kFailed = 2, // The handler failed to start. |
| 76 }; |
| 77 |
| 78 // This is a tri-state of type StartupState. It starts at 0 == kNotReady, and |
| 79 // when the handler is known to have started successfully, or failed to start |
| 80 // the value will be updated. The unhandled exception filter will not proceed |
| 81 // until one of those two cases happens. |
| 82 base::subtle::AtomicWord g_handler_startup_state; |
65 | 83 |
66 // A CRITICAL_SECTION initialized with | 84 // A CRITICAL_SECTION initialized with |
67 // RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO to force it to be allocated with a | 85 // RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO to force it to be allocated with a |
68 // valid .DebugInfo field. The address of this critical section is given to the | 86 // valid .DebugInfo field. The address of this critical section is given to the |
69 // handler. All critical sections with debug info are linked in a doubly-linked | 87 // handler. All critical sections with debug info are linked in a doubly-linked |
70 // list, so this allows the handler to capture all of them. | 88 // list, so this allows the handler to capture all of them. |
71 CRITICAL_SECTION g_critical_section_with_debug_info; | 89 CRITICAL_SECTION g_critical_section_with_debug_info; |
72 | 90 |
| 91 void SetHandlerStartupState(StartupState state) { |
| 92 DCHECK(state == StartupState::kSucceeded || |
| 93 state == StartupState::kFailed); |
| 94 base::subtle::Acquire_Store(&g_handler_startup_state, |
| 95 static_cast<base::subtle::AtomicWord>(state)); |
| 96 } |
| 97 |
| 98 StartupState BlockUntilHandlerStartedOrFailed() { |
| 99 // Wait until we know the handler has either succeeded or failed to start. |
| 100 base::subtle::AtomicWord startup_state; |
| 101 while ( |
| 102 (startup_state = base::subtle::Release_Load(&g_handler_startup_state)) == |
| 103 static_cast<int>(StartupState::kNotReady)) { |
| 104 Sleep(1); |
| 105 } |
| 106 |
| 107 return static_cast<StartupState>(startup_state); |
| 108 } |
| 109 |
73 LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) { | 110 LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) { |
| 111 if (BlockUntilHandlerStartedOrFailed() == StartupState::kFailed) { |
| 112 // If we know for certain that the handler has failed to start, then abort |
| 113 // here, rather than trying to signal to a handler that will never arrive, |
| 114 // and then sleeping unnecessarily. |
| 115 LOG(ERROR) << "crash server failed to launch, self-terminating"; |
| 116 TerminateProcess(GetCurrentProcess(), kTerminationCodeCrashNoDump); |
| 117 return EXCEPTION_CONTINUE_SEARCH; |
| 118 } |
| 119 |
| 120 // Otherwise, we know the handler startup has succeeded, and we can continue. |
| 121 |
74 // Tracks whether a thread has already entered UnhandledExceptionHandler. | 122 // Tracks whether a thread has already entered UnhandledExceptionHandler. |
75 static base::subtle::AtomicWord have_crashed; | 123 static base::subtle::AtomicWord have_crashed; |
76 | 124 |
77 // This is a per-process handler. While this handler is being invoked, other | 125 // This is a per-process handler. While this handler is being invoked, other |
78 // threads are still executing as usual, so multiple threads could enter at | 126 // threads are still executing as usual, so multiple threads could enter at |
79 // the same time. Because we're in a crashing state, we shouldn't be doing | 127 // the same time. Because we're in a crashing state, we shouldn't be doing |
80 // anything that might cause allocations, call into kernel mode, etc. So, we | 128 // anything that might cause allocations, call into kernel mode, etc. So, we |
81 // don't want to take a critical section here to avoid simultaneous access to | 129 // don't want to take a critical section here to avoid simultaneous access to |
82 // the global exception pointers in ExceptionInformation. Because the crash | 130 // the global exception pointers in ExceptionInformation. Because the crash |
83 // handler will record all threads, it's fine to simply have the second and | 131 // handler will record all threads, it's fine to simply have the second and |
84 // subsequent entrants block here. They will soon be suspended by the crash | 132 // subsequent entrants block here. They will soon be suspended by the crash |
85 // handler, and then the entire process will be terminated below. This means | 133 // handler, and then the entire process will be terminated below. This means |
86 // that we won't save the exception pointers from the second and further | 134 // that we won't save the exception pointers from the second and further |
87 // crashes, but contention here is very unlikely, and we'll still have a stack | 135 // crashes, but contention here is very unlikely, and we'll still have a stack |
88 // that's blocked at this location. | 136 // that's blocked at this location. |
89 if (base::subtle::Barrier_AtomicIncrement(&have_crashed, 1) > 1) { | 137 if (base::subtle::Barrier_AtomicIncrement(&have_crashed, 1) > 1) { |
90 SleepEx(INFINITE, false); | 138 SleepEx(INFINITE, false); |
91 } | 139 } |
92 | 140 |
93 // Otherwise, we're the first thread, so record the exception pointer and | 141 // Otherwise, we're the first thread, so record the exception pointer and |
94 // signal the crash handler. | 142 // signal the crash handler. |
95 g_crash_exception_information.thread_id = GetCurrentThreadId(); | 143 g_crash_exception_information.thread_id = GetCurrentThreadId(); |
96 g_crash_exception_information.exception_pointers = | 144 g_crash_exception_information.exception_pointers = |
97 reinterpret_cast<crashpad::WinVMAddress>(exception_pointers); | 145 reinterpret_cast<WinVMAddress>(exception_pointers); |
98 | 146 |
99 // Now signal the crash server, which will take a dump and then terminate us | 147 // Now signal the crash server, which will take a dump and then terminate us |
100 // when it's complete. | 148 // when it's complete. |
101 SetEvent(g_signal_exception); | 149 SetEvent(g_signal_exception); |
102 | 150 |
103 // Time to wait for the handler to create a dump. | 151 // Time to wait for the handler to create a dump. |
104 const DWORD kMillisecondsUntilTerminate = 60 * 1000; | 152 const DWORD kMillisecondsUntilTerminate = 60 * 1000; |
105 | 153 |
106 // Sleep for a while to allow it to process us. Eventually, we terminate | 154 // Sleep for a while to allow it to process us. Eventually, we terminate |
107 // ourselves in case the crash server is gone, so that we don't leave zombies | 155 // ourselves in case the crash server is gone, so that we don't leave zombies |
108 // around. This would ideally never happen. | 156 // around. This would ideally never happen. |
109 Sleep(kMillisecondsUntilTerminate); | 157 Sleep(kMillisecondsUntilTerminate); |
110 | 158 |
111 LOG(ERROR) << "crash server did not respond, self-terminating"; | 159 LOG(ERROR) << "crash server did not respond, self-terminating"; |
112 | 160 |
113 TerminateProcess(GetCurrentProcess(), crashpad::kTerminationCodeCrashNoDump); | 161 TerminateProcess(GetCurrentProcess(), kTerminationCodeCrashNoDump); |
114 | 162 |
115 return EXCEPTION_CONTINUE_SEARCH; | 163 return EXCEPTION_CONTINUE_SEARCH; |
116 } | 164 } |
117 | 165 |
118 std::wstring FormatArgumentString(const std::string& name, | 166 std::wstring FormatArgumentString(const std::string& name, |
119 const std::wstring& value) { | 167 const std::wstring& value) { |
120 return std::wstring(L"--") + base::UTF8ToUTF16(name) + L"=" + value; | 168 return std::wstring(L"--") + base::UTF8ToUTF16(name) + L"=" + value; |
121 } | 169 } |
122 | 170 |
123 struct ScopedProcThreadAttributeListTraits { | 171 struct ScopedProcThreadAttributeListTraits { |
124 static PPROC_THREAD_ATTRIBUTE_LIST InvalidValue() { | 172 static PPROC_THREAD_ATTRIBUTE_LIST InvalidValue() { return nullptr; } |
125 return nullptr; | |
126 } | |
127 | 173 |
128 static void Free(PPROC_THREAD_ATTRIBUTE_LIST proc_thread_attribute_list) { | 174 static void Free(PPROC_THREAD_ATTRIBUTE_LIST proc_thread_attribute_list) { |
129 // This is able to use GET_FUNCTION_REQUIRED() instead of GET_FUNCTION() | 175 // This is able to use GET_FUNCTION_REQUIRED() instead of GET_FUNCTION() |
130 // because it will only be called if InitializeProcThreadAttributeList() and | 176 // because it will only be called if InitializeProcThreadAttributeList() and |
131 // UpdateProcThreadAttribute() are present. | 177 // UpdateProcThreadAttribute() are present. |
132 static const auto delete_proc_thread_attribute_list = | 178 static const auto delete_proc_thread_attribute_list = |
133 GET_FUNCTION_REQUIRED(L"kernel32.dll", ::DeleteProcThreadAttributeList); | 179 GET_FUNCTION_REQUIRED(L"kernel32.dll", ::DeleteProcThreadAttributeList); |
134 delete_proc_thread_attribute_list(proc_thread_attribute_list); | 180 delete_proc_thread_attribute_list(proc_thread_attribute_list); |
135 } | 181 } |
136 }; | 182 }; |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
179 data_vector->push_back(static_cast<unsigned char>((data & 0xff0000) >> 16)); | 225 data_vector->push_back(static_cast<unsigned char>((data & 0xff0000) >> 16)); |
180 data_vector->push_back(static_cast<unsigned char>((data & 0xff000000) >> 24)); | 226 data_vector->push_back(static_cast<unsigned char>((data & 0xff000000) >> 24)); |
181 } | 227 } |
182 | 228 |
183 void AddUint64(std::vector<unsigned char>* data_vector, uint64_t data) { | 229 void AddUint64(std::vector<unsigned char>* data_vector, uint64_t data) { |
184 AddUint32(data_vector, static_cast<uint32_t>(data & 0xffffffffULL)); | 230 AddUint32(data_vector, static_cast<uint32_t>(data & 0xffffffffULL)); |
185 AddUint32(data_vector, | 231 AddUint32(data_vector, |
186 static_cast<uint32_t>((data & 0xffffffff00000000ULL) >> 32)); | 232 static_cast<uint32_t>((data & 0xffffffff00000000ULL) >> 32)); |
187 } | 233 } |
188 | 234 |
189 } // namespace | 235 //! \brief Creates a randomized pipe name to listen for client registrations |
| 236 //! on and returns its name. |
| 237 //! |
| 238 //! \param[out] pipe_name The pipe name that will be listened on. |
| 239 //! \param[out] pipe_handle The first pipe instance corresponding for the pipe. |
| 240 void CreatePipe(std::wstring* pipe_name, ScopedFileHANDLE* pipe_instance) { |
| 241 int tries = 5; |
| 242 std::string pipe_name_base = |
| 243 base::StringPrintf("\\\\.\\pipe\\crashpad_%d_", GetCurrentProcessId()); |
| 244 do { |
| 245 *pipe_name = base::UTF8ToUTF16(pipe_name_base + RandomString()); |
190 | 246 |
191 namespace crashpad { | 247 pipe_instance->reset(CreateNamedPipeInstance(*pipe_name, true)); |
192 | 248 |
193 CrashpadClient::CrashpadClient() | 249 // CreateNamedPipe() is documented as setting the error to |
194 : ipc_pipe_() { | 250 // ERROR_ACCESS_DENIED if FILE_FLAG_FIRST_PIPE_INSTANCE is specified and the |
| 251 // pipe name is already in use. However it may set the error to other codes |
| 252 // such as ERROR_PIPE_BUSY (if the pipe already exists and has reached its |
| 253 // maximum instance count) or ERROR_INVALID_PARAMETER (if the pipe already |
| 254 // exists and its attributes differ from those specified to |
| 255 // CreateNamedPipe()). Some of these errors may be ambiguous: for example, |
| 256 // ERROR_INVALID_PARAMETER may also occur if CreateNamedPipe() is called |
| 257 // incorrectly even in the absence of an existing pipe by the same name. |
| 258 // Rather than chasing down all of the possible errors that might indicate |
| 259 // that a pipe name is already in use, retry up to a few times on any error. |
| 260 } while (!pipe_instance->is_valid() && --tries); |
| 261 |
| 262 PCHECK(pipe_instance->is_valid()) << "CreateNamedPipe"; |
195 } | 263 } |
196 | 264 |
197 CrashpadClient::~CrashpadClient() { | 265 struct BackgroundHandlerStartThreadData { |
198 } | 266 BackgroundHandlerStartThreadData( |
| 267 const base::FilePath& handler, |
| 268 const base::FilePath& database, |
| 269 const base::FilePath& metrics_dir, |
| 270 const std::string& url, |
| 271 const std::map<std::string, std::string>& annotations, |
| 272 const std::vector<std::string>& arguments, |
| 273 const std::wstring& ipc_pipe, |
| 274 ScopedFileHANDLE ipc_pipe_handle) |
| 275 : handler(handler), |
| 276 database(database), |
| 277 metrics_dir(metrics_dir), |
| 278 url(url), |
| 279 annotations(annotations), |
| 280 arguments(arguments), |
| 281 ipc_pipe(ipc_pipe), |
| 282 ipc_pipe_handle(std::move(ipc_pipe_handle)) {} |
199 | 283 |
200 bool CrashpadClient::StartHandler( | 284 base::FilePath handler; |
201 const base::FilePath& handler, | 285 base::FilePath database; |
202 const base::FilePath& database, | 286 base::FilePath metrics_dir; |
203 const base::FilePath& metrics_dir, | 287 std::string url; |
204 const std::string& url, | 288 std::map<std::string, std::string> annotations; |
205 const std::map<std::string, std::string>& annotations, | 289 std::vector<std::string> arguments; |
206 const std::vector<std::string>& arguments, | 290 std::wstring ipc_pipe; |
207 bool restartable) { | 291 ScopedFileHANDLE ipc_pipe_handle; |
208 DCHECK(ipc_pipe_.empty()); | 292 }; |
209 | 293 |
210 HANDLE pipe_read; | 294 // Ensures that SetHandlerStartupState() is called on scope exit. Assumes |
211 HANDLE pipe_write; | 295 // failure, and on success, SetSuccessful() should be called. |
212 SECURITY_ATTRIBUTES security_attributes = {}; | 296 class ScopedCallSetHandlerStartupState { |
213 security_attributes.nLength = sizeof(security_attributes); | 297 public: |
214 security_attributes.bInheritHandle = TRUE; | 298 ScopedCallSetHandlerStartupState() : successful_(false) {} |
215 if (!CreatePipe(&pipe_read, &pipe_write, &security_attributes, 0)) { | 299 |
216 PLOG(ERROR) << "CreatePipe"; | 300 ~ScopedCallSetHandlerStartupState() { |
217 return false; | 301 SetHandlerStartupState(successful_ ? StartupState::kSucceeded |
| 302 : StartupState::kFailed); |
218 } | 303 } |
219 ScopedFileHandle pipe_read_owner(pipe_read); | |
220 ScopedFileHandle pipe_write_owner(pipe_write); | |
221 | 304 |
222 // The new process only needs the write side of the pipe. | 305 void SetSuccessful() { successful_ = true; } |
223 BOOL rv = SetHandleInformation(pipe_read, HANDLE_FLAG_INHERIT, 0); | 306 |
224 PLOG_IF(WARNING, !rv) << "SetHandleInformation"; | 307 private: |
| 308 bool successful_; |
| 309 |
| 310 DISALLOW_COPY_AND_ASSIGN(ScopedCallSetHandlerStartupState); |
| 311 }; |
| 312 |
| 313 bool StartHandlerProcess( |
| 314 std::unique_ptr<BackgroundHandlerStartThreadData> data) { |
| 315 ScopedCallSetHandlerStartupState scoped_startup_state_caller; |
225 | 316 |
226 std::wstring command_line; | 317 std::wstring command_line; |
227 AppendCommandLineArgument(handler.value(), &command_line); | 318 AppendCommandLineArgument(data->handler.value(), &command_line); |
228 for (const std::string& argument : arguments) { | 319 for (const std::string& argument : data->arguments) { |
229 AppendCommandLineArgument(base::UTF8ToUTF16(argument), &command_line); | 320 AppendCommandLineArgument(base::UTF8ToUTF16(argument), &command_line); |
230 } | 321 } |
231 if (!database.value().empty()) { | 322 if (!data->database.value().empty()) { |
232 AppendCommandLineArgument(FormatArgumentString("database", | |
233 database.value()), | |
234 &command_line); | |
235 } | |
236 if (!metrics_dir.value().empty()) { | |
237 AppendCommandLineArgument( | 323 AppendCommandLineArgument( |
238 FormatArgumentString("metrics-dir", metrics_dir.value()), | 324 FormatArgumentString("database", data->database.value()), |
239 &command_line); | 325 &command_line); |
240 } | 326 } |
241 if (!url.empty()) { | 327 if (!data->metrics_dir.value().empty()) { |
242 AppendCommandLineArgument(FormatArgumentString("url", | 328 AppendCommandLineArgument( |
243 base::UTF8ToUTF16(url)), | 329 FormatArgumentString("metrics-dir", data->metrics_dir.value()), |
244 &command_line); | 330 &command_line); |
245 } | 331 } |
246 for (const auto& kv : annotations) { | 332 if (!data->url.empty()) { |
| 333 AppendCommandLineArgument( |
| 334 FormatArgumentString("url", base::UTF8ToUTF16(data->url)), |
| 335 &command_line); |
| 336 } |
| 337 for (const auto& kv : data->annotations) { |
247 AppendCommandLineArgument( | 338 AppendCommandLineArgument( |
248 FormatArgumentString("annotation", | 339 FormatArgumentString("annotation", |
249 base::UTF8ToUTF16(kv.first + '=' + kv.second)), | 340 base::UTF8ToUTF16(kv.first + '=' + kv.second)), |
250 &command_line); | 341 &command_line); |
251 } | 342 } |
| 343 |
| 344 ScopedKernelHANDLE this_process( |
| 345 OpenProcess(kXPProcessAllAccess, true, GetCurrentProcessId())); |
| 346 if (!this_process.is_valid()) { |
| 347 PLOG(ERROR) << "OpenProcess"; |
| 348 return false; |
| 349 } |
| 350 |
| 351 InitialClientData initial_client_data( |
| 352 g_signal_exception, |
| 353 g_signal_non_crash_dump, |
| 354 g_non_crash_dump_done, |
| 355 data->ipc_pipe_handle.get(), |
| 356 this_process.get(), |
| 357 reinterpret_cast<WinVMAddress>(&g_crash_exception_information), |
| 358 reinterpret_cast<WinVMAddress>(&g_non_crash_exception_information), |
| 359 reinterpret_cast<WinVMAddress>(&g_critical_section_with_debug_info)); |
252 AppendCommandLineArgument( | 360 AppendCommandLineArgument( |
253 base::UTF8ToUTF16(base::StringPrintf("--handshake-handle=0x%x", | 361 base::UTF8ToUTF16(std::string("--initial-client-data=") + |
254 HandleToInt(pipe_write))), | 362 initial_client_data.StringRepresentation()), |
255 &command_line); | 363 &command_line); |
256 | 364 |
| 365 BOOL rv; |
257 DWORD creation_flags; | 366 DWORD creation_flags; |
258 STARTUPINFOEX startup_info = {}; | 367 STARTUPINFOEX startup_info = {}; |
259 startup_info.StartupInfo.dwFlags = STARTF_USESTDHANDLES; | 368 startup_info.StartupInfo.dwFlags = STARTF_USESTDHANDLES; |
260 startup_info.StartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); | 369 startup_info.StartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); |
261 startup_info.StartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); | 370 startup_info.StartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); |
262 startup_info.StartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); | 371 startup_info.StartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); |
263 | 372 |
264 std::vector<HANDLE> handle_list; | 373 std::vector<HANDLE> handle_list; |
265 std::unique_ptr<uint8_t[]> proc_thread_attribute_list_storage; | 374 std::unique_ptr<uint8_t[]> proc_thread_attribute_list_storage; |
266 ScopedProcThreadAttributeList proc_thread_attribute_list_owner; | 375 ScopedProcThreadAttributeList proc_thread_attribute_list_owner; |
(...skipping 30 matching lines...) Expand all Loading... |
297 reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>( | 406 reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>( |
298 proc_thread_attribute_list_storage.get()); | 407 proc_thread_attribute_list_storage.get()); |
299 rv = initialize_proc_thread_attribute_list( | 408 rv = initialize_proc_thread_attribute_list( |
300 startup_info.lpAttributeList, 1, 0, &size); | 409 startup_info.lpAttributeList, 1, 0, &size); |
301 if (!rv) { | 410 if (!rv) { |
302 PLOG(ERROR) << "InitializeProcThreadAttributeList"; | 411 PLOG(ERROR) << "InitializeProcThreadAttributeList"; |
303 return false; | 412 return false; |
304 } | 413 } |
305 proc_thread_attribute_list_owner.reset(startup_info.lpAttributeList); | 414 proc_thread_attribute_list_owner.reset(startup_info.lpAttributeList); |
306 | 415 |
307 handle_list.reserve(4); | 416 handle_list.reserve(8); |
308 handle_list.push_back(pipe_write); | 417 handle_list.push_back(g_signal_exception); |
| 418 handle_list.push_back(g_signal_non_crash_dump); |
| 419 handle_list.push_back(g_non_crash_dump_done); |
| 420 handle_list.push_back(data->ipc_pipe_handle.get()); |
| 421 handle_list.push_back(this_process.get()); |
309 AddHandleToListIfValidAndInheritable(&handle_list, | 422 AddHandleToListIfValidAndInheritable(&handle_list, |
310 startup_info.StartupInfo.hStdInput); | 423 startup_info.StartupInfo.hStdInput); |
311 AddHandleToListIfValidAndInheritable(&handle_list, | 424 AddHandleToListIfValidAndInheritable(&handle_list, |
312 startup_info.StartupInfo.hStdOutput); | 425 startup_info.StartupInfo.hStdOutput); |
313 AddHandleToListIfValidAndInheritable(&handle_list, | 426 AddHandleToListIfValidAndInheritable(&handle_list, |
314 startup_info.StartupInfo.hStdError); | 427 startup_info.StartupInfo.hStdError); |
315 rv = update_proc_thread_attribute( | 428 rv = update_proc_thread_attribute( |
316 startup_info.lpAttributeList, | 429 startup_info.lpAttributeList, |
317 0, | 430 0, |
318 PROC_THREAD_ATTRIBUTE_HANDLE_LIST, | 431 PROC_THREAD_ATTRIBUTE_HANDLE_LIST, |
319 &handle_list[0], | 432 &handle_list[0], |
320 handle_list.size() * sizeof(handle_list[0]), | 433 handle_list.size() * sizeof(handle_list[0]), |
321 nullptr, | 434 nullptr, |
322 nullptr); | 435 nullptr); |
323 if (!rv) { | 436 if (!rv) { |
324 PLOG(ERROR) << "UpdateProcThreadAttribute"; | 437 PLOG(ERROR) << "UpdateProcThreadAttribute"; |
325 return false; | 438 return false; |
326 } | 439 } |
327 } | 440 } |
328 | 441 |
329 PROCESS_INFORMATION process_info; | 442 PROCESS_INFORMATION process_info; |
330 rv = CreateProcess(handler.value().c_str(), | 443 rv = CreateProcess(data->handler.value().c_str(), |
331 &command_line[0], | 444 &command_line[0], |
332 nullptr, | 445 nullptr, |
333 nullptr, | 446 nullptr, |
334 true, | 447 true, |
335 creation_flags, | 448 creation_flags, |
336 nullptr, | 449 nullptr, |
337 nullptr, | 450 nullptr, |
338 &startup_info.StartupInfo, | 451 &startup_info.StartupInfo, |
339 &process_info); | 452 &process_info); |
340 if (!rv) { | 453 if (!rv) { |
341 PLOG(ERROR) << "CreateProcess"; | 454 PLOG(ERROR) << "CreateProcess"; |
342 return false; | 455 return false; |
343 } | 456 } |
344 | 457 |
345 rv = CloseHandle(process_info.hThread); | 458 rv = CloseHandle(process_info.hThread); |
346 PLOG_IF(WARNING, !rv) << "CloseHandle thread"; | 459 PLOG_IF(WARNING, !rv) << "CloseHandle thread"; |
347 | 460 |
348 rv = CloseHandle(process_info.hProcess); | 461 rv = CloseHandle(process_info.hProcess); |
349 PLOG_IF(WARNING, !rv) << "CloseHandle process"; | 462 PLOG_IF(WARNING, !rv) << "CloseHandle process"; |
350 | 463 |
351 pipe_write_owner.reset(); | 464 // It is important to close our side of the pipe here before confirming that |
| 465 // we can communicate with the server. By doing so, the only remaining copy of |
| 466 // the server side of the pipe belongs to the exception handler process we |
| 467 // just spawned. Otherwise, the pipe will continue to exist indefinitely, so |
| 468 // the connection loop will not detect that it will never be serviced. |
| 469 data->ipc_pipe_handle.reset(); |
352 | 470 |
353 uint32_t ipc_pipe_length; | 471 // Confirm that the server is waiting for connections before continuing. |
354 if (!LoggingReadFile(pipe_read, &ipc_pipe_length, sizeof(ipc_pipe_length))) { | 472 ClientToServerMessage message = {}; |
| 473 message.type = ClientToServerMessage::kPing; |
| 474 ServerToClientMessage response = {}; |
| 475 if (!SendToCrashHandlerServer(data->ipc_pipe, message, &response)) { |
355 return false; | 476 return false; |
356 } | 477 } |
357 | 478 |
358 ipc_pipe_.resize(ipc_pipe_length); | 479 scoped_startup_state_caller.SetSuccessful(); |
359 if (ipc_pipe_length && | 480 return true; |
360 !LoggingReadFile( | 481 } |
361 pipe_read, &ipc_pipe_[0], ipc_pipe_length * sizeof(ipc_pipe_[0]))) { | 482 |
362 return false; | 483 DWORD WINAPI BackgroundHandlerStartThreadProc(void* data) { |
| 484 std::unique_ptr<BackgroundHandlerStartThreadData> data_as_ptr( |
| 485 reinterpret_cast<BackgroundHandlerStartThreadData*>(data)); |
| 486 return StartHandlerProcess(std::move(data_as_ptr)) ? 0 : 1; |
| 487 } |
| 488 |
| 489 void CommonInProcessInitialization() { |
| 490 // We create this dummy CRITICAL_SECTION with the |
| 491 // RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO flag set to have an entry point |
| 492 // into the doubly-linked list of RTL_CRITICAL_SECTION_DEBUG objects. This |
| 493 // allows us to walk the list at crash time to gather data for !locks. A |
| 494 // debugger would instead inspect ntdll!RtlCriticalSectionList to get the head |
| 495 // of the list. But that is not an exported symbol, so on an arbitrary client |
| 496 // machine, we don't have a way of getting that pointer. |
| 497 InitializeCriticalSectionWithDebugInfoIfPossible( |
| 498 &g_critical_section_with_debug_info); |
| 499 |
| 500 g_non_crash_dump_lock = new base::Lock(); |
| 501 } |
| 502 |
| 503 } // namespace |
| 504 |
| 505 CrashpadClient::CrashpadClient() : ipc_pipe_(), handler_start_thread_() {} |
| 506 |
| 507 CrashpadClient::~CrashpadClient() {} |
| 508 |
| 509 bool CrashpadClient::StartHandler( |
| 510 const base::FilePath& handler, |
| 511 const base::FilePath& database, |
| 512 const base::FilePath& metrics_dir, |
| 513 const std::string& url, |
| 514 const std::map<std::string, std::string>& annotations, |
| 515 const std::vector<std::string>& arguments, |
| 516 bool restartable, |
| 517 bool asynchronous_start) { |
| 518 DCHECK(ipc_pipe_.empty()); |
| 519 |
| 520 // Both the pipe and the signalling events have to be created on the main |
| 521 // thread (not the spawning thread) so that they're valid after we return from |
| 522 // this function. |
| 523 ScopedFileHANDLE ipc_pipe_handle; |
| 524 CreatePipe(&ipc_pipe_, &ipc_pipe_handle); |
| 525 |
| 526 SECURITY_ATTRIBUTES security_attributes = {0}; |
| 527 security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); |
| 528 security_attributes.bInheritHandle = true; |
| 529 |
| 530 g_signal_exception = |
| 531 CreateEvent(&security_attributes, false /* auto reset */, false, nullptr); |
| 532 g_signal_non_crash_dump = |
| 533 CreateEvent(&security_attributes, false /* auto reset */, false, nullptr); |
| 534 g_non_crash_dump_done = |
| 535 CreateEvent(&security_attributes, false /* auto reset */, false, nullptr); |
| 536 |
| 537 CommonInProcessInitialization(); |
| 538 |
| 539 SetUnhandledExceptionFilter(&UnhandledExceptionHandler); |
| 540 |
| 541 auto data = new BackgroundHandlerStartThreadData(handler, |
| 542 database, |
| 543 metrics_dir, |
| 544 url, |
| 545 annotations, |
| 546 arguments, |
| 547 ipc_pipe_, |
| 548 std::move(ipc_pipe_handle)); |
| 549 |
| 550 if (asynchronous_start) { |
| 551 // It is important that the current thread not be synchronized with the |
| 552 // thread that is created here. StartHandler() needs to be callable inside a |
| 553 // DllMain(). In that case, the background thread will not start until the |
| 554 // current DllMain() completes, which would cause deadlock if it was waited |
| 555 // upon. |
| 556 handler_start_thread_.reset(CreateThread(nullptr, |
| 557 0, |
| 558 &BackgroundHandlerStartThreadProc, |
| 559 reinterpret_cast<void*>(data), |
| 560 0, |
| 561 nullptr)); |
| 562 if (!handler_start_thread_.is_valid()) { |
| 563 PLOG(ERROR) << "CreateThread"; |
| 564 SetHandlerStartupState(StartupState::kFailed); |
| 565 return false; |
| 566 } |
| 567 |
| 568 // In asynchronous mode, we can't report on the overall success or failure |
| 569 // of initialization at this point. |
| 570 return true; |
| 571 } else { |
| 572 return StartHandlerProcess( |
| 573 std::unique_ptr<BackgroundHandlerStartThreadData>(data)); |
363 } | 574 } |
364 | |
365 return true; | |
366 } | 575 } |
367 | 576 |
368 bool CrashpadClient::SetHandlerIPCPipe(const std::wstring& ipc_pipe) { | 577 bool CrashpadClient::SetHandlerIPCPipe(const std::wstring& ipc_pipe) { |
369 DCHECK(ipc_pipe_.empty()); | 578 DCHECK(ipc_pipe_.empty()); |
370 DCHECK(!ipc_pipe.empty()); | 579 DCHECK(!ipc_pipe.empty()); |
371 | 580 |
372 ipc_pipe_ = ipc_pipe; | 581 ipc_pipe_ = ipc_pipe; |
373 | 582 |
374 return true; | |
375 } | |
376 | |
377 std::wstring CrashpadClient::GetHandlerIPCPipe() const { | |
378 DCHECK(!ipc_pipe_.empty()); | |
379 return ipc_pipe_; | |
380 } | |
381 | |
382 bool CrashpadClient::UseHandler() { | |
383 DCHECK(!ipc_pipe_.empty()); | 583 DCHECK(!ipc_pipe_.empty()); |
384 DCHECK_EQ(g_signal_exception, INVALID_HANDLE_VALUE); | 584 DCHECK_EQ(g_signal_exception, INVALID_HANDLE_VALUE); |
385 DCHECK_EQ(g_signal_non_crash_dump, INVALID_HANDLE_VALUE); | 585 DCHECK_EQ(g_signal_non_crash_dump, INVALID_HANDLE_VALUE); |
386 DCHECK_EQ(g_non_crash_dump_done, INVALID_HANDLE_VALUE); | 586 DCHECK_EQ(g_non_crash_dump_done, INVALID_HANDLE_VALUE); |
387 DCHECK(!g_critical_section_with_debug_info.DebugInfo); | 587 DCHECK(!g_critical_section_with_debug_info.DebugInfo); |
388 DCHECK(!g_non_crash_dump_lock); | 588 DCHECK(!g_non_crash_dump_lock); |
389 | 589 |
390 ClientToServerMessage message; | 590 ClientToServerMessage message; |
391 memset(&message, 0, sizeof(message)); | 591 memset(&message, 0, sizeof(message)); |
392 message.type = ClientToServerMessage::kRegister; | 592 message.type = ClientToServerMessage::kRegister; |
393 message.registration.version = RegistrationRequest::kMessageVersion; | 593 message.registration.version = RegistrationRequest::kMessageVersion; |
394 message.registration.client_process_id = GetCurrentProcessId(); | 594 message.registration.client_process_id = GetCurrentProcessId(); |
395 message.registration.crash_exception_information = | 595 message.registration.crash_exception_information = |
396 reinterpret_cast<WinVMAddress>(&g_crash_exception_information); | 596 reinterpret_cast<WinVMAddress>(&g_crash_exception_information); |
397 message.registration.non_crash_exception_information = | 597 message.registration.non_crash_exception_information = |
398 reinterpret_cast<WinVMAddress>(&g_non_crash_exception_information); | 598 reinterpret_cast<WinVMAddress>(&g_non_crash_exception_information); |
399 | 599 |
400 // We create this dummy CRITICAL_SECTION with the | 600 CommonInProcessInitialization(); |
401 // RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO flag set to have an entry point | 601 |
402 // into the doubly-linked list of RTL_CRITICAL_SECTION_DEBUG objects. This | 602 message.registration.critical_section_address = |
403 // allows us to walk the list at crash time to gather data for !locks. A | 603 reinterpret_cast<WinVMAddress>(&g_critical_section_with_debug_info); |
404 // debugger would instead inspect ntdll!RtlCriticalSectionList to get the head | |
405 // of the list. But that is not an exported symbol, so on an arbitrary client | |
406 // machine, we don't have a way of getting that pointer. | |
407 if (InitializeCriticalSectionWithDebugInfoIfPossible( | |
408 &g_critical_section_with_debug_info)) { | |
409 message.registration.critical_section_address = | |
410 reinterpret_cast<WinVMAddress>(&g_critical_section_with_debug_info); | |
411 } | |
412 | 604 |
413 ServerToClientMessage response = {}; | 605 ServerToClientMessage response = {}; |
414 | 606 |
415 if (!SendToCrashHandlerServer(ipc_pipe_, message, &response)) { | 607 if (!SendToCrashHandlerServer(ipc_pipe_, message, &response)) { |
416 return false; | 608 return false; |
417 } | 609 } |
418 | 610 |
| 611 SetHandlerStartupState(StartupState::kSucceeded); |
| 612 SetUnhandledExceptionFilter(&UnhandledExceptionHandler); |
| 613 |
419 // The server returns these already duplicated to be valid in this process. | 614 // The server returns these already duplicated to be valid in this process. |
420 g_signal_exception = | 615 g_signal_exception = |
421 IntToHandle(response.registration.request_crash_dump_event); | 616 IntToHandle(response.registration.request_crash_dump_event); |
422 g_signal_non_crash_dump = | 617 g_signal_non_crash_dump = |
423 IntToHandle(response.registration.request_non_crash_dump_event); | 618 IntToHandle(response.registration.request_non_crash_dump_event); |
424 g_non_crash_dump_done = | 619 g_non_crash_dump_done = |
425 IntToHandle(response.registration.non_crash_dump_completed_event); | 620 IntToHandle(response.registration.non_crash_dump_completed_event); |
426 | 621 |
427 g_non_crash_dump_lock = new base::Lock(); | 622 return true; |
| 623 } |
428 | 624 |
429 // In theory we could store the previous handler but it is not clear what | 625 std::wstring CrashpadClient::GetHandlerIPCPipe() const { |
430 // use we have for it. | 626 DCHECK(!ipc_pipe_.empty()); |
431 SetUnhandledExceptionFilter(&UnhandledExceptionHandler); | 627 return ipc_pipe_; |
432 return true; | 628 } |
| 629 |
| 630 bool CrashpadClient::WaitForHandlerStart() { |
| 631 DCHECK(handler_start_thread_.is_valid()); |
| 632 if (WaitForSingleObject(handler_start_thread_.get(), INFINITE) != |
| 633 WAIT_OBJECT_0) { |
| 634 PLOG(ERROR) << "WaitForSingleObject"; |
| 635 return false; |
| 636 } |
| 637 |
| 638 DWORD exit_code; |
| 639 if (!GetExitCodeThread(handler_start_thread_.get(), &exit_code)) { |
| 640 PLOG(ERROR) << "GetExitCodeThread"; |
| 641 return false; |
| 642 } |
| 643 |
| 644 handler_start_thread_.reset(); |
| 645 return exit_code == 0; |
433 } | 646 } |
434 | 647 |
435 // static | 648 // static |
436 void CrashpadClient::DumpWithoutCrash(const CONTEXT& context) { | 649 void CrashpadClient::DumpWithoutCrash(const CONTEXT& context) { |
437 if (g_signal_non_crash_dump == INVALID_HANDLE_VALUE || | 650 if (g_signal_non_crash_dump == INVALID_HANDLE_VALUE || |
438 g_non_crash_dump_done == INVALID_HANDLE_VALUE) { | 651 g_non_crash_dump_done == INVALID_HANDLE_VALUE) { |
439 LOG(ERROR) << "haven't called UseHandler()"; | 652 LOG(ERROR) << "not connected"; |
440 return; | 653 return; |
441 } | 654 } |
442 | 655 |
| 656 if (BlockUntilHandlerStartedOrFailed() == StartupState::kFailed) { |
| 657 // If we know for certain that the handler has failed to start, then abort |
| 658 // here, as we would otherwise wait indefinitely for the |
| 659 // g_non_crash_dump_done event that would never be signalled. |
| 660 LOG(ERROR) << "crash server failed to launch, no dump captured"; |
| 661 return; |
| 662 } |
| 663 |
443 // In the non-crashing case, we aren't concerned about avoiding calls into | 664 // In the non-crashing case, we aren't concerned about avoiding calls into |
444 // Win32 APIs, so just use regular locking here in case of multiple threads | 665 // Win32 APIs, so just use regular locking here in case of multiple threads |
445 // calling this function. If a crash occurs while we're in here, the worst | 666 // calling this function. If a crash occurs while we're in here, the worst |
446 // that can happen is that the server captures a partial dump for this path | 667 // that can happen is that the server captures a partial dump for this path |
447 // because on the other thread gathering a crash dump, it TerminateProcess()d, | 668 // because on the other thread gathering a crash dump, it TerminateProcess()d, |
448 // causing this one to abort. | 669 // causing this one to abort. |
449 base::AutoLock lock(*g_non_crash_dump_lock); | 670 base::AutoLock lock(*g_non_crash_dump_lock); |
450 | 671 |
451 // Create a fake EXCEPTION_POINTERS to give the handler something to work | 672 // Create a fake EXCEPTION_POINTERS to give the handler something to work |
452 // with. | 673 // with. |
(...skipping 15 matching lines...) Expand all Loading... |
468 #if defined(ARCH_CPU_64_BITS) | 689 #if defined(ARCH_CPU_64_BITS) |
469 record.ExceptionAddress = reinterpret_cast<void*>(context.Rip); | 690 record.ExceptionAddress = reinterpret_cast<void*>(context.Rip); |
470 #else | 691 #else |
471 record.ExceptionAddress = reinterpret_cast<void*>(context.Eip); | 692 record.ExceptionAddress = reinterpret_cast<void*>(context.Eip); |
472 #endif // ARCH_CPU_64_BITS | 693 #endif // ARCH_CPU_64_BITS |
473 | 694 |
474 exception_pointers.ExceptionRecord = &record; | 695 exception_pointers.ExceptionRecord = &record; |
475 | 696 |
476 g_non_crash_exception_information.thread_id = GetCurrentThreadId(); | 697 g_non_crash_exception_information.thread_id = GetCurrentThreadId(); |
477 g_non_crash_exception_information.exception_pointers = | 698 g_non_crash_exception_information.exception_pointers = |
478 reinterpret_cast<crashpad::WinVMAddress>(&exception_pointers); | 699 reinterpret_cast<WinVMAddress>(&exception_pointers); |
479 | 700 |
480 bool set_event_result = !!SetEvent(g_signal_non_crash_dump); | 701 bool set_event_result = !!SetEvent(g_signal_non_crash_dump); |
481 PLOG_IF(ERROR, !set_event_result) << "SetEvent"; | 702 PLOG_IF(ERROR, !set_event_result) << "SetEvent"; |
482 | 703 |
483 DWORD wfso_result = WaitForSingleObject(g_non_crash_dump_done, INFINITE); | 704 DWORD wfso_result = WaitForSingleObject(g_non_crash_dump_done, INFINITE); |
484 PLOG_IF(ERROR, wfso_result != WAIT_OBJECT_0) << "WaitForSingleObject"; | 705 PLOG_IF(ERROR, wfso_result != WAIT_OBJECT_0) << "WaitForSingleObject"; |
485 } | 706 } |
486 | 707 |
487 // static | 708 // static |
488 void CrashpadClient::DumpAndCrash(EXCEPTION_POINTERS* exception_pointers) { | 709 void CrashpadClient::DumpAndCrash(EXCEPTION_POINTERS* exception_pointers) { |
489 if (g_signal_exception == INVALID_HANDLE_VALUE) { | 710 if (g_signal_exception == INVALID_HANDLE_VALUE) { |
490 LOG(ERROR) << "haven't called UseHandler(), no dump captured"; | 711 LOG(ERROR) << "not connected"; |
491 TerminateProcess(GetCurrentProcess(), kTerminationCodeUseHandlerNotCalled); | 712 TerminateProcess(GetCurrentProcess(), |
| 713 kTerminationCodeNotConnectedToHandler); |
492 return; | 714 return; |
493 } | 715 } |
494 | 716 |
| 717 // We don't need to check for handler startup here, as |
| 718 // UnhandledExceptionHandler() necessarily does that. |
| 719 |
495 UnhandledExceptionHandler(exception_pointers); | 720 UnhandledExceptionHandler(exception_pointers); |
496 } | 721 } |
497 | 722 |
498 bool CrashpadClient::DumpAndCrashTargetProcess(HANDLE process, | 723 bool CrashpadClient::DumpAndCrashTargetProcess(HANDLE process, |
499 HANDLE blame_thread, | 724 HANDLE blame_thread, |
500 DWORD exception_code) const { | 725 DWORD exception_code) const { |
501 // Confirm we're on Vista or later. | 726 // Confirm we're on Vista or later. |
502 const DWORD version = GetVersion(); | 727 const DWORD version = GetVersion(); |
503 const DWORD major_version = LOBYTE(LOWORD(version)); | 728 const DWORD major_version = LOBYTE(LOWORD(version)); |
504 if (major_version < 6) { | 729 if (major_version < 6) { |
(...skipping 227 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
732 status = NtClose(injected_thread); | 957 status = NtClose(injected_thread); |
733 if (!NT_SUCCESS(status)) { | 958 if (!NT_SUCCESS(status)) { |
734 NTSTATUS_LOG(ERROR, status) << "NtClose"; | 959 NTSTATUS_LOG(ERROR, status) << "NtClose"; |
735 result = false; | 960 result = false; |
736 } | 961 } |
737 | 962 |
738 return result; | 963 return result; |
739 } | 964 } |
740 | 965 |
741 } // namespace crashpad | 966 } // namespace crashpad |
OLD | NEW |