Add MinidumpMiscInfoWriter::InitializeFromSnapshot() and its test.

TEST=minidump_test MinidumpMiscInfoWriter.InitializeFromSnapshot
R=rsesek@chromium.org

Review URL: https://codereview.chromium.org/701783004
This commit is contained in:
Mark Mentovai 2014-11-05 18:15:19 -05:00
parent 8609cdb60d
commit bdfd147a47
7 changed files with 510 additions and 5 deletions

View File

@ -14,14 +14,122 @@
#include "minidump/minidump_misc_info_writer.h"
#include <limits>
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "minidump/minidump_writer_util.h"
#include "package.h"
#include "snapshot/process_snapshot.h"
#include "snapshot/system_snapshot.h"
#include "util/file/file_writer.h"
#include "util/numeric/in_range_cast.h"
#include "util/numeric/safe_assignment.h"
#if defined(OS_MACOSX)
#include <AvailabilityMacros.h>
#endif
namespace crashpad {
namespace {
uint32_t TimevalToRoundedSeconds(const timeval& tv) {
uint32_t seconds =
InRangeCast<uint32_t>(tv.tv_sec, std::numeric_limits<uint32_t>::max());
const int kMicrosecondsPerSecond = 1E6;
if (tv.tv_usec >= kMicrosecondsPerSecond / 2 &&
seconds != std::numeric_limits<uint32_t>::max()) {
++seconds;
}
return seconds;
}
// For MINIDUMP_MISC_INFO_4::BuildString. dbghelp only places OS version
// information here, but if a machine description is also available, this is the
// only reasonable place in a minidump file to put it.
std::string BuildString(const SystemSnapshot* system_snapshot) {
std::string os_version_full = system_snapshot->OSVersionFull();
std::string machine_description = system_snapshot->MachineDescription();
if (!os_version_full.empty()) {
if (!machine_description.empty()) {
return base::StringPrintf(
"%s; %s", os_version_full.c_str(), machine_description.c_str());
}
return os_version_full;
}
return machine_description;
}
#if defined(OS_MACOSX)
// Converts the value of the MAC_OS_VERSION_MIN_REQUIRED or
// MAC_OS_X_VERSION_MAX_ALLOWED macro from <AvailabilityMacros.h> to a number
// identifying the minor Mac OS X version that it represents. For example, with
// an argument of MAC_OS_X_VERSION_10_6, this function will return 6.
int AvailabilityVersionToMacOSXMinorVersion(int availability) {
// Through MAC_OS_X_VERSION_10_9, the minor version is the tens digit.
if (availability >= 1000 && availability <= 1099) {
return (availability / 10) % 10;
}
// After MAC_OS_X_VERSION_10_9, the older format was insufficient to represent
// versions. Since then, the minor version is the thousands and hundreds
// digits.
if (availability >= 100000 && availability <= 109999) {
return (availability / 100) % 100;
}
return 0;
}
#endif
} // namespace
namespace internal {
// For MINIDUMP_MISC_INFO_4::DbgBldStr. dbghelp produces strings like
// “dbghelp.i386,6.3.9600.16520” and “dbghelp.amd64,6.3.9600.16520”. Mimic that
// format, and add the OS that wrote the minidump along with any relevant
// platform-specific data describing the compilation environment.
std::string MinidumpMiscInfoDebugBuildString() {
// Caution: the minidump file format only has room for 39 UTF-16 code units
// plus a UTF-16 NUL terminator. Dont let strings get longer than this, or
// they will be truncated and a message will be logged.
#if defined(OS_MACOSX)
const char kOS[] = "mac";
#elif defined(OS_LINUX)
const char kOS[] = "linux";
#else
#error define kOS for this operating system
#endif
#if defined(ARCH_CPU_X86)
const char kCPU[] = "i386";
#elif defined(ARCH_CPU_X86_64)
const char kCPU[] = "amd64";
#else
#error define kCPU for this CPU
#endif
std::string debug_build_string = base::StringPrintf("%s.%s,%s,%s",
PACKAGE_TARNAME,
kCPU,
PACKAGE_VERSION,
kOS);
#if defined(OS_MACOSX)
debug_build_string += base::StringPrintf(
",%d,%d",
AvailabilityVersionToMacOSXMinorVersion(MAC_OS_X_VERSION_MIN_REQUIRED),
AvailabilityVersionToMacOSXMinorVersion(MAC_OS_X_VERSION_MAX_ALLOWED));
#endif
return debug_build_string;
}
} // namespace internal
MinidumpMiscInfoWriter::MinidumpMiscInfoWriter()
: MinidumpStreamWriter(), misc_info_() {
@ -30,7 +138,78 @@ MinidumpMiscInfoWriter::MinidumpMiscInfoWriter()
MinidumpMiscInfoWriter::~MinidumpMiscInfoWriter() {
}
void MinidumpMiscInfoWriter::SetProcessId(uint32_t process_id) {
void MinidumpMiscInfoWriter::InitializeFromSnapshot(
const ProcessSnapshot* process_snapshot) {
DCHECK_EQ(state(), kStateMutable);
DCHECK_EQ(misc_info_.Flags1, 0u);
SetProcessID(InRangeCast<uint32_t>(process_snapshot->ProcessID(), 0));
const SystemSnapshot* system_snapshot = process_snapshot->System();
uint64_t current_mhz;
uint64_t max_mhz;
system_snapshot->CPUFrequency(&current_mhz, &max_mhz);
const uint32_t kHzPerMHz = 1E6;
SetProcessorPowerInfo(
InRangeCast<uint32_t>(current_mhz / kHzPerMHz,
std::numeric_limits<uint32_t>::max()),
InRangeCast<uint32_t>(max_mhz / kHzPerMHz,
std::numeric_limits<uint32_t>::max()),
0,
0,
0);
timeval start_time;
process_snapshot->ProcessStartTime(&start_time);
timeval user_time;
timeval system_time;
process_snapshot->ProcessCPUTimes(&user_time, &system_time);
// Round the resource usage fields to the nearest second, because the minidump
// format only has one-second resolution. The start_time field is truncated
// instead of rounded so that the process uptime is reflected more accurately
// when the start time is compared to the snapshot time in the
// MINIDUMP_HEADER, which is also truncated, not rounded.
uint32_t user_seconds = TimevalToRoundedSeconds(user_time);
uint32_t system_seconds = TimevalToRoundedSeconds(system_time);
SetProcessTimes(start_time.tv_sec, user_seconds, system_seconds);
// This determines the systems time zone, which may be different than the
// process notion of the time zone.
SystemSnapshot::DaylightSavingTimeStatus dst_status;
int standard_offset_seconds;
int daylight_offset_seconds;
std::string standard_name;
std::string daylight_name;
system_snapshot->TimeZone(&dst_status,
&standard_offset_seconds,
&daylight_offset_seconds,
&standard_name,
&daylight_name);
// standard_offset_seconds is seconds east of UTC, but the minidump file wants
// minutes west of UTC. daylight_offset_seconds is also seconds east of UTC,
// but the minidump file wants minutes west of the standard offset. The empty
// ({}) arguments are for the transition times in and out of daylight saving
// time. These are not determined because no API exists to do so, and the
// transition times may vary from year to year.
SetTimeZone(dst_status,
standard_offset_seconds / -60,
standard_name,
{},
0,
daylight_name,
{},
(standard_offset_seconds - daylight_offset_seconds) / 60);
SetBuildString(BuildString(system_snapshot),
internal::MinidumpMiscInfoDebugBuildString());
}
void MinidumpMiscInfoWriter::SetProcessID(uint32_t process_id) {
DCHECK_EQ(state(), kStateMutable);
misc_info_.ProcessId = process_id;

View File

@ -28,6 +28,24 @@
namespace crashpad {
class ProcessSnapshot;
namespace internal {
//! \brief Returns the string to set in MINIDUMP_MISC_INFO_4::DbgBldStr.
//!
//! dbghelp produces strings like `"dbghelp.i386,6.3.9600.16520"` and
//! `"dbghelp.amd64,6.3.9600.16520"`. This function mimics that format, and adds
//! the OS that wrote the minidump along with any relevant platform-specific
//! data describing the compilation environment.
//!
//! This function is an implementation detail of
//! MinidumpMiscInfoWriter::InitializeFromSnapshot() and is only exposed for
//! testing purposes.
std::string MinidumpMiscInfoDebugBuildString();
} // namespace internal
//! \brief The writer for a stream in the MINIDUMP_MISC_INFO family in a
//! minidump file.
//!
@ -41,8 +59,17 @@ class MinidumpMiscInfoWriter final : public internal::MinidumpStreamWriter {
MinidumpMiscInfoWriter();
~MinidumpMiscInfoWriter() override;
//! \brief Initializes MINIDUMP_MISC_INFO_N based on \a process_snapshot.
//!
//! \param[in] process_snapshot The process snapshot to use as source data.
//!
//! \note Valid in #kStateMutable. No mutator methods may be called before
//! this method, and it is not normally necessary to call any mutator
//! methods after this method.
void InitializeFromSnapshot(const ProcessSnapshot* process_snapshot);
//! \brief Sets the field referenced by #MINIDUMP_MISC1_PROCESS_ID.
void SetProcessId(uint32_t process_id);
void SetProcessID(uint32_t process_id);
//! \brief Sets the fields referenced by #MINIDUMP_MISC1_PROCESS_TIMES.
void SetProcessTimes(time_t process_create_time,

View File

@ -27,6 +27,8 @@
#include "minidump/minidump_file_writer.h"
#include "minidump/test/minidump_file_writer_test_util.h"
#include "minidump/test/minidump_writable_test_util.h"
#include "snapshot/test/test_process_snapshot.h"
#include "snapshot/test/test_system_snapshot.h"
#include "util/file/string_file_writer.h"
#include "util/stdlib/strlcpy.h"
@ -177,7 +179,7 @@ TEST(MinidumpMiscInfoWriter, ProcessId) {
const uint32_t kProcessId = 12345;
misc_info_writer->SetProcessId(kProcessId);
misc_info_writer->SetProcessID(kProcessId);
minidump_file_writer.AddStream(misc_info_writer.Pass());
@ -535,7 +537,7 @@ TEST(MinidumpMiscInfoWriter, Everything) {
const char kBuildString[] = "build string";
const char kDebugBuildString[] = "debug build string";
misc_info_writer->SetProcessId(kProcessId);
misc_info_writer->SetProcessID(kProcessId);
misc_info_writer->SetProcessTimes(
kProcessCreateTime, kProcessUserTime, kProcessKernelTime);
misc_info_writer->SetProcessorPowerInfo(kProcessorMaxMhz,
@ -612,6 +614,94 @@ TEST(MinidumpMiscInfoWriter, Everything) {
ExpectMiscInfoEqual(&expected, observed);
}
TEST(MinidumpMiscInfoWriter, InitializeFromSnapshot) {
MINIDUMP_MISC_INFO_4 expect_misc_info = {};
const char kStandardTimeName[] = "EST";
const char kDaylightTimeName[] = "EDT";
const char kOSVersionFull[] =
"Mac OS X 10.9.5 (13F34); "
"Darwin 13.4.0 Darwin Kernel Version 13.4.0: "
"Sun Aug 17 19:50:11 PDT 2014; "
"root:xnu-2422.115.4~1/RELEASE_X86_64 x86_64";
const char kMachineDescription[] = "MacBookPro11,3 (Mac-2BD1B31983FE1663)";
string16 standard_time_name_utf16 = base::UTF8ToUTF16(kStandardTimeName);
string16 daylight_time_name_utf16 = base::UTF8ToUTF16(kDaylightTimeName);
string16 build_string_utf16 = base::UTF8ToUTF16(
std::string(kOSVersionFull) + "; " + kMachineDescription);
std::string debug_build_string = internal::MinidumpMiscInfoDebugBuildString();
EXPECT_FALSE(debug_build_string.empty());
string16 debug_build_string_utf16 = base::UTF8ToUTF16(debug_build_string);
expect_misc_info.SizeOfInfo = sizeof(expect_misc_info);
expect_misc_info.Flags1 = MINIDUMP_MISC1_PROCESS_ID |
MINIDUMP_MISC1_PROCESS_TIMES |
MINIDUMP_MISC1_PROCESSOR_POWER_INFO |
MINIDUMP_MISC3_TIMEZONE |
MINIDUMP_MISC4_BUILDSTRING;
expect_misc_info.ProcessId = 12345;
expect_misc_info.ProcessCreateTime = 0x555c7740;
expect_misc_info.ProcessUserTime = 60;
expect_misc_info.ProcessKernelTime = 15;
expect_misc_info.ProcessorCurrentMhz = 2800;
expect_misc_info.ProcessorMaxMhz = 2800;
expect_misc_info.TimeZoneId = 1;
expect_misc_info.TimeZone.Bias = 300;
c16lcpy(expect_misc_info.TimeZone.StandardName,
standard_time_name_utf16.c_str(),
arraysize(expect_misc_info.TimeZone.StandardName));
expect_misc_info.TimeZone.StandardBias = 0;
c16lcpy(expect_misc_info.TimeZone.DaylightName,
daylight_time_name_utf16.c_str(),
arraysize(expect_misc_info.TimeZone.DaylightName));
expect_misc_info.TimeZone.DaylightBias = -60;
c16lcpy(expect_misc_info.BuildString,
build_string_utf16.c_str(),
arraysize(expect_misc_info.BuildString));
c16lcpy(expect_misc_info.DbgBldStr,
debug_build_string_utf16.c_str(),
arraysize(expect_misc_info.DbgBldStr));
const timeval kStartTime = { expect_misc_info.ProcessCreateTime, 0 };
const timeval kUserCPUTime = { expect_misc_info.ProcessUserTime, 0 };
const timeval kSystemCPUTime = { expect_misc_info.ProcessKernelTime, 0 };
TestProcessSnapshot process_snapshot;
process_snapshot.SetProcessID(expect_misc_info.ProcessId);
process_snapshot.SetProcessStartTime(kStartTime);
process_snapshot.SetProcessCPUTimes(kUserCPUTime, kSystemCPUTime);
auto system_snapshot = make_scoped_ptr(new TestSystemSnapshot());
const uint64_t kHzPerMHz = 1E6;
system_snapshot->SetCPUFrequency(
expect_misc_info.ProcessorCurrentMhz * kHzPerMHz,
expect_misc_info.ProcessorMaxMhz * kHzPerMHz);
system_snapshot->SetTimeZone(SystemSnapshot::kObservingStandardTime,
expect_misc_info.TimeZone.Bias * -60,
(expect_misc_info.TimeZone.Bias +
expect_misc_info.TimeZone.DaylightBias) * -60,
kStandardTimeName,
kDaylightTimeName);
system_snapshot->SetOSVersionFull(kOSVersionFull);
system_snapshot->SetMachineDescription(kMachineDescription);
process_snapshot.SetSystem(system_snapshot.Pass());
auto misc_info_writer = make_scoped_ptr(new MinidumpMiscInfoWriter());
misc_info_writer->InitializeFromSnapshot(&process_snapshot);
MinidumpFileWriter minidump_file_writer;
minidump_file_writer.AddStream(misc_info_writer.Pass());
StringFileWriter file_writer;
ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer));
const MINIDUMP_MISC_INFO_4* misc_info;
ASSERT_NO_FATAL_FAILURE(GetMiscInfoStream(file_writer.string(), &misc_info));
ExpectMiscInfoEqual(&expect_misc_info, misc_info);
}
} // namespace
} // namespace test
} // namespace crashpad

View File

@ -339,7 +339,6 @@ TEST(MinidumpSystemInfoWriter, InitializeFromSnapshot_X86) {
const MINIDUMP_SYSTEM_INFO* system_info;
const MINIDUMP_STRING* csd_version;
ASSERT_NO_FATAL_FAILURE(GetSystemInfoStream(file_writer.string(),
strlen(kOSVersionBuild),
&system_info,

View File

@ -94,6 +94,8 @@
'test/test_memory_snapshot.h',
'test/test_module_snapshot.cc',
'test/test_module_snapshot.h',
'test/test_process_snapshot.cc',
'test/test_process_snapshot.h',
'test/test_system_snapshot.cc',
'test/test_system_snapshot.h',
'test/test_thread_snapshot.cc',

View File

@ -0,0 +1,87 @@
// Copyright 2014 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "snapshot/exception_snapshot.h"
#include "snapshot/module_snapshot.h"
#include "snapshot/system_snapshot.h"
#include "snapshot/test/test_process_snapshot.h"
#include "snapshot/thread_snapshot.h"
namespace crashpad {
namespace test {
TestProcessSnapshot::TestProcessSnapshot()
: process_id_(0),
parent_process_id_(0),
snapshot_time_(),
process_start_time_(),
process_cpu_user_time_(),
process_cpu_system_time_(),
system_(),
threads_(),
modules_(),
exception_() {
}
TestProcessSnapshot::~TestProcessSnapshot() {
}
pid_t TestProcessSnapshot::ProcessID() const {
return process_id_;
}
pid_t TestProcessSnapshot::ParentProcessID() const {
return parent_process_id_;
}
void TestProcessSnapshot::SnapshotTime(timeval* snapshot_time) const {
*snapshot_time = snapshot_time_;
}
void TestProcessSnapshot::ProcessStartTime(timeval* start_time) const {
*start_time = process_start_time_;
}
void TestProcessSnapshot::ProcessCPUTimes(timeval* user_time,
timeval* system_time) const {
*user_time = process_cpu_user_time_;
*system_time = process_cpu_system_time_;
}
const SystemSnapshot* TestProcessSnapshot::System() const {
return system_.get();
}
std::vector<const ThreadSnapshot*> TestProcessSnapshot::Threads() const {
std::vector<const ThreadSnapshot*> threads;
for (const ThreadSnapshot* thread : threads_) {
threads.push_back(thread);
}
return threads;
}
std::vector<const ModuleSnapshot*> TestProcessSnapshot::Modules() const {
std::vector<const ModuleSnapshot*> modules;
for (const ModuleSnapshot* module : modules_) {
modules.push_back(module);
}
return modules;
}
const ExceptionSnapshot* TestProcessSnapshot::Exception() const {
return exception_.get();
}
} // namespace test
} // namespace crashpad

View File

@ -0,0 +1,121 @@
// Copyright 2014 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef CRASHPAD_SNAPSHOT_TEST_TEST_PROCESS_SNAPSHOT_H_
#define CRASHPAD_SNAPSHOT_TEST_TEST_PROCESS_SNAPSHOT_H_
#include <stdint.h>
#include <sys/time.h>
#include <sys/types.h>
#include <vector>
#include "base/basictypes.h"
#include "base/memory/scoped_ptr.h"
#include "snapshot/exception_snapshot.h"
#include "snapshot/process_snapshot.h"
#include "snapshot/system_snapshot.h"
#include "util/stdlib/pointer_container.h"
namespace crashpad {
class ModuleSnapshot;
class ThreadSnapshot;
namespace test {
//! \brief A test ProcessSnapshot that can carry arbitrary data for testing
//! purposes.
class TestProcessSnapshot final : public ProcessSnapshot {
public:
TestProcessSnapshot();
~TestProcessSnapshot() override;
void SetProcessID(pid_t process_id) { process_id_ = process_id; }
void SetParentProcessID(pid_t parent_process_id) {
parent_process_id_ = parent_process_id;
}
void SetSnapshotTime(const timeval& snapshot_time) {
snapshot_time_ = snapshot_time;
}
void SetProcessStartTime(const timeval& start_time) {
process_start_time_ = start_time;
}
void SetProcessCPUTimes(const timeval& user_time,
const timeval& system_time) {
process_cpu_user_time_ = user_time;
process_cpu_system_time_ = system_time;
}
//! \brief Sets the system snapshot to be returned by System().
//!
//! \param[in] system The system snapshot that System() will return. The
//! TestProcessSnapshot object takes ownership of \a system.
void SetSystem(scoped_ptr<SystemSnapshot> system) { system_ = system.Pass(); }
//! \brief Adds a thread snapshot to be returned by Threads().
//!
//! \param[in] thread The thread snapshot that will be included in Threads().
//! The TestProcessSnapshot object takes ownership of \a thread.
void AddThread(scoped_ptr<ThreadSnapshot> thread) {
threads_.push_back(thread.release());
}
//! \brief Adds a module snapshot to be returned by Modules().
//!
//! \param[in] module The module snapshot that will be included in Modules().
//! The TestProcessSnapshot object takes ownership of \a module.
void AddModule(scoped_ptr<ModuleSnapshot> module) {
modules_.push_back(module.release());
}
//! \brief Sets the exception snapshot to be returned by Exception().
//!
//! \param[in] exception The exception snapshot that Exception() will return.
//! The TestProcessSnapshot object takes ownership of \a exception.
void SetException(scoped_ptr<ExceptionSnapshot> exception) {
exception_ = exception.Pass();
}
// ProcessSnapshot:
pid_t ProcessID() const override;
pid_t ParentProcessID() const override;
void SnapshotTime(timeval* snapshot_time) const override;
void ProcessStartTime(timeval* start_time) const override;
void ProcessCPUTimes(timeval* user_time, timeval* system_time) const override;
const SystemSnapshot* System() const override;
std::vector<const ThreadSnapshot*> Threads() const override;
std::vector<const ModuleSnapshot*> Modules() const override;
const ExceptionSnapshot* Exception() const override;
private:
pid_t process_id_;
pid_t parent_process_id_;
timeval snapshot_time_;
timeval process_start_time_;
timeval process_cpu_user_time_;
timeval process_cpu_system_time_;
scoped_ptr<SystemSnapshot> system_;
PointerVector<ThreadSnapshot> threads_;
PointerVector<ModuleSnapshot> modules_;
scoped_ptr<ExceptionSnapshot> exception_;
DISALLOW_COPY_AND_ASSIGN(TestProcessSnapshot);
};
} // namespace test
} // namespace crashpad
#endif // CRASHPAD_SNAPSHOT_TEST_TEST_PROCESS_SNAPSHOT_H_