mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-26 23:01:05 +08:00
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:
parent
52c2f6edfc
commit
8248c030e2
@ -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',
|
||||
|
66
minidump/minidump_thread_id_map.cc
Normal file
66
minidump/minidump_thread_id_map.cc
Normal 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 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<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
|
52
minidump/minidump_thread_id_map.h
Normal file
52
minidump/minidump_thread_id_map.h
Normal 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_
|
190
minidump/minidump_thread_id_map_test.cc
Normal file
190
minidump/minidump_thread_id_map_test.cc
Normal 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) {
|
||||
// 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<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
|
@ -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<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 {
|
||||
DCHECK_EQ(state(), kStateWritable);
|
||||
|
||||
@ -108,6 +137,21 @@ 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(
|
||||
MinidumpMemoryListWriter* memory_list_writer) {
|
||||
DCHECK_EQ(state(), kStateMutable);
|
||||
|
@ -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<const ThreadSnapshot*>& thread_snapshots,
|
||||
MinidumpThreadIDMap* thread_id_map);
|
||||
|
||||
//! \brief Sets the MinidumpMemoryListWriter that each thread’s stack memory
|
||||
//! region should be added to as extra memory.
|
||||
//!
|
||||
|
@ -17,14 +17,19 @@
|
||||
#include <dbghelp.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#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 <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) {
|
||||
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<InitializeFromSnapshotNoContextTraits>(
|
||||
false), "context_");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#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
|
||||
|
@ -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',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
59
snapshot/test/test_thread_snapshot.cc
Normal file
59
snapshot/test/test_thread_snapshot.cc
Normal 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
|
91
snapshot/test/test_thread_snapshot.h
Normal file
91
snapshot/test/test_thread_snapshot.h
Normal 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 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<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_
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user