Add MinidumpThreadListWriter::InitializeFromSnapshot(), everything

downstream, and tests.

TEST=minidump_test
R=rsesek@chromium.org

Review URL: https://codereview.chromium.org/693933002
This commit is contained in:
Mark Mentovai 2014-11-04 12:36:29 -05:00
parent 52c2f6edfc
commit 8248c030e2
12 changed files with 744 additions and 1 deletions

View File

@ -57,6 +57,8 @@
'minidump_string_writer.h', 'minidump_string_writer.h',
'minidump_system_info_writer.cc', 'minidump_system_info_writer.cc',
'minidump_system_info_writer.h', 'minidump_system_info_writer.h',
'minidump_thread_id_map.cc',
'minidump_thread_id_map.h',
'minidump_thread_writer.cc', 'minidump_thread_writer.cc',
'minidump_thread_writer.h', 'minidump_thread_writer.h',
'minidump_writable.cc', 'minidump_writable.cc',
@ -90,6 +92,7 @@
'minidump_simple_string_dictionary_writer_test.cc', 'minidump_simple_string_dictionary_writer_test.cc',
'minidump_string_writer_test.cc', 'minidump_string_writer_test.cc',
'minidump_system_info_writer_test.cc', 'minidump_system_info_writer_test.cc',
'minidump_thread_id_map_test.cc',
'minidump_thread_writer_test.cc', 'minidump_thread_writer_test.cc',
'minidump_writable_test.cc', 'minidump_writable_test.cc',
'test/minidump_context_test_util.cc', 'test/minidump_context_test_util.cc',

View File

@ -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 <limits>
#include <set>
#include <utility>
#include "base/logging.h"
#include "snapshot/thread_snapshot.h"
namespace crashpad {
void BuildMinidumpThreadIDMap(
const std::vector<const ThreadSnapshot*>& thread_snapshots,
MinidumpThreadIDMap* thread_id_map) {
DCHECK(thread_id_map->empty());
// First, try truncating each 64-bit thread ID to 32 bits. If thats 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<uint32_t> 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<uint32_t>::max());
}
}
} // namespace crashpad

View File

@ -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 <stdint.h>
#include <map>
#include <vector>
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<uint64_t, uint32_t>;
//! \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<const ThreadSnapshot*>& thread_snapshots,
MinidumpThreadIDMap* thread_id_map);
} // namespace crashpad
#endif // CRASHPAD_MINIDUMP_MINIDUMP_THREAD_ID_MAP_H_

View File

@ -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 <vector>
#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<const ThreadSnapshot*>& thread_snapshots() const {
return thread_snapshots_;
}
private:
std::vector<const ThreadSnapshot*> thread_snapshots_;
TestThreadSnapshot test_thread_snapshots_[5];
DISALLOW_COPY_AND_ASSIGN(MinidumpThreadIDMapTest);
};
TEST_F(MinidumpThreadIDMapTest, NoThreads) {
// Dont use thread_snapshots(), because its got some threads in it, and the
// point of this test is to make sure that BuildMinidumpThreadIDMap() works
// with no threads.
std::vector<const ThreadSnapshot*> 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

View File

@ -19,6 +19,8 @@
#include "base/logging.h" #include "base/logging.h"
#include "minidump/minidump_context_writer.h" #include "minidump/minidump_context_writer.h"
#include "minidump/minidump_memory_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/file/file_writer.h"
#include "util/numeric/safe_assignment.h" #include "util/numeric/safe_assignment.h"
@ -31,6 +33,33 @@ MinidumpThreadWriter::MinidumpThreadWriter()
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<MinidumpMemoryWriter> stack =
MinidumpMemoryWriter::CreateFromSnapshot(stack_snapshot);
SetStack(stack.Pass());
}
scoped_ptr<MinidumpContextWriter> context =
MinidumpContextWriter::CreateFromSnapshot(thread_snapshot->Context());
SetContext(context.Pass());
}
const MINIDUMP_THREAD* MinidumpThreadWriter::MinidumpThread() const { const MINIDUMP_THREAD* MinidumpThreadWriter::MinidumpThread() const {
DCHECK_EQ(state(), kStateWritable); DCHECK_EQ(state(), kStateWritable);
@ -108,6 +137,21 @@ MinidumpThreadListWriter::MinidumpThreadListWriter()
MinidumpThreadListWriter::~MinidumpThreadListWriter() { MinidumpThreadListWriter::~MinidumpThreadListWriter() {
} }
void MinidumpThreadListWriter::InitializeFromSnapshot(
const std::vector<const ThreadSnapshot*>& 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( void MinidumpThreadListWriter::SetMemoryListWriter(
MinidumpMemoryListWriter* memory_list_writer) { MinidumpMemoryListWriter* memory_list_writer) {
DCHECK_EQ(state(), kStateMutable); DCHECK_EQ(state(), kStateMutable);

View File

@ -23,6 +23,7 @@
#include "base/basictypes.h" #include "base/basictypes.h"
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "minidump/minidump_stream_writer.h" #include "minidump/minidump_stream_writer.h"
#include "minidump/minidump_thread_id_map.h"
#include "minidump/minidump_writable.h" #include "minidump/minidump_writable.h"
#include "util/stdlib/pointer_container.h" #include "util/stdlib/pointer_container.h"
@ -31,6 +32,7 @@ namespace crashpad {
class MinidumpContextWriter; class MinidumpContextWriter;
class MinidumpMemoryListWriter; class MinidumpMemoryListWriter;
class MinidumpMemoryWriter; class MinidumpMemoryWriter;
class ThreadSnapshot;
//! \brief The writer for a MINIDUMP_THREAD object in a minidump file. //! \brief The writer for a MINIDUMP_THREAD object in a minidump file.
//! //!
@ -43,6 +45,18 @@ class MinidumpThreadWriter final : public internal::MinidumpWritable {
MinidumpThreadWriter(); MinidumpThreadWriter();
~MinidumpThreadWriter() override; ~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 objects data. //! \brief Returns a MINIDUMP_THREAD referencing this objects data.
//! //!
//! This method is expected to be called by a MinidumpThreadListWriter in //! This method is expected to be called by a MinidumpThreadListWriter in
@ -127,6 +141,20 @@ class MinidumpThreadListWriter final : public internal::MinidumpStreamWriter {
MinidumpThreadListWriter(); MinidumpThreadListWriter();
~MinidumpThreadListWriter() override; ~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<const ThreadSnapshot*>& thread_snapshots,
MinidumpThreadIDMap* thread_id_map);
//! \brief Sets the MinidumpMemoryListWriter that each threads stack memory //! \brief Sets the MinidumpMemoryListWriter that each threads stack memory
//! region should be added to as extra memory. //! region should be added to as extra memory.
//! //!

View File

@ -17,14 +17,19 @@
#include <dbghelp.h> #include <dbghelp.h>
#include <sys/types.h> #include <sys/types.h>
#include "base/strings/stringprintf.h"
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "minidump/minidump_context_writer.h" #include "minidump/minidump_context_writer.h"
#include "minidump/minidump_memory_writer.h" #include "minidump/minidump_memory_writer.h"
#include "minidump/minidump_file_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_context_test_util.h"
#include "minidump/test/minidump_memory_writer_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_file_writer_test_util.h"
#include "minidump/test/minidump_writable_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" #include "util/file/string_file_writer.h"
namespace crashpad { 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 <typename Traits>
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<TestThreadSnapshot> thread_snapshots_owner;
std::vector<const ThreadSnapshot*> 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<const void**>(&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<InitializeFromSnapshotX86Traits>(false);
}
TEST(MinidumpThreadWriter, InitializeFromSnapshot_AMD64) {
RunInitializeFromSnapshotTest<InitializeFromSnapshotAMD64Traits>(false);
}
TEST(MinidumpThreadWriter, InitializeFromSnapshot_ThreadIDCollision) {
RunInitializeFromSnapshotTest<InitializeFromSnapshotX86Traits>(true);
}
TEST(MinidumpThreadWriterDeathTest, NoContext) { TEST(MinidumpThreadWriterDeathTest, NoContext) {
MinidumpFileWriter minidump_file_writer; MinidumpFileWriter minidump_file_writer;
auto thread_list_writer = make_scoped_ptr(new MinidumpThreadListWriter()); 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_"); ASSERT_DEATH(minidump_file_writer.WriteEverything(&file_writer), "context_");
} }
TEST(MinidumpThreadWriterDeathTest, InitializeFromSnapshot_NoContext) {
ASSERT_DEATH(
RunInitializeFromSnapshotTest<InitializeFromSnapshotNoContextTraits>(
false), "context_");
}
} // namespace } // namespace
} // namespace test } // namespace test
} // namespace crashpad } // namespace crashpad

View File

@ -17,6 +17,7 @@
#include <stdint.h> #include <stdint.h>
#include <string.h> #include <string.h>
#include "base/basictypes.h"
#include "gtest/gtest.h" #include "gtest/gtest.h"
namespace crashpad { namespace crashpad {
@ -143,6 +144,21 @@ TEST(CPUContextX86, FxsaveToFsaveTagWord) {
SetX87Register(&st_mm[7], kExponentNormal, true, kFractionNormal); // valid SetX87Register(&st_mm[7], kExponentNormal, true, kFractionNormal); // valid
EXPECT_EQ(0xfe90, EXPECT_EQ(0xfe90,
CPUContextX86::FxsaveToFsaveTagWord(fsw, fxsave_tag, st_mm)); 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 shouldnt be consulted at
// all, so theyre 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 } // namespace

View File

@ -92,6 +92,8 @@
'test/test_memory_snapshot.h', 'test/test_memory_snapshot.h',
'test/test_module_snapshot.cc', 'test/test_module_snapshot.cc',
'test/test_module_snapshot.h', 'test/test_module_snapshot.h',
'test/test_thread_snapshot.cc',
'test/test_thread_snapshot.h',
], ],
}, },
{ {

View File

@ -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

View File

@ -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 <stdint.h>
#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 objects 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<MemorySnapshot> 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<MemorySnapshot> 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_

View File

@ -36,7 +36,8 @@ class ThreadSnapshot {
virtual const CPUContext* Context() const = 0; virtual const CPUContext* Context() const = 0;
//! \brief Returns a MemorySnapshot object corresponding to the memory region //! \brief Returns a MemorySnapshot object corresponding to the memory region
//! that contains the threads stack. //! that contains the threads stack, or `nullptr` if no stack region is
//! available.
//! //!
//! The caller does not take ownership of this object, it is scoped to the //! The caller does not take ownership of this object, it is scoped to the
//! lifetime of the ThreadSnapshot object that it was obtained from. //! lifetime of the ThreadSnapshot object that it was obtained from.