win: Add support for capturing unloaded modules

R=mark@chromium.org
BUG=crashpad:89

Change-Id: Ib6a67147e538811168d68f14a457fdceab30c02e
Reviewed-on: https://chromium-review.googlesource.com/327231
Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
Scott Graham 2016-02-11 17:19:30 -08:00
parent a392a2c3ed
commit badfacccee
26 changed files with 871 additions and 0 deletions

View File

@ -160,6 +160,9 @@ enum MINIDUMP_STREAM_TYPE {
//! \brief The stream contains information about active `HANDLE`s.
HandleDataStream = 12,
//! \brief The stream type for MINIDUMP_UNLOADED_MODULE_LIST.
UnloadedModuleListStream = 14,
//! \brief The stream type for MINIDUMP_MISC_INFO, MINIDUMP_MISC_INFO_2,
//! MINIDUMP_MISC_INFO_3, and MINIDUMP_MISC_INFO_4.
//!
@ -674,6 +677,9 @@ struct __attribute__((packed, aligned(4))) MINIDUMP_HANDLE_DESCRIPTOR_2
};
//! \brief Represents the header for a handle data stream.
//!
//! A list of MINIDUMP_HANDLE_DESCRIPTOR or MINIDUMP_HANDLE_DESCRIPTOR_2
//! structures will immediately follow in the stream.
struct __attribute((packed, aligned(4))) MINIDUMP_HANDLE_DATA_STREAM {
//! \brief The size of the header information for the stream, in bytes. This
//! value is `sizeof(MINIDUMP_HANDLE_DATA_STREAM)`.
@ -691,6 +697,58 @@ struct __attribute((packed, aligned(4))) MINIDUMP_HANDLE_DATA_STREAM {
uint32_t Reserved;
};
//! \brief Information about a specific module that was recorded as being
//! unloaded at the time the snapshot was taken.
//!
//! An unloaded module may be a shared library or a loadable module.
//!
//! \sa MINIDUMP_UNLOADED_MODULE_LIST
struct __attribute__((packed, aligned(4))) MINIDUMP_UNLOADED_MODULE {
//! \brief The base address where the module was loaded in the address space
//! of the process that the minidump file contains a snapshot of.
uint64_t BaseOfImage;
//! \brief The size of the unloaded module.
uint32_t SizeOfImage;
//! \brief The modules checksum, or `0` if unknown.
//!
//! On Windows, this field comes from the `CheckSum` field of the modules
//! `IMAGE_OPTIONAL_HEADER` structure, if present. It reflects the checksum at
//! the time the module was linked.
uint32_t CheckSum;
//! \brief The modules timestamp, in `time_t` units, seconds since the POSIX
//! epoch.
//!
//! On Windows, this field comes from the `TimeDateStamp` field of the
//! modules `IMAGE_HEADER` structure. It reflects the timestamp at the time
//! the module was linked.
uint32_t TimeDateStamp;
//! \brief ::RVA of a MINIDUMP_STRING containing the modules path or file
//! name.
RVA ModuleNameRva;
};
//! \brief Information about all modules recorded as unloaded when the snapshot
//! was taken.
//!
//! A list of MINIDUMP_UNLOADED_MODULE structures will immediately follow in the
//! stream.
struct __attribute__((packed, aligned(4))) MINIDUMP_UNLOADED_MODULE_LIST {
//! \brief The size of the header information for the stream, in bytes. This
//! value is `sizeof(MINIDUMP_UNLOADED_MODULE_LIST)`.
uint32_t SizeOfHeader;
//! \brief The size of a descriptor in the stream, in bytes. This value is
//! `sizeof(MINIDUMP_UNLOADED_MODULE)`.
uint32_t SizeOfEntry;
//! \brief The number of entries in the stream.
uint32_t NumberOfEntries;
};
//! \anchor MINIDUMP_MISCx
//! \name MINIDUMP_MISC*
//!

View File

@ -165,6 +165,16 @@ int CrashyMain(int argc, wchar_t* argv[]) {
return EXIT_FAILURE;
}
// Load and unload some uncommonly used modules so we can see them in the list
// reported by `lm`. At least two so that we confirm we got the size of
// RTL_UNLOAD_EVENT_TRACE right.
CHECK(GetModuleHandle(L"lz32.dll") == nullptr);
CHECK(GetModuleHandle(L"wmerror.dll") == nullptr);
HMODULE lz32 = LoadLibrary(L"lz32.dll");
HMODULE wmerror = LoadLibrary(L"wmerror.dll");
FreeLibrary(lz32);
FreeLibrary(wmerror);
// Make sure data pointed to by the stack is captured.
const int kDataSize = 512;
int* pointed_to_data = new int[kDataSize];

View File

@ -70,6 +70,8 @@
'minidump_thread_id_map.h',
'minidump_thread_writer.cc',
'minidump_thread_writer.h',
'minidump_unloaded_module_writer.cc',
'minidump_unloaded_module_writer.h',
'minidump_writable.cc',
'minidump_writable.h',
'minidump_writer_util.cc',

View File

@ -76,6 +76,11 @@ enum MinidumpStreamType : uint32_t {
//! \sa HandleDataStream
kMinidumpStreamTypeHandleData = HandleDataStream,
//! \brief The stream type for MINIDUMP_UNLOADED_MODULE_LIST.
//!
//! \sa UnloadedModuleListStream
kMinidumpStreamTypeUnloadedModuleList = UnloadedModuleListStream,
//! \brief The stream type for MINIDUMP_MISC_INFO, MINIDUMP_MISC_INFO_2,
//! MINIDUMP_MISC_INFO_3, and MINIDUMP_MISC_INFO_4.
//!

View File

@ -27,6 +27,7 @@
#include "minidump/minidump_system_info_writer.h"
#include "minidump/minidump_thread_id_map.h"
#include "minidump/minidump_thread_writer.h"
#include "minidump/minidump_unloaded_module_writer.h"
#include "minidump/minidump_writer_util.h"
#include "snapshot/exception_snapshot.h"
#include "snapshot/process_snapshot.h"
@ -95,6 +96,14 @@ void MinidumpFileWriter::InitializeFromSnapshot(
module_list->InitializeFromSnapshot(process_snapshot->Modules());
AddStream(std::move(module_list));
auto unloaded_modules = process_snapshot->UnloadedModules();
if (!unloaded_modules.empty()) {
auto unloaded_module_list =
make_scoped_ptr(new MinidumpUnloadedModuleListWriter());
unloaded_module_list->InitializeFromSnapshot(unloaded_modules);
AddStream(std::move(unloaded_module_list));
}
auto crashpad_info = make_scoped_ptr(new MinidumpCrashpadInfoWriter());
crashpad_info->InitializeFromSnapshot(process_snapshot);

View File

@ -49,6 +49,7 @@
'minidump_system_info_writer_test.cc',
'minidump_thread_id_map_test.cc',
'minidump_thread_writer_test.cc',
'minidump_unloaded_module_writer_test.cc',
'minidump_writable_test.cc',
'test/minidump_context_test_util.cc',
'test/minidump_context_test_util.h',

View File

@ -0,0 +1,203 @@
// Copyright 2016 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_unloaded_module_writer.h"
#include <limits>
#include "minidump/minidump_writer_util.h"
#include "util/file/file_writer.h"
#include "util/numeric/in_range_cast.h"
#include "util/numeric/safe_assignment.h"
namespace crashpad {
MinidumpUnloadedModuleWriter::MinidumpUnloadedModuleWriter()
: MinidumpWritable(), unloaded_module_(), name_() {}
MinidumpUnloadedModuleWriter::~MinidumpUnloadedModuleWriter() {
}
void MinidumpUnloadedModuleWriter::InitializeFromSnapshot(
const UnloadedModuleSnapshot& unloaded_module_snapshot) {
DCHECK_EQ(state(), kStateMutable);
DCHECK(!name_);
SetName(unloaded_module_snapshot.Name());
SetImageBaseAddress(unloaded_module_snapshot.Address());
SetImageSize(InRangeCast<uint32_t>(unloaded_module_snapshot.Size(),
std::numeric_limits<uint32_t>::max()));
SetTimestamp(unloaded_module_snapshot.Timestamp());
SetChecksum(unloaded_module_snapshot.Checksum());
}
const MINIDUMP_UNLOADED_MODULE*
MinidumpUnloadedModuleWriter::MinidumpUnloadedModule() const {
DCHECK_EQ(state(), kStateWritable);
return &unloaded_module_;
}
void MinidumpUnloadedModuleWriter::SetName(const std::string& name) {
DCHECK_EQ(state(), kStateMutable);
if (!name_) {
name_.reset(new internal::MinidumpUTF16StringWriter());
}
name_->SetUTF8(name);
}
void MinidumpUnloadedModuleWriter::SetTimestamp(time_t timestamp) {
DCHECK_EQ(state(), kStateMutable);
internal::MinidumpWriterUtil::AssignTimeT(&unloaded_module_.TimeDateStamp,
timestamp);
}
bool MinidumpUnloadedModuleWriter::Freeze() {
DCHECK_EQ(state(), kStateMutable);
CHECK(name_);
if (!MinidumpWritable::Freeze()) {
return false;
}
name_->RegisterRVA(&unloaded_module_.ModuleNameRva);
return true;
}
size_t MinidumpUnloadedModuleWriter::SizeOfObject() {
DCHECK_GE(state(), kStateFrozen);
// This object doesnt directly write anything itself. Its
// MINIDUMP_UNLOADED_MODULE is written by its parent as part of a
// MINIDUMP_UNLOADED_MODULE_LIST, and its children are responsible for writing
// themselves.
return 0;
}
std::vector<internal::MinidumpWritable*>
MinidumpUnloadedModuleWriter::Children() {
DCHECK_GE(state(), kStateFrozen);
DCHECK(name_);
std::vector<MinidumpWritable*> children(1, name_.get());
return children;
}
bool MinidumpUnloadedModuleWriter::WriteObject(
FileWriterInterface* file_writer) {
DCHECK_EQ(state(), kStateWritable);
// This object doesnt directly write anything itself. Its
// MINIDUMP_UNLOADED_MODULE is written by its parent as part of a
// MINIDUMP_UNLOADED_MODULE_LIST, and its children are responsible for writing
// themselves.
return true;
}
MinidumpUnloadedModuleListWriter::MinidumpUnloadedModuleListWriter()
: MinidumpStreamWriter(),
unloaded_modules_(),
unloaded_module_list_base_() {}
MinidumpUnloadedModuleListWriter::~MinidumpUnloadedModuleListWriter() {
}
void MinidumpUnloadedModuleListWriter::InitializeFromSnapshot(
const std::vector<UnloadedModuleSnapshot>& unloaded_module_snapshots) {
DCHECK_EQ(state(), kStateMutable);
DCHECK(unloaded_modules_.empty());
for (auto unloaded_module_snapshot : unloaded_module_snapshots) {
auto unloaded_module = make_scoped_ptr(new MinidumpUnloadedModuleWriter());
unloaded_module->InitializeFromSnapshot(unloaded_module_snapshot);
AddUnloadedModule(std::move(unloaded_module));
}
}
void MinidumpUnloadedModuleListWriter::AddUnloadedModule(
scoped_ptr<MinidumpUnloadedModuleWriter> unloaded_module) {
DCHECK_EQ(state(), kStateMutable);
unloaded_modules_.push_back(unloaded_module.release());
}
bool MinidumpUnloadedModuleListWriter::Freeze() {
DCHECK_EQ(state(), kStateMutable);
if (!MinidumpStreamWriter::Freeze()) {
return false;
}
unloaded_module_list_base_.SizeOfHeader =
sizeof(MINIDUMP_UNLOADED_MODULE_LIST);
unloaded_module_list_base_.SizeOfEntry = sizeof(MINIDUMP_UNLOADED_MODULE);
size_t unloaded_module_count = unloaded_modules_.size();
if (!AssignIfInRange(&unloaded_module_list_base_.NumberOfEntries,
unloaded_module_count)) {
LOG(ERROR) << "unloaded_module_count " << unloaded_module_count
<< " out of range";
return false;
}
return true;
}
size_t MinidumpUnloadedModuleListWriter::SizeOfObject() {
DCHECK_GE(state(), kStateFrozen);
return sizeof(unloaded_module_list_base_) +
unloaded_modules_.size() * sizeof(MINIDUMP_UNLOADED_MODULE);
}
std::vector<internal::MinidumpWritable*>
MinidumpUnloadedModuleListWriter::Children() {
DCHECK_GE(state(), kStateFrozen);
std::vector<MinidumpWritable*> children;
for (MinidumpUnloadedModuleWriter* unloaded_module : unloaded_modules_) {
children.push_back(unloaded_module);
}
return children;
}
bool MinidumpUnloadedModuleListWriter::WriteObject(
FileWriterInterface* file_writer) {
DCHECK_EQ(state(), kStateWritable);
WritableIoVec iov;
iov.iov_base = &unloaded_module_list_base_;
iov.iov_len = sizeof(unloaded_module_list_base_);
std::vector<WritableIoVec> iovecs(1, iov);
for (const MinidumpUnloadedModuleWriter* unloaded_module :
unloaded_modules_) {
iov.iov_base = unloaded_module->MinidumpUnloadedModule();
iov.iov_len = sizeof(MINIDUMP_UNLOADED_MODULE);
iovecs.push_back(iov);
}
return file_writer->WriteIoVec(&iovecs);
}
MinidumpStreamType MinidumpUnloadedModuleListWriter::StreamType() const {
return kMinidumpStreamTypeUnloadedModuleList;
}
} // namespace crashpad

View File

@ -0,0 +1,154 @@
// Copyright 2016 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_UNLOADED_MODULE_WRITER_H_
#define CRASHPAD_MINIDUMP_MINIDUMP_UNLOADED_MODULE_WRITER_H_
#include <windows.h>
#include <dbghelp.h>
#include <stdint.h>
#include <string>
#include <vector>
#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
#include "minidump/minidump_stream_writer.h"
#include "minidump/minidump_string_writer.h"
#include "minidump/minidump_writable.h"
#include "snapshot/unloaded_module_snapshot.h"
namespace crashpad {
//! \brief The writer for a MINIDUMP_UNLOADED_MODULE object in a minidump file.
//!
//! Because MINIDUMP_UNLOADED_MODULE objects only appear as elements of
//! MINIDUMP_UNLOADED_MODULE_LIST objects, this class does not write any data on
//! its own. It makes its MINIDUMP_UNLOADED_MODULE data available to its
//! MinidumpUnloadedModuleListWriter parent, which writes it as part of a
//! MINIDUMP_UNLOADED_MODULE_LIST.
class MinidumpUnloadedModuleWriter final : public internal::MinidumpWritable {
public:
MinidumpUnloadedModuleWriter();
~MinidumpUnloadedModuleWriter() override;
//! \brief Initializes the MINIDUMP_UNLOADED_MODULE based on \a
//! unloaded_module_snapshot.
//!
//! \param[in] unloaded_module_snapshot The unloaded module snapshot to use as
//! source data.
//!
//! \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 UnloadedModuleSnapshot& unloaded_module_snapshot);
//! \brief Returns a MINIDUMP_UNLOADED_MODULE referencing this objects data.
//!
//! This method is expected to be called by a MinidumpUnloadedModuleListWriter
//! in order to obtain a MINIDUMP_UNLOADED_MODULE to include in its list.
//!
//! \note Valid in #kStateWritable.
const MINIDUMP_UNLOADED_MODULE* MinidumpUnloadedModule() const;
//! \brief Arranges for MINIDUMP_UNLOADED_MODULE::ModuleNameRva to point to a
//! MINIDUMP_STRING containing \a name.
//!
//! \note Valid in #kStateMutable.
void SetName(const std::string& name);
//! \brief Sets MINIDUMP_UNLOADED_MODULE::BaseOfImage.
void SetImageBaseAddress(uint64_t image_base_address) {
unloaded_module_.BaseOfImage = image_base_address;
}
//! \brief Sets MINIDUMP_UNLOADED_MODULE::SizeOfImage.
void SetImageSize(uint32_t image_size) {
unloaded_module_.SizeOfImage = image_size;
}
//! \brief Sets MINIDUMP_UNLOADED_MODULE::CheckSum.
void SetChecksum(uint32_t checksum) { unloaded_module_.CheckSum = checksum; }
//! \brief Sets MINIDUMP_UNLOADED_MODULE::TimeDateStamp.
//!
//! \note Valid in #kStateMutable.
void SetTimestamp(time_t timestamp);
protected:
// MinidumpWritable:
bool Freeze() override;
size_t SizeOfObject() override;
std::vector<MinidumpWritable*> Children() override;
bool WriteObject(FileWriterInterface* file_writer) override;
private:
MINIDUMP_UNLOADED_MODULE unloaded_module_;
scoped_ptr<internal::MinidumpUTF16StringWriter> name_;
DISALLOW_COPY_AND_ASSIGN(MinidumpUnloadedModuleWriter);
};
//! \brief The writer for a MINIDUMP_UNLOADED_MODULE_LIST stream in a minidump
//! file, containing a list of MINIDUMP_UNLOADED_MODULE objects.
class MinidumpUnloadedModuleListWriter final
: public internal::MinidumpStreamWriter {
public:
MinidumpUnloadedModuleListWriter();
~MinidumpUnloadedModuleListWriter() override;
//! \brief Adds an initialized MINIDUMP_UNLOADED_MODULE for each unloaded
//! module in \a unloaded_module_snapshots to the
//! MINIDUMP_UNLOADED_MODULE_LIST.
//!
//! \param[in] unloaded_module_snapshots The unloaded module snapshots to use
//! as source data.
//!
//! \note Valid in #kStateMutable. AddUnloadedModule() may not be called
//! before this this method, and it is not normally necessary to call
//! AddUnloadedModule() after this method.
void InitializeFromSnapshot(
const std::vector<UnloadedModuleSnapshot>& unloaded_module_snapshots);
//! \brief Adds a MinidumpUnloadedModuleWriter to the
//! MINIDUMP_UNLOADED_MODULE_LIST.
//!
//! This object takes ownership of \a unloaded_module and becomes its parent
//! in the overall tree of internal::MinidumpWritable objects.
//!
//! \note Valid in #kStateMutable.
void AddUnloadedModule(
scoped_ptr<MinidumpUnloadedModuleWriter> unloaded_module);
protected:
// MinidumpWritable:
bool Freeze() override;
size_t SizeOfObject() override;
std::vector<MinidumpWritable*> Children() override;
bool WriteObject(FileWriterInterface* file_writer) override;
// MinidumpStreamWriter:
MinidumpStreamType StreamType() const override;
private:
PointerVector<MinidumpUnloadedModuleWriter> unloaded_modules_;
MINIDUMP_UNLOADED_MODULE_LIST unloaded_module_list_base_;
DISALLOW_COPY_AND_ASSIGN(MinidumpUnloadedModuleListWriter);
};
} // namespace crashpad
#endif // CRASHPAD_MINIDUMP_MINIDUMP_UNLOADED_MODULE_WRITER_H_

View File

@ -0,0 +1,162 @@
// Copyright 2016 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_unloaded_module_writer.h"
#include "base/strings/utf_string_conversions.h"
#include "gtest/gtest.h"
#include "minidump/minidump_file_writer.h"
#include "minidump/test/minidump_file_writer_test_util.h"
#include "minidump/test/minidump_string_writer_test_util.h"
#include "minidump/test/minidump_writable_test_util.h"
#include "util/file/string_file.h"
namespace crashpad {
namespace test {
namespace {
void ExpectUnloadedModule(const MINIDUMP_UNLOADED_MODULE* expected,
const MINIDUMP_UNLOADED_MODULE* observed,
const std::string& file_contents,
const std::string& expected_module_name) {
EXPECT_EQ(expected->BaseOfImage, observed->BaseOfImage);
EXPECT_EQ(expected->SizeOfImage, observed->SizeOfImage);
EXPECT_EQ(expected->CheckSum, observed->CheckSum);
EXPECT_EQ(expected->TimeDateStamp, observed->TimeDateStamp);
EXPECT_NE(0u, observed->ModuleNameRva);
base::string16 observed_module_name_utf16 =
MinidumpStringAtRVAAsString(file_contents, observed->ModuleNameRva);
base::string16 expected_module_name_utf16 =
base::UTF8ToUTF16(expected_module_name);
EXPECT_EQ(expected_module_name_utf16, observed_module_name_utf16);
}
void GetUnloadedModuleListStream(
const std::string& file_contents,
const MINIDUMP_UNLOADED_MODULE_LIST** unloaded_module_list) {
const size_t kDirectoryOffset = sizeof(MINIDUMP_HEADER);
const size_t kUnloadedModuleListStreamOffset =
kDirectoryOffset + sizeof(MINIDUMP_DIRECTORY);
const size_t kUnloadedModulesOffset =
kUnloadedModuleListStreamOffset + sizeof(MINIDUMP_UNLOADED_MODULE_LIST);
ASSERT_GE(file_contents.size(), kUnloadedModulesOffset);
const MINIDUMP_DIRECTORY* directory;
const MINIDUMP_HEADER* header =
MinidumpHeaderAtStart(file_contents, &directory);
ASSERT_NO_FATAL_FAILURE(VerifyMinidumpHeader(header, 1, 0));
ASSERT_TRUE(directory);
ASSERT_EQ(kMinidumpStreamTypeUnloadedModuleList, directory[0].StreamType);
EXPECT_EQ(kUnloadedModuleListStreamOffset, directory[0].Location.Rva);
*unloaded_module_list =
MinidumpWritableAtLocationDescriptor<MINIDUMP_UNLOADED_MODULE_LIST>(
file_contents, directory[0].Location);
ASSERT_TRUE(unloaded_module_list);
}
TEST(MinidumpUnloadedModuleWriter, EmptyModule) {
MinidumpFileWriter minidump_file_writer;
auto unloaded_module_list_writer =
make_scoped_ptr(new MinidumpUnloadedModuleListWriter());
const char kModuleName[] = "test_dll";
auto unloaded_module_writer =
make_scoped_ptr(new MinidumpUnloadedModuleWriter());
unloaded_module_writer->SetName(kModuleName);
unloaded_module_list_writer->AddUnloadedModule(
std::move(unloaded_module_writer));
minidump_file_writer.AddStream(std::move(unloaded_module_list_writer));
StringFile string_file;
ASSERT_TRUE(minidump_file_writer.WriteEverything(&string_file));
ASSERT_GT(string_file.string().size(),
sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) +
sizeof(MINIDUMP_UNLOADED_MODULE_LIST) +
1 * sizeof(MINIDUMP_UNLOADED_MODULE));
const MINIDUMP_UNLOADED_MODULE_LIST* unloaded_module_list = nullptr;
ASSERT_NO_FATAL_FAILURE(
GetUnloadedModuleListStream(string_file.string(), &unloaded_module_list));
EXPECT_EQ(1u, unloaded_module_list->NumberOfEntries);
MINIDUMP_UNLOADED_MODULE expected = {};
ASSERT_NO_FATAL_FAILURE(
ExpectUnloadedModule(&expected,
reinterpret_cast<const MINIDUMP_UNLOADED_MODULE*>(
&unloaded_module_list[1]),
string_file.string(),
kModuleName));
}
TEST(MinidumpUnloadedModuleWriter, OneModule) {
MinidumpFileWriter minidump_file_writer;
auto unloaded_module_list_writer =
make_scoped_ptr(new MinidumpUnloadedModuleListWriter());
const char kModuleName[] = "statically_linked";
const uint64_t kModuleBase = 0x10da69000;
const uint32_t kModuleSize = 0x1000;
const uint32_t kChecksum = 0x76543210;
const time_t kTimestamp = 0x386d4380;
auto unloaded_module_writer =
make_scoped_ptr(new MinidumpUnloadedModuleWriter());
unloaded_module_writer->SetName(kModuleName);
unloaded_module_writer->SetImageBaseAddress(kModuleBase);
unloaded_module_writer->SetImageSize(kModuleSize);
unloaded_module_writer->SetChecksum(kChecksum);
unloaded_module_writer->SetTimestamp(kTimestamp);
unloaded_module_list_writer->AddUnloadedModule(
std::move(unloaded_module_writer));
minidump_file_writer.AddStream(std::move(unloaded_module_list_writer));
StringFile string_file;
ASSERT_TRUE(minidump_file_writer.WriteEverything(&string_file));
ASSERT_GT(string_file.string().size(),
sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) +
sizeof(MINIDUMP_UNLOADED_MODULE_LIST) +
1 * sizeof(MINIDUMP_UNLOADED_MODULE));
const MINIDUMP_UNLOADED_MODULE_LIST* unloaded_module_list = nullptr;
ASSERT_NO_FATAL_FAILURE(
GetUnloadedModuleListStream(string_file.string(), &unloaded_module_list));
EXPECT_EQ(1u, unloaded_module_list->NumberOfEntries);
MINIDUMP_UNLOADED_MODULE expected = {};
expected.BaseOfImage = kModuleBase;
expected.SizeOfImage = kModuleSize;
expected.CheckSum = kChecksum;
expected.TimeDateStamp = kTimestamp;
ASSERT_NO_FATAL_FAILURE(
ExpectUnloadedModule(&expected,
reinterpret_cast<const MINIDUMP_UNLOADED_MODULE*>(
&unloaded_module_list[1]),
string_file.string(),
kModuleName));
}
} // namespace
} // namespace test
} // namespace crashpad

View File

@ -173,6 +173,14 @@ struct MinidumpModuleListTraits {
}
};
struct MinidumpUnloadedModuleListTraits {
using ListType = MINIDUMP_UNLOADED_MODULE_LIST;
enum : size_t { kElementSize = sizeof(MINIDUMP_UNLOADED_MODULE) };
static size_t ElementCount(const ListType* list) {
return list->NumberOfEntries;
}
};
struct MinidumpThreadListTraits {
using ListType = MINIDUMP_THREAD_LIST;
enum : size_t { kElementSize = sizeof(MINIDUMP_THREAD) };
@ -252,6 +260,15 @@ const MINIDUMP_MODULE_LIST* MinidumpWritableAtLocationDescriptor<
file_contents, location);
}
template <>
const MINIDUMP_UNLOADED_MODULE_LIST*
MinidumpWritableAtLocationDescriptor<MINIDUMP_UNLOADED_MODULE_LIST>(
const std::string& file_contents,
const MINIDUMP_LOCATION_DESCRIPTOR& location) {
return MinidumpListAtLocationDescriptor<MinidumpUnloadedModuleListTraits>(
file_contents, location);
}
template <>
const MINIDUMP_THREAD_LIST* MinidumpWritableAtLocationDescriptor<
MINIDUMP_THREAD_LIST>(const std::string& file_contents,

View File

@ -89,6 +89,7 @@ MINIDUMP_ALLOW_OVERSIZED_DATA(MINIDUMP_DIRECTORY);
// variable-sized lists.
MINIDUMP_ALLOW_OVERSIZED_DATA(MINIDUMP_MEMORY_LIST);
MINIDUMP_ALLOW_OVERSIZED_DATA(MINIDUMP_MODULE_LIST);
MINIDUMP_ALLOW_OVERSIZED_DATA(MINIDUMP_UNLOADED_MODULE_LIST);
MINIDUMP_ALLOW_OVERSIZED_DATA(MINIDUMP_THREAD_LIST);
MINIDUMP_ALLOW_OVERSIZED_DATA(MINIDUMP_HANDLE_DATA_STREAM);
MINIDUMP_ALLOW_OVERSIZED_DATA(MINIDUMP_MEMORY_INFO_LIST);
@ -186,6 +187,12 @@ const MINIDUMP_MODULE_LIST* MinidumpWritableAtLocationDescriptor<
MINIDUMP_MODULE_LIST>(const std::string& file_contents,
const MINIDUMP_LOCATION_DESCRIPTOR& location);
template <>
const MINIDUMP_UNLOADED_MODULE_LIST*
MinidumpWritableAtLocationDescriptor<MINIDUMP_UNLOADED_MODULE_LIST>(
const std::string& file_contents,
const MINIDUMP_LOCATION_DESCRIPTOR& location);
template <>
const MINIDUMP_THREAD_LIST* MinidumpWritableAtLocationDescriptor<
MINIDUMP_THREAD_LIST>(const std::string& file_contents,

View File

@ -186,6 +186,12 @@ std::vector<const ModuleSnapshot*> ProcessSnapshotMac::Modules() const {
return modules;
}
std::vector<UnloadedModuleSnapshot> ProcessSnapshotMac::UnloadedModules()
const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return std::vector<UnloadedModuleSnapshot>();
}
const ExceptionSnapshot* ProcessSnapshotMac::Exception() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return exception_.get();

View File

@ -38,6 +38,7 @@
#include "snapshot/process_snapshot.h"
#include "snapshot/system_snapshot.h"
#include "snapshot/thread_snapshot.h"
#include "snapshot/unloaded_module_snapshot.h"
#include "util/mach/mach_extensions.h"
#include "util/misc/initialization_state_dcheck.h"
#include "util/misc/uuid.h"
@ -126,6 +127,7 @@ class ProcessSnapshotMac final : public ProcessSnapshot {
const SystemSnapshot* System() const override;
std::vector<const ThreadSnapshot*> Threads() const override;
std::vector<const ModuleSnapshot*> Modules() const override;
std::vector<UnloadedModuleSnapshot> UnloadedModules() const override;
const ExceptionSnapshot* Exception() const override;
std::vector<const MemoryMapRegionSnapshot*> MemoryMap() const override;
std::vector<HandleSnapshot> Handles() const override;

View File

@ -28,6 +28,7 @@ ProcessSnapshotMinidump::ProcessSnapshotMinidump()
stream_directory_(),
stream_map_(),
modules_(),
unloaded_modules_(),
crashpad_info_(),
annotations_simple_map_(),
file_reader_(nullptr),
@ -171,6 +172,13 @@ std::vector<const ModuleSnapshot*> ProcessSnapshotMinidump::Modules() const {
return modules;
}
std::vector<UnloadedModuleSnapshot> ProcessSnapshotMinidump::UnloadedModules()
const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
NOTREACHED(); // https://crashpad.chromium.org/bug/10
return unloaded_modules_;
}
const ExceptionSnapshot* ProcessSnapshotMinidump::Exception() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
NOTREACHED(); // https://crashpad.chromium.org/bug/10

View File

@ -33,6 +33,7 @@
#include "snapshot/process_snapshot.h"
#include "snapshot/system_snapshot.h"
#include "snapshot/thread_snapshot.h"
#include "snapshot/unloaded_module_snapshot.h"
#include "util/file/file_reader.h"
#include "util/misc/initialization_state_dcheck.h"
#include "util/misc/uuid.h"
@ -69,6 +70,7 @@ class ProcessSnapshotMinidump final : public ProcessSnapshot {
const SystemSnapshot* System() const override;
std::vector<const ThreadSnapshot*> Threads() const override;
std::vector<const ModuleSnapshot*> Modules() const override;
std::vector<UnloadedModuleSnapshot> UnloadedModules() const override;
const ExceptionSnapshot* Exception() const override;
std::vector<const MemoryMapRegionSnapshot*> MemoryMap() const override;
std::vector<HandleSnapshot> Handles() const override;
@ -94,6 +96,7 @@ class ProcessSnapshotMinidump final : public ProcessSnapshot {
std::vector<MINIDUMP_DIRECTORY> stream_directory_;
std::map<MinidumpStreamType, const MINIDUMP_LOCATION_DESCRIPTOR*> stream_map_;
PointerVector<internal::ModuleSnapshotMinidump> modules_;
std::vector<UnloadedModuleSnapshot> unloaded_modules_;
MinidumpCrashpadInfo crashpad_info_;
std::map<std::string, std::string> annotations_simple_map_;
FileReaderInterface* file_reader_; // weak

View File

@ -33,6 +33,7 @@ class MemorySnapshot;
class ModuleSnapshot;
class SystemSnapshot;
class ThreadSnapshot;
class UnloadedModuleSnapshot;
//! \brief An abstract interface to a snapshot representing the state of a
//! process.
@ -146,6 +147,12 @@ class ProcessSnapshot {
//! ProcessSnapshot object that they were obtained from.
virtual std::vector<const ModuleSnapshot*> Modules() const = 0;
//! \brief Returns UnloadedModuleSnapshot objects reflecting the code modules
//! the were recorded as unloaded at the time of the snapshot.
//!
//! \return A vector of UnloadedModuleSnapshot objects.
virtual std::vector<UnloadedModuleSnapshot> UnloadedModules() const = 0;
//! \brief Returns ThreadSnapshot objects reflecting the threads (lightweight
//! processes) existing in the snapshot process at the time of the
//! snapshot.

View File

@ -91,6 +91,8 @@
'process_snapshot.h',
'system_snapshot.h',
'thread_snapshot.h',
'unloaded_module_snapshot.cc',
'unloaded_module_snapshot.h',
'win/cpu_context_win.cc',
'win/cpu_context_win.h',
'win/exception_snapshot_win.cc',

View File

@ -96,6 +96,11 @@ std::vector<const ModuleSnapshot*> TestProcessSnapshot::Modules() const {
return modules;
}
std::vector<UnloadedModuleSnapshot> TestProcessSnapshot::UnloadedModules()
const {
return unloaded_modules_;
}
const ExceptionSnapshot* TestProcessSnapshot::Exception() const {
return exception_.get();
}

View File

@ -33,6 +33,7 @@
#include "snapshot/process_snapshot.h"
#include "snapshot/system_snapshot.h"
#include "snapshot/thread_snapshot.h"
#include "snapshot/unloaded_module_snapshot.h"
#include "util/misc/uuid.h"
#include "util/stdlib/pointer_container.h"
@ -92,6 +93,15 @@ class TestProcessSnapshot final : public ProcessSnapshot {
modules_.push_back(module.release());
}
//! \brief Adds an unloaded module snapshot to be returned by
//! UnloadedModules().
//!
//! \param[in] unloaded_module The unloaded module snapshot that will be
//! included in UnloadedModules().
void AddModule(const UnloadedModuleSnapshot& unloaded_module) {
unloaded_modules_.push_back(unloaded_module);
}
//! \brief Sets the exception snapshot to be returned by Exception().
//!
//! \param[in] exception The exception snapshot that Exception() will return.
@ -139,6 +149,7 @@ class TestProcessSnapshot final : public ProcessSnapshot {
const SystemSnapshot* System() const override;
std::vector<const ThreadSnapshot*> Threads() const override;
std::vector<const ModuleSnapshot*> Modules() const override;
std::vector<UnloadedModuleSnapshot> UnloadedModules() const override;
const ExceptionSnapshot* Exception() const override;
std::vector<const MemoryMapRegionSnapshot*> MemoryMap() const override;
std::vector<HandleSnapshot> Handles() const override;
@ -157,6 +168,7 @@ class TestProcessSnapshot final : public ProcessSnapshot {
scoped_ptr<SystemSnapshot> system_;
PointerVector<ThreadSnapshot> threads_;
PointerVector<ModuleSnapshot> modules_;
std::vector<UnloadedModuleSnapshot> unloaded_modules_;
scoped_ptr<ExceptionSnapshot> exception_;
PointerVector<MemoryMapRegionSnapshot> memory_map_;
std::vector<HandleSnapshot> handles_;

View File

@ -0,0 +1,33 @@
// Copyright 2016 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/unloaded_module_snapshot.h"
namespace crashpad {
UnloadedModuleSnapshot::UnloadedModuleSnapshot(uint64_t address,
uint64_t size,
uint32_t checksum,
uint32_t timestamp,
const std::string& name)
: name_(name),
address_(address),
size_(size),
checksum_(checksum),
timestamp_(timestamp) {}
UnloadedModuleSnapshot::~UnloadedModuleSnapshot() {
}
} // namespace crashpad

View File

@ -0,0 +1,61 @@
// Copyright 2016 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_UNLOADED_MODULE_SNAPSHOT_H_
#define CRASHPAD_SNAPSHOT_UNLOADED_MODULE_SNAPSHOT_H_
#include <stdint.h>
#include <string>
namespace crashpad {
//! \brief Information about an unloaded module that was previously loaded into
//! a snapshot process.
class UnloadedModuleSnapshot {
public:
UnloadedModuleSnapshot(uint64_t address,
uint64_t size,
uint32_t checksum,
uint32_t timestamp,
const std::string& name);
~UnloadedModuleSnapshot();
//! \brief The base address of the module in the target processes' address
//! space.
uint64_t Address() const { return address_; }
//! \brief The size of the module.
uint64_t Size() const { return size_; }
//! \brief The checksum of the image.
uint32_t Checksum() const { return checksum_; }
//! \brief The time and date stamp in `time_t` format.
uint32_t Timestamp() const { return timestamp_; }
//! \brief The name of the module.
std::string Name() const { return name_; }
private:
std::string name_;
uint64_t address_;
uint64_t size_;
uint32_t checksum_;
uint32_t timestamp_;
};
} // namespace crashpad
#endif // CRASHPAD_SNAPSHOT_UNLOADED_MODULE_SNAPSHOT_H_

View File

@ -235,6 +235,11 @@ def RunTests(cdb_path,
out.Check(r'Event\s+\d+', 'capture some event handles')
out.Check(r'File\s+\d+', 'capture some file handles')
out = CdbRun(cdb_path, dump_path, 'lm')
out.Check(r'Unloaded modules:', 'captured some unloaded modules')
out.Check(r'lz32\.dll', 'found expected unloaded module lz32')
out.Check(r'wmerror\.dll', 'found expected unloaded module wmerror')
out = CdbRun(cdb_path, destroyed_dump_path, '.ecxr;!peb;k 2')
out.Check(r'Ldr\.InMemoryOrderModuleList:.*\d+ \. \d+', 'PEB_LDR_DATA saved')
out.Check(r'ntdll\.dll', 'ntdll present', re.IGNORECASE)

View File

@ -21,6 +21,7 @@
#include "base/strings/utf_string_conversions.h"
#include "snapshot/win/memory_snapshot_win.h"
#include "snapshot/win/module_snapshot_win.h"
#include "util/win/nt_internals.h"
#include "util/win/registration_protocol_win.h"
#include "util/win/time.h"
@ -67,6 +68,7 @@ bool ProcessSnapshotWin::Initialize(
}
InitializeModules();
InitializeUnloadedModules();
GetCrashpadOptionsInternal(&options_);
@ -177,6 +179,12 @@ std::vector<const ModuleSnapshot*> ProcessSnapshotWin::Modules() const {
return modules;
}
std::vector<UnloadedModuleSnapshot> ProcessSnapshotWin::UnloadedModules()
const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return unloaded_modules_;
}
const ExceptionSnapshot* ProcessSnapshotWin::Exception() const {
return exception_.get();
}
@ -242,6 +250,53 @@ void ProcessSnapshotWin::InitializeModules() {
}
}
void ProcessSnapshotWin::InitializeUnloadedModules() {
// As documented by https://msdn.microsoft.com/en-us/library/cc678403.aspx
// we can retrieve the location for our unload events, and use that address in
// the target process. Unfortunately, this of course only works for
// 64-reading-64 and 32-reading-32, so at the moment, we simply do not
// retrieve unloaded modules for 64-reading-32. See
// https://crashpad.chromium.org/bug/89.
#if defined(ARCH_CPU_X86_64)
if (!process_reader_.Is64Bit()) {
LOG(ERROR)
<< "reading unloaded modules across bitness not currently supported";
return;
}
using Traits = process_types::internal::Traits64;
#elif defined(ARCH_CPU_X86)
using Traits = process_types::internal::Traits32;
#else
#error port
#endif
RTL_UNLOAD_EVENT_TRACE<Traits>* unload_event_trace_address =
RtlGetUnloadEventTrace<Traits>();
WinVMAddress address_in_target_process =
reinterpret_cast<WinVMAddress>(unload_event_trace_address);
std::vector<RTL_UNLOAD_EVENT_TRACE<Traits>> events(
RTL_UNLOAD_EVENT_TRACE_NUMBER);
if (!process_reader_.ReadMemory(address_in_target_process,
events.size() * sizeof(events[0]),
&events[0])) {
return;
}
for (const RTL_UNLOAD_EVENT_TRACE<Traits>& uet : events) {
if (uet.ImageName[0]) {
unloaded_modules_.push_back(UnloadedModuleSnapshot(
uet.BaseAddress,
uet.SizeOfImage,
uet.CheckSum,
uet.TimeDateStamp,
base::UTF16ToUTF8(
base::StringPiece16(uet.ImageName, arraysize(uet.ImageName)))));
}
}
}
void ProcessSnapshotWin::GetCrashpadOptionsInternal(
CrashpadInfoClientOptions* options) {
CrashpadInfoClientOptions local_options;

View File

@ -34,6 +34,7 @@
#include "snapshot/process_snapshot.h"
#include "snapshot/system_snapshot.h"
#include "snapshot/thread_snapshot.h"
#include "snapshot/unloaded_module_snapshot.h"
#include "snapshot/win/exception_snapshot_win.h"
#include "snapshot/win/memory_map_region_snapshot_win.h"
#include "snapshot/win/memory_snapshot_win.h"
@ -134,6 +135,7 @@ class ProcessSnapshotWin final : public ProcessSnapshot {
const SystemSnapshot* System() const override;
std::vector<const ThreadSnapshot*> Threads() const override;
std::vector<const ModuleSnapshot*> Modules() const override;
std::vector<UnloadedModuleSnapshot> UnloadedModules() const override;
const ExceptionSnapshot* Exception() const override;
std::vector<const MemoryMapRegionSnapshot*> MemoryMap() const override;
std::vector<HandleSnapshot> Handles() const override;
@ -146,6 +148,9 @@ class ProcessSnapshotWin final : public ProcessSnapshot {
// Initializes modules_ on behalf of Initialize().
void InitializeModules();
// Initializes unloaded_modules_ on behalf of Initialize().
void InitializeUnloadedModules();
// Initializes options_ on behalf of Initialize().
void GetCrashpadOptionsInternal(CrashpadInfoClientOptions* options);
@ -182,6 +187,7 @@ class ProcessSnapshotWin final : public ProcessSnapshot {
PointerVector<internal::MemorySnapshotWin> extra_memory_;
PointerVector<internal::ThreadSnapshotWin> threads_;
PointerVector<internal::ModuleSnapshotWin> modules_;
std::vector<UnloadedModuleSnapshot> unloaded_modules_;
scoped_ptr<internal::ExceptionSnapshotWin> exception_;
PointerVector<internal::MemoryMapRegionSnapshotWin> memory_map_;
ProcessReaderWin process_reader_;

View File

@ -26,6 +26,8 @@ NTSTATUS NTAPI NtOpenThread(HANDLE* ThreadHandle,
OBJECT_ATTRIBUTES* ObjectAttributes,
CLIENT_ID* ClientId);
void* NTAPI RtlGetUnloadEventTrace();
namespace crashpad {
NTSTATUS NtQuerySystemInformation(
@ -83,6 +85,14 @@ NTSTATUS NtQueryObject(HANDLE handle,
return_length);
}
template <class Traits>
RTL_UNLOAD_EVENT_TRACE<Traits>* RtlGetUnloadEventTrace() {
static const auto rtl_get_unload_event_trace =
GET_FUNCTION_REQUIRED(L"ntdll.dll", ::RtlGetUnloadEventTrace);
return reinterpret_cast<RTL_UNLOAD_EVENT_TRACE<Traits>*>(
rtl_get_unload_event_trace());
}
// Explicit instantiations with the only 2 valid template arguments to avoid
// putting the body of the function in the header.
template NTSTATUS NtOpenThread<process_types::internal::Traits32>(
@ -99,4 +109,10 @@ template NTSTATUS NtOpenThread<process_types::internal::Traits64>(
const process_types::CLIENT_ID<process_types::internal::Traits64>*
client_id);
template RTL_UNLOAD_EVENT_TRACE<process_types::internal::Traits32>*
RtlGetUnloadEventTrace<process_types::internal::Traits32>();
template RTL_UNLOAD_EVENT_TRACE<process_types::internal::Traits64>*
RtlGetUnloadEventTrace<process_types::internal::Traits64>();
} // namespace crashpad

View File

@ -54,4 +54,26 @@ NTSTATUS NtQueryObject(HANDLE handle,
ULONG object_information_length,
ULONG* return_length);
// From https://msdn.microsoft.com/en-us/library/bb432428(VS.85).aspx and
// http://processhacker.sourceforge.net/doc/struct___r_t_l___u_n_l_o_a_d___e_v_e_n_t___t_r_a_c_e.html
#define RTL_UNLOAD_EVENT_TRACE_NUMBER 64
template <class Traits>
struct RTL_UNLOAD_EVENT_TRACE {
typename Traits::Pointer BaseAddress;
typename Traits::UnsignedIntegral SizeOfImage;
ULONG Sequence;
ULONG TimeDateStamp;
ULONG CheckSum;
WCHAR ImageName[32];
ULONG Version0;
union {
ULONG Version1;
typename Traits::Pad alignment_for_x64;
};
};
template <class Traits>
RTL_UNLOAD_EVENT_TRACE<Traits>* RtlGetUnloadEventTrace();
} // namespace crashpad