diff --git a/minidump/minidump.gyp b/minidump/minidump.gyp index 38a04fc4..ceaf951f 100644 --- a/minidump/minidump.gyp +++ b/minidump/minidump.gyp @@ -57,6 +57,8 @@ 'minidump_string_writer.h', 'minidump_system_info_writer.cc', 'minidump_system_info_writer.h', + 'minidump_thread_id_map.cc', + 'minidump_thread_id_map.h', 'minidump_thread_writer.cc', 'minidump_thread_writer.h', 'minidump_writable.cc', @@ -90,6 +92,7 @@ 'minidump_simple_string_dictionary_writer_test.cc', 'minidump_string_writer_test.cc', 'minidump_system_info_writer_test.cc', + 'minidump_thread_id_map_test.cc', 'minidump_thread_writer_test.cc', 'minidump_writable_test.cc', 'test/minidump_context_test_util.cc', diff --git a/minidump/minidump_thread_id_map.cc b/minidump/minidump_thread_id_map.cc new file mode 100644 index 00000000..919566f5 --- /dev/null +++ b/minidump/minidump_thread_id_map.cc @@ -0,0 +1,66 @@ +// 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_id_map.h" + +#include +#include +#include + +#include "base/logging.h" +#include "snapshot/thread_snapshot.h" + +namespace crashpad { + +void BuildMinidumpThreadIDMap( + const std::vector& thread_snapshots, + MinidumpThreadIDMap* thread_id_map) { + DCHECK(thread_id_map->empty()); + + // First, try truncating each 64-bit thread ID to 32 bits. If that’s possible + // for each unique 64-bit thread ID, then this will be used as the mapping. + // This preserves as much of the original thread ID as possible when feasible. + bool collision = false; + std::set thread_ids_32; + for (const ThreadSnapshot* thread_snapshot : thread_snapshots) { + uint64_t thread_id_64 = thread_snapshot->ThreadID(); + if (thread_id_map->find(thread_id_64) == thread_id_map->end()) { + uint32_t thread_id_32 = thread_id_64; + if (thread_ids_32.find(thread_id_32) != thread_ids_32.end()) { + collision = true; + break; + } + thread_ids_32.insert(thread_id_32); + thread_id_map->insert(std::make_pair(thread_id_64, thread_id_32)); + } + } + + if (collision) { + // Since there was a collision, go back and assign each unique 64-bit thread + // ID its own sequential 32-bit equivalent. The 32-bit thread IDs will not + // bear any resemblance to the original 64-bit thread IDs. + thread_id_map->clear(); + for (const ThreadSnapshot* thread_snapshot : thread_snapshots) { + uint64_t thread_id_64 = thread_snapshot->ThreadID(); + if (thread_id_map->find(thread_id_64) == thread_id_map->end()) { + uint32_t thread_id_32 = thread_id_map->size(); + thread_id_map->insert(std::make_pair(thread_id_64, thread_id_32)); + } + } + + DCHECK_LE(thread_id_map->size(), std::numeric_limits::max()); + } +} + +} // namespace crashpad diff --git a/minidump/minidump_thread_id_map.h b/minidump/minidump_thread_id_map.h new file mode 100644 index 00000000..33b105fb --- /dev/null +++ b/minidump/minidump_thread_id_map.h @@ -0,0 +1,52 @@ +// 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_ID_MAP_H_ +#define CRASHPAD_MINIDUMP_MINIDUMP_THREAD_ID_MAP_H_ + +#include + +#include +#include + +namespace crashpad { + +class ThreadSnapshot; + +//! \brief A map that connects 64-bit snapshot thread IDs to 32-bit minidump +//! thread IDs. +//! +//! 64-bit snapshot thread IDs are obtained from ThreadSnapshot::ThreadID(). +//! 32-bit minidump thread IDs are stored in MINIDUMP_THREAD::ThreadId. +//! +//! A ThreadIDMap ensures that there are no collisions among the set of 32-bit +//! minidump thread IDs. +using MinidumpThreadIDMap = std::map; + +//! \brief Builds a MinidumpThreadIDMap for a group of ThreadSnapshot objects. +//! +//! \param[in] thread_snapshots The thread snapshots to use as source data. +//! \param[out] thread_id_map A MinidumpThreadIDMap to be built by this method. +//! This map must be empty when this function is called. +//! +//! The map ensures that for any unique 64-bit thread ID found in a +//! ThreadSnapshot, the 32-bit thread ID used in a minidump file will also be +//! unique. +void BuildMinidumpThreadIDMap( + const std::vector& thread_snapshots, + MinidumpThreadIDMap* thread_id_map); + +} // namespace crashpad + +#endif // CRASHPAD_MINIDUMP_MINIDUMP_THREAD_ID_MAP_H_ diff --git a/minidump/minidump_thread_id_map_test.cc b/minidump/minidump_thread_id_map_test.cc new file mode 100644 index 00000000..058709e9 --- /dev/null +++ b/minidump/minidump_thread_id_map_test.cc @@ -0,0 +1,190 @@ +// 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_id_map.h" + +#include + +#include "base/basictypes.h" +#include "gtest/gtest.h" +#include "snapshot/test/test_thread_snapshot.h" + +namespace crashpad { +namespace test { +namespace { + +class MinidumpThreadIDMapTest : public testing::Test { + public: + MinidumpThreadIDMapTest() + : Test(), + thread_snapshots_(), + test_thread_snapshots_() { + } + + ~MinidumpThreadIDMapTest() override {} + + // testing::Test: + void SetUp() override { + for (size_t index = 0; index < arraysize(test_thread_snapshots_); ++index) { + thread_snapshots_.push_back(&test_thread_snapshots_[index]); + } + } + + protected: + static bool MapHasKeyValue( + const MinidumpThreadIDMap* map, uint64_t key, uint32_t expected_value) { + auto iterator = map->find(key); + if (iterator == map->end()) { + EXPECT_NE(map->end(), iterator); + return false; + } + if (iterator->second != expected_value) { + EXPECT_EQ(expected_value, iterator->second); + return false; + } + return true; + } + + void SetThreadID(size_t index, uint64_t thread_id) { + ASSERT_LT(index, arraysize(test_thread_snapshots_)); + test_thread_snapshots_[index].SetThreadID(thread_id); + } + + const std::vector& thread_snapshots() const { + return thread_snapshots_; + } + + private: + std::vector thread_snapshots_; + TestThreadSnapshot test_thread_snapshots_[5]; + + DISALLOW_COPY_AND_ASSIGN(MinidumpThreadIDMapTest); +}; + +TEST_F(MinidumpThreadIDMapTest, NoThreads) { + // Don’t use thread_snapshots(), because it’s got some threads in it, and the + // point of this test is to make sure that BuildMinidumpThreadIDMap() works + // with no threads. + std::vector thread_snapshots; + MinidumpThreadIDMap thread_id_map; + BuildMinidumpThreadIDMap(thread_snapshots, &thread_id_map); + + EXPECT_TRUE(thread_id_map.empty()); +} + +TEST_F(MinidumpThreadIDMapTest, SimpleMapping) { + SetThreadID(0, 1); + SetThreadID(1, 3); + SetThreadID(2, 5); + SetThreadID(3, 7); + SetThreadID(4, 9); + + MinidumpThreadIDMap thread_id_map; + BuildMinidumpThreadIDMap(thread_snapshots(), &thread_id_map); + + EXPECT_EQ(5u, thread_id_map.size()); + EXPECT_PRED3(MapHasKeyValue, &thread_id_map, 1, 1); + EXPECT_PRED3(MapHasKeyValue, &thread_id_map, 3, 3); + EXPECT_PRED3(MapHasKeyValue, &thread_id_map, 5, 5); + EXPECT_PRED3(MapHasKeyValue, &thread_id_map, 7, 7); + EXPECT_PRED3(MapHasKeyValue, &thread_id_map, 9, 9); +} + +TEST_F(MinidumpThreadIDMapTest, Truncation) { + SetThreadID(0, 0x0000000000000000); + SetThreadID(1, 0x9999999900000001); + SetThreadID(2, 0x9999999980000001); + SetThreadID(3, 0x99999999fffffffe); + SetThreadID(4, 0x99999999ffffffff); + + MinidumpThreadIDMap thread_id_map; + BuildMinidumpThreadIDMap(thread_snapshots(), &thread_id_map); + + EXPECT_EQ(5u, thread_id_map.size()); + EXPECT_PRED3(MapHasKeyValue, &thread_id_map, 0x0000000000000000, 0x00000000); + EXPECT_PRED3(MapHasKeyValue, &thread_id_map, 0x9999999900000001, 0x00000001); + EXPECT_PRED3(MapHasKeyValue, &thread_id_map, 0x9999999980000001, 0x80000001); + EXPECT_PRED3(MapHasKeyValue, &thread_id_map, 0x99999999fffffffe, 0xfffffffe); + EXPECT_PRED3(MapHasKeyValue, &thread_id_map, 0x99999999ffffffff, 0xffffffff); +} + +TEST_F(MinidumpThreadIDMapTest, DuplicateThreadID) { + SetThreadID(0, 2); + SetThreadID(1, 4); + SetThreadID(2, 4); + SetThreadID(3, 6); + SetThreadID(4, 8); + + MinidumpThreadIDMap thread_id_map; + BuildMinidumpThreadIDMap(thread_snapshots(), &thread_id_map); + + EXPECT_EQ(4u, thread_id_map.size()); + EXPECT_PRED3(MapHasKeyValue, &thread_id_map, 2, 2); + EXPECT_PRED3(MapHasKeyValue, &thread_id_map, 4, 4); + EXPECT_PRED3(MapHasKeyValue, &thread_id_map, 6, 6); + EXPECT_PRED3(MapHasKeyValue, &thread_id_map, 8, 8); +} + +TEST_F(MinidumpThreadIDMapTest, Collision) { + SetThreadID(0, 0x0000000000000010); + SetThreadID(1, 0x0000000000000020); + SetThreadID(2, 0x0000000000000030); + SetThreadID(3, 0x0000000000000040); + SetThreadID(4, 0x0000000100000010); + + MinidumpThreadIDMap thread_id_map; + BuildMinidumpThreadIDMap(thread_snapshots(), &thread_id_map); + + EXPECT_EQ(5u, thread_id_map.size()); + EXPECT_PRED3(MapHasKeyValue, &thread_id_map, 0x0000000000000010, 0); + EXPECT_PRED3(MapHasKeyValue, &thread_id_map, 0x0000000000000020, 1); + EXPECT_PRED3(MapHasKeyValue, &thread_id_map, 0x0000000000000030, 2); + EXPECT_PRED3(MapHasKeyValue, &thread_id_map, 0x0000000000000040, 3); + EXPECT_PRED3(MapHasKeyValue, &thread_id_map, 0x0000000100000010, 4); +} + +TEST_F(MinidumpThreadIDMapTest, DuplicateAndCollision) { + SetThreadID(0, 0x0000000100000010); + SetThreadID(1, 0x0000000000000010); + SetThreadID(2, 0x0000000000000020); + SetThreadID(3, 0x0000000000000030); + SetThreadID(4, 0x0000000000000020); + + MinidumpThreadIDMap thread_id_map; + BuildMinidumpThreadIDMap(thread_snapshots(), &thread_id_map); + + EXPECT_EQ(4u, thread_id_map.size()); + EXPECT_PRED3(MapHasKeyValue, &thread_id_map, 0x0000000100000010, 0); + EXPECT_PRED3(MapHasKeyValue, &thread_id_map, 0x0000000000000010, 1); + EXPECT_PRED3(MapHasKeyValue, &thread_id_map, 0x0000000000000020, 2); + EXPECT_PRED3(MapHasKeyValue, &thread_id_map, 0x0000000000000030, 3); +} + +TEST_F(MinidumpThreadIDMapTest, AllDuplicates) { + SetThreadID(0, 6); + SetThreadID(1, 6); + SetThreadID(2, 6); + SetThreadID(3, 6); + SetThreadID(4, 6); + + MinidumpThreadIDMap thread_id_map; + BuildMinidumpThreadIDMap(thread_snapshots(), &thread_id_map); + + EXPECT_EQ(1u, thread_id_map.size()); + EXPECT_PRED3(MapHasKeyValue, &thread_id_map, 6, 6); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/minidump/minidump_thread_writer.cc b/minidump/minidump_thread_writer.cc index efca1bda..814473d0 100644 --- a/minidump/minidump_thread_writer.cc +++ b/minidump/minidump_thread_writer.cc @@ -19,6 +19,8 @@ #include "base/logging.h" #include "minidump/minidump_context_writer.h" #include "minidump/minidump_memory_writer.h" +#include "snapshot/memory_snapshot.h" +#include "snapshot/thread_snapshot.h" #include "util/file/file_writer.h" #include "util/numeric/safe_assignment.h" @@ -31,6 +33,33 @@ MinidumpThreadWriter::MinidumpThreadWriter() MinidumpThreadWriter::~MinidumpThreadWriter() { } +void MinidumpThreadWriter::InitializeFromSnapshot( + const ThreadSnapshot* thread_snapshot, + const MinidumpThreadIDMap* thread_id_map) { + DCHECK_EQ(state(), kStateMutable); + DCHECK(!stack_); + DCHECK(!context_); + + auto thread_id_it = thread_id_map->find(thread_snapshot->ThreadID()); + DCHECK(thread_id_it != thread_id_map->end()); + SetThreadID(thread_id_it->second); + + SetSuspendCount(thread_snapshot->SuspendCount()); + SetPriority(thread_snapshot->Priority()); + SetTEB(thread_snapshot->ThreadSpecificDataAddress()); + + const MemorySnapshot* stack_snapshot = thread_snapshot->Stack(); + if (stack_snapshot && stack_snapshot->Size() > 0) { + scoped_ptr stack = + MinidumpMemoryWriter::CreateFromSnapshot(stack_snapshot); + SetStack(stack.Pass()); + } + + scoped_ptr context = + MinidumpContextWriter::CreateFromSnapshot(thread_snapshot->Context()); + SetContext(context.Pass()); +} + const MINIDUMP_THREAD* MinidumpThreadWriter::MinidumpThread() const { DCHECK_EQ(state(), kStateWritable); @@ -108,6 +137,21 @@ MinidumpThreadListWriter::MinidumpThreadListWriter() MinidumpThreadListWriter::~MinidumpThreadListWriter() { } +void MinidumpThreadListWriter::InitializeFromSnapshot( + const std::vector& thread_snapshots, + MinidumpThreadIDMap* thread_id_map) { + DCHECK_EQ(state(), kStateMutable); + DCHECK(threads_.empty()); + + BuildMinidumpThreadIDMap(thread_snapshots, thread_id_map); + + for (const ThreadSnapshot* thread_snapshot : thread_snapshots) { + auto thread = make_scoped_ptr(new MinidumpThreadWriter()); + thread->InitializeFromSnapshot(thread_snapshot, thread_id_map); + AddThread(thread.Pass()); + } +} + void MinidumpThreadListWriter::SetMemoryListWriter( MinidumpMemoryListWriter* memory_list_writer) { DCHECK_EQ(state(), kStateMutable); diff --git a/minidump/minidump_thread_writer.h b/minidump/minidump_thread_writer.h index 5ce04816..e9bd114d 100644 --- a/minidump/minidump_thread_writer.h +++ b/minidump/minidump_thread_writer.h @@ -23,6 +23,7 @@ #include "base/basictypes.h" #include "base/memory/scoped_ptr.h" #include "minidump/minidump_stream_writer.h" +#include "minidump/minidump_thread_id_map.h" #include "minidump/minidump_writable.h" #include "util/stdlib/pointer_container.h" @@ -31,6 +32,7 @@ namespace crashpad { class MinidumpContextWriter; class MinidumpMemoryListWriter; class MinidumpMemoryWriter; +class ThreadSnapshot; //! \brief The writer for a MINIDUMP_THREAD object in a minidump file. //! @@ -43,6 +45,18 @@ class MinidumpThreadWriter final : public internal::MinidumpWritable { MinidumpThreadWriter(); ~MinidumpThreadWriter() override; + //! \brief Initializes the MINIDUMP_THREAD based on \a thread_snapshot. + //! + //! \param[in] thread_snapshot The thread snapshot to use as source data. + //! \param[in] thread_id_map A MinidumpThreadIDMap to be consulted to + //! determine the 32-bit minidump thread ID to use for \a thread_snapshot. + //! + //! \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 ThreadSnapshot* thread_snapshot, + const MinidumpThreadIDMap* thread_id_map); + //! \brief Returns a MINIDUMP_THREAD referencing this object’s data. //! //! This method is expected to be called by a MinidumpThreadListWriter in @@ -127,6 +141,20 @@ class MinidumpThreadListWriter final : public internal::MinidumpStreamWriter { MinidumpThreadListWriter(); ~MinidumpThreadListWriter() override; + //! \brief Adds an initialized MINIDUMP_THREAD for each thread in \a + //! thread_snapshots to the MINIDUMP_THREAD_LIST. + //! + //! \param[in] thread_snapshots The thread snapshots to use as source data. + //! \param[out] thread_id_map A MinidumpThreadIDMap to be built by this + //! method. This map must be empty when this method is called. + //! + //! \note Valid in #kStateMutable. AddThread() may not be called before this + //! method, and it is not normally necessary to call AddThread() after + //! this method. + void InitializeFromSnapshot( + const std::vector& thread_snapshots, + MinidumpThreadIDMap* thread_id_map); + //! \brief Sets the MinidumpMemoryListWriter that each thread’s stack memory //! region should be added to as extra memory. //! diff --git a/minidump/minidump_thread_writer_test.cc b/minidump/minidump_thread_writer_test.cc index 6dc065e7..a74307e9 100644 --- a/minidump/minidump_thread_writer_test.cc +++ b/minidump/minidump_thread_writer_test.cc @@ -17,14 +17,19 @@ #include #include +#include "base/strings/stringprintf.h" #include "gtest/gtest.h" #include "minidump/minidump_context_writer.h" #include "minidump/minidump_memory_writer.h" #include "minidump/minidump_file_writer.h" +#include "minidump/minidump_thread_id_map.h" #include "minidump/test/minidump_context_test_util.h" #include "minidump/test/minidump_memory_writer_test_util.h" #include "minidump/test/minidump_file_writer_test_util.h" #include "minidump/test/minidump_writable_test_util.h" +#include "snapshot/test/test_cpu_context.h" +#include "snapshot/test/test_memory_snapshot.h" +#include "snapshot/test/test_thread_snapshot.h" #include "util/file/string_file_writer.h" namespace crashpad { @@ -475,6 +480,186 @@ TEST(MinidumpThreadWriter, ThreeThreads_x86_MemoryList) { } } +struct InitializeFromSnapshotX86Traits { + typedef MinidumpContextX86 MinidumpContextType; + static void InitializeCPUContext(CPUContext* context, uint32_t seed) { + return InitializeCPUContextX86(context, seed); + } + static void ExpectMinidumpContext( + uint32_t expect_seed, const MinidumpContextX86* observed, bool snapshot) { + return ExpectMinidumpContextX86(expect_seed, observed, snapshot); + } +}; + +struct InitializeFromSnapshotAMD64Traits { + typedef MinidumpContextAMD64 MinidumpContextType; + static void InitializeCPUContext(CPUContext* context, uint32_t seed) { + return InitializeCPUContextX86_64(context, seed); + } + static void ExpectMinidumpContext(uint32_t expect_seed, + const MinidumpContextAMD64* observed, + bool snapshot) { + return ExpectMinidumpContextAMD64(expect_seed, observed, snapshot); + } +}; + +struct InitializeFromSnapshotNoContextTraits { + typedef MinidumpContextX86 MinidumpContextType; + static void InitializeCPUContext(CPUContext* context, uint32_t seed) { + context->architecture = kCPUArchitectureUnknown; + } + static void ExpectMinidumpContext(uint32_t expect_seed, + const MinidumpContextX86* observed, + bool snapshot) { + FAIL(); + } +}; + +template +void RunInitializeFromSnapshotTest(bool thread_id_collision) { + typedef typename Traits::MinidumpContextType MinidumpContextType; + MINIDUMP_THREAD expect_threads[3] = {}; + uint64_t thread_ids[arraysize(expect_threads)] = {}; + uint8_t memory_values[arraysize(expect_threads)] = {}; + uint32_t context_seeds[arraysize(expect_threads)] = {}; + + expect_threads[0].ThreadId = 1; + expect_threads[0].SuspendCount = 2; + expect_threads[0].Priority = 3; + expect_threads[0].Teb = 0x0123456789abcdef; + expect_threads[0].Stack.StartOfMemoryRange = 0x1000; + expect_threads[0].Stack.Memory.DataSize = 0x100; + expect_threads[0].ThreadContext.DataSize = sizeof(MinidumpContextType); + memory_values[0] = 'A'; + context_seeds[0] = 0x80000000; + + // The thread at index 1 has no stack. + expect_threads[1].ThreadId = 11; + expect_threads[1].SuspendCount = 12; + expect_threads[1].Priority = 13; + expect_threads[1].Teb = 0xfedcba9876543210; + expect_threads[1].ThreadContext.DataSize = sizeof(MinidumpContextType); + context_seeds[1] = 0x40000001; + + expect_threads[2].ThreadId = 21; + expect_threads[2].SuspendCount = 22; + expect_threads[2].Priority = 23; + expect_threads[2].Teb = 0x1111111111111111; + expect_threads[2].Stack.StartOfMemoryRange = 0x3000; + expect_threads[2].Stack.Memory.DataSize = 0x300; + expect_threads[2].ThreadContext.DataSize = sizeof(MinidumpContextType); + memory_values[2] = 'd'; + context_seeds[2] = 0x20000002; + + if (thread_id_collision) { + thread_ids[0] = 0x0123456700000001; + thread_ids[1] = 0x89abcdef00000001; + thread_ids[2] = 4; + expect_threads[0].ThreadId = 0; + expect_threads[1].ThreadId = 1; + expect_threads[2].ThreadId = 2; + } else { + thread_ids[0] = 1; + thread_ids[1] = 11; + thread_ids[2] = 22; + expect_threads[0].ThreadId = thread_ids[0]; + expect_threads[1].ThreadId = thread_ids[1]; + expect_threads[2].ThreadId = thread_ids[2]; + } + + PointerVector thread_snapshots_owner; + std::vector thread_snapshots; + for (size_t index = 0; index < arraysize(expect_threads); ++index) { + TestThreadSnapshot* thread_snapshot = new TestThreadSnapshot(); + thread_snapshots_owner.push_back(thread_snapshot); + + thread_snapshot->SetThreadID(thread_ids[index]); + thread_snapshot->SetSuspendCount(expect_threads[index].SuspendCount); + thread_snapshot->SetPriority(expect_threads[index].Priority); + thread_snapshot->SetThreadSpecificDataAddress(expect_threads[index].Teb); + + if (expect_threads[index].Stack.Memory.DataSize) { + auto memory_snapshot = make_scoped_ptr(new TestMemorySnapshot()); + memory_snapshot->SetAddress( + expect_threads[index].Stack.StartOfMemoryRange); + memory_snapshot->SetSize(expect_threads[index].Stack.Memory.DataSize); + memory_snapshot->SetValue(memory_values[index]); + thread_snapshot->SetStack(memory_snapshot.Pass()); + } + + Traits::InitializeCPUContext(thread_snapshot->MutableContext(), + context_seeds[index]); + + thread_snapshots.push_back(thread_snapshot); + } + + auto thread_list_writer = make_scoped_ptr(new MinidumpThreadListWriter()); + auto memory_list_writer = make_scoped_ptr(new MinidumpMemoryListWriter()); + thread_list_writer->SetMemoryListWriter(memory_list_writer.get()); + MinidumpThreadIDMap thread_id_map; + thread_list_writer->InitializeFromSnapshot(thread_snapshots, &thread_id_map); + + MinidumpFileWriter minidump_file_writer; + minidump_file_writer.AddStream(thread_list_writer.Pass()); + minidump_file_writer.AddStream(memory_list_writer.Pass()); + + StringFileWriter file_writer; + ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer)); + + const MINIDUMP_THREAD_LIST* thread_list; + const MINIDUMP_MEMORY_LIST* memory_list; + ASSERT_NO_FATAL_FAILURE( + GetThreadListStream(file_writer.string(), &thread_list, &memory_list)); + + ASSERT_EQ(3u, thread_list->NumberOfThreads); + ASSERT_EQ(2u, memory_list->NumberOfMemoryRanges); + + size_t memory_index = 0; + for (size_t index = 0; index < thread_list->NumberOfThreads; ++index) { + SCOPED_TRACE(base::StringPrintf("index %zu", index)); + + const MINIDUMP_MEMORY_DESCRIPTOR* observed_stack; + const MINIDUMP_MEMORY_DESCRIPTOR** observed_stack_p = + expect_threads[index].Stack.Memory.DataSize ? &observed_stack : nullptr; + const MinidumpContextType* observed_context; + ASSERT_NO_FATAL_FAILURE( + ExpectThread(&expect_threads[index], + &thread_list->Threads[index], + file_writer.string(), + observed_stack_p, + reinterpret_cast(&observed_context))); + + ASSERT_NO_FATAL_FAILURE(Traits::ExpectMinidumpContext( + context_seeds[index], observed_context, true)); + + if (observed_stack_p) { + ASSERT_NO_FATAL_FAILURE(ExpectMinidumpMemoryDescriptorAndContents( + &expect_threads[index].Stack, + observed_stack, + file_writer.string(), + memory_values[index], + index == thread_list->NumberOfThreads - 1)); + + ASSERT_NO_FATAL_FAILURE(ExpectMinidumpMemoryDescriptor( + observed_stack, &memory_list->MemoryRanges[memory_index])); + + ++memory_index; + } + } +} + +TEST(MinidumpThreadWriter, InitializeFromSnapshot_x86) { + RunInitializeFromSnapshotTest(false); +} + +TEST(MinidumpThreadWriter, InitializeFromSnapshot_AMD64) { + RunInitializeFromSnapshotTest(false); +} + +TEST(MinidumpThreadWriter, InitializeFromSnapshot_ThreadIDCollision) { + RunInitializeFromSnapshotTest(true); +} + TEST(MinidumpThreadWriterDeathTest, NoContext) { MinidumpFileWriter minidump_file_writer; auto thread_list_writer = make_scoped_ptr(new MinidumpThreadListWriter()); @@ -488,6 +673,12 @@ TEST(MinidumpThreadWriterDeathTest, NoContext) { ASSERT_DEATH(minidump_file_writer.WriteEverything(&file_writer), "context_"); } +TEST(MinidumpThreadWriterDeathTest, InitializeFromSnapshot_NoContext) { + ASSERT_DEATH( + RunInitializeFromSnapshotTest( + false), "context_"); +} + } // namespace } // namespace test } // namespace crashpad diff --git a/snapshot/cpu_context_test.cc b/snapshot/cpu_context_test.cc index e48561f7..a5f0b6aa 100644 --- a/snapshot/cpu_context_test.cc +++ b/snapshot/cpu_context_test.cc @@ -17,6 +17,7 @@ #include #include +#include "base/basictypes.h" #include "gtest/gtest.h" namespace crashpad { @@ -143,6 +144,21 @@ TEST(CPUContextX86, FxsaveToFsaveTagWord) { SetX87Register(&st_mm[7], kExponentNormal, true, kFractionNormal); // valid EXPECT_EQ(0xfe90, CPUContextX86::FxsaveToFsaveTagWord(fsw, fxsave_tag, st_mm)); + + // In this set, everything is valid. + fsw = 0 << 11; // top = 0: logical 0-7 maps to physical 0-7 + fxsave_tag = 0xff; // nothing empty + for (size_t index = 0; index < arraysize(st_mm); ++index) { + SetX87Register(&st_mm[index], kExponentNormal, true, kFractionAllZero); + } + EXPECT_EQ(0, CPUContextX86::FxsaveToFsaveTagWord(fsw, fxsave_tag, st_mm)); + + // In this set, everything is empty. The registers shouldn’t be consulted at + // all, so they’re left alone from the previous set. + fsw = 0 << 11; // top = 0: logical 0-7 maps to physical 0-7 + fxsave_tag = 0; // everything empty + EXPECT_EQ(0xffff, + CPUContextX86::FxsaveToFsaveTagWord(fsw, fxsave_tag, st_mm)); } } // namespace diff --git a/snapshot/snapshot.gyp b/snapshot/snapshot.gyp index 4ada9e5c..890fbfee 100644 --- a/snapshot/snapshot.gyp +++ b/snapshot/snapshot.gyp @@ -92,6 +92,8 @@ 'test/test_memory_snapshot.h', 'test/test_module_snapshot.cc', 'test/test_module_snapshot.h', + 'test/test_thread_snapshot.cc', + 'test/test_thread_snapshot.h', ], }, { diff --git a/snapshot/test/test_thread_snapshot.cc b/snapshot/test/test_thread_snapshot.cc new file mode 100644 index 00000000..8ee26dff --- /dev/null +++ b/snapshot/test/test_thread_snapshot.cc @@ -0,0 +1,59 @@ +// 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/test/test_thread_snapshot.h" + +namespace crashpad { +namespace test { + +TestThreadSnapshot::TestThreadSnapshot() + : context_union_(), + context_(), + stack_(), + thread_id_(0), + suspend_count_(0), + priority_(0), + thread_specific_data_address_(0) { + context_.x86 = &context_union_.x86; +} + +TestThreadSnapshot::~TestThreadSnapshot() { +} + +const CPUContext* TestThreadSnapshot::Context() const { + return &context_; +} + +const MemorySnapshot* TestThreadSnapshot::Stack() const { + return stack_.get(); +} + +uint64_t TestThreadSnapshot::ThreadID() const { + return thread_id_; +} + +int TestThreadSnapshot::SuspendCount() const { + return suspend_count_; +} + +int TestThreadSnapshot::Priority() const { + return priority_; +} + +uint64_t TestThreadSnapshot::ThreadSpecificDataAddress() const { + return thread_specific_data_address_; +} + +} // namespace test +} // namespace crashpad diff --git a/snapshot/test/test_thread_snapshot.h b/snapshot/test/test_thread_snapshot.h new file mode 100644 index 00000000..b9564695 --- /dev/null +++ b/snapshot/test/test_thread_snapshot.h @@ -0,0 +1,91 @@ +// 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_THREAD_SNAPSHOT_H_ +#define CRASHPAD_SNAPSHOT_TEST_TEST_THREAD_SNAPSHOT_H_ + +#include + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "snapshot/cpu_context.h" +#include "snapshot/memory_snapshot.h" +#include "snapshot/thread_snapshot.h" + +namespace crashpad { +namespace test { + +//! \brief A test ThreadSnapshot that can carry arbitrary data for testing +//! purposes. +class TestThreadSnapshot final : public ThreadSnapshot { + public: + TestThreadSnapshot(); + ~TestThreadSnapshot(); + + //! \brief Obtains a pointer to the underlying mutable CPUContext structure. + //! + //! This method is intended to be used by callers to populate the CPUContext + //! structure. + //! + //! \return The same pointer that Context() does, while treating the data as + //! mutable. + //! + //! \attention This returns a non-`const` pointer to this object’s private + //! data so that a caller can populate the context structure directly. + //! This is done because providing setter interfaces to each field in the + //! context structure would be unwieldy and cumbersome. Care must be taken + //! to populate the context structure correctly. + CPUContext* MutableContext() { return &context_; } + + //! \brief Sets the memory region to be returned by Stack(). + //! + //! \param[in] stack The memory region that Stack() will return. The + //! TestThreadSnapshot object takes ownership of \a stack. + void SetStack(scoped_ptr stack) { stack_ = stack.Pass(); } + + void SetThreadID(uint64_t thread_id) { thread_id_ = thread_id; } + void SetSuspendCount(int suspend_count) { suspend_count_ = suspend_count; } + void SetPriority(int priority) { priority_ = priority; } + void SetThreadSpecificDataAddress(uint64_t thread_specific_data_address) { + thread_specific_data_address_ = thread_specific_data_address; + } + + // ThreadSnapshot: + + const CPUContext* Context() const override; + const MemorySnapshot* Stack() const override; + uint64_t ThreadID() const override; + int SuspendCount() const override; + int Priority() const override; + uint64_t ThreadSpecificDataAddress() const override; + + private: + union { + CPUContextX86 x86; + CPUContextX86_64 x86_64; + } context_union_; + CPUContext context_; + scoped_ptr stack_; + uint64_t thread_id_; + int suspend_count_; + int priority_; + uint64_t thread_specific_data_address_; + + DISALLOW_COPY_AND_ASSIGN(TestThreadSnapshot); +}; + +} // namespace test +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_TEST_TEST_THREAD_SNAPSHOT_H_ diff --git a/snapshot/thread_snapshot.h b/snapshot/thread_snapshot.h index 7ae88314..ba1c55cb 100644 --- a/snapshot/thread_snapshot.h +++ b/snapshot/thread_snapshot.h @@ -36,7 +36,8 @@ class ThreadSnapshot { virtual const CPUContext* Context() const = 0; //! \brief Returns a MemorySnapshot object corresponding to the memory region - //! that contains the thread’s stack. + //! that contains the thread’s stack, or `nullptr` if no stack region is + //! available. //! //! The caller does not take ownership of this object, it is scoped to the //! lifetime of the ThreadSnapshot object that it was obtained from.