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

Side by Side Diff: tools/exception_port_tool.cc

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

Powered by Google App Engine
This is Rietveld 408576698