mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-27 15:32:10 +08:00
281be63d00
This uses “static” at function scope to avoid making local copies, even in cases where the compiler can’t see that the local copy is unnecessary. “constexpr” adds additional safety in that it prevents global state from being initialized from any runtime dependencies, which would be undesirable. At namespace scope, “constexpr” is also used where appropriate. For the most part, this was a mechanical transformation for things matching '(^| )const [^=]*\['. Similar transformations could be applied to non-arrays in some cases, but there’s limited practical impact in most non-array cases relative to arrays, there are far more use sites, and much more manual intervention would be required. Change-Id: I3513b739ee8b0be026f8285475cddc5f9cc81152 Reviewed-on: https://chromium-review.googlesource.com/583997 Commit-Queue: Mark Mentovai <mark@chromium.org> Reviewed-by: Leonard Mosescu <mosescu@chromium.org>
334 lines
11 KiB
C++
334 lines
11 KiB
C++
// 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 <fcntl.h>
|
||
#include <getopt.h>
|
||
#include <libgen.h>
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include <sys/types.h>
|
||
#include <unistd.h>
|
||
|
||
#include <algorithm>
|
||
#include <string>
|
||
#include <vector>
|
||
|
||
#include "base/files/scoped_file.h"
|
||
#include "base/logging.h"
|
||
#include "base/mac/mach_logging.h"
|
||
#include "tools/tool_support.h"
|
||
#include "util/mach/exc_server_variants.h"
|
||
#include "util/mach/exception_behaviors.h"
|
||
#include "util/mach/exception_types.h"
|
||
#include "util/mach/mach_extensions.h"
|
||
#include "util/mach/mach_message.h"
|
||
#include "util/mach/mach_message_server.h"
|
||
#include "util/mach/symbolic_constants_mach.h"
|
||
#include "util/posix/symbolic_constants_posix.h"
|
||
#include "util/stdlib/string_number_conversion.h"
|
||
|
||
namespace crashpad {
|
||
namespace {
|
||
|
||
struct Options {
|
||
std::string file_path;
|
||
std::string mach_service;
|
||
FILE* file;
|
||
int timeout_secs;
|
||
bool has_timeout;
|
||
MachMessageServer::Persistent persistent;
|
||
};
|
||
|
||
class ExceptionServer : public UniversalMachExcServer::Interface {
|
||
public:
|
||
ExceptionServer(const Options& options,
|
||
const std::string& me,
|
||
int* exceptions_handled)
|
||
: UniversalMachExcServer::Interface(),
|
||
options_(options),
|
||
me_(me),
|
||
exceptions_handled_(exceptions_handled) {}
|
||
|
||
// UniversalMachExcServer::Interface:
|
||
virtual kern_return_t CatchMachException(
|
||
exception_behavior_t behavior,
|
||
exception_handler_t exception_port,
|
||
thread_t thread,
|
||
task_t task,
|
||
exception_type_t exception,
|
||
const mach_exception_data_type_t* code,
|
||
mach_msg_type_number_t code_count,
|
||
thread_state_flavor_t* flavor,
|
||
ConstThreadState old_state,
|
||
mach_msg_type_number_t old_state_count,
|
||
thread_state_t new_state,
|
||
mach_msg_type_number_t* new_state_count,
|
||
const mach_msg_trailer_t* trailer,
|
||
bool* destroy_complex_request) override {
|
||
*destroy_complex_request = true;
|
||
++*exceptions_handled_;
|
||
|
||
fprintf(options_.file,
|
||
"%s: behavior %s",
|
||
me_.c_str(),
|
||
ExceptionBehaviorToString(
|
||
behavior, kUseFullName | kUnknownIsNumeric | kUseOr).c_str());
|
||
|
||
kern_return_t kr;
|
||
if (ExceptionBehaviorHasIdentity(behavior)) {
|
||
// It’s not possible to call pid_for_task() once EXC_CORPSE_NOTIFY has
|
||
// been generated. It is possible to obtain the process ID by mapping the
|
||
// corpse kcdata area from the task’s address space at code[0] (size
|
||
// code[1]) and locating TASK_CRASHINFO_PID within that area. This area
|
||
// also includes TASK_CRASHINFO_CRASHED_THREADID which could be used
|
||
// instead of thread_info() below, and TASK_CRASHINFO_EXCEPTION_CODES
|
||
// which could be used to recover the exception codes passed to the
|
||
// EXC_CRASH handler. None of this is currently done because corpses are a
|
||
// new 10.11-only feature. See 10.11 <corpses/task_corpse.h> and
|
||
// <kern/kern_cdata.h>.
|
||
if (exception != EXC_CORPSE_NOTIFY) {
|
||
pid_t pid;
|
||
kr = pid_for_task(task, &pid);
|
||
if (kr != KERN_SUCCESS) {
|
||
fprintf(options_.file, "\n");
|
||
fflush(options_.file);
|
||
MACH_LOG(ERROR, kr) << "pid_for_task";
|
||
return KERN_FAILURE;
|
||
}
|
||
fprintf(options_.file, ", pid %d", pid);
|
||
}
|
||
|
||
thread_identifier_info identifier_info;
|
||
mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT;
|
||
kr = thread_info(thread,
|
||
THREAD_IDENTIFIER_INFO,
|
||
reinterpret_cast<thread_info_t>(&identifier_info),
|
||
&count);
|
||
if (kr != KERN_SUCCESS) {
|
||
fprintf(options_.file, "\n");
|
||
fflush(options_.file);
|
||
MACH_LOG(ERROR, kr) << "thread_info";
|
||
return KERN_FAILURE;
|
||
}
|
||
fprintf(options_.file, ", thread %lld", identifier_info.thread_id);
|
||
}
|
||
|
||
fprintf(
|
||
options_.file,
|
||
", exception %s, codes[%d]",
|
||
ExceptionToString(exception, kUseFullName | kUnknownIsNumeric).c_str(),
|
||
code_count);
|
||
|
||
for (size_t index = 0; index < code_count; ++index) {
|
||
fprintf(options_.file,
|
||
"%s %#llx",
|
||
index != 0 ? "," : "",
|
||
code[index]);
|
||
}
|
||
|
||
if (exception == EXC_CRASH) {
|
||
mach_exception_code_t original_code_0;
|
||
int signal;
|
||
exception_type_t original_exception =
|
||
ExcCrashRecoverOriginalException(code[0], &original_code_0, &signal);
|
||
fprintf(options_.file,
|
||
", original exception %s, original code[0] %lld, signal %s",
|
||
ExceptionToString(original_exception,
|
||
kUseFullName | kUnknownIsNumeric).c_str(),
|
||
original_code_0,
|
||
SignalToString(signal, kUseFullName | kUnknownIsNumeric).c_str());
|
||
}
|
||
|
||
if (ExceptionBehaviorHasState(behavior)) {
|
||
std::string flavor_string =
|
||
ThreadStateFlavorToString(*flavor, kUseFullName | kUnknownIsNumeric);
|
||
fprintf(options_.file,
|
||
", flavor %s, old_state_count %d",
|
||
flavor_string.c_str(),
|
||
old_state_count);
|
||
}
|
||
|
||
fprintf(options_.file, "\n");
|
||
fflush(options_.file);
|
||
|
||
if (exception != EXC_CRASH && exception != kMachExceptionSimulated) {
|
||
// Find another handler.
|
||
return KERN_FAILURE;
|
||
}
|
||
|
||
ExcServerCopyState(
|
||
behavior, old_state, old_state_count, new_state, new_state_count);
|
||
|
||
return ExcServerSuccessfulReturnValue(exception, behavior, false);
|
||
}
|
||
|
||
private:
|
||
const Options& options_;
|
||
const std::string& me_;
|
||
int* exceptions_handled_;
|
||
};
|
||
|
||
void Usage(const std::string& me) {
|
||
fprintf(stderr,
|
||
"Usage: %s -m SERVICE [OPTION]...\n"
|
||
"Catch Mach exceptions and display information about them.\n"
|
||
"\n"
|
||
" -f, --file=FILE append information to FILE instead of stdout\n"
|
||
" -m, --mach-service=SERVICE register SERVICE with the bootstrap server\n"
|
||
" -p, --persistent continue processing exceptions after the first\n"
|
||
" -t, --timeout=TIMEOUT run for a maximum of TIMEOUT seconds\n"
|
||
" --help display this help and exit\n"
|
||
" --version output version information and exit\n",
|
||
me.c_str());
|
||
ToolSupport::UsageTail(me);
|
||
}
|
||
|
||
int CatchExceptionToolMain(int argc, char* argv[]) {
|
||
const std::string me(basename(argv[0]));
|
||
|
||
enum OptionFlags {
|
||
// “Short” (single-character) options.
|
||
kOptionFile = 'f',
|
||
kOptionMachService = 'm',
|
||
kOptionPersistent = 'p',
|
||
kOptionTimeout = 't',
|
||
|
||
// Long options without short equivalents.
|
||
kOptionLastChar = 255,
|
||
|
||
// Standard options.
|
||
kOptionHelp = -2,
|
||
kOptionVersion = -3,
|
||
};
|
||
|
||
Options options = {};
|
||
|
||
static constexpr option long_options[] = {
|
||
{"file", required_argument, nullptr, kOptionFile},
|
||
{"mach-service", required_argument, nullptr, kOptionMachService},
|
||
{"persistent", no_argument, nullptr, kOptionPersistent},
|
||
{"timeout", required_argument, nullptr, kOptionTimeout},
|
||
{"help", no_argument, nullptr, kOptionHelp},
|
||
{"version", no_argument, nullptr, kOptionVersion},
|
||
{nullptr, 0, nullptr, 0},
|
||
};
|
||
|
||
int opt;
|
||
while ((opt = getopt_long(argc, argv, "f:m:pt:", long_options, nullptr)) !=
|
||
-1) {
|
||
switch (opt) {
|
||
case kOptionFile:
|
||
options.file_path = optarg;
|
||
break;
|
||
case kOptionMachService:
|
||
options.mach_service = optarg;
|
||
break;
|
||
case kOptionPersistent:
|
||
options.persistent = MachMessageServer::kPersistent;
|
||
break;
|
||
case kOptionTimeout:
|
||
if (!StringToNumber(optarg, &options.timeout_secs) ||
|
||
options.timeout_secs < 0) {
|
||
ToolSupport::UsageHint(me, "-t requires a zero or positive TIMEOUT");
|
||
return EXIT_FAILURE;
|
||
}
|
||
options.has_timeout = true;
|
||
break;
|
||
case kOptionHelp:
|
||
Usage(me);
|
||
return EXIT_SUCCESS;
|
||
case kOptionVersion:
|
||
ToolSupport::Version(me);
|
||
return EXIT_SUCCESS;
|
||
default:
|
||
ToolSupport::UsageHint(me, nullptr);
|
||
return EXIT_FAILURE;
|
||
}
|
||
}
|
||
argc -= optind;
|
||
argv += optind;
|
||
|
||
if (options.mach_service.empty()) {
|
||
ToolSupport::UsageHint(me, "-m is required");
|
||
return EXIT_FAILURE;
|
||
}
|
||
|
||
base::mac::ScopedMachReceiveRight
|
||
service_port(BootstrapCheckIn(options.mach_service));
|
||
if (service_port == kMachPortNull) {
|
||
return EXIT_FAILURE;
|
||
}
|
||
|
||
base::ScopedFILE file_owner;
|
||
if (options.file_path.empty()) {
|
||
options.file = stdout;
|
||
} else {
|
||
file_owner.reset(fopen(options.file_path.c_str(), "a"));
|
||
if (!file_owner.get()) {
|
||
PLOG(ERROR) << "fopen " << options.file_path;
|
||
return EXIT_FAILURE;
|
||
}
|
||
options.file = file_owner.get();
|
||
if (fcntl(fileno(options.file), F_SETFD, FD_CLOEXEC) == -1) {
|
||
PLOG(ERROR) << "fcntl " << options.file_path;
|
||
return EXIT_FAILURE;
|
||
}
|
||
}
|
||
|
||
int exceptions_handled = 0;
|
||
ExceptionServer exception_server(options, me, &exceptions_handled);
|
||
UniversalMachExcServer universal_mach_exc_server(&exception_server);
|
||
|
||
// Assume that if persistent mode has been requested, it’s desirable to ignore
|
||
// large messages and keep running.
|
||
MachMessageServer::ReceiveLarge receive_large =
|
||
(options.persistent == MachMessageServer::kPersistent)
|
||
? MachMessageServer::kReceiveLargeIgnore
|
||
: MachMessageServer::kReceiveLargeError;
|
||
|
||
mach_msg_timeout_t timeout_ms;
|
||
if (!options.has_timeout) {
|
||
timeout_ms = kMachMessageTimeoutWaitIndefinitely;
|
||
} else if (options.timeout_secs == 0) {
|
||
timeout_ms = kMachMessageTimeoutNonblocking;
|
||
} else {
|
||
timeout_ms = options.timeout_secs * 1000;
|
||
}
|
||
|
||
mach_msg_return_t mr = MachMessageServer::Run(&universal_mach_exc_server,
|
||
service_port.get(),
|
||
MACH_MSG_OPTION_NONE,
|
||
options.persistent,
|
||
receive_large,
|
||
timeout_ms);
|
||
if (mr == MACH_RCV_TIMED_OUT && options.has_timeout && options.persistent &&
|
||
exceptions_handled) {
|
||
// This is not an error: when exiting on timeout during persistent
|
||
// processing and at least one exception was handled, it’s considered a
|
||
// success.
|
||
} else if (mr != MACH_MSG_SUCCESS) {
|
||
MACH_LOG(ERROR, mr) << "MachMessageServer::Run";
|
||
return EXIT_FAILURE;
|
||
}
|
||
|
||
return EXIT_SUCCESS;
|
||
}
|
||
|
||
} // namespace
|
||
} // namespace crashpad
|
||
|
||
int main(int argc, char* argv[]) {
|
||
return crashpad::CatchExceptionToolMain(argc, argv);
|
||
}
|