mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-20 10:43:46 +00:00
Merge master 42b57efa554a into doc
This commit is contained in:
commit
07e174cd8d
205
DEPS
205
DEPS
@ -37,6 +37,90 @@ deps = {
|
||||
'crashpad/third_party/zlib/zlib':
|
||||
Var('chromium_git') + '/chromium/src/third_party/zlib@' +
|
||||
'13dc246a58e4b72104d35f9b1809af95221ebda7',
|
||||
|
||||
# CIPD packages below.
|
||||
'crashpad/third_party/linux/clang/linux-amd64': {
|
||||
'packages': [
|
||||
{
|
||||
'package': 'fuchsia/clang/linux-amd64',
|
||||
'version': 'goma',
|
||||
},
|
||||
],
|
||||
'condition': 'checkout_linux and pull_linux_clang',
|
||||
'dep_type': 'cipd'
|
||||
},
|
||||
'crashpad/third_party/linux/clang/mac-amd64': {
|
||||
'packages': [
|
||||
{
|
||||
'package': 'fuchsia/clang/mac-amd64',
|
||||
'version': 'goma',
|
||||
},
|
||||
],
|
||||
'condition': 'checkout_fuchsia and host_os == "mac"',
|
||||
'dep_type': 'cipd'
|
||||
},
|
||||
'crashpad/third_party/fuchsia/clang/linux-amd64': {
|
||||
'packages': [
|
||||
{
|
||||
'package': 'fuchsia/clang/linux-amd64',
|
||||
'version': 'goma',
|
||||
},
|
||||
],
|
||||
'condition': 'checkout_fuchsia and host_os == "linux"',
|
||||
'dep_type': 'cipd'
|
||||
},
|
||||
'crashpad/third_party/fuchsia/qemu/mac-amd64': {
|
||||
'packages': [
|
||||
{
|
||||
'package': 'fuchsia/qemu/mac-amd64',
|
||||
'version': 'latest'
|
||||
},
|
||||
],
|
||||
'condition': 'checkout_fuchsia and host_os == "mac"',
|
||||
'dep_type': 'cipd'
|
||||
},
|
||||
'crashpad/third_party/fuchsia/qemu/linux-amd64': {
|
||||
'packages': [
|
||||
{
|
||||
'package': 'fuchsia/qemu/linux-amd64',
|
||||
'version': 'latest'
|
||||
},
|
||||
],
|
||||
'condition': 'checkout_fuchsia and host_os == "linux"',
|
||||
'dep_type': 'cipd'
|
||||
},
|
||||
'crashpad/third_party/fuchsia/sdk/linux-amd64': {
|
||||
# The SDK is keyed to the host system because it contains build tools.
|
||||
# Currently, linux-amd64 is the only SDK published (see
|
||||
# https://chrome-infra-packages.appspot.com/#/?path=fuchsia/sdk).
|
||||
# As long as this is the case, use that SDK package
|
||||
# even on other build hosts.
|
||||
# The sysroot (containing headers and libraries) and other components are
|
||||
# related to the target and should be functional with an appropriate
|
||||
# toolchain that runs on the build host (fuchsia_clang, above).
|
||||
'packages': [
|
||||
{
|
||||
'package': 'fuchsia/sdk/linux-amd64',
|
||||
'version': 'latest'
|
||||
},
|
||||
],
|
||||
'condition': 'checkout_fuchsia',
|
||||
'dep_type': 'cipd'
|
||||
},
|
||||
'crashpad/third_party/win/toolchain': {
|
||||
# This package is only updated when the solution in .gclient includes an
|
||||
# entry like:
|
||||
# "custom_vars": { "pull_win_toolchain": True }
|
||||
# This is because the contained bits are not redistributable.
|
||||
'packages': [
|
||||
{
|
||||
'package': 'chrome_internal/third_party/sdk/windows',
|
||||
'version': 'uploaded:2018-06-13'
|
||||
},
|
||||
],
|
||||
'condition': 'checkout_win and pull_win_toolchain',
|
||||
'dep_type': 'cipd'
|
||||
},
|
||||
}
|
||||
|
||||
hooks = [
|
||||
@ -118,28 +202,6 @@ hooks = [
|
||||
'buildtools/win/gn.exe.sha1',
|
||||
],
|
||||
},
|
||||
{
|
||||
# This uses “cipd install” so that mac-amd64 and linux-amd64 can coexist
|
||||
# peacefully. “cipd ensure” would remove the macOS package when running on a
|
||||
# Linux build host and vice-versa. https://crbug.com/789364. This package is
|
||||
# only updated when the solution in .gclient includes an entry like:
|
||||
# "custom_vars": { "pull_linux_clang": True }
|
||||
# The ref used is "goma". This is like "latest", but is considered a more
|
||||
# stable latest by the Fuchsia toolchain team.
|
||||
'name': 'clang_linux',
|
||||
'pattern': '.',
|
||||
'condition': 'checkout_linux and pull_linux_clang',
|
||||
'action': [
|
||||
'cipd',
|
||||
'install',
|
||||
# sic, using Fuchsia team's generic build of clang for linux-amd64 to
|
||||
# build for linux-amd64 target too.
|
||||
'fuchsia/clang/linux-amd64',
|
||||
'goma',
|
||||
'-root', 'crashpad/third_party/linux/clang/linux-amd64',
|
||||
'-log-level', 'info',
|
||||
],
|
||||
},
|
||||
{
|
||||
# If using a local clang ("pull_linux_clang" above), also pull down a
|
||||
# sysroot.
|
||||
@ -150,105 +212,6 @@ hooks = [
|
||||
'crashpad/build/install_linux_sysroot.py',
|
||||
],
|
||||
},
|
||||
{
|
||||
# Same rationale for using "install" rather than "ensure" as for first clang
|
||||
# package. https://crbug.com/789364.
|
||||
# Same rationale for using "goma" instead of "latest" as clang_linux above.
|
||||
'name': 'fuchsia_clang_mac',
|
||||
'pattern': '.',
|
||||
'condition': 'checkout_fuchsia and host_os == "mac"',
|
||||
'action': [
|
||||
'cipd',
|
||||
'install',
|
||||
'fuchsia/clang/mac-amd64',
|
||||
'goma',
|
||||
'-root', 'crashpad/third_party/fuchsia/clang/mac-amd64',
|
||||
'-log-level', 'info',
|
||||
],
|
||||
},
|
||||
{
|
||||
# Same rationale for using "install" rather than "ensure" as for first clang
|
||||
# package. https://crbug.com/789364.
|
||||
# Same rationale for using "goma" instead of "latest" as clang_linux above.
|
||||
'name': 'fuchsia_clang_linux',
|
||||
'pattern': '.',
|
||||
'condition': 'checkout_fuchsia and host_os == "linux"',
|
||||
'action': [
|
||||
'cipd',
|
||||
'install',
|
||||
'fuchsia/clang/linux-amd64',
|
||||
'goma',
|
||||
'-root', 'crashpad/third_party/fuchsia/clang/linux-amd64',
|
||||
'-log-level', 'info',
|
||||
],
|
||||
},
|
||||
{
|
||||
# Same rationale for using "install" rather than "ensure" as for clang
|
||||
# packages. https://crbug.com/789364.
|
||||
'name': 'fuchsia_qemu_mac',
|
||||
'pattern': '.',
|
||||
'condition': 'checkout_fuchsia and host_os == "mac"',
|
||||
'action': [
|
||||
'cipd',
|
||||
'install',
|
||||
'fuchsia/qemu/mac-amd64',
|
||||
'latest',
|
||||
'-root', 'crashpad/third_party/fuchsia/qemu/mac-amd64',
|
||||
'-log-level', 'info',
|
||||
],
|
||||
},
|
||||
{
|
||||
# Same rationale for using "install" rather than "ensure" as for clang
|
||||
# packages. https://crbug.com/789364.
|
||||
'name': 'fuchsia_qemu_linux',
|
||||
'pattern': '.',
|
||||
'condition': 'checkout_fuchsia and host_os == "linux"',
|
||||
'action': [
|
||||
'cipd',
|
||||
'install',
|
||||
'fuchsia/qemu/linux-amd64',
|
||||
'latest',
|
||||
'-root', 'crashpad/third_party/fuchsia/qemu/linux-amd64',
|
||||
'-log-level', 'info',
|
||||
],
|
||||
},
|
||||
{
|
||||
# The SDK is keyed to the host system because it contains build tools.
|
||||
# Currently, linux-amd64 is the only SDK published (see
|
||||
# https://chrome-infra-packages.appspot.com/#/?path=fuchsia/sdk). As long as
|
||||
# this is the case, use that SDK package even on other build hosts. The
|
||||
# sysroot (containing headers and libraries) and other components are
|
||||
# related to the target and should be functional with an appropriate
|
||||
# toolchain that runs on the build host (fuchsia_clang, above).
|
||||
'name': 'fuchsia_sdk',
|
||||
'pattern': '.',
|
||||
'condition': 'checkout_fuchsia',
|
||||
'action': [
|
||||
'cipd',
|
||||
'install',
|
||||
'fuchsia/sdk/linux-amd64',
|
||||
'latest',
|
||||
'-root', 'crashpad/third_party/fuchsia/sdk/linux-amd64',
|
||||
'-log-level', 'info',
|
||||
],
|
||||
},
|
||||
{
|
||||
'name': 'toolchain_win',
|
||||
'pattern': '.',
|
||||
# This package is only updated when the solution in .gclient includes an
|
||||
# entry like:
|
||||
# "custom_vars": { "pull_win_toolchain": True }
|
||||
# This is because the contained bits are not redistributable.
|
||||
'condition': 'checkout_win and pull_win_toolchain',
|
||||
'action': [
|
||||
'cipd',
|
||||
'install',
|
||||
'chrome_internal/third_party/sdk/windows',
|
||||
'uploaded:2018-06-13',
|
||||
'-root', 'crashpad/third_party/win/toolchain',
|
||||
'-log-level', 'info',
|
||||
],
|
||||
},
|
||||
{
|
||||
'name': 'gyp',
|
||||
'pattern': '\.gypi?$',
|
||||
|
@ -69,7 +69,8 @@ CrashReportDatabase::UploadReport::UploadReport()
|
||||
reader_(std::make_unique<FileReader>()),
|
||||
database_(nullptr),
|
||||
attachment_readers_(),
|
||||
attachment_map_() {}
|
||||
attachment_map_(),
|
||||
report_metrics_(false) {}
|
||||
|
||||
CrashReportDatabase::UploadReport::~UploadReport() {
|
||||
if (database_) {
|
||||
|
@ -178,6 +178,7 @@ class CrashReportDatabase {
|
||||
CrashReportDatabase* database_;
|
||||
std::vector<std::unique_ptr<FileReader>> attachment_readers_;
|
||||
std::map<std::string, FileReader*> attachment_map_;
|
||||
bool report_metrics_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(UploadReport);
|
||||
};
|
||||
@ -326,11 +327,16 @@ class CrashReportDatabase {
|
||||
//! \param[in] uuid The unique identifier for the crash report record.
|
||||
//! \param[out] report A crash report record for the report to be uploaded.
|
||||
//! Only valid if this returns #kNoError.
|
||||
//! \param[in] report_metrics If `false`, metrics will not be recorded for
|
||||
//! this upload attempt when RecordUploadComplete() is called or \a report
|
||||
//! is destroyed. Metadata for the upload attempt will still be recorded
|
||||
//! in the database.
|
||||
//!
|
||||
//! \return The operation status code.
|
||||
virtual OperationStatus GetReportForUploading(
|
||||
const UUID& uuid,
|
||||
std::unique_ptr<const UploadReport>* report) = 0;
|
||||
std::unique_ptr<const UploadReport>* report,
|
||||
bool report_metrics = true) = 0;
|
||||
|
||||
//! \brief Records a successful upload for a report and updates the last
|
||||
//! upload attempt time as returned by
|
||||
|
@ -180,7 +180,8 @@ class CrashReportDatabaseGeneric : public CrashReportDatabase {
|
||||
OperationStatus GetCompletedReports(std::vector<Report>* reports) override;
|
||||
OperationStatus GetReportForUploading(
|
||||
const UUID& uuid,
|
||||
std::unique_ptr<const UploadReport>* report) override;
|
||||
std::unique_ptr<const UploadReport>* report,
|
||||
bool report_metrics) override;
|
||||
OperationStatus SkipReportUpload(const UUID& uuid,
|
||||
Metrics::CrashSkippedReason reason) override;
|
||||
OperationStatus DeleteReport(const UUID& uuid) override;
|
||||
@ -455,7 +456,8 @@ OperationStatus CrashReportDatabaseGeneric::GetCompletedReports(
|
||||
|
||||
OperationStatus CrashReportDatabaseGeneric::GetReportForUploading(
|
||||
const UUID& uuid,
|
||||
std::unique_ptr<const UploadReport>* report) {
|
||||
std::unique_ptr<const UploadReport>* report,
|
||||
bool report_metrics) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
auto upload_report = std::make_unique<LockfileUploadReport>();
|
||||
@ -470,6 +472,7 @@ OperationStatus CrashReportDatabaseGeneric::GetReportForUploading(
|
||||
if (!upload_report->Initialize(path, this)) {
|
||||
return kFileSystemError;
|
||||
}
|
||||
upload_report->report_metrics_ = report_metrics;
|
||||
|
||||
report->reset(upload_report.release());
|
||||
return kNoError;
|
||||
@ -609,7 +612,9 @@ OperationStatus CrashReportDatabaseGeneric::RecordUploadAttempt(
|
||||
const std::string& id) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
Metrics::CrashUploadAttempted(successful);
|
||||
if (report->report_metrics_) {
|
||||
Metrics::CrashUploadAttempted(successful);
|
||||
}
|
||||
time_t now = time(nullptr);
|
||||
|
||||
report->id = id;
|
||||
|
@ -141,7 +141,8 @@ class CrashReportDatabaseMac : public CrashReportDatabase {
|
||||
OperationStatus GetCompletedReports(std::vector<Report>* reports) override;
|
||||
OperationStatus GetReportForUploading(
|
||||
const UUID& uuid,
|
||||
std::unique_ptr<const UploadReport>* report) override;
|
||||
std::unique_ptr<const UploadReport>* report,
|
||||
bool report_metrics) override;
|
||||
OperationStatus SkipReportUpload(const UUID& uuid,
|
||||
Metrics::CrashSkippedReason reason) override;
|
||||
OperationStatus DeleteReport(const UUID& uuid) override;
|
||||
@ -422,7 +423,8 @@ CrashReportDatabaseMac::GetCompletedReports(
|
||||
CrashReportDatabase::OperationStatus
|
||||
CrashReportDatabaseMac::GetReportForUploading(
|
||||
const UUID& uuid,
|
||||
std::unique_ptr<const UploadReport>* report) {
|
||||
std::unique_ptr<const UploadReport>* report,
|
||||
bool report_metrics) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
auto upload_report = std::make_unique<UploadReportMac>();
|
||||
@ -444,6 +446,7 @@ CrashReportDatabaseMac::GetReportForUploading(
|
||||
|
||||
upload_report->database_ = this;
|
||||
upload_report->lock_fd.reset(lock.release());
|
||||
upload_report->report_metrics_ = report_metrics;
|
||||
report->reset(upload_report.release());
|
||||
return kNoError;
|
||||
}
|
||||
@ -454,7 +457,9 @@ CrashReportDatabaseMac::RecordUploadAttempt(UploadReport* report,
|
||||
const std::string& id) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
Metrics::CrashUploadAttempted(successful);
|
||||
if (report->report_metrics_) {
|
||||
Metrics::CrashUploadAttempted(successful);
|
||||
}
|
||||
|
||||
DCHECK(report);
|
||||
DCHECK(successful || id.empty());
|
||||
|
@ -594,7 +594,8 @@ class CrashReportDatabaseWin : public CrashReportDatabase {
|
||||
OperationStatus GetCompletedReports(std::vector<Report>* reports) override;
|
||||
OperationStatus GetReportForUploading(
|
||||
const UUID& uuid,
|
||||
std::unique_ptr<const UploadReport>* report) override;
|
||||
std::unique_ptr<const UploadReport>* report,
|
||||
bool report_metrics) override;
|
||||
OperationStatus SkipReportUpload(const UUID& uuid,
|
||||
Metrics::CrashSkippedReason reason) override;
|
||||
OperationStatus DeleteReport(const UUID& uuid) override;
|
||||
@ -731,7 +732,8 @@ OperationStatus CrashReportDatabaseWin::GetCompletedReports(
|
||||
|
||||
OperationStatus CrashReportDatabaseWin::GetReportForUploading(
|
||||
const UUID& uuid,
|
||||
std::unique_ptr<const UploadReport>* report) {
|
||||
std::unique_ptr<const UploadReport>* report,
|
||||
bool report_metrics) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
std::unique_ptr<Metadata> metadata(AcquireMetadata());
|
||||
@ -749,6 +751,7 @@ OperationStatus CrashReportDatabaseWin::GetReportForUploading(
|
||||
if (!upload_report->Initialize(upload_report->file_path, this)) {
|
||||
return kFileSystemError;
|
||||
}
|
||||
upload_report->report_metrics_ = report_metrics;
|
||||
|
||||
report->reset(upload_report.release());
|
||||
}
|
||||
@ -761,7 +764,9 @@ OperationStatus CrashReportDatabaseWin::RecordUploadAttempt(
|
||||
const std::string& id) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
Metrics::CrashUploadAttempted(successful);
|
||||
if (report->report_metrics_) {
|
||||
Metrics::CrashUploadAttempted(successful);
|
||||
}
|
||||
|
||||
std::unique_ptr<Metadata> metadata(AcquireMetadata());
|
||||
if (!metadata)
|
||||
|
@ -41,9 +41,10 @@ class MockDatabase : public CrashReportDatabase {
|
||||
MOCK_METHOD2(LookUpCrashReport, OperationStatus(const UUID&, Report*));
|
||||
MOCK_METHOD1(GetPendingReports, OperationStatus(std::vector<Report>*));
|
||||
MOCK_METHOD1(GetCompletedReports, OperationStatus(std::vector<Report>*));
|
||||
MOCK_METHOD2(GetReportForUploading,
|
||||
MOCK_METHOD3(GetReportForUploading,
|
||||
OperationStatus(const UUID&,
|
||||
std::unique_ptr<const UploadReport>*));
|
||||
std::unique_ptr<const UploadReport>*,
|
||||
bool report_metrics));
|
||||
MOCK_METHOD3(RecordUploadAttempt,
|
||||
OperationStatus(UploadReport*, bool, const std::string&));
|
||||
MOCK_METHOD2(SkipReportUpload,
|
||||
|
@ -115,8 +115,7 @@ TEST(SimpleStringDictionary, CopyAndAssign) {
|
||||
// Add a bunch of values to the dictionary, remove some entries in the middle,
|
||||
// and then add more.
|
||||
TEST(SimpleStringDictionary, Iterator) {
|
||||
SimpleStringDictionary* dict = new SimpleStringDictionary;
|
||||
ASSERT_TRUE(dict);
|
||||
SimpleStringDictionary dict;
|
||||
|
||||
char key[SimpleStringDictionary::key_size];
|
||||
char value[SimpleStringDictionary::value_size];
|
||||
@ -135,32 +134,32 @@ TEST(SimpleStringDictionary, Iterator) {
|
||||
for (int i = 0; i < kPartitionIndex; ++i) {
|
||||
sprintf(key, "key%d", i);
|
||||
sprintf(value, "value%d", i);
|
||||
dict->SetKeyValue(key, value);
|
||||
dict.SetKeyValue(key, value);
|
||||
}
|
||||
expected_dictionary_size = kPartitionIndex;
|
||||
|
||||
// set a couple of the keys twice (with the same value) - should be nop
|
||||
dict->SetKeyValue("key2", "value2");
|
||||
dict->SetKeyValue("key4", "value4");
|
||||
dict->SetKeyValue("key15", "value15");
|
||||
dict.SetKeyValue("key2", "value2");
|
||||
dict.SetKeyValue("key4", "value4");
|
||||
dict.SetKeyValue("key15", "value15");
|
||||
|
||||
// Remove some random elements in the middle
|
||||
dict->RemoveKey("key7");
|
||||
dict->RemoveKey("key18");
|
||||
dict->RemoveKey("key23");
|
||||
dict->RemoveKey("key31");
|
||||
dict.RemoveKey("key7");
|
||||
dict.RemoveKey("key18");
|
||||
dict.RemoveKey("key23");
|
||||
dict.RemoveKey("key31");
|
||||
expected_dictionary_size -= 4; // we just removed four key/value pairs
|
||||
|
||||
// Set some more key/value pairs like key59/value59, key60/value60, ...
|
||||
for (int i = kPartitionIndex; i < kDictionaryCapacity; ++i) {
|
||||
sprintf(key, "key%d", i);
|
||||
sprintf(value, "value%d", i);
|
||||
dict->SetKeyValue(key, value);
|
||||
dict.SetKeyValue(key, value);
|
||||
}
|
||||
expected_dictionary_size += kDictionaryCapacity - kPartitionIndex;
|
||||
|
||||
// Now create an iterator on the dictionary
|
||||
SimpleStringDictionary::Iterator iter(*dict);
|
||||
SimpleStringDictionary::Iterator iter(dict);
|
||||
|
||||
// We then verify that it iterates through exactly the number of key/value
|
||||
// pairs we expect, and that they match one-for-one with what we would expect.
|
||||
|
@ -26,7 +26,7 @@
|
||||
static constexpr __ptrace_request PTRACE_GET_THREAD_AREA =
|
||||
static_cast<__ptrace_request>(25);
|
||||
#define PTRACE_GET_THREAD_AREA PTRACE_GET_THREAD_AREA
|
||||
#elif defined(__arm__) || defined(__arm64__)
|
||||
#elif defined(__arm__) || defined(__aarch64__)
|
||||
static constexpr __ptrace_request PTRACE_GET_THREAD_AREA =
|
||||
static_cast<__ptrace_request>(22);
|
||||
#define PTRACE_GET_THREAD_AREA PTRACE_GET_THREAD_AREA
|
||||
@ -42,7 +42,7 @@ static constexpr __ptrace_request PTRACE_GET_THREAD_AREA_3264 =
|
||||
|
||||
// https://sourceware.org/bugzilla/show_bug.cgi?id=22433
|
||||
#if !defined(PTRACE_GETVFPREGS) && !defined(PT_GETVFPREGS) && \
|
||||
defined(__GLIBC__) && (defined(__arm__) || defined(__arm64__))
|
||||
defined(__GLIBC__) && (defined(__arm__) || defined(__aarch64__))
|
||||
static constexpr __ptrace_request PTRACE_GETVFPREGS =
|
||||
static_cast<__ptrace_request>(27);
|
||||
#define PTRACE_GETVFPREGS PTRACE_GETVFPREGS
|
||||
|
@ -20,7 +20,7 @@
|
||||
#include <features.h>
|
||||
|
||||
// glibc for 64-bit ARM uses different names for these structs prior to 2.20.
|
||||
#if defined(__arm64__) && defined(__GLIBC__)
|
||||
#if defined(__aarch64__) && defined(__GLIBC__)
|
||||
#if !__GLIBC_PREREQ(2, 20)
|
||||
using user_regs_struct = user_pt_regs;
|
||||
using user_fpsimd_struct = user_fpsimd_state;
|
||||
|
@ -165,7 +165,7 @@ crashpad_executable("crashpad_handler") {
|
||||
# installer will ignore files not named like a shared object, so give the
|
||||
# handler executable an acceptable name.
|
||||
if (crashpad_is_android) {
|
||||
copy("crashpad_handler_module") {
|
||||
copy("crashpad_handler_named_as_so") {
|
||||
deps = [
|
||||
":crashpad_handler",
|
||||
]
|
||||
|
@ -227,6 +227,10 @@ void CrashReportUploadThread::ProcessPendingReport(
|
||||
database_->RecordUploadComplete(std::move(upload_report), response_body);
|
||||
break;
|
||||
case UploadResult::kPermanentFailure:
|
||||
upload_report.reset();
|
||||
database_->SkipReportUpload(
|
||||
report.uuid, Metrics::CrashSkippedReason::kPrepareForUploadFailed);
|
||||
break;
|
||||
case UploadResult::kRetry:
|
||||
upload_report.reset();
|
||||
|
||||
|
@ -33,10 +33,19 @@ verifiers {
|
||||
try_job {
|
||||
buckets {
|
||||
name: "master.client.crashpad"
|
||||
builders { name: "crashpad_try_mac_dbg" }
|
||||
builders { name: "crashpad_try_mac_rel" }
|
||||
builders { name: "crashpad_try_win_dbg" }
|
||||
builders { name: "crashpad_try_win_rel" }
|
||||
# https://crbug.com/743139 - disabled until we can move these to swarming,
|
||||
# at which point we can just remove them.
|
||||
#builders { name: "crashpad_try_win_x86_dbg" }
|
||||
#builders { name: "crashpad_try_win_x86_rel" }
|
||||
}
|
||||
buckets {
|
||||
name: "luci.crashpad.try"
|
||||
builders { name: "crashpad_try_mac_dbg" }
|
||||
builders { name: "crashpad_try_mac_rel" }
|
||||
builders { name: "crashpad_try_win_dbg" experiment_percentage: 100 }
|
||||
builders { name: "crashpad_try_win_rel" experiment_percentage: 100 }
|
||||
builders { name: "crashpad_try_linux_dbg" }
|
||||
builders { name: "crashpad_try_linux_rel" }
|
||||
builders { name: "crashpad_try_fuchsia_arm64_dbg" }
|
||||
|
@ -19,7 +19,7 @@
|
||||
#define PACKAGE_COPYRIGHT \
|
||||
"Copyright " PACKAGE_COPYRIGHT_YEAR " " PACKAGE_COPYRIGHT_OWNER
|
||||
#define PACKAGE_COPYRIGHT_OWNER "The Crashpad Authors"
|
||||
#define PACKAGE_COPYRIGHT_YEAR "2017"
|
||||
#define PACKAGE_COPYRIGHT_YEAR "2018"
|
||||
#define PACKAGE_NAME "Crashpad"
|
||||
#define PACKAGE_STRING PACKAGE_NAME " " PACKAGE_VERSION
|
||||
#define PACKAGE_TARNAME "crashpad"
|
||||
|
@ -31,12 +31,14 @@ class ElfImageReader::ProgramHeaderTable {
|
||||
public:
|
||||
virtual ~ProgramHeaderTable() {}
|
||||
|
||||
virtual bool VerifyLoadSegments() const = 0;
|
||||
virtual bool VerifyLoadSegments(bool verbose) const = 0;
|
||||
virtual size_t Size() const = 0;
|
||||
virtual bool GetDynamicSegment(VMAddress* address, VMSize* size) const = 0;
|
||||
virtual bool GetPreferredElfHeaderAddress(VMAddress* address) const = 0;
|
||||
virtual bool GetPreferredElfHeaderAddress(VMAddress* address,
|
||||
bool verbose) const = 0;
|
||||
virtual bool GetPreferredLoadedMemoryRange(VMAddress* address,
|
||||
VMSize* size) const = 0;
|
||||
VMSize* size,
|
||||
bool verbose) const = 0;
|
||||
|
||||
// Locate the next PT_NOTE segment starting at segment index start_index. If a
|
||||
// PT_NOTE segment is found, start_index is set to the next index after the
|
||||
@ -58,14 +60,15 @@ class ElfImageReader::ProgramHeaderTableSpecific
|
||||
|
||||
bool Initialize(const ProcessMemoryRange& memory,
|
||||
VMAddress address,
|
||||
VMSize num_segments) {
|
||||
VMSize num_segments,
|
||||
bool verbose) {
|
||||
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
|
||||
table_.resize(num_segments);
|
||||
if (!memory.Read(address, sizeof(PhdrType) * num_segments, table_.data())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!VerifyLoadSegments()) {
|
||||
if (!VerifyLoadSegments(verbose)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -73,7 +76,7 @@ class ElfImageReader::ProgramHeaderTableSpecific
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VerifyLoadSegments() const override {
|
||||
bool VerifyLoadSegments(bool verbose) const override {
|
||||
constexpr bool is_64_bit = std::is_same<PhdrType, Elf64_Phdr>::value;
|
||||
VMAddress last_vaddr;
|
||||
bool load_found = false;
|
||||
@ -83,12 +86,12 @@ class ElfImageReader::ProgramHeaderTableSpecific
|
||||
is_64_bit, header.p_vaddr, header.p_memsz);
|
||||
|
||||
if (!load_range.IsValid()) {
|
||||
LOG(ERROR) << "bad load range";
|
||||
LOG_IF(ERROR, verbose) << "bad load range";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (load_found && header.p_vaddr <= last_vaddr) {
|
||||
LOG(ERROR) << "out of order load segments";
|
||||
LOG_IF(ERROR, verbose) << "out of order load segments";
|
||||
return false;
|
||||
}
|
||||
load_found = true;
|
||||
@ -100,7 +103,8 @@ class ElfImageReader::ProgramHeaderTableSpecific
|
||||
|
||||
size_t Size() const override { return sizeof(PhdrType) * table_.size(); }
|
||||
|
||||
bool GetPreferredElfHeaderAddress(VMAddress* address) const override {
|
||||
bool GetPreferredElfHeaderAddress(VMAddress* address,
|
||||
bool verbose) const override {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
for (const auto& header : table_) {
|
||||
if (header.p_type == PT_LOAD && header.p_offset == 0) {
|
||||
@ -108,12 +112,13 @@ class ElfImageReader::ProgramHeaderTableSpecific
|
||||
return true;
|
||||
}
|
||||
}
|
||||
LOG(ERROR) << "no preferred header address";
|
||||
LOG_IF(ERROR, verbose) << "no preferred header address";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetPreferredLoadedMemoryRange(VMAddress* base,
|
||||
VMSize* size) const override {
|
||||
VMSize* size,
|
||||
bool verbose) const override {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
VMAddress preferred_base = 0;
|
||||
@ -133,7 +138,7 @@ class ElfImageReader::ProgramHeaderTableSpecific
|
||||
*size = preferred_end - preferred_base;
|
||||
return true;
|
||||
}
|
||||
LOG(ERROR) << "no load segments";
|
||||
LOG_IF(ERROR, verbose) << "no load segments";
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -340,7 +345,8 @@ ElfImageReader::ElfImageReader()
|
||||
ElfImageReader::~ElfImageReader() {}
|
||||
|
||||
bool ElfImageReader::Initialize(const ProcessMemoryRange& memory,
|
||||
VMAddress address) {
|
||||
VMAddress address,
|
||||
bool verbose) {
|
||||
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
|
||||
ehdr_address_ = address;
|
||||
if (!memory_.Initialize(memory)) {
|
||||
@ -354,13 +360,13 @@ bool ElfImageReader::Initialize(const ProcessMemoryRange& memory,
|
||||
|
||||
if (e_ident[EI_MAG0] != ELFMAG0 || e_ident[EI_MAG1] != ELFMAG1 ||
|
||||
e_ident[EI_MAG2] != ELFMAG2 || e_ident[EI_MAG3] != ELFMAG3) {
|
||||
LOG(ERROR) << "Incorrect ELF magic number";
|
||||
LOG_IF(ERROR, verbose) << "Incorrect ELF magic number";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(memory_.Is64Bit() && e_ident[EI_CLASS] == ELFCLASS64) &&
|
||||
!(!memory_.Is64Bit() && e_ident[EI_CLASS] == ELFCLASS32)) {
|
||||
LOG(ERROR) << "unexpected bitness";
|
||||
LOG_IF(ERROR, verbose) << "unexpected bitness";
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -370,12 +376,12 @@ bool ElfImageReader::Initialize(const ProcessMemoryRange& memory,
|
||||
constexpr uint8_t expected_encoding = ELFDATA2MSB;
|
||||
#endif
|
||||
if (e_ident[EI_DATA] != expected_encoding) {
|
||||
LOG(ERROR) << "unexpected encoding";
|
||||
LOG_IF(ERROR, verbose) << "unexpected encoding";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (e_ident[EI_VERSION] != EV_CURRENT) {
|
||||
LOG(ERROR) << "unexpected version";
|
||||
LOG_IF(ERROR, verbose) << "unexpected version";
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -388,15 +394,15 @@ bool ElfImageReader::Initialize(const ProcessMemoryRange& memory,
|
||||
#define VERIFY_HEADER(header) \
|
||||
do { \
|
||||
if (header.e_type != ET_EXEC && header.e_type != ET_DYN) { \
|
||||
LOG(ERROR) << "unexpected image type"; \
|
||||
LOG_IF(ERROR, verbose) << "unexpected image type"; \
|
||||
return false; \
|
||||
} \
|
||||
if (header.e_version != EV_CURRENT) { \
|
||||
LOG(ERROR) << "unexpected version"; \
|
||||
LOG_IF(ERROR, verbose) << "unexpected version"; \
|
||||
return false; \
|
||||
} \
|
||||
if (header.e_ehsize != sizeof(header)) { \
|
||||
LOG(ERROR) << "unexpected header size"; \
|
||||
LOG_IF(ERROR, verbose) << "unexpected header size"; \
|
||||
return false; \
|
||||
} \
|
||||
} while (false);
|
||||
@ -407,21 +413,21 @@ bool ElfImageReader::Initialize(const ProcessMemoryRange& memory,
|
||||
VERIFY_HEADER(header_32_);
|
||||
}
|
||||
|
||||
if (!InitializeProgramHeaders()) {
|
||||
if (!InitializeProgramHeaders(verbose)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
VMAddress preferred_ehdr_address;
|
||||
if (!program_headers_.get()->GetPreferredElfHeaderAddress(
|
||||
&preferred_ehdr_address)) {
|
||||
&preferred_ehdr_address, verbose)) {
|
||||
return false;
|
||||
}
|
||||
load_bias_ = ehdr_address_ - preferred_ehdr_address;
|
||||
|
||||
VMAddress base_address;
|
||||
VMSize loaded_size;
|
||||
if (!program_headers_.get()->GetPreferredLoadedMemoryRange(&base_address,
|
||||
&loaded_size)) {
|
||||
if (!program_headers_.get()->GetPreferredLoadedMemoryRange(
|
||||
&base_address, &loaded_size, verbose)) {
|
||||
return false;
|
||||
}
|
||||
base_address += load_bias_;
|
||||
@ -443,12 +449,12 @@ bool ElfImageReader::Initialize(const ProcessMemoryRange& memory,
|
||||
CheckedVMAddressRange range(memory_.Is64Bit(), base_address, loaded_size);
|
||||
if (!range.ContainsRange(
|
||||
CheckedVMAddressRange(memory_.Is64Bit(), ehdr_address_, ehdr_size))) {
|
||||
LOG(ERROR) << "ehdr out of range";
|
||||
LOG_IF(ERROR, verbose) << "ehdr out of range";
|
||||
return false;
|
||||
}
|
||||
if (!range.ContainsRange(CheckedVMAddressRange(
|
||||
memory.Is64Bit(), phdr_address, program_headers_->Size()))) {
|
||||
LOG(ERROR) << "phdrs out of range";
|
||||
LOG_IF(ERROR, verbose) << "phdrs out of range";
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -545,19 +551,40 @@ bool ElfImageReader::GetDebugAddress(VMAddress* debug) {
|
||||
return GetAddressFromDynamicArray(DT_DEBUG, true, debug);
|
||||
}
|
||||
|
||||
bool ElfImageReader::InitializeProgramHeaders() {
|
||||
#define INITIALIZE_PROGRAM_HEADERS(PhdrType, header) \
|
||||
do { \
|
||||
if (header.e_phentsize != sizeof(PhdrType)) { \
|
||||
LOG(ERROR) << "unexpected phdr size"; \
|
||||
return false; \
|
||||
} \
|
||||
auto phdrs = new ProgramHeaderTableSpecific<PhdrType>(); \
|
||||
program_headers_.reset(phdrs); \
|
||||
if (!phdrs->Initialize( \
|
||||
memory_, ehdr_address_ + header.e_phoff, header.e_phnum)) { \
|
||||
return false; \
|
||||
} \
|
||||
bool ElfImageReader::GetDynamicArrayAddress(VMAddress* address) {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
VMAddress dyn_segment_address;
|
||||
VMSize dyn_segment_size;
|
||||
if (!program_headers_.get()->GetDynamicSegment(&dyn_segment_address,
|
||||
&dyn_segment_size)) {
|
||||
LOG(ERROR) << "no dynamic segment";
|
||||
return false;
|
||||
}
|
||||
*address = dyn_segment_address + GetLoadBias();
|
||||
return true;
|
||||
}
|
||||
|
||||
VMAddress ElfImageReader::GetProgramHeaderTableAddress() {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
return ehdr_address_ +
|
||||
(memory_.Is64Bit() ? header_64_.e_phoff : header_32_.e_phoff);
|
||||
}
|
||||
|
||||
bool ElfImageReader::InitializeProgramHeaders(bool verbose) {
|
||||
#define INITIALIZE_PROGRAM_HEADERS(PhdrType, header) \
|
||||
do { \
|
||||
if (header.e_phentsize != sizeof(PhdrType)) { \
|
||||
LOG_IF(ERROR, verbose) << "unexpected phdr size"; \
|
||||
return false; \
|
||||
} \
|
||||
auto phdrs = new ProgramHeaderTableSpecific<PhdrType>(); \
|
||||
program_headers_.reset(phdrs); \
|
||||
if (!phdrs->Initialize(memory_, \
|
||||
ehdr_address_ + header.e_phoff, \
|
||||
header.e_phnum, \
|
||||
verbose)) { \
|
||||
return false; \
|
||||
} \
|
||||
} while (false);
|
||||
|
||||
if (memory_.Is64Bit()) {
|
||||
|
@ -118,7 +118,13 @@ class ElfImageReader {
|
||||
//! \param[in] memory A memory reader for the remote process.
|
||||
//! \param[in] address The address in the remote process' address space where
|
||||
//! the ELF image is loaded.
|
||||
bool Initialize(const ProcessMemoryRange& memory, VMAddress address);
|
||||
//! \param[in] verbose `true` if this method should log error messages during
|
||||
//! initialization. Setting this value to `false` will reduce the error
|
||||
//! messages relating to verifying the ELF image, but may not suppress
|
||||
//! logging entirely.
|
||||
bool Initialize(const ProcessMemoryRange& memory,
|
||||
VMAddress address,
|
||||
bool verbose = true);
|
||||
|
||||
//! \brief Returns the base address of the image's memory range.
|
||||
//!
|
||||
@ -172,6 +178,16 @@ class ElfImageReader {
|
||||
//! \return `true` if the debug address was found.
|
||||
bool GetDebugAddress(VMAddress* debug);
|
||||
|
||||
//! \brief Determine the address of `PT_DYNAMIC` segment.
|
||||
//!
|
||||
//! \param[out] address The address of the array, valid if this method returns
|
||||
//! `true`.
|
||||
//! \return `true` on success. Otherwise `false` with a message logged.
|
||||
bool GetDynamicArrayAddress(VMAddress* address);
|
||||
|
||||
//! \brief Return the address of the program header table.
|
||||
VMAddress GetProgramHeaderTableAddress();
|
||||
|
||||
//! \brief Return a NoteReader for this image, which scans all PT_NOTE
|
||||
//! segments in the image.
|
||||
//!
|
||||
@ -238,7 +254,7 @@ class ElfImageReader {
|
||||
template <typename PhdrType>
|
||||
class ProgramHeaderTableSpecific;
|
||||
|
||||
bool InitializeProgramHeaders();
|
||||
bool InitializeProgramHeaders(bool verbose);
|
||||
bool InitializeDynamicArray();
|
||||
bool InitializeDynamicSymbolTable();
|
||||
bool GetAddressFromDynamicArray(uint64_t tag, bool log, VMAddress* address);
|
||||
|
@ -103,10 +103,10 @@ void LocateExecutable(PtraceConnection* connection,
|
||||
ASSERT_TRUE(memory_map.Initialize(connection));
|
||||
const MemoryMap::Mapping* phdr_mapping = memory_map.FindMapping(phdrs);
|
||||
ASSERT_TRUE(phdr_mapping);
|
||||
const MemoryMap::Mapping* exe_mapping =
|
||||
memory_map.FindFileMmapStart(*phdr_mapping);
|
||||
ASSERT_TRUE(exe_mapping);
|
||||
*elf_address = exe_mapping->range.Base();
|
||||
std::vector<const MemoryMap::Mapping*> possible_mappings =
|
||||
memory_map.FindFilePossibleMmapStarts(*phdr_mapping);
|
||||
ASSERT_EQ(possible_mappings.size(), 1u);
|
||||
*elf_address = possible_mappings[0]->range.Base();
|
||||
}
|
||||
|
||||
#endif // OS_FUCHSIA
|
||||
|
@ -74,9 +74,10 @@ void TestAgainstTarget(PtraceConnection* connection) {
|
||||
|
||||
const MemoryMap::Mapping* phdr_mapping = mappings.FindMapping(phdrs);
|
||||
ASSERT_TRUE(phdr_mapping);
|
||||
const MemoryMap::Mapping* exe_mapping =
|
||||
mappings.FindFileMmapStart(*phdr_mapping);
|
||||
LinuxVMAddress elf_address = exe_mapping->range.Base();
|
||||
std::vector<const MemoryMap::Mapping*> exe_mappings =
|
||||
mappings.FindFilePossibleMmapStarts(*phdr_mapping);
|
||||
ASSERT_EQ(exe_mappings.size(), 1u);
|
||||
LinuxVMAddress elf_address = exe_mappings[0]->range.Base();
|
||||
|
||||
ProcessMemoryLinux memory;
|
||||
ASSERT_TRUE(memory.Initialize(connection->GetProcessID()));
|
||||
@ -142,9 +143,24 @@ void TestAgainstTarget(PtraceConnection* connection) {
|
||||
mappings.FindMapping(module.dynamic_array);
|
||||
ASSERT_TRUE(dyn_mapping);
|
||||
|
||||
const MemoryMap::Mapping* module_mapping =
|
||||
mappings.FindFileMmapStart(*dyn_mapping);
|
||||
ASSERT_TRUE(module_mapping);
|
||||
std::vector<const MemoryMap::Mapping*> possible_mappings =
|
||||
mappings.FindFilePossibleMmapStarts(*dyn_mapping);
|
||||
ASSERT_GE(possible_mappings.size(), 1u);
|
||||
|
||||
std::unique_ptr<ElfImageReader> module_reader;
|
||||
const MemoryMap::Mapping* module_mapping = nullptr;
|
||||
for (const auto mapping : possible_mappings) {
|
||||
auto parsed_module = std::make_unique<ElfImageReader>();
|
||||
VMAddress dynamic_address;
|
||||
if (parsed_module->Initialize(range, mapping->range.Base()) &&
|
||||
parsed_module->GetDynamicArrayAddress(&dynamic_address) &&
|
||||
dynamic_address == module.dynamic_array) {
|
||||
module_reader = std::move(parsed_module);
|
||||
module_mapping = mapping;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(module_reader.get());
|
||||
|
||||
#if defined(OS_ANDROID)
|
||||
EXPECT_FALSE(module.name.empty());
|
||||
@ -168,20 +184,17 @@ void TestAgainstTarget(PtraceConnection* connection) {
|
||||
module.name);
|
||||
#endif // OS_ANDROID
|
||||
|
||||
ElfImageReader module_reader;
|
||||
ASSERT_TRUE(module_reader.Initialize(range, module_mapping->range.Base()));
|
||||
|
||||
// Android's loader stops setting its own load bias after Android 4.4.4
|
||||
// (API 20) until Android 6.0 (API 23).
|
||||
if (is_android_loader && android_runtime_api > 20 &&
|
||||
android_runtime_api < 23) {
|
||||
EXPECT_EQ(module.load_bias, 0);
|
||||
} else {
|
||||
EXPECT_EQ(module.load_bias, module_reader.GetLoadBias());
|
||||
EXPECT_EQ(module.load_bias, module_reader->GetLoadBias());
|
||||
}
|
||||
|
||||
CheckedLinuxAddressRange module_range(
|
||||
connection->Is64Bit(), module_reader.Address(), module_reader.Size());
|
||||
connection->Is64Bit(), module_reader->Address(), module_reader->Size());
|
||||
EXPECT_TRUE(module_range.ContainsValue(module.dynamic_array));
|
||||
}
|
||||
}
|
||||
|
@ -347,20 +347,45 @@ void ProcessReaderLinux::InitializeModules() {
|
||||
return;
|
||||
}
|
||||
|
||||
const MemoryMap::Mapping* exe_mapping;
|
||||
if (!(exe_mapping = GetMemoryMap()->FindMapping(phdrs)) ||
|
||||
!(exe_mapping = GetMemoryMap()->FindFileMmapStart(*exe_mapping))) {
|
||||
return;
|
||||
}
|
||||
|
||||
ProcessMemoryRange range;
|
||||
if (!range.Initialize(Memory(), is_64_bit_)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto exe_reader = std::make_unique<ElfImageReader>();
|
||||
if (!exe_reader->Initialize(range, exe_mapping->range.Base())) {
|
||||
return;
|
||||
// The strategy used for identifying loaded modules depends on ELF files
|
||||
// conventionally loading their header and program headers into memory.
|
||||
// Locating the correct module could fail if the headers aren't mapped, are
|
||||
// mapped at an unexpected location, or if there are other mappings
|
||||
// constructed to look like the ELF module being searched for.
|
||||
const MemoryMap::Mapping* exe_mapping = nullptr;
|
||||
std::unique_ptr<ElfImageReader> exe_reader;
|
||||
{
|
||||
const MemoryMap::Mapping* phdr_mapping = memory_map_.FindMapping(phdrs);
|
||||
if (!phdr_mapping) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<const MemoryMap::Mapping*> possible_mappings =
|
||||
memory_map_.FindFilePossibleMmapStarts(*phdr_mapping);
|
||||
for (auto riter = possible_mappings.rbegin();
|
||||
riter != possible_mappings.rend();
|
||||
++riter) {
|
||||
auto mapping = *riter;
|
||||
auto parsed_exe = std::make_unique<ElfImageReader>();
|
||||
if (parsed_exe->Initialize(
|
||||
range,
|
||||
mapping->range.Base(),
|
||||
/* verbose= */ possible_mappings.size() == 1) &&
|
||||
parsed_exe->GetProgramHeaderTableAddress() == phdrs) {
|
||||
exe_mapping = mapping;
|
||||
exe_reader = std::move(parsed_exe);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!exe_mapping) {
|
||||
LOG(ERROR) << "no exe mappings " << possible_mappings.size();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LinuxVMAddress debug_address;
|
||||
@ -386,19 +411,42 @@ void ProcessReaderLinux::InitializeModules() {
|
||||
aux.GetValue(AT_BASE, &loader_base);
|
||||
|
||||
for (const DebugRendezvous::LinkEntry& entry : debug.Modules()) {
|
||||
const MemoryMap::Mapping* mapping;
|
||||
if (!(mapping = memory_map_.FindMapping(entry.dynamic_array)) ||
|
||||
!(mapping = memory_map_.FindFileMmapStart(*mapping))) {
|
||||
continue;
|
||||
}
|
||||
const MemoryMap::Mapping* module_mapping = nullptr;
|
||||
std::unique_ptr<ElfImageReader> elf_reader;
|
||||
{
|
||||
const MemoryMap::Mapping* dyn_mapping =
|
||||
memory_map_.FindMapping(entry.dynamic_array);
|
||||
if (!dyn_mapping) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto elf_reader = std::make_unique<ElfImageReader>();
|
||||
if (!elf_reader->Initialize(range, mapping->range.Base())) {
|
||||
continue;
|
||||
std::vector<const MemoryMap::Mapping*> possible_mappings =
|
||||
memory_map_.FindFilePossibleMmapStarts(*dyn_mapping);
|
||||
for (auto riter = possible_mappings.rbegin();
|
||||
riter != possible_mappings.rend();
|
||||
++riter) {
|
||||
auto mapping = *riter;
|
||||
auto parsed_module = std::make_unique<ElfImageReader>();
|
||||
VMAddress dynamic_address;
|
||||
if (parsed_module->Initialize(
|
||||
range,
|
||||
mapping->range.Base(),
|
||||
/* verbose= */ possible_mappings.size() == 1) &&
|
||||
parsed_module->GetDynamicArrayAddress(&dynamic_address) &&
|
||||
dynamic_address == entry.dynamic_array) {
|
||||
module_mapping = mapping;
|
||||
elf_reader = std::move(parsed_module);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!module_mapping) {
|
||||
LOG(ERROR) << "no module mappings " << possible_mappings.size();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Module module = {};
|
||||
module.name = !entry.name.empty() ? entry.name : mapping->name;
|
||||
module.name = !entry.name.empty() ? entry.name : module_mapping->name;
|
||||
module.elf_reader = elf_reader.get();
|
||||
module.type = loader_base && elf_reader->Address() == loader_base
|
||||
? ModuleSnapshot::kModuleTypeDynamicLoader
|
||||
|
@ -14,6 +14,8 @@
|
||||
|
||||
#include "snapshot/linux/process_reader_linux.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <elf.h>
|
||||
#include <errno.h>
|
||||
#include <link.h>
|
||||
#include <pthread.h>
|
||||
@ -38,8 +40,13 @@
|
||||
#include "test/linux/fake_ptrace_connection.h"
|
||||
#include "test/linux/get_tls.h"
|
||||
#include "test/multiprocess.h"
|
||||
#include "test/scoped_module_handle.h"
|
||||
#include "test/test_paths.h"
|
||||
#include "util/file/file_io.h"
|
||||
#include "util/file/file_writer.h"
|
||||
#include "util/file/filesystem.h"
|
||||
#include "util/linux/direct_ptrace_connection.h"
|
||||
#include "util/misc/address_sanitizer.h"
|
||||
#include "util/misc/from_pointer_cast.h"
|
||||
#include "util/synchronization/semaphore.h"
|
||||
|
||||
@ -264,12 +271,17 @@ void ExpectThreads(const ThreadMap& thread_map,
|
||||
iterator->second.tls);
|
||||
|
||||
ASSERT_TRUE(memory_map.FindMapping(thread.stack_region_address));
|
||||
EXPECT_LE(thread.stack_region_address, iterator->second.stack_address);
|
||||
|
||||
ASSERT_TRUE(memory_map.FindMapping(thread.stack_region_address +
|
||||
thread.stack_region_size - 1));
|
||||
|
||||
#if !defined(ADDRESS_SANITIZER)
|
||||
// AddressSanitizer causes stack variables to be stored separately from the
|
||||
// call stack.
|
||||
EXPECT_LE(thread.stack_region_address, iterator->second.stack_address);
|
||||
EXPECT_GE(thread.stack_region_address + thread.stack_region_size,
|
||||
iterator->second.stack_address);
|
||||
#endif // !defined(ADDRESS_SANITIZER)
|
||||
|
||||
if (iterator->second.max_stack_size) {
|
||||
EXPECT_LT(thread.stack_region_size, iterator->second.max_stack_size);
|
||||
}
|
||||
@ -501,7 +513,220 @@ void ExpectModulesFromSelf(
|
||||
#endif // !OS_ANDROID || !ARCH_CPU_ARMEL || __ANDROID_API__ >= 21
|
||||
}
|
||||
|
||||
bool WriteTestModule(const base::FilePath& module_path) {
|
||||
#if defined(ARCH_CPU_64_BITS)
|
||||
using Ehdr = Elf64_Ehdr;
|
||||
using Phdr = Elf64_Phdr;
|
||||
using Shdr = Elf64_Shdr;
|
||||
using Dyn = Elf64_Dyn;
|
||||
using Sym = Elf64_Sym;
|
||||
unsigned char elf_class = ELFCLASS64;
|
||||
#else
|
||||
using Ehdr = Elf32_Ehdr;
|
||||
using Phdr = Elf32_Phdr;
|
||||
using Shdr = Elf32_Shdr;
|
||||
using Dyn = Elf32_Dyn;
|
||||
using Sym = Elf32_Sym;
|
||||
unsigned char elf_class = ELFCLASS32;
|
||||
#endif
|
||||
|
||||
struct {
|
||||
Ehdr ehdr;
|
||||
struct {
|
||||
Phdr load1;
|
||||
Phdr load2;
|
||||
Phdr dynamic;
|
||||
} phdr_table;
|
||||
struct {
|
||||
Dyn hash;
|
||||
Dyn strtab;
|
||||
Dyn symtab;
|
||||
Dyn strsz;
|
||||
Dyn syment;
|
||||
Dyn null;
|
||||
} dynamic_array;
|
||||
struct {
|
||||
Elf32_Word nbucket;
|
||||
Elf32_Word nchain;
|
||||
Elf32_Word bucket;
|
||||
Elf32_Word chain;
|
||||
} hash_table;
|
||||
struct {
|
||||
} string_table;
|
||||
struct {
|
||||
Sym und_symbol;
|
||||
} symbol_table;
|
||||
struct {
|
||||
Shdr null;
|
||||
Shdr dynamic;
|
||||
Shdr string_table;
|
||||
} shdr_table;
|
||||
} module = {};
|
||||
|
||||
module.ehdr.e_ident[EI_MAG0] = ELFMAG0;
|
||||
module.ehdr.e_ident[EI_MAG1] = ELFMAG1;
|
||||
module.ehdr.e_ident[EI_MAG2] = ELFMAG2;
|
||||
module.ehdr.e_ident[EI_MAG3] = ELFMAG3;
|
||||
|
||||
module.ehdr.e_ident[EI_CLASS] = elf_class;
|
||||
|
||||
#if defined(ARCH_CPU_LITTLE_ENDIAN)
|
||||
module.ehdr.e_ident[EI_DATA] = ELFDATA2LSB;
|
||||
#else
|
||||
module.ehdr.e_ident[EI_DATA] = ELFDATA2MSB;
|
||||
#endif // ARCH_CPU_LITTLE_ENDIAN
|
||||
|
||||
module.ehdr.e_ident[EI_VERSION] = EV_CURRENT;
|
||||
|
||||
module.ehdr.e_type = ET_DYN;
|
||||
|
||||
#if defined(ARCH_CPU_X86)
|
||||
module.ehdr.e_machine = EM_386;
|
||||
#elif defined(ARCH_CPU_X86_64)
|
||||
module.ehdr.e_machine = EM_X86_64;
|
||||
#elif defined(ARCH_CPU_ARMEL)
|
||||
module.ehdr.e_machine = EM_ARM;
|
||||
#elif defined(ARCH_CPU_ARM64)
|
||||
module.ehdr.e_machine = EM_AARCH64;
|
||||
#elif defined(ARCH_CPU_MIPSEL) || defined(ARCH_CPU_MIPS64EL)
|
||||
module.ehdr.e_machine = EM_MIPS;
|
||||
#endif
|
||||
|
||||
module.ehdr.e_version = EV_CURRENT;
|
||||
module.ehdr.e_ehsize = sizeof(module.ehdr);
|
||||
|
||||
module.ehdr.e_phoff = offsetof(decltype(module), phdr_table);
|
||||
module.ehdr.e_phnum = sizeof(module.phdr_table) / sizeof(Phdr);
|
||||
module.ehdr.e_phentsize = sizeof(Phdr);
|
||||
|
||||
module.ehdr.e_shoff = offsetof(decltype(module), shdr_table);
|
||||
module.ehdr.e_shentsize = sizeof(Shdr);
|
||||
module.ehdr.e_shnum = sizeof(module.shdr_table) / sizeof(Shdr);
|
||||
module.ehdr.e_shstrndx = SHN_UNDEF;
|
||||
|
||||
constexpr size_t load2_vaddr = 0x200000;
|
||||
|
||||
module.phdr_table.load1.p_type = PT_LOAD;
|
||||
module.phdr_table.load1.p_offset = 0;
|
||||
module.phdr_table.load1.p_vaddr = 0;
|
||||
module.phdr_table.load1.p_filesz = sizeof(module);
|
||||
module.phdr_table.load1.p_memsz = sizeof(module);
|
||||
module.phdr_table.load1.p_flags = PF_R;
|
||||
module.phdr_table.load1.p_align = load2_vaddr;
|
||||
|
||||
module.phdr_table.load2.p_type = PT_LOAD;
|
||||
module.phdr_table.load2.p_offset = 0;
|
||||
module.phdr_table.load2.p_vaddr = load2_vaddr;
|
||||
module.phdr_table.load2.p_filesz = sizeof(module);
|
||||
module.phdr_table.load2.p_memsz = sizeof(module);
|
||||
module.phdr_table.load2.p_flags = PF_R | PF_W;
|
||||
module.phdr_table.load2.p_align = load2_vaddr;
|
||||
|
||||
module.phdr_table.dynamic.p_type = PT_DYNAMIC;
|
||||
module.phdr_table.dynamic.p_offset =
|
||||
offsetof(decltype(module), dynamic_array);
|
||||
module.phdr_table.dynamic.p_vaddr =
|
||||
load2_vaddr + module.phdr_table.dynamic.p_offset;
|
||||
module.phdr_table.dynamic.p_filesz = sizeof(module.dynamic_array);
|
||||
module.phdr_table.dynamic.p_memsz = sizeof(module.dynamic_array);
|
||||
module.phdr_table.dynamic.p_flags = PF_R | PF_W;
|
||||
module.phdr_table.dynamic.p_align = 8;
|
||||
|
||||
module.dynamic_array.hash.d_tag = DT_HASH;
|
||||
module.dynamic_array.hash.d_un.d_ptr = offsetof(decltype(module), hash_table);
|
||||
module.dynamic_array.strtab.d_tag = DT_STRTAB;
|
||||
module.dynamic_array.strtab.d_un.d_ptr =
|
||||
offsetof(decltype(module), string_table);
|
||||
module.dynamic_array.symtab.d_tag = DT_SYMTAB;
|
||||
module.dynamic_array.symtab.d_un.d_ptr =
|
||||
offsetof(decltype(module), symbol_table);
|
||||
module.dynamic_array.strsz.d_tag = DT_STRSZ;
|
||||
module.dynamic_array.strsz.d_un.d_val = sizeof(module.string_table);
|
||||
module.dynamic_array.syment.d_tag = DT_SYMENT;
|
||||
module.dynamic_array.syment.d_un.d_val = sizeof(Sym);
|
||||
|
||||
module.dynamic_array.null.d_tag = DT_NULL;
|
||||
|
||||
module.hash_table.nbucket = 1;
|
||||
module.hash_table.nchain = 1;
|
||||
module.hash_table.bucket = 0;
|
||||
module.hash_table.chain = 0;
|
||||
|
||||
module.shdr_table.null.sh_type = SHT_NULL;
|
||||
|
||||
module.shdr_table.dynamic.sh_name = 0;
|
||||
module.shdr_table.dynamic.sh_type = SHT_DYNAMIC;
|
||||
module.shdr_table.dynamic.sh_flags = SHF_WRITE | SHF_ALLOC;
|
||||
module.shdr_table.dynamic.sh_addr = module.phdr_table.dynamic.p_vaddr;
|
||||
module.shdr_table.dynamic.sh_offset = module.phdr_table.dynamic.p_offset;
|
||||
module.shdr_table.dynamic.sh_size = module.phdr_table.dynamic.p_filesz;
|
||||
module.shdr_table.dynamic.sh_link =
|
||||
offsetof(decltype(module.shdr_table), string_table) / sizeof(Shdr);
|
||||
|
||||
module.shdr_table.string_table.sh_name = 0;
|
||||
module.shdr_table.string_table.sh_type = SHT_STRTAB;
|
||||
module.shdr_table.string_table.sh_offset =
|
||||
offsetof(decltype(module), string_table);
|
||||
|
||||
FileWriter writer;
|
||||
if (!writer.Open(module_path,
|
||||
FileWriteMode::kCreateOrFail,
|
||||
FilePermissions::kWorldReadable)) {
|
||||
ADD_FAILURE();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!writer.Write(&module, sizeof(module))) {
|
||||
ADD_FAILURE();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ScopedModuleHandle LoadTestModule(const std::string& module_name) {
|
||||
base::FilePath module_path(
|
||||
TestPaths::Executable().DirName().Append(module_name));
|
||||
|
||||
if (!WriteTestModule(module_path)) {
|
||||
return ScopedModuleHandle(nullptr);
|
||||
}
|
||||
EXPECT_TRUE(IsRegularFile(module_path));
|
||||
|
||||
ScopedModuleHandle handle(
|
||||
dlopen(module_path.value().c_str(), RTLD_LAZY | RTLD_LOCAL));
|
||||
EXPECT_TRUE(handle.valid())
|
||||
<< "dlopen: " << module_path.value() << " " << dlerror();
|
||||
|
||||
EXPECT_TRUE(LoggingRemoveFile(module_path));
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
void ExpectTestModule(ProcessReaderLinux* reader,
|
||||
const std::string& module_name) {
|
||||
for (const auto& module : reader->Modules()) {
|
||||
if (module.name.find(module_name) != std::string::npos) {
|
||||
ASSERT_TRUE(module.elf_reader);
|
||||
|
||||
VMAddress dynamic_addr;
|
||||
ASSERT_TRUE(module.elf_reader->GetDynamicArrayAddress(&dynamic_addr));
|
||||
|
||||
auto dynamic_mapping = reader->GetMemoryMap()->FindMapping(dynamic_addr);
|
||||
auto mappings =
|
||||
reader->GetMemoryMap()->FindFilePossibleMmapStarts(*dynamic_mapping);
|
||||
EXPECT_EQ(mappings.size(), 2u);
|
||||
return;
|
||||
}
|
||||
}
|
||||
ADD_FAILURE() << "Test module not found";
|
||||
}
|
||||
|
||||
TEST(ProcessReaderLinux, SelfModules) {
|
||||
const std::string module_name = "test_module.so";
|
||||
ScopedModuleHandle empty_test_module(LoadTestModule(module_name));
|
||||
ASSERT_TRUE(empty_test_module.valid());
|
||||
|
||||
FakePtraceConnection connection;
|
||||
connection.Initialize(getpid());
|
||||
|
||||
@ -509,15 +734,19 @@ TEST(ProcessReaderLinux, SelfModules) {
|
||||
ASSERT_TRUE(process_reader.Initialize(&connection));
|
||||
|
||||
ExpectModulesFromSelf(process_reader.Modules());
|
||||
ExpectTestModule(&process_reader, module_name);
|
||||
}
|
||||
|
||||
class ChildModuleTest : public Multiprocess {
|
||||
public:
|
||||
ChildModuleTest() : Multiprocess() {}
|
||||
ChildModuleTest() : Multiprocess(), module_name_("test_module.so") {}
|
||||
~ChildModuleTest() = default;
|
||||
|
||||
private:
|
||||
void MultiprocessParent() override {
|
||||
char c;
|
||||
ASSERT_TRUE(LoggingReadFileExactly(ReadPipeHandle(), &c, sizeof(c)));
|
||||
|
||||
DirectPtraceConnection connection;
|
||||
ASSERT_TRUE(connection.Initialize(ChildPID()));
|
||||
|
||||
@ -525,9 +754,20 @@ class ChildModuleTest : public Multiprocess {
|
||||
ASSERT_TRUE(process_reader.Initialize(&connection));
|
||||
|
||||
ExpectModulesFromSelf(process_reader.Modules());
|
||||
ExpectTestModule(&process_reader, module_name_);
|
||||
}
|
||||
|
||||
void MultiprocessChild() override { CheckedReadFileAtEOF(ReadPipeHandle()); }
|
||||
void MultiprocessChild() override {
|
||||
ScopedModuleHandle empty_test_module(LoadTestModule(module_name_));
|
||||
ASSERT_TRUE(empty_test_module.valid());
|
||||
|
||||
char c;
|
||||
ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(), &c, sizeof(c)));
|
||||
|
||||
CheckedReadFileAtEOF(ReadPipeHandle());
|
||||
}
|
||||
|
||||
const std::string module_name_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ChildModuleTest);
|
||||
};
|
||||
|
@ -31,6 +31,7 @@ ProcessSnapshotMinidump::ProcessSnapshotMinidump()
|
||||
crashpad_info_(),
|
||||
annotations_simple_map_(),
|
||||
file_reader_(nullptr),
|
||||
process_id_(static_cast<pid_t>(-1)),
|
||||
initialized_() {
|
||||
}
|
||||
|
||||
@ -83,19 +84,19 @@ bool ProcessSnapshotMinidump::Initialize(FileReaderInterface* file_reader) {
|
||||
stream_map_[stream_type] = &directory.Location;
|
||||
}
|
||||
|
||||
INITIALIZATION_STATE_SET_VALID(initialized_);
|
||||
|
||||
if (!InitializeCrashpadInfo()) {
|
||||
if (!InitializeCrashpadInfo() ||
|
||||
!InitializeMiscInfo() ||
|
||||
!InitializeModules()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return InitializeModules();
|
||||
INITIALIZATION_STATE_SET_VALID(initialized_);
|
||||
return true;
|
||||
}
|
||||
|
||||
pid_t ProcessSnapshotMinidump::ProcessID() const {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
NOTREACHED(); // https://crashpad.chromium.org/bug/10
|
||||
return 0;
|
||||
return process_id_;
|
||||
}
|
||||
|
||||
pid_t ProcessSnapshotMinidump::ParentProcessID() const {
|
||||
@ -234,6 +235,45 @@ bool ProcessSnapshotMinidump::InitializeCrashpadInfo() {
|
||||
&annotations_simple_map_);
|
||||
}
|
||||
|
||||
bool ProcessSnapshotMinidump::InitializeMiscInfo() {
|
||||
const auto& stream_it = stream_map_.find(kMinidumpStreamTypeMiscInfo);
|
||||
if (stream_it == stream_map_.end()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!file_reader_->SeekSet(stream_it->second->Rva)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t size = stream_it->second->DataSize;
|
||||
if (size != sizeof(MINIDUMP_MISC_INFO_5) &&
|
||||
size != sizeof(MINIDUMP_MISC_INFO_4) &&
|
||||
size != sizeof(MINIDUMP_MISC_INFO_3) &&
|
||||
size != sizeof(MINIDUMP_MISC_INFO_2) &&
|
||||
size != sizeof(MINIDUMP_MISC_INFO)) {
|
||||
LOG(ERROR) << "misc_info size mismatch";
|
||||
return false;
|
||||
}
|
||||
|
||||
MINIDUMP_MISC_INFO_5 info;
|
||||
if (!file_reader_->ReadExactly(&info, size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (stream_it->second->DataSize) {
|
||||
case sizeof(MINIDUMP_MISC_INFO_5):
|
||||
case sizeof(MINIDUMP_MISC_INFO_4):
|
||||
case sizeof(MINIDUMP_MISC_INFO_3):
|
||||
case sizeof(MINIDUMP_MISC_INFO_2):
|
||||
case sizeof(MINIDUMP_MISC_INFO):
|
||||
// TODO(jperaza): Save the remaining misc info.
|
||||
// https://crashpad.chromium.org/bug/10
|
||||
process_id_ = info.ProcessId;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProcessSnapshotMinidump::InitializeModules() {
|
||||
const auto& stream_it = stream_map_.find(kMinidumpStreamTypeModuleList);
|
||||
if (stream_it == stream_map_.end()) {
|
||||
|
@ -92,6 +92,10 @@ class ProcessSnapshotMinidump final : public ProcessSnapshot {
|
||||
std::map<uint32_t, MINIDUMP_LOCATION_DESCRIPTOR>*
|
||||
module_crashpad_info_links);
|
||||
|
||||
// Initializes data carried in a MINIDUMP_MISC_INFO structure on behalf of
|
||||
// Initialize().
|
||||
bool InitializeMiscInfo();
|
||||
|
||||
MINIDUMP_HEADER header_;
|
||||
std::vector<MINIDUMP_DIRECTORY> stream_directory_;
|
||||
std::map<MinidumpStreamType, const MINIDUMP_LOCATION_DESCRIPTOR*> stream_map_;
|
||||
@ -100,6 +104,7 @@ class ProcessSnapshotMinidump final : public ProcessSnapshot {
|
||||
MinidumpCrashpadInfo crashpad_info_;
|
||||
std::map<std::string, std::string> annotations_simple_map_;
|
||||
FileReaderInterface* file_reader_; // weak
|
||||
pid_t process_id_;
|
||||
InitializationStateDcheck initialized_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ProcessSnapshotMinidump);
|
||||
|
@ -420,6 +420,38 @@ TEST(ProcessSnapshotMinidump, Modules) {
|
||||
EXPECT_EQ(annotation_objects, annotations_4);
|
||||
}
|
||||
|
||||
TEST(ProcessSnapshotMinidump, ProcessID) {
|
||||
StringFile string_file;
|
||||
|
||||
MINIDUMP_HEADER header = {};
|
||||
ASSERT_TRUE(string_file.Write(&header, sizeof(header)));
|
||||
|
||||
static const pid_t kTestProcessId = 42;
|
||||
MINIDUMP_MISC_INFO misc_info = {};
|
||||
misc_info.SizeOfInfo = sizeof(misc_info);
|
||||
misc_info.Flags1 = MINIDUMP_MISC1_PROCESS_ID;
|
||||
misc_info.ProcessId = kTestProcessId;
|
||||
|
||||
MINIDUMP_DIRECTORY misc_directory = {};
|
||||
misc_directory.StreamType = kMinidumpStreamTypeMiscInfo;
|
||||
misc_directory.Location.DataSize = sizeof(misc_info);
|
||||
misc_directory.Location.Rva = static_cast<RVA>(string_file.SeekGet());
|
||||
ASSERT_TRUE(string_file.Write(&misc_info, sizeof(misc_info)));
|
||||
|
||||
header.StreamDirectoryRva = static_cast<RVA>(string_file.SeekGet());
|
||||
ASSERT_TRUE(string_file.Write(&misc_directory, sizeof(misc_directory)));
|
||||
|
||||
header.Signature = MINIDUMP_SIGNATURE;
|
||||
header.Version = MINIDUMP_VERSION;
|
||||
header.NumberOfStreams = 1;
|
||||
ASSERT_TRUE(string_file.SeekSet(0));
|
||||
ASSERT_TRUE(string_file.Write(&header, sizeof(header)));
|
||||
|
||||
ProcessSnapshotMinidump process_snapshot;
|
||||
ASSERT_TRUE(process_snapshot.Initialize(&string_file));
|
||||
EXPECT_EQ(process_snapshot.ProcessID(), kTestProcessId);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/multiprocess_exec.h"
|
||||
#include "util/file/file_io.h"
|
||||
#include "util/misc/address_sanitizer.h"
|
||||
#include "util/numeric/safe_assignment.h"
|
||||
|
||||
#if defined(OS_LINUX) || defined(OS_ANDROID)
|
||||
@ -162,6 +163,23 @@ class StackSanitizationChecker : public MemorySnapshot::Delegate {
|
||||
|
||||
// MemorySnapshot::Delegate
|
||||
bool MemorySnapshotDelegateRead(void* data, size_t size) override {
|
||||
#if defined(ADDRESS_SANITIZER) && (defined(OS_LINUX) || defined(OS_ANDROID))
|
||||
// AddressSanitizer causes stack variables to be stored separately from the
|
||||
// call stack.
|
||||
auto addr_not_in_stack_range =
|
||||
[](VMAddress addr, VMAddress stack_addr, VMSize stack_size) {
|
||||
return addr < stack_addr || addr >= stack_addr + stack_size;
|
||||
};
|
||||
EXPECT_PRED3(addr_not_in_stack_range,
|
||||
addrs_.code_pointer_address,
|
||||
stack_->Address(),
|
||||
size);
|
||||
EXPECT_PRED3(addr_not_in_stack_range,
|
||||
addrs_.string_address,
|
||||
stack_->Address(),
|
||||
size);
|
||||
return true;
|
||||
#else
|
||||
size_t pointer_offset;
|
||||
if (!AssignIfInRange(&pointer_offset,
|
||||
addrs_.code_pointer_address - stack_->Address())) {
|
||||
@ -192,6 +210,7 @@ class StackSanitizationChecker : public MemorySnapshot::Delegate {
|
||||
EXPECT_STREQ(string, kSensitiveStackData);
|
||||
}
|
||||
return true;
|
||||
#endif // ADDRESS_SANITIZER && (OS_LINUX || OS_ANDROID)
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -36,6 +36,11 @@ void ScopedModuleHandle::Impl::Close(ModuleHandle handle) {
|
||||
|
||||
ScopedModuleHandle::ScopedModuleHandle(ModuleHandle handle) : handle_(handle) {}
|
||||
|
||||
ScopedModuleHandle::ScopedModuleHandle(ScopedModuleHandle&& other)
|
||||
: handle_(other.handle_) {
|
||||
other.handle_ = nullptr;
|
||||
}
|
||||
|
||||
ScopedModuleHandle::~ScopedModuleHandle() {
|
||||
if (valid()) {
|
||||
Impl::Close(handle_);
|
||||
|
@ -57,6 +57,7 @@ class ScopedModuleHandle {
|
||||
using ModuleHandle = Impl::ModuleHandle;
|
||||
|
||||
explicit ScopedModuleHandle(ModuleHandle handle);
|
||||
ScopedModuleHandle(ScopedModuleHandle&& handle);
|
||||
~ScopedModuleHandle();
|
||||
|
||||
//! \return The module handle being managed.
|
||||
|
@ -296,40 +296,39 @@ const MemoryMap::Mapping* MemoryMap::FindMappingWithName(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const MemoryMap::Mapping* MemoryMap::FindFileMmapStart(
|
||||
std::vector<const MemoryMap::Mapping*> MemoryMap::FindFilePossibleMmapStarts(
|
||||
const Mapping& mapping) const {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
|
||||
size_t index = 0;
|
||||
for (; index < mappings_.size(); ++index) {
|
||||
if (mappings_[index].Equals(mapping)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index >= mappings_.size()) {
|
||||
LOG(ERROR) << "mapping not found";
|
||||
return nullptr;
|
||||
}
|
||||
std::vector<const Mapping*> possible_starts;
|
||||
|
||||
// If the mapping is anonymous, as is for the VDSO, there is no mapped file to
|
||||
// find the start of, so just return the input mapping.
|
||||
if (mapping.device == 0 && mapping.inode == 0) {
|
||||
return &mappings_[index];
|
||||
for (const auto& candidate : mappings_) {
|
||||
if (mapping.Equals(candidate)) {
|
||||
possible_starts.push_back(&candidate);
|
||||
return possible_starts;
|
||||
}
|
||||
}
|
||||
|
||||
LOG(ERROR) << "mapping not found";
|
||||
return std::vector<const Mapping*>();
|
||||
}
|
||||
|
||||
do {
|
||||
// There may by anonymous mappings or other files mapped into the holes,
|
||||
// so check that the mapping uses the same file as the input, but keep
|
||||
// searching if it doesn't.
|
||||
if (mappings_[index].device == mapping.device &&
|
||||
mappings_[index].inode == mapping.inode &&
|
||||
mappings_[index].offset == 0) {
|
||||
return &mappings_[index];
|
||||
for (const auto& candidate : mappings_) {
|
||||
if (candidate.device == mapping.device &&
|
||||
candidate.inode == mapping.inode &&
|
||||
candidate.offset == 0) {
|
||||
possible_starts.push_back(&candidate);
|
||||
}
|
||||
} while (index--);
|
||||
if (mapping.Equals(candidate)) {
|
||||
return possible_starts;
|
||||
}
|
||||
}
|
||||
|
||||
LOG(ERROR) << "mapping not found";
|
||||
return nullptr;
|
||||
return std::vector<const Mapping*>();
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
||||
|
@ -76,20 +76,27 @@ class MemoryMap {
|
||||
//! it was obtained from.
|
||||
const Mapping* FindMappingWithName(const std::string& name) const;
|
||||
|
||||
//! \brief Find the first Mapping in a series of mappings for the same file.
|
||||
//! \brief Find Mappings that share a Mapping's file, mapped from offset 0.
|
||||
//!
|
||||
//! Executables and libaries are typically loaded into several mappings with
|
||||
//! varying permissions for different segments. This method searches for the
|
||||
//! mapping with the highest address at or below \a mapping, which maps the
|
||||
//! same file as \a mapping from file offset 0.
|
||||
//! varying permissions for different segments. Portions of an ELF file may
|
||||
//! be mapped multiple times as part of loading the file, for example, when
|
||||
//! initializing GNU_RELRO segments. This method searches for mappings at or
|
||||
//! below \a mapping in memory that are mapped from the same file as \a
|
||||
//! mapping from offset 0.
|
||||
//!
|
||||
//! If \a mapping is not found, `nullptr` is returned. If \a mapping is found
|
||||
//! but does not map a file, \a mapping is returned.
|
||||
//! This method is intended to help identify the possible base address for
|
||||
//! loaded modules, but it is the caller's responsibility to determine which
|
||||
//! returned mapping is correct.
|
||||
//!
|
||||
//! If \a mapping does not refer to a valid mapping, an empty vector will be
|
||||
//! returned and a message will be logged. If \a mapping is found but does not
|
||||
//! map a file, \a mapping is returned in \a possible_starts.
|
||||
//!
|
||||
//! \param[in] mapping A Mapping whose series to find the start of.
|
||||
//! \return The first Mapping in the series or `nullptr` on failure with a
|
||||
//! message logged.
|
||||
const Mapping* FindFileMmapStart(const Mapping& mapping) const;
|
||||
//! \return a vector of the possible mapping starts.
|
||||
std::vector<const Mapping*> FindFilePossibleMmapStarts(
|
||||
const Mapping& mapping) const;
|
||||
|
||||
private:
|
||||
std::vector<Mapping> mappings_;
|
||||
|
@ -355,8 +355,8 @@ TEST(MemoryMap, MapRunningChild) {
|
||||
|
||||
// Expects first and third pages from mapping_start to refer to the same mapped
|
||||
// file. The second page should not.
|
||||
void ExpectFindFileMmapStart(LinuxVMAddress mapping_start,
|
||||
LinuxVMSize page_size) {
|
||||
void ExpectFindFilePossibleMmapStarts(LinuxVMAddress mapping_start,
|
||||
LinuxVMSize page_size) {
|
||||
FakePtraceConnection connection;
|
||||
ASSERT_TRUE(connection.Initialize(getpid()));
|
||||
|
||||
@ -373,17 +373,27 @@ void ExpectFindFileMmapStart(LinuxVMAddress mapping_start,
|
||||
ASSERT_NE(mapping1, mapping2);
|
||||
ASSERT_NE(mapping2, mapping3);
|
||||
|
||||
EXPECT_EQ(map.FindFileMmapStart(*mapping1), mapping1);
|
||||
EXPECT_EQ(map.FindFileMmapStart(*mapping2), mapping2);
|
||||
EXPECT_EQ(map.FindFileMmapStart(*mapping3), mapping1);
|
||||
std::vector<const MemoryMap::Mapping*> mappings;
|
||||
|
||||
mappings = map.FindFilePossibleMmapStarts(*mapping1);
|
||||
ASSERT_EQ(mappings.size(), 1u);
|
||||
EXPECT_EQ(mappings[0], mapping1);
|
||||
|
||||
mappings = map.FindFilePossibleMmapStarts(*mapping2);
|
||||
ASSERT_EQ(mappings.size(), 1u);
|
||||
EXPECT_EQ(mappings[0], mapping2);
|
||||
|
||||
mappings = map.FindFilePossibleMmapStarts(*mapping3);
|
||||
ASSERT_EQ(mappings.size(), 1u);
|
||||
EXPECT_EQ(mappings[0], mapping1);
|
||||
}
|
||||
|
||||
TEST(MemoryMap, FindFileMmapStart) {
|
||||
TEST(MemoryMap, FindFilePossibleMmapStarts) {
|
||||
const size_t page_size = getpagesize();
|
||||
|
||||
ScopedTempDir temp_dir;
|
||||
base::FilePath path =
|
||||
temp_dir.path().Append(FILE_PATH_LITERAL("FindFileMmapStartTestFile"));
|
||||
base::FilePath path = temp_dir.path().Append(
|
||||
FILE_PATH_LITERAL("FindFilePossibleMmapStartsTestFile"));
|
||||
ScopedFileHandle handle;
|
||||
size_t file_length = page_size * 3;
|
||||
ASSERT_NO_FATAL_FAILURE(InitializeFile(path, file_length, &handle));
|
||||
@ -418,9 +428,19 @@ TEST(MemoryMap, FindFileMmapStart) {
|
||||
ASSERT_NE(mapping1, mapping2);
|
||||
ASSERT_NE(mapping2, mapping3);
|
||||
|
||||
EXPECT_EQ(map.FindFileMmapStart(*mapping1), mapping1);
|
||||
EXPECT_EQ(map.FindFileMmapStart(*mapping2), mapping1);
|
||||
EXPECT_EQ(map.FindFileMmapStart(*mapping3), mapping1);
|
||||
std::vector<const MemoryMap::Mapping*> mappings;
|
||||
|
||||
mappings = map.FindFilePossibleMmapStarts(*mapping1);
|
||||
ASSERT_EQ(mappings.size(), 1u);
|
||||
EXPECT_EQ(mappings[0], mapping1);
|
||||
|
||||
mappings = map.FindFilePossibleMmapStarts(*mapping2);
|
||||
ASSERT_EQ(mappings.size(), 1u);
|
||||
EXPECT_EQ(mappings[0], mapping1);
|
||||
|
||||
mappings = map.FindFilePossibleMmapStarts(*mapping3);
|
||||
ASSERT_EQ(mappings.size(), 1u);
|
||||
EXPECT_EQ(mappings[0], mapping1);
|
||||
|
||||
#if defined(ARCH_CPU_64_BITS)
|
||||
constexpr bool is_64_bit = true;
|
||||
@ -429,7 +449,7 @@ TEST(MemoryMap, FindFileMmapStart) {
|
||||
#endif
|
||||
MemoryMap::Mapping bad_mapping;
|
||||
bad_mapping.range.SetRange(is_64_bit, 0, 1);
|
||||
EXPECT_EQ(map.FindFileMmapStart(bad_mapping), nullptr);
|
||||
EXPECT_EQ(map.FindFilePossibleMmapStarts(bad_mapping).size(), 0u);
|
||||
}
|
||||
|
||||
// Make the second page an anonymous mapping
|
||||
@ -449,12 +469,12 @@ TEST(MemoryMap, FindFileMmapStart) {
|
||||
MAP_PRIVATE | MAP_FIXED,
|
||||
handle.get(),
|
||||
page_size * 2));
|
||||
ExpectFindFileMmapStart(mapping_start, page_size);
|
||||
ExpectFindFilePossibleMmapStarts(mapping_start, page_size);
|
||||
|
||||
// Map the second page to another file.
|
||||
ScopedFileHandle handle2;
|
||||
base::FilePath path2 =
|
||||
temp_dir.path().Append(FILE_PATH_LITERAL("FindFileMmapStartTestFile2"));
|
||||
base::FilePath path2 = temp_dir.path().Append(
|
||||
FILE_PATH_LITERAL("FindFilePossibleMmapStartsTestFile2"));
|
||||
ASSERT_NO_FATAL_FAILURE(InitializeFile(path2, page_size, &handle2));
|
||||
|
||||
page2_mapping.ResetMmap(file_mapping.addr_as<char*>() + page_size,
|
||||
@ -463,7 +483,106 @@ TEST(MemoryMap, FindFileMmapStart) {
|
||||
MAP_PRIVATE | MAP_FIXED,
|
||||
handle2.get(),
|
||||
0);
|
||||
ExpectFindFileMmapStart(mapping_start, page_size);
|
||||
ExpectFindFilePossibleMmapStarts(mapping_start, page_size);
|
||||
}
|
||||
|
||||
TEST(MemoryMap, FindFilePossibleMmapStarts_MultipleStarts) {
|
||||
ScopedTempDir temp_dir;
|
||||
base::FilePath path =
|
||||
temp_dir.path().Append(FILE_PATH_LITERAL("MultipleStartsTestFile"));
|
||||
const size_t page_size = getpagesize();
|
||||
ScopedFileHandle handle;
|
||||
ASSERT_NO_FATAL_FAILURE(InitializeFile(path, page_size * 2, &handle));
|
||||
|
||||
// Locate a sequence of pages to setup a test in.
|
||||
char* seq_addr;
|
||||
{
|
||||
ScopedMmap whole_mapping;
|
||||
ASSERT_TRUE(whole_mapping.ResetMmap(
|
||||
nullptr, page_size * 8, PROT_READ, MAP_PRIVATE | MAP_ANON, -1, 0));
|
||||
seq_addr = whole_mapping.addr_as<char*>();
|
||||
}
|
||||
|
||||
// Arrange file and anonymous mappings in the sequence.
|
||||
ScopedMmap file_mapping0;
|
||||
ASSERT_TRUE(file_mapping0.ResetMmap(seq_addr,
|
||||
page_size,
|
||||
PROT_READ,
|
||||
MAP_PRIVATE | MAP_FIXED,
|
||||
handle.get(),
|
||||
page_size));
|
||||
|
||||
ScopedMmap file_mapping1;
|
||||
ASSERT_TRUE(file_mapping1.ResetMmap(seq_addr + page_size,
|
||||
page_size * 2,
|
||||
PROT_READ,
|
||||
MAP_PRIVATE | MAP_FIXED,
|
||||
handle.get(),
|
||||
0));
|
||||
|
||||
ScopedMmap file_mapping2;
|
||||
ASSERT_TRUE(file_mapping2.ResetMmap(seq_addr + page_size * 3,
|
||||
page_size,
|
||||
PROT_READ,
|
||||
MAP_PRIVATE | MAP_FIXED,
|
||||
handle.get(),
|
||||
0));
|
||||
|
||||
// Skip a page
|
||||
|
||||
ScopedMmap file_mapping3;
|
||||
ASSERT_TRUE(file_mapping3.ResetMmap(seq_addr + page_size * 5,
|
||||
page_size,
|
||||
PROT_READ,
|
||||
MAP_PRIVATE | MAP_FIXED,
|
||||
handle.get(),
|
||||
0));
|
||||
|
||||
ScopedMmap anon_mapping;
|
||||
ASSERT_TRUE(anon_mapping.ResetMmap(seq_addr + page_size * 6,
|
||||
page_size,
|
||||
PROT_READ,
|
||||
MAP_PRIVATE | MAP_ANON | MAP_FIXED,
|
||||
-1,
|
||||
0));
|
||||
|
||||
ScopedMmap file_mapping4;
|
||||
ASSERT_TRUE(file_mapping4.ResetMmap(seq_addr + page_size * 7,
|
||||
page_size,
|
||||
PROT_READ,
|
||||
MAP_PRIVATE | MAP_FIXED,
|
||||
handle.get(),
|
||||
0));
|
||||
|
||||
FakePtraceConnection connection;
|
||||
ASSERT_TRUE(connection.Initialize(getpid()));
|
||||
MemoryMap map;
|
||||
ASSERT_TRUE(map.Initialize(&connection));
|
||||
|
||||
auto mapping = map.FindMapping(file_mapping0.addr_as<VMAddress>());
|
||||
ASSERT_TRUE(mapping);
|
||||
auto possible_starts = map.FindFilePossibleMmapStarts(*mapping);
|
||||
EXPECT_EQ(possible_starts.size(), 0u);
|
||||
|
||||
mapping = map.FindMapping(file_mapping1.addr_as<VMAddress>());
|
||||
ASSERT_TRUE(mapping);
|
||||
possible_starts = map.FindFilePossibleMmapStarts(*mapping);
|
||||
EXPECT_EQ(possible_starts.size(), 1u);
|
||||
|
||||
mapping = map.FindMapping(file_mapping2.addr_as<VMAddress>());
|
||||
ASSERT_TRUE(mapping);
|
||||
possible_starts = map.FindFilePossibleMmapStarts(*mapping);
|
||||
EXPECT_EQ(possible_starts.size(), 2u);
|
||||
|
||||
mapping = map.FindMapping(file_mapping3.addr_as<VMAddress>());
|
||||
ASSERT_TRUE(mapping);
|
||||
possible_starts = map.FindFilePossibleMmapStarts(*mapping);
|
||||
EXPECT_EQ(possible_starts.size(), 3u);
|
||||
|
||||
mapping = map.FindMapping(file_mapping4.addr_as<VMAddress>());
|
||||
ASSERT_TRUE(mapping);
|
||||
possible_starts = map.FindFilePossibleMmapStarts(*mapping);
|
||||
EXPECT_EQ(possible_starts.size(), 4u);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -101,7 +101,7 @@ constexpr const char* kFlavorNames[] = {
|
||||
"PPC_THREAD_STATE64",
|
||||
"PPC_EXCEPTION_STATE64",
|
||||
"THREAD_STATE_NONE",
|
||||
#elif defined(__arm__) || defined(__arm64__)
|
||||
#elif defined(__arm__) || defined(__aarch64__)
|
||||
// sed -Ene 's/^#define ((ARM|THREAD)_[[:graph:]]+)[[:space:]]+[[:digit:]]{1,2}.*$/ "\1",/p'
|
||||
// usr/include/mach/arm/thread_status.h
|
||||
// (iOS 7 SDK)
|
||||
@ -153,7 +153,7 @@ std::string ThreadStateFlavorFullToShort(const base::StringPiece& flavor) {
|
||||
static constexpr char kArchPrefix[] = "x86_";
|
||||
#elif defined(__ppc__) || defined(__ppc64__)
|
||||
static constexpr char kArchPrefix[] = "PPC_";
|
||||
#elif defined(__arm__) || defined(__arm64__)
|
||||
#elif defined(__arm__) || defined(__aarch64__)
|
||||
static constexpr char kArchPrefix[] = "ARM_"
|
||||
#endif
|
||||
prefix_len = strlen(kArchPrefix);
|
||||
|
@ -801,7 +801,7 @@ constexpr struct {
|
||||
{PPC_VECTOR_STATE, "PPC_VECTOR_STATE", "VECTOR"},
|
||||
{PPC_THREAD_STATE64, "PPC_THREAD_STATE64", "THREAD64"},
|
||||
{PPC_EXCEPTION_STATE64, "PPC_EXCEPTION_STATE64", "EXCEPTION64"},
|
||||
#elif defined(__arm__) || defined(__arm64__)
|
||||
#elif defined(__arm__) || defined(__aarch64__)
|
||||
{ARM_THREAD_STATE, "ARM_THREAD_STATE", "THREAD"},
|
||||
{ARM_VFP_STATE, "ARM_VFP_STATE", "VFP"},
|
||||
{ARM_EXCEPTION_STATE, "ARM_EXCEPTION_STATE", "EXCEPTION"},
|
||||
@ -860,7 +860,7 @@ TEST(SymbolicConstantsMach, ThreadStateFlavorToString) {
|
||||
flavor <= x86_AVX_STATE
|
||||
#elif defined(__ppc__) || defined(__ppc64__)
|
||||
flavor <= THREAD_STATE_NONE
|
||||
#elif defined(__arm__) || defined(__arm64__)
|
||||
#elif defined(__arm__) || defined(__aarch64__)
|
||||
(flavor <= ARM_EXCEPTION_STATE64 || flavor == ARM_THREAD_STATE32 ||
|
||||
(flavor >= ARM_DEBUG_STATE32 && flavor <= ARM_NEON_STATE64))
|
||||
#endif
|
||||
@ -948,7 +948,7 @@ TEST(SymbolicConstantsMach, StringToThreadStateFlavor) {
|
||||
"PPC_JUNK_STATE32",
|
||||
"x86_THREAD_STATE",
|
||||
"ARM_THREAD_STATE",
|
||||
#elif defined(__arm__) || defined(__arm64__)
|
||||
#elif defined(__arm__) || defined(__aarch64__)
|
||||
" ARM_THREAD_STATE64",
|
||||
"ARM_THREAD_STATE64 ",
|
||||
"ARM_THREAD_STATE642",
|
||||
@ -1013,7 +1013,7 @@ TEST(SymbolicConstantsMach, StringToThreadStateFlavor) {
|
||||
NUL_TEST_DATA("PPC_THREAD_\0STATE64"),
|
||||
NUL_TEST_DATA("PPC_THREAD_STA\0TE64"),
|
||||
NUL_TEST_DATA("PPC_THREAD_STATE\00064"),
|
||||
#elif defined(__arm__) || defined(__arm64__)
|
||||
#elif defined(__arm__) || defined(__aarch64__)
|
||||
NUL_TEST_DATA("\0ARM_THREAD_STATE64"),
|
||||
NUL_TEST_DATA("ARM\0_THREAD_STATE64"),
|
||||
NUL_TEST_DATA("ARM_\0THREAD_STATE64"),
|
||||
|
@ -60,12 +60,16 @@ void TestCaptureContext() {
|
||||
kReferencePC);
|
||||
#endif // !defined(ADDRESS_SANITIZER)
|
||||
|
||||
// Declare sp and context_2 here because all local variables need to be
|
||||
// declared before computing the stack pointer reference value, so that the
|
||||
// reference value can be the lowest value possible.
|
||||
uintptr_t sp;
|
||||
const uintptr_t sp = StackPointerFromContext(context_1);
|
||||
|
||||
// Declare context_2 here because all local variables need to be declared
|
||||
// before computing the stack pointer reference value, so that the reference
|
||||
// value can be the lowest value possible.
|
||||
NativeCPUContext context_2;
|
||||
|
||||
// AddressSanitizer on Linux causes stack variables to be stored separately from
|
||||
// the call stack.
|
||||
#if !defined(ADDRESS_SANITIZER) || (!defined(OS_LINUX) && !defined(OS_ANDROID))
|
||||
// The stack pointer reference value is the lowest address of a local variable
|
||||
// in this function. The captured program counter will be slightly less than
|
||||
// or equal to the reference stack pointer.
|
||||
@ -74,11 +78,11 @@ void TestCaptureContext() {
|
||||
reinterpret_cast<uintptr_t>(&context_2)),
|
||||
std::min(reinterpret_cast<uintptr_t>(&pc),
|
||||
reinterpret_cast<uintptr_t>(&sp)));
|
||||
sp = StackPointerFromContext(context_1);
|
||||
EXPECT_PRED2([](uintptr_t actual,
|
||||
uintptr_t reference) { return reference - actual < 768u; },
|
||||
sp,
|
||||
kReferenceSP);
|
||||
#endif // !ADDRESS_SANITIZER || (!OS_LINUX && !OS_ANDROID)
|
||||
|
||||
// Capture the context again, expecting that the stack pointer stays the same
|
||||
// and the program counter increases. Strictly speaking, there’s no guarantee
|
||||
|
@ -77,6 +77,10 @@ class Metrics {
|
||||
//! server.
|
||||
kUploadFailed = 4,
|
||||
|
||||
//! \brief There was an error between accessing the report from the database
|
||||
//! and uploading it to the crash server.
|
||||
kPrepareForUploadFailed = 5,
|
||||
|
||||
//! \brief The number of values in this enumeration; not a valid value.
|
||||
kMaxValue
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user