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

Unified 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | tools/tools.gyp » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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;
+}
« 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