diff --git a/minidump/minidump.gyp b/minidump/minidump.gyp index 0becefd9..86be29cb 100644 --- a/minidump/minidump.gyp +++ b/minidump/minidump.gyp @@ -50,6 +50,8 @@ 'minidump_string_writer.h', 'minidump_system_info_writer.cc', 'minidump_system_info_writer.h', + 'minidump_thread_writer.cc', + 'minidump_thread_writer.h', 'minidump_writable.cc', 'minidump_writable.h', 'minidump_writer_util.cc', @@ -83,6 +85,7 @@ 'minidump_system_info_writer_test.cc', 'minidump_test_util.cc', 'minidump_test_util.h', + 'minidump_thread_writer_test.cc', 'minidump_writable_test.cc', ], }, diff --git a/minidump/minidump_memory_writer_test_util.cc b/minidump/minidump_memory_writer_test_util.cc index e255e1d8..c7b0c3c5 100644 --- a/minidump/minidump_memory_writer_test_util.cc +++ b/minidump/minidump_memory_writer_test_util.cc @@ -64,12 +64,9 @@ bool TestMinidumpMemoryWriter::WriteObject(FileWriterInterface* file_writer) { return rv; } -void ExpectMinidumpMemoryDescriptorAndContents( +void ExpectMinidumpMemoryDescriptor( const MINIDUMP_MEMORY_DESCRIPTOR* expected, - const MINIDUMP_MEMORY_DESCRIPTOR* observed, - const std::string& file_contents, - uint8_t value, - bool at_eof) { + const MINIDUMP_MEMORY_DESCRIPTOR* observed) { EXPECT_EQ(expected->StartOfMemoryRange, observed->StartOfMemoryRange); EXPECT_EQ(expected->Memory.DataSize, observed->Memory.DataSize); if (expected->Memory.Rva != 0) { @@ -78,6 +75,16 @@ void ExpectMinidumpMemoryDescriptorAndContents( (expected->Memory.Rva + kMemoryAlignment - 1) & ~(kMemoryAlignment - 1), observed->Memory.Rva); } +} + +void ExpectMinidumpMemoryDescriptorAndContents( + const MINIDUMP_MEMORY_DESCRIPTOR* expected, + const MINIDUMP_MEMORY_DESCRIPTOR* observed, + const std::string& file_contents, + uint8_t value, + bool at_eof) { + ExpectMinidumpMemoryDescriptor(expected, observed); + if (at_eof) { EXPECT_EQ(file_contents.size(), observed->Memory.Rva + observed->Memory.DataSize); diff --git a/minidump/minidump_memory_writer_test_util.h b/minidump/minidump_memory_writer_test_util.h index f6ad2292..69b2aebb 100644 --- a/minidump/minidump_memory_writer_test_util.h +++ b/minidump/minidump_memory_writer_test_util.h @@ -56,9 +56,7 @@ class TestMinidumpMemoryWriter final : public MinidumpMemoryWriter { }; //! \brief Verifies, via gtest assertions, that a MINIDUMP_MEMORY_DESCRIPTOR -//! structure contains expected values, and that the memory region it points -//! to contains expected values assuming it was written by a -//! TestMinidumpMemoryWriter object. +//! structure contains expected values. //! //! In \a expected and \a observed, //! MINIDUMP_MEMORY_DESCRIPTOR::StartOfMemoryRange and @@ -70,6 +68,21 @@ class TestMinidumpMemoryWriter final : public MinidumpMemoryWriter { //! expected values. //! \param[in] observed A MINIDUMP_MEMORY_DESCRIPTOR structure containing //! observed values. +void ExpectMinidumpMemoryDescriptor(const MINIDUMP_MEMORY_DESCRIPTOR* expected, + const MINIDUMP_MEMORY_DESCRIPTOR* observed); + +//! \brief Verifies, via gtest assertions, that a MINIDUMP_MEMORY_DESCRIPTOR +//! structure contains expected values, and that the memory region it points +//! to contains expected values assuming it was written by a +//! TestMinidumpMemoryWriter object. +//! +//! \a expected and \a observed are compared by +//! ExpectMinidumpMemoryDescriptor(). +//! +//! \param[in] expected A MINIDUMP_MEMORY_DESCRIPTOR structure containing +//! expected values. +//! \param[in] observed A MINIDUMP_MEMORY_DESCRIPTOR structure containing +//! observed values. //! \param[in] file_contents The contents of the minidump file in which \a //! observed was found. The memory region referenced by \a observed will be //! read from this string. diff --git a/minidump/minidump_thread_writer.cc b/minidump/minidump_thread_writer.cc new file mode 100644 index 00000000..baccfb16 --- /dev/null +++ b/minidump/minidump_thread_writer.cc @@ -0,0 +1,179 @@ +// 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_thread_writer.h" + +#include "base/logging.h" +#include "minidump/minidump_context_writer.h" +#include "minidump/minidump_memory_writer.h" +#include "util/numeric/safe_assignment.h" + +namespace crashpad { + +MinidumpThreadWriter::MinidumpThreadWriter() + : MinidumpWritable(), thread_(), stack_(NULL), context_(NULL) { +} + +const MINIDUMP_THREAD* MinidumpThreadWriter::MinidumpThread() const { + DCHECK_EQ(state(), kStateWritable); + + return &thread_; +} + +void MinidumpThreadWriter::SetStack(MinidumpMemoryWriter* stack) { + DCHECK_EQ(state(), kStateMutable); + + stack_ = stack; +} + +void MinidumpThreadWriter::SetContext(MinidumpContextWriter* context) { + DCHECK_EQ(state(), kStateMutable); + + context_ = context; +} + +bool MinidumpThreadWriter::Freeze() { + DCHECK_EQ(state(), kStateMutable); + CHECK(context_); + + if (!MinidumpWritable::Freeze()) { + return false; + } + + if (stack_) { + stack_->RegisterMemoryDescriptor(&thread_.Stack); + } + + context_->RegisterLocationDescriptor(&thread_.ThreadContext); + + return true; +} + +size_t MinidumpThreadWriter::SizeOfObject() { + DCHECK_GE(state(), kStateFrozen); + + // This object doesn’t directly write anything itself. Its MINIDUMP_THREAD is + // written by its parent as part of a MINIDUMP_THREAD_LIST, and its children + // are responsible for writing themselves. + return 0; +} + +std::vector MinidumpThreadWriter::Children() { + DCHECK_GE(state(), kStateFrozen); + DCHECK(context_); + + std::vector children; + if (stack_) { + children.push_back(stack_); + } + children.push_back(context_); + + return children; +} + +bool MinidumpThreadWriter::WriteObject(FileWriterInterface* file_writer) { + DCHECK_EQ(state(), kStateWritable); + + // This object doesn’t directly write anything itself. Its MINIDUMP_THREAD is + // written by its parent as part of a MINIDUMP_THREAD_LIST, and its children + // are responsible for writing themselves. + return true; +} + +MinidumpThreadListWriter::MinidumpThreadListWriter() + : MinidumpStreamWriter(), + thread_list_base_(), + threads_(), + memory_list_writer_(NULL) { +} + +MinidumpThreadListWriter::~MinidumpThreadListWriter() { +} + +void MinidumpThreadListWriter::SetMemoryListWriter( + MinidumpMemoryListWriter* memory_list_writer) { + DCHECK_EQ(state(), kStateMutable); + DCHECK(threads_.empty()); + + memory_list_writer_ = memory_list_writer; +} + +void MinidumpThreadListWriter::AddThread(MinidumpThreadWriter* thread) { + DCHECK_EQ(state(), kStateMutable); + + threads_.push_back(thread); + + if (memory_list_writer_) { + MinidumpMemoryWriter* stack = thread->Stack(); + if (stack) { + memory_list_writer_->AddExtraMemory(stack); + } + } +} + +bool MinidumpThreadListWriter::Freeze() { + DCHECK_EQ(state(), kStateMutable); + + if (!MinidumpStreamWriter::Freeze()) { + return false; + } + + size_t thread_count = threads_.size(); + if (!AssignIfInRange(&thread_list_base_.NumberOfThreads, thread_count)) { + LOG(ERROR) << "thread_count " << thread_count << " out of range"; + return false; + } + + return true; +} + +size_t MinidumpThreadListWriter::SizeOfObject() { + DCHECK_GE(state(), kStateFrozen); + + return sizeof(thread_list_base_) + threads_.size() * sizeof(MINIDUMP_THREAD); +} + +std::vector MinidumpThreadListWriter::Children() { + DCHECK_GE(state(), kStateFrozen); + + std::vector children; + for (MinidumpThreadWriter* thread : threads_) { + children.push_back(thread); + } + + return children; +} + +bool MinidumpThreadListWriter::WriteObject(FileWriterInterface* file_writer) { + DCHECK_EQ(state(), kStateWritable); + + WritableIoVec iov; + iov.iov_base = &thread_list_base_; + iov.iov_len = sizeof(thread_list_base_); + std::vector iovecs(1, iov); + + for (const MinidumpThreadWriter* thread : threads_) { + iov.iov_len = sizeof(MINIDUMP_THREAD); + iov.iov_base = thread->MinidumpThread(); + iovecs.push_back(iov); + } + + return file_writer->WriteIoVec(&iovecs); +} + +MinidumpStreamType MinidumpThreadListWriter::StreamType() const { + return kMinidumpStreamTypeThreadList; +} + +} // namespace crashpad diff --git a/minidump/minidump_thread_writer.h b/minidump/minidump_thread_writer.h new file mode 100644 index 00000000..8f3c107b --- /dev/null +++ b/minidump/minidump_thread_writer.h @@ -0,0 +1,185 @@ +// 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_THREAD_WRITER_H_ +#define CRASHPAD_MINIDUMP_MINIDUMP_THREAD_WRITER_H_ + +#include +#include +#include + +#include + +#include "base/basictypes.h" +#include "minidump/minidump_stream_writer.h" +#include "minidump/minidump_writable.h" +#include "util/file/file_writer.h" + +namespace crashpad { + +class MinidumpContextWriter; +class MinidumpMemoryListWriter; +class MinidumpMemoryWriter; + +//! \brief The writer for a MINIDUMP_THREAD object in a minidump file. +//! +//! Because MINIDUMP_THREAD objects only appear as elements of +//! MINIDUMP_THREAD_LIST objects, this class does not write any data on its own. +//! It makes its MINIDUMP_THREAD data available to its MinidumpThreadListWriter +//! parent, which writes it as part of a MINIDUMP_THREAD_LIST. +class MinidumpThreadWriter final : public internal::MinidumpWritable { + public: + MinidumpThreadWriter(); + ~MinidumpThreadWriter() {} + + //! \brief Returns a MINIDUMP_THREAD referencing this object’s data. + //! + //! This method is expected to be called by a MinidumpThreadListWriter in + //! order to obtain a MINIDUMP_THREAD to include in its list. + //! + //! \note Valid in #kStateWritable. + const MINIDUMP_THREAD* MinidumpThread() const; + + //! \brief Returns a MinidumpMemoryWriter that will write the memory region + //! corresponding to this object’s stack. + //! + //! If the thread does not have a stack, or its stack could not be determined, + //! this will return NULL. + //! + //! This method is provided so that MinidumpThreadListWriter can obtain thread + //! stack memory regions for the purposes of adding them to a + //! MinidumpMemoryListWriter (configured by calling + //! MinidumpThreadListWriter::SetMemoryListWriter()) by calling + //! MinidumpMemoryListWriter::AddExtraMemory(). + //! + //! \note Valid in any state. + MinidumpMemoryWriter* Stack() const { return stack_; } + + //! \brief Arranges for MINIDUMP_THREAD::Stack to point to the MINIDUMP_MEMORY + //! object to be written by \a stack. + //! + //! \a stack will become a child of this object in the overall tree of + //! internal::MinidumpWritable objects. + //! + //! \note Valid in #kStateMutable. + void SetStack(MinidumpMemoryWriter* stack); + + //! \brief Arranges for MINIDUMP_THREAD::ThreadContext to point to the CPU + //! context to be written by \a context. + //! + //! A context is required in all MINIDUMP_THREAD 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_THREAD::ThreadId. + void SetThreadID(uint32_t thread_id) { thread_.ThreadId = thread_id; } + + //! \brief Sets MINIDUMP_THREAD::SuspendCount. + void SetSuspendCount(uint32_t suspend_count) { + thread_.SuspendCount = suspend_count; + } + + //! \brief Sets MINIDUMP_THREAD::PriorityClass. + void SetPriorityClass(uint32_t priority_class) { + thread_.PriorityClass = priority_class; + } + + //! \brief Sets MINIDUMP_THREAD::Priority. + void SetPriority(uint32_t priority) { thread_.Priority = priority; } + + //! \brief Sets MINIDUMP_THREAD::Teb. + void SetTEB(uint64_t teb) { thread_.Teb = teb; } + + protected: + // MinidumpWritable: + virtual bool Freeze() override; + virtual size_t SizeOfObject() override; + virtual std::vector Children() override; + virtual bool WriteObject(FileWriterInterface* file_writer) override; + + private: + MINIDUMP_THREAD thread_; + MinidumpMemoryWriter* stack_; // weak + MinidumpContextWriter* context_; // weak + + DISALLOW_COPY_AND_ASSIGN(MinidumpThreadWriter); +}; + +//! \brief The writer for a MINIDUMP_THREAD_LIST stream in a minidump file, +//! containing a list of MINIDUMP_THREAD objects. +class MinidumpThreadListWriter final : public internal::MinidumpStreamWriter { + public: + MinidumpThreadListWriter(); + ~MinidumpThreadListWriter(); + + //! \brief Sets the MinidumpMemoryListWriter that each thread’s stack memory + //! region should be added to as extra memory. + //! + //! Each MINIDUMP_THREAD object can contain a reference to a + //! MinidumpMemoryWriter object that contains a snapshot of its stack memory. + //! In the overall tree of internal::MinidumpWritable objects, these + //! MinidumpMemoryWriter objects are considered children of their + //! MINIDUMP_THREAD, and are referenced by a MINIDUMP_MEMORY_DESCRIPTOR + //! contained in the MINIDUMP_THREAD. It is also possible for the same memory + //! regions to have MINIDUMP_MEMORY_DESCRIPTOR objects present in a + //! MINIDUMP_MEMORY_LIST stream. This is accomplished by calling this method, + //! which informs a MinidumpThreadListWriter that it should call + //! MinidumpMemoryListWriter::AddExtraMemory() for each extant thread stack + //! while the thread is being added in AddThread(). When this is done, the + //! MinidumpMemoryListWriter will contain a MINIDUMP_MEMORY_DESCRIPTOR + //! pointing to the thread’s stack memory in its MINIDUMP_MEMORY_LIST. Note + //! that the actual contents of the memory is only written once, as a child of + //! the MinidumpThreadWriter. The MINIDUMP_MEMORY_DESCRIPTOR objects in both + //! the MINIDUMP_THREAD and MINIDUMP_MEMORY_LIST will point to the same copy + //! of the memory’s contents. + //! + //! \note This method must be called before AddThread() is called. Threads + //! added by AddThread() prior to this method being called will not have + //! their stacks added to \a memory_list_writer as extra memory. + //! \note Valid in #kStateMutable. + void SetMemoryListWriter(MinidumpMemoryListWriter* memory_list_writer); + + //! \brief Adds a MinidumpThreadWriter to the MINIDUMP_THREAD_LIST. + //! + //! \a thread will become a child of this object in the overall tree of + //! internal::MinidumpWritable objects. + //! + //! \note Valid in #kStateMutable. + void AddThread(MinidumpThreadWriter* thread); + + 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_THREAD_LIST thread_list_base_; + std::vector threads_; // weak + MinidumpMemoryListWriter* memory_list_writer_; // weak + + DISALLOW_COPY_AND_ASSIGN(MinidumpThreadListWriter); +}; + +} // namespace crashpad + +#endif // CRASHPAD_MINIDUMP_MINIDUMP_THREAD_WRITER_H_ diff --git a/minidump/minidump_thread_writer_test.cc b/minidump/minidump_thread_writer_test.cc new file mode 100644 index 00000000..4b7dad99 --- /dev/null +++ b/minidump/minidump_thread_writer_test.cc @@ -0,0 +1,490 @@ +// 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_thread_writer.h" + +#include + +#include "gtest/gtest.h" +#include "minidump/minidump_context_test_util.h" +#include "minidump/minidump_context_writer.h" +#include "minidump/minidump_memory_writer.h" +#include "minidump/minidump_memory_writer_test_util.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_THREAD_LIST stream in |thread_list|. If +// |memory_list| is non-NULL, a MINIDUMP_MEMORY_LIST stream is also expected in +// |file_contents|, and that stream will be returned in |memory_list|. +void GetThreadListStream(const std::string& file_contents, + const MINIDUMP_THREAD_LIST** thread_list, + const MINIDUMP_MEMORY_LIST** memory_list) { + const size_t kDirectoryOffset = sizeof(MINIDUMP_HEADER); + const uint32_t kExpectedStreams = memory_list ? 2 : 1; + const size_t kThreadListStreamOffset = + kDirectoryOffset + kExpectedStreams * sizeof(MINIDUMP_DIRECTORY); + const size_t kThreadsOffset = + kThreadListStreamOffset + sizeof(MINIDUMP_THREAD_LIST); + + ASSERT_GE(file_contents.size(), kThreadsOffset); + + const MINIDUMP_HEADER* header = + reinterpret_cast(&file_contents[0]); + + ASSERT_NO_FATAL_FAILURE(VerifyMinidumpHeader(header, kExpectedStreams, 0)); + + const MINIDUMP_DIRECTORY* directory = + reinterpret_cast( + &file_contents[kDirectoryOffset]); + + ASSERT_EQ(kMinidumpStreamTypeThreadList, directory[0].StreamType); + ASSERT_GE(directory[0].Location.DataSize, sizeof(MINIDUMP_THREAD_LIST)); + ASSERT_EQ(kThreadListStreamOffset, directory[0].Location.Rva); + + *thread_list = reinterpret_cast( + &file_contents[kThreadListStreamOffset]); + + ASSERT_EQ(sizeof(MINIDUMP_THREAD_LIST) + + (*thread_list)->NumberOfThreads * sizeof(MINIDUMP_THREAD), + directory[0].Location.DataSize); + + if (memory_list) { + *memory_list = reinterpret_cast( + &file_contents[directory[1].Location.Rva]); + } +} + +TEST(MinidumpThreadWriter, EmptyThreadList) { + MinidumpFileWriter minidump_file_writer; + MinidumpThreadListWriter thread_list_writer; + + minidump_file_writer.AddStream(&thread_list_writer); + + StringFileWriter file_writer; + ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer)); + + ASSERT_EQ(sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) + + sizeof(MINIDUMP_THREAD_LIST), + file_writer.string().size()); + + const MINIDUMP_THREAD_LIST* thread_list; + ASSERT_NO_FATAL_FAILURE( + GetThreadListStream(file_writer.string(), &thread_list, NULL)); + + EXPECT_EQ(0u, thread_list->NumberOfThreads); +} + +// The MINIDUMP_THREADs |expected| and |observed| are compared against each +// other using gtest assertions. If |stack| is non-NULL, |observed| is expected +// to contain a populated MINIDUMP_MEMORY_DESCRIPTOR in its Stack field, +// otherwise, its Stack field is expected to be zeroed out. The memory +// descriptor will be placed in |stack|. |observed| must contain a populated +// ThreadContext field. The context will be recovered from |file_contents| and +// stored in |context_base|. +void ExpectThread(const MINIDUMP_THREAD* expected, + const MINIDUMP_THREAD* observed, + const std::string& file_contents, + const MINIDUMP_MEMORY_DESCRIPTOR** stack, + const void** context_base) { + EXPECT_EQ(expected->ThreadId, observed->ThreadId); + EXPECT_EQ(expected->SuspendCount, observed->SuspendCount); + EXPECT_EQ(expected->PriorityClass, observed->PriorityClass); + EXPECT_EQ(expected->Priority, observed->Priority); + EXPECT_EQ(expected->Teb, observed->Teb); + + EXPECT_EQ(expected->Stack.StartOfMemoryRange, + observed->Stack.StartOfMemoryRange); + EXPECT_EQ(expected->Stack.Memory.DataSize, observed->Stack.Memory.DataSize); + if (stack) { + ASSERT_NE(0u, observed->Stack.Memory.DataSize); + ASSERT_NE(0u, observed->Stack.Memory.Rva); + ASSERT_GE(file_contents.size(), + observed->Stack.Memory.Rva + observed->Stack.Memory.DataSize); + *stack = &observed->Stack; + } else { + EXPECT_EQ(0u, observed->Stack.StartOfMemoryRange); + EXPECT_EQ(0u, observed->Stack.Memory.DataSize); + EXPECT_EQ(0u, observed->Stack.Memory.Rva); + } + + 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 + expected->ThreadContext.DataSize); + *context_base = &file_contents[observed->ThreadContext.Rva]; +} + +TEST(MinidumpThreadWriter, OneThread_x86_NoStack) { + MinidumpFileWriter minidump_file_writer; + MinidumpThreadListWriter thread_list_writer; + + const uint32_t kThreadID = 0x11111111; + const uint32_t kSuspendCount = 1; + const uint32_t kPriorityClass = 0x20; + const uint32_t kPriority = 10; + const uint64_t kTEB = 0x55555555; + const uint32_t kSeed = 123; + + MinidumpThreadWriter thread_writer; + thread_writer.SetThreadID(kThreadID); + thread_writer.SetSuspendCount(kSuspendCount); + thread_writer.SetPriorityClass(kPriorityClass); + thread_writer.SetPriority(kPriority); + thread_writer.SetTEB(kTEB); + + MinidumpContextX86Writer context_x86_writer; + InitializeMinidumpContextX86(context_x86_writer.context(), kSeed); + thread_writer.SetContext(&context_x86_writer); + + thread_list_writer.AddThread(&thread_writer); + minidump_file_writer.AddStream(&thread_list_writer); + + StringFileWriter file_writer; + ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer)); + + ASSERT_EQ(sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) + + sizeof(MINIDUMP_THREAD_LIST) + 1 * sizeof(MINIDUMP_THREAD) + + 1 * sizeof(MinidumpContextX86), + file_writer.string().size()); + + const MINIDUMP_THREAD_LIST* thread_list; + ASSERT_NO_FATAL_FAILURE( + GetThreadListStream(file_writer.string(), &thread_list, NULL)); + + EXPECT_EQ(1u, thread_list->NumberOfThreads); + + MINIDUMP_THREAD expected = {}; + expected.ThreadId = kThreadID; + expected.SuspendCount = kSuspendCount; + expected.PriorityClass = kPriorityClass; + expected.Priority = kPriority; + expected.Teb = kTEB; + expected.ThreadContext.DataSize = sizeof(MinidumpContextX86); + + const MinidumpContextX86* observed_context; + ASSERT_NO_FATAL_FAILURE( + ExpectThread(&expected, + &thread_list->Threads[0], + file_writer.string(), + NULL, + reinterpret_cast(&observed_context))); + + ASSERT_NO_FATAL_FAILURE(ExpectMinidumpContextX86(kSeed, observed_context)); +} + +TEST(MinidumpThreadWriter, OneThread_AMD64_Stack) { + MinidumpFileWriter minidump_file_writer; + MinidumpThreadListWriter thread_list_writer; + + const uint32_t kThreadID = 0x22222222; + const uint32_t kSuspendCount = 2; + const uint32_t kPriorityClass = 0x30; + const uint32_t kPriority = 20; + const uint64_t kTEB = 0x5555555555555555; + const uint64_t kMemoryBase = 0x765432100000; + const size_t kMemorySize = 32; + const uint8_t kMemoryValue = 99; + const uint32_t kSeed = 456; + + MinidumpThreadWriter thread_writer; + thread_writer.SetThreadID(kThreadID); + thread_writer.SetSuspendCount(kSuspendCount); + thread_writer.SetPriorityClass(kPriorityClass); + thread_writer.SetPriority(kPriority); + thread_writer.SetTEB(kTEB); + + TestMinidumpMemoryWriter memory_writer( + kMemoryBase, kMemorySize, kMemoryValue); + thread_writer.SetStack(&memory_writer); + + MinidumpContextAMD64Writer context_amd64_writer; + InitializeMinidumpContextAMD64(context_amd64_writer.context(), kSeed); + thread_writer.SetContext(&context_amd64_writer); + + thread_list_writer.AddThread(&thread_writer); + minidump_file_writer.AddStream(&thread_list_writer); + + StringFileWriter file_writer; + ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer)); + + ASSERT_EQ(sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) + + sizeof(MINIDUMP_THREAD_LIST) + 1 * sizeof(MINIDUMP_THREAD) + + 1 * sizeof(MinidumpContextAMD64) + kMemorySize, + file_writer.string().size()); + + const MINIDUMP_THREAD_LIST* thread_list; + ASSERT_NO_FATAL_FAILURE( + GetThreadListStream(file_writer.string(), &thread_list, NULL)); + + EXPECT_EQ(1u, thread_list->NumberOfThreads); + + MINIDUMP_THREAD expected = {}; + expected.ThreadId = kThreadID; + expected.SuspendCount = kSuspendCount; + expected.PriorityClass = kPriorityClass; + expected.Priority = kPriority; + expected.Teb = kTEB; + expected.Stack.StartOfMemoryRange = kMemoryBase; + expected.Stack.Memory.DataSize = kMemorySize; + expected.ThreadContext.DataSize = sizeof(MinidumpContextAMD64); + + const MINIDUMP_MEMORY_DESCRIPTOR* observed_stack; + const MinidumpContextAMD64* observed_context; + ASSERT_NO_FATAL_FAILURE( + ExpectThread(&expected, + &thread_list->Threads[0], + file_writer.string(), + &observed_stack, + reinterpret_cast(&observed_context))); + + ASSERT_NO_FATAL_FAILURE( + ExpectMinidumpMemoryDescriptorAndContents(&expected.Stack, + observed_stack, + file_writer.string(), + kMemoryValue, + true)); + ASSERT_NO_FATAL_FAILURE(ExpectMinidumpContextAMD64(kSeed, observed_context)); +} + +TEST(MinidumpThreadWriter, ThreeThreads_x86_MemoryList) { + MinidumpFileWriter minidump_file_writer; + MinidumpThreadListWriter thread_list_writer; + MinidumpMemoryListWriter memory_list_writer; + thread_list_writer.SetMemoryListWriter(&memory_list_writer); + + const uint32_t kThreadID0 = 1111111; + const uint32_t kSuspendCount0 = 111111; + const uint32_t kPriorityClass0 = 11111; + const uint32_t kPriority0 = 1111; + const uint64_t kTEB0 = 111; + const uint64_t kMemoryBase0 = 0x1110; + const size_t kMemorySize0 = 16; + const uint8_t kMemoryValue0 = 11; + const uint32_t kSeed0 = 1; + + MinidumpThreadWriter thread_writer_0; + thread_writer_0.SetThreadID(kThreadID0); + thread_writer_0.SetSuspendCount(kSuspendCount0); + thread_writer_0.SetPriorityClass(kPriorityClass0); + thread_writer_0.SetPriority(kPriority0); + thread_writer_0.SetTEB(kTEB0); + + TestMinidumpMemoryWriter memory_writer_0( + kMemoryBase0, kMemorySize0, kMemoryValue0); + thread_writer_0.SetStack(&memory_writer_0); + + MinidumpContextX86Writer context_x86_writer_0; + InitializeMinidumpContextX86(context_x86_writer_0.context(), kSeed0); + thread_writer_0.SetContext(&context_x86_writer_0); + + thread_list_writer.AddThread(&thread_writer_0); + + const uint32_t kThreadID1 = 2222222; + const uint32_t kSuspendCount1 = 222222; + const uint32_t kPriorityClass1 = 22222; + const uint32_t kPriority1 = 2222; + const uint64_t kTEB1 = 222; + const uint64_t kMemoryBase1 = 0x2220; + const size_t kMemorySize1 = 32; + const uint8_t kMemoryValue1 = 22; + const uint32_t kSeed1 = 2; + + MinidumpThreadWriter thread_writer_1; + thread_writer_1.SetThreadID(kThreadID1); + thread_writer_1.SetSuspendCount(kSuspendCount1); + thread_writer_1.SetPriorityClass(kPriorityClass1); + thread_writer_1.SetPriority(kPriority1); + thread_writer_1.SetTEB(kTEB1); + + TestMinidumpMemoryWriter memory_writer_1( + kMemoryBase1, kMemorySize1, kMemoryValue1); + thread_writer_1.SetStack(&memory_writer_1); + + MinidumpContextX86Writer context_x86_writer_1; + InitializeMinidumpContextX86(context_x86_writer_1.context(), kSeed1); + thread_writer_1.SetContext(&context_x86_writer_1); + + thread_list_writer.AddThread(&thread_writer_1); + + const uint32_t kThreadID2 = 3333333; + const uint32_t kSuspendCount2 = 333333; + const uint32_t kPriorityClass2 = 33333; + const uint32_t kPriority2 = 3333; + const uint64_t kTEB2 = 333; + const uint64_t kMemoryBase2 = 0x3330; + const size_t kMemorySize2 = 48; + const uint8_t kMemoryValue2 = 33; + const uint32_t kSeed2 = 3; + + MinidumpThreadWriter thread_writer_2; + thread_writer_2.SetThreadID(kThreadID2); + thread_writer_2.SetSuspendCount(kSuspendCount2); + thread_writer_2.SetPriorityClass(kPriorityClass2); + thread_writer_2.SetPriority(kPriority2); + thread_writer_2.SetTEB(kTEB2); + + TestMinidumpMemoryWriter memory_writer_2( + kMemoryBase2, kMemorySize2, kMemoryValue2); + thread_writer_2.SetStack(&memory_writer_2); + + MinidumpContextX86Writer context_x86_writer_2; + InitializeMinidumpContextX86(context_x86_writer_2.context(), kSeed2); + thread_writer_2.SetContext(&context_x86_writer_2); + + thread_list_writer.AddThread(&thread_writer_2); + + minidump_file_writer.AddStream(&thread_list_writer); + minidump_file_writer.AddStream(&memory_list_writer); + + StringFileWriter file_writer; + ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer)); + + ASSERT_EQ(sizeof(MINIDUMP_HEADER) + 2 * sizeof(MINIDUMP_DIRECTORY) + + sizeof(MINIDUMP_THREAD_LIST) + 3 * sizeof(MINIDUMP_THREAD) + + sizeof(MINIDUMP_MEMORY_LIST) + + 3 * sizeof(MINIDUMP_MEMORY_DESCRIPTOR) + + 3 * sizeof(MinidumpContextX86) + kMemorySize0 + kMemorySize1 + + kMemorySize2 + 12, // 12 for alignment + file_writer.string().size()); + + const MINIDUMP_THREAD_LIST* thread_list; + const MINIDUMP_MEMORY_LIST* memory_list; + ASSERT_NO_FATAL_FAILURE( + GetThreadListStream(file_writer.string(), &thread_list, &memory_list)); + + EXPECT_EQ(3u, thread_list->NumberOfThreads); + EXPECT_EQ(3u, memory_list->NumberOfMemoryRanges); + + { + SCOPED_TRACE("thread 0"); + + MINIDUMP_THREAD expected = {}; + expected.ThreadId = kThreadID0; + expected.SuspendCount = kSuspendCount0; + expected.PriorityClass = kPriorityClass0; + expected.Priority = kPriority0; + expected.Teb = kTEB0; + expected.Stack.StartOfMemoryRange = kMemoryBase0; + expected.Stack.Memory.DataSize = kMemorySize0; + expected.ThreadContext.DataSize = sizeof(MinidumpContextX86); + + const MINIDUMP_MEMORY_DESCRIPTOR* observed_stack; + const MinidumpContextX86* observed_context; + ASSERT_NO_FATAL_FAILURE( + ExpectThread(&expected, + &thread_list->Threads[0], + file_writer.string(), + &observed_stack, + reinterpret_cast(&observed_context))); + + ASSERT_NO_FATAL_FAILURE( + ExpectMinidumpMemoryDescriptorAndContents(&expected.Stack, + observed_stack, + file_writer.string(), + kMemoryValue0, + false)); + ASSERT_NO_FATAL_FAILURE(ExpectMinidumpContextX86(kSeed0, observed_context)); + ASSERT_NO_FATAL_FAILURE(ExpectMinidumpMemoryDescriptor( + observed_stack, &memory_list->MemoryRanges[0])); + } + + { + SCOPED_TRACE("thread 1"); + + MINIDUMP_THREAD expected = {}; + expected.ThreadId = kThreadID1; + expected.SuspendCount = kSuspendCount1; + expected.PriorityClass = kPriorityClass1; + expected.Priority = kPriority1; + expected.Teb = kTEB1; + expected.Stack.StartOfMemoryRange = kMemoryBase1; + expected.Stack.Memory.DataSize = kMemorySize1; + expected.ThreadContext.DataSize = sizeof(MinidumpContextX86); + + const MINIDUMP_MEMORY_DESCRIPTOR* observed_stack; + const MinidumpContextX86* observed_context; + ASSERT_NO_FATAL_FAILURE( + ExpectThread(&expected, + &thread_list->Threads[1], + file_writer.string(), + &observed_stack, + reinterpret_cast(&observed_context))); + + ASSERT_NO_FATAL_FAILURE( + ExpectMinidumpMemoryDescriptorAndContents(&expected.Stack, + observed_stack, + file_writer.string(), + kMemoryValue1, + false)); + ASSERT_NO_FATAL_FAILURE(ExpectMinidumpContextX86(kSeed1, observed_context)); + ASSERT_NO_FATAL_FAILURE(ExpectMinidumpMemoryDescriptor( + observed_stack, &memory_list->MemoryRanges[1])); + } + + { + SCOPED_TRACE("thread 2"); + + MINIDUMP_THREAD expected = {}; + expected.ThreadId = kThreadID2; + expected.SuspendCount = kSuspendCount2; + expected.PriorityClass = kPriorityClass2; + expected.Priority = kPriority2; + expected.Teb = kTEB2; + expected.Stack.StartOfMemoryRange = kMemoryBase2; + expected.Stack.Memory.DataSize = kMemorySize2; + expected.ThreadContext.DataSize = sizeof(MinidumpContextX86); + + const MINIDUMP_MEMORY_DESCRIPTOR* observed_stack; + const MinidumpContextX86* observed_context; + ASSERT_NO_FATAL_FAILURE( + ExpectThread(&expected, + &thread_list->Threads[2], + file_writer.string(), + &observed_stack, + reinterpret_cast(&observed_context))); + + ASSERT_NO_FATAL_FAILURE( + ExpectMinidumpMemoryDescriptorAndContents(&expected.Stack, + observed_stack, + file_writer.string(), + kMemoryValue2, + true)); + ASSERT_NO_FATAL_FAILURE(ExpectMinidumpContextX86(kSeed2, observed_context)); + ASSERT_NO_FATAL_FAILURE(ExpectMinidumpMemoryDescriptor( + observed_stack, &memory_list->MemoryRanges[2])); + } +} + +TEST(MinidumpThreadWriterDeathTest, NoContext) { + MinidumpFileWriter minidump_file_writer; + MinidumpThreadListWriter thread_list_writer; + + MinidumpThreadWriter thread_writer; + + thread_list_writer.AddThread(&thread_writer); + minidump_file_writer.AddStream(&thread_list_writer); + + StringFileWriter file_writer; + ASSERT_DEATH(minidump_file_writer.WriteEverything(&file_writer), "context_"); +} + +} // namespace +} // namespace test +} // namespace crashpad