| OLD | NEW |
| (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 <getopt.h> |
| 16 #include <libgen.h> |
| 17 #include <stdio.h> |
| 18 #include <string.h> |
| 19 #include <unistd.h> |
| 20 |
| 21 #include <algorithm> |
| 22 #include <string> |
| 23 #include <vector> |
| 24 |
| 25 #include "base/files/scoped_file.h" |
| 26 #include "base/logging.h" |
| 27 #include "base/mac/mach_logging.h" |
| 28 #include "tools/tool_support.h" |
| 29 #include "util/mach/exc_server_variants.h" |
| 30 #include "util/mach/exception_behaviors.h" |
| 31 #include "util/mach/exception_types.h" |
| 32 #include "util/mach/mach_extensions.h" |
| 33 #include "util/mach/mach_message.h" |
| 34 #include "util/mach/mach_message_server.h" |
| 35 #include "util/mach/symbolic_constants_mach.h" |
| 36 #include "util/posix/symbolic_constants_posix.h" |
| 37 #include "util/stdlib/string_number_conversion.h" |
| 38 |
| 39 namespace crashpad { |
| 40 namespace { |
| 41 |
| 42 struct Options { |
| 43 std::string file_path; |
| 44 std::string mach_service; |
| 45 FILE* file; |
| 46 int timeout_secs; |
| 47 bool has_timeout; |
| 48 MachMessageServer::Persistent persistent; |
| 49 }; |
| 50 |
| 51 class ExceptionServer : public UniversalMachExcServer::Interface { |
| 52 public: |
| 53 ExceptionServer(const Options& options, |
| 54 const std::string& me, |
| 55 int* exceptions_handled) |
| 56 : UniversalMachExcServer::Interface(), |
| 57 options_(options), |
| 58 me_(me), |
| 59 exceptions_handled_(exceptions_handled) {} |
| 60 |
| 61 // UniversalMachExcServer::Interface: |
| 62 virtual kern_return_t CatchMachException( |
| 63 exception_behavior_t behavior, |
| 64 exception_handler_t exception_port, |
| 65 thread_t thread, |
| 66 task_t task, |
| 67 exception_type_t exception, |
| 68 const mach_exception_data_type_t* code, |
| 69 mach_msg_type_number_t code_count, |
| 70 thread_state_flavor_t* flavor, |
| 71 ConstThreadState old_state, |
| 72 mach_msg_type_number_t old_state_count, |
| 73 thread_state_t new_state, |
| 74 mach_msg_type_number_t* new_state_count, |
| 75 const mach_msg_trailer_t* trailer, |
| 76 bool* destroy_complex_request) override { |
| 77 *destroy_complex_request = true; |
| 78 ++*exceptions_handled_; |
| 79 |
| 80 fprintf(options_.file, |
| 81 "%s: behavior %s", |
| 82 me_.c_str(), |
| 83 ExceptionBehaviorToString( |
| 84 behavior, kUseFullName | kUnknownIsNumeric | kUseOr).c_str()); |
| 85 |
| 86 kern_return_t kr; |
| 87 if (ExceptionBehaviorHasIdentity(behavior)) { |
| 88 // It’s not possible to call pid_for_task() once EXC_CORPSE_NOTIFY has |
| 89 // been generated. It is possible to obtain the process ID by mapping the |
| 90 // corpse kcdata area from the task’s address space at code[0] (size |
| 91 // code[1]) and locating TASK_CRASHINFO_PID within that area. This area |
| 92 // also includes TASK_CRASHINFO_CRASHED_THREADID which could be used |
| 93 // instead of thread_info() below, and TASK_CRASHINFO_EXCEPTION_CODES |
| 94 // which could be used to recover the exception codes passed to the |
| 95 // EXC_CRASH handler. None of this is currently done because corpses are a |
| 96 // new 10.11-only feature. See 10.11 <corpses/task_corpse.h> and |
| 97 // <kern/kern_cdata.h>. |
| 98 if (exception != EXC_CORPSE_NOTIFY) { |
| 99 pid_t pid; |
| 100 kr = pid_for_task(task, &pid); |
| 101 if (kr != KERN_SUCCESS) { |
| 102 fprintf(options_.file, "\n"); |
| 103 fflush(options_.file); |
| 104 MACH_LOG(ERROR, kr) << "pid_for_task"; |
| 105 return KERN_FAILURE; |
| 106 } |
| 107 fprintf(options_.file, ", pid %d", pid); |
| 108 } |
| 109 |
| 110 thread_identifier_info identifier_info; |
| 111 mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT; |
| 112 kr = thread_info(thread, |
| 113 THREAD_IDENTIFIER_INFO, |
| 114 reinterpret_cast<thread_info_t>(&identifier_info), |
| 115 &count); |
| 116 if (kr != KERN_SUCCESS) { |
| 117 fprintf(options_.file, "\n"); |
| 118 fflush(options_.file); |
| 119 MACH_LOG(ERROR, kr) << "thread_info"; |
| 120 return KERN_FAILURE; |
| 121 } |
| 122 fprintf(options_.file, ", thread %lld", identifier_info.thread_id); |
| 123 } |
| 124 |
| 125 fprintf( |
| 126 options_.file, |
| 127 ", exception %s, codes[%d]", |
| 128 ExceptionToString(exception, kUseFullName | kUnknownIsNumeric).c_str(), |
| 129 code_count); |
| 130 |
| 131 for (size_t index = 0; index < code_count; ++index) { |
| 132 fprintf(options_.file, |
| 133 "%s %#llx", |
| 134 index != 0 ? "," : "", |
| 135 code[index]); |
| 136 } |
| 137 |
| 138 if (exception == EXC_CRASH) { |
| 139 mach_exception_code_t original_code_0; |
| 140 int signal; |
| 141 exception_type_t original_exception = |
| 142 ExcCrashRecoverOriginalException(code[0], &original_code_0, &signal); |
| 143 fprintf(options_.file, |
| 144 ", original exception %s, original code[0] %lld, signal %s", |
| 145 ExceptionToString(original_exception, |
| 146 kUseFullName | kUnknownIsNumeric).c_str(), |
| 147 original_code_0, |
| 148 SignalToString(signal, kUseFullName | kUnknownIsNumeric).c_str()); |
| 149 } |
| 150 |
| 151 if (ExceptionBehaviorHasState(behavior)) { |
| 152 std::string flavor_string = |
| 153 ThreadStateFlavorToString(*flavor, kUseFullName | kUnknownIsNumeric); |
| 154 fprintf(options_.file, |
| 155 ", flavor %s, old_state_count %d", |
| 156 flavor_string.c_str(), |
| 157 old_state_count); |
| 158 } |
| 159 |
| 160 fprintf(options_.file, "\n"); |
| 161 fflush(options_.file); |
| 162 |
| 163 if (exception != EXC_CRASH && exception != kMachExceptionSimulated) { |
| 164 // Find another handler. |
| 165 return KERN_FAILURE; |
| 166 } |
| 167 |
| 168 ExcServerCopyState( |
| 169 behavior, old_state, old_state_count, new_state, new_state_count); |
| 170 |
| 171 return ExcServerSuccessfulReturnValue(exception, behavior, false); |
| 172 } |
| 173 |
| 174 private: |
| 175 const Options& options_; |
| 176 const std::string& me_; |
| 177 int* exceptions_handled_; |
| 178 }; |
| 179 |
| 180 void Usage(const std::string& me) { |
| 181 fprintf(stderr, |
| 182 "Usage: %s -m SERVICE [OPTION]...\n" |
| 183 "Catch Mach exceptions and display information about them.\n" |
| 184 "\n" |
| 185 " -f, --file=FILE append information to FILE instead of stdout\n" |
| 186 " -m, --mach-service=SERVICE register SERVICE with the bootstrap server\n" |
| 187 " -p, --persistent continue processing exceptions after the first\n" |
| 188 " -t, --timeout=TIMEOUT run for a maximum of TIMEOUT seconds\n" |
| 189 " --help display this help and exit\n" |
| 190 " --version output version information and exit\n", |
| 191 me.c_str()); |
| 192 ToolSupport::UsageTail(me); |
| 193 } |
| 194 |
| 195 int CatchExceptionToolMain(int argc, char* argv[]) { |
| 196 const std::string me(basename(argv[0])); |
| 197 |
| 198 enum OptionFlags { |
| 199 // “Short” (single-character) options. |
| 200 kOptionFile = 'f', |
| 201 kOptionMachService = 'm', |
| 202 kOptionPersistent = 'p', |
| 203 kOptionTimeout = 't', |
| 204 |
| 205 // Long options without short equivalents. |
| 206 kOptionLastChar = 255, |
| 207 |
| 208 // Standard options. |
| 209 kOptionHelp = -2, |
| 210 kOptionVersion = -3, |
| 211 }; |
| 212 |
| 213 Options options = {}; |
| 214 |
| 215 const option long_options[] = { |
| 216 {"file", required_argument, nullptr, kOptionFile}, |
| 217 {"mach-service", required_argument, nullptr, kOptionMachService}, |
| 218 {"persistent", no_argument, nullptr, kOptionPersistent}, |
| 219 {"timeout", required_argument, nullptr, kOptionTimeout}, |
| 220 {"help", no_argument, nullptr, kOptionHelp}, |
| 221 {"version", no_argument, nullptr, kOptionVersion}, |
| 222 {nullptr, 0, nullptr, 0}, |
| 223 }; |
| 224 |
| 225 int opt; |
| 226 while ((opt = getopt_long(argc, argv, "f:m:pt:", long_options, nullptr)) != |
| 227 -1) { |
| 228 switch (opt) { |
| 229 case kOptionFile: |
| 230 options.file_path = optarg; |
| 231 break; |
| 232 case kOptionMachService: |
| 233 options.mach_service = optarg; |
| 234 break; |
| 235 case kOptionPersistent: |
| 236 options.persistent = MachMessageServer::kPersistent; |
| 237 break; |
| 238 case kOptionTimeout: |
| 239 if (!StringToNumber(optarg, &options.timeout_secs) || |
| 240 options.timeout_secs < 0) { |
| 241 ToolSupport::UsageHint(me, "-t requires a zero or positive TIMEOUT"); |
| 242 return EXIT_FAILURE; |
| 243 } |
| 244 options.has_timeout = true; |
| 245 break; |
| 246 case kOptionHelp: |
| 247 Usage(me); |
| 248 return EXIT_SUCCESS; |
| 249 case kOptionVersion: |
| 250 ToolSupport::Version(me); |
| 251 return EXIT_SUCCESS; |
| 252 default: |
| 253 ToolSupport::UsageHint(me, nullptr); |
| 254 return EXIT_FAILURE; |
| 255 } |
| 256 } |
| 257 argc -= optind; |
| 258 argv += optind; |
| 259 |
| 260 if (options.mach_service.empty()) { |
| 261 ToolSupport::UsageHint(me, "-m is required"); |
| 262 return EXIT_FAILURE; |
| 263 } |
| 264 |
| 265 base::mac::ScopedMachReceiveRight |
| 266 service_port(BootstrapCheckIn(options.mach_service)); |
| 267 if (service_port == kMachPortNull) { |
| 268 return EXIT_FAILURE; |
| 269 } |
| 270 |
| 271 base::ScopedFILE file_owner; |
| 272 if (options.file_path.empty()) { |
| 273 options.file = stdout; |
| 274 } else { |
| 275 file_owner.reset(fopen(options.file_path.c_str(), "a")); |
| 276 if (!file_owner.get()) { |
| 277 PLOG(ERROR) << "fopen " << options.file_path; |
| 278 return EXIT_FAILURE; |
| 279 } |
| 280 options.file = file_owner.get(); |
| 281 } |
| 282 |
| 283 int exceptions_handled = 0; |
| 284 ExceptionServer exception_server(options, me, &exceptions_handled); |
| 285 UniversalMachExcServer universal_mach_exc_server(&exception_server); |
| 286 |
| 287 // Assume that if persistent mode has been requested, it’s desirable to ignore |
| 288 // large messages and keep running. |
| 289 MachMessageServer::ReceiveLarge receive_large = |
| 290 (options.persistent == MachMessageServer::kPersistent) |
| 291 ? MachMessageServer::kReceiveLargeIgnore |
| 292 : MachMessageServer::kReceiveLargeError; |
| 293 |
| 294 mach_msg_timeout_t timeout_ms; |
| 295 if (!options.has_timeout) { |
| 296 timeout_ms = kMachMessageTimeoutWaitIndefinitely; |
| 297 } else if (options.timeout_secs == 0) { |
| 298 timeout_ms = kMachMessageTimeoutNonblocking; |
| 299 } else { |
| 300 timeout_ms = options.timeout_secs * 1000; |
| 301 } |
| 302 |
| 303 mach_msg_return_t mr = MachMessageServer::Run(&universal_mach_exc_server, |
| 304 service_port.get(), |
| 305 MACH_MSG_OPTION_NONE, |
| 306 options.persistent, |
| 307 receive_large, |
| 308 timeout_ms); |
| 309 if (mr == MACH_RCV_TIMED_OUT && options.has_timeout && options.persistent && |
| 310 exceptions_handled) { |
| 311 // This is not an error: when exiting on timeout during persistent |
| 312 // processing and at least one exception was handled, it’s considered a |
| 313 // success. |
| 314 } else if (mr != MACH_MSG_SUCCESS) { |
| 315 MACH_LOG(ERROR, mr) << "MachMessageServer::Run"; |
| 316 return EXIT_FAILURE; |
| 317 } |
| 318 |
| 319 return EXIT_SUCCESS; |
| 320 } |
| 321 |
| 322 } // namespace |
| 323 } // namespace crashpad |
| 324 |
| 325 int main(int argc, char* argv[]) { |
| 326 return crashpad::CatchExceptionToolMain(argc, argv); |
| 327 } |
| OLD | NEW |