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..728cf3e8f3f02e15bd494d40aebaf3e1ab4714f4 |
--- /dev/null |
+++ b/tools/exception_port_tool.cc |
@@ -0,0 +1,589 @@ |
+// 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; |
+ |
+//! \brief Manages a pool of Mach send rights, deallocating all send rights upon |
+//! destruction. |
+//! |
+//! This class effectively implements what a vector of |
+//! base::mac::ScopedMachSendRight objects would be. |
+//! |
+//! The various “show” operations performed by this program display Mach ports |
+//! by their names as they are known in this task. For this to be useful, rights |
+//! to the same ports must have consistent names across successive calls. This |
+//! cannot be guaranteed if the rights are deallocated as soon as they are used, |
+//! because if that deallocation causes the task to lose its last right to a |
+//! port, subsequently regaining a right to the same port would cause it to be |
+//! known by a new name in this task. |
+//! |
+//! Instead of immediately deallocating send rights that are used for display, |
+//! they can be added to this pool. The pool collects send rights, ensuring that |
+//! they remain alive in this task, and that subsequent calls that obtain the |
+//! same rights cause them to be known by the same name. All rights are |
+//! deallocated upon destruction. |
+class MachSendRightPool { |
+ public: |
+ MachSendRightPool() |
+ : send_rights_() { |
+ } |
+ |
+ ~MachSendRightPool() { |
+ for (mach_port_t send_right : send_rights_) { |
+ kern_return_t kr = mach_port_deallocate(mach_task_self(), send_right); |
+ MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr) << "mach_port_deallocate"; |
+ } |
+ } |
+ |
+ //! \brief Adds a send right to the pool. |
+ //! |
+ //! \param[in] send_right The send right to be added. The pool object takes |
+ //! ownership of the send right, which remains valid until the pool object |
+ //! is destroyed. |
+ //! |
+ //! It is possible and in fact likely that one pool will wind up owning the |
+ //! same send right multiple times. This is acceptable, because send rights |
+ //! are reference-counted. |
+ void AddSendRight(mach_port_t send_right) { |
+ send_rights_.push_back(send_right); |
+ } |
+ |
+ private: |
+ std::vector<mach_port_t> send_rights_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(MachSendRightPool); |
+}; |
+ |
+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; |
+ } 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(). Any send rights obtained by this function are added to |
+// |mach_send_right_pool|. |
+void ShowBootstrapService( |
+ const std::string& service_name, MachSendRightPool* mach_send_right_pool) { |
+ mach_port_t service_port; |
+ 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; |
+ } |
+ |
+ mach_send_right_pool->AddSendRight(service_port); |
+ |
+ 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. Any send rights obtained by |
+// this function are added to |mach_send_right_pool|. |
+void ShowExceptionPorts(const ExceptionPorts& exception_ports, |
+ bool numeric, |
+ bool is_new, |
+ MachSendRightPool* mach_send_right_pool) { |
+ 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) { |
+ mach_send_right_pool->AddSendRight(handlers[port_index].port); |
+ |
+ 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) { |
+ base::mac::ScopedMachSendRight service_port_owner; |
+ mach_port_t service_port = MACH_PORT_NULL; |
+ 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; |
+ } |
+ |
+ // The service port doesn’t need to be added to a MachSendRightPool because |
+ // it’s not used for display at all. ScopedMachSendRight is sufficient. |
+ service_port_owner.reset(service_port); |
+ } 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" |
+" (must be superuser or permitted by taskgated)\n" |
+" -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; |
+ pid_t pid; |
+ 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 or if taskgated approves. |
+ // taskgated does not normally approve. |
+ 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); |
+ } |
+ |
+ MachSendRightPool mach_send_right_pool; |
+ |
+ // Show bootstrap services requested. |
+ for (const char* service : options.show_bootstrap) { |
+ ShowBootstrapService(service, &mach_send_right_pool); |
+ } |
+ |
+ // Show the original exception ports. |
+ if (options.show_host) { |
+ ShowExceptionPorts( |
+ ExceptionPorts(ExceptionPorts::kTargetTypeHost, MACH_PORT_NULL), |
+ options.numeric, |
+ false, |
+ &mach_send_right_pool); |
+ } |
+ if (options.show_task) { |
+ ShowExceptionPorts( |
+ ExceptionPorts(ExceptionPorts::kTargetTypeTask, options.alternate_task), |
+ options.numeric, |
+ false, |
+ &mach_send_right_pool); |
+ } |
+ if (options.show_thread) { |
+ ShowExceptionPorts( |
+ ExceptionPorts(ExceptionPorts::kTargetTypeThread, MACH_PORT_NULL), |
+ options.numeric, |
+ false, |
+ &mach_send_right_pool); |
+ } |
+ |
+ 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, |
+ &mach_send_right_pool); |
+ } |
+ if (options.show_new_task) { |
+ ShowExceptionPorts( |
+ ExceptionPorts(ExceptionPorts::kTargetTypeTask, |
+ options.alternate_task), |
+ options.numeric, |
+ true, |
+ &mach_send_right_pool); |
+ } |
+ if (options.show_new_thread) { |
+ ShowExceptionPorts( |
+ ExceptionPorts(ExceptionPorts::kTargetTypeThread, MACH_PORT_NULL), |
+ options.numeric, |
+ true, |
+ &mach_send_right_pool); |
+ } |
+ } |
+ |
+ 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; |
+} |