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

Side by Side Diff: tools/exception_port_tool.cc

Issue 804913003: tools: Move Mac-specific tools to tools/mac (Closed) Base URL: https://chromium.googlesource.com/crashpad/crashpad@master
Patch Set: Created 6 years 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
« no previous file with comments | « tools/exception_port_tool.ad ('k') | tools/mac/catch_exception_tool.ad » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2014 The Crashpad Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (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
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include <errno.h>
16 #include <getopt.h>
17 #include <libgen.h>
18 #include <mach/mach.h>
19 #include <servers/bootstrap.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <unistd.h>
24
25 #include <string>
26 #include <vector>
27
28 #include "base/basictypes.h"
29 #include "base/mac/mach_logging.h"
30 #include "base/mac/scoped_mach_port.h"
31 #include "base/strings/stringprintf.h"
32 #include "tools/tool_support.h"
33 #include "util/mach/exception_ports.h"
34 #include "util/mach/mach_extensions.h"
35 #include "util/mach/symbolic_constants_mach.h"
36 #include "util/mach/task_for_pid.h"
37 #include "util/posix/drop_privileges.h"
38 #include "util/stdlib/string_number_conversion.h"
39
40 namespace crashpad {
41 namespace {
42
43 //! \brief Manages a pool of Mach send rights, deallocating all send rights upon
44 //! destruction.
45 //!
46 //! This class effectively implements what a vector of
47 //! base::mac::ScopedMachSendRight objects would be.
48 //!
49 //! The various “show” operations performed by this program display Mach ports
50 //! by their names as they are known in this task. For this to be useful, rights
51 //! to the same ports must have consistent names across successive calls. This
52 //! cannot be guaranteed if the rights are deallocated as soon as they are used,
53 //! because if that deallocation causes the task to lose its last right to a
54 //! port, subsequently regaining a right to the same port would cause it to be
55 //! known by a new name in this task.
56 //!
57 //! Instead of immediately deallocating send rights that are used for display,
58 //! they can be added to this pool. The pool collects send rights, ensuring that
59 //! they remain alive in this task, and that subsequent calls that obtain the
60 //! same rights cause them to be known by the same name. All rights are
61 //! deallocated upon destruction.
62 class MachSendRightPool {
63 public:
64 MachSendRightPool()
65 : send_rights_() {
66 }
67
68 ~MachSendRightPool() {
69 for (mach_port_t send_right : send_rights_) {
70 kern_return_t kr = mach_port_deallocate(mach_task_self(), send_right);
71 MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr) << "mach_port_deallocate";
72 }
73 }
74
75 //! \brief Adds a send right to the pool.
76 //!
77 //! \param[in] send_right The send right to be added. The pool object takes
78 //! ownership of the send right, which remains valid until the pool object
79 //! is destroyed.
80 //!
81 //! It is possible and in fact likely that one pool will wind up owning the
82 //! same send right multiple times. This is acceptable, because send rights
83 //! are reference-counted.
84 void AddSendRight(mach_port_t send_right) {
85 send_rights_.push_back(send_right);
86 }
87
88 private:
89 std::vector<mach_port_t> send_rights_;
90
91 DISALLOW_COPY_AND_ASSIGN(MachSendRightPool);
92 };
93
94 struct ExceptionHandlerDescription {
95 ExceptionPorts::TargetType target_type;
96 exception_mask_t mask;
97 exception_behavior_t behavior;
98 thread_state_flavor_t flavor;
99 std::string handler;
100 };
101
102 const char kHandlerNull[] = "NULL";
103 const char kHandlerBootstrapColon[] = "bootstrap:";
104
105 // Populates |description| based on a textual representation in
106 // |handler_string_ro|, returning true on success and false on failure (parse
107 // error). The --help string describes the format of |handler_string_ro|.
108 // Briefly, it is a comma-separated string that allows the members of
109 // |description| to be specified as "field=value". Values for "target" can be
110 // "host", "task", or "thread"; values for "handler" are of the form
111 // "bootstrap:service_name" where service_name will be looked up with the
112 // bootstrap server; and values for the other fields are interpreted by
113 // SymbolicConstantsMach.
114 bool ParseHandlerString(const char* handler_string_ro,
115 ExceptionHandlerDescription* description) {
116 const char kTargetEquals[] = "target=";
117 const char kMaskEquals[] = "mask=";
118 const char kBehaviorEquals[] = "behavior=";
119 const char kFlavorEquals[] = "flavor=";
120 const char kHandlerEquals[] = "handler=";
121
122 std::string handler_string(handler_string_ro);
123 char* handler_string_c = &handler_string[0];
124
125 char* token;
126 while ((token = strsep(&handler_string_c, ",")) != nullptr) {
127 if (strncmp(token, kTargetEquals, strlen(kTargetEquals)) == 0) {
128 const char* value = token + strlen(kTargetEquals);
129 if (strcmp(value, "host") == 0) {
130 description->target_type = ExceptionPorts::kTargetTypeHost;
131 } else if (strcmp(value, "task") == 0) {
132 description->target_type = ExceptionPorts::kTargetTypeTask;
133 } else if (strcmp(value, "thread") == 0) {
134 description->target_type = ExceptionPorts::kTargetTypeThread;
135 } else {
136 return false;
137 }
138 } else if (strncmp(token, kMaskEquals, strlen(kMaskEquals)) == 0) {
139 const char* value = token + strlen(kMaskEquals);
140 if (!StringToExceptionMask(
141 value,
142 kAllowFullName | kAllowShortName | kAllowNumber | kAllowOr,
143 &description->mask)) {
144 return false;
145 }
146 } else if (strncmp(token, kBehaviorEquals, strlen(kBehaviorEquals)) == 0) {
147 const char* value = token + strlen(kBehaviorEquals);
148 if (!StringToExceptionBehavior(
149 value,
150 kAllowFullName | kAllowShortName | kAllowNumber,
151 &description->behavior)) {
152 return false;
153 }
154 } else if (strncmp(token, kFlavorEquals, strlen(kFlavorEquals)) == 0) {
155 const char* value = token + strlen(kFlavorEquals);
156 if (!StringToThreadStateFlavor(
157 value,
158 kAllowFullName | kAllowShortName | kAllowNumber,
159 &description->flavor)) {
160 return false;
161 }
162 } else if (strncmp(token, kHandlerEquals, strlen(kHandlerEquals)) == 0) {
163 const char* value = token + strlen(kHandlerEquals);
164 if (strcmp(value, kHandlerNull) != 0 &&
165 strncmp(value,
166 kHandlerBootstrapColon,
167 strlen(kHandlerBootstrapColon)) != 0) {
168 return false;
169 }
170 description->handler = std::string(value);
171 } else {
172 return false;
173 }
174 }
175
176 return true;
177 }
178
179 // ShowExceptionPorts() shows handlers as numeric mach_port_t values, which are
180 // opaque and meaningless on their own. ShowBootstrapService() can be used to
181 // look up a service with the bootstrap server by name and show its mach_port_t
182 // value, which can then be associated with handlers shown by
183 // ShowExceptionPorts(). Any send rights obtained by this function are added to
184 // |mach_send_right_pool|.
185 void ShowBootstrapService(const std::string& service_name,
186 MachSendRightPool* mach_send_right_pool) {
187 mach_port_t service_port;
188 kern_return_t kr = bootstrap_look_up(
189 bootstrap_port, const_cast<char*>(service_name.c_str()), &service_port);
190 if (kr != BOOTSTRAP_SUCCESS) {
191 BOOTSTRAP_LOG(ERROR, kr) << "bootstrap_look_up " << service_name;
192 return;
193 }
194
195 mach_send_right_pool->AddSendRight(service_port);
196
197 printf("service %s %#x\n", service_name.c_str(), service_port);
198 }
199
200 // Prints information about all exception ports known for |exception_ports|. If
201 // |numeric| is true, all information is printed in numeric form, otherwise, it
202 // will be converted to symbolic constants where possible by
203 // SymbolicConstantsMach. If |is_new| is true, information will be presented as
204 // “new exception ports”, indicating that they show the state of the exception
205 // ports after SetExceptionPort() has been called. Any send rights obtained by
206 // this function are added to |mach_send_right_pool|.
207 void ShowExceptionPorts(const ExceptionPorts& exception_ports,
208 bool numeric,
209 bool is_new,
210 MachSendRightPool* mach_send_right_pool) {
211 const char* target_name = exception_ports.TargetTypeName();
212
213 std::vector<ExceptionPorts::ExceptionHandler> handlers;
214 if (!exception_ports.GetExceptionPorts(ExcMaskAll() | EXC_MASK_CRASH,
215 &handlers)) {
216 return;
217 }
218
219 const char* age_name = is_new ? "new " : "";
220
221 if (handlers.size() == 0) {
222 printf("no %s%s exception ports\n", age_name, target_name);
223 }
224
225 for (size_t port_index = 0; port_index < handlers.size(); ++port_index) {
226 mach_send_right_pool->AddSendRight(handlers[port_index].port);
227
228 if (numeric) {
229 printf(
230 "%s%s exception port %zu, mask %#x, port %#x, "
231 "behavior %#x, flavor %u\n",
232 age_name,
233 target_name,
234 port_index,
235 handlers[port_index].mask,
236 handlers[port_index].port,
237 handlers[port_index].behavior,
238 handlers[port_index].flavor);
239 } else {
240 std::string mask_string = ExceptionMaskToString(
241 handlers[port_index].mask, kUseShortName | kUnknownIsEmpty | kUseOr);
242 if (mask_string.empty()) {
243 mask_string.assign("?");
244 }
245
246 std::string behavior_string = ExceptionBehaviorToString(
247 handlers[port_index].behavior, kUseShortName | kUnknownIsEmpty);
248 if (behavior_string.empty()) {
249 behavior_string.assign("?");
250 }
251
252 std::string flavor_string = ThreadStateFlavorToString(
253 handlers[port_index].flavor, kUseShortName | kUnknownIsEmpty);
254 if (flavor_string.empty()) {
255 flavor_string.assign("?");
256 }
257
258 printf(
259 "%s%s exception port %zu, mask %#x (%s), port %#x, "
260 "behavior %#x (%s), flavor %u (%s)\n",
261 age_name,
262 target_name,
263 port_index,
264 handlers[port_index].mask,
265 mask_string.c_str(),
266 handlers[port_index].port,
267 handlers[port_index].behavior,
268 behavior_string.c_str(),
269 handlers[port_index].flavor,
270 flavor_string.c_str());
271 }
272 }
273 }
274
275 // Sets the exception port for |target_port|, a send right to a thread, task, or
276 // host port, to |description|, which identifies what type of port |target_port|
277 // is and describes an exception port to be set. Returns true on success.
278 //
279 // This function may be called more than once if setting different handlers is
280 // desired.
281 bool SetExceptionPort(const ExceptionHandlerDescription* description,
282 mach_port_t target_port) {
283 base::mac::ScopedMachSendRight service_port_owner;
284 exception_handler_t service_port = MACH_PORT_NULL;
285 kern_return_t kr;
286 if (description->handler.compare(
287 0, strlen(kHandlerBootstrapColon), kHandlerBootstrapColon) == 0) {
288 const char* service_name =
289 description->handler.c_str() + strlen(kHandlerBootstrapColon);
290 kr = bootstrap_look_up(
291 bootstrap_port, const_cast<char*>(service_name), &service_port);
292 if (kr != BOOTSTRAP_SUCCESS) {
293 BOOTSTRAP_LOG(ERROR, kr) << "bootstrap_look_up " << service_name;
294 return false;
295 }
296
297 // The service port doesn’t need to be added to a MachSendRightPool because
298 // it’s not used for display at all. ScopedMachSendRight is sufficient.
299 service_port_owner.reset(service_port);
300 } else if (description->handler != kHandlerNull) {
301 return false;
302 }
303
304 ExceptionPorts exception_ports(description->target_type, target_port);
305 if (!exception_ports.SetExceptionPort(description->mask,
306 service_port,
307 description->behavior,
308 description->flavor)) {
309 return false;
310 }
311
312 return true;
313 }
314
315 void Usage(const std::string& me) {
316 fprintf(stderr,
317 "Usage: %s [OPTION]... [COMMAND [ARG]...]\n"
318 "View and change Mach exception ports, and run COMMAND if supplied.\n"
319 "\n"
320 " -s, --set-handler=DESCRIPTION set an exception port to DESCRIPTION, see belo w\n"
321 " --show-bootstrap=SERVICE look up and display a service registered with\ n"
322 " the bootstrap server\n"
323 " -p, --pid=PID operate on PID instead of the current task\n"
324 " -h, --show-host display original host exception ports\n"
325 " -t, --show-task display original task exception ports\n"
326 " --show-thread display original thread exception ports\n"
327 " -H, --show-new-host display modified host exception ports\n"
328 " -T, --show-new-task display modified task exception ports\n"
329 " --show-new-thread display modified thread exception ports\n"
330 " -n, --numeric display values numerically, not symbolically\n "
331 " --help display this help and exit\n"
332 " --version output version information and exit\n"
333 "\n"
334 "Any operations on host exception ports require superuser permissions.\n"
335 "\n"
336 "DESCRIPTION is formatted as a comma-separated sequence of tokens, where each\n"
337 "token consists of a key and value separated by an equals sign. Available keys:\ n"
338 " target which target's exception ports to set: host, task, or thread\n"
339 " mask the mask of exception types to handle: CRASH, ALL, or others\n"
340 " behavior the specific exception handler routine to call: DEFAULT, STATE,\n"
341 " or STATE_IDENTITY, possibly with MACH_EXCEPTION_CODES.\n"
342 " flavor the thread state flavor passed to the handler: architecture-specifi c\n"
343 " handler the exception handler: NULL or bootstrap:SERVICE, indicating that\n "
344 " the handler should be looked up with the bootstrap server\n"
345 "The default DESCRIPTION is\n"
346 " target=task,mask=CRASH,behavior=DEFAULT|MACH,flavor=NONE,handler=NULL\n",
347 me.c_str());
348 ToolSupport::UsageTail(me);
349 }
350
351 int ExceptionPortToolMain(int argc, char* argv[]) {
352 const std::string me(basename(argv[0]));
353
354 enum ExitCode {
355 kExitSuccess = EXIT_SUCCESS,
356
357 // To differentiate this tool’s errors from errors in the programs it execs,
358 // use a high exit code for ordinary failures instead of EXIT_FAILURE. This
359 // is the same rationale for using the distinct exit codes for exec
360 // failures.
361 kExitFailure = 125,
362
363 // Like env, use exit code 126 if the program was found but could not be
364 // invoked, and 127 if it could not be found.
365 // http://pubs.opengroup.org/onlinepubs/9699919799/utilities/env.html
366 kExitExecFailure = 126,
367 kExitExecENOENT = 127,
368 };
369
370 enum OptionFlags {
371 // “Short” (single-character) options.
372 kOptionSetPort = 's',
373 kOptionPid = 'p',
374 kOptionShowHost = 'h',
375 kOptionShowTask = 't',
376 kOptionShowNewHost = 'H',
377 kOptionShowNewTask = 'T',
378 kOptionNumeric = 'n',
379
380 // Long options without short equivalents.
381 kOptionLastChar = 255,
382 kOptionShowBootstrap,
383 kOptionShowThread,
384 kOptionShowNewThread,
385
386 // Standard options.
387 kOptionHelp = -2,
388 kOptionVersion = -3,
389 };
390
391 struct {
392 std::vector<const char*> show_bootstrap;
393 std::vector<ExceptionHandlerDescription> set_handler;
394 pid_t pid;
395 task_t alternate_task;
396 bool show_host;
397 bool show_task;
398 bool show_thread;
399 bool show_new_host;
400 bool show_new_task;
401 bool show_new_thread;
402 bool numeric;
403 } options = {};
404
405 const struct option long_options[] = {
406 {"set-handler", required_argument, nullptr, kOptionSetPort},
407 {"show-bootstrap", required_argument, nullptr, kOptionShowBootstrap},
408 {"pid", required_argument, nullptr, kOptionPid},
409 {"show-host", no_argument, nullptr, kOptionShowHost},
410 {"show-task", no_argument, nullptr, kOptionShowTask},
411 {"show-thread", no_argument, nullptr, kOptionShowThread},
412 {"show-new-host", no_argument, nullptr, kOptionShowNewHost},
413 {"show-new-task", no_argument, nullptr, kOptionShowNewTask},
414 {"show-new-thread", no_argument, nullptr, kOptionShowNewThread},
415 {"numeric", no_argument, nullptr, kOptionNumeric},
416 {"help", no_argument, nullptr, kOptionHelp},
417 {"version", no_argument, nullptr, kOptionVersion},
418 {nullptr, 0, nullptr, 0},
419 };
420
421 int opt;
422 while ((opt = getopt_long(argc, argv, "+s:p:htHTn", long_options, nullptr)) !=
423 -1) {
424 switch (opt) {
425 case kOptionSetPort: {
426 options.set_handler.push_back({});
427 ExceptionHandlerDescription* description = &options.set_handler.back();
428 description->target_type = ExceptionPorts::kTargetTypeTask;
429 description->mask = EXC_MASK_CRASH;
430 description->behavior = EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES;
431 description->flavor = THREAD_STATE_NONE;
432 description->handler = "NULL";
433 if (!ParseHandlerString(optarg, description)) {
434 fprintf(stderr,
435 "%s: invalid exception handler: %s\n",
436 me.c_str(),
437 optarg);
438 return kExitFailure;
439 }
440 break;
441 }
442 case kOptionShowBootstrap:
443 options.show_bootstrap.push_back(optarg);
444 break;
445 case kOptionPid:
446 if (!StringToNumber(optarg, &options.pid)) {
447 fprintf(stderr, "%s: invalid pid: %s\n", me.c_str(), optarg);
448 return kExitFailure;
449 }
450 break;
451 case kOptionShowHost:
452 options.show_host = true;
453 break;
454 case kOptionShowTask:
455 options.show_task = true;
456 break;
457 case kOptionShowThread:
458 options.show_thread = true;
459 break;
460 case kOptionShowNewHost:
461 options.show_new_host = true;
462 break;
463 case kOptionShowNewTask:
464 options.show_new_task = true;
465 break;
466 case kOptionShowNewThread:
467 options.show_new_thread = true;
468 break;
469 case kOptionNumeric:
470 options.numeric = true;
471 break;
472 case kOptionHelp:
473 Usage(me);
474 return kExitSuccess;
475 case kOptionVersion:
476 ToolSupport::Version(me);
477 return kExitSuccess;
478 default:
479 ToolSupport::UsageHint(me, nullptr);
480 return kExitFailure;
481 }
482 }
483 argc -= optind;
484 argv += optind;
485
486 if (options.show_bootstrap.empty() && !options.show_host &&
487 !options.show_task && !options.show_thread &&
488 options.set_handler.empty() && argc == 0) {
489 ToolSupport::UsageHint(me, "nothing to do");
490 return kExitFailure;
491 }
492
493 base::mac::ScopedMachSendRight alternate_task_owner;
494 if (options.pid) {
495 if (argc) {
496 ToolSupport::UsageHint(me, "cannot combine -p with COMMAND");
497 return kExitFailure;
498 }
499
500 options.alternate_task = TaskForPID(options.pid);
501 if (options.alternate_task == TASK_NULL) {
502 return kExitFailure;
503 }
504 alternate_task_owner.reset(options.alternate_task);
505 }
506
507 // This tool may have been installed as a setuid binary so that TaskForPID()
508 // could succeed. Drop any privileges now that they’re no longer necessary.
509 DropPrivileges();
510
511 MachSendRightPool mach_send_right_pool;
512
513 // Show bootstrap services requested.
514 for (const char* service : options.show_bootstrap) {
515 ShowBootstrapService(service, &mach_send_right_pool);
516 }
517
518 // Show the original exception ports.
519 if (options.show_host) {
520 ShowExceptionPorts(
521 ExceptionPorts(ExceptionPorts::kTargetTypeHost, HOST_NULL),
522 options.numeric,
523 false,
524 &mach_send_right_pool);
525 }
526 if (options.show_task) {
527 ShowExceptionPorts(
528 ExceptionPorts(ExceptionPorts::kTargetTypeTask, options.alternate_task),
529 options.numeric,
530 false,
531 &mach_send_right_pool);
532 }
533 if (options.show_thread) {
534 ShowExceptionPorts(
535 ExceptionPorts(ExceptionPorts::kTargetTypeThread, THREAD_NULL),
536 options.numeric,
537 false,
538 &mach_send_right_pool);
539 }
540
541 if (!options.set_handler.empty()) {
542 // Set new exception handlers.
543 for (ExceptionHandlerDescription description : options.set_handler) {
544 if (!SetExceptionPort(
545 &description,
546 description.target_type == ExceptionPorts::kTargetTypeTask
547 ? options.alternate_task
548 : TASK_NULL)) {
549 return kExitFailure;
550 }
551 }
552
553 // Show changed exception ports.
554 if (options.show_new_host) {
555 ShowExceptionPorts(
556 ExceptionPorts(ExceptionPorts::kTargetTypeHost, HOST_NULL),
557 options.numeric,
558 true,
559 &mach_send_right_pool);
560 }
561 if (options.show_new_task) {
562 ShowExceptionPorts(
563 ExceptionPorts(ExceptionPorts::kTargetTypeTask,
564 options.alternate_task),
565 options.numeric,
566 true,
567 &mach_send_right_pool);
568 }
569 if (options.show_new_thread) {
570 ShowExceptionPorts(
571 ExceptionPorts(ExceptionPorts::kTargetTypeThread, THREAD_NULL),
572 options.numeric,
573 true,
574 &mach_send_right_pool);
575 }
576 }
577
578 if (argc) {
579 // Using the remaining arguments, start a new program with the new set of
580 // exception ports in effect.
581 execvp(argv[0], argv);
582 PLOG(ERROR) << "execvp " << argv[0];
583 return errno == ENOENT ? kExitExecENOENT : kExitExecFailure;
584 }
585
586 return kExitSuccess;
587 }
588
589 } // namespace
590 } // namespace crashpad
591
592 int main(int argc, char* argv[]) {
593 return crashpad::ExceptionPortToolMain(argc, argv);
594 }
OLDNEW
« no previous file with comments | « tools/exception_port_tool.ad ('k') | tools/mac/catch_exception_tool.ad » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698