Chromium Code Reviews| Index: tools/exception_port_tool.cc |
| diff --git a/tools/exception_port_tool.cc b/tools/exception_port_tool.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..4fc67052317508b0aa0201691bf2ce1b6f28877e |
| --- /dev/null |
| +++ b/tools/exception_port_tool.cc |
| @@ -0,0 +1,512 @@ |
| +// Copyright 2014 The Crashpad Authors. All rights reserved. |
| +// |
| +// Licensed under the Apache License, Version 2.0 (the "License"); |
| +// you may not use this file except in compliance with the License. |
| +// You may obtain a copy of the License at |
| +// |
| +// http://www.apache.org/licenses/LICENSE-2.0 |
| +// |
| +// Unless required by applicable law or agreed to in writing, software |
| +// distributed under the License is distributed on an "AS IS" BASIS, |
| +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| +// See the License for the specific language governing permissions and |
| +// limitations under the License. |
| + |
| +#include <errno.h> |
| +#include <getopt.h> |
| +#include <libgen.h> |
| +#include <mach/mach.h> |
| +#include <servers/bootstrap.h> |
| +#include <stdio.h> |
| +#include <stdlib.h> |
| +#include <string.h> |
| +#include <unistd.h> |
| + |
| +#include <string> |
| +#include <vector> |
| + |
| +#include "base/basictypes.h" |
| +#include "base/mac/mach_logging.h" |
| +#include "base/mac/scoped_mach_port.h" |
| +#include "base/strings/stringprintf.h" |
| +#include "tools/tool_support.h" |
| +#include "util/mach/exception_ports.h" |
| +#include "util/mach/mach_extensions.h" |
| +#include "util/mach/symbolic_constants_mach.h" |
| +#include "util/stdlib/string_number_conversion.h" |
| + |
| +namespace { |
| + |
| +using namespace crashpad; |
| + |
| +struct ExceptionHandlerDescription { |
| + ExceptionPorts::TargetType target_type; |
| + exception_mask_t mask; |
| + exception_behavior_t behavior; |
| + thread_state_flavor_t flavor; |
| + std::string handler; |
| +}; |
| + |
| +const char kHandlerNull[] = "NULL"; |
| +const char kHandlerBootstrapColon[] = "bootstrap:"; |
| + |
| +// Populates |description| based on a textual representation in |
| +// |handler_string_ro|, returning true on success and false on failure (parse |
| +// error). The --help string describes the format of |handler_string_ro|. |
| +// Briefly, it is a comma-separated string that allows the members of |
| +// |description| to be specified as "field=value". Values for "target" can be |
| +// "host", "task", or "thread"; values for "handler" are of the form |
| +// "bootstrap:service_name" where service_name will be looked up with the |
| +// bootstrap server; and values for the other fields are interpreted by |
| +// SymbolicConstantsMach. |
| +bool ParseHandlerString(const char* handler_string_ro, |
| + ExceptionHandlerDescription* description) { |
| + const char kTargetEquals[] = "target="; |
| + const char kMaskEquals[] = "mask="; |
| + const char kBehaviorEquals[] = "behavior="; |
| + const char kFlavorEquals[] = "flavor="; |
| + const char kHandlerEquals[] = "handler="; |
| + |
| + std::string handler_string(handler_string_ro); |
| + char* handler_string_c = &handler_string[0]; |
| + |
| + char* token; |
| + while ((token = strsep(&handler_string_c, ",")) != NULL) { |
| + if (strncmp(token, kTargetEquals, strlen(kTargetEquals)) == 0) { |
| + const char* value = token + strlen(kTargetEquals); |
| + if (strcmp(value, "host") == 0) { |
| + description->target_type = ExceptionPorts::kTargetTypeHost; |
| + } else if (strcmp(value, "task") == 0) { |
| + description->target_type = ExceptionPorts::kTargetTypeTask; |
| + } else if (strcmp(value, "thread") == 0) { |
| + description->target_type = ExceptionPorts::kTargetTypeThread; |
| + } |
|
Robert Sesek
2014/09/18 14:43:48
else { return false; } ?
|
| + } else if (strncmp(token, kMaskEquals, strlen(kMaskEquals)) == 0) { |
| + const char* value = token + strlen(kMaskEquals); |
| + if (!StringToExceptionMask( |
| + value, |
| + kAllowFullName | kAllowShortName | kAllowNumber | kAllowOr, |
| + &description->mask)) { |
| + return false; |
| + } |
| + } else if (strncmp(token, kBehaviorEquals, strlen(kBehaviorEquals)) == 0) { |
| + const char* value = token + strlen(kBehaviorEquals); |
| + if (!StringToExceptionBehavior( |
| + value, |
| + kAllowFullName | kAllowShortName | kAllowNumber, |
| + &description->behavior)) { |
| + return false; |
| + } |
| + } else if (strncmp(token, kFlavorEquals, strlen(kFlavorEquals)) == 0) { |
| + const char* value = token + strlen(kFlavorEquals); |
| + if (!StringToThreadStateFlavor( |
| + value, |
| + kAllowFullName | kAllowShortName | kAllowNumber, |
| + &description->flavor)) { |
| + return false; |
| + } |
| + } else if (strncmp(token, kHandlerEquals, strlen(kHandlerEquals)) == 0) { |
| + const char* value = token + strlen(kHandlerEquals); |
| + if (strcmp(value, kHandlerNull) != 0 && |
| + strncmp(value, |
| + kHandlerBootstrapColon, |
| + strlen(kHandlerBootstrapColon)) != 0) { |
| + return false; |
| + } |
| + description->handler = std::string(value); |
| + } else { |
| + return false; |
| + } |
| + } |
| + |
| + return true; |
| +} |
| + |
| +// ShowExceptionPorts() shows handlers as numeric mach_port_t values, which are |
| +// opaque and meaningless on their own. ShowBootstrapService() can be used to |
| +// look up a service with the bootstrap server by name and show its mach_port_t |
| +// value, which can then be associated with handlers shown by |
| +// ShowExceptionPorts(). |
| +void ShowBootstrapService(const std::string& service_name) { |
| + mach_port_t service_port; |
|
Robert Sesek
2014/09/18 14:43:47
This send right is leaked.
|
| + kern_return_t kr = bootstrap_look_up( |
| + bootstrap_port, const_cast<char*>(service_name.c_str()), &service_port); |
| + if (kr != BOOTSTRAP_SUCCESS) { |
| + BOOTSTRAP_LOG(ERROR, kr) << "bootstrap_look_up " << service_name; |
| + return; |
| + } |
| + |
| + printf("service %s %#x\n", service_name.c_str(), service_port); |
| +} |
| + |
| +// Prints information about all exception ports known for |exception_ports|. If |
| +// |numeric| is true, all information is printed in numeric form, otherwise, it |
| +// will be converted to symbolic constants where possible by |
| +// SymbolicConstantsMach. If |is_new| is true, information will be presented as |
| +// “new exception ports”, indicating that they show the state of the exception |
| +// ports after SetExceptionPort() has been called. |
| +void ShowExceptionPorts(const ExceptionPorts& exception_ports, |
| + bool numeric, |
| + bool is_new) { |
| + const char* target_name = exception_ports.TargetTypeName(); |
| + |
| + std::vector<ExceptionPorts::ExceptionHandler> handlers; |
| + if (!exception_ports.GetExceptionPorts(ExcMaskAll() | EXC_MASK_CRASH, |
| + &handlers)) { |
| + return; |
| + } |
| + |
| + const char* age_name = is_new ? "new " : ""; |
| + |
| + if (handlers.size() == 0) { |
| + printf("no %s%s exception ports\n", age_name, target_name); |
| + } |
| + |
| + for (size_t port_index = 0; port_index < handlers.size(); ++port_index) { |
| + if (numeric) { |
| + printf( |
| + "%s%s exception port %zu, mask %#x, port %#x, " |
| + "behavior %#x, flavor %u\n", |
| + age_name, |
| + target_name, |
| + port_index, |
| + handlers[port_index].mask, |
| + handlers[port_index].port, |
| + handlers[port_index].behavior, |
| + handlers[port_index].flavor); |
| + } else { |
| + std::string mask_string = ExceptionMaskToString( |
| + handlers[port_index].mask, kUseShortName | kUnknownIsEmpty | kUseOr); |
| + if (mask_string.empty()) { |
| + mask_string.assign("?"); |
| + } |
| + |
| + std::string behavior_string = ExceptionBehaviorToString( |
| + handlers[port_index].behavior, kUseShortName | kUnknownIsEmpty); |
| + if (behavior_string.empty()) { |
| + behavior_string.assign("?"); |
| + } |
| + |
| + std::string flavor_string = ThreadStateFlavorToString( |
| + handlers[port_index].flavor, kUseShortName | kUnknownIsEmpty); |
| + if (flavor_string.empty()) { |
| + flavor_string.assign("?"); |
| + } |
| + |
| + printf( |
| + "%s%s exception port %zu, mask %#x (%s), port %#x, " |
| + "behavior %#x (%s), flavor %u (%s)\n", |
| + age_name, |
| + target_name, |
| + port_index, |
| + handlers[port_index].mask, |
| + mask_string.c_str(), |
| + handlers[port_index].port, |
| + handlers[port_index].behavior, |
| + behavior_string.c_str(), |
| + handlers[port_index].flavor, |
| + flavor_string.c_str()); |
| + } |
| + } |
| +} |
| + |
| +// Sets the exception port for |target_port|, a send right to a thread, task, or |
| +// host port, to |description|, which identifies what type of port |target_port| |
| +// is and describes an exception port to be set. Returns true on success. |
| +// |
| +// This function may be called more than once if setting different handlers is |
| +// desired. |
| +bool SetExceptionPort(const ExceptionHandlerDescription* description, |
| + mach_port_t target_port) { |
| + mach_port_t service_port = MACH_PORT_NULL; |
|
Robert Sesek
2014/09/18 14:43:48
Also leaked.
|
| + kern_return_t kr; |
| + if (description->handler.compare( |
| + 0, strlen(kHandlerBootstrapColon), kHandlerBootstrapColon) == 0) { |
| + const char* service_name = |
| + description->handler.c_str() + strlen(kHandlerBootstrapColon); |
| + kr = bootstrap_look_up( |
| + bootstrap_port, const_cast<char*>(service_name), &service_port); |
| + if (kr != BOOTSTRAP_SUCCESS) { |
| + BOOTSTRAP_LOG(ERROR, kr) << "bootstrap_look_up " << service_name; |
| + return false; |
| + } |
| + } else if (description->handler != kHandlerNull) { |
| + return false; |
| + } |
| + |
| + ExceptionPorts exception_ports(description->target_type, target_port); |
| + if (!exception_ports.SetExceptionPort(description->mask, |
| + service_port, |
| + description->behavior, |
| + description->flavor)) { |
| + return false; |
| + } |
| + |
| + return true; |
| +} |
| + |
| +void Usage(const std::string& me) { |
| + fprintf(stderr, |
| +"Usage: %s [OPTION]... [COMMAND [ARG]...]\n" |
| +"View and change Mach exception ports, and run COMMAND if supplied.\n" |
| +"\n" |
| +" -s, --set_handler=DESCRIPTION set an exception port to DESCRIPTION, see below\n" |
| +" --show_bootstrap=SERVICE look up and display a service registered with\n" |
| +" the bootstrap server\n" |
| +" -p, --pid=PID operate on PID instead of the current task\n" |
|
Robert Sesek
2014/09/18 14:43:47
"(requires root)" ?
|
| +" -h, --show_host display original host exception ports\n" |
| +" -t, --show_task display original task exception ports\n" |
| +" --show_thread display original thread exception ports\n" |
| +" -H, --show_new_host display modified host exception ports\n" |
| +" -T, --show_new_task display modified task exception ports\n" |
| +" --show_new_thread display modified thread exception ports\n" |
| +" -n, --numeric display values numerically, not symbolically\n" |
| +" --help display this help and exit\n" |
| +" --version output version information and exit\n" |
| +"\n" |
| +"Any operations on host exception ports require superuser permissions.\n" |
| +"\n" |
| +"DESCRIPTION is formatted as a comma-separated sequence of tokens, where each\n" |
| +"token consists of a key and value separated by an equals sign. Available keys:\n" |
| +" target which target's exception ports to set: host, task, or target\n" |
| +" mask the mask of exception types to handle: CRASH, ALL, or others\n" |
| +" behavior the specific exception handler routine to call: DEFAULT, STATE,\n" |
| +" or STATE_IDENTITY, possibly with MACH_EXCEPTION_CODES.\n" |
| +" flavor the thread state flavor passed to the handler: architecture-specific\n" |
| +" handler the exception handler: NULL or bootstrap:SERVICE, indicating that\n" |
| +" the handler should be looked up with the bootstrap server\n" |
| +"The default DESCRIPTION is\n" |
| +" target=task,mask=CRASH,behavior=DEFAULT|MACH,flavor=NONE,handler=NULL\n", |
| + me.c_str()); |
| + ToolSupport::UsageTail(me); |
| +} |
| + |
| +} // namespace |
| + |
| +int main(int argc, char* argv[]) { |
| + const std::string me(basename(argv[0])); |
| + |
| + enum ExitCode { |
| + kExitSuccess = EXIT_SUCCESS, |
| + |
| + // To differentiate this tool’s errors from errors in the programs it execs, |
| + // use a high exit code for ordinary failures instead of EXIT_FAILURE. This |
| + // is the same rationale for using the distinct exit codes for exec |
| + // failures. |
| + kExitFailure = 125, |
| + |
| + // Like env, use exit code 126 if the program was found but could not be |
| + // invoked, and 127 if it could not be found. |
| + // http://pubs.opengroup.org/onlinepubs/9699919799/utilities/env.html |
| + kExitExecFailure = 126, |
| + kExitExecENOENT = 127, |
| + }; |
| + |
| + enum OptionFlags { |
| + // “Short” (single-character) options. |
| + kOptionSetPort = 's', |
| + kOptionPid = 'p', |
| + kOptionShowHost = 'h', |
| + kOptionShowTask = 't', |
| + kOptionShowNewHost = 'H', |
| + kOptionShowNewTask = 'T', |
| + kOptionNumeric = 'n', |
| + |
| + // Long options without short equivalents. |
| + kOptionLastChar = 255, |
| + kOptionShowBootstrap, |
| + kOptionShowThread, |
| + kOptionShowNewThread, |
| + |
| + // Standard options. |
| + kOptionHelp = -2, |
| + kOptionVersion = -3, |
| + }; |
| + |
| + struct { |
| + std::vector<const char*> show_bootstrap; |
| + std::vector<ExceptionHandlerDescription> set_handler; |
| + int pid; |
|
Robert Sesek
2014/09/18 14:43:47
Not pid_t?
|
| + mach_port_t alternate_task; |
| + bool show_host; |
| + bool show_task; |
| + bool show_thread; |
| + bool show_new_host; |
| + bool show_new_task; |
| + bool show_new_thread; |
| + bool numeric; |
| + } options = {}; |
| + |
| + const struct option long_options[] = { |
| + {"set_handler", required_argument, NULL, kOptionSetPort}, |
| + {"show_bootstrap", required_argument, NULL, kOptionShowBootstrap}, |
| + {"pid", required_argument, NULL, kOptionPid}, |
| + {"show_host", no_argument, NULL, kOptionShowHost}, |
| + {"show_task", no_argument, NULL, kOptionShowTask}, |
| + {"show_thread", no_argument, NULL, kOptionShowThread}, |
| + {"show_new_host", no_argument, NULL, kOptionShowNewHost}, |
| + {"show_new_task", no_argument, NULL, kOptionShowNewTask}, |
| + {"show_new_thread", no_argument, NULL, kOptionShowNewThread}, |
| + {"numeric", no_argument, NULL, kOptionNumeric}, |
| + {"help", no_argument, NULL, kOptionHelp}, |
| + {"version", no_argument, NULL, kOptionVersion}, |
| + {NULL, 0, NULL, 0}, |
| + }; |
| + |
| + int opt; |
| + while ((opt = getopt_long(argc, argv, "+s:p:htHTn", long_options, NULL)) != |
| + -1) { |
| + switch (opt) { |
| + case kOptionSetPort: { |
| + options.set_handler.push_back({}); |
| + ExceptionHandlerDescription* description = &options.set_handler.back(); |
| + description->target_type = ExceptionPorts::kTargetTypeTask; |
| + description->mask = EXC_MASK_CRASH; |
| + description->behavior = EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES; |
| + description->flavor = THREAD_STATE_NONE; |
| + description->handler = "NULL"; |
| + if (!ParseHandlerString(optarg, description)) { |
| + fprintf(stderr, |
| + "%s: invalid exception handler: %s\n", |
| + me.c_str(), |
| + optarg); |
| + return kExitFailure; |
| + } |
| + break; |
| + } |
| + case kOptionShowBootstrap: |
| + options.show_bootstrap.push_back(optarg); |
| + break; |
| + case kOptionPid: |
| + if (!StringToNumber(optarg, &options.pid)) { |
| + fprintf(stderr, "%s: invalid pid: %s\n", me.c_str(), optarg); |
| + return kExitFailure; |
| + } |
| + break; |
| + case kOptionShowHost: |
| + options.show_host = true; |
| + break; |
| + case kOptionShowTask: |
| + options.show_task = true; |
| + break; |
| + case kOptionShowThread: |
| + options.show_thread = true; |
| + break; |
| + case kOptionShowNewHost: |
| + options.show_new_host = true; |
| + break; |
| + case kOptionShowNewTask: |
| + options.show_new_task = true; |
| + break; |
| + case kOptionShowNewThread: |
| + options.show_new_thread = true; |
| + break; |
| + case kOptionNumeric: |
| + options.numeric = true; |
| + break; |
| + case kOptionHelp: |
| + Usage(me); |
| + return kExitSuccess; |
| + case kOptionVersion: |
| + ToolSupport::Version(me); |
| + return kExitSuccess; |
| + default: |
| + ToolSupport::UsageHint(me, NULL); |
| + return kExitFailure; |
| + } |
| + } |
| + argc -= optind; |
| + argv += optind; |
| + |
| + if (options.show_bootstrap.empty() && !options.show_host && |
| + !options.show_task && !options.show_thread && |
| + options.set_handler.empty() && argc == 0) { |
| + ToolSupport::UsageHint(me, "nothing to do"); |
| + return kExitFailure; |
| + } |
| + |
| + base::mac::ScopedMachSendRight alternate_task_owner; |
| + if (options.pid) { |
| + if (argc) { |
| + ToolSupport::UsageHint(me, "cannot combine -p with COMMAND"); |
| + return kExitFailure; |
| + } |
| + |
| + // This is only expected to work as root. |
| + kern_return_t kr = |
| + task_for_pid(mach_task_self(), options.pid, &options.alternate_task); |
| + if (kr != KERN_SUCCESS) { |
| + MACH_LOG(ERROR, kr) << "task_for_pid"; |
| + return kExitFailure; |
| + } |
| + alternate_task_owner.reset(options.alternate_task); |
| + } |
| + |
| + // Show bootstrap services requested. |
| + for (const char* service : options.show_bootstrap) { |
| + ShowBootstrapService(service); |
| + } |
| + |
| + // Show the original exception ports. |
| + if (options.show_host) { |
| + ShowExceptionPorts( |
| + ExceptionPorts(ExceptionPorts::kTargetTypeHost, MACH_PORT_NULL), |
| + options.numeric, |
| + false); |
| + } |
| + if (options.show_task) { |
| + ShowExceptionPorts( |
| + ExceptionPorts(ExceptionPorts::kTargetTypeTask, options.alternate_task), |
| + options.numeric, |
| + false); |
| + } |
| + if (options.show_thread) { |
| + ShowExceptionPorts( |
| + ExceptionPorts(ExceptionPorts::kTargetTypeThread, MACH_PORT_NULL), |
| + options.numeric, |
| + false); |
| + } |
| + |
| + if (!options.set_handler.empty()) { |
| + // Set new exception handlers. |
| + for (ExceptionHandlerDescription description : options.set_handler) { |
| + if (!SetExceptionPort( |
| + &description, |
| + description.target_type == ExceptionPorts::kTargetTypeTask |
| + ? options.alternate_task |
| + : MACH_PORT_NULL)) { |
| + return kExitFailure; |
| + } |
| + } |
| + |
| + // Show changed exception ports. |
| + if (options.show_new_host) { |
| + ShowExceptionPorts( |
| + ExceptionPorts(ExceptionPorts::kTargetTypeHost, MACH_PORT_NULL), |
| + options.numeric, |
| + true); |
| + } |
| + if (options.show_new_task) { |
| + ShowExceptionPorts(ExceptionPorts(ExceptionPorts::kTargetTypeTask, |
| + options.alternate_task), |
| + options.numeric, |
| + true); |
| + } |
| + if (options.show_new_thread) { |
| + ShowExceptionPorts( |
| + ExceptionPorts(ExceptionPorts::kTargetTypeThread, MACH_PORT_NULL), |
| + options.numeric, |
| + true); |
| + } |
| + } |
| + |
| + if (argc) { |
| + // Using the remaining arguments, start a new program with the new set of |
| + // exception ports in effect. |
| + execvp(argv[0], argv); |
| + PLOG(ERROR) << "execvp " << argv[0]; |
| + return errno == ENOENT ? kExitExecENOENT : kExitExecFailure; |
| + } |
| + |
| + return kExitSuccess; |
| +} |