From 9d115e239190a4165660cbeedcbb7bbba2996edf Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Thu, 9 Oct 2014 15:13:13 -0400 Subject: [PATCH] Add MinidumpExceptionWriter and its test. TEST=minidump_test MinidumpExceptionWriter* R=rsesek@chromium.org Review URL: https://codereview.chromium.org/639573002 --- minidump/minidump.gyp | 3 + minidump/minidump_exception_writer.cc | 90 +++++++++ minidump/minidump_exception_writer.h | 106 ++++++++++ minidump/minidump_exception_writer_test.cc | 220 +++++++++++++++++++++ 4 files changed, 419 insertions(+) create mode 100644 minidump/minidump_exception_writer.cc create mode 100644 minidump/minidump_exception_writer.h create mode 100644 minidump/minidump_exception_writer_test.cc diff --git a/minidump/minidump.gyp b/minidump/minidump.gyp index 0038d4e9..0becefd9 100644 --- a/minidump/minidump.gyp +++ b/minidump/minidump.gyp @@ -32,6 +32,8 @@ 'minidump_context.h', 'minidump_context_writer.cc', 'minidump_context_writer.h', + 'minidump_exception_writer.cc', + 'minidump_exception_writer.h', 'minidump_extensions.cc', 'minidump_extensions.h', 'minidump_file_writer.cc', @@ -70,6 +72,7 @@ 'minidump_context_test_util.cc', 'minidump_context_test_util.h', 'minidump_context_writer_test.cc', + 'minidump_exception_writer_test.cc', 'minidump_file_writer_test.cc', 'minidump_memory_writer_test.cc', 'minidump_memory_writer_test_util.cc', diff --git a/minidump/minidump_exception_writer.cc b/minidump/minidump_exception_writer.cc new file mode 100644 index 00000000..2a990f04 --- /dev/null +++ b/minidump/minidump_exception_writer.cc @@ -0,0 +1,90 @@ +// 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 "minidump/minidump_exception_writer.h" + +#include "base/logging.h" + +namespace crashpad { + +MinidumpExceptionWriter::MinidumpExceptionWriter() + : MinidumpStreamWriter(), exception_(), context_(NULL) { +} + +void MinidumpExceptionWriter::SetContext(MinidumpContextWriter* context) { + DCHECK_EQ(state(), kStateMutable); + + context_ = context; +} + +void MinidumpExceptionWriter::SetExceptionInformation( + const std::vector& exception_information) { + DCHECK_EQ(state(), kStateMutable); + + const size_t parameters = exception_information.size(); + const size_t kMaxParameters = + arraysize(exception_.ExceptionRecord.ExceptionInformation); + CHECK_LE(parameters, kMaxParameters); + + exception_.ExceptionRecord.NumberParameters = parameters; + size_t parameter = 0; + for (; parameter < parameters; ++parameter) { + exception_.ExceptionRecord.ExceptionInformation[parameter] = + exception_information[parameter]; + } + for (; parameter < kMaxParameters; ++parameter) { + exception_.ExceptionRecord.ExceptionInformation[parameter] = 0; + } +} + +bool MinidumpExceptionWriter::Freeze() { + DCHECK_EQ(state(), kStateMutable); + CHECK(context_); + + if (!MinidumpStreamWriter::Freeze()) { + return false; + } + + context_->RegisterLocationDescriptor(&exception_.ThreadContext); + + return true; +} + +size_t MinidumpExceptionWriter::SizeOfObject() { + DCHECK_GE(state(), kStateFrozen); + + return sizeof(exception_); +} + +std::vector MinidumpExceptionWriter::Children() { + DCHECK_GE(state(), kStateFrozen); + DCHECK(context_); + + std::vector children; + children.push_back(context_); + + return children; +} + +bool MinidumpExceptionWriter::WriteObject(FileWriterInterface* file_writer) { + DCHECK_EQ(state(), kStateWritable); + + return file_writer->Write(&exception_, sizeof(exception_)); +} + +MinidumpStreamType MinidumpExceptionWriter::StreamType() const { + return kMinidumpStreamTypeException; +} + +} // namespace crashpad diff --git a/minidump/minidump_exception_writer.h b/minidump/minidump_exception_writer.h new file mode 100644 index 00000000..b18eefa4 --- /dev/null +++ b/minidump/minidump_exception_writer.h @@ -0,0 +1,106 @@ +// 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_MINIDUMP_MINIDUMP_EXCEPTION_WRITER_H_ +#define CRASHPAD_MINIDUMP_MINIDUMP_EXCEPTION_WRITER_H_ + +#include +#include +#include + +#include + +#include "base/basictypes.h" +#include "minidump/minidump_context_writer.h" +#include "minidump/minidump_stream_writer.h" +#include "util/file/file_writer.h" + +namespace crashpad { + +//! \brief The writer for a MINIDUMP_EXCEPTION_STREAM stream in a minidump file. +class MinidumpExceptionWriter final : public internal::MinidumpStreamWriter { + public: + MinidumpExceptionWriter(); + ~MinidumpExceptionWriter() {} + + //! \brief Arranges for MINIDUMP_EXCEPTION_STREAM::ThreadContext to point to + //! the CPU context to be written by \a context. + //! + //! A context is required in all MINIDUMP_EXCEPTION_STREAM objects. + //! + //! \a context will become a child of this object in the overall tree of + //! internal::MinidumpWritable objects. + //! + //! \note Valid in #kStateMutable. + void SetContext(MinidumpContextWriter* context); + + //! \brief Sets MINIDUMP_EXCEPTION_STREAM::ThreadId. + void SetThreadID(uint32_t thread_id) { exception_.ThreadId = thread_id; } + + //! \brief Sets MINIDUMP_EXCEPTION::ExceptionCode. + void SetExceptionCode(uint32_t exception_code) { + exception_.ExceptionRecord.ExceptionCode = exception_code; + } + + //! \brief Sets MINIDUMP_EXCEPTION::ExceptionFlags. + void SetExceptionFlags(uint32_t exception_flags) { + exception_.ExceptionRecord.ExceptionFlags = exception_flags; + } + + //! \brief Sets MINIDUMP_EXCEPTION::ExceptionRecord. + void SetExceptionRecord(uint64_t exception_record) { + exception_.ExceptionRecord.ExceptionRecord = exception_record; + } + + //! \brief Sets MINIDUMP_EXCEPTION::ExceptionAddress. + void SetExceptionAddress(uint64_t exception_address) { + exception_.ExceptionRecord.ExceptionAddress = exception_address; + } + + //! \brief Sets MINIDUMP_EXCEPTION::ExceptionInformation and + //! MINIDUMP_EXCEPTION::NumberParameters. + //! + //! MINIDUMP_EXCEPTION::NumberParameters is set to the number of elements in + //! \a exception_information. The elements of + //! MINIDUMP_EXCEPTION::ExceptionInformation are set to the elements of \a + //! exception_information. Unused elements in + //! MINIDUMP_EXCEPTION::ExceptionInformation are set to `0`. + //! + //! \a exception_information must have no more than + //! #EXCEPTION_MAXIMUM_PARAMETERS elements. + //! + //! \note Valid in #kStateMutable. + void SetExceptionInformation( + const std::vector& exception_information); + + protected: + // MinidumpWritable: + virtual bool Freeze() override; + virtual size_t SizeOfObject() override; + virtual std::vector Children() override; + virtual bool WriteObject(FileWriterInterface* file_writer) override; + + // MinidumpStreamWriter: + virtual MinidumpStreamType StreamType() const override; + + private: + MINIDUMP_EXCEPTION_STREAM exception_; + MinidumpContextWriter* context_; // weak + + DISALLOW_COPY_AND_ASSIGN(MinidumpExceptionWriter); +}; + +} // namespace crashpad + +#endif // CRASHPAD_MINIDUMP_MINIDUMP_EXCEPTION_WRITER_H_ diff --git a/minidump/minidump_exception_writer_test.cc b/minidump/minidump_exception_writer_test.cc new file mode 100644 index 00000000..6c289650 --- /dev/null +++ b/minidump/minidump_exception_writer_test.cc @@ -0,0 +1,220 @@ +// 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 "minidump/minidump_exception_writer.h" + +#include +#include + +#include +#include + +#include "gtest/gtest.h" +#include "minidump/minidump_context.h" +#include "minidump/minidump_context_test_util.h" +#include "minidump/minidump_context_writer.h" +#include "minidump/minidump_extensions.h" +#include "minidump/minidump_file_writer.h" +#include "minidump/minidump_test_util.h" +#include "util/file/string_file_writer.h" + +namespace crashpad { +namespace test { +namespace { + +// This returns the MINIDUMP_EXCEPTION_STREAM stream in |exception_stream|. +void GetExceptionStream(const std::string& file_contents, + const MINIDUMP_EXCEPTION_STREAM** exception_stream) { + const size_t kDirectoryOffset = sizeof(MINIDUMP_HEADER); + const size_t kExceptionStreamOffset = + kDirectoryOffset + sizeof(MINIDUMP_DIRECTORY); + const size_t kContextOffset = + kExceptionStreamOffset + sizeof(MINIDUMP_EXCEPTION_STREAM); + const size_t kFileSize = kContextOffset + sizeof(MinidumpContextX86); + ASSERT_EQ(file_contents.size(), kFileSize); + + const MINIDUMP_HEADER* header = + reinterpret_cast(&file_contents[0]); + + ASSERT_NO_FATAL_FAILURE(VerifyMinidumpHeader(header, 1, 0)); + + const MINIDUMP_DIRECTORY* directory = + reinterpret_cast( + &file_contents[kDirectoryOffset]); + + ASSERT_EQ(kMinidumpStreamTypeException, directory[0].StreamType); + ASSERT_GE(directory[0].Location.DataSize, sizeof(MINIDUMP_EXCEPTION_STREAM)); + ASSERT_EQ(kExceptionStreamOffset, directory[0].Location.Rva); + + *exception_stream = reinterpret_cast( + &file_contents[kExceptionStreamOffset]); +} + +// The MINIDUMP_EXCEPTION_STREAMs |expected| and |observed| are compared against +// each other using gtest assertions. The context will be recovered from +// |file_contents| and stored in |context|. +void ExpectExceptionStream(const MINIDUMP_EXCEPTION_STREAM* expected, + const MINIDUMP_EXCEPTION_STREAM* observed, + const std::string& file_contents, + const MinidumpContextX86** context) { + EXPECT_EQ(expected->ThreadId, observed->ThreadId); + EXPECT_EQ(0u, observed->__alignment); + EXPECT_EQ(expected->ExceptionRecord.ExceptionCode, + observed->ExceptionRecord.ExceptionCode); + EXPECT_EQ(expected->ExceptionRecord.ExceptionFlags, + observed->ExceptionRecord.ExceptionFlags); + EXPECT_EQ(expected->ExceptionRecord.ExceptionRecord, + observed->ExceptionRecord.ExceptionRecord); + EXPECT_EQ(expected->ExceptionRecord.ExceptionAddress, + observed->ExceptionRecord.ExceptionAddress); + EXPECT_EQ(expected->ExceptionRecord.NumberParameters, + observed->ExceptionRecord.NumberParameters); + EXPECT_EQ(0u, observed->ExceptionRecord.__unusedAlignment); + for (size_t index = 0; + index < arraysize(observed->ExceptionRecord.ExceptionInformation); + ++index) { + EXPECT_EQ(expected->ExceptionRecord.ExceptionInformation[index], + observed->ExceptionRecord.ExceptionInformation[index]); + } + EXPECT_EQ(expected->ThreadContext.DataSize, observed->ThreadContext.DataSize); + ASSERT_NE(0u, observed->ThreadContext.DataSize); + ASSERT_NE(0u, observed->ThreadContext.Rva); + ASSERT_GE(file_contents.size(), + observed->ThreadContext.Rva + observed->ThreadContext.DataSize); + *context = reinterpret_cast( + &file_contents[observed->ThreadContext.Rva]); +} + +TEST(MinidumpExceptionWriter, Minimal) { + MinidumpFileWriter minidump_file_writer; + MinidumpExceptionWriter exception_writer; + + const uint32_t kSeed = 100; + + MinidumpContextX86Writer context_x86_writer; + InitializeMinidumpContextX86(context_x86_writer.context(), kSeed); + exception_writer.SetContext(&context_x86_writer); + + minidump_file_writer.AddStream(&exception_writer); + + StringFileWriter file_writer; + ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer)); + + const MINIDUMP_EXCEPTION_STREAM* observed_exception_stream; + ASSERT_NO_FATAL_FAILURE( + GetExceptionStream(file_writer.string(), &observed_exception_stream)); + + MINIDUMP_EXCEPTION_STREAM expected_exception_stream = {}; + expected_exception_stream.ThreadContext.DataSize = sizeof(MinidumpContextX86); + + const MinidumpContextX86* observed_context; + ASSERT_NO_FATAL_FAILURE(ExpectExceptionStream(&expected_exception_stream, + observed_exception_stream, + file_writer.string(), + &observed_context)); + + ASSERT_NO_FATAL_FAILURE(ExpectMinidumpContextX86(kSeed, observed_context)); +} + +TEST(MinidumpExceptionWriter, Standard) { + MinidumpFileWriter minidump_file_writer; + MinidumpExceptionWriter exception_writer; + + const uint32_t kSeed = 200; + const uint32_t kThreadID = 1; + const uint32_t kExceptionCode = 2; + const uint32_t kExceptionFlags = 3; + const uint32_t kExceptionRecord = 4; + const uint32_t kExceptionAddress = 5; + const uint64_t kExceptionInformation0 = 6; + const uint64_t kExceptionInformation1 = 7; + const uint64_t kExceptionInformation2 = 7; + + MinidumpContextX86Writer context_x86_writer; + InitializeMinidumpContextX86(context_x86_writer.context(), kSeed); + exception_writer.SetContext(&context_x86_writer); + + exception_writer.SetThreadID(kThreadID); + exception_writer.SetExceptionCode(kExceptionCode); + exception_writer.SetExceptionFlags(kExceptionFlags); + exception_writer.SetExceptionRecord(kExceptionRecord); + exception_writer.SetExceptionAddress(kExceptionAddress); + + // Set a lot of exception information at first, and then replace it with less. + // This tests that the exception that is written does not contain the + // “garbage” from the initial SetExceptionInformation() call. + std::vector exception_information(EXCEPTION_MAXIMUM_PARAMETERS, + 0x5a5a5a5a5a5a5a5a); + exception_writer.SetExceptionInformation(exception_information); + + exception_information.clear(); + exception_information.push_back(kExceptionInformation0); + exception_information.push_back(kExceptionInformation1); + exception_information.push_back(kExceptionInformation2); + exception_writer.SetExceptionInformation(exception_information); + + minidump_file_writer.AddStream(&exception_writer); + + StringFileWriter file_writer; + ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer)); + + const MINIDUMP_EXCEPTION_STREAM* observed_exception_stream; + ASSERT_NO_FATAL_FAILURE( + GetExceptionStream(file_writer.string(), &observed_exception_stream)); + + MINIDUMP_EXCEPTION_STREAM expected_exception_stream = {}; + expected_exception_stream.ThreadId = kThreadID; + expected_exception_stream.ExceptionRecord.ExceptionCode = kExceptionCode; + expected_exception_stream.ExceptionRecord.ExceptionFlags = kExceptionFlags; + expected_exception_stream.ExceptionRecord.ExceptionRecord = kExceptionRecord; + expected_exception_stream.ExceptionRecord.ExceptionAddress = + kExceptionAddress; + expected_exception_stream.ExceptionRecord.NumberParameters = + exception_information.size(); + for (size_t index = 0; index < exception_information.size(); ++index) { + expected_exception_stream.ExceptionRecord.ExceptionInformation[index] = + exception_information[index]; + } + expected_exception_stream.ThreadContext.DataSize = sizeof(MinidumpContextX86); + + const MinidumpContextX86* observed_context; + ASSERT_NO_FATAL_FAILURE(ExpectExceptionStream(&expected_exception_stream, + observed_exception_stream, + file_writer.string(), + &observed_context)); + + ASSERT_NO_FATAL_FAILURE(ExpectMinidumpContextX86(kSeed, observed_context)); +} + +TEST(MinidumpExceptionWriterDeathTest, NoContext) { + MinidumpFileWriter minidump_file_writer; + MinidumpExceptionWriter exception_writer; + + minidump_file_writer.AddStream(&exception_writer); + + StringFileWriter file_writer; + ASSERT_DEATH(minidump_file_writer.WriteEverything(&file_writer), "context_"); +} + +TEST(MinidumpExceptionWriterDeathTest, TooMuchInformation) { + MinidumpExceptionWriter exception_writer; + std::vector exception_information(EXCEPTION_MAXIMUM_PARAMETERS + 1, + 0x5a5a5a5a5a5a5a5a); + ASSERT_DEATH(exception_writer.SetExceptionInformation(exception_information), + "kMaxParameters"); +} + +} // namespace +} // namespace test +} // namespace crashpad