Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(287)

Side by Side Diff: client/crashpad_client_mac.cc

Issue 1413033007: mac: Restart crashpad_handler from the initial client if it dies (Closed) Base URL: https://chromium.googlesource.com/crashpad/crashpad@master
Patch Set: Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2014 The Crashpad Authors. All rights reserved. 1 // Copyright 2014 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 <errno.h>
17 #include <mach/mach.h> 18 #include <mach/mach.h>
19 #include <pthread.h>
18 #include <sys/wait.h> 20 #include <sys/wait.h>
19 #include <unistd.h> 21 #include <unistd.h>
20 22
21 #include "base/logging.h" 23 #include "base/logging.h"
22 #include "base/mac/mach_logging.h" 24 #include "base/mac/mach_logging.h"
23 #include "base/posix/eintr_wrapper.h" 25 #include "base/posix/eintr_wrapper.h"
24 #include "base/strings/stringprintf.h" 26 #include "base/strings/stringprintf.h"
27 #include "util/mac/mac_util.h"
25 #include "util/mach/child_port_handshake.h" 28 #include "util/mach/child_port_handshake.h"
26 #include "util/mach/exception_ports.h" 29 #include "util/mach/exception_ports.h"
27 #include "util/mach/mach_extensions.h" 30 #include "util/mach/mach_extensions.h"
31 #include "util/mach/mach_message.h"
32 #include "util/mach/notify_server.h"
33 #include "util/misc/clock.h"
28 #include "util/misc/implicit_cast.h" 34 #include "util/misc/implicit_cast.h"
29 #include "util/posix/close_multiple.h" 35 #include "util/posix/close_multiple.h"
30 36
31 namespace crashpad { 37 namespace crashpad {
32 38
33 namespace { 39 namespace {
34 40
35 std::string FormatArgumentString(const std::string& name, 41 std::string FormatArgumentString(const std::string& name,
36 const std::string& value) { 42 const std::string& value) {
37 return base::StringPrintf("--%s=%s", name.c_str(), value.c_str()); 43 return base::StringPrintf("--%s=%s", name.c_str(), value.c_str());
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
72 // so AND them with ExcMaskValid(). EXC_MASK_CRASH is always supported. 78 // so AND them with ExcMaskValid(). EXC_MASK_CRASH is always supported.
73 bool SetCrashExceptionPorts(exception_handler_t exception_handler) { 79 bool SetCrashExceptionPorts(exception_handler_t exception_handler) {
74 ExceptionPorts exception_ports(ExceptionPorts::kTargetTypeTask, TASK_NULL); 80 ExceptionPorts exception_ports(ExceptionPorts::kTargetTypeTask, TASK_NULL);
75 return exception_ports.SetExceptionPort( 81 return exception_ports.SetExceptionPort(
76 (EXC_MASK_CRASH | EXC_MASK_RESOURCE | EXC_MASK_GUARD) & ExcMaskValid(), 82 (EXC_MASK_CRASH | EXC_MASK_RESOURCE | EXC_MASK_GUARD) & ExcMaskValid(),
77 exception_handler, 83 exception_handler,
78 EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, 84 EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES,
79 MACHINE_THREAD_STATE); 85 MACHINE_THREAD_STATE);
80 } 86 }
81 87
82 //! \brief Starts a Crashpad handler. 88 class ScopedPthreadAttrDestroy {
83 class HandlerStarter final {
84 public: 89 public:
85 //! \brief Starts a Crashpad handler. 90 explicit ScopedPthreadAttrDestroy(pthread_attr_t* pthread_attr)
91 : pthread_attr_(pthread_attr) {
92 }
93
94 ~ScopedPthreadAttrDestroy() {
95 errno = pthread_attr_destroy(pthread_attr_);
96 PLOG_IF(WARNING, errno != 0) << "pthread_attr_destroy";
97 }
98
99 private:
100 pthread_attr_t* pthread_attr_;
101
102 DISALLOW_COPY_AND_ASSIGN(ScopedPthreadAttrDestroy);
103 };
104
105 //! \brief Starts a Crashpad handler, possibly restarting it if it dies.
106 class HandlerStarter final : public NotifyServer::DefaultInterface {
107 public:
108 ~HandlerStarter() {}
109
110 //! \brief Starts a Crashpad handler initially, as opposed to starting it for
111 //! subsequent restarts.
86 //! 112 //!
87 //! All parameters are as in CrashpadClient::StartHandler(). 113 //! All parameters are as in CrashpadClient::StartHandler().
88 //! 114 //!
89 //! \return On success, a send right to the Crashpad handler that has been 115 //! \return On success, a send right to the Crashpad handler that has been
90 //! started. On failure, `MACH_PORT_NULL` with a message logged. 116 //! started. On failure, `MACH_PORT_NULL` with a message logged.
91 static base::mac::ScopedMachSendRight Start( 117 static base::mac::ScopedMachSendRight InitialStart(
92 const base::FilePath& handler, 118 const base::FilePath& handler,
93 const base::FilePath& database, 119 const base::FilePath& database,
94 const std::string& url, 120 const std::string& url,
95 const std::map<std::string, std::string>& annotations, 121 const std::map<std::string, std::string>& annotations,
96 const std::vector<std::string>& arguments) { 122 const std::vector<std::string>& arguments,
123 bool restartable) {
97 base::mac::ScopedMachReceiveRight receive_right( 124 base::mac::ScopedMachReceiveRight receive_right(
98 NewMachPort(MACH_PORT_RIGHT_RECEIVE)); 125 NewMachPort(MACH_PORT_RIGHT_RECEIVE));
99 if (receive_right == kMachPortNull) { 126 if (receive_right == kMachPortNull) {
100 return base::mac::ScopedMachSendRight(); 127 return base::mac::ScopedMachSendRight();
101 } 128 }
102 129
103 mach_port_t port; 130 mach_port_t port;
104 mach_msg_type_name_t right_type; 131 mach_msg_type_name_t right_type;
105 kern_return_t kr = mach_port_extract_right(mach_task_self(), 132 kern_return_t kr = mach_port_extract_right(mach_task_self(),
106 receive_right.get(), 133 receive_right.get(),
107 MACH_MSG_TYPE_MAKE_SEND, 134 MACH_MSG_TYPE_MAKE_SEND,
108 &port, 135 &port,
109 &right_type); 136 &right_type);
110 if (kr != KERN_SUCCESS) { 137 if (kr != KERN_SUCCESS) {
111 MACH_LOG(ERROR, kr) << "mach_port_extract_right"; 138 MACH_LOG(ERROR, kr) << "mach_port_extract_right";
112 return base::mac::ScopedMachSendRight(); 139 return base::mac::ScopedMachSendRight();
113 } 140 }
114 base::mac::ScopedMachSendRight send_right(port); 141 base::mac::ScopedMachSendRight send_right(port);
115 DCHECK_EQ(port, receive_right.get()); 142 DCHECK_EQ(port, receive_right.get());
116 DCHECK_EQ(right_type, 143 DCHECK_EQ(right_type,
117 implicit_cast<mach_msg_type_name_t>(MACH_MSG_TYPE_PORT_SEND)); 144 implicit_cast<mach_msg_type_name_t>(MACH_MSG_TYPE_PORT_SEND));
118 145
146 scoped_ptr<HandlerStarter> handler_starter;
Robert Sesek 2015/11/03 15:50:27 handler_restarter ?
147 if (restartable) {
148 handler_starter.reset(new HandlerStarter());
149 if (handler_starter->notify_port_ == kMachPortNull) {
Robert Sesek 2015/11/03 15:50:27 Leave a comment here indicating that this is an er
150 handler_starter.reset();
151 }
152 }
153
119 if (!CommonStart(handler, 154 if (!CommonStart(handler,
120 database, 155 database,
121 url, 156 url,
122 annotations, 157 annotations,
123 arguments, 158 arguments,
124 receive_right.Pass())) { 159 receive_right.Pass(),
160 handler_starter.get(),
161 false)) {
125 return base::mac::ScopedMachSendRight(); 162 return base::mac::ScopedMachSendRight();
126 } 163 }
127 164
165 if (handler_starter && handler_starter->StartRestartThread(
166 handler, database, url, annotations, arguments)) {
167 // The thread owns the object now.
168 ignore_result(handler_starter.release());
169 }
170
128 return send_right; 171 return send_right;
129 } 172 }
130 173
174 // NotifyServer::DefaultInterface:
175
176 kern_return_t DoMachNotifyPortDestroyed(notify_port_t notify,
177 mach_port_t rights,
178 const mach_msg_trailer_t* trailer,
179 bool* destroy_request) override {
180 // The receive right corresponding to this process’ crash exception port is
181 // now owned by this process. Any crashes that occur before the receive
182 // right is moved to a new handler process will cause the process to hang in
183 // an unkillable state prior to OS X 10.10.
184
185 if (notify != notify_port_) {
186 LOG(WARNING) << "notify port mismatch";
187 return KERN_FAILURE;
188 }
189
190 // If CommonStart() fails, the receive right will die, and this will just
191 // be called again for another try.
192 CommonStart(handler_,
193 database_,
194 url_,
195 annotations_,
196 arguments_,
197 base::mac::ScopedMachReceiveRight(rights),
198 this,
199 true);
200
201 return KERN_SUCCESS;
202 }
203
131 private: 204 private:
205 HandlerStarter()
206 : NotifyServer::DefaultInterface(),
207 handler_(),
208 database_(),
209 url_(),
210 annotations_(),
211 arguments_(),
212 notify_port_(NewMachPort(MACH_PORT_RIGHT_RECEIVE)),
213 last_start_time_(0) {
214 }
215
216 //! \brief Starts a Crashpad handler.
217 //!
218 //! All parameters are as in CrashpadClient::StartHandler(), with these
219 //! additions:
220 //!
221 //! \param[in] receive_right The receive right to move to the Crashpad
222 //! handler. The handler will use this receive right to run its exception
223 //! server.
224 //! \param[in] handler_starter If CrashpadClient::StartHandler() was invoked
225 //! with \a restartable set to `true`, this is the restart state object.
226 //! Otherwise, this is `nullptr`.
227 //! \param[in] restart If CrashpadClient::StartHandler() was invoked with \a
228 //! restartable set to `true` and CommonStart() is being called to restart
229 //! a previously-started handler, this is `true`. Otherwise, this is
230 //! `false`.
231 //!
232 //! \return `true` on success, `false` on failure, with a message logged.
233 //! Failures include failure to start the handler process and failure to
234 //! rendezvous with it via ChildPortHandshake.
132 static bool CommonStart(const base::FilePath& handler, 235 static bool CommonStart(const base::FilePath& handler,
133 const base::FilePath& database, 236 const base::FilePath& database,
134 const std::string& url, 237 const std::string& url,
135 const std::map<std::string, std::string>& annotations, 238 const std::map<std::string, std::string>& annotations,
136 const std::vector<std::string>& arguments, 239 const std::vector<std::string>& arguments,
137 base::mac::ScopedMachReceiveRight receive_right) { 240 base::mac::ScopedMachReceiveRight receive_right,
241 HandlerStarter* handler_starter,
242 bool restart) {
243 if (handler_starter) {
244 // The port-destroyed notification must be requested each time. It uses
245 // a send-once right, so once the notification is received, it won’t be
246 // sent again unless re-requested.
247 mach_port_t previous;
248 kern_return_t kr =
249 mach_port_request_notification(mach_task_self(),
250 receive_right.get(),
251 MACH_NOTIFY_PORT_DESTROYED,
252 0,
253 handler_starter->notify_port_.get(),
254 MACH_MSG_TYPE_MAKE_SEND_ONCE,
255 &previous);
256 if (kr != KERN_SUCCESS) {
257 MACH_LOG(WARNING, kr) << "mach_port_request_notification";
258
259 // This will cause the restart thread to terminate after this restart
260 // attempt. There’s no longer any need for it, because no more
261 // port-destroyed notifications can be delivered.
262 handler_starter->notify_port_.reset();
263 } else {
264 base::mac::ScopedMachSendRight previous_owner(previous);
265 if (!restart) {
266 DCHECK_EQ(previous, kMachPortNull);
267 }
268 }
269
270 if (handler_starter->last_start_time_) {
271 // If the handler was ever started before, don’t restart it too quickly.
272 const uint64_t kNanosecondsPerSecond = 1E9;
273 const uint64_t kMinimumStartInterval = 1 * kNanosecondsPerSecond;
274
275 const uint64_t earliest_next_start_time =
276 handler_starter->last_start_time_ + kMinimumStartInterval;
277 const uint64_t now_time = ClockMonotonicNanoseconds();
278 if (earliest_next_start_time > now_time) {
279 SleepNanoseconds(earliest_next_start_time - now_time);
280 }
281 }
282
283 handler_starter->last_start_time_ = ClockMonotonicNanoseconds();
284 }
285
138 // Set up the arguments for execve() first. These aren’t needed until 286 // Set up the arguments for execve() first. These aren’t needed until
139 // execve() is called, but it’s dangerous to do this in a child process 287 // execve() is called, but it’s dangerous to do this in a child process
140 // after fork(). 288 // after fork().
141 ChildPortHandshake child_port_handshake; 289 ChildPortHandshake child_port_handshake;
142 base::ScopedFD server_write_fd = child_port_handshake.ServerWriteFD(); 290 base::ScopedFD server_write_fd = child_port_handshake.ServerWriteFD();
143 291
144 // Use handler as argv[0], followed by arguments directed by this method’s 292 // Use handler as argv[0], followed by arguments directed by this method’s
145 // parameters and a --handshake-fd argument. |arguments| are added first so 293 // parameters and a --handshake-fd argument. |arguments| are added first so
146 // that if it erroneously contains an argument such as --url, the actual 294 // that if it erroneously contains an argument such as --url, the actual
147 // |url| argument passed to this method will supersede it. In normal 295 // |url| argument passed to this method will supersede it. In normal
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
188 // process will not result in a zombie process. 336 // process will not result in a zombie process.
189 pid_t pid = fork(); 337 pid_t pid = fork();
190 if (pid < 0) { 338 if (pid < 0) {
191 PLOG(ERROR) << "fork"; 339 PLOG(ERROR) << "fork";
192 return false; 340 return false;
193 } 341 }
194 342
195 if (pid == 0) { 343 if (pid == 0) {
196 // Child process. 344 // Child process.
197 345
346 if (restart) {
347 // When restarting, reset the system default crash handler first.
348 // Otherwise, the crash exception port here will have been inherited
349 // from the parent process, which was probably using the exception
350 // server now being restarted. The handler can’t monitor itself for its
351 // own crashes via this interface.
352 CrashpadClient::UseSystemDefaultHandler();
353 }
354
198 // Call setsid(), creating a new process group and a new session, both led 355 // Call setsid(), creating a new process group and a new session, both led
199 // by this process. The new process group has no controlling terminal. 356 // by this process. The new process group has no controlling terminal.
200 // This disconnects it from signals generated by the parent process’ 357 // This disconnects it from signals generated by the parent process’
201 // terminal. 358 // terminal.
202 // 359 //
203 // setsid() is done in the child instead of the grandchild so that the 360 // setsid() is done in the child instead of the grandchild so that the
204 // grandchild will not be a session leader. If it were a session leader, 361 // grandchild will not be a session leader. If it were a session leader,
205 // an accidental open() of a terminal device without O_NOCTTY would make 362 // an accidental open() of a terminal device without O_NOCTTY would make
206 // that terminal the controlling terminal. 363 // that terminal the controlling terminal.
207 // 364 //
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
262 // Rendezvous with the handler running in the grandchild process. 419 // Rendezvous with the handler running in the grandchild process.
263 if (!child_port_handshake.RunClient(receive_right.get(), 420 if (!child_port_handshake.RunClient(receive_right.get(),
264 MACH_MSG_TYPE_MOVE_RECEIVE)) { 421 MACH_MSG_TYPE_MOVE_RECEIVE)) {
265 return false; 422 return false;
266 } 423 }
267 424
268 ignore_result(receive_right.release()); 425 ignore_result(receive_right.release());
269 return true; 426 return true;
270 } 427 }
271 428
272 DISALLOW_IMPLICIT_CONSTRUCTORS(HandlerStarter); 429 bool StartRestartThread(const base::FilePath& handler,
430 const base::FilePath& database,
431 const std::string& url,
432 const std::map<std::string, std::string>& annotations,
433 const std::vector<std::string>& arguments) {
434 handler_ = handler;
435 database_ = database;
436 url_ = url;
437 annotations_ = annotations;
438 arguments_ = arguments;
439
440 pthread_attr_t pthread_attr;
441 errno = pthread_attr_init(&pthread_attr);
442 if (errno != 0) {
443 PLOG(WARNING) << "pthread_attr_init";
444 return false;
445 }
446 ScopedPthreadAttrDestroy pthread_attr_owner(&pthread_attr);
447
448 errno = pthread_attr_setdetachstate(&pthread_attr, PTHREAD_CREATE_DETACHED);
449 if (errno != 0) {
450 PLOG(WARNING) << "pthread_attr_setdetachstate";
451 return false;
452 }
453
454 pthread_t pthread;
455 errno = pthread_create(&pthread, &pthread_attr, RestartThreadMain, this);
456 if (errno != 0) {
457 PLOG(WARNING) << "pthread_create";
458 return false;
459 }
460
461 return true;
462 }
463
464 static void* RestartThreadMain(void* argument) {
465 HandlerStarter* self = reinterpret_cast<HandlerStarter*>(argument);
466
467 NotifyServer notify_server(self);
468 mach_msg_return_t mr;
469 do {
470 mr = MachMessageServer::Run(&notify_server,
471 self->notify_port_.get(),
472 0,
473 MachMessageServer::kPersistent,
474 MachMessageServer::kReceiveLargeError,
475 kMachMessageTimeoutWaitIndefinitely);
476 MACH_LOG_IF(ERROR, mr != MACH_MSG_SUCCESS, mr)
477 << "MachMessageServer::Run";
478 } while (self->notify_port_ != kMachPortNull && mr == MACH_MSG_SUCCESS);
479
480 delete self;
481
482 return nullptr;
483 }
484
485 base::FilePath handler_;
486 base::FilePath database_;
487 std::string url_;
488 std::map<std::string, std::string> annotations_;
489 std::vector<std::string> arguments_;
490 base::mac::ScopedMachReceiveRight notify_port_;
491 uint64_t last_start_time_;
492
493 DISALLOW_COPY_AND_ASSIGN(HandlerStarter);
273 }; 494 };
274 495
275 } // namespace 496 } // namespace
276 497
277 CrashpadClient::CrashpadClient() 498 CrashpadClient::CrashpadClient()
278 : exception_port_() { 499 : exception_port_() {
279 } 500 }
280 501
281 CrashpadClient::~CrashpadClient() { 502 CrashpadClient::~CrashpadClient() {
282 } 503 }
283 504
284 bool CrashpadClient::StartHandler( 505 bool CrashpadClient::StartHandler(
285 const base::FilePath& handler, 506 const base::FilePath& handler,
286 const base::FilePath& database, 507 const base::FilePath& database,
287 const std::string& url, 508 const std::string& url,
288 const std::map<std::string, std::string>& annotations, 509 const std::map<std::string, std::string>& annotations,
289 const std::vector<std::string>& arguments) { 510 const std::vector<std::string>& arguments,
511 bool restartable) {
290 DCHECK(!exception_port_.is_valid()); 512 DCHECK(!exception_port_.is_valid());
291 513
292 exception_port_ = HandlerStarter::Start( 514 // The “restartable” behavior can only be selected on OS X 10.10 and later. In
293 handler, database, url, annotations, arguments); 515 // previous OS versions, if the initial client were to crash while attempting
516 // to restart the handler, it would become an unkillable process.
517 exception_port_ = HandlerStarter::InitialStart(
518 handler,
519 database,
520 url,
521 annotations,
522 arguments,
523 restartable && MacOSXMinorVersion() >= 10);
294 if (!exception_port_.is_valid()) { 524 if (!exception_port_.is_valid()) {
295 return false; 525 return false;
296 } 526 }
297 527
298 return true; 528 return true;
299 } 529 }
300 530
301 bool CrashpadClient::UseHandler() { 531 bool CrashpadClient::UseHandler() {
302 DCHECK(exception_port_.is_valid()); 532 DCHECK(exception_port_.is_valid());
303 533
304 return SetCrashExceptionPorts(exception_port_.get()); 534 return SetCrashExceptionPorts(exception_port_.get());
305 } 535 }
306 536
307 // static 537 // static
308 void CrashpadClient::UseSystemDefaultHandler() { 538 void CrashpadClient::UseSystemDefaultHandler() {
309 base::mac::ScopedMachSendRight 539 base::mac::ScopedMachSendRight
310 system_crash_reporter_handler(SystemCrashReporterHandler()); 540 system_crash_reporter_handler(SystemCrashReporterHandler());
311 541
312 // Proceed even if SystemCrashReporterHandler() failed, setting MACH_PORT_NULL 542 // Proceed even if SystemCrashReporterHandler() failed, setting MACH_PORT_NULL
313 // to clear the current exception ports. 543 // to clear the current exception ports.
314 if (!SetCrashExceptionPorts(system_crash_reporter_handler.get())) { 544 if (!SetCrashExceptionPorts(system_crash_reporter_handler.get())) {
315 SetCrashExceptionPorts(MACH_PORT_NULL); 545 SetCrashExceptionPorts(MACH_PORT_NULL);
316 } 546 }
317 } 547 }
318 548
319 } // namespace crashpad 549 } // namespace crashpad
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698