mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-25 06:19:57 +00:00
Merge master 6da9708e7cc9 into doc
This commit is contained in:
commit
9bb349b19d
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,5 +14,6 @@
|
|||||||
/third_party/gyp/gyp
|
/third_party/gyp/gyp
|
||||||
/third_party/llvm
|
/third_party/llvm
|
||||||
/third_party/mini_chromium/mini_chromium
|
/third_party/mini_chromium/mini_chromium
|
||||||
|
/third_party/zlib/zlib
|
||||||
/xcodebuild
|
/xcodebuild
|
||||||
tags
|
tags
|
||||||
|
3
DEPS
3
DEPS
@ -39,6 +39,9 @@ deps = {
|
|||||||
'crashpad/third_party/mini_chromium/mini_chromium':
|
'crashpad/third_party/mini_chromium/mini_chromium':
|
||||||
Var('chromium_git') + '/chromium/mini_chromium@' +
|
Var('chromium_git') + '/chromium/mini_chromium@' +
|
||||||
'f65519e442d23498937251e680a3b113927613b0',
|
'f65519e442d23498937251e680a3b113927613b0',
|
||||||
|
'crashpad/third_party/zlib/zlib':
|
||||||
|
Var('chromium_git') + '/chromium/src/third_party/zlib@' +
|
||||||
|
'13dc246a58e4b72104d35f9b1809af95221ebda7',
|
||||||
}
|
}
|
||||||
|
|
||||||
hooks = [
|
hooks = [
|
||||||
|
@ -13,16 +13,12 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
{
|
{
|
||||||
# Crashpad can obtain dependencies in three different ways, directed by the
|
# Crashpad’s GYP build can obtain dependencies in two different ways, directed
|
||||||
# crashpad_standalone GYP variable. It may have these values:
|
# by the crashpad_standalone GYP variable. It may have these values:
|
||||||
# standalone
|
# standalone
|
||||||
# A “standalone” Crashpad build, where the dependencies are in the
|
# A “standalone” Crashpad build, where the dependencies are in the
|
||||||
# Crashpad tree. third_party/mini_chromium and third_party/gtest provide
|
# Crashpad tree. third_party/mini_chromium and third_party/gtest provide
|
||||||
# the base and gtest libraries.
|
# the base and gtest libraries.
|
||||||
# chromium
|
|
||||||
# An in-Chromium build, where Crashpad is within the Chromium tree.
|
|
||||||
# Chromium provides its own base library and its copy of the gtest
|
|
||||||
# library.
|
|
||||||
# external
|
# external
|
||||||
# A build with external dependencies. mini_chromium provides the base
|
# A build with external dependencies. mini_chromium provides the base
|
||||||
# library, but it’s located outside of the Crashpad tree, as is gtest.
|
# library, but it’s located outside of the Crashpad tree, as is gtest.
|
||||||
@ -30,13 +26,15 @@
|
|||||||
# In order for Crashpad’s .gyp files to reference the correct versions
|
# In order for Crashpad’s .gyp files to reference the correct versions
|
||||||
# depending on how dependencies are being provided, include this .gypi file
|
# depending on how dependencies are being provided, include this .gypi file
|
||||||
# and reference the crashpad_dependencies variable.
|
# and reference the crashpad_dependencies variable.
|
||||||
|
#
|
||||||
|
# Note that Crashpad’s in-Chromium build uses GN instead of GYP, and
|
||||||
|
# Chromium’s GN build configures Crashpad to use Chromium’s own base library
|
||||||
|
# and its copy of the gtest library.
|
||||||
|
|
||||||
'variables': {
|
'variables': {
|
||||||
# When building as a standalone project or with external dependencies,
|
# When with external dependencies, build/gyp_crashpad.py sets
|
||||||
# build/gyp_crashpad.py sets crashpad_dependencies to "standalone" or
|
# crashpad_dependencies to "external", and this % assignment will not
|
||||||
# "external", and this % assignment will not override it. The variable will
|
# override it.
|
||||||
# not be set by anything else when building as part of Chromium, so in that
|
'crashpad_dependencies%': 'standalone',
|
||||||
# case, this will define it with value "chromium".
|
|
||||||
'crashpad_dependencies%': 'chromium',
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -31,12 +31,12 @@ def ChooseDependencyPath(local_path, external_path):
|
|||||||
external_path: The external path to fall back to.
|
external_path: The external path to fall back to.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A 2-tuple. The first element is 'standalone' or 'external', depending on
|
A 2-tuple. The first element is None or 'external', depending on whether
|
||||||
whether local_path or external_path was chosen. The second element is the
|
local_path or external_path was chosen. The second element is the chosen
|
||||||
chosen path.
|
path.
|
||||||
"""
|
"""
|
||||||
if os.path.exists(local_path) or not os.path.exists(external_path):
|
if os.path.exists(local_path) or not os.path.exists(external_path):
|
||||||
return ('standalone', local_path)
|
return (None, local_path)
|
||||||
return ('external', external_path)
|
return ('external', external_path)
|
||||||
|
|
||||||
|
|
||||||
@ -64,7 +64,8 @@ def main(args):
|
|||||||
'mini_chromium', 'build', 'common.gypi'),
|
'mini_chromium', 'build', 'common.gypi'),
|
||||||
os.path.join(crashpad_dir, os.pardir, os.pardir, 'mini_chromium',
|
os.path.join(crashpad_dir, os.pardir, os.pardir, 'mini_chromium',
|
||||||
'mini_chromium', 'build', 'common.gypi')))
|
'mini_chromium', 'build', 'common.gypi')))
|
||||||
args.extend(['-D', 'crashpad_dependencies=%s' % dependencies])
|
if dependencies is not None:
|
||||||
|
args.extend(['-D', 'crashpad_dependencies=%s' % dependencies])
|
||||||
args.extend(['--include', mini_chromium_dir])
|
args.extend(['--include', mini_chromium_dir])
|
||||||
args.extend(['--depth', crashpad_dir_or_dot])
|
args.extend(['--depth', crashpad_dir_or_dot])
|
||||||
args.append(os.path.join(crashpad_dir, 'crashpad.gyp'))
|
args.append(os.path.join(crashpad_dir, 'crashpad.gyp'))
|
||||||
|
@ -139,7 +139,8 @@ class CallRecordUploadAttempt {
|
|||||||
|
|
||||||
CrashReportUploadThread::CrashReportUploadThread(CrashReportDatabase* database,
|
CrashReportUploadThread::CrashReportUploadThread(CrashReportDatabase* database,
|
||||||
const std::string& url,
|
const std::string& url,
|
||||||
bool rate_limit)
|
bool rate_limit,
|
||||||
|
bool upload_gzip)
|
||||||
: url_(url),
|
: url_(url),
|
||||||
// Check for pending reports every 15 minutes, even in the absence of a
|
// Check for pending reports every 15 minutes, even in the absence of a
|
||||||
// signal from the handler thread. This allows for failed uploads to be
|
// signal from the handler thread. This allows for failed uploads to be
|
||||||
@ -147,7 +148,8 @@ CrashReportUploadThread::CrashReportUploadThread(CrashReportDatabase* database,
|
|||||||
// processes to be recognized.
|
// processes to be recognized.
|
||||||
thread_(15 * 60, this),
|
thread_(15 * 60, this),
|
||||||
database_(database),
|
database_(database),
|
||||||
rate_limit_(rate_limit) {
|
rate_limit_(rate_limit),
|
||||||
|
upload_gzip_(upload_gzip) {
|
||||||
}
|
}
|
||||||
|
|
||||||
CrashReportUploadThread::~CrashReportUploadThread() {
|
CrashReportUploadThread::~CrashReportUploadThread() {
|
||||||
@ -308,6 +310,7 @@ CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport(
|
|||||||
}
|
}
|
||||||
|
|
||||||
HTTPMultipartBuilder http_multipart_builder;
|
HTTPMultipartBuilder http_multipart_builder;
|
||||||
|
http_multipart_builder.SetGzipEnabled(upload_gzip_);
|
||||||
|
|
||||||
const char kMinidumpKey[] = "upload_file_minidump";
|
const char kMinidumpKey[] = "upload_file_minidump";
|
||||||
|
|
||||||
@ -332,9 +335,11 @@ CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport(
|
|||||||
|
|
||||||
std::unique_ptr<HTTPTransport> http_transport(HTTPTransport::Create());
|
std::unique_ptr<HTTPTransport> http_transport(HTTPTransport::Create());
|
||||||
http_transport->SetURL(url_);
|
http_transport->SetURL(url_);
|
||||||
HTTPHeaders::value_type content_type =
|
HTTPHeaders content_headers;
|
||||||
http_multipart_builder.GetContentType();
|
http_multipart_builder.PopulateContentHeaders(&content_headers);
|
||||||
http_transport->SetHeader(content_type.first, content_type.second);
|
for (const auto& content_header : content_headers) {
|
||||||
|
http_transport->SetHeader(content_header.first, content_header.second);
|
||||||
|
}
|
||||||
http_transport->SetBodyStream(http_multipart_builder.GetBodyStream());
|
http_transport->SetBodyStream(http_multipart_builder.GetBodyStream());
|
||||||
// TODO(mark): The timeout should be configurable by the client.
|
// TODO(mark): The timeout should be configurable by the client.
|
||||||
http_transport->SetTimeout(60.0); // 1 minute.
|
http_transport->SetTimeout(60.0); // 1 minute.
|
||||||
|
@ -45,9 +45,11 @@ class CrashReportUploadThread : public WorkerThread::Delegate {
|
|||||||
//! \param[in] url The URL of the server to upload crash reports to.
|
//! \param[in] url The URL of the server to upload crash reports to.
|
||||||
//! \param[in] rate_limit Whether uploads should be throttled to a (currently
|
//! \param[in] rate_limit Whether uploads should be throttled to a (currently
|
||||||
//! hardcoded) rate.
|
//! hardcoded) rate.
|
||||||
|
//! \param[in] upload_gzip Whether uploads should use `gzip` compression.
|
||||||
CrashReportUploadThread(CrashReportDatabase* database,
|
CrashReportUploadThread(CrashReportDatabase* database,
|
||||||
const std::string& url,
|
const std::string& url,
|
||||||
bool rate_limit);
|
bool rate_limit,
|
||||||
|
bool upload_gzip);
|
||||||
~CrashReportUploadThread();
|
~CrashReportUploadThread();
|
||||||
|
|
||||||
//! \brief Starts a dedicated upload thread, which executes ThreadMain().
|
//! \brief Starts a dedicated upload thread, which executes ThreadMain().
|
||||||
@ -139,6 +141,7 @@ class CrashReportUploadThread : public WorkerThread::Delegate {
|
|||||||
WorkerThread thread_;
|
WorkerThread thread_;
|
||||||
CrashReportDatabase* database_; // weak
|
CrashReportDatabase* database_; // weak
|
||||||
bool rate_limit_;
|
bool rate_limit_;
|
||||||
|
bool upload_gzip_;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(CrashReportUploadThread);
|
DISALLOW_COPY_AND_ASSIGN(CrashReportUploadThread);
|
||||||
};
|
};
|
||||||
|
@ -144,6 +144,14 @@ establish the Crashpad client environment before running a program.
|
|||||||
throttled to one per hour. Using this option disables that behavior, and
|
throttled to one per hour. Using this option disables that behavior, and
|
||||||
Crashpad will attempt to upload all captured reports.
|
Crashpad will attempt to upload all captured reports.
|
||||||
|
|
||||||
|
* **--no-upload-gzip**
|
||||||
|
|
||||||
|
Do not use `gzip` compression for uploaded crash reports. Normally, the
|
||||||
|
entire request body is compressed into a `gzip` stream and transmitted with
|
||||||
|
`Content-Encoding: gzip`. This option disables compression, and is intended
|
||||||
|
for use with collection servers that don’t accept uploads compressed in this
|
||||||
|
way.
|
||||||
|
|
||||||
* **--pipe-name**=_PIPE_
|
* **--pipe-name**=_PIPE_
|
||||||
|
|
||||||
Listen on the given pipe name for connections from clients. _PIPE_ must be of
|
Listen on the given pipe name for connections from clients. _PIPE_ must be of
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
{
|
{
|
||||||
'includes': [
|
'includes': [
|
||||||
'../build/crashpad.gypi',
|
'../build/crashpad.gypi',
|
||||||
'../build/crashpad_dependencies.gypi',
|
|
||||||
],
|
],
|
||||||
'targets': [
|
'targets': [
|
||||||
{
|
{
|
||||||
@ -66,29 +65,6 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
'conditions': [
|
'conditions': [
|
||||||
['OS=="mac"', {
|
|
||||||
# In an in-Chromium build with component=shared_library,
|
|
||||||
# crashpad_handler will depend on shared libraries such as
|
|
||||||
# libbase.dylib located in out/{Debug,Release} via the @rpath
|
|
||||||
# mechanism. When crashpad_handler is copied to its home deep inside
|
|
||||||
# the Chromium app bundle, it needs to have an LC_RPATH command
|
|
||||||
# pointing back to the directory containing these dependency
|
|
||||||
# libraries.
|
|
||||||
'variables': {
|
|
||||||
'component%': 'static_library',
|
|
||||||
},
|
|
||||||
'conditions': [
|
|
||||||
['crashpad_dependencies=="chromium" and component=="shared_library"', {
|
|
||||||
'xcode_settings': {
|
|
||||||
'LD_RUNPATH_SEARCH_PATHS': [ # -Wl,-rpath
|
|
||||||
# Get back from
|
|
||||||
# Chromium.app/Contents/Versions/V/Framework.framework/Helpers
|
|
||||||
'@loader_path/../../../../../..',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
],
|
|
||||||
}],
|
|
||||||
['OS=="win"', {
|
['OS=="win"', {
|
||||||
'msvs_settings': {
|
'msvs_settings': {
|
||||||
'VCLinkerTool': {
|
'VCLinkerTool': {
|
||||||
|
@ -24,8 +24,10 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "base/auto_reset.h"
|
#include "base/auto_reset.h"
|
||||||
|
#include "base/compiler_specific.h"
|
||||||
#include "base/files/file_path.h"
|
#include "base/files/file_path.h"
|
||||||
#include "base/files/scoped_file.h"
|
#include "base/files/scoped_file.h"
|
||||||
#include "base/logging.h"
|
#include "base/logging.h"
|
||||||
@ -64,6 +66,7 @@
|
|||||||
#include "util/win/exception_handler_server.h"
|
#include "util/win/exception_handler_server.h"
|
||||||
#include "util/win/handle.h"
|
#include "util/win/handle.h"
|
||||||
#include "util/win/initial_client_data.h"
|
#include "util/win/initial_client_data.h"
|
||||||
|
#include "util/win/session_end_watcher.h"
|
||||||
#endif // OS_MACOSX
|
#endif // OS_MACOSX
|
||||||
|
|
||||||
namespace crashpad {
|
namespace crashpad {
|
||||||
@ -93,6 +96,7 @@ void Usage(const base::FilePath& me) {
|
|||||||
#endif // OS_MACOSX
|
#endif // OS_MACOSX
|
||||||
" --metrics-dir=DIR store metrics files in DIR (only in Chromium)\n"
|
" --metrics-dir=DIR store metrics files in DIR (only in Chromium)\n"
|
||||||
" --no-rate-limit don't rate limit crash uploads\n"
|
" --no-rate-limit don't rate limit crash uploads\n"
|
||||||
|
" --no-upload-gzip don't use gzip compression when uploading\n"
|
||||||
#if defined(OS_MACOSX)
|
#if defined(OS_MACOSX)
|
||||||
" --reset-own-crash-exception-port-to-system-default\n"
|
" --reset-own-crash-exception-port-to-system-default\n"
|
||||||
" reset the server's exception handler to default\n"
|
" reset the server's exception handler to default\n"
|
||||||
@ -107,11 +111,65 @@ void Usage(const base::FilePath& me) {
|
|||||||
ToolSupport::UsageTail(me);
|
ToolSupport::UsageTail(me);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calls Metrics::HandlerLifetimeMilestone, but only on the first call. This is
|
||||||
|
// to prevent multiple exit events from inadvertently being recorded, which
|
||||||
|
// might happen if a crash occurs during destruction in what would otherwise be
|
||||||
|
// a normal exit, or if a CallMetricsRecordNormalExit object is destroyed after
|
||||||
|
// something else logs an exit event.
|
||||||
|
void MetricsRecordExit(Metrics::LifetimeMilestone milestone) {
|
||||||
|
static bool once = [](Metrics::LifetimeMilestone milestone) {
|
||||||
|
Metrics::HandlerLifetimeMilestone(milestone);
|
||||||
|
return true;
|
||||||
|
}(milestone);
|
||||||
|
ALLOW_UNUSED_LOCAL(once);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calls MetricsRecordExit() to record a failure, and returns EXIT_FAILURE for
|
||||||
|
// the convenience of callers in main() which can simply write “return
|
||||||
|
// ExitFailure();”.
|
||||||
|
int ExitFailure() {
|
||||||
|
MetricsRecordExit(Metrics::LifetimeMilestone::kFailed);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CallMetricsRecordNormalExit {
|
||||||
|
public:
|
||||||
|
CallMetricsRecordNormalExit() {}
|
||||||
|
~CallMetricsRecordNormalExit() {
|
||||||
|
MetricsRecordExit(Metrics::LifetimeMilestone::kExitedNormally);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(CallMetricsRecordNormalExit);
|
||||||
|
};
|
||||||
|
|
||||||
#if defined(OS_MACOSX)
|
#if defined(OS_MACOSX)
|
||||||
|
|
||||||
struct sigaction g_original_crash_sigaction[NSIG];
|
void InstallSignalHandler(const std::vector<int>& signals,
|
||||||
|
void (*handler)(int, siginfo_t*, void*)) {
|
||||||
|
struct sigaction sa = {};
|
||||||
|
sigemptyset(&sa.sa_mask);
|
||||||
|
sa.sa_flags = SA_SIGINFO;
|
||||||
|
sa.sa_sigaction = handler;
|
||||||
|
|
||||||
|
for (int sig : signals) {
|
||||||
|
int rv = sigaction(sig, &sa, nullptr);
|
||||||
|
PCHECK(rv == 0) << "sigaction " << sig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RestoreDefaultSignalHandler(int sig) {
|
||||||
|
struct sigaction sa = {};
|
||||||
|
sigemptyset(&sa.sa_mask);
|
||||||
|
sa.sa_flags = 0;
|
||||||
|
sa.sa_handler = SIG_DFL;
|
||||||
|
int rv = sigaction(sig, &sa, nullptr);
|
||||||
|
DPLOG_IF(ERROR, rv != 0) << "sigaction " << sig;
|
||||||
|
}
|
||||||
|
|
||||||
void HandleCrashSignal(int sig, siginfo_t* siginfo, void* context) {
|
void HandleCrashSignal(int sig, siginfo_t* siginfo, void* context) {
|
||||||
|
MetricsRecordExit(Metrics::LifetimeMilestone::kCrashed);
|
||||||
|
|
||||||
// Is siginfo->si_code useful? The only interesting values on macOS are 0 (not
|
// Is siginfo->si_code useful? The only interesting values on macOS are 0 (not
|
||||||
// useful, signals generated asynchronously such as by kill() or raise()) and
|
// useful, signals generated asynchronously such as by kill() or raise()) and
|
||||||
// small positive numbers (useful, signal generated via a hardware fault). The
|
// small positive numbers (useful, signal generated via a hardware fault). The
|
||||||
@ -143,22 +201,17 @@ void HandleCrashSignal(int sig, siginfo_t* siginfo, void* context) {
|
|||||||
}
|
}
|
||||||
Metrics::HandlerCrashed(metrics_code);
|
Metrics::HandlerCrashed(metrics_code);
|
||||||
|
|
||||||
// Restore the previous signal handler.
|
RestoreDefaultSignalHandler(sig);
|
||||||
DCHECK_GT(sig, 0);
|
|
||||||
DCHECK_LT(static_cast<size_t>(sig), arraysize(g_original_crash_sigaction));
|
|
||||||
struct sigaction* osa = &g_original_crash_sigaction[sig];
|
|
||||||
int rv = sigaction(sig, osa, nullptr);
|
|
||||||
DPLOG_IF(ERROR, rv != 0) << "sigaction " << sig;
|
|
||||||
|
|
||||||
// If the signal was received synchronously resulting from a hardware fault,
|
// If the signal was received synchronously resulting from a hardware fault,
|
||||||
// returning from the signal handler will cause the kernel to re-raise it,
|
// returning from the signal handler will cause the kernel to re-raise it,
|
||||||
// because this handler hasn’t done anything to alleviate the condition that
|
// because this handler hasn’t done anything to alleviate the condition that
|
||||||
// caused the signal to be raised in the first place. With the old signal
|
// caused the signal to be raised in the first place. With the default signal
|
||||||
// handler in place (expected to be SIG_DFL), it will cause the same behavior
|
// handler in place, it will cause the same behavior to be taken as though
|
||||||
// to be taken as though this signal handler had never been installed at all
|
// this signal handler had never been installed at all (expected to be a
|
||||||
// (expected to be a crash). This is ideal, because the signal is re-raised
|
// crash). This is ideal, because the signal is re-raised with the same
|
||||||
// with the same properties and from the same context that initially triggered
|
// properties and from the same context that initially triggered it, providing
|
||||||
// it, providing the best debugging experience.
|
// the best debugging experience.
|
||||||
|
|
||||||
if ((sig != SIGILL && sig != SIGFPE && sig != SIGBUS && sig != SIGSEGV) ||
|
if ((sig != SIGILL && sig != SIGFPE && sig != SIGBUS && sig != SIGSEGV) ||
|
||||||
!si_code_valid) {
|
!si_code_valid) {
|
||||||
@ -166,8 +219,7 @@ void HandleCrashSignal(int sig, siginfo_t* siginfo, void* context) {
|
|||||||
// asynchronously via kill() and raise(), and those arising via hardware
|
// asynchronously via kill() and raise(), and those arising via hardware
|
||||||
// traps such as int3 (resulting in SIGTRAP but advancing the instruction
|
// traps such as int3 (resulting in SIGTRAP but advancing the instruction
|
||||||
// pointer), will not reoccur on their own when returning from the signal
|
// pointer), will not reoccur on their own when returning from the signal
|
||||||
// handler. Re-raise them or call to the previous signal handler as
|
// handler. Re-raise them.
|
||||||
// appropriate.
|
|
||||||
//
|
//
|
||||||
// Unfortunately, when SIGBUS is received asynchronously via kill(),
|
// Unfortunately, when SIGBUS is received asynchronously via kill(),
|
||||||
// siginfo->si_code makes it appear as though it was actually received via a
|
// siginfo->si_code makes it appear as though it was actually received via a
|
||||||
@ -180,49 +232,66 @@ void HandleCrashSignal(int sig, siginfo_t* siginfo, void* context) {
|
|||||||
// very valuable for debugging and are visible to a Mach exception handler.
|
// very valuable for debugging and are visible to a Mach exception handler.
|
||||||
// Since SIGBUS is normally received synchronously in response to a hardware
|
// Since SIGBUS is normally received synchronously in response to a hardware
|
||||||
// fault, don’t sweat the unexpected asynchronous case.
|
// fault, don’t sweat the unexpected asynchronous case.
|
||||||
if (osa->sa_handler == SIG_DFL) {
|
//
|
||||||
// Because this signal handler executes with the signal blocked, this
|
// Because this signal handler executes with the signal blocked, this
|
||||||
// raise() cannot immediately deliver the signal. Delivery is deferred
|
// raise() cannot immediately deliver the signal. Delivery is deferred until
|
||||||
// until this signal handler returns and the signal becomes unblocked. The
|
// this signal handler returns and the signal becomes unblocked. The
|
||||||
// re-raised signal will appear with the same context as where it was
|
// re-raised signal will appear with the same context as where it was
|
||||||
// initially triggered.
|
// initially triggered.
|
||||||
rv = raise(sig);
|
int rv = raise(sig);
|
||||||
DPLOG_IF(ERROR, rv != 0) << "raise";
|
DPLOG_IF(ERROR, rv != 0) << "raise";
|
||||||
} else if (osa->sa_handler != SIG_IGN) {
|
|
||||||
if (osa->sa_flags & SA_SIGINFO) {
|
|
||||||
osa->sa_sigaction(sig, siginfo, context);
|
|
||||||
} else {
|
|
||||||
osa->sa_handler(sig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstallCrashHandler() {
|
void HandleTerminateSignal(int sig, siginfo_t* siginfo, void* context) {
|
||||||
struct sigaction sa = {};
|
MetricsRecordExit(Metrics::LifetimeMilestone::kTerminated);
|
||||||
sigemptyset(&sa.sa_mask);
|
|
||||||
sa.sa_flags = SA_SIGINFO;
|
|
||||||
sa.sa_sigaction = HandleCrashSignal;
|
|
||||||
|
|
||||||
|
RestoreDefaultSignalHandler(sig);
|
||||||
|
|
||||||
|
// Re-raise the signal. See the explanation in HandleCrashSignal(). Note that
|
||||||
|
// no checks for signals arising from synchronous hardware faults are made
|
||||||
|
// because termination signals never originate in that way.
|
||||||
|
int rv = raise(sig);
|
||||||
|
DPLOG_IF(ERROR, rv != 0) << "raise";
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstallCrashHandler() {
|
||||||
// These are the core-generating signals from 10.12.3
|
// These are the core-generating signals from 10.12.3
|
||||||
// xnu-3789.41.3/bsd/sys/signalvar.h sigprop: entries with SA_CORE are in the
|
// xnu-3789.41.3/bsd/sys/signalvar.h sigprop: entries with SA_CORE are in the
|
||||||
// set.
|
// set.
|
||||||
const int kSignals[] = {SIGQUIT,
|
const int kCrashSignals[] = {SIGQUIT,
|
||||||
SIGILL,
|
SIGILL,
|
||||||
SIGTRAP,
|
SIGTRAP,
|
||||||
SIGABRT,
|
SIGABRT,
|
||||||
SIGEMT,
|
SIGEMT,
|
||||||
SIGFPE,
|
SIGFPE,
|
||||||
SIGBUS,
|
SIGBUS,
|
||||||
SIGSEGV,
|
SIGSEGV,
|
||||||
SIGSYS};
|
SIGSYS};
|
||||||
|
InstallSignalHandler(
|
||||||
|
std::vector<int>(&kCrashSignals[0],
|
||||||
|
&kCrashSignals[arraysize(kCrashSignals)]),
|
||||||
|
HandleCrashSignal);
|
||||||
|
|
||||||
for (int sig : kSignals) {
|
// Not a crash handler, but close enough. These are non-core-generating but
|
||||||
DCHECK_GT(sig, 0);
|
// terminating signals from 10.12.3 xnu-3789.41.3/bsd/sys/signalvar.h sigprop:
|
||||||
DCHECK_LT(static_cast<size_t>(sig), arraysize(g_original_crash_sigaction));
|
// entries with SA_KILL but not SA_CORE are in the set. SIGKILL is excluded
|
||||||
int rv = sigaction(sig, &sa, &g_original_crash_sigaction[sig]);
|
// because it is uncatchable.
|
||||||
PCHECK(rv == 0) << "sigaction " << sig;
|
const int kTerminateSignals[] = {SIGHUP,
|
||||||
}
|
SIGINT,
|
||||||
|
SIGPIPE,
|
||||||
|
SIGALRM,
|
||||||
|
SIGTERM,
|
||||||
|
SIGXCPU,
|
||||||
|
SIGXFSZ,
|
||||||
|
SIGVTALRM,
|
||||||
|
SIGPROF,
|
||||||
|
SIGUSR1,
|
||||||
|
SIGUSR2};
|
||||||
|
InstallSignalHandler(
|
||||||
|
std::vector<int>(&kTerminateSignals[0],
|
||||||
|
&kTerminateSignals[arraysize(kTerminateSignals)]),
|
||||||
|
HandleTerminateSignal);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ResetSIGTERMTraits {
|
struct ResetSIGTERMTraits {
|
||||||
@ -242,6 +311,9 @@ ExceptionHandlerServer* g_exception_handler_server;
|
|||||||
|
|
||||||
// This signal handler is only operative when being run from launchd.
|
// This signal handler is only operative when being run from launchd.
|
||||||
void HandleSIGTERM(int sig, siginfo_t* siginfo, void* context) {
|
void HandleSIGTERM(int sig, siginfo_t* siginfo, void* context) {
|
||||||
|
// Don’t call MetricsRecordExit(). This is part of the normal exit path when
|
||||||
|
// running from launchd.
|
||||||
|
|
||||||
DCHECK(g_exception_handler_server);
|
DCHECK(g_exception_handler_server);
|
||||||
g_exception_handler_server->Stop();
|
g_exception_handler_server->Stop();
|
||||||
}
|
}
|
||||||
@ -251,6 +323,7 @@ void HandleSIGTERM(int sig, siginfo_t* siginfo, void* context) {
|
|||||||
LONG(WINAPI* g_original_exception_filter)(EXCEPTION_POINTERS*) = nullptr;
|
LONG(WINAPI* g_original_exception_filter)(EXCEPTION_POINTERS*) = nullptr;
|
||||||
|
|
||||||
LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) {
|
LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) {
|
||||||
|
MetricsRecordExit(Metrics::LifetimeMilestone::kCrashed);
|
||||||
Metrics::HandlerCrashed(exception_pointers->ExceptionRecord->ExceptionCode);
|
Metrics::HandlerCrashed(exception_pointers->ExceptionRecord->ExceptionCode);
|
||||||
|
|
||||||
if (g_original_exception_filter)
|
if (g_original_exception_filter)
|
||||||
@ -259,9 +332,37 @@ LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) {
|
|||||||
return EXCEPTION_CONTINUE_SEARCH;
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handles events like Control-C and Control-Break on a console.
|
||||||
|
BOOL WINAPI ConsoleHandler(DWORD console_event) {
|
||||||
|
MetricsRecordExit(Metrics::LifetimeMilestone::kTerminated);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles a WM_ENDSESSION message sent when the user session is ending.
|
||||||
|
class TerminateHandler final : public SessionEndWatcher {
|
||||||
|
public:
|
||||||
|
TerminateHandler() : SessionEndWatcher() {}
|
||||||
|
~TerminateHandler() override {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// SessionEndWatcher:
|
||||||
|
void SessionEnding() override {
|
||||||
|
MetricsRecordExit(Metrics::LifetimeMilestone::kTerminated);
|
||||||
|
}
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(TerminateHandler);
|
||||||
|
};
|
||||||
|
|
||||||
void InstallCrashHandler() {
|
void InstallCrashHandler() {
|
||||||
g_original_exception_filter =
|
g_original_exception_filter =
|
||||||
SetUnhandledExceptionFilter(&UnhandledExceptionHandler);
|
SetUnhandledExceptionFilter(&UnhandledExceptionHandler);
|
||||||
|
|
||||||
|
// These are termination handlers, not crash handlers, but that’s close
|
||||||
|
// enough. Note that destroying the TerminateHandler would wait for its thread
|
||||||
|
// to exit, which isn’t necessary or desirable.
|
||||||
|
SetConsoleCtrlHandler(ConsoleHandler, true);
|
||||||
|
static TerminateHandler* terminate_handler = new TerminateHandler();
|
||||||
|
ALLOW_UNUSED_LOCAL(terminate_handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // OS_MACOSX
|
#endif // OS_MACOSX
|
||||||
@ -270,6 +371,7 @@ void InstallCrashHandler() {
|
|||||||
|
|
||||||
int HandlerMain(int argc, char* argv[]) {
|
int HandlerMain(int argc, char* argv[]) {
|
||||||
InstallCrashHandler();
|
InstallCrashHandler();
|
||||||
|
CallMetricsRecordNormalExit metrics_record_normal_exit;
|
||||||
|
|
||||||
const base::FilePath argv0(
|
const base::FilePath argv0(
|
||||||
ToolSupport::CommandLineArgumentToFilePathStringType(argv[0]));
|
ToolSupport::CommandLineArgumentToFilePathStringType(argv[0]));
|
||||||
@ -291,6 +393,7 @@ int HandlerMain(int argc, char* argv[]) {
|
|||||||
#endif // OS_MACOSX
|
#endif // OS_MACOSX
|
||||||
kOptionMetrics,
|
kOptionMetrics,
|
||||||
kOptionNoRateLimit,
|
kOptionNoRateLimit,
|
||||||
|
kOptionNoUploadGzip,
|
||||||
#if defined(OS_MACOSX)
|
#if defined(OS_MACOSX)
|
||||||
kOptionResetOwnCrashExceptionPortToSystemDefault,
|
kOptionResetOwnCrashExceptionPortToSystemDefault,
|
||||||
#elif defined(OS_WIN)
|
#elif defined(OS_WIN)
|
||||||
@ -317,11 +420,13 @@ int HandlerMain(int argc, char* argv[]) {
|
|||||||
InitialClientData initial_client_data;
|
InitialClientData initial_client_data;
|
||||||
#endif // OS_MACOSX
|
#endif // OS_MACOSX
|
||||||
bool rate_limit;
|
bool rate_limit;
|
||||||
|
bool upload_gzip;
|
||||||
} options = {};
|
} options = {};
|
||||||
#if defined(OS_MACOSX)
|
#if defined(OS_MACOSX)
|
||||||
options.handshake_fd = -1;
|
options.handshake_fd = -1;
|
||||||
#endif
|
#endif
|
||||||
options.rate_limit = true;
|
options.rate_limit = true;
|
||||||
|
options.upload_gzip = true;
|
||||||
|
|
||||||
const option long_options[] = {
|
const option long_options[] = {
|
||||||
{"annotation", required_argument, nullptr, kOptionAnnotation},
|
{"annotation", required_argument, nullptr, kOptionAnnotation},
|
||||||
@ -340,6 +445,7 @@ int HandlerMain(int argc, char* argv[]) {
|
|||||||
#endif // OS_MACOSX
|
#endif // OS_MACOSX
|
||||||
{"metrics-dir", required_argument, nullptr, kOptionMetrics},
|
{"metrics-dir", required_argument, nullptr, kOptionMetrics},
|
||||||
{"no-rate-limit", no_argument, nullptr, kOptionNoRateLimit},
|
{"no-rate-limit", no_argument, nullptr, kOptionNoRateLimit},
|
||||||
|
{"no-upload-gzip", no_argument, nullptr, kOptionNoUploadGzip},
|
||||||
#if defined(OS_MACOSX)
|
#if defined(OS_MACOSX)
|
||||||
{"reset-own-crash-exception-port-to-system-default",
|
{"reset-own-crash-exception-port-to-system-default",
|
||||||
no_argument,
|
no_argument,
|
||||||
@ -362,7 +468,7 @@ int HandlerMain(int argc, char* argv[]) {
|
|||||||
std::string value;
|
std::string value;
|
||||||
if (!SplitStringFirst(optarg, '=', &key, &value)) {
|
if (!SplitStringFirst(optarg, '=', &key, &value)) {
|
||||||
ToolSupport::UsageHint(me, "--annotation requires KEY=VALUE");
|
ToolSupport::UsageHint(me, "--annotation requires KEY=VALUE");
|
||||||
return EXIT_FAILURE;
|
return ExitFailure();
|
||||||
}
|
}
|
||||||
std::string old_value;
|
std::string old_value;
|
||||||
if (!MapInsertOrReplace(&options.annotations, key, value, &old_value)) {
|
if (!MapInsertOrReplace(&options.annotations, key, value, &old_value)) {
|
||||||
@ -381,7 +487,7 @@ int HandlerMain(int argc, char* argv[]) {
|
|||||||
options.handshake_fd < 0) {
|
options.handshake_fd < 0) {
|
||||||
ToolSupport::UsageHint(me,
|
ToolSupport::UsageHint(me,
|
||||||
"--handshake-fd requires a file descriptor");
|
"--handshake-fd requires a file descriptor");
|
||||||
return EXIT_FAILURE;
|
return ExitFailure();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -394,7 +500,7 @@ int HandlerMain(int argc, char* argv[]) {
|
|||||||
if (!options.initial_client_data.InitializeFromString(optarg)) {
|
if (!options.initial_client_data.InitializeFromString(optarg)) {
|
||||||
ToolSupport::UsageHint(
|
ToolSupport::UsageHint(
|
||||||
me, "failed to parse --initial-client-data");
|
me, "failed to parse --initial-client-data");
|
||||||
return EXIT_FAILURE;
|
return ExitFailure();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -407,6 +513,10 @@ int HandlerMain(int argc, char* argv[]) {
|
|||||||
options.rate_limit = false;
|
options.rate_limit = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case kOptionNoUploadGzip: {
|
||||||
|
options.upload_gzip = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
#if defined(OS_MACOSX)
|
#if defined(OS_MACOSX)
|
||||||
case kOptionResetOwnCrashExceptionPortToSystemDefault: {
|
case kOptionResetOwnCrashExceptionPortToSystemDefault: {
|
||||||
options.reset_own_crash_exception_port_to_system_default = true;
|
options.reset_own_crash_exception_port_to_system_default = true;
|
||||||
@ -424,15 +534,17 @@ int HandlerMain(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
case kOptionHelp: {
|
case kOptionHelp: {
|
||||||
Usage(me);
|
Usage(me);
|
||||||
|
MetricsRecordExit(Metrics::LifetimeMilestone::kExitedEarly);
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
case kOptionVersion: {
|
case kOptionVersion: {
|
||||||
ToolSupport::Version(me);
|
ToolSupport::Version(me);
|
||||||
|
MetricsRecordExit(Metrics::LifetimeMilestone::kExitedEarly);
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
ToolSupport::UsageHint(me, nullptr);
|
ToolSupport::UsageHint(me, nullptr);
|
||||||
return EXIT_FAILURE;
|
return ExitFailure();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -442,34 +554,34 @@ int HandlerMain(int argc, char* argv[]) {
|
|||||||
#if defined(OS_MACOSX)
|
#if defined(OS_MACOSX)
|
||||||
if (options.handshake_fd < 0 && options.mach_service.empty()) {
|
if (options.handshake_fd < 0 && options.mach_service.empty()) {
|
||||||
ToolSupport::UsageHint(me, "--handshake-fd or --mach-service is required");
|
ToolSupport::UsageHint(me, "--handshake-fd or --mach-service is required");
|
||||||
return EXIT_FAILURE;
|
return ExitFailure();
|
||||||
}
|
}
|
||||||
if (options.handshake_fd >= 0 && !options.mach_service.empty()) {
|
if (options.handshake_fd >= 0 && !options.mach_service.empty()) {
|
||||||
ToolSupport::UsageHint(
|
ToolSupport::UsageHint(
|
||||||
me, "--handshake-fd and --mach-service are incompatible");
|
me, "--handshake-fd and --mach-service are incompatible");
|
||||||
return EXIT_FAILURE;
|
return ExitFailure();
|
||||||
}
|
}
|
||||||
#elif defined(OS_WIN)
|
#elif defined(OS_WIN)
|
||||||
if (!options.initial_client_data.IsValid() && options.pipe_name.empty()) {
|
if (!options.initial_client_data.IsValid() && options.pipe_name.empty()) {
|
||||||
ToolSupport::UsageHint(me,
|
ToolSupport::UsageHint(me,
|
||||||
"--initial-client-data or --pipe-name is required");
|
"--initial-client-data or --pipe-name is required");
|
||||||
return EXIT_FAILURE;
|
return ExitFailure();
|
||||||
}
|
}
|
||||||
if (options.initial_client_data.IsValid() && !options.pipe_name.empty()) {
|
if (options.initial_client_data.IsValid() && !options.pipe_name.empty()) {
|
||||||
ToolSupport::UsageHint(
|
ToolSupport::UsageHint(
|
||||||
me, "--initial-client-data and --pipe-name are incompatible");
|
me, "--initial-client-data and --pipe-name are incompatible");
|
||||||
return EXIT_FAILURE;
|
return ExitFailure();
|
||||||
}
|
}
|
||||||
#endif // OS_MACOSX
|
#endif // OS_MACOSX
|
||||||
|
|
||||||
if (!options.database) {
|
if (!options.database) {
|
||||||
ToolSupport::UsageHint(me, "--database is required");
|
ToolSupport::UsageHint(me, "--database is required");
|
||||||
return EXIT_FAILURE;
|
return ExitFailure();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argc) {
|
if (argc) {
|
||||||
ToolSupport::UsageHint(me, nullptr);
|
ToolSupport::UsageHint(me, nullptr);
|
||||||
return EXIT_FAILURE;
|
return ExitFailure();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(OS_MACOSX)
|
#if defined(OS_MACOSX)
|
||||||
@ -494,7 +606,7 @@ int HandlerMain(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!receive_right.is_valid()) {
|
if (!receive_right.is_valid()) {
|
||||||
return EXIT_FAILURE;
|
return ExitFailure();
|
||||||
}
|
}
|
||||||
|
|
||||||
ExceptionHandlerServer exception_handler_server(
|
ExceptionHandlerServer exception_handler_server(
|
||||||
@ -511,6 +623,7 @@ int HandlerMain(int argc, char* argv[]) {
|
|||||||
// launchd.plist(5).
|
// launchd.plist(5).
|
||||||
//
|
//
|
||||||
// Set up a SIGTERM handler that will call exception_handler_server.Stop().
|
// Set up a SIGTERM handler that will call exception_handler_server.Stop().
|
||||||
|
// This replaces the HandleTerminateSignal handler for SIGTERM.
|
||||||
struct sigaction sa = {};
|
struct sigaction sa = {};
|
||||||
sigemptyset(&sa.sa_mask);
|
sigemptyset(&sa.sa_mask);
|
||||||
sa.sa_flags = SA_SIGINFO;
|
sa.sa_flags = SA_SIGINFO;
|
||||||
@ -544,18 +657,20 @@ int HandlerMain(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Metrics::HandlerLifetimeMilestone(Metrics::LifetimeMilestone::kStarted);
|
||||||
|
|
||||||
std::unique_ptr<CrashReportDatabase> database(CrashReportDatabase::Initialize(
|
std::unique_ptr<CrashReportDatabase> database(CrashReportDatabase::Initialize(
|
||||||
base::FilePath(ToolSupport::CommandLineArgumentToFilePathStringType(
|
base::FilePath(ToolSupport::CommandLineArgumentToFilePathStringType(
|
||||||
options.database))));
|
options.database))));
|
||||||
if (!database) {
|
if (!database) {
|
||||||
return EXIT_FAILURE;
|
return ExitFailure();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(scottmg): options.rate_limit should be removed when we have a
|
// TODO(scottmg): options.rate_limit should be removed when we have a
|
||||||
// configurable database setting to control upload limiting.
|
// configurable database setting to control upload limiting.
|
||||||
// See https://crashpad.chromium.org/bug/23.
|
// See https://crashpad.chromium.org/bug/23.
|
||||||
CrashReportUploadThread upload_thread(
|
CrashReportUploadThread upload_thread(
|
||||||
database.get(), options.url, options.rate_limit);
|
database.get(), options.url, options.rate_limit, options.upload_gzip);
|
||||||
upload_thread.Start();
|
upload_thread.Start();
|
||||||
|
|
||||||
PruneCrashReportThread prune_thread(database.get(),
|
PruneCrashReportThread prune_thread(database.get(),
|
||||||
|
@ -92,7 +92,8 @@ enum MinidumpContextX86Flags : uint32_t {
|
|||||||
//! \brief Indicates the validity of floating-point state
|
//! \brief Indicates the validity of floating-point state
|
||||||
//! (`CONTEXT_FLOATING_POINT`).
|
//! (`CONTEXT_FLOATING_POINT`).
|
||||||
//!
|
//!
|
||||||
//! The `float_save` field is valid.
|
//! The `fsave` field is valid. The `float_save` field is included in this
|
||||||
|
//! definition, but its members have no practical use asdie from `fsave`.
|
||||||
kMinidumpContextX86FloatingPoint = kMinidumpContextX86 | 0x00000008,
|
kMinidumpContextX86FloatingPoint = kMinidumpContextX86 | 0x00000008,
|
||||||
|
|
||||||
//! \brief Indicates the validity of debug registers
|
//! \brief Indicates the validity of debug registers
|
||||||
@ -130,8 +131,9 @@ enum MinidumpContextX86Flags : uint32_t {
|
|||||||
//! \brief A 32-bit x86 CPU context (register state) carried in a minidump file.
|
//! \brief A 32-bit x86 CPU context (register state) carried in a minidump file.
|
||||||
//!
|
//!
|
||||||
//! This is analogous to the `CONTEXT` structure on Windows when targeting
|
//! This is analogous to the `CONTEXT` structure on Windows when targeting
|
||||||
//! 32-bit x86. This structure is used instead of `CONTEXT` to make it available
|
//! 32-bit x86, and the `WOW64_CONTEXT` structure when targeting an x86-family
|
||||||
//! when targeting other architectures.
|
//! CPU, either 32- or 64-bit. This structure is used instead of `CONTEXT` or
|
||||||
|
//! `WOW64_CONTEXT` to make it available when targeting other architectures.
|
||||||
//!
|
//!
|
||||||
//! \note This structure doesn’t carry `dr4` or `dr5`, which are obsolete and
|
//! \note This structure doesn’t carry `dr4` or `dr5`, which are obsolete and
|
||||||
//! normally alias `dr6` and `dr7`, respectively. See Intel Software
|
//! normally alias `dr6` and `dr7`, respectively. See Intel Software
|
||||||
@ -152,16 +154,12 @@ struct MinidumpContextX86 {
|
|||||||
uint32_t dr6;
|
uint32_t dr6;
|
||||||
uint32_t dr7;
|
uint32_t dr7;
|
||||||
|
|
||||||
struct {
|
// CPUContextX86::Fsave has identical layout to what the x86 CONTEXT
|
||||||
uint32_t control_word;
|
// structure places here.
|
||||||
uint32_t status_word;
|
CPUContextX86::Fsave fsave;
|
||||||
uint32_t tag_word;
|
union {
|
||||||
uint32_t error_offset;
|
uint32_t spare_0; // As in the native x86 CONTEXT structure since Windows 8
|
||||||
uint32_t error_selector;
|
uint32_t cr0_npx_state; // As in WOW64_CONTEXT and older SDKs’ x86 CONTEXT
|
||||||
uint32_t data_offset;
|
|
||||||
uint32_t data_selector;
|
|
||||||
uint8_t register_area[80];
|
|
||||||
uint32_t spare_0;
|
|
||||||
} float_save;
|
} float_save;
|
||||||
|
|
||||||
uint32_t gs;
|
uint32_t gs;
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
#include "minidump/minidump_context_writer.h"
|
#include "minidump/minidump_context_writer.h"
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <dbghelp.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
@ -24,6 +26,28 @@
|
|||||||
|
|
||||||
namespace crashpad {
|
namespace crashpad {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Sanity-check complex structures to ensure interoperability.
|
||||||
|
static_assert(sizeof(MinidumpContextX86) == 716, "MinidumpContextX86 size");
|
||||||
|
static_assert(sizeof(MinidumpContextAMD64) == 1232,
|
||||||
|
"MinidumpContextAMD64 size");
|
||||||
|
|
||||||
|
// These structures can also be checked against definitions in the Windows SDK.
|
||||||
|
#if defined(OS_WIN)
|
||||||
|
#if defined(ARCH_CPU_X86_FAMILY)
|
||||||
|
static_assert(sizeof(MinidumpContextX86) == sizeof(WOW64_CONTEXT),
|
||||||
|
"WOW64_CONTEXT size");
|
||||||
|
#if defined(ARCH_CPU_X86)
|
||||||
|
static_assert(sizeof(MinidumpContextX86) == sizeof(CONTEXT), "CONTEXT size");
|
||||||
|
#elif defined(ARCH_CPU_X86_64)
|
||||||
|
static_assert(sizeof(MinidumpContextAMD64) == sizeof(CONTEXT), "CONTEXT size");
|
||||||
|
#endif
|
||||||
|
#endif // ARCH_CPU_X86_FAMILY
|
||||||
|
#endif // OS_WIN
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
MinidumpContextWriter::~MinidumpContextWriter() {
|
MinidumpContextWriter::~MinidumpContextWriter() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,28 +113,11 @@ void MinidumpContextX86Writer::InitializeFromSnapshot(
|
|||||||
context_.dr6 = context_snapshot->dr6;
|
context_.dr6 = context_snapshot->dr6;
|
||||||
context_.dr7 = context_snapshot->dr7;
|
context_.dr7 = context_snapshot->dr7;
|
||||||
|
|
||||||
// The contents of context_.float_save effectively alias everything in
|
// The contents of context_.fsave effectively alias everything in
|
||||||
// context_.fxsave that’s related to x87 FPU state. context_.float_save
|
// context_.fxsave that’s related to x87 FPU state. context_.fsave doesn’t
|
||||||
// doesn’t carry state specific to SSE (or later), such as mxcsr and the xmm
|
// carry state specific to SSE (or later), such as mxcsr and the xmm
|
||||||
// registers.
|
// registers.
|
||||||
context_.float_save.control_word = context_snapshot->fxsave.fcw;
|
CPUContextX86::FxsaveToFsave(context_snapshot->fxsave, &context_.fsave);
|
||||||
context_.float_save.status_word = context_snapshot->fxsave.fsw;
|
|
||||||
context_.float_save.tag_word =
|
|
||||||
CPUContextX86::FxsaveToFsaveTagWord(context_snapshot->fxsave.fsw,
|
|
||||||
context_snapshot->fxsave.ftw,
|
|
||||||
context_snapshot->fxsave.st_mm);
|
|
||||||
context_.float_save.error_offset = context_snapshot->fxsave.fpu_ip;
|
|
||||||
context_.float_save.error_selector = context_snapshot->fxsave.fpu_cs;
|
|
||||||
context_.float_save.data_offset = context_snapshot->fxsave.fpu_dp;
|
|
||||||
context_.float_save.data_selector = context_snapshot->fxsave.fpu_ds;
|
|
||||||
|
|
||||||
for (size_t index = 0, offset = 0;
|
|
||||||
index < arraysize(context_snapshot->fxsave.st_mm);
|
|
||||||
offset += sizeof(context_snapshot->fxsave.st_mm[index].st), ++index) {
|
|
||||||
memcpy(&context_.float_save.register_area[offset],
|
|
||||||
&context_snapshot->fxsave.st_mm[index].st,
|
|
||||||
sizeof(context_snapshot->fxsave.st_mm[index].st));
|
|
||||||
}
|
|
||||||
|
|
||||||
context_.gs = context_snapshot->gs;
|
context_.gs = context_snapshot->gs;
|
||||||
context_.fs = context_snapshot->fs;
|
context_.fs = context_snapshot->fs;
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
#include "snapshot/cpu_context.h"
|
#include "snapshot/cpu_context.h"
|
||||||
#include "snapshot/test/test_cpu_context.h"
|
#include "snapshot/test/test_cpu_context.h"
|
||||||
|
#include "test/hex_string.h"
|
||||||
|
|
||||||
namespace crashpad {
|
namespace crashpad {
|
||||||
namespace test {
|
namespace test {
|
||||||
@ -56,6 +57,7 @@ void InitializeMinidumpContextX86(MinidumpContextX86* context, uint32_t seed) {
|
|||||||
context->ss = value++ & 0xffff;
|
context->ss = value++ & 0xffff;
|
||||||
|
|
||||||
InitializeCPUContextX86Fxsave(&context->fxsave, &value);
|
InitializeCPUContextX86Fxsave(&context->fxsave, &value);
|
||||||
|
CPUContextX86::FxsaveToFsave(context->fxsave, &context->fsave);
|
||||||
|
|
||||||
context->dr0 = value++;
|
context->dr0 = value++;
|
||||||
context->dr1 = value++;
|
context->dr1 = value++;
|
||||||
@ -65,30 +67,6 @@ void InitializeMinidumpContextX86(MinidumpContextX86* context, uint32_t seed) {
|
|||||||
context->dr6 = value++;
|
context->dr6 = value++;
|
||||||
context->dr7 = value++;
|
context->dr7 = value++;
|
||||||
|
|
||||||
// Copy the values that are aliased between the fxsave area
|
|
||||||
// (context->extended_registers) and the floating-point save area
|
|
||||||
// (context->float_save).
|
|
||||||
context->float_save.control_word = context->fxsave.fcw;
|
|
||||||
context->float_save.status_word = context->fxsave.fsw;
|
|
||||||
context->float_save.tag_word = CPUContextX86::FxsaveToFsaveTagWord(
|
|
||||||
context->fxsave.fsw, context->fxsave.ftw, context->fxsave.st_mm);
|
|
||||||
context->float_save.error_offset = context->fxsave.fpu_ip;
|
|
||||||
context->float_save.error_selector = context->fxsave.fpu_cs;
|
|
||||||
context->float_save.data_offset = context->fxsave.fpu_dp;
|
|
||||||
context->float_save.data_selector = context->fxsave.fpu_ds;
|
|
||||||
for (size_t st_mm_index = 0;
|
|
||||||
st_mm_index < arraysize(context->fxsave.st_mm);
|
|
||||||
++st_mm_index) {
|
|
||||||
for (size_t byte = 0;
|
|
||||||
byte < arraysize(context->fxsave.st_mm[st_mm_index].st);
|
|
||||||
++byte) {
|
|
||||||
size_t st_index =
|
|
||||||
st_mm_index * arraysize(context->fxsave.st_mm[st_mm_index].st) + byte;
|
|
||||||
context->float_save.register_area[st_index] =
|
|
||||||
context->fxsave.st_mm[st_mm_index].st[byte];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set this field last, because it has no analogue in CPUContextX86.
|
// Set this field last, because it has no analogue in CPUContextX86.
|
||||||
context->float_save.spare_0 = value++;
|
context->float_save.spare_0 = value++;
|
||||||
}
|
}
|
||||||
@ -188,37 +166,31 @@ void ExpectMinidumpContextFxsave(const FxsaveType* expected,
|
|||||||
st_mm_index < arraysize(expected->st_mm);
|
st_mm_index < arraysize(expected->st_mm);
|
||||||
++st_mm_index) {
|
++st_mm_index) {
|
||||||
SCOPED_TRACE(base::StringPrintf("st_mm_index %" PRIuS, st_mm_index));
|
SCOPED_TRACE(base::StringPrintf("st_mm_index %" PRIuS, st_mm_index));
|
||||||
for (size_t byte = 0;
|
EXPECT_EQ(BytesToHexString(expected->st_mm[st_mm_index].st,
|
||||||
byte < arraysize(expected->st_mm[st_mm_index].st);
|
arraysize(expected->st_mm[st_mm_index].st)),
|
||||||
++byte) {
|
BytesToHexString(observed->st_mm[st_mm_index].st,
|
||||||
EXPECT_EQ(expected->st_mm[st_mm_index].st[byte],
|
arraysize(observed->st_mm[st_mm_index].st)));
|
||||||
observed->st_mm[st_mm_index].st[byte]) << "byte " << byte;
|
EXPECT_EQ(
|
||||||
}
|
BytesToHexString(expected->st_mm[st_mm_index].st_reserved,
|
||||||
for (size_t byte = 0;
|
arraysize(expected->st_mm[st_mm_index].st_reserved)),
|
||||||
byte < arraysize(expected->st_mm[st_mm_index].st_reserved);
|
BytesToHexString(observed->st_mm[st_mm_index].st_reserved,
|
||||||
++byte) {
|
arraysize(observed->st_mm[st_mm_index].st_reserved)));
|
||||||
EXPECT_EQ(expected->st_mm[st_mm_index].st_reserved[byte],
|
|
||||||
observed->st_mm[st_mm_index].st_reserved[byte])
|
|
||||||
<< "byte " << byte;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for (size_t xmm_index = 0;
|
for (size_t xmm_index = 0;
|
||||||
xmm_index < arraysize(expected->xmm);
|
xmm_index < arraysize(expected->xmm);
|
||||||
++xmm_index) {
|
++xmm_index) {
|
||||||
SCOPED_TRACE(base::StringPrintf("xmm_index %" PRIuS, xmm_index));
|
EXPECT_EQ(BytesToHexString(expected->xmm[xmm_index],
|
||||||
for (size_t byte = 0; byte < arraysize(expected->xmm[xmm_index]); ++byte) {
|
arraysize(expected->xmm[xmm_index])),
|
||||||
EXPECT_EQ(expected->xmm[xmm_index][byte], observed->xmm[xmm_index][byte])
|
BytesToHexString(observed->xmm[xmm_index],
|
||||||
<< "byte " << byte;
|
arraysize(observed->xmm[xmm_index])))
|
||||||
}
|
<< "xmm_index " << xmm_index;
|
||||||
}
|
|
||||||
for (size_t byte = 0; byte < arraysize(expected->reserved_4); ++byte) {
|
|
||||||
EXPECT_EQ(expected->reserved_4[byte], observed->reserved_4[byte])
|
|
||||||
<< "byte " << byte;
|
|
||||||
}
|
|
||||||
for (size_t byte = 0; byte < arraysize(expected->available); ++byte) {
|
|
||||||
EXPECT_EQ(expected->available[byte], observed->available[byte])
|
|
||||||
<< "byte " << byte;
|
|
||||||
}
|
}
|
||||||
|
EXPECT_EQ(
|
||||||
|
BytesToHexString(expected->reserved_4, arraysize(expected->reserved_4)),
|
||||||
|
BytesToHexString(observed->reserved_4, arraysize(observed->reserved_4)));
|
||||||
|
EXPECT_EQ(
|
||||||
|
BytesToHexString(expected->available, arraysize(expected->available)),
|
||||||
|
BytesToHexString(observed->available, arraysize(observed->available)));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@ -236,22 +208,19 @@ void ExpectMinidumpContextX86(
|
|||||||
EXPECT_EQ(expected.dr6, observed->dr6);
|
EXPECT_EQ(expected.dr6, observed->dr6);
|
||||||
EXPECT_EQ(expected.dr7, observed->dr7);
|
EXPECT_EQ(expected.dr7, observed->dr7);
|
||||||
|
|
||||||
EXPECT_EQ(expected.float_save.control_word,
|
EXPECT_EQ(expected.fsave.fcw, observed->fsave.fcw);
|
||||||
observed->float_save.control_word);
|
EXPECT_EQ(expected.fsave.fsw, observed->fsave.fsw);
|
||||||
EXPECT_EQ(expected.float_save.status_word, observed->float_save.status_word);
|
EXPECT_EQ(expected.fsave.ftw, observed->fsave.ftw);
|
||||||
EXPECT_EQ(expected.float_save.tag_word, observed->float_save.tag_word);
|
EXPECT_EQ(expected.fsave.fpu_ip, observed->fsave.fpu_ip);
|
||||||
EXPECT_EQ(expected.float_save.error_offset,
|
EXPECT_EQ(expected.fsave.fpu_cs, observed->fsave.fpu_cs);
|
||||||
observed->float_save.error_offset);
|
EXPECT_EQ(expected.fsave.fpu_dp, observed->fsave.fpu_dp);
|
||||||
EXPECT_EQ(expected.float_save.error_selector,
|
EXPECT_EQ(expected.fsave.fpu_ds, observed->fsave.fpu_ds);
|
||||||
observed->float_save.error_selector);
|
for (size_t index = 0; index < arraysize(expected.fsave.st); ++index) {
|
||||||
EXPECT_EQ(expected.float_save.data_offset, observed->float_save.data_offset);
|
EXPECT_EQ(BytesToHexString(expected.fsave.st[index],
|
||||||
EXPECT_EQ(expected.float_save.data_selector,
|
arraysize(expected.fsave.st[index])),
|
||||||
observed->float_save.data_selector);
|
BytesToHexString(observed->fsave.st[index],
|
||||||
for (size_t index = 0;
|
arraysize(observed->fsave.st[index])))
|
||||||
index < arraysize(expected.float_save.register_area);
|
<< "index " << index;
|
||||||
++index) {
|
|
||||||
EXPECT_EQ(expected.float_save.register_area[index],
|
|
||||||
observed->float_save.register_area[index]) << "index " << index;
|
|
||||||
}
|
}
|
||||||
if (snapshot) {
|
if (snapshot) {
|
||||||
EXPECT_EQ(0u, observed->float_save.spare_0);
|
EXPECT_EQ(0u, observed->float_save.spare_0);
|
||||||
|
@ -14,23 +14,84 @@
|
|||||||
|
|
||||||
#include "snapshot/cpu_context.h"
|
#include "snapshot/cpu_context.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
#include "base/logging.h"
|
#include "base/logging.h"
|
||||||
|
#include "base/macros.h"
|
||||||
#include "util/misc/implicit_cast.h"
|
#include "util/misc/implicit_cast.h"
|
||||||
|
|
||||||
namespace crashpad {
|
namespace crashpad {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Sanity-check complex structures to ensure interoperability.
|
||||||
|
static_assert(sizeof(CPUContextX86::Fsave) == 108, "CPUContextX86::Fsave size");
|
||||||
|
static_assert(sizeof(CPUContextX86::Fxsave) == 512,
|
||||||
|
"CPUContextX86::Fxsave size");
|
||||||
|
static_assert(sizeof(CPUContextX86_64::Fxsave) == 512,
|
||||||
|
"CPUContextX86_64::Fxsave size");
|
||||||
|
|
||||||
|
enum {
|
||||||
|
kX87TagValid = 0,
|
||||||
|
kX87TagZero,
|
||||||
|
kX87TagSpecial,
|
||||||
|
kX87TagEmpty,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// static
|
||||||
|
void CPUContextX86::FxsaveToFsave(const Fxsave& fxsave, Fsave* fsave) {
|
||||||
|
fsave->fcw = fxsave.fcw;
|
||||||
|
fsave->reserved_1 = 0;
|
||||||
|
fsave->fsw = fxsave.fsw;
|
||||||
|
fsave->reserved_2 = 0;
|
||||||
|
fsave->ftw = FxsaveToFsaveTagWord(fxsave.fsw, fxsave.ftw, fxsave.st_mm);
|
||||||
|
fsave->reserved_3 = 0;
|
||||||
|
fsave->fpu_ip = fxsave.fpu_ip;
|
||||||
|
fsave->fpu_cs = fxsave.fpu_cs;
|
||||||
|
fsave->fop = fxsave.fop;
|
||||||
|
fsave->fpu_dp = fxsave.fpu_dp;
|
||||||
|
fsave->fpu_ds = fxsave.fpu_ds;
|
||||||
|
fsave->reserved_4 = 0;
|
||||||
|
static_assert(arraysize(fsave->st) == arraysize(fxsave.st_mm),
|
||||||
|
"FPU stack registers must be equivalent");
|
||||||
|
for (size_t index = 0; index < arraysize(fsave->st); ++index) {
|
||||||
|
memcpy(fsave->st[index], fxsave.st_mm[index].st, sizeof(fsave->st[index]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
void CPUContextX86::FsaveToFxsave(const Fsave& fsave, Fxsave* fxsave) {
|
||||||
|
fxsave->fcw = fsave.fcw;
|
||||||
|
fxsave->fsw = fsave.fsw;
|
||||||
|
fxsave->ftw = FsaveToFxsaveTagWord(fsave.ftw);
|
||||||
|
fxsave->reserved_1 = 0;
|
||||||
|
fxsave->fop = fsave.fop;
|
||||||
|
fxsave->fpu_ip = fsave.fpu_ip;
|
||||||
|
fxsave->fpu_cs = fsave.fpu_cs;
|
||||||
|
fxsave->reserved_2 = 0;
|
||||||
|
fxsave->fpu_dp = fsave.fpu_dp;
|
||||||
|
fxsave->fpu_ds = fsave.fpu_ds;
|
||||||
|
fxsave->reserved_3 = 0;
|
||||||
|
fxsave->mxcsr = 0;
|
||||||
|
fxsave->mxcsr_mask = 0;
|
||||||
|
static_assert(arraysize(fxsave->st_mm) == arraysize(fsave.st),
|
||||||
|
"FPU stack registers must be equivalent");
|
||||||
|
for (size_t index = 0; index < arraysize(fsave.st); ++index) {
|
||||||
|
memcpy(fxsave->st_mm[index].st, fsave.st[index], sizeof(fsave.st[index]));
|
||||||
|
memset(fxsave->st_mm[index].st_reserved,
|
||||||
|
0,
|
||||||
|
sizeof(fxsave->st_mm[index].st_reserved));
|
||||||
|
}
|
||||||
|
memset(fxsave->xmm, 0, sizeof(*fxsave) - offsetof(Fxsave, xmm));
|
||||||
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
uint16_t CPUContextX86::FxsaveToFsaveTagWord(
|
uint16_t CPUContextX86::FxsaveToFsaveTagWord(
|
||||||
uint16_t fsw,
|
uint16_t fsw,
|
||||||
uint8_t fxsave_tag,
|
uint8_t fxsave_tag,
|
||||||
const CPUContextX86::X87OrMMXRegister st_mm[8]) {
|
const CPUContextX86::X87OrMMXRegister st_mm[8]) {
|
||||||
enum {
|
|
||||||
kX87TagValid = 0,
|
|
||||||
kX87TagZero,
|
|
||||||
kX87TagSpecial,
|
|
||||||
kX87TagEmpty,
|
|
||||||
};
|
|
||||||
|
|
||||||
// The x87 tag word (in both abridged and full form) identifies physical
|
// The x87 tag word (in both abridged and full form) identifies physical
|
||||||
// registers, but |st_mm| is arranged in logical stack order. In order to map
|
// registers, but |st_mm| is arranged in logical stack order. In order to map
|
||||||
// physical tag word bits to the logical stack registers they correspond to,
|
// physical tag word bits to the logical stack registers they correspond to,
|
||||||
@ -85,6 +146,17 @@ uint16_t CPUContextX86::FxsaveToFsaveTagWord(
|
|||||||
return fsave_tag;
|
return fsave_tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
uint8_t CPUContextX86::FsaveToFxsaveTagWord(uint16_t fsave_tag) {
|
||||||
|
uint8_t fxsave_tag = 0;
|
||||||
|
for (int physical_index = 0; physical_index < 8; ++physical_index) {
|
||||||
|
const uint8_t fsave_bits = (fsave_tag >> (physical_index * 2)) & 0x3;
|
||||||
|
const bool fxsave_bit = fsave_bits != kX87TagEmpty;
|
||||||
|
fxsave_tag |= fxsave_bit << physical_index;
|
||||||
|
}
|
||||||
|
return fxsave_tag;
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t CPUContext::InstructionPointer() const {
|
uint64_t CPUContext::InstructionPointer() const {
|
||||||
switch (architecture) {
|
switch (architecture) {
|
||||||
case kCPUArchitectureX86:
|
case kCPUArchitectureX86:
|
||||||
|
@ -25,6 +25,22 @@ namespace crashpad {
|
|||||||
struct CPUContextX86 {
|
struct CPUContextX86 {
|
||||||
using X87Register = uint8_t[10];
|
using X87Register = uint8_t[10];
|
||||||
|
|
||||||
|
struct Fsave {
|
||||||
|
uint16_t fcw; // FPU control word
|
||||||
|
uint16_t reserved_1;
|
||||||
|
uint16_t fsw; // FPU status word
|
||||||
|
uint16_t reserved_2;
|
||||||
|
uint16_t ftw; // full FPU tag word
|
||||||
|
uint16_t reserved_3;
|
||||||
|
uint32_t fpu_ip; // FPU instruction pointer offset
|
||||||
|
uint16_t fpu_cs; // FPU instruction pointer segment selector
|
||||||
|
uint16_t fop; // FPU opcode
|
||||||
|
uint32_t fpu_dp; // FPU data pointer offset
|
||||||
|
uint16_t fpu_ds; // FPU data pointer segment selector
|
||||||
|
uint16_t reserved_4;
|
||||||
|
X87Register st[8];
|
||||||
|
};
|
||||||
|
|
||||||
union X87OrMMXRegister {
|
union X87OrMMXRegister {
|
||||||
struct {
|
struct {
|
||||||
X87Register st;
|
X87Register st;
|
||||||
@ -58,14 +74,49 @@ struct CPUContextX86 {
|
|||||||
uint8_t available[48];
|
uint8_t available[48];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//! \brief Converts an `fxsave` area to an `fsave` area.
|
||||||
|
//!
|
||||||
|
//! `fsave` state is restricted to the x87 FPU, while `fxsave` state includes
|
||||||
|
//! state related to the x87 FPU as well as state specific to SSE.
|
||||||
|
//!
|
||||||
|
//! As the `fxsave` format is a superset of the `fsave` format, this operation
|
||||||
|
//! fully populates the `fsave` area. `fsave` uses the full 16-bit form for
|
||||||
|
//! the x87 floating-point tag word, so FxsaveToFsaveTagWord() is used to
|
||||||
|
//! derive Fsave::ftw from the abridged 8-bit form used by `fxsave`. Reserved
|
||||||
|
//! fields in \a fsave are set to `0`.
|
||||||
|
//!
|
||||||
|
//! \param[in] fxsave The `fxsave` area to convert.
|
||||||
|
//! \param[out] fsave The `fsave` area to populate.
|
||||||
|
//!
|
||||||
|
//! \sa FsaveToFxsave()
|
||||||
|
static void FxsaveToFsave(const Fxsave& fxsave, Fsave* fsave);
|
||||||
|
|
||||||
|
//! \brief Converts an `fsave` area to an `fxsave` area.
|
||||||
|
//!
|
||||||
|
//! `fsave` state is restricted to the x87 FPU, while `fxsave` state includes
|
||||||
|
//! state related to the x87 FPU as well as state specific to SSE.
|
||||||
|
//!
|
||||||
|
//! As the `fsave` format is a subset of the `fxsave` format, this operation
|
||||||
|
//! cannot fully populate the `fxsave` area. Fields in \a fxsave that have no
|
||||||
|
//! equivalent in \a fsave are set to `0`, including Fxsave::mxcsr,
|
||||||
|
//! Fxsave::mxcsr_mask, Fxsave::xmm, and Fxsave::available.
|
||||||
|
//! FsaveToFxsaveTagWord() is used to derive Fxsave::ftw from the full 16-bit
|
||||||
|
//! form used by `fsave`. Reserved fields in \a fxsave are set to `0`.
|
||||||
|
//!
|
||||||
|
//! \param[in] fsave The `fsave` area to convert.
|
||||||
|
//! \param[out] fxsave The `fxsave` area to populate.
|
||||||
|
//!
|
||||||
|
//! \sa FxsaveToFsave()
|
||||||
|
static void FsaveToFxsave(const Fsave& fsave, Fxsave* fxsave);
|
||||||
|
|
||||||
//! \brief Converts x87 floating-point tag words from `fxsave` (abridged,
|
//! \brief Converts x87 floating-point tag words from `fxsave` (abridged,
|
||||||
//! 8-bit) to `fsave` (full, 16-bit) form.
|
//! 8-bit) to `fsave` (full, 16-bit) form.
|
||||||
//!
|
//!
|
||||||
//! `fxsave` stores the x87 floating-point tag word in abridged 8-bit form,
|
//! `fxsave` stores the x87 floating-point tag word in abridged 8-bit form,
|
||||||
//! and `fsave` stores it in full 16-bit form. Some users, notably
|
//! and `fsave` stores it in full 16-bit form. Some users, notably
|
||||||
//! MinidumpContextX86::float_save::tag_word, require the full 16-bit form,
|
//! CPUContextX86::Fsave::ftw, require the full 16-bit form, where most other
|
||||||
//! where most other contemporary code uses `fxsave` and thus the abridged
|
//! contemporary code uses `fxsave` and thus the abridged 8-bit form found in
|
||||||
//! 8-bit form found in CPUContextX86::Fxsave::ftw.
|
//! CPUContextX86::Fxsave::ftw.
|
||||||
//!
|
//!
|
||||||
//! This function converts an abridged tag word to the full version by using
|
//! This function converts an abridged tag word to the full version by using
|
||||||
//! the abridged tag word and the contents of the registers it describes. See
|
//! the abridged tag word and the contents of the registers it describes. See
|
||||||
@ -75,6 +126,8 @@ struct CPUContextX86 {
|
|||||||
//! Manual, Volume 2: System Programming (24593-3.24), “FXSAVE Format for x87
|
//! Manual, Volume 2: System Programming (24593-3.24), “FXSAVE Format for x87
|
||||||
//! Tag Word”.
|
//! Tag Word”.
|
||||||
//!
|
//!
|
||||||
|
//! \sa FsaveToFxsaveTagWord()
|
||||||
|
//!
|
||||||
//! \param[in] fsw The FPU status word, used to map logical \a st_mm registers
|
//! \param[in] fsw The FPU status word, used to map logical \a st_mm registers
|
||||||
//! to their physical counterparts. This can be taken from
|
//! to their physical counterparts. This can be taken from
|
||||||
//! CPUContextX86::Fxsave::fsw.
|
//! CPUContextX86::Fxsave::fsw.
|
||||||
@ -87,6 +140,16 @@ struct CPUContextX86 {
|
|||||||
static uint16_t FxsaveToFsaveTagWord(
|
static uint16_t FxsaveToFsaveTagWord(
|
||||||
uint16_t fsw, uint8_t fxsave_tag, const X87OrMMXRegister st_mm[8]);
|
uint16_t fsw, uint8_t fxsave_tag, const X87OrMMXRegister st_mm[8]);
|
||||||
|
|
||||||
|
//! \brief Converts x87 floating-point tag words from `fsave` (full, 16-bit)
|
||||||
|
//! to `fxsave` (abridged, 8-bit) form.
|
||||||
|
//!
|
||||||
|
//! This function performs the inverse operation of FxsaveToFsaveTagWord().
|
||||||
|
//!
|
||||||
|
//! \param[in] fsave_tag The full FPU tag word.
|
||||||
|
//!
|
||||||
|
//! \return The abridged FPU tag word.
|
||||||
|
static uint8_t FsaveToFxsaveTagWord(uint16_t fsave_tag);
|
||||||
|
|
||||||
// Integer registers.
|
// Integer registers.
|
||||||
uint32_t eax;
|
uint32_t eax;
|
||||||
uint32_t ebx;
|
uint32_t ebx;
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "base/macros.h"
|
#include "base/macros.h"
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
#include "test/hex_string.h"
|
||||||
|
|
||||||
namespace crashpad {
|
namespace crashpad {
|
||||||
namespace test {
|
namespace test {
|
||||||
@ -48,37 +49,188 @@ enum FractionValue {
|
|||||||
//! \param[in] j_bit The value to use for the “J bit” (“integer bit”).
|
//! \param[in] j_bit The value to use for the “J bit” (“integer bit”).
|
||||||
//! \param[in] fraction_value If kFractionAllZero, the fraction will be zeroed
|
//! \param[in] fraction_value If kFractionAllZero, the fraction will be zeroed
|
||||||
//! out. If kFractionNormal, the fraction will not be all zeroes.
|
//! out. If kFractionNormal, the fraction will not be all zeroes.
|
||||||
void SetX87Register(CPUContextX86::X87OrMMXRegister* st_mm,
|
void SetX87Register(CPUContextX86::X87Register* st,
|
||||||
ExponentValue exponent_value,
|
ExponentValue exponent_value,
|
||||||
bool j_bit,
|
bool j_bit,
|
||||||
FractionValue fraction_value) {
|
FractionValue fraction_value) {
|
||||||
switch (exponent_value) {
|
switch (exponent_value) {
|
||||||
case kExponentAllZero:
|
case kExponentAllZero:
|
||||||
st_mm->st[9] = 0x80;
|
(*st)[9] = 0x80;
|
||||||
st_mm->st[8] = 0;
|
(*st)[8] = 0;
|
||||||
break;
|
break;
|
||||||
case kExponentAllOne:
|
case kExponentAllOne:
|
||||||
st_mm->st[9] = 0x7f;
|
(*st)[9] = 0x7f;
|
||||||
st_mm->st[8] = 0xff;
|
(*st)[8] = 0xff;
|
||||||
break;
|
break;
|
||||||
case kExponentNormal:
|
case kExponentNormal:
|
||||||
st_mm->st[9] = 0x55;
|
(*st)[9] = 0x55;
|
||||||
st_mm->st[8] = 0x55;
|
(*st)[8] = 0x55;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t fraction_pattern = fraction_value == kFractionAllZero ? 0 : 0x55;
|
uint8_t fraction_pattern = fraction_value == kFractionAllZero ? 0 : 0x55;
|
||||||
memset(&st_mm->st[0], fraction_pattern, 8);
|
memset(st, fraction_pattern, 8);
|
||||||
|
|
||||||
if (j_bit) {
|
if (j_bit) {
|
||||||
st_mm->st[7] |= 0x80;
|
(*st)[7] |= 0x80;
|
||||||
} else {
|
} else {
|
||||||
st_mm->st[7] &= ~0x80;
|
(*st)[7] &= ~0x80;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//! \brief Initializes an x87 register to a known bit pattern.
|
||||||
|
//!
|
||||||
|
//! This behaves as SetX87Register() but also clears the reserved portion of the
|
||||||
|
//! field as used in the `fxsave` format.
|
||||||
|
void SetX87OrMMXRegister(CPUContextX86::X87OrMMXRegister* st_mm,
|
||||||
|
ExponentValue exponent_value,
|
||||||
|
bool j_bit,
|
||||||
|
FractionValue fraction_value) {
|
||||||
|
SetX87Register(&st_mm->st, exponent_value, j_bit, fraction_value);
|
||||||
memset(st_mm->st_reserved, 0, sizeof(st_mm->st_reserved));
|
memset(st_mm->st_reserved, 0, sizeof(st_mm->st_reserved));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(CPUContextX86, FxsaveToFsave) {
|
||||||
|
// Establish a somewhat plausible fxsave state. Use nonzero values for
|
||||||
|
// reserved fields and things that aren’t present in fsave.
|
||||||
|
CPUContextX86::Fxsave fxsave;
|
||||||
|
fxsave.fcw = 0x027f; // mask exceptions, 53-bit precision, round to nearest
|
||||||
|
fxsave.fsw = 1 << 11; // top = 1: logical 0-7 maps to physical 1-7, 0
|
||||||
|
fxsave.ftw = 0x1f; // physical 5-7 (logical 4-6) empty
|
||||||
|
fxsave.reserved_1 = 0x5a;
|
||||||
|
fxsave.fop = 0x1fe; // fsin
|
||||||
|
fxsave.fpu_ip = 0x76543210;
|
||||||
|
fxsave.fpu_cs = 0x0007;
|
||||||
|
fxsave.reserved_2 = 0x5a5a;
|
||||||
|
fxsave.fpu_dp = 0xfedcba98;
|
||||||
|
fxsave.fpu_ds = 0x000f;
|
||||||
|
fxsave.reserved_3 = 0x5a5a;
|
||||||
|
fxsave.mxcsr = 0x1f80;
|
||||||
|
fxsave.mxcsr_mask = 0xffff;
|
||||||
|
SetX87Register(
|
||||||
|
&fxsave.st_mm[0].st, kExponentNormal, true, kFractionAllZero); // valid
|
||||||
|
SetX87Register(
|
||||||
|
&fxsave.st_mm[1].st, kExponentAllZero, false, kFractionAllZero); // zero
|
||||||
|
SetX87Register(
|
||||||
|
&fxsave.st_mm[2].st, kExponentAllOne, true, kFractionAllZero); // spec.
|
||||||
|
SetX87Register(
|
||||||
|
&fxsave.st_mm[3].st, kExponentAllOne, true, kFractionNormal); // spec.
|
||||||
|
SetX87Register(
|
||||||
|
&fxsave.st_mm[4].st, kExponentAllZero, false, kFractionAllZero);
|
||||||
|
SetX87Register(
|
||||||
|
&fxsave.st_mm[5].st, kExponentAllZero, false, kFractionAllZero);
|
||||||
|
SetX87Register(
|
||||||
|
&fxsave.st_mm[6].st, kExponentAllZero, false, kFractionAllZero);
|
||||||
|
SetX87Register(
|
||||||
|
&fxsave.st_mm[7].st, kExponentNormal, true, kFractionNormal); // valid
|
||||||
|
for (size_t index = 0; index < arraysize(fxsave.st_mm); ++index) {
|
||||||
|
memset(&fxsave.st_mm[index].st_reserved,
|
||||||
|
0x5a,
|
||||||
|
sizeof(fxsave.st_mm[index].st_reserved));
|
||||||
|
}
|
||||||
|
memset(&fxsave.xmm, 0x5a, sizeof(fxsave) - offsetof(decltype(fxsave), xmm));
|
||||||
|
|
||||||
|
CPUContextX86::Fsave fsave;
|
||||||
|
CPUContextX86::FxsaveToFsave(fxsave, &fsave);
|
||||||
|
|
||||||
|
// Everything should have come over from fxsave. Reserved fields should be
|
||||||
|
// zero.
|
||||||
|
EXPECT_EQ(fxsave.fcw, fsave.fcw);
|
||||||
|
EXPECT_EQ(0, fsave.reserved_1);
|
||||||
|
EXPECT_EQ(fxsave.fsw, fsave.fsw);
|
||||||
|
EXPECT_EQ(0, fsave.reserved_2);
|
||||||
|
EXPECT_EQ(0xfe90, fsave.ftw); // FxsaveToFsaveTagWord
|
||||||
|
EXPECT_EQ(0, fsave.reserved_3);
|
||||||
|
EXPECT_EQ(fxsave.fpu_ip, fsave.fpu_ip);
|
||||||
|
EXPECT_EQ(fxsave.fpu_cs, fsave.fpu_cs);
|
||||||
|
EXPECT_EQ(fxsave.fop, fsave.fop);
|
||||||
|
EXPECT_EQ(fxsave.fpu_dp, fsave.fpu_dp);
|
||||||
|
EXPECT_EQ(fxsave.fpu_ds, fsave.fpu_ds);
|
||||||
|
EXPECT_EQ(0, fsave.reserved_4);
|
||||||
|
for (size_t index = 0; index < arraysize(fsave.st); ++index) {
|
||||||
|
EXPECT_EQ(BytesToHexString(fxsave.st_mm[index].st,
|
||||||
|
arraysize(fxsave.st_mm[index].st)),
|
||||||
|
BytesToHexString(fsave.st[index], arraysize(fsave.st[index])))
|
||||||
|
<< "index " << index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CPUContextX86, FsaveToFxsave) {
|
||||||
|
// Establish a somewhat plausible fsave state. Use nonzero values for
|
||||||
|
// reserved fields.
|
||||||
|
CPUContextX86::Fsave fsave;
|
||||||
|
fsave.fcw = 0x0300; // unmask exceptions, 64-bit precision, round to nearest
|
||||||
|
fsave.reserved_1 = 0xa5a5;
|
||||||
|
fsave.fsw = 2 << 11; // top = 2: logical 0-7 maps to physical 2-7, 0-1
|
||||||
|
fsave.reserved_2 = 0xa5a5;
|
||||||
|
fsave.ftw = 0xa9ff; // physical 0-3 (logical 6-7, 0-1) empty; physical 4
|
||||||
|
// (logical 2) zero; physical 5-7 (logical 3-5) special
|
||||||
|
fsave.reserved_3 = 0xa5a5;
|
||||||
|
fsave.fpu_ip = 0x456789ab;
|
||||||
|
fsave.fpu_cs = 0x1013;
|
||||||
|
fsave.fop = 0x01ee; // fldz
|
||||||
|
fsave.fpu_dp = 0x0123cdef;
|
||||||
|
fsave.fpu_ds = 0x2017;
|
||||||
|
fsave.reserved_4 = 0xa5a5;
|
||||||
|
SetX87Register(&fsave.st[0], kExponentAllZero, false, kFractionNormal);
|
||||||
|
SetX87Register(&fsave.st[1], kExponentAllZero, true, kFractionNormal);
|
||||||
|
SetX87Register(
|
||||||
|
&fsave.st[2], kExponentAllZero, false, kFractionAllZero); // zero
|
||||||
|
SetX87Register(
|
||||||
|
&fsave.st[3], kExponentAllZero, true, kFractionAllZero); // spec.
|
||||||
|
SetX87Register(
|
||||||
|
&fsave.st[4], kExponentAllZero, false, kFractionNormal); // spec.
|
||||||
|
SetX87Register(
|
||||||
|
&fsave.st[5], kExponentAllZero, true, kFractionNormal); // spec.
|
||||||
|
SetX87Register(&fsave.st[6], kExponentAllZero, false, kFractionAllZero);
|
||||||
|
SetX87Register(&fsave.st[7], kExponentAllZero, true, kFractionAllZero);
|
||||||
|
|
||||||
|
CPUContextX86::Fxsave fxsave;
|
||||||
|
CPUContextX86::FsaveToFxsave(fsave, &fxsave);
|
||||||
|
|
||||||
|
// Everything in fsave should have come over from there. Fields not present in
|
||||||
|
// fsave and reserved fields should be zero.
|
||||||
|
EXPECT_EQ(fsave.fcw, fxsave.fcw);
|
||||||
|
EXPECT_EQ(fsave.fsw, fxsave.fsw);
|
||||||
|
EXPECT_EQ(0xf0, fxsave.ftw); // FsaveToFxsaveTagWord
|
||||||
|
EXPECT_EQ(0, fxsave.reserved_1);
|
||||||
|
EXPECT_EQ(fsave.fop, fxsave.fop);
|
||||||
|
EXPECT_EQ(fsave.fpu_ip, fxsave.fpu_ip);
|
||||||
|
EXPECT_EQ(fsave.fpu_cs, fxsave.fpu_cs);
|
||||||
|
EXPECT_EQ(0, fxsave.reserved_2);
|
||||||
|
EXPECT_EQ(fsave.fpu_dp, fxsave.fpu_dp);
|
||||||
|
EXPECT_EQ(fsave.fpu_ds, fxsave.fpu_ds);
|
||||||
|
EXPECT_EQ(0, fxsave.reserved_3);
|
||||||
|
EXPECT_EQ(0u, fxsave.mxcsr);
|
||||||
|
EXPECT_EQ(0u, fxsave.mxcsr_mask);
|
||||||
|
for (size_t index = 0; index < arraysize(fxsave.st_mm); ++index) {
|
||||||
|
EXPECT_EQ(BytesToHexString(fsave.st[index], arraysize(fsave.st[index])),
|
||||||
|
BytesToHexString(fxsave.st_mm[index].st,
|
||||||
|
arraysize(fxsave.st_mm[index].st)))
|
||||||
|
<< "index " << index;
|
||||||
|
EXPECT_EQ(std::string(arraysize(fxsave.st_mm[index].st_reserved) * 2, '0'),
|
||||||
|
BytesToHexString(fxsave.st_mm[index].st_reserved,
|
||||||
|
arraysize(fxsave.st_mm[index].st_reserved)))
|
||||||
|
<< "index " << index;
|
||||||
|
}
|
||||||
|
size_t unused_len = sizeof(fxsave) - offsetof(decltype(fxsave), xmm);
|
||||||
|
EXPECT_EQ(std::string(unused_len * 2, '0'),
|
||||||
|
BytesToHexString(fxsave.xmm, unused_len));
|
||||||
|
|
||||||
|
// Since the fsave format is a subset of the fxsave format, fsave-fxsave-fsave
|
||||||
|
// should round-trip cleanly.
|
||||||
|
CPUContextX86::Fsave fsave_2;
|
||||||
|
CPUContextX86::FxsaveToFsave(fxsave, &fsave_2);
|
||||||
|
|
||||||
|
// Clear the reserved fields in the original fsave structure, since they’re
|
||||||
|
// expected to be clear in the copy.
|
||||||
|
fsave.reserved_1 = 0;
|
||||||
|
fsave.reserved_2 = 0;
|
||||||
|
fsave.reserved_3 = 0;
|
||||||
|
fsave.reserved_4 = 0;
|
||||||
|
EXPECT_EQ(0, memcmp(&fsave, &fsave_2, sizeof(fsave)));
|
||||||
|
}
|
||||||
|
|
||||||
TEST(CPUContextX86, FxsaveToFsaveTagWord) {
|
TEST(CPUContextX86, FxsaveToFsaveTagWord) {
|
||||||
// The fsave tag word uses bit pattern 00 for valid, 01 for zero, 10 for
|
// The fsave tag word uses bit pattern 00 for valid, 01 for zero, 10 for
|
||||||
// “special”, and 11 for empty. Like the fxsave tag word, it is arranged by
|
// “special”, and 11 for empty. Like the fxsave tag word, it is arranged by
|
||||||
@ -93,40 +245,52 @@ TEST(CPUContextX86, FxsaveToFsaveTagWord) {
|
|||||||
uint16_t fsw = 0 << 11; // top = 0: logical 0-7 maps to physical 0-7
|
uint16_t fsw = 0 << 11; // top = 0: logical 0-7 maps to physical 0-7
|
||||||
uint8_t fxsave_tag = 0x0f; // physical 4-7 (logical 4-7) empty
|
uint8_t fxsave_tag = 0x0f; // physical 4-7 (logical 4-7) empty
|
||||||
CPUContextX86::X87OrMMXRegister st_mm[8];
|
CPUContextX86::X87OrMMXRegister st_mm[8];
|
||||||
SetX87Register(&st_mm[0], kExponentNormal, false, kFractionNormal); // spec.
|
SetX87OrMMXRegister(
|
||||||
SetX87Register(&st_mm[1], kExponentNormal, true, kFractionNormal); // valid
|
&st_mm[0], kExponentNormal, false, kFractionNormal); // spec.
|
||||||
SetX87Register(&st_mm[2], kExponentNormal, false, kFractionAllZero); // spec.
|
SetX87OrMMXRegister(
|
||||||
SetX87Register(&st_mm[3], kExponentNormal, true, kFractionAllZero); // valid
|
&st_mm[1], kExponentNormal, true, kFractionNormal); // valid
|
||||||
SetX87Register(&st_mm[4], kExponentNormal, false, kFractionNormal);
|
SetX87OrMMXRegister(
|
||||||
SetX87Register(&st_mm[5], kExponentNormal, true, kFractionNormal);
|
&st_mm[2], kExponentNormal, false, kFractionAllZero); // spec.
|
||||||
SetX87Register(&st_mm[6], kExponentNormal, false, kFractionAllZero);
|
SetX87OrMMXRegister(
|
||||||
SetX87Register(&st_mm[7], kExponentNormal, true, kFractionAllZero);
|
&st_mm[3], kExponentNormal, true, kFractionAllZero); // valid
|
||||||
|
SetX87OrMMXRegister(&st_mm[4], kExponentNormal, false, kFractionNormal);
|
||||||
|
SetX87OrMMXRegister(&st_mm[5], kExponentNormal, true, kFractionNormal);
|
||||||
|
SetX87OrMMXRegister(&st_mm[6], kExponentNormal, false, kFractionAllZero);
|
||||||
|
SetX87OrMMXRegister(&st_mm[7], kExponentNormal, true, kFractionAllZero);
|
||||||
EXPECT_EQ(0xff22,
|
EXPECT_EQ(0xff22,
|
||||||
CPUContextX86::FxsaveToFsaveTagWord(fsw, fxsave_tag, st_mm));
|
CPUContextX86::FxsaveToFsaveTagWord(fsw, fxsave_tag, st_mm));
|
||||||
|
|
||||||
fsw = 2 << 11; // top = 2: logical 0-7 maps to physical 2-7, 0-1
|
fsw = 2 << 11; // top = 2: logical 0-7 maps to physical 2-7, 0-1
|
||||||
fxsave_tag = 0xf0; // physical 0-3 (logical 6-7, 0-1) empty
|
fxsave_tag = 0xf0; // physical 0-3 (logical 6-7, 0-1) empty
|
||||||
SetX87Register(&st_mm[0], kExponentAllZero, false, kFractionNormal);
|
SetX87OrMMXRegister(&st_mm[0], kExponentAllZero, false, kFractionNormal);
|
||||||
SetX87Register(&st_mm[1], kExponentAllZero, true, kFractionNormal);
|
SetX87OrMMXRegister(&st_mm[1], kExponentAllZero, true, kFractionNormal);
|
||||||
SetX87Register(&st_mm[2], kExponentAllZero, false, kFractionAllZero); // zero
|
SetX87OrMMXRegister(
|
||||||
SetX87Register(&st_mm[3], kExponentAllZero, true, kFractionAllZero); // spec.
|
&st_mm[2], kExponentAllZero, false, kFractionAllZero); // zero
|
||||||
SetX87Register(&st_mm[4], kExponentAllZero, false, kFractionNormal); // spec.
|
SetX87OrMMXRegister(
|
||||||
SetX87Register(&st_mm[5], kExponentAllZero, true, kFractionNormal); // spec.
|
&st_mm[3], kExponentAllZero, true, kFractionAllZero); // spec.
|
||||||
SetX87Register(&st_mm[6], kExponentAllZero, false, kFractionAllZero);
|
SetX87OrMMXRegister(
|
||||||
SetX87Register(&st_mm[7], kExponentAllZero, true, kFractionAllZero);
|
&st_mm[4], kExponentAllZero, false, kFractionNormal); // spec.
|
||||||
|
SetX87OrMMXRegister(
|
||||||
|
&st_mm[5], kExponentAllZero, true, kFractionNormal); // spec.
|
||||||
|
SetX87OrMMXRegister(&st_mm[6], kExponentAllZero, false, kFractionAllZero);
|
||||||
|
SetX87OrMMXRegister(&st_mm[7], kExponentAllZero, true, kFractionAllZero);
|
||||||
EXPECT_EQ(0xa9ff,
|
EXPECT_EQ(0xa9ff,
|
||||||
CPUContextX86::FxsaveToFsaveTagWord(fsw, fxsave_tag, st_mm));
|
CPUContextX86::FxsaveToFsaveTagWord(fsw, fxsave_tag, st_mm));
|
||||||
|
|
||||||
fsw = 5 << 11; // top = 5: logical 0-7 maps to physical 5-7, 0-4
|
fsw = 5 << 11; // top = 5: logical 0-7 maps to physical 5-7, 0-4
|
||||||
fxsave_tag = 0x5a; // physical 0, 2, 5, and 7 (logical 5, 0, 2, and 3) empty
|
fxsave_tag = 0x5a; // physical 0, 2, 5, and 7 (logical 5, 0, 2, and 3) empty
|
||||||
SetX87Register(&st_mm[0], kExponentAllOne, false, kFractionNormal);
|
SetX87OrMMXRegister(&st_mm[0], kExponentAllOne, false, kFractionNormal);
|
||||||
SetX87Register(&st_mm[1], kExponentAllOne, true, kFractionNormal); // spec.
|
SetX87OrMMXRegister(
|
||||||
SetX87Register(&st_mm[2], kExponentAllOne, false, kFractionAllZero);
|
&st_mm[1], kExponentAllOne, true, kFractionNormal); // spec.
|
||||||
SetX87Register(&st_mm[3], kExponentAllOne, true, kFractionAllZero);
|
SetX87OrMMXRegister(&st_mm[2], kExponentAllOne, false, kFractionAllZero);
|
||||||
SetX87Register(&st_mm[4], kExponentAllOne, false, kFractionNormal); // spec.
|
SetX87OrMMXRegister(&st_mm[3], kExponentAllOne, true, kFractionAllZero);
|
||||||
SetX87Register(&st_mm[5], kExponentAllOne, true, kFractionNormal);
|
SetX87OrMMXRegister(
|
||||||
SetX87Register(&st_mm[6], kExponentAllOne, false, kFractionAllZero); // spec.
|
&st_mm[4], kExponentAllOne, false, kFractionNormal); // spec.
|
||||||
SetX87Register(&st_mm[7], kExponentAllOne, true, kFractionAllZero); // spec.
|
SetX87OrMMXRegister(&st_mm[5], kExponentAllOne, true, kFractionNormal);
|
||||||
|
SetX87OrMMXRegister(
|
||||||
|
&st_mm[6], kExponentAllOne, false, kFractionAllZero); // spec.
|
||||||
|
SetX87OrMMXRegister(
|
||||||
|
&st_mm[7], kExponentAllOne, true, kFractionAllZero); // spec.
|
||||||
EXPECT_EQ(0xeebb,
|
EXPECT_EQ(0xeebb,
|
||||||
CPUContextX86::FxsaveToFsaveTagWord(fsw, fxsave_tag, st_mm));
|
CPUContextX86::FxsaveToFsaveTagWord(fsw, fxsave_tag, st_mm));
|
||||||
|
|
||||||
@ -134,14 +298,19 @@ TEST(CPUContextX86, FxsaveToFsaveTagWord) {
|
|||||||
// register file.
|
// register file.
|
||||||
fsw = 1 << 11; // top = 1: logical 0-7 maps to physical 1-7, 0
|
fsw = 1 << 11; // top = 1: logical 0-7 maps to physical 1-7, 0
|
||||||
fxsave_tag = 0x1f; // physical 5-7 (logical 4-6) empty
|
fxsave_tag = 0x1f; // physical 5-7 (logical 4-6) empty
|
||||||
SetX87Register(&st_mm[0], kExponentNormal, true, kFractionAllZero); // valid
|
SetX87OrMMXRegister(
|
||||||
SetX87Register(&st_mm[1], kExponentAllZero, false, kFractionAllZero); // zero
|
&st_mm[0], kExponentNormal, true, kFractionAllZero); // valid
|
||||||
SetX87Register(&st_mm[2], kExponentAllOne, true, kFractionAllZero); // spec.
|
SetX87OrMMXRegister(
|
||||||
SetX87Register(&st_mm[3], kExponentAllOne, true, kFractionNormal); // spec.
|
&st_mm[1], kExponentAllZero, false, kFractionAllZero); // zero
|
||||||
SetX87Register(&st_mm[4], kExponentAllZero, false, kFractionAllZero);
|
SetX87OrMMXRegister(
|
||||||
SetX87Register(&st_mm[5], kExponentAllZero, false, kFractionAllZero);
|
&st_mm[2], kExponentAllOne, true, kFractionAllZero); // spec.
|
||||||
SetX87Register(&st_mm[6], kExponentAllZero, false, kFractionAllZero);
|
SetX87OrMMXRegister(
|
||||||
SetX87Register(&st_mm[7], kExponentNormal, true, kFractionNormal); // valid
|
&st_mm[3], kExponentAllOne, true, kFractionNormal); // spec.
|
||||||
|
SetX87OrMMXRegister(&st_mm[4], kExponentAllZero, false, kFractionAllZero);
|
||||||
|
SetX87OrMMXRegister(&st_mm[5], kExponentAllZero, false, kFractionAllZero);
|
||||||
|
SetX87OrMMXRegister(&st_mm[6], kExponentAllZero, false, kFractionAllZero);
|
||||||
|
SetX87OrMMXRegister(
|
||||||
|
&st_mm[7], kExponentNormal, true, kFractionNormal); // valid
|
||||||
EXPECT_EQ(0xfe90,
|
EXPECT_EQ(0xfe90,
|
||||||
CPUContextX86::FxsaveToFsaveTagWord(fsw, fxsave_tag, st_mm));
|
CPUContextX86::FxsaveToFsaveTagWord(fsw, fxsave_tag, st_mm));
|
||||||
|
|
||||||
@ -149,7 +318,7 @@ TEST(CPUContextX86, FxsaveToFsaveTagWord) {
|
|||||||
fsw = 0 << 11; // top = 0: logical 0-7 maps to physical 0-7
|
fsw = 0 << 11; // top = 0: logical 0-7 maps to physical 0-7
|
||||||
fxsave_tag = 0xff; // nothing empty
|
fxsave_tag = 0xff; // nothing empty
|
||||||
for (size_t index = 0; index < arraysize(st_mm); ++index) {
|
for (size_t index = 0; index < arraysize(st_mm); ++index) {
|
||||||
SetX87Register(&st_mm[index], kExponentNormal, true, kFractionAllZero);
|
SetX87OrMMXRegister(&st_mm[index], kExponentNormal, true, kFractionAllZero);
|
||||||
}
|
}
|
||||||
EXPECT_EQ(0, CPUContextX86::FxsaveToFsaveTagWord(fsw, fxsave_tag, st_mm));
|
EXPECT_EQ(0, CPUContextX86::FxsaveToFsaveTagWord(fsw, fxsave_tag, st_mm));
|
||||||
|
|
||||||
@ -161,6 +330,17 @@ TEST(CPUContextX86, FxsaveToFsaveTagWord) {
|
|||||||
CPUContextX86::FxsaveToFsaveTagWord(fsw, fxsave_tag, st_mm));
|
CPUContextX86::FxsaveToFsaveTagWord(fsw, fxsave_tag, st_mm));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(CPUContextX86, FsaveToFxsaveTagWord) {
|
||||||
|
// The register sets that these x87 tag words might apply to are given in the
|
||||||
|
// FxsaveToFsaveTagWord test above.
|
||||||
|
EXPECT_EQ(0x0f, CPUContextX86::FsaveToFxsaveTagWord(0xff22));
|
||||||
|
EXPECT_EQ(0xf0, CPUContextX86::FsaveToFxsaveTagWord(0xa9ff));
|
||||||
|
EXPECT_EQ(0x5a, CPUContextX86::FsaveToFxsaveTagWord(0xeebb));
|
||||||
|
EXPECT_EQ(0x1f, CPUContextX86::FsaveToFxsaveTagWord(0xfe90));
|
||||||
|
EXPECT_EQ(0xff, CPUContextX86::FsaveToFxsaveTagWord(0x0000));
|
||||||
|
EXPECT_EQ(0x00, CPUContextX86::FsaveToFxsaveTagWord(0xffff));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
} // namespace test
|
} // namespace test
|
||||||
} // namespace crashpad
|
} // namespace crashpad
|
||||||
|
@ -24,16 +24,53 @@ namespace crashpad {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
// Validation for casts used with CPUContextX86::FsaveToFxsave().
|
||||||
|
static_assert(sizeof(CPUContextX86::Fsave) ==
|
||||||
|
offsetof(WOW64_FLOATING_SAVE_AREA, Cr0NpxState),
|
||||||
|
"WoW64 fsave types must be equivalent");
|
||||||
|
#if defined(ARCH_CPU_X86)
|
||||||
|
static_assert(sizeof(CPUContextX86::Fsave) ==
|
||||||
|
offsetof(FLOATING_SAVE_AREA, Spare0),
|
||||||
|
"fsave types must be equivalent");
|
||||||
|
#endif // ARCH_CPU_X86
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool HasContextPart(const T& context, uint32_t bits) {
|
||||||
|
return (context.ContextFlags & bits) == bits;
|
||||||
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
void CommonInitializeX86Context(const T& context, CPUContextX86* out) {
|
void CommonInitializeX86Context(const T& context, CPUContextX86* out) {
|
||||||
LOG_IF(ERROR, !(context.ContextFlags & WOW64_CONTEXT_i386))
|
// This function assumes that the WOW64_CONTEXT_* and x86 CONTEXT_* values
|
||||||
<< "non-x86 context";
|
// for ContextFlags are identical. This can be tested when targeting 32-bit
|
||||||
|
// x86.
|
||||||
|
#if defined(ARCH_CPU_X86)
|
||||||
|
static_assert(sizeof(CONTEXT) == sizeof(WOW64_CONTEXT),
|
||||||
|
"type mismatch: CONTEXT");
|
||||||
|
#define ASSERT_WOW64_EQUIVALENT(x) \
|
||||||
|
do { \
|
||||||
|
static_assert(x == WOW64_##x, "value mismatch: " #x); \
|
||||||
|
} while (false)
|
||||||
|
ASSERT_WOW64_EQUIVALENT(CONTEXT_i386);
|
||||||
|
ASSERT_WOW64_EQUIVALENT(CONTEXT_i486);
|
||||||
|
ASSERT_WOW64_EQUIVALENT(CONTEXT_CONTROL);
|
||||||
|
ASSERT_WOW64_EQUIVALENT(CONTEXT_INTEGER);
|
||||||
|
ASSERT_WOW64_EQUIVALENT(CONTEXT_SEGMENTS);
|
||||||
|
ASSERT_WOW64_EQUIVALENT(CONTEXT_FLOATING_POINT);
|
||||||
|
ASSERT_WOW64_EQUIVALENT(CONTEXT_DEBUG_REGISTERS);
|
||||||
|
ASSERT_WOW64_EQUIVALENT(CONTEXT_EXTENDED_REGISTERS);
|
||||||
|
ASSERT_WOW64_EQUIVALENT(CONTEXT_FULL);
|
||||||
|
ASSERT_WOW64_EQUIVALENT(CONTEXT_ALL);
|
||||||
|
ASSERT_WOW64_EQUIVALENT(CONTEXT_XSTATE);
|
||||||
|
#undef ASSERT_WOW64_EQUIVALENT
|
||||||
|
#endif // ARCH_CPU_X86
|
||||||
|
|
||||||
memset(out, 0, sizeof(*out));
|
memset(out, 0, sizeof(*out));
|
||||||
|
|
||||||
// We assume in this function that the WOW64_CONTEXT_* and x86 CONTEXT_*
|
LOG_IF(ERROR, !HasContextPart(context, WOW64_CONTEXT_i386))
|
||||||
// values for ContextFlags are identical.
|
<< "non-x86 context";
|
||||||
|
|
||||||
if (context.ContextFlags & WOW64_CONTEXT_CONTROL) {
|
if (HasContextPart(context, WOW64_CONTEXT_CONTROL)) {
|
||||||
out->ebp = context.Ebp;
|
out->ebp = context.Ebp;
|
||||||
out->eip = context.Eip;
|
out->eip = context.Eip;
|
||||||
out->cs = static_cast<uint16_t>(context.SegCs);
|
out->cs = static_cast<uint16_t>(context.SegCs);
|
||||||
@ -42,7 +79,7 @@ void CommonInitializeX86Context(const T& context, CPUContextX86* out) {
|
|||||||
out->ss = static_cast<uint16_t>(context.SegSs);
|
out->ss = static_cast<uint16_t>(context.SegSs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.ContextFlags & WOW64_CONTEXT_INTEGER) {
|
if (HasContextPart(context, WOW64_CONTEXT_INTEGER)) {
|
||||||
out->eax = context.Eax;
|
out->eax = context.Eax;
|
||||||
out->ebx = context.Ebx;
|
out->ebx = context.Ebx;
|
||||||
out->ecx = context.Ecx;
|
out->ecx = context.Ecx;
|
||||||
@ -51,32 +88,38 @@ void CommonInitializeX86Context(const T& context, CPUContextX86* out) {
|
|||||||
out->esi = context.Esi;
|
out->esi = context.Esi;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.ContextFlags & WOW64_CONTEXT_SEGMENTS) {
|
if (HasContextPart(context, WOW64_CONTEXT_SEGMENTS)) {
|
||||||
out->ds = static_cast<uint16_t>(context.SegDs);
|
out->ds = static_cast<uint16_t>(context.SegDs);
|
||||||
out->es = static_cast<uint16_t>(context.SegEs);
|
out->es = static_cast<uint16_t>(context.SegEs);
|
||||||
out->fs = static_cast<uint16_t>(context.SegFs);
|
out->fs = static_cast<uint16_t>(context.SegFs);
|
||||||
out->gs = static_cast<uint16_t>(context.SegGs);
|
out->gs = static_cast<uint16_t>(context.SegGs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.ContextFlags & WOW64_CONTEXT_DEBUG_REGISTERS) {
|
if (HasContextPart(context, WOW64_CONTEXT_DEBUG_REGISTERS)) {
|
||||||
out->dr0 = context.Dr0;
|
out->dr0 = context.Dr0;
|
||||||
out->dr1 = context.Dr1;
|
out->dr1 = context.Dr1;
|
||||||
out->dr2 = context.Dr2;
|
out->dr2 = context.Dr2;
|
||||||
out->dr3 = context.Dr3;
|
out->dr3 = context.Dr3;
|
||||||
|
|
||||||
// DR4 and DR5 are obsolete synonyms for DR6 and DR7, see
|
// DR4 and DR5 are obsolete synonyms for DR6 and DR7, see
|
||||||
// https://en.wikipedia.org/wiki/X86_debug_register.
|
// https://en.wikipedia.org/wiki/X86_debug_register.
|
||||||
out->dr4 = context.Dr6;
|
out->dr4 = context.Dr6;
|
||||||
out->dr5 = context.Dr7;
|
out->dr5 = context.Dr7;
|
||||||
|
|
||||||
out->dr6 = context.Dr6;
|
out->dr6 = context.Dr6;
|
||||||
out->dr7 = context.Dr7;
|
out->dr7 = context.Dr7;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.ContextFlags & WOW64_CONTEXT_EXTENDED_REGISTERS) {
|
if (HasContextPart(context, WOW64_CONTEXT_EXTENDED_REGISTERS)) {
|
||||||
static_assert(sizeof(out->fxsave) == sizeof(context.ExtendedRegisters),
|
static_assert(sizeof(out->fxsave) == sizeof(context.ExtendedRegisters),
|
||||||
"types must be equivalent");
|
"fxsave types must be equivalent");
|
||||||
memcpy(&out->fxsave, &context.ExtendedRegisters, sizeof(out->fxsave));
|
memcpy(&out->fxsave, &context.ExtendedRegisters, sizeof(out->fxsave));
|
||||||
} else if (context.ContextFlags & WOW64_CONTEXT_FLOATING_POINT) {
|
} else if (HasContextPart(context, WOW64_CONTEXT_FLOATING_POINT)) {
|
||||||
CHECK(false) << "TODO(scottmg): extract x87 data";
|
// The static_assert that validates this cast can’t be here because it
|
||||||
|
// relies on field names that vary based on the template parameter.
|
||||||
|
CPUContextX86::FsaveToFxsave(
|
||||||
|
*reinterpret_cast<const CPUContextX86::Fsave*>(&context.FloatSave),
|
||||||
|
&out->fxsave);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,9 +134,9 @@ void InitializeX86Context(const WOW64_CONTEXT& context, CPUContextX86* out) {
|
|||||||
void InitializeX64Context(const CONTEXT& context, CPUContextX86_64* out) {
|
void InitializeX64Context(const CONTEXT& context, CPUContextX86_64* out) {
|
||||||
memset(out, 0, sizeof(*out));
|
memset(out, 0, sizeof(*out));
|
||||||
|
|
||||||
LOG_IF(ERROR, !(context.ContextFlags & CONTEXT_AMD64)) << "non-x64 context";
|
LOG_IF(ERROR, !HasContextPart(context, CONTEXT_AMD64)) << "non-x64 context";
|
||||||
|
|
||||||
if (context.ContextFlags & CONTEXT_CONTROL) {
|
if (HasContextPart(context, CONTEXT_CONTROL)) {
|
||||||
out->cs = context.SegCs;
|
out->cs = context.SegCs;
|
||||||
out->rflags = context.EFlags;
|
out->rflags = context.EFlags;
|
||||||
out->rip = context.Rip;
|
out->rip = context.Rip;
|
||||||
@ -101,7 +144,7 @@ void InitializeX64Context(const CONTEXT& context, CPUContextX86_64* out) {
|
|||||||
// SegSs ignored.
|
// SegSs ignored.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.ContextFlags & CONTEXT_INTEGER) {
|
if (HasContextPart(context, CONTEXT_INTEGER)) {
|
||||||
out->rax = context.Rax;
|
out->rax = context.Rax;
|
||||||
out->rbx = context.Rbx;
|
out->rbx = context.Rbx;
|
||||||
out->rcx = context.Rcx;
|
out->rcx = context.Rcx;
|
||||||
@ -119,30 +162,32 @@ void InitializeX64Context(const CONTEXT& context, CPUContextX86_64* out) {
|
|||||||
out->r15 = context.R15;
|
out->r15 = context.R15;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.ContextFlags & CONTEXT_SEGMENTS) {
|
if (HasContextPart(context, CONTEXT_SEGMENTS)) {
|
||||||
out->fs = context.SegFs;
|
out->fs = context.SegFs;
|
||||||
out->gs = context.SegGs;
|
out->gs = context.SegGs;
|
||||||
// SegDs ignored.
|
// SegDs ignored.
|
||||||
// SegEs ignored.
|
// SegEs ignored.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.ContextFlags & CONTEXT_DEBUG_REGISTERS) {
|
if (HasContextPart(context, CONTEXT_DEBUG_REGISTERS)) {
|
||||||
out->dr0 = context.Dr0;
|
out->dr0 = context.Dr0;
|
||||||
out->dr1 = context.Dr1;
|
out->dr1 = context.Dr1;
|
||||||
out->dr2 = context.Dr2;
|
out->dr2 = context.Dr2;
|
||||||
out->dr3 = context.Dr3;
|
out->dr3 = context.Dr3;
|
||||||
|
|
||||||
// DR4 and DR5 are obsolete synonyms for DR6 and DR7, see
|
// DR4 and DR5 are obsolete synonyms for DR6 and DR7, see
|
||||||
// https://en.wikipedia.org/wiki/X86_debug_register.
|
// https://en.wikipedia.org/wiki/X86_debug_register.
|
||||||
out->dr4 = context.Dr6;
|
out->dr4 = context.Dr6;
|
||||||
out->dr5 = context.Dr7;
|
out->dr5 = context.Dr7;
|
||||||
|
|
||||||
out->dr6 = context.Dr6;
|
out->dr6 = context.Dr6;
|
||||||
out->dr7 = context.Dr7;
|
out->dr7 = context.Dr7;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.ContextFlags & CONTEXT_FLOATING_POINT) {
|
if (HasContextPart(context, CONTEXT_FLOATING_POINT)) {
|
||||||
static_assert(sizeof(out->fxsave) == sizeof(context.FltSave),
|
static_assert(sizeof(out->fxsave) == sizeof(context.FltSave),
|
||||||
"types must be equivalent");
|
"types must be equivalent");
|
||||||
memcpy(&out->fxsave, &context.FltSave.ControlWord, sizeof(out->fxsave));
|
memcpy(&out->fxsave, &context.FltSave, sizeof(out->fxsave));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,14 +16,92 @@
|
|||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "base/macros.h"
|
||||||
#include "build/build_config.h"
|
#include "build/build_config.h"
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
#include "test/hex_string.h"
|
||||||
#include "snapshot/cpu_context.h"
|
#include "snapshot/cpu_context.h"
|
||||||
|
|
||||||
namespace crashpad {
|
namespace crashpad {
|
||||||
namespace test {
|
namespace test {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void TestInitializeX86Context() {
|
||||||
|
T context = {0};
|
||||||
|
context.ContextFlags = WOW64_CONTEXT_INTEGER |
|
||||||
|
WOW64_CONTEXT_DEBUG_REGISTERS |
|
||||||
|
WOW64_CONTEXT_EXTENDED_REGISTERS;
|
||||||
|
context.Eax = 1;
|
||||||
|
context.Dr0 = 3;
|
||||||
|
context.ExtendedRegisters[4] = 2; // FTW
|
||||||
|
|
||||||
|
// Test the simple case, where everything in the CPUContextX86 argument is set
|
||||||
|
// directly from the supplied thread, float, and debug state parameters.
|
||||||
|
{
|
||||||
|
CPUContextX86 cpu_context_x86 = {};
|
||||||
|
InitializeX86Context(context, &cpu_context_x86);
|
||||||
|
EXPECT_EQ(1u, cpu_context_x86.eax);
|
||||||
|
EXPECT_EQ(2u, cpu_context_x86.fxsave.ftw);
|
||||||
|
EXPECT_EQ(3u, cpu_context_x86.dr0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void TestInitializeX86Context_FsaveWithoutFxsave() {
|
||||||
|
T context = {0};
|
||||||
|
context.ContextFlags = WOW64_CONTEXT_INTEGER |
|
||||||
|
WOW64_CONTEXT_FLOATING_POINT |
|
||||||
|
WOW64_CONTEXT_DEBUG_REGISTERS;
|
||||||
|
context.Eax = 1;
|
||||||
|
|
||||||
|
// In fields that are wider than they need to be, set the high bits to ensure
|
||||||
|
// that they’re masked off appropriately in the output.
|
||||||
|
context.FloatSave.ControlWord = 0xffff027f;
|
||||||
|
context.FloatSave.StatusWord = 0xffff0004;
|
||||||
|
context.FloatSave.TagWord = 0xffffa9ff;
|
||||||
|
context.FloatSave.ErrorOffset = 0x01234567;
|
||||||
|
context.FloatSave.ErrorSelector = 0x0bad0003;
|
||||||
|
context.FloatSave.DataOffset = 0x89abcdef;
|
||||||
|
context.FloatSave.DataSelector = 0xffff0007;
|
||||||
|
context.FloatSave.RegisterArea[77] = 0x80;
|
||||||
|
context.FloatSave.RegisterArea[78] = 0xff;
|
||||||
|
context.FloatSave.RegisterArea[79] = 0x7f;
|
||||||
|
|
||||||
|
context.Dr0 = 3;
|
||||||
|
|
||||||
|
{
|
||||||
|
CPUContextX86 cpu_context_x86 = {};
|
||||||
|
InitializeX86Context(context, &cpu_context_x86);
|
||||||
|
|
||||||
|
EXPECT_EQ(1u, cpu_context_x86.eax);
|
||||||
|
|
||||||
|
EXPECT_EQ(0x027f, cpu_context_x86.fxsave.fcw);
|
||||||
|
EXPECT_EQ(0x0004, cpu_context_x86.fxsave.fsw);
|
||||||
|
EXPECT_EQ(0x00f0, cpu_context_x86.fxsave.ftw);
|
||||||
|
EXPECT_EQ(0x0bad, cpu_context_x86.fxsave.fop);
|
||||||
|
EXPECT_EQ(0x01234567, cpu_context_x86.fxsave.fpu_ip);
|
||||||
|
EXPECT_EQ(0x0003, cpu_context_x86.fxsave.fpu_cs);
|
||||||
|
EXPECT_EQ(0x89abcdef, cpu_context_x86.fxsave.fpu_dp);
|
||||||
|
EXPECT_EQ(0x0007, cpu_context_x86.fxsave.fpu_ds);
|
||||||
|
for (size_t st_mm = 0; st_mm < 7; ++st_mm) {
|
||||||
|
EXPECT_EQ(
|
||||||
|
std::string(arraysize(cpu_context_x86.fxsave.st_mm[st_mm].st) * 2,
|
||||||
|
'0'),
|
||||||
|
BytesToHexString(cpu_context_x86.fxsave.st_mm[st_mm].st,
|
||||||
|
arraysize(cpu_context_x86.fxsave.st_mm[st_mm].st)))
|
||||||
|
<< "st_mm " << st_mm;
|
||||||
|
}
|
||||||
|
EXPECT_EQ("0000000000000080ff7f",
|
||||||
|
BytesToHexString(cpu_context_x86.fxsave.st_mm[7].st,
|
||||||
|
arraysize(cpu_context_x86.fxsave.st_mm[7].st)));
|
||||||
|
|
||||||
|
EXPECT_EQ(3u, cpu_context_x86.dr0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(ARCH_CPU_X86_FAMILY)
|
||||||
|
|
||||||
#if defined(ARCH_CPU_X86_64)
|
#if defined(ARCH_CPU_X86_64)
|
||||||
|
|
||||||
TEST(CPUContextWin, InitializeX64Context) {
|
TEST(CPUContextWin, InitializeX64Context) {
|
||||||
@ -45,28 +123,25 @@ TEST(CPUContextWin, InitializeX64Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#endif // ARCH_CPU_X86_64
|
||||||
|
|
||||||
TEST(CPUContextWin, InitializeX86Context) {
|
TEST(CPUContextWin, InitializeX86Context) {
|
||||||
CONTEXT context = {0};
|
#if defined(ARCH_CPU_X86)
|
||||||
context.ContextFlags =
|
TestInitializeX86Context<CONTEXT>();
|
||||||
CONTEXT_INTEGER | CONTEXT_EXTENDED_REGISTERS | CONTEXT_DEBUG_REGISTERS;
|
#else // ARCH_CPU_X86
|
||||||
context.Eax = 1;
|
TestInitializeX86Context<WOW64_CONTEXT>();
|
||||||
context.ExtendedRegisters[4] = 2; // FTW.
|
#endif // ARCH_CPU_X86
|
||||||
context.Dr0 = 3;
|
|
||||||
|
|
||||||
// Test the simple case, where everything in the CPUContextX86 argument is
|
|
||||||
// set directly from the supplied thread, float, and debug state parameters.
|
|
||||||
{
|
|
||||||
CPUContextX86 cpu_context_x86 = {};
|
|
||||||
InitializeX86Context(context, &cpu_context_x86);
|
|
||||||
EXPECT_EQ(1u, cpu_context_x86.eax);
|
|
||||||
EXPECT_EQ(2u, cpu_context_x86.fxsave.ftw);
|
|
||||||
EXPECT_EQ(3u, cpu_context_x86.dr0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // ARCH_CPU_X86_64
|
TEST(CPUContextWin, InitializeX86Context_FsaveWithoutFxsave) {
|
||||||
|
#if defined(ARCH_CPU_X86)
|
||||||
|
TestInitializeX86Context_FsaveWithoutFxsave<CONTEXT>();
|
||||||
|
#else // ARCH_CPU_X86
|
||||||
|
TestInitializeX86Context_FsaveWithoutFxsave<WOW64_CONTEXT>();
|
||||||
|
#endif // ARCH_CPU_X86
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // ARCH_CPU_X86_FAMILY
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
} // namespace test
|
} // namespace test
|
||||||
|
35
test/hex_string.cc
Normal file
35
test/hex_string.cc
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2017 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 "test/hex_string.h"
|
||||||
|
|
||||||
|
#include "base/strings/stringprintf.h"
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
namespace test {
|
||||||
|
|
||||||
|
std::string BytesToHexString(const void* bytes, size_t length) {
|
||||||
|
const unsigned char* bytes_c = reinterpret_cast<const unsigned char*>(bytes);
|
||||||
|
|
||||||
|
std::string hex_string;
|
||||||
|
hex_string.reserve(length * 2);
|
||||||
|
for (size_t index = 0; index < length; ++index) {
|
||||||
|
hex_string.append(base::StringPrintf("%02x", bytes_c[index]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return hex_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace test
|
||||||
|
} // namespace crashpad
|
40
test/hex_string.h
Normal file
40
test/hex_string.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// Copyright 2017 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.
|
||||||
|
|
||||||
|
#ifndef CRASHPAD_TEST_HEX_STRING_H_
|
||||||
|
#define CRASHPAD_TEST_HEX_STRING_H_
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
namespace test {
|
||||||
|
|
||||||
|
//! \brief Returns a hexadecimal string corresponding to \a bytes and \a length.
|
||||||
|
//!
|
||||||
|
//! Example usage:
|
||||||
|
//! \code
|
||||||
|
//! uint8_t expected[10];
|
||||||
|
//! uint8_t observed[10];
|
||||||
|
//! // …
|
||||||
|
//! EXPECT_EQ(BytesToHexString(expected, arraysize(expected)),
|
||||||
|
//! BytesToHexString(observed, arraysize(observed)));
|
||||||
|
//! \endcode
|
||||||
|
std::string BytesToHexString(const void* bytes, size_t length);
|
||||||
|
|
||||||
|
} // namespace test
|
||||||
|
} // namespace crashpad
|
||||||
|
|
||||||
|
#endif // CRASHPAD_TEST_HEX_STRING_H_
|
34
test/hex_string_test.cc
Normal file
34
test/hex_string_test.cc
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2017 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 "test/hex_string.h"
|
||||||
|
|
||||||
|
#include "base/macros.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
namespace test {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
TEST(HexString, HexString) {
|
||||||
|
EXPECT_EQ("", BytesToHexString(nullptr, 0));
|
||||||
|
|
||||||
|
const char kBytes[] = "Abc123xyz \x0a\x7f\xf0\x9f\x92\xa9_";
|
||||||
|
EXPECT_EQ("41626331323378797a200a7ff09f92a95f00",
|
||||||
|
BytesToHexString(kBytes, arraysize(kBytes)));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace test
|
||||||
|
} // namespace crashpad
|
@ -35,6 +35,8 @@
|
|||||||
'file.cc',
|
'file.cc',
|
||||||
'file.h',
|
'file.h',
|
||||||
'gtest_death_check.h',
|
'gtest_death_check.h',
|
||||||
|
'hex_string.cc',
|
||||||
|
'hex_string.h',
|
||||||
'mac/dyld.h',
|
'mac/dyld.h',
|
||||||
'mac/mach_errors.cc',
|
'mac/mach_errors.cc',
|
||||||
'mac/mach_errors.h',
|
'mac/mach_errors.h',
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
'..',
|
'..',
|
||||||
],
|
],
|
||||||
'sources': [
|
'sources': [
|
||||||
|
'hex_string_test.cc',
|
||||||
'mac/mach_multiprocess_test.cc',
|
'mac/mach_multiprocess_test.cc',
|
||||||
'multiprocess_exec_test.cc',
|
'multiprocess_exec_test.cc',
|
||||||
'multiprocess_posix_test.cc',
|
'multiprocess_posix_test.cc',
|
||||||
|
362
third_party/gtest/gmock.gyp
vendored
362
third_party/gtest/gmock.gyp
vendored
@ -17,212 +17,190 @@
|
|||||||
'../../build/crashpad_dependencies.gypi',
|
'../../build/crashpad_dependencies.gypi',
|
||||||
],
|
],
|
||||||
'conditions': [
|
'conditions': [
|
||||||
['crashpad_dependencies!="chromium"', {
|
['1==1', { # Defer processing until crashpad_dependencies is set
|
||||||
'variables': {
|
'variables': {
|
||||||
'conditions': [
|
'conditions': [
|
||||||
['crashpad_dependencies=="standalone"', {
|
['crashpad_dependencies=="standalone"', {
|
||||||
'gmock_dir': 'gtest/googlemock',
|
'gmock_dir': 'gtest/googlemock',
|
||||||
}, {
|
}],
|
||||||
|
['crashpad_dependencies=="external"', {
|
||||||
'gmock_dir': '../../../../gmock',
|
'gmock_dir': '../../../../gmock',
|
||||||
}],
|
}],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
'target_defaults': {
|
}],
|
||||||
# gmock relies heavily on objects with static storage duration.
|
],
|
||||||
'xcode_settings': {
|
'target_defaults': {
|
||||||
'WARNING_CFLAGS!': [
|
# gmock relies heavily on objects with static storage duration.
|
||||||
'-Wexit-time-destructors',
|
'xcode_settings': {
|
||||||
],
|
'WARNING_CFLAGS!': [
|
||||||
},
|
'-Wexit-time-destructors',
|
||||||
'cflags!': [
|
],
|
||||||
'-Wexit-time-destructors',
|
},
|
||||||
|
'cflags!': [
|
||||||
|
'-Wexit-time-destructors',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
'targets': [
|
||||||
|
{
|
||||||
|
'target_name': 'gmock',
|
||||||
|
'type': 'static_library',
|
||||||
|
'dependencies': [
|
||||||
|
'gtest.gyp:gtest',
|
||||||
|
],
|
||||||
|
'include_dirs': [
|
||||||
|
'<(gmock_dir)',
|
||||||
|
'<(gmock_dir)/include',
|
||||||
|
],
|
||||||
|
'sources': [
|
||||||
|
'<(gmock_dir)/include/gmock/gmock-actions.h',
|
||||||
|
'<(gmock_dir)/include/gmock/gmock-cardinalities.h',
|
||||||
|
'<(gmock_dir)/include/gmock/gmock-generated-actions.h',
|
||||||
|
'<(gmock_dir)/include/gmock/gmock-generated-function-mockers.h',
|
||||||
|
'<(gmock_dir)/include/gmock/gmock-generated-matchers.h',
|
||||||
|
'<(gmock_dir)/include/gmock/gmock-generated-nice-strict.h',
|
||||||
|
'<(gmock_dir)/include/gmock/gmock-matchers.h',
|
||||||
|
'<(gmock_dir)/include/gmock/gmock-more-actions.h',
|
||||||
|
'<(gmock_dir)/include/gmock/gmock-more-matchers.h',
|
||||||
|
'<(gmock_dir)/include/gmock/gmock-spec-builders.h',
|
||||||
|
'<(gmock_dir)/include/gmock/gmock.h',
|
||||||
|
'<(gmock_dir)/include/gmock/internal/custom/gmock-generated-actions.h',
|
||||||
|
'<(gmock_dir)/include/gmock/internal/custom/gmock-matchers.h',
|
||||||
|
'<(gmock_dir)/include/gmock/internal/custom/gmock-port.h',
|
||||||
|
'<(gmock_dir)/include/gmock/internal/gmock-generated-internal-utils.h',
|
||||||
|
'<(gmock_dir)/include/gmock/internal/gmock-internal-utils.h',
|
||||||
|
'<(gmock_dir)/include/gmock/internal/gmock-port.h',
|
||||||
|
'<(gmock_dir)/src/gmock-all.cc',
|
||||||
|
'<(gmock_dir)/src/gmock-cardinalities.cc',
|
||||||
|
'<(gmock_dir)/src/gmock-internal-utils.cc',
|
||||||
|
'<(gmock_dir)/src/gmock-matchers.cc',
|
||||||
|
'<(gmock_dir)/src/gmock-spec-builders.cc',
|
||||||
|
'<(gmock_dir)/src/gmock.cc',
|
||||||
|
],
|
||||||
|
'sources!': [
|
||||||
|
'<(gmock_dir)/src/gmock-all.cc',
|
||||||
|
],
|
||||||
|
|
||||||
|
'direct_dependent_settings': {
|
||||||
|
'include_dirs': [
|
||||||
|
'<(gmock_dir)/include',
|
||||||
],
|
],
|
||||||
},
|
'conditions': [
|
||||||
|
['clang!=0', {
|
||||||
'targets': [
|
# The MOCK_METHODn() macros do not specify “override”, which
|
||||||
{
|
# triggers this warning in users: “error: 'Method' overrides a
|
||||||
'target_name': 'gmock',
|
# member function but is not marked 'override'
|
||||||
'type': 'static_library',
|
# [-Werror,-Winconsistent-missing-override]”. Suppress these
|
||||||
'dependencies': [
|
# warnings, and add -Wno-unknown-warning-option because only
|
||||||
'gtest.gyp:gtest',
|
# recent versions of clang (trunk r220703 and later, version
|
||||||
],
|
# 3.6 and later) recognize it.
|
||||||
'include_dirs': [
|
|
||||||
'<(gmock_dir)',
|
|
||||||
'<(gmock_dir)/include',
|
|
||||||
],
|
|
||||||
'sources': [
|
|
||||||
'<(gmock_dir)/include/gmock/gmock-actions.h',
|
|
||||||
'<(gmock_dir)/include/gmock/gmock-cardinalities.h',
|
|
||||||
'<(gmock_dir)/include/gmock/gmock-generated-actions.h',
|
|
||||||
'<(gmock_dir)/include/gmock/gmock-generated-function-mockers.h',
|
|
||||||
'<(gmock_dir)/include/gmock/gmock-generated-matchers.h',
|
|
||||||
'<(gmock_dir)/include/gmock/gmock-generated-nice-strict.h',
|
|
||||||
'<(gmock_dir)/include/gmock/gmock-matchers.h',
|
|
||||||
'<(gmock_dir)/include/gmock/gmock-more-actions.h',
|
|
||||||
'<(gmock_dir)/include/gmock/gmock-more-matchers.h',
|
|
||||||
'<(gmock_dir)/include/gmock/gmock-spec-builders.h',
|
|
||||||
'<(gmock_dir)/include/gmock/gmock.h',
|
|
||||||
'<(gmock_dir)/include/gmock/internal/custom/gmock-generated-actions.h',
|
|
||||||
'<(gmock_dir)/include/gmock/internal/custom/gmock-matchers.h',
|
|
||||||
'<(gmock_dir)/include/gmock/internal/custom/gmock-port.h',
|
|
||||||
'<(gmock_dir)/include/gmock/internal/gmock-generated-internal-utils.h',
|
|
||||||
'<(gmock_dir)/include/gmock/internal/gmock-internal-utils.h',
|
|
||||||
'<(gmock_dir)/include/gmock/internal/gmock-port.h',
|
|
||||||
'<(gmock_dir)/src/gmock-all.cc',
|
|
||||||
'<(gmock_dir)/src/gmock-cardinalities.cc',
|
|
||||||
'<(gmock_dir)/src/gmock-internal-utils.cc',
|
|
||||||
'<(gmock_dir)/src/gmock-matchers.cc',
|
|
||||||
'<(gmock_dir)/src/gmock-spec-builders.cc',
|
|
||||||
'<(gmock_dir)/src/gmock.cc',
|
|
||||||
],
|
|
||||||
'sources!': [
|
|
||||||
'<(gmock_dir)/src/gmock-all.cc',
|
|
||||||
],
|
|
||||||
|
|
||||||
'direct_dependent_settings': {
|
|
||||||
'include_dirs': [
|
|
||||||
'<(gmock_dir)/include',
|
|
||||||
],
|
|
||||||
'conditions': [
|
'conditions': [
|
||||||
['clang!=0', {
|
['OS=="mac"', {
|
||||||
# The MOCK_METHODn() macros do not specify “override”, which
|
'xcode_settings': {
|
||||||
# triggers this warning in users: “error: 'Method' overrides a
|
'WARNING_CFLAGS': [
|
||||||
# member function but is not marked 'override'
|
'-Wno-inconsistent-missing-override',
|
||||||
# [-Werror,-Winconsistent-missing-override]”. Suppress these
|
'-Wno-unknown-warning-option',
|
||||||
# warnings, and add -Wno-unknown-warning-option because only
|
],
|
||||||
# recent versions of clang (trunk r220703 and later, version
|
},
|
||||||
# 3.6 and later) recognize it.
|
}],
|
||||||
'conditions': [
|
['OS=="linux"', {
|
||||||
['OS=="mac"', {
|
'cflags': [
|
||||||
'xcode_settings': {
|
'-Wno-inconsistent-missing-override',
|
||||||
'WARNING_CFLAGS': [
|
'-Wno-unknown-warning-option',
|
||||||
'-Wno-inconsistent-missing-override',
|
|
||||||
'-Wno-unknown-warning-option',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
['OS=="linux"', {
|
|
||||||
'cflags': [
|
|
||||||
'-Wno-inconsistent-missing-override',
|
|
||||||
'-Wno-unknown-warning-option',
|
|
||||||
],
|
|
||||||
}],
|
|
||||||
],
|
],
|
||||||
}],
|
}],
|
||||||
],
|
],
|
||||||
},
|
}],
|
||||||
'export_dependent_settings': [
|
],
|
||||||
'gtest.gyp:gtest',
|
},
|
||||||
],
|
'export_dependent_settings': [
|
||||||
},
|
'gtest.gyp:gtest',
|
||||||
{
|
|
||||||
'target_name': 'gmock_main',
|
|
||||||
'type': 'static_library',
|
|
||||||
'dependencies': [
|
|
||||||
'gmock',
|
|
||||||
'gtest.gyp:gtest',
|
|
||||||
],
|
|
||||||
'sources': [
|
|
||||||
'<(gmock_dir)/src/gmock_main.cc',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'target_name': 'gmock_test_executable',
|
|
||||||
'type': 'none',
|
|
||||||
'dependencies': [
|
|
||||||
'gmock',
|
|
||||||
'gtest.gyp:gtest',
|
|
||||||
],
|
|
||||||
'direct_dependent_settings': {
|
|
||||||
'type': 'executable',
|
|
||||||
'include_dirs': [
|
|
||||||
'<(gmock_dir)',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
'export_dependent_settings': [
|
|
||||||
'gmock',
|
|
||||||
'gtest.gyp:gtest',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'target_name': 'gmock_all_test',
|
|
||||||
'dependencies': [
|
|
||||||
'gmock_test_executable',
|
|
||||||
'gmock_main',
|
|
||||||
],
|
|
||||||
'include_dirs': [
|
|
||||||
'gtest/googletest',
|
|
||||||
],
|
|
||||||
'sources': [
|
|
||||||
'<(gmock_dir)/test/gmock-actions_test.cc',
|
|
||||||
'<(gmock_dir)/test/gmock-cardinalities_test.cc',
|
|
||||||
'<(gmock_dir)/test/gmock-generated-actions_test.cc',
|
|
||||||
'<(gmock_dir)/test/gmock-generated-function-mockers_test.cc',
|
|
||||||
'<(gmock_dir)/test/gmock-generated-internal-utils_test.cc',
|
|
||||||
'<(gmock_dir)/test/gmock-generated-matchers_test.cc',
|
|
||||||
'<(gmock_dir)/test/gmock-internal-utils_test.cc',
|
|
||||||
'<(gmock_dir)/test/gmock-matchers_test.cc',
|
|
||||||
'<(gmock_dir)/test/gmock-more-actions_test.cc',
|
|
||||||
'<(gmock_dir)/test/gmock-nice-strict_test.cc',
|
|
||||||
'<(gmock_dir)/test/gmock-port_test.cc',
|
|
||||||
'<(gmock_dir)/test/gmock-spec-builders_test.cc',
|
|
||||||
'<(gmock_dir)/test/gmock_test.cc',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'target_name': 'gmock_link_test',
|
|
||||||
'dependencies': [
|
|
||||||
'gmock_test_executable',
|
|
||||||
'gmock_main',
|
|
||||||
],
|
|
||||||
'sources': [
|
|
||||||
'<(gmock_dir)/test/gmock_link_test.cc',
|
|
||||||
'<(gmock_dir)/test/gmock_link_test.h',
|
|
||||||
'<(gmock_dir)/test/gmock_link2_test.cc',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'target_name': 'gmock_stress_test',
|
|
||||||
'dependencies': [
|
|
||||||
'gmock_test_executable',
|
|
||||||
],
|
|
||||||
'sources': [
|
|
||||||
'<(gmock_dir)/test/gmock_stress_test.cc',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'target_name': 'gmock_all_tests',
|
|
||||||
'type': 'none',
|
|
||||||
'dependencies': [
|
|
||||||
'gmock_all_test',
|
|
||||||
'gmock_link_test',
|
|
||||||
'gmock_stress_test',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
}, { # else: crashpad_dependencies=="chromium"
|
},
|
||||||
'targets': [
|
{
|
||||||
{
|
'target_name': 'gmock_main',
|
||||||
'target_name': 'gmock',
|
'type': 'static_library',
|
||||||
'type': 'none',
|
'dependencies': [
|
||||||
'dependencies': [
|
'gmock',
|
||||||
'<(DEPTH)/testing/gmock.gyp:gmock',
|
'gtest.gyp:gtest',
|
||||||
],
|
|
||||||
'export_dependent_settings': [
|
|
||||||
'<(DEPTH)/testing/gmock.gyp:gmock',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'target_name': 'gmock_main',
|
|
||||||
'type': 'none',
|
|
||||||
'dependencies': [
|
|
||||||
'<(DEPTH)/testing/gmock.gyp:gmock_main',
|
|
||||||
],
|
|
||||||
'export_dependent_settings': [
|
|
||||||
'<(DEPTH)/testing/gmock.gyp:gmock_main',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
}],
|
'sources': [
|
||||||
|
'<(gmock_dir)/src/gmock_main.cc',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'target_name': 'gmock_test_executable',
|
||||||
|
'type': 'none',
|
||||||
|
'dependencies': [
|
||||||
|
'gmock',
|
||||||
|
'gtest.gyp:gtest',
|
||||||
|
],
|
||||||
|
'direct_dependent_settings': {
|
||||||
|
'type': 'executable',
|
||||||
|
'include_dirs': [
|
||||||
|
'<(gmock_dir)',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'export_dependent_settings': [
|
||||||
|
'gmock',
|
||||||
|
'gtest.gyp:gtest',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'target_name': 'gmock_all_test',
|
||||||
|
'dependencies': [
|
||||||
|
'gmock_test_executable',
|
||||||
|
'gmock_main',
|
||||||
|
],
|
||||||
|
'include_dirs': [
|
||||||
|
'gtest/googletest',
|
||||||
|
],
|
||||||
|
'sources': [
|
||||||
|
'<(gmock_dir)/test/gmock-actions_test.cc',
|
||||||
|
'<(gmock_dir)/test/gmock-cardinalities_test.cc',
|
||||||
|
'<(gmock_dir)/test/gmock-generated-actions_test.cc',
|
||||||
|
'<(gmock_dir)/test/gmock-generated-function-mockers_test.cc',
|
||||||
|
'<(gmock_dir)/test/gmock-generated-internal-utils_test.cc',
|
||||||
|
'<(gmock_dir)/test/gmock-generated-matchers_test.cc',
|
||||||
|
'<(gmock_dir)/test/gmock-internal-utils_test.cc',
|
||||||
|
'<(gmock_dir)/test/gmock-matchers_test.cc',
|
||||||
|
'<(gmock_dir)/test/gmock-more-actions_test.cc',
|
||||||
|
'<(gmock_dir)/test/gmock-nice-strict_test.cc',
|
||||||
|
'<(gmock_dir)/test/gmock-port_test.cc',
|
||||||
|
'<(gmock_dir)/test/gmock-spec-builders_test.cc',
|
||||||
|
'<(gmock_dir)/test/gmock_test.cc',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'target_name': 'gmock_link_test',
|
||||||
|
'dependencies': [
|
||||||
|
'gmock_test_executable',
|
||||||
|
'gmock_main',
|
||||||
|
],
|
||||||
|
'sources': [
|
||||||
|
'<(gmock_dir)/test/gmock_link_test.cc',
|
||||||
|
'<(gmock_dir)/test/gmock_link_test.h',
|
||||||
|
'<(gmock_dir)/test/gmock_link2_test.cc',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'target_name': 'gmock_stress_test',
|
||||||
|
'dependencies': [
|
||||||
|
'gmock_test_executable',
|
||||||
|
],
|
||||||
|
'sources': [
|
||||||
|
'<(gmock_dir)/test/gmock_stress_test.cc',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'target_name': 'gmock_all_tests',
|
||||||
|
'type': 'none',
|
||||||
|
'dependencies': [
|
||||||
|
'gmock_all_test',
|
||||||
|
'gmock_link_test',
|
||||||
|
'gmock_stress_test',
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
498
third_party/gtest/gtest.gyp
vendored
498
third_party/gtest/gtest.gyp
vendored
@ -17,274 +17,252 @@
|
|||||||
'../../build/crashpad_dependencies.gypi',
|
'../../build/crashpad_dependencies.gypi',
|
||||||
],
|
],
|
||||||
'conditions': [
|
'conditions': [
|
||||||
['crashpad_dependencies!="chromium"', {
|
['1==1', { # Defer processing until crashpad_dependencies is set
|
||||||
'variables': {
|
'variables': {
|
||||||
'conditions': [
|
'conditions': [
|
||||||
['crashpad_dependencies=="standalone"', {
|
['crashpad_dependencies=="standalone"', {
|
||||||
'gtest_dir': 'gtest/googletest',
|
'gtest_dir': 'gtest/googletest',
|
||||||
}, {
|
}],
|
||||||
|
['crashpad_dependencies=="external"', {
|
||||||
'gtest_dir': '../../../../gtest',
|
'gtest_dir': '../../../../gtest',
|
||||||
}],
|
}],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
'target_defaults': {
|
|
||||||
# gtest relies heavily on objects with static storage duration.
|
|
||||||
'xcode_settings': {
|
|
||||||
'WARNING_CFLAGS!': [
|
|
||||||
'-Wexit-time-destructors',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
'cflags!': [
|
|
||||||
'-Wexit-time-destructors',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
'targets': [
|
|
||||||
{
|
|
||||||
'target_name': 'gtest',
|
|
||||||
'type': 'static_library',
|
|
||||||
'include_dirs': [
|
|
||||||
'<(gtest_dir)',
|
|
||||||
'<(gtest_dir)/include',
|
|
||||||
],
|
|
||||||
'sources': [
|
|
||||||
'<(gtest_dir)/include/gtest/gtest-death-test.h',
|
|
||||||
'<(gtest_dir)/include/gtest/gtest-message.h',
|
|
||||||
'<(gtest_dir)/include/gtest/gtest-param-test.h',
|
|
||||||
'<(gtest_dir)/include/gtest/gtest-printers.h',
|
|
||||||
'<(gtest_dir)/include/gtest/gtest-spi.h',
|
|
||||||
'<(gtest_dir)/include/gtest/gtest-test-part.h',
|
|
||||||
'<(gtest_dir)/include/gtest/gtest-typed-test.h',
|
|
||||||
'<(gtest_dir)/include/gtest/gtest.h',
|
|
||||||
'<(gtest_dir)/include/gtest/gtest_pred_impl.h',
|
|
||||||
'<(gtest_dir)/include/gtest/gtest_prod.h',
|
|
||||||
'<(gtest_dir)/include/gtest/internal/custom/gtest-port.h',
|
|
||||||
'<(gtest_dir)/include/gtest/internal/custom/gtest-printers.h',
|
|
||||||
'<(gtest_dir)/include/gtest/internal/custom/gtest.h',
|
|
||||||
'<(gtest_dir)/include/gtest/internal/gtest-death-test-internal.h',
|
|
||||||
'<(gtest_dir)/include/gtest/internal/gtest-filepath.h',
|
|
||||||
'<(gtest_dir)/include/gtest/internal/gtest-internal.h',
|
|
||||||
'<(gtest_dir)/include/gtest/internal/gtest-linked_ptr.h',
|
|
||||||
'<(gtest_dir)/include/gtest/internal/gtest-param-util-generated.h',
|
|
||||||
'<(gtest_dir)/include/gtest/internal/gtest-param-util.h',
|
|
||||||
'<(gtest_dir)/include/gtest/internal/gtest-port-arch.h',
|
|
||||||
'<(gtest_dir)/include/gtest/internal/gtest-port.h',
|
|
||||||
'<(gtest_dir)/include/gtest/internal/gtest-string.h',
|
|
||||||
'<(gtest_dir)/include/gtest/internal/gtest-tuple.h',
|
|
||||||
'<(gtest_dir)/include/gtest/internal/gtest-type-util.h',
|
|
||||||
'<(gtest_dir)/src/gtest-all.cc',
|
|
||||||
'<(gtest_dir)/src/gtest-death-test.cc',
|
|
||||||
'<(gtest_dir)/src/gtest-filepath.cc',
|
|
||||||
'<(gtest_dir)/src/gtest-internal-inl.h',
|
|
||||||
'<(gtest_dir)/src/gtest-port.cc',
|
|
||||||
'<(gtest_dir)/src/gtest-printers.cc',
|
|
||||||
'<(gtest_dir)/src/gtest-test-part.cc',
|
|
||||||
'<(gtest_dir)/src/gtest-typed-test.cc',
|
|
||||||
'<(gtest_dir)/src/gtest.cc',
|
|
||||||
],
|
|
||||||
'sources!': [
|
|
||||||
'<(gtest_dir)/src/gtest-all.cc',
|
|
||||||
],
|
|
||||||
'direct_dependent_settings': {
|
|
||||||
'include_dirs': [
|
|
||||||
'<(gtest_dir)/include',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
'conditions': [
|
|
||||||
['crashpad_dependencies=="external"', {
|
|
||||||
'include_dirs': [
|
|
||||||
'<(gtest_dir)/../..',
|
|
||||||
],
|
|
||||||
'defines': [
|
|
||||||
'GUNIT_NO_GOOGLE3=1',
|
|
||||||
],
|
|
||||||
'direct_dependent_settings': {
|
|
||||||
'include_dirs': [
|
|
||||||
'<(gtest_dir)/../..',
|
|
||||||
],
|
|
||||||
'defines': [
|
|
||||||
'GUNIT_NO_GOOGLE3=1',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'target_name': 'gtest_main',
|
|
||||||
'type': 'static_library',
|
|
||||||
'dependencies': [
|
|
||||||
'gtest',
|
|
||||||
],
|
|
||||||
'sources': [
|
|
||||||
'<(gtest_dir)/src/gtest_main.cc',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'target_name': 'gtest_test_executable',
|
|
||||||
'type': 'none',
|
|
||||||
'dependencies': [
|
|
||||||
'gtest',
|
|
||||||
],
|
|
||||||
'direct_dependent_settings': {
|
|
||||||
'type': 'executable',
|
|
||||||
'include_dirs': [
|
|
||||||
'<(gtest_dir)',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
'export_dependent_settings': [
|
|
||||||
'gtest',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'target_name': 'gtest_all_test',
|
|
||||||
'dependencies': [
|
|
||||||
'gtest_test_executable',
|
|
||||||
'gtest_main',
|
|
||||||
],
|
|
||||||
'sources': [
|
|
||||||
'<(gtest_dir)/test/gtest-death-test_test.cc',
|
|
||||||
'<(gtest_dir)/test/gtest-filepath_test.cc',
|
|
||||||
'<(gtest_dir)/test/gtest-linked_ptr_test.cc',
|
|
||||||
'<(gtest_dir)/test/gtest-message_test.cc',
|
|
||||||
'<(gtest_dir)/test/gtest-options_test.cc',
|
|
||||||
'<(gtest_dir)/test/gtest-port_test.cc',
|
|
||||||
'<(gtest_dir)/test/gtest-printers_test.cc',
|
|
||||||
'<(gtest_dir)/test/gtest-test-part_test.cc',
|
|
||||||
'<(gtest_dir)/test/gtest-typed-test2_test.cc',
|
|
||||||
'<(gtest_dir)/test/gtest-typed-test_test.cc',
|
|
||||||
'<(gtest_dir)/test/gtest-typed-test_test.h',
|
|
||||||
'<(gtest_dir)/test/gtest_main_unittest.cc',
|
|
||||||
'<(gtest_dir)/test/gtest_pred_impl_unittest.cc',
|
|
||||||
'<(gtest_dir)/test/gtest_prod_test.cc',
|
|
||||||
'<(gtest_dir)/test/gtest_unittest.cc',
|
|
||||||
'<(gtest_dir)/test/production.cc',
|
|
||||||
'<(gtest_dir)/test/production.h',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'target_name': 'gtest_environment_test',
|
|
||||||
'dependencies': [
|
|
||||||
'gtest_test_executable',
|
|
||||||
],
|
|
||||||
'sources': [
|
|
||||||
'<(gtest_dir)/test/gtest_environment_test.cc',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'target_name': 'gtest_listener_test',
|
|
||||||
'dependencies': [
|
|
||||||
'gtest_test_executable',
|
|
||||||
],
|
|
||||||
'sources': [
|
|
||||||
'<(gtest_dir)/test/gtest-listener_test.cc',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'target_name': 'gtest_no_test',
|
|
||||||
'dependencies': [
|
|
||||||
'gtest_test_executable',
|
|
||||||
],
|
|
||||||
'sources': [
|
|
||||||
'<(gtest_dir)/test/gtest_no_test_unittest.cc',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'target_name': 'gtest_param_test',
|
|
||||||
'dependencies': [
|
|
||||||
'gtest_test_executable',
|
|
||||||
],
|
|
||||||
'sources': [
|
|
||||||
'<(gtest_dir)/test/gtest-param-test2_test.cc',
|
|
||||||
'<(gtest_dir)/test/gtest-param-test_test.cc',
|
|
||||||
'<(gtest_dir)/test/gtest-param-test_test.h',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'target_name': 'gtest_premature_exit_test',
|
|
||||||
'dependencies': [
|
|
||||||
'gtest_test_executable',
|
|
||||||
],
|
|
||||||
'sources': [
|
|
||||||
'<(gtest_dir)/test/gtest_premature_exit_test.cc',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'target_name': 'gtest_repeat_test',
|
|
||||||
'dependencies': [
|
|
||||||
'gtest_test_executable',
|
|
||||||
],
|
|
||||||
'sources': [
|
|
||||||
'<(gtest_dir)/test/gtest_repeat_test.cc',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'target_name': 'gtest_sole_header_test',
|
|
||||||
'dependencies': [
|
|
||||||
'gtest_test_executable',
|
|
||||||
'gtest_main',
|
|
||||||
],
|
|
||||||
'sources': [
|
|
||||||
'<(gtest_dir)/test/gtest_sole_header_test.cc',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'target_name': 'gtest_stress_test',
|
|
||||||
'dependencies': [
|
|
||||||
'gtest_test_executable',
|
|
||||||
],
|
|
||||||
'sources': [
|
|
||||||
'<(gtest_dir)/test/gtest_stress_test.cc',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'target_name': 'gtest_unittest_api_test',
|
|
||||||
'dependencies': [
|
|
||||||
'gtest_test_executable',
|
|
||||||
],
|
|
||||||
'sources': [
|
|
||||||
'<(gtest_dir)/test/gtest-unittest-api_test.cc',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'target_name': 'gtest_all_tests',
|
|
||||||
'type': 'none',
|
|
||||||
'dependencies': [
|
|
||||||
'gtest_all_test',
|
|
||||||
'gtest_environment_test',
|
|
||||||
'gtest_listener_test',
|
|
||||||
'gtest_no_test',
|
|
||||||
'gtest_param_test',
|
|
||||||
'gtest_premature_exit_test',
|
|
||||||
'gtest_repeat_test',
|
|
||||||
'gtest_sole_header_test',
|
|
||||||
'gtest_stress_test',
|
|
||||||
'gtest_unittest_api_test',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}, { # else: crashpad_dependencies=="chromium"
|
|
||||||
'targets': [
|
|
||||||
{
|
|
||||||
'target_name': 'gtest',
|
|
||||||
'type': 'none',
|
|
||||||
'dependencies': [
|
|
||||||
'<(DEPTH)/testing/gtest.gyp:gtest',
|
|
||||||
],
|
|
||||||
'export_dependent_settings': [
|
|
||||||
'<(DEPTH)/testing/gtest.gyp:gtest',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'target_name': 'gtest_main',
|
|
||||||
'type': 'none',
|
|
||||||
'dependencies': [
|
|
||||||
'<(DEPTH)/testing/gtest.gyp:gtest_main',
|
|
||||||
],
|
|
||||||
'export_dependent_settings': [
|
|
||||||
'<(DEPTH)/testing/gtest.gyp:gtest_main',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}],
|
}],
|
||||||
],
|
],
|
||||||
|
'target_defaults': {
|
||||||
|
# gtest relies heavily on objects with static storage duration.
|
||||||
|
'xcode_settings': {
|
||||||
|
'WARNING_CFLAGS!': [
|
||||||
|
'-Wexit-time-destructors',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'cflags!': [
|
||||||
|
'-Wexit-time-destructors',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
'targets': [
|
||||||
|
{
|
||||||
|
'target_name': 'gtest',
|
||||||
|
'type': 'static_library',
|
||||||
|
'include_dirs': [
|
||||||
|
'<(gtest_dir)',
|
||||||
|
'<(gtest_dir)/include',
|
||||||
|
],
|
||||||
|
'sources': [
|
||||||
|
'<(gtest_dir)/include/gtest/gtest-death-test.h',
|
||||||
|
'<(gtest_dir)/include/gtest/gtest-message.h',
|
||||||
|
'<(gtest_dir)/include/gtest/gtest-param-test.h',
|
||||||
|
'<(gtest_dir)/include/gtest/gtest-printers.h',
|
||||||
|
'<(gtest_dir)/include/gtest/gtest-spi.h',
|
||||||
|
'<(gtest_dir)/include/gtest/gtest-test-part.h',
|
||||||
|
'<(gtest_dir)/include/gtest/gtest-typed-test.h',
|
||||||
|
'<(gtest_dir)/include/gtest/gtest.h',
|
||||||
|
'<(gtest_dir)/include/gtest/gtest_pred_impl.h',
|
||||||
|
'<(gtest_dir)/include/gtest/gtest_prod.h',
|
||||||
|
'<(gtest_dir)/include/gtest/internal/custom/gtest-port.h',
|
||||||
|
'<(gtest_dir)/include/gtest/internal/custom/gtest-printers.h',
|
||||||
|
'<(gtest_dir)/include/gtest/internal/custom/gtest.h',
|
||||||
|
'<(gtest_dir)/include/gtest/internal/gtest-death-test-internal.h',
|
||||||
|
'<(gtest_dir)/include/gtest/internal/gtest-filepath.h',
|
||||||
|
'<(gtest_dir)/include/gtest/internal/gtest-internal.h',
|
||||||
|
'<(gtest_dir)/include/gtest/internal/gtest-linked_ptr.h',
|
||||||
|
'<(gtest_dir)/include/gtest/internal/gtest-param-util-generated.h',
|
||||||
|
'<(gtest_dir)/include/gtest/internal/gtest-param-util.h',
|
||||||
|
'<(gtest_dir)/include/gtest/internal/gtest-port-arch.h',
|
||||||
|
'<(gtest_dir)/include/gtest/internal/gtest-port.h',
|
||||||
|
'<(gtest_dir)/include/gtest/internal/gtest-string.h',
|
||||||
|
'<(gtest_dir)/include/gtest/internal/gtest-tuple.h',
|
||||||
|
'<(gtest_dir)/include/gtest/internal/gtest-type-util.h',
|
||||||
|
'<(gtest_dir)/src/gtest-all.cc',
|
||||||
|
'<(gtest_dir)/src/gtest-death-test.cc',
|
||||||
|
'<(gtest_dir)/src/gtest-filepath.cc',
|
||||||
|
'<(gtest_dir)/src/gtest-internal-inl.h',
|
||||||
|
'<(gtest_dir)/src/gtest-port.cc',
|
||||||
|
'<(gtest_dir)/src/gtest-printers.cc',
|
||||||
|
'<(gtest_dir)/src/gtest-test-part.cc',
|
||||||
|
'<(gtest_dir)/src/gtest-typed-test.cc',
|
||||||
|
'<(gtest_dir)/src/gtest.cc',
|
||||||
|
],
|
||||||
|
'sources!': [
|
||||||
|
'<(gtest_dir)/src/gtest-all.cc',
|
||||||
|
],
|
||||||
|
'direct_dependent_settings': {
|
||||||
|
'include_dirs': [
|
||||||
|
'<(gtest_dir)/include',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'conditions': [
|
||||||
|
['crashpad_dependencies=="external"', {
|
||||||
|
'include_dirs': [
|
||||||
|
'<(gtest_dir)/../..',
|
||||||
|
],
|
||||||
|
'defines': [
|
||||||
|
'GUNIT_NO_GOOGLE3=1',
|
||||||
|
],
|
||||||
|
'direct_dependent_settings': {
|
||||||
|
'include_dirs': [
|
||||||
|
'<(gtest_dir)/../..',
|
||||||
|
],
|
||||||
|
'defines': [
|
||||||
|
'GUNIT_NO_GOOGLE3=1',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'target_name': 'gtest_main',
|
||||||
|
'type': 'static_library',
|
||||||
|
'dependencies': [
|
||||||
|
'gtest',
|
||||||
|
],
|
||||||
|
'sources': [
|
||||||
|
'<(gtest_dir)/src/gtest_main.cc',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'target_name': 'gtest_test_executable',
|
||||||
|
'type': 'none',
|
||||||
|
'dependencies': [
|
||||||
|
'gtest',
|
||||||
|
],
|
||||||
|
'direct_dependent_settings': {
|
||||||
|
'type': 'executable',
|
||||||
|
'include_dirs': [
|
||||||
|
'<(gtest_dir)',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'export_dependent_settings': [
|
||||||
|
'gtest',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'target_name': 'gtest_all_test',
|
||||||
|
'dependencies': [
|
||||||
|
'gtest_test_executable',
|
||||||
|
'gtest_main',
|
||||||
|
],
|
||||||
|
'sources': [
|
||||||
|
'<(gtest_dir)/test/gtest-death-test_test.cc',
|
||||||
|
'<(gtest_dir)/test/gtest-filepath_test.cc',
|
||||||
|
'<(gtest_dir)/test/gtest-linked_ptr_test.cc',
|
||||||
|
'<(gtest_dir)/test/gtest-message_test.cc',
|
||||||
|
'<(gtest_dir)/test/gtest-options_test.cc',
|
||||||
|
'<(gtest_dir)/test/gtest-port_test.cc',
|
||||||
|
'<(gtest_dir)/test/gtest-printers_test.cc',
|
||||||
|
'<(gtest_dir)/test/gtest-test-part_test.cc',
|
||||||
|
'<(gtest_dir)/test/gtest-typed-test2_test.cc',
|
||||||
|
'<(gtest_dir)/test/gtest-typed-test_test.cc',
|
||||||
|
'<(gtest_dir)/test/gtest-typed-test_test.h',
|
||||||
|
'<(gtest_dir)/test/gtest_main_unittest.cc',
|
||||||
|
'<(gtest_dir)/test/gtest_pred_impl_unittest.cc',
|
||||||
|
'<(gtest_dir)/test/gtest_prod_test.cc',
|
||||||
|
'<(gtest_dir)/test/gtest_unittest.cc',
|
||||||
|
'<(gtest_dir)/test/production.cc',
|
||||||
|
'<(gtest_dir)/test/production.h',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'target_name': 'gtest_environment_test',
|
||||||
|
'dependencies': [
|
||||||
|
'gtest_test_executable',
|
||||||
|
],
|
||||||
|
'sources': [
|
||||||
|
'<(gtest_dir)/test/gtest_environment_test.cc',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'target_name': 'gtest_listener_test',
|
||||||
|
'dependencies': [
|
||||||
|
'gtest_test_executable',
|
||||||
|
],
|
||||||
|
'sources': [
|
||||||
|
'<(gtest_dir)/test/gtest-listener_test.cc',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'target_name': 'gtest_no_test',
|
||||||
|
'dependencies': [
|
||||||
|
'gtest_test_executable',
|
||||||
|
],
|
||||||
|
'sources': [
|
||||||
|
'<(gtest_dir)/test/gtest_no_test_unittest.cc',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'target_name': 'gtest_param_test',
|
||||||
|
'dependencies': [
|
||||||
|
'gtest_test_executable',
|
||||||
|
],
|
||||||
|
'sources': [
|
||||||
|
'<(gtest_dir)/test/gtest-param-test2_test.cc',
|
||||||
|
'<(gtest_dir)/test/gtest-param-test_test.cc',
|
||||||
|
'<(gtest_dir)/test/gtest-param-test_test.h',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'target_name': 'gtest_premature_exit_test',
|
||||||
|
'dependencies': [
|
||||||
|
'gtest_test_executable',
|
||||||
|
],
|
||||||
|
'sources': [
|
||||||
|
'<(gtest_dir)/test/gtest_premature_exit_test.cc',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'target_name': 'gtest_repeat_test',
|
||||||
|
'dependencies': [
|
||||||
|
'gtest_test_executable',
|
||||||
|
],
|
||||||
|
'sources': [
|
||||||
|
'<(gtest_dir)/test/gtest_repeat_test.cc',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'target_name': 'gtest_sole_header_test',
|
||||||
|
'dependencies': [
|
||||||
|
'gtest_test_executable',
|
||||||
|
'gtest_main',
|
||||||
|
],
|
||||||
|
'sources': [
|
||||||
|
'<(gtest_dir)/test/gtest_sole_header_test.cc',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'target_name': 'gtest_stress_test',
|
||||||
|
'dependencies': [
|
||||||
|
'gtest_test_executable',
|
||||||
|
],
|
||||||
|
'sources': [
|
||||||
|
'<(gtest_dir)/test/gtest_stress_test.cc',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'target_name': 'gtest_unittest_api_test',
|
||||||
|
'dependencies': [
|
||||||
|
'gtest_test_executable',
|
||||||
|
],
|
||||||
|
'sources': [
|
||||||
|
'<(gtest_dir)/test/gtest-unittest-api_test.cc',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'target_name': 'gtest_all_tests',
|
||||||
|
'type': 'none',
|
||||||
|
'dependencies': [
|
||||||
|
'gtest_all_test',
|
||||||
|
'gtest_environment_test',
|
||||||
|
'gtest_listener_test',
|
||||||
|
'gtest_no_test',
|
||||||
|
'gtest_param_test',
|
||||||
|
'gtest_premature_exit_test',
|
||||||
|
'gtest_repeat_test',
|
||||||
|
'gtest_sole_header_test',
|
||||||
|
'gtest_stress_test',
|
||||||
|
'gtest_unittest_api_test',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
17
third_party/mini_chromium/mini_chromium.gyp
vendored
17
third_party/mini_chromium/mini_chromium.gyp
vendored
@ -18,11 +18,10 @@
|
|||||||
],
|
],
|
||||||
'targets': [
|
'targets': [
|
||||||
{
|
{
|
||||||
# To support Crashpad’s standalone build, its in-Chromium build, and its
|
# To support Crashpad’s standalone build and its build depending on
|
||||||
# build depending on external libraries, Crashpad code depending on base
|
# external libraries, Crashpad code depending on base should do so through
|
||||||
# should do so through this shim, which will either get base from
|
# this shim, which will either get base from mini_chromium or an external
|
||||||
# mini_chromium, Chromium, or an external library depending on the build
|
# library depending on the build type.
|
||||||
# type.
|
|
||||||
'target_name': 'base',
|
'target_name': 'base',
|
||||||
'type': 'none',
|
'type': 'none',
|
||||||
'conditions': [
|
'conditions': [
|
||||||
@ -34,14 +33,6 @@
|
|||||||
'mini_chromium/base/base.gyp:base',
|
'mini_chromium/base/base.gyp:base',
|
||||||
],
|
],
|
||||||
}],
|
}],
|
||||||
['crashpad_dependencies=="chromium"', {
|
|
||||||
'dependencies': [
|
|
||||||
'<(DEPTH)/base/base.gyp:base',
|
|
||||||
],
|
|
||||||
'export_dependent_settings': [
|
|
||||||
'<(DEPTH)/base/base.gyp:base',
|
|
||||||
],
|
|
||||||
}],
|
|
||||||
['crashpad_dependencies=="external"', {
|
['crashpad_dependencies=="external"', {
|
||||||
'dependencies': [
|
'dependencies': [
|
||||||
'../../../../mini_chromium/mini_chromium/base/base.gyp:base',
|
'../../../../mini_chromium/mini_chromium/base/base.gyp:base',
|
||||||
|
18
third_party/zlib/README.crashpad
vendored
Normal file
18
third_party/zlib/README.crashpad
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
Name: zlib
|
||||||
|
Short Name: zlib
|
||||||
|
URL: http://zlib.net/
|
||||||
|
Revision: See zlib/README.chromium
|
||||||
|
License: zlib
|
||||||
|
License File: zlib/LICENSE
|
||||||
|
Security Critical: yes
|
||||||
|
|
||||||
|
Description:
|
||||||
|
“A massively spiffy yet delicately unobtrusive compression library.”
|
||||||
|
|
||||||
|
zlib is a free, general-purpose, legally unencumbered lossless data-compression
|
||||||
|
library. zlib implements the “deflate” compression algorithm described by RFC
|
||||||
|
1951, which combines the LZ77 (Lempel-Ziv) algorithm with Huffman coding. zlib
|
||||||
|
also implements the zlib (RFC 1950) and gzip (RFC 1952) wrapper formats.
|
||||||
|
|
||||||
|
Local Modifications:
|
||||||
|
See zlib/README.chromium.
|
137
third_party/zlib/zlib.gyp
vendored
Normal file
137
third_party/zlib/zlib.gyp
vendored
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
# Copyright 2017 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.
|
||||||
|
|
||||||
|
{
|
||||||
|
'variables': {
|
||||||
|
'conditions': [
|
||||||
|
# Use the system zlib by default where available, as it is on most
|
||||||
|
# platforms. Windows does not have a system zlib, so use “embedded” which
|
||||||
|
# directs the build to use the source code in the zlib subdirectory.
|
||||||
|
['OS!="win"', {
|
||||||
|
'zlib_source%': 'system',
|
||||||
|
}, {
|
||||||
|
'zlib_source%': 'embedded',
|
||||||
|
}],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'targets': [
|
||||||
|
{
|
||||||
|
'target_name': 'zlib',
|
||||||
|
'conditions': [
|
||||||
|
['zlib_source=="system"', {
|
||||||
|
'type': 'none',
|
||||||
|
'direct_dependent_settings': {
|
||||||
|
'defines': [
|
||||||
|
'CRASHPAD_ZLIB_SOURCE_SYSTEM',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'link_settings': {
|
||||||
|
'conditions': [
|
||||||
|
['OS=="mac"', {
|
||||||
|
'libraries': [
|
||||||
|
'$(SDKROOT)/usr/lib/libz.dylib',
|
||||||
|
],
|
||||||
|
}, {
|
||||||
|
'libraries': [
|
||||||
|
'-lz',
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
['zlib_source=="embedded"', {
|
||||||
|
'type': 'static_library',
|
||||||
|
'include_dirs': [
|
||||||
|
'zlib',
|
||||||
|
],
|
||||||
|
'defines': [
|
||||||
|
'CRASHPAD_ZLIB_SOURCE_EMBEDDED',
|
||||||
|
'HAVE_STDARG_H',
|
||||||
|
],
|
||||||
|
'direct_dependent_settings': {
|
||||||
|
'include_dirs': [
|
||||||
|
'zlib',
|
||||||
|
],
|
||||||
|
'defines': [
|
||||||
|
'CRASHPAD_ZLIB_SOURCE_EMBEDDED',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'sources': [
|
||||||
|
'zlib/adler32.c',
|
||||||
|
'zlib/compress.c',
|
||||||
|
'zlib/crc32.c',
|
||||||
|
'zlib/crc32.h',
|
||||||
|
'zlib/crc_folding.c',
|
||||||
|
'zlib/deflate.c',
|
||||||
|
'zlib/deflate.h',
|
||||||
|
'zlib/fill_window_sse.c',
|
||||||
|
'zlib/gzclose.c',
|
||||||
|
'zlib/gzguts.h',
|
||||||
|
'zlib/gzlib.c',
|
||||||
|
'zlib/gzread.c',
|
||||||
|
'zlib/gzwrite.c',
|
||||||
|
'zlib/infback.c',
|
||||||
|
'zlib/inffast.c',
|
||||||
|
'zlib/inffast.h',
|
||||||
|
'zlib/inffixed.h',
|
||||||
|
'zlib/inflate.c',
|
||||||
|
'zlib/inflate.h',
|
||||||
|
'zlib/inftrees.c',
|
||||||
|
'zlib/inftrees.h',
|
||||||
|
'zlib/names.h',
|
||||||
|
'zlib/simd_stub.c',
|
||||||
|
'zlib/trees.c',
|
||||||
|
'zlib/trees.h',
|
||||||
|
'zlib/uncompr.c',
|
||||||
|
'zlib/x86.c',
|
||||||
|
'zlib/x86.h',
|
||||||
|
'zlib/zconf.h',
|
||||||
|
'zlib/zlib.h',
|
||||||
|
'zlib/zutil.c',
|
||||||
|
'zlib/zutil.h',
|
||||||
|
'zlib_crashpad.h',
|
||||||
|
],
|
||||||
|
'conditions': [
|
||||||
|
['target_arch=="x86" or target_arch=="amd64"', {
|
||||||
|
'sources!': [
|
||||||
|
'zlib/simd_stub.c',
|
||||||
|
],
|
||||||
|
}, {
|
||||||
|
'sources!': [
|
||||||
|
'zlib/crc_folding.c',
|
||||||
|
'zlib/fill_window_sse.c',
|
||||||
|
'zlib/x86.c',
|
||||||
|
'zlib/x86.h',
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
['OS!="win"', {
|
||||||
|
'defines': [
|
||||||
|
'HAVE_HIDDEN',
|
||||||
|
'HAVE_UNISTD_H',
|
||||||
|
],
|
||||||
|
}, {
|
||||||
|
'msvs_disabled_warnings': [
|
||||||
|
4131, # uses old-style declarator
|
||||||
|
4244, # conversion from 't1' to 't2', possible loss of data
|
||||||
|
4245, # conversion from 't1' to 't2', signed/unsigned mismatch
|
||||||
|
4267, # conversion from 'size_t' to 't', possible loss of data
|
||||||
|
4324, # structure was padded due to alignment specifier
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
32
third_party/zlib/zlib_crashpad.h
vendored
Normal file
32
third_party/zlib/zlib_crashpad.h
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Copyright 2017 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.
|
||||||
|
|
||||||
|
#ifndef CRASHPAD_THIRD_PARTY_ZLIB_ZLIB_CRASHPAD_H_
|
||||||
|
#define CRASHPAD_THIRD_PARTY_ZLIB_ZLIB_CRASHPAD_H_
|
||||||
|
|
||||||
|
// #include this file instead of the system version of <zlib.h> or equivalent
|
||||||
|
// available at any other location in the source tree. It will #include the
|
||||||
|
// proper <zlib.h> depending on how the build has been configured.
|
||||||
|
|
||||||
|
#if defined(CRASHPAD_ZLIB_SOURCE_SYSTEM)
|
||||||
|
#include <zlib.h>
|
||||||
|
#elif defined(CRASHPAD_ZLIB_SOURCE_EMBEDDED)
|
||||||
|
#include "third_party/zlib/zlib/zlib.h"
|
||||||
|
#elif defined(CRASHPAD_ZLIB_SOURCE_CHROMIUM)
|
||||||
|
#include "third_party/zlib/zlib.h"
|
||||||
|
#else
|
||||||
|
#error Unknown zlib source
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // CRASHPAD_THIRD_PARTY_ZLIB_ZLIB_CRASHPAD_H_
|
@ -15,6 +15,7 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import argparse
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
@ -107,23 +108,32 @@ extern "C" {
|
|||||||
file.close()
|
file.close()
|
||||||
|
|
||||||
def main(args):
|
def main(args):
|
||||||
if len(args) == 5:
|
parser = argparse.ArgumentParser()
|
||||||
(defs_file, user_c, server_c, user_h, server_h) = args
|
parser.add_argument('--developer-dir', help='Path to Xcode')
|
||||||
elif len(args) == 6:
|
parser.add_argument('--sdk', help='Path to SDK')
|
||||||
(defs_file, user_c, server_c, user_h, server_h, dev_dir) = args
|
parser.add_argument('defs')
|
||||||
os.environ['DEVELOPER_DIR'] = dev_dir
|
parser.add_argument('user_c')
|
||||||
else:
|
parser.add_argument('server_c')
|
||||||
assert False, "Wrong number of arguments"
|
parser.add_argument('user_h')
|
||||||
subprocess.check_call(['mig',
|
parser.add_argument('server_h')
|
||||||
'-user', user_c,
|
parsed = parser.parse_args(args)
|
||||||
'-server', server_c,
|
|
||||||
'-header', user_h,
|
command = ['mig',
|
||||||
'-sheader', server_h,
|
'-user', parsed.user_c,
|
||||||
defs_file])
|
'-server', parsed.server_c,
|
||||||
FixUserImplementation(user_c)
|
'-header', parsed.user_h,
|
||||||
server_declarations = FixServerImplementation(server_c)
|
'-sheader', parsed.server_h,
|
||||||
FixHeader(user_h)
|
]
|
||||||
FixHeader(server_h, server_declarations)
|
if parsed.sdk is not None:
|
||||||
|
command.extend(['-isysroot', parsed.sdk])
|
||||||
|
if parsed.developer_dir is not None:
|
||||||
|
os.environ['DEVELOPER_DIR'] = parsed.developer_dir
|
||||||
|
command.append(parsed.defs)
|
||||||
|
subprocess.check_call(command)
|
||||||
|
FixUserImplementation(parsed.user_c)
|
||||||
|
server_declarations = FixServerImplementation(parsed.server_c)
|
||||||
|
FixHeader(parsed.user_h)
|
||||||
|
FixHeader(parsed.server_h, server_declarations)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
sys.exit(main(sys.argv[1:]))
|
sys.exit(main(sys.argv[1:]))
|
||||||
|
@ -98,6 +98,14 @@ void Metrics::ExceptionEncountered() {
|
|||||||
ExceptionProcessing(ExceptionProcessingState::kStarted);
|
ExceptionProcessing(ExceptionProcessingState::kStarted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
void Metrics::HandlerLifetimeMilestone(LifetimeMilestone milestone) {
|
||||||
|
UMA_HISTOGRAM_ENUMERATION("Crashpad.HandlerLifetimeMilestone",
|
||||||
|
static_cast<int32_t>(milestone),
|
||||||
|
static_cast<int32_t>(LifetimeMilestone::kMaxValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
void Metrics::HandlerCrashed(uint32_t exception_code) {
|
void Metrics::HandlerCrashed(uint32_t exception_code) {
|
||||||
UMA_HISTOGRAM_SPARSE_SLOWLY(
|
UMA_HISTOGRAM_SPARSE_SLOWLY(
|
||||||
"Crashpad.HandlerCrash.ExceptionCode." METRICS_OS_NAME,
|
"Crashpad.HandlerCrash.ExceptionCode." METRICS_OS_NAME,
|
||||||
|
@ -30,8 +30,10 @@ namespace crashpad {
|
|||||||
//! Chromium's base, they allow integration with its metrics system.
|
//! Chromium's base, they allow integration with its metrics system.
|
||||||
class Metrics {
|
class Metrics {
|
||||||
public:
|
public:
|
||||||
//! \brief Values for CrashReportPending(). These are used as metrics
|
//! \brief Values for CrashReportPending().
|
||||||
//! enumeration values, so new values should always be added at the end.
|
//!
|
||||||
|
//! \note These are used as metrics enumeration values, so new values should
|
||||||
|
//! always be added at the end, before PendingReportReason::kMaxValue.
|
||||||
enum class PendingReportReason : int32_t {
|
enum class PendingReportReason : int32_t {
|
||||||
//! \brief A report was newly created and is ready for upload.
|
//! \brief A report was newly created and is ready for upload.
|
||||||
kNewlyCreated = 0,
|
kNewlyCreated = 0,
|
||||||
@ -53,8 +55,10 @@ class Metrics {
|
|||||||
//! \brief Reports on a crash upload attempt, and if it succeeded.
|
//! \brief Reports on a crash upload attempt, and if it succeeded.
|
||||||
static void CrashUploadAttempted(bool successful);
|
static void CrashUploadAttempted(bool successful);
|
||||||
|
|
||||||
//! \brief Values for CrashUploadSkipped(). These are used as metrics
|
//! \brief Values for CrashUploadSkipped().
|
||||||
//! enumeration values, so new values should always be added at the end.
|
//!
|
||||||
|
//! \note These are used as metrics enumeration values, so new values should
|
||||||
|
//! always be added at the end, before CrashSkippedReason::kMaxValue.
|
||||||
enum class CrashSkippedReason : int32_t {
|
enum class CrashSkippedReason : int32_t {
|
||||||
//! \brief Crash uploading is disabled.
|
//! \brief Crash uploading is disabled.
|
||||||
kUploadsDisabled = 0,
|
kUploadsDisabled = 0,
|
||||||
@ -81,8 +85,10 @@ class Metrics {
|
|||||||
//! database, without the report being uploadad.
|
//! database, without the report being uploadad.
|
||||||
static void CrashUploadSkipped(CrashSkippedReason reason);
|
static void CrashUploadSkipped(CrashSkippedReason reason);
|
||||||
|
|
||||||
//! \brief The result of capturing an exception. These are used as metrics
|
//! \brief The result of capturing an exception.
|
||||||
//! enumeration values, so new values should always be added at the end.
|
//!
|
||||||
|
//! \note These are used as metrics enumeration values, so new values should
|
||||||
|
//! always be added at the end, before CaptureResult::kMaxValue.
|
||||||
enum class CaptureResult : int32_t {
|
enum class CaptureResult : int32_t {
|
||||||
//! \brief The exception capture succeeded normally.
|
//! \brief The exception capture succeeded normally.
|
||||||
kSuccess = 0,
|
kSuccess = 0,
|
||||||
@ -130,6 +136,37 @@ class Metrics {
|
|||||||
//! \brief The exception handler server started capturing an exception.
|
//! \brief The exception handler server started capturing an exception.
|
||||||
static void ExceptionEncountered();
|
static void ExceptionEncountered();
|
||||||
|
|
||||||
|
//! \brief An important event in a handler process’ lifetime.
|
||||||
|
//!
|
||||||
|
//! \note These are used as metrics enumeration values, so new values should
|
||||||
|
//! always be added at the end, before LifetimeMilestone::kMaxValue.
|
||||||
|
enum class LifetimeMilestone : int32_t {
|
||||||
|
//! \brief The handler process started.
|
||||||
|
kStarted = 0,
|
||||||
|
|
||||||
|
//! \brief The handler process exited normally and cleanly.
|
||||||
|
kExitedNormally,
|
||||||
|
|
||||||
|
//! \brief The handler process exited early, but was successful in
|
||||||
|
//! performing some non-default action on user request.
|
||||||
|
kExitedEarly,
|
||||||
|
|
||||||
|
//! \brief The handler process exited with a failure code.
|
||||||
|
kFailed,
|
||||||
|
|
||||||
|
//! \brief The handler process was forcibly terminated.
|
||||||
|
kTerminated,
|
||||||
|
|
||||||
|
//! \brief The handler process crashed.
|
||||||
|
kCrashed,
|
||||||
|
|
||||||
|
//! \brief The number of values in this enumeration; not a valid value.
|
||||||
|
kMaxValue
|
||||||
|
};
|
||||||
|
|
||||||
|
//! \brief Records a handler start/exit/crash event.
|
||||||
|
static void HandlerLifetimeMilestone(LifetimeMilestone milestone);
|
||||||
|
|
||||||
//! \brief The handler process crashed with the given exception code.
|
//! \brief The handler process crashed with the given exception code.
|
||||||
//!
|
//!
|
||||||
//! This is currently only reported on Windows.
|
//! This is currently only reported on Windows.
|
||||||
|
37
util/misc/zlib.cc
Normal file
37
util/misc/zlib.cc
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// Copyright 2017 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 "util/misc/zlib.h"
|
||||||
|
|
||||||
|
#include "base/logging.h"
|
||||||
|
#include "base/strings/stringprintf.h"
|
||||||
|
#include "third_party/zlib/zlib_crashpad.h"
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
|
||||||
|
int ZlibWindowBitsWithGzipWrapper(int window_bits) {
|
||||||
|
// See the documentation for deflateInit2() and inflateInit2() in <zlib.h>. 0
|
||||||
|
// is only valid during decompression.
|
||||||
|
|
||||||
|
DCHECK(window_bits == 0 || (window_bits >= 8 && window_bits <= 15))
|
||||||
|
<< window_bits;
|
||||||
|
|
||||||
|
return 16 + window_bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ZlibErrorString(int zr) {
|
||||||
|
return base::StringPrintf("%s (%d)", zError(zr), zr);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace crashpad
|
42
util/misc/zlib.h
Normal file
42
util/misc/zlib.h
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Copyright 2017 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.
|
||||||
|
|
||||||
|
#ifndef CRASHPAD_UTIL_MISC_ZLIB_H_
|
||||||
|
#define CRASHPAD_UTIL_MISC_ZLIB_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
|
||||||
|
//! \brief Obtain a \a window_bits parameter to pass to `deflateInit2()` or
|
||||||
|
//! `inflateInit2()` that specifies a `gzip` wrapper instead of the default
|
||||||
|
//! zlib wrapper.
|
||||||
|
//!
|
||||||
|
//! \param[in] window_bits A \a window_bits value that only specifies the base-2
|
||||||
|
//! logarithm of the deflate sliding window size.
|
||||||
|
//!
|
||||||
|
//! \return \a window_bits adjusted to specify a `gzip` wrapper, to be passed to
|
||||||
|
//! `deflateInit2()` or `inflateInit2()`.
|
||||||
|
int ZlibWindowBitsWithGzipWrapper(int window_bits);
|
||||||
|
|
||||||
|
//! \brief Formats a string for an error received from the zlib library.
|
||||||
|
//!
|
||||||
|
//! \param[in] zr A zlib result code, such as `Z_STREAM_ERROR`.
|
||||||
|
//!
|
||||||
|
//! \return A formatted string.
|
||||||
|
std::string ZlibErrorString(int zr);
|
||||||
|
|
||||||
|
} // namespace crashpad
|
||||||
|
|
||||||
|
#endif // CRASHPAD_UTIL_MISC_ZLIB_H_
|
126
util/net/http_body_gzip.cc
Normal file
126
util/net/http_body_gzip.cc
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
// Copyright 2017 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 "util/net/http_body_gzip.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "base/logging.h"
|
||||||
|
#include "base/numerics/safe_conversions.h"
|
||||||
|
#include "third_party/zlib/zlib_crashpad.h"
|
||||||
|
#include "util/misc/zlib.h"
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
|
||||||
|
GzipHTTPBodyStream::GzipHTTPBodyStream(std::unique_ptr<HTTPBodyStream> source)
|
||||||
|
: input_(),
|
||||||
|
source_(std::move(source)),
|
||||||
|
z_stream_(new z_stream()),
|
||||||
|
state_(State::kUninitialized) {}
|
||||||
|
|
||||||
|
GzipHTTPBodyStream::~GzipHTTPBodyStream() {
|
||||||
|
DCHECK(state_ == State::kUninitialized ||
|
||||||
|
state_ == State::kFinished ||
|
||||||
|
state_ == State::kError);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileOperationResult GzipHTTPBodyStream::GetBytesBuffer(uint8_t* buffer,
|
||||||
|
size_t max_len) {
|
||||||
|
if (state_ == State::kError) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state_ == State::kFinished) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state_ == State::kUninitialized) {
|
||||||
|
z_stream_->zalloc = Z_NULL;
|
||||||
|
z_stream_->zfree = Z_NULL;
|
||||||
|
z_stream_->opaque = Z_NULL;
|
||||||
|
|
||||||
|
// The default values for zlib’s internal MAX_WBITS and DEF_MEM_LEVEL. These
|
||||||
|
// are the values that deflateInit() would use, but they’re not exported
|
||||||
|
// from zlib. deflateInit2() is used instead of deflateInit() to get the
|
||||||
|
// gzip wrapper.
|
||||||
|
const int kZlibMaxWindowBits = 15;
|
||||||
|
const int kZlibDefaultMemoryLevel = 8;
|
||||||
|
|
||||||
|
int zr = deflateInit2(z_stream_.get(),
|
||||||
|
Z_DEFAULT_COMPRESSION,
|
||||||
|
Z_DEFLATED,
|
||||||
|
ZlibWindowBitsWithGzipWrapper(kZlibMaxWindowBits),
|
||||||
|
kZlibDefaultMemoryLevel,
|
||||||
|
Z_DEFAULT_STRATEGY);
|
||||||
|
if (zr != Z_OK) {
|
||||||
|
LOG(ERROR) << "deflateInit2: " << ZlibErrorString(zr);
|
||||||
|
state_ = State::kError;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
state_ = State::kOperating;
|
||||||
|
}
|
||||||
|
|
||||||
|
z_stream_->next_out = buffer;
|
||||||
|
z_stream_->avail_out = base::saturated_cast<uInt>(max_len);
|
||||||
|
|
||||||
|
while (state_ != State::kFinished && z_stream_->avail_out > 0) {
|
||||||
|
if (state_ != State::kInputEOF && z_stream_->avail_in == 0) {
|
||||||
|
FileOperationResult input_bytes =
|
||||||
|
source_->GetBytesBuffer(input_, sizeof(input_));
|
||||||
|
if (input_bytes == -1) {
|
||||||
|
Done(State::kError);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input_bytes == 0) {
|
||||||
|
state_ = State::kInputEOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
z_stream_->next_in = input_;
|
||||||
|
z_stream_->avail_in = base::checked_cast<uInt>(input_bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
int zr = deflate(z_stream_.get(),
|
||||||
|
state_ == State::kInputEOF ? Z_FINISH : Z_NO_FLUSH);
|
||||||
|
if (state_ == State::kInputEOF && zr == Z_STREAM_END) {
|
||||||
|
Done(State::kFinished);
|
||||||
|
if (state_ == State::kError) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else if (zr != Z_OK) {
|
||||||
|
LOG(ERROR) << "deflate: " << ZlibErrorString(zr);
|
||||||
|
Done(State::kError);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DCHECK_LE(z_stream_->avail_out, max_len);
|
||||||
|
return max_len - z_stream_->avail_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GzipHTTPBodyStream::Done(State state) {
|
||||||
|
DCHECK(state_ == State::kOperating || state_ == State::kInputEOF) << state_;
|
||||||
|
DCHECK(state == State::kFinished || state == State::kError) << state;
|
||||||
|
|
||||||
|
int zr = deflateEnd(z_stream_.get());
|
||||||
|
if (zr != Z_OK) {
|
||||||
|
LOG(ERROR) << "deflateEnd: " << ZlibErrorString(zr);
|
||||||
|
state_ = State::kError;
|
||||||
|
} else {
|
||||||
|
state_ = state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace crashpad
|
67
util/net/http_body_gzip.h
Normal file
67
util/net/http_body_gzip.h
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// Copyright 2017 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.
|
||||||
|
|
||||||
|
#ifndef CRASHPAD_UTIL_NET_HTTP_BODY_GZIP_H_
|
||||||
|
#define CRASHPAD_UTIL_NET_HTTP_BODY_GZIP_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "base/macros.h"
|
||||||
|
#include "util/file/file_io.h"
|
||||||
|
#include "util/net/http_body.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
typedef struct z_stream_s z_stream;
|
||||||
|
} // extern "C"
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
|
||||||
|
//! \brief An implementation of HTTPBodyStream that `gzip`-compresses another
|
||||||
|
//! HTTPBodyStream.
|
||||||
|
class GzipHTTPBodyStream : public HTTPBodyStream {
|
||||||
|
public:
|
||||||
|
explicit GzipHTTPBodyStream(std::unique_ptr<HTTPBodyStream> source);
|
||||||
|
|
||||||
|
~GzipHTTPBodyStream() override;
|
||||||
|
|
||||||
|
// HTTPBodyStream:
|
||||||
|
FileOperationResult GetBytesBuffer(uint8_t* buffer, size_t max_len) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum State : int {
|
||||||
|
kUninitialized,
|
||||||
|
kOperating,
|
||||||
|
kInputEOF,
|
||||||
|
kFinished,
|
||||||
|
kError,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calls deflateEnd() and transitions state_ to state. If deflateEnd() fails,
|
||||||
|
// logs a message and transitions state_ to State::kError.
|
||||||
|
void Done(State state);
|
||||||
|
|
||||||
|
uint8_t input_[4096];
|
||||||
|
std::unique_ptr<HTTPBodyStream> source_;
|
||||||
|
std::unique_ptr<z_stream> z_stream_;
|
||||||
|
State state_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(GzipHTTPBodyStream);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace crashpad
|
||||||
|
|
||||||
|
#endif // CRASHPAD_UTIL_NET_HTTP_BODY_GZIP_H_
|
178
util/net/http_body_gzip_test.cc
Normal file
178
util/net/http_body_gzip_test.cc
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
// Copyright 2017 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 "util/net/http_body_gzip.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "base/macros.h"
|
||||||
|
#include "base/rand_util.h"
|
||||||
|
#include "base/numerics/safe_conversions.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "third_party/zlib/zlib_crashpad.h"
|
||||||
|
#include "util/misc/zlib.h"
|
||||||
|
#include "util/net/http_body.h"
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
namespace test {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class ScopedZlibInflateStream {
|
||||||
|
public:
|
||||||
|
explicit ScopedZlibInflateStream(z_stream* zlib) : zlib_(zlib) {}
|
||||||
|
~ScopedZlibInflateStream() {
|
||||||
|
int zr = inflateEnd(zlib_);
|
||||||
|
EXPECT_EQ(Z_OK, zr) << "inflateEnd: " << ZlibErrorString(zr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
z_stream* zlib_; // weak
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(ScopedZlibInflateStream);
|
||||||
|
};
|
||||||
|
|
||||||
|
void GzipInflate(const std::string& compressed,
|
||||||
|
std::string* decompressed,
|
||||||
|
size_t buf_size) {
|
||||||
|
decompressed->clear();
|
||||||
|
|
||||||
|
// There’s got to be at least a small buffer.
|
||||||
|
buf_size = std::max(buf_size, static_cast<size_t>(1));
|
||||||
|
|
||||||
|
std::unique_ptr<uint8_t[]> buf(new uint8_t[buf_size]);
|
||||||
|
z_stream zlib = {};
|
||||||
|
zlib.zalloc = Z_NULL;
|
||||||
|
zlib.zfree = Z_NULL;
|
||||||
|
zlib.opaque = Z_NULL;
|
||||||
|
zlib.next_in = reinterpret_cast<Bytef*>(const_cast<char*>(&compressed[0]));
|
||||||
|
zlib.avail_in = base::checked_cast<uInt>(compressed.size());
|
||||||
|
zlib.next_out = buf.get();
|
||||||
|
zlib.avail_out = base::checked_cast<uInt>(buf_size);
|
||||||
|
|
||||||
|
int zr = inflateInit2(&zlib, ZlibWindowBitsWithGzipWrapper(0));
|
||||||
|
ASSERT_EQ(Z_OK, zr) << "inflateInit2: " << ZlibErrorString(zr);
|
||||||
|
ScopedZlibInflateStream zlib_inflate(&zlib);
|
||||||
|
|
||||||
|
zr = inflate(&zlib, Z_FINISH);
|
||||||
|
ASSERT_EQ(Z_STREAM_END, zr) << "inflate: " << ZlibErrorString(zr);
|
||||||
|
|
||||||
|
ASSERT_LE(zlib.avail_out, buf_size);
|
||||||
|
decompressed->assign(reinterpret_cast<char*>(buf.get()),
|
||||||
|
buf_size - zlib.avail_out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestGzipDeflateInflate(const std::string& string) {
|
||||||
|
std::unique_ptr<HTTPBodyStream> string_stream(
|
||||||
|
new StringHTTPBodyStream(string));
|
||||||
|
GzipHTTPBodyStream gzip_stream(std::move(string_stream));
|
||||||
|
|
||||||
|
// The minimum size of a gzip wrapper per RFC 1952: a 10-byte header and an
|
||||||
|
// 8-byte trailer.
|
||||||
|
const size_t kGzipHeaderSize = 18;
|
||||||
|
|
||||||
|
// Per http://www.zlib.net/zlib_tech.html, in the worst case, zlib will store
|
||||||
|
// uncompressed data as-is, at an overhead of 5 bytes per 16384-byte block.
|
||||||
|
// Zero-length input will “compress” to a 2-byte zlib stream. Add the overhead
|
||||||
|
// of the gzip wrapper, assuming no optional fields are present.
|
||||||
|
size_t buf_size =
|
||||||
|
string.size() + kGzipHeaderSize +
|
||||||
|
(string.empty() ? 2 : (((string.size() + 16383) / 16384) * 5));
|
||||||
|
std::unique_ptr<uint8_t[]> buf(new uint8_t[buf_size]);
|
||||||
|
FileOperationResult compressed_bytes =
|
||||||
|
gzip_stream.GetBytesBuffer(buf.get(), buf_size);
|
||||||
|
ASSERT_NE(compressed_bytes, -1);
|
||||||
|
ASSERT_LE(static_cast<size_t>(compressed_bytes), buf_size);
|
||||||
|
|
||||||
|
// Make sure that the stream is really at EOF.
|
||||||
|
uint8_t eof_buf[16];
|
||||||
|
ASSERT_EQ(0, gzip_stream.GetBytesBuffer(eof_buf, sizeof(eof_buf)));
|
||||||
|
|
||||||
|
std::string compressed(reinterpret_cast<char*>(buf.get()), compressed_bytes);
|
||||||
|
|
||||||
|
ASSERT_GE(compressed.size(), kGzipHeaderSize);
|
||||||
|
EXPECT_EQ('\37', compressed[0]);
|
||||||
|
EXPECT_EQ('\213', compressed[1]);
|
||||||
|
EXPECT_EQ(Z_DEFLATED, compressed[2]);
|
||||||
|
|
||||||
|
std::string decompressed;
|
||||||
|
ASSERT_NO_FATAL_FAILURE(
|
||||||
|
GzipInflate(compressed, &decompressed, string.size()));
|
||||||
|
|
||||||
|
EXPECT_EQ(string, decompressed);
|
||||||
|
|
||||||
|
// In block mode, compression should be identical.
|
||||||
|
string_stream.reset(new StringHTTPBodyStream(string));
|
||||||
|
GzipHTTPBodyStream block_gzip_stream(std::move(string_stream));
|
||||||
|
uint8_t block_buf[4096];
|
||||||
|
std::string block_compressed;
|
||||||
|
FileOperationResult block_compressed_bytes;
|
||||||
|
while ((block_compressed_bytes = block_gzip_stream.GetBytesBuffer(
|
||||||
|
block_buf, sizeof(block_buf))) > 0) {
|
||||||
|
block_compressed.append(reinterpret_cast<char*>(block_buf),
|
||||||
|
block_compressed_bytes);
|
||||||
|
}
|
||||||
|
ASSERT_EQ(0, block_compressed_bytes);
|
||||||
|
EXPECT_EQ(compressed, block_compressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string MakeString(size_t size) {
|
||||||
|
std::string string;
|
||||||
|
for (size_t i = 0; i < size; ++i) {
|
||||||
|
string.append(1, (i % 256) ^ ((i >> 8) % 256));
|
||||||
|
}
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr size_t kFourKBytes = 4096;
|
||||||
|
constexpr size_t kManyBytes = 375017;
|
||||||
|
|
||||||
|
TEST(GzipHTTPBodyStream, Empty) {
|
||||||
|
TestGzipDeflateInflate(std::string());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GzipHTTPBodyStream, OneByte) {
|
||||||
|
TestGzipDeflateInflate(std::string("Z"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GzipHTTPBodyStream, FourKBytes_NUL) {
|
||||||
|
TestGzipDeflateInflate(std::string(kFourKBytes, '\0'));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GzipHTTPBodyStream, ManyBytes_NUL) {
|
||||||
|
TestGzipDeflateInflate(std::string(kManyBytes, '\0'));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GzipHTTPBodyStream, FourKBytes_Deterministic) {
|
||||||
|
TestGzipDeflateInflate(MakeString(kFourKBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GzipHTTPBodyStream, ManyBytes_Deterministic) {
|
||||||
|
TestGzipDeflateInflate(MakeString(kManyBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GzipHTTPBodyStream, FourKBytes_Random) {
|
||||||
|
TestGzipDeflateInflate(base::RandBytesAsString(kFourKBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GzipHTTPBodyStream, ManyBytes_Random) {
|
||||||
|
TestGzipDeflateInflate(base::RandBytesAsString(kManyBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace test
|
||||||
|
} // namespace crashpad
|
@ -17,7 +17,7 @@
|
|||||||
namespace crashpad {
|
namespace crashpad {
|
||||||
|
|
||||||
const char kContentType[] = "Content-Type";
|
const char kContentType[] = "Content-Type";
|
||||||
|
|
||||||
const char kContentLength[] = "Content-Length";
|
const char kContentLength[] = "Content-Length";
|
||||||
|
const char kContentEncoding[] = "Content-Encoding";
|
||||||
|
|
||||||
} // namespace crashpad
|
} // namespace crashpad
|
||||||
|
@ -29,6 +29,9 @@ extern const char kContentType[];
|
|||||||
//! \brief The header name `"Content-Length"`.
|
//! \brief The header name `"Content-Length"`.
|
||||||
extern const char kContentLength[];
|
extern const char kContentLength[];
|
||||||
|
|
||||||
|
//! \brief The header name `"Content-Encoding"`.
|
||||||
|
extern const char kContentEncoding[];
|
||||||
|
|
||||||
} // namespace crashpad
|
} // namespace crashpad
|
||||||
|
|
||||||
#endif // CRASHPAD_UTIL_NET_HTTP_HEADERS_H_
|
#endif // CRASHPAD_UTIL_NET_HTTP_HEADERS_H_
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
#include "base/rand_util.h"
|
#include "base/rand_util.h"
|
||||||
#include "base/strings/stringprintf.h"
|
#include "base/strings/stringprintf.h"
|
||||||
#include "util/net/http_body.h"
|
#include "util/net/http_body.h"
|
||||||
|
#include "util/net/http_body_gzip.h"
|
||||||
|
|
||||||
namespace crashpad {
|
namespace crashpad {
|
||||||
|
|
||||||
@ -116,12 +117,18 @@ void AssertSafeMIMEType(const std::string& string) {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
HTTPMultipartBuilder::HTTPMultipartBuilder()
|
HTTPMultipartBuilder::HTTPMultipartBuilder()
|
||||||
: boundary_(GenerateBoundaryString()), form_data_(), file_attachments_() {
|
: boundary_(GenerateBoundaryString()),
|
||||||
}
|
form_data_(),
|
||||||
|
file_attachments_(),
|
||||||
|
gzip_enabled_(false) {}
|
||||||
|
|
||||||
HTTPMultipartBuilder::~HTTPMultipartBuilder() {
|
HTTPMultipartBuilder::~HTTPMultipartBuilder() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HTTPMultipartBuilder::SetGzipEnabled(bool gzip_enabled) {
|
||||||
|
gzip_enabled_ = gzip_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
void HTTPMultipartBuilder::SetFormData(const std::string& key,
|
void HTTPMultipartBuilder::SetFormData(const std::string& key,
|
||||||
const std::string& value) {
|
const std::string& value) {
|
||||||
EraseKey(key);
|
EraseKey(key);
|
||||||
@ -179,13 +186,24 @@ std::unique_ptr<HTTPBodyStream> HTTPMultipartBuilder::GetBodyStream() {
|
|||||||
streams.push_back(
|
streams.push_back(
|
||||||
new StringHTTPBodyStream("--" + boundary_ + "--" + kCRLF));
|
new StringHTTPBodyStream("--" + boundary_ + "--" + kCRLF));
|
||||||
|
|
||||||
return std::unique_ptr<HTTPBodyStream>(new CompositeHTTPBodyStream(streams));
|
auto composite =
|
||||||
|
std::unique_ptr<HTTPBodyStream>(new CompositeHTTPBodyStream(streams));
|
||||||
|
if (gzip_enabled_) {
|
||||||
|
return std::unique_ptr<HTTPBodyStream>(
|
||||||
|
new GzipHTTPBodyStream(std::move(composite)));
|
||||||
|
}
|
||||||
|
return composite;
|
||||||
}
|
}
|
||||||
|
|
||||||
HTTPHeaders::value_type HTTPMultipartBuilder::GetContentType() const {
|
void HTTPMultipartBuilder::PopulateContentHeaders(
|
||||||
|
HTTPHeaders* http_headers) const {
|
||||||
std::string content_type =
|
std::string content_type =
|
||||||
base::StringPrintf("multipart/form-data; boundary=%s", boundary_.c_str());
|
base::StringPrintf("multipart/form-data; boundary=%s", boundary_.c_str());
|
||||||
return std::make_pair(kContentType, content_type);
|
(*http_headers)[kContentType] = content_type;
|
||||||
|
|
||||||
|
if (gzip_enabled_) {
|
||||||
|
(*http_headers)[kContentEncoding] = "gzip";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HTTPMultipartBuilder::EraseKey(const std::string& key) {
|
void HTTPMultipartBuilder::EraseKey(const std::string& key) {
|
||||||
|
@ -34,6 +34,15 @@ class HTTPMultipartBuilder {
|
|||||||
HTTPMultipartBuilder();
|
HTTPMultipartBuilder();
|
||||||
~HTTPMultipartBuilder();
|
~HTTPMultipartBuilder();
|
||||||
|
|
||||||
|
//! \brief Enables or disables `gzip` compression.
|
||||||
|
//!
|
||||||
|
//! \param[in] gzip_enabled Whether to enable or disable `gzip` compression.
|
||||||
|
//!
|
||||||
|
//! When `gzip` compression is enabled, the body stream returned by
|
||||||
|
//! GetBodyStream() will be `gzip`-compressed, and the content headers set by
|
||||||
|
//! PopulateContentHeaders() will contain `Content-Encoding: gzip`.
|
||||||
|
void SetGzipEnabled(bool gzip_enabled);
|
||||||
|
|
||||||
//! \brief Sets a `Content-Disposition: form-data` key-value pair.
|
//! \brief Sets a `Content-Disposition: form-data` key-value pair.
|
||||||
//!
|
//!
|
||||||
//! \param[in] key The key of the form data, specified as the `name` in the
|
//! \param[in] key The key of the form data, specified as the `name` in the
|
||||||
@ -64,8 +73,11 @@ class HTTPMultipartBuilder {
|
|||||||
//! \return A caller-owned HTTPBodyStream object.
|
//! \return A caller-owned HTTPBodyStream object.
|
||||||
std::unique_ptr<HTTPBodyStream> GetBodyStream();
|
std::unique_ptr<HTTPBodyStream> GetBodyStream();
|
||||||
|
|
||||||
//! \brief Gets the header pair for `"Content-Type"`.
|
//! \brief Adds the appropriate content headers to \a http_headers.
|
||||||
HTTPHeaders::value_type GetContentType() const;
|
//!
|
||||||
|
//! Any headers that this method adds will replace existing headers by the
|
||||||
|
//! same name in \a http_headers.
|
||||||
|
void PopulateContentHeaders(HTTPHeaders* http_headers) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct FileAttachment {
|
struct FileAttachment {
|
||||||
@ -81,6 +93,7 @@ class HTTPMultipartBuilder {
|
|||||||
std::string boundary_;
|
std::string boundary_;
|
||||||
std::map<std::string, std::string> form_data_;
|
std::map<std::string, std::string> form_data_;
|
||||||
std::map<std::string, FileAttachment> file_attachments_;
|
std::map<std::string, FileAttachment> file_attachments_;
|
||||||
|
bool gzip_enabled_;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(HTTPMultipartBuilder);
|
DISALLOW_COPY_AND_ASSIGN(HTTPMultipartBuilder);
|
||||||
};
|
};
|
||||||
|
@ -71,6 +71,7 @@ TEST(HTTPMultipartBuilder, ThreeStringFields) {
|
|||||||
ASSERT_TRUE(body.get());
|
ASSERT_TRUE(body.get());
|
||||||
std::string contents = ReadStreamToString(body.get());
|
std::string contents = ReadStreamToString(body.get());
|
||||||
auto lines = SplitCRLF(contents);
|
auto lines = SplitCRLF(contents);
|
||||||
|
ASSERT_EQ(13u, lines.size());
|
||||||
auto lines_it = lines.begin();
|
auto lines_it = lines.begin();
|
||||||
|
|
||||||
// The first line is the boundary. All subsequent boundaries must match this.
|
// The first line is the boundary. All subsequent boundaries must match this.
|
||||||
@ -164,6 +165,7 @@ TEST(HTTPMultipartBuilder, OverwriteFormDataWithEscapedKey) {
|
|||||||
ASSERT_TRUE(body.get());
|
ASSERT_TRUE(body.get());
|
||||||
std::string contents = ReadStreamToString(body.get());
|
std::string contents = ReadStreamToString(body.get());
|
||||||
auto lines = SplitCRLF(contents);
|
auto lines = SplitCRLF(contents);
|
||||||
|
ASSERT_EQ(5u, lines.size());
|
||||||
auto lines_it = lines.begin();
|
auto lines_it = lines.begin();
|
||||||
|
|
||||||
const std::string& boundary = *lines_it++;
|
const std::string& boundary = *lines_it++;
|
||||||
@ -253,6 +255,7 @@ TEST(HTTPMultipartBuilder, SharedFormDataAndAttachmentKeyNamespace) {
|
|||||||
ASSERT_TRUE(body.get());
|
ASSERT_TRUE(body.get());
|
||||||
std::string contents = ReadStreamToString(body.get());
|
std::string contents = ReadStreamToString(body.get());
|
||||||
auto lines = SplitCRLF(contents);
|
auto lines = SplitCRLF(contents);
|
||||||
|
ASSERT_EQ(9u, lines.size());
|
||||||
auto lines_it = lines.begin();
|
auto lines_it = lines.begin();
|
||||||
|
|
||||||
const std::string& boundary = *lines_it++;
|
const std::string& boundary = *lines_it++;
|
||||||
|
@ -221,7 +221,21 @@ TEST(HTTPTransport, ValidFormData) {
|
|||||||
builder.SetFormData("key2", "--abcdefg123");
|
builder.SetFormData("key2", "--abcdefg123");
|
||||||
|
|
||||||
HTTPHeaders headers;
|
HTTPHeaders headers;
|
||||||
EXPECT_TRUE(headers.insert(builder.GetContentType()).second);
|
builder.PopulateContentHeaders(&headers);
|
||||||
|
|
||||||
|
HTTPTransportTestFixture test(
|
||||||
|
headers, builder.GetBodyStream(), 200, &ValidFormData);
|
||||||
|
test.Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(HTTPTransport, ValidFormData_Gzip) {
|
||||||
|
HTTPMultipartBuilder builder;
|
||||||
|
builder.SetGzipEnabled(true);
|
||||||
|
builder.SetFormData("key1", "test");
|
||||||
|
builder.SetFormData("key2", "--abcdefg123");
|
||||||
|
|
||||||
|
HTTPHeaders headers;
|
||||||
|
builder.PopulateContentHeaders(&headers);
|
||||||
|
|
||||||
HTTPTransportTestFixture test(headers, builder.GetBodyStream(), 200,
|
HTTPTransportTestFixture test(headers, builder.GetBodyStream(), 200,
|
||||||
&ValidFormData);
|
&ValidFormData);
|
||||||
|
@ -33,6 +33,7 @@ This could easily have been written in C++ instead.
|
|||||||
import BaseHTTPServer
|
import BaseHTTPServer
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
|
import zlib
|
||||||
|
|
||||||
class BufferedReadFile(object):
|
class BufferedReadFile(object):
|
||||||
"""A File-like object that stores all read contents into a buffer."""
|
"""A File-like object that stores all read contents into a buffer."""
|
||||||
@ -81,11 +82,20 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
|||||||
self.rfile.buffer = ''
|
self.rfile.buffer = ''
|
||||||
|
|
||||||
if self.headers.get('Transfer-Encoding', '').lower() == 'chunked':
|
if self.headers.get('Transfer-Encoding', '').lower() == 'chunked':
|
||||||
|
if 'Content-Length' in self.headers:
|
||||||
|
raise AssertionError
|
||||||
body = self.handle_chunked_encoding()
|
body = self.handle_chunked_encoding()
|
||||||
else:
|
else:
|
||||||
length = int(self.headers.get('Content-Length', -1))
|
length = int(self.headers.get('Content-Length', -1))
|
||||||
body = self.rfile.read(length)
|
body = self.rfile.read(length)
|
||||||
|
|
||||||
|
if self.headers.get('Content-Encoding', '').lower() == 'gzip':
|
||||||
|
# 15 is the value of |wbits|, which should be at the maximum possible
|
||||||
|
# value to ensure that any gzip stream can be decoded. The offset of 16
|
||||||
|
# specifies that the stream to decompress will be formatted with a gzip
|
||||||
|
# wrapper.
|
||||||
|
body = zlib.decompress(body, 16 + 15)
|
||||||
|
|
||||||
RequestHandler.raw_request += body
|
RequestHandler.raw_request += body
|
||||||
|
|
||||||
self.send_response(self.response_code)
|
self.send_response(self.response_code)
|
||||||
|
@ -14,20 +14,22 @@
|
|||||||
|
|
||||||
#include "util/thread/thread.h"
|
#include "util/thread/thread.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
#include "base/logging.h"
|
#include "base/logging.h"
|
||||||
|
|
||||||
namespace crashpad {
|
namespace crashpad {
|
||||||
|
|
||||||
void Thread::Start() {
|
void Thread::Start() {
|
||||||
DCHECK(!platform_thread_);
|
DCHECK(!platform_thread_);
|
||||||
int rv = pthread_create(&platform_thread_, nullptr, ThreadEntryThunk, this);
|
errno = pthread_create(&platform_thread_, nullptr, ThreadEntryThunk, this);
|
||||||
PCHECK(0 == rv);
|
PCHECK(errno == 0) << "pthread_create";
|
||||||
}
|
}
|
||||||
|
|
||||||
void Thread::Join() {
|
void Thread::Join() {
|
||||||
DCHECK(platform_thread_);
|
DCHECK(platform_thread_);
|
||||||
int rv = pthread_join(platform_thread_, nullptr);
|
errno = pthread_join(platform_thread_, nullptr);
|
||||||
PCHECK(0 == rv);
|
PCHECK(errno == 0) << "pthread_join";
|
||||||
platform_thread_ = 0;
|
platform_thread_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,13 +22,13 @@ void Thread::Start() {
|
|||||||
DCHECK(!platform_thread_);
|
DCHECK(!platform_thread_);
|
||||||
platform_thread_ =
|
platform_thread_ =
|
||||||
CreateThread(nullptr, 0, ThreadEntryThunk, this, 0, nullptr);
|
CreateThread(nullptr, 0, ThreadEntryThunk, this, 0, nullptr);
|
||||||
PCHECK(platform_thread_);
|
PCHECK(platform_thread_) << "CreateThread";
|
||||||
}
|
}
|
||||||
|
|
||||||
void Thread::Join() {
|
void Thread::Join() {
|
||||||
DCHECK(platform_thread_);
|
DCHECK(platform_thread_);
|
||||||
DWORD result = WaitForSingleObject(platform_thread_, INFINITE);
|
DWORD result = WaitForSingleObject(platform_thread_, INFINITE);
|
||||||
PCHECK(WAIT_OBJECT_0 == result);
|
PCHECK(result == WAIT_OBJECT_0) << "WaitForSingleObject";
|
||||||
platform_thread_ = 0;
|
platform_thread_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
'dependencies': [
|
'dependencies': [
|
||||||
'../compat/compat.gyp:crashpad_compat',
|
'../compat/compat.gyp:crashpad_compat',
|
||||||
'../third_party/mini_chromium/mini_chromium.gyp:base',
|
'../third_party/mini_chromium/mini_chromium.gyp:base',
|
||||||
|
'../third_party/zlib/zlib.gyp:zlib',
|
||||||
],
|
],
|
||||||
'include_dirs': [
|
'include_dirs': [
|
||||||
'..',
|
'..',
|
||||||
@ -106,8 +107,12 @@
|
|||||||
'misc/tri_state.h',
|
'misc/tri_state.h',
|
||||||
'misc/uuid.cc',
|
'misc/uuid.cc',
|
||||||
'misc/uuid.h',
|
'misc/uuid.h',
|
||||||
|
'misc/zlib.cc',
|
||||||
|
'misc/zlib.h',
|
||||||
'net/http_body.cc',
|
'net/http_body.cc',
|
||||||
'net/http_body.h',
|
'net/http_body.h',
|
||||||
|
'net/http_body_gzip.cc',
|
||||||
|
'net/http_body_gzip.h',
|
||||||
'net/http_headers.cc',
|
'net/http_headers.cc',
|
||||||
'net/http_headers.h',
|
'net/http_headers.h',
|
||||||
'net/http_multipart_builder.cc',
|
'net/http_multipart_builder.cc',
|
||||||
@ -193,6 +198,8 @@
|
|||||||
'win/scoped_local_alloc.h',
|
'win/scoped_local_alloc.h',
|
||||||
'win/scoped_process_suspend.cc',
|
'win/scoped_process_suspend.cc',
|
||||||
'win/scoped_process_suspend.h',
|
'win/scoped_process_suspend.h',
|
||||||
|
'win/session_end_watcher.cc',
|
||||||
|
'win/session_end_watcher.h',
|
||||||
'win/termination_codes.h',
|
'win/termination_codes.h',
|
||||||
'win/time.cc',
|
'win/time.cc',
|
||||||
'win/time.h',
|
'win/time.h',
|
||||||
@ -263,6 +270,7 @@
|
|||||||
['OS=="win"', {
|
['OS=="win"', {
|
||||||
'link_settings': {
|
'link_settings': {
|
||||||
'libraries': [
|
'libraries': [
|
||||||
|
'-luser32.lib',
|
||||||
'-lwinhttp.lib',
|
'-lwinhttp.lib',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
'../third_party/gtest/gmock.gyp:gmock_main',
|
'../third_party/gtest/gmock.gyp:gmock_main',
|
||||||
'../third_party/gtest/gtest.gyp:gtest',
|
'../third_party/gtest/gtest.gyp:gtest',
|
||||||
'../third_party/mini_chromium/mini_chromium.gyp:base',
|
'../third_party/mini_chromium/mini_chromium.gyp:base',
|
||||||
|
'../third_party/zlib/zlib.gyp:zlib',
|
||||||
],
|
],
|
||||||
'include_dirs': [
|
'include_dirs': [
|
||||||
'..',
|
'..',
|
||||||
@ -62,6 +63,7 @@
|
|||||||
'misc/scoped_forbid_return_test.cc',
|
'misc/scoped_forbid_return_test.cc',
|
||||||
'misc/random_string_test.cc',
|
'misc/random_string_test.cc',
|
||||||
'misc/uuid_test.cc',
|
'misc/uuid_test.cc',
|
||||||
|
'net/http_body_gzip_test.cc',
|
||||||
'net/http_body_test.cc',
|
'net/http_body_test.cc',
|
||||||
'net/http_body_test_util.cc',
|
'net/http_body_test_util.cc',
|
||||||
'net/http_body_test_util.h',
|
'net/http_body_test_util.h',
|
||||||
@ -93,6 +95,7 @@
|
|||||||
'win/process_info_test.cc',
|
'win/process_info_test.cc',
|
||||||
'win/registration_protocol_win_test.cc',
|
'win/registration_protocol_win_test.cc',
|
||||||
'win/scoped_process_suspend_test.cc',
|
'win/scoped_process_suspend_test.cc',
|
||||||
|
'win/session_end_watcher_test.cc',
|
||||||
'win/time_test.cc',
|
'win/time_test.cc',
|
||||||
],
|
],
|
||||||
'conditions': [
|
'conditions': [
|
||||||
@ -112,6 +115,7 @@
|
|||||||
'-ladvapi32.lib',
|
'-ladvapi32.lib',
|
||||||
'-limagehlp.lib',
|
'-limagehlp.lib',
|
||||||
'-lrpcrt4.lib',
|
'-lrpcrt4.lib',
|
||||||
|
'-luser32.lib',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}],
|
}],
|
||||||
|
@ -25,7 +25,7 @@ void ScopedFileHANDLECloseTraits::Free(HANDLE handle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ScopedKernelHANDLECloseTraits::Free(HANDLE handle) {
|
void ScopedKernelHANDLECloseTraits::Free(HANDLE handle) {
|
||||||
PCHECK(CloseHandle(handle));
|
PCHECK(CloseHandle(handle)) << "CloseHandle";
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace internal
|
} // namespace internal
|
||||||
|
243
util/win/session_end_watcher.cc
Normal file
243
util/win/session_end_watcher.cc
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
// Copyright 2017 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 "util/win/session_end_watcher.h"
|
||||||
|
|
||||||
|
#include "base/logging.h"
|
||||||
|
#include "base/scoped_generic.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
extern IMAGE_DOS_HEADER __ImageBase;
|
||||||
|
} // extern "C"
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class ScopedSetEvent {
|
||||||
|
public:
|
||||||
|
explicit ScopedSetEvent(HANDLE event) : event_(event) {}
|
||||||
|
~ScopedSetEvent() {
|
||||||
|
if (!SetEvent(event_)) {
|
||||||
|
PLOG(ERROR) << "SetEvent";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
HANDLE event_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(ScopedSetEvent);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ScopedWindowClass and ScopedWindow operate on ATOM* and HWND*, respectively,
|
||||||
|
// instead of ATOM and HWND, so that the actual storage can exist as a local
|
||||||
|
// variable or a member variable, and the scoper can be responsible for
|
||||||
|
// releasing things only if the actual storage hasn’t been released and zeroed
|
||||||
|
// already by something else.
|
||||||
|
struct ScopedWindowClassTraits {
|
||||||
|
static ATOM* InvalidValue() { return nullptr; }
|
||||||
|
static void Free(ATOM* window_class) {
|
||||||
|
if (*window_class) {
|
||||||
|
if (!UnregisterClass(MAKEINTATOM(*window_class), 0)) {
|
||||||
|
PLOG(ERROR) << "UnregisterClass";
|
||||||
|
} else {
|
||||||
|
*window_class = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using ScopedWindowClass = base::ScopedGeneric<ATOM*, ScopedWindowClassTraits>;
|
||||||
|
|
||||||
|
struct ScopedWindowTraits {
|
||||||
|
static HWND* InvalidValue() { return nullptr; }
|
||||||
|
static void Free(HWND* window) {
|
||||||
|
if (*window) {
|
||||||
|
if (!DestroyWindow(*window)) {
|
||||||
|
PLOG(ERROR) << "DestroyWindow";
|
||||||
|
} else {
|
||||||
|
*window = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using ScopedWindow = base::ScopedGeneric<HWND*, ScopedWindowTraits>;
|
||||||
|
|
||||||
|
// GetWindowLongPtr()’s return value doesn’t unambiguously indicate whether it
|
||||||
|
// was successful, because 0 could either represent successful retrieval of the
|
||||||
|
// value 0, or failure. This wrapper is more convenient to use.
|
||||||
|
bool GetWindowLongPtrAndSuccess(HWND window, int index, LONG_PTR* value) {
|
||||||
|
SetLastError(ERROR_SUCCESS);
|
||||||
|
*value = GetWindowLongPtr(window, index);
|
||||||
|
return *value || GetLastError() == ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWindowLongPtr() has the same problem as GetWindowLongPtr(). Use this
|
||||||
|
// wrapper instead.
|
||||||
|
bool SetWindowLongPtrAndGetSuccess(HWND window, int index, LONG_PTR value) {
|
||||||
|
SetLastError(ERROR_SUCCESS);
|
||||||
|
LONG_PTR previous = SetWindowLongPtr(window, index, value);
|
||||||
|
return previous || GetLastError() == ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
SessionEndWatcher::SessionEndWatcher()
|
||||||
|
: Thread(),
|
||||||
|
window_(nullptr),
|
||||||
|
started_(nullptr),
|
||||||
|
stopped_(nullptr) {
|
||||||
|
// Set bManualReset for these events so that WaitForStart() and WaitForStop()
|
||||||
|
// can be called multiple times.
|
||||||
|
|
||||||
|
started_.reset(CreateEvent(nullptr, true, false, nullptr));
|
||||||
|
PLOG_IF(ERROR, !started_.get()) << "CreateEvent";
|
||||||
|
|
||||||
|
stopped_.reset(CreateEvent(nullptr, true, false, nullptr));
|
||||||
|
PLOG_IF(ERROR, !stopped_.get()) << "CreateEvent";
|
||||||
|
|
||||||
|
Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
SessionEndWatcher::~SessionEndWatcher() {
|
||||||
|
// Tear everything down by posting a WM_CLOSE to the window. This obviously
|
||||||
|
// can’t work until the window has been created, and that happens on a
|
||||||
|
// different thread, so wait for the start event to be signaled first.
|
||||||
|
WaitForStart();
|
||||||
|
if (window_) {
|
||||||
|
if (!PostMessage(window_, WM_CLOSE, 0, 0)) {
|
||||||
|
PLOG(ERROR) << "PostMessage";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Join();
|
||||||
|
DCHECK(!window_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SessionEndWatcher::WaitForStart() {
|
||||||
|
if (WaitForSingleObject(started_.get(), INFINITE) != WAIT_OBJECT_0) {
|
||||||
|
PLOG(ERROR) << "WaitForSingleObject";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SessionEndWatcher::WaitForStop() {
|
||||||
|
if (WaitForSingleObject(stopped_.get(), INFINITE) != WAIT_OBJECT_0) {
|
||||||
|
PLOG(ERROR) << "WaitForSingleObject";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SessionEndWatcher::ThreadMain() {
|
||||||
|
ATOM atom = 0;
|
||||||
|
ScopedWindowClass window_class(&atom);
|
||||||
|
ScopedWindow window(&window_);
|
||||||
|
|
||||||
|
ScopedSetEvent call_set_stop(stopped_.get());
|
||||||
|
|
||||||
|
{
|
||||||
|
ScopedSetEvent call_set_start(started_.get());
|
||||||
|
|
||||||
|
WNDCLASS wndclass = {};
|
||||||
|
wndclass.lpfnWndProc = WindowProc;
|
||||||
|
wndclass.hInstance = reinterpret_cast<HMODULE>(&__ImageBase);
|
||||||
|
wndclass.lpszClassName = L"crashpad_SessionEndWatcher";
|
||||||
|
atom = RegisterClass(&wndclass);
|
||||||
|
if (!atom) {
|
||||||
|
PLOG(ERROR) << "RegisterClass";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window_ = CreateWindow(MAKEINTATOM(atom), // lpClassName
|
||||||
|
nullptr, // lpWindowName
|
||||||
|
0, // dwStyle
|
||||||
|
0, // x
|
||||||
|
0, // y
|
||||||
|
0, // nWidth
|
||||||
|
0, // nHeight
|
||||||
|
nullptr, // hWndParent
|
||||||
|
nullptr, // hMenu
|
||||||
|
nullptr, // hInstance
|
||||||
|
this); // lpParam
|
||||||
|
if (!window_) {
|
||||||
|
PLOG(ERROR) << "CreateWindow";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MSG message;
|
||||||
|
BOOL rv = 0;
|
||||||
|
while (window_ && (rv = GetMessage(&message, window_, 0, 0)) > 0) {
|
||||||
|
TranslateMessage(&message);
|
||||||
|
DispatchMessage(&message);
|
||||||
|
}
|
||||||
|
if (window_ && rv == -1) {
|
||||||
|
PLOG(ERROR) << "GetMessage";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
LRESULT CALLBACK SessionEndWatcher::WindowProc(HWND window,
|
||||||
|
UINT message,
|
||||||
|
WPARAM w_param,
|
||||||
|
LPARAM l_param) {
|
||||||
|
// Figure out which object this is. A pointer to it is stuffed into the last
|
||||||
|
// parameter of CreateWindow(), which shows up as CREATESTRUCT::lpCreateParams
|
||||||
|
// in a WM_CREATE message. That should be processed before any of the other
|
||||||
|
// messages of interest to this function. Once the object is known, save a
|
||||||
|
// pointer to it in the GWLP_USERDATA slot for later retrieval when processing
|
||||||
|
// other messages.
|
||||||
|
SessionEndWatcher* self;
|
||||||
|
if (!GetWindowLongPtrAndSuccess(
|
||||||
|
window, GWLP_USERDATA, reinterpret_cast<LONG_PTR*>(&self))) {
|
||||||
|
PLOG(ERROR) << "GetWindowLongPtr";
|
||||||
|
}
|
||||||
|
if (!self && message == WM_CREATE) {
|
||||||
|
CREATESTRUCT* create = reinterpret_cast<CREATESTRUCT*>(l_param);
|
||||||
|
self = reinterpret_cast<SessionEndWatcher*>(create->lpCreateParams);
|
||||||
|
if (!SetWindowLongPtrAndGetSuccess(
|
||||||
|
window, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(self))) {
|
||||||
|
PLOG(ERROR) << "SetWindowLongPtr";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self) {
|
||||||
|
if (message == WM_ENDSESSION) {
|
||||||
|
// If w_param is false, this WM_ENDSESSION message cancels a previous
|
||||||
|
// WM_QUERYENDSESSION.
|
||||||
|
if (w_param) {
|
||||||
|
self->SessionEnding();
|
||||||
|
|
||||||
|
// If the session is ending, post a close message which will kick off
|
||||||
|
// window destruction and cause the message loop thread to terminate.
|
||||||
|
if (!PostMessage(self->window_, WM_CLOSE, 0, 0)) {
|
||||||
|
PLOG(ERROR) << "PostMessage";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (message == WM_DESTROY) {
|
||||||
|
// The window is being destroyed. Clear GWLP_USERDATA so that |self| won’t
|
||||||
|
// be found during a subsequent call into this function for this window.
|
||||||
|
// Clear self->window_ too, because it refers to an object that soon won’t
|
||||||
|
// exist. That signals the message loop to stop processing messages.
|
||||||
|
if (!SetWindowLongPtrAndGetSuccess(window, GWLP_USERDATA, 0)) {
|
||||||
|
PLOG(ERROR) << "SetWindowLongPtr";
|
||||||
|
}
|
||||||
|
self->window_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the message is WM_CLOSE, DefWindowProc() will call DestroyWindow(), and
|
||||||
|
// this function will be called again with a WM_DESTROY message.
|
||||||
|
return DefWindowProc(window, message, w_param, l_param);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace crashpad
|
79
util/win/session_end_watcher.h
Normal file
79
util/win/session_end_watcher.h
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
// Copyright 2017 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.
|
||||||
|
|
||||||
|
#ifndef CRASHPAD_UTIL_WIN_SESSION_END_WATCHER_H_
|
||||||
|
#define CRASHPAD_UTIL_WIN_SESSION_END_WATCHER_H_
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "base/macros.h"
|
||||||
|
#include "util/thread/thread.h"
|
||||||
|
#include "util/win/scoped_handle.h"
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
|
||||||
|
//! \brief Creates a hidden window and waits for a `WM_ENDSESSION` message,
|
||||||
|
//! indicating that the session is ending and the application should
|
||||||
|
//! terminate.
|
||||||
|
//!
|
||||||
|
//! A dedicated thread will be created to run the `GetMessage()`-based message
|
||||||
|
//! loop required to monitor for this message.
|
||||||
|
//!
|
||||||
|
//! Users should subclass this class and receive notifications by implementing
|
||||||
|
//! the SessionEndWatcherEvent() method.
|
||||||
|
class SessionEndWatcher : public Thread {
|
||||||
|
public:
|
||||||
|
SessionEndWatcher();
|
||||||
|
|
||||||
|
//! \note The destructor waits for the thread that runs the message loop to
|
||||||
|
//! terminate.
|
||||||
|
~SessionEndWatcher() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Exposed for testing.
|
||||||
|
HWND GetWindow() const { return window_; }
|
||||||
|
|
||||||
|
// Exposed for testing. Blocks until window_ has been created. May be called
|
||||||
|
// multiple times if necessary.
|
||||||
|
void WaitForStart();
|
||||||
|
|
||||||
|
// Exposed for testing. Blocks until the message loop ends. May be called
|
||||||
|
// multiple times if necessary.
|
||||||
|
void WaitForStop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Thread:
|
||||||
|
void ThreadMain() override;
|
||||||
|
|
||||||
|
static LRESULT CALLBACK WindowProc(HWND window,
|
||||||
|
UINT message,
|
||||||
|
WPARAM w_param,
|
||||||
|
LPARAM l_param);
|
||||||
|
|
||||||
|
//! \brief A `WM_ENDSESSION` message was received and it indicates that the
|
||||||
|
//! user session will be ending imminently.
|
||||||
|
//!
|
||||||
|
//! This method is called on the thread that runs the message loop.
|
||||||
|
virtual void SessionEnding() = 0;
|
||||||
|
|
||||||
|
HWND window_; // Conceptually strong, but ownership managed in ThreadMain()
|
||||||
|
ScopedKernelHANDLE started_;
|
||||||
|
ScopedKernelHANDLE stopped_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(SessionEndWatcher);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace crashpad
|
||||||
|
|
||||||
|
#endif // CRASHPAD_UTIL_WIN_SESSION_END_WATCHER_H_
|
62
util/win/session_end_watcher_test.cc
Normal file
62
util/win/session_end_watcher_test.cc
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// Copyright 2017 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 "util/win/session_end_watcher.h"
|
||||||
|
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "test/errors.h"
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
namespace test {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class SessionEndWatcherTest final : public SessionEndWatcher {
|
||||||
|
public:
|
||||||
|
SessionEndWatcherTest() : SessionEndWatcher(), called_(false) {}
|
||||||
|
|
||||||
|
~SessionEndWatcherTest() override {}
|
||||||
|
|
||||||
|
void Run() {
|
||||||
|
WaitForStart();
|
||||||
|
|
||||||
|
HWND window = GetWindow();
|
||||||
|
ASSERT_TRUE(window);
|
||||||
|
EXPECT_TRUE(PostMessage(window, WM_ENDSESSION, 1, 0));
|
||||||
|
|
||||||
|
WaitForStop();
|
||||||
|
|
||||||
|
EXPECT_TRUE(called_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// SessionEndWatcher:
|
||||||
|
void SessionEnding() override { called_ = true; }
|
||||||
|
|
||||||
|
bool called_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(SessionEndWatcherTest);
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(SessionEndWatcher, SessionEndWatcher) {
|
||||||
|
SessionEndWatcherTest test;
|
||||||
|
test.Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SessionEndWatcher, DoNothing) {
|
||||||
|
SessionEndWatcherTest test;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace test
|
||||||
|
} // namespace crashpad
|
Loading…
x
Reference in New Issue
Block a user