From 8cf0a52becf937650056dad55769a2d85d30f75d Mon Sep 17 00:00:00 2001 From: "hansl@google.com" Date: Wed, 12 May 2010 17:51:21 +0000 Subject: [PATCH] Moved exception_handler_test to the more aptly named exception_handler_death_test. It doesn't test anything else than death and exit. Created the exception_handler_test that test the generation of dump and the dumps themselves. Moved all dump analysis code from minidump to its right class DumpAnalysis. The class is used by both minidump_test and exception_handler_test. The tests are way simpler that way (ie. no handling of HANDLE). minidump_test now uses the minidump_generator class instead of using Win32. It works well and pass all tests. exception_handler now passes both the exception and assertion infos to the client to generate the dump. If one is NULL it's going to be handled correctly. crash_generation_client can now RequestDump with both exception and assertion info. minidump_generator returns both the mini and full dump string pointers, and output both (or either) depending on which was generated. All original interfaces and method signature are still there, but call the new functions if possible. Review URL: http://codereview.chromium.org/1994015 git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@596 4c0a9323-5329-0410-9bdc-e9ce6186880e --- .../crash_generation_client.cc | 29 +- .../crash_generation_client.h | 9 +- .../crash_generation/minidump_generator.cc | 22 +- .../crash_generation/minidump_generator.h | 20 +- .../windows/handler/exception_handler.cc | 29 +- src/client/windows/unittests/client_tests.gyp | 4 + src/client/windows/unittests/dump_analysis.cc | 184 ++++++++ src/client/windows/unittests/dump_analysis.h | 102 +++++ .../unittests/exception_handler_death_test.cc | 181 ++++++++ .../unittests/exception_handler_test.cc | 176 ++++--- src/client/windows/unittests/minidump_test.cc | 428 ++++++------------ 11 files changed, 789 insertions(+), 395 deletions(-) create mode 100755 src/client/windows/unittests/dump_analysis.cc create mode 100755 src/client/windows/unittests/dump_analysis.h create mode 100755 src/client/windows/unittests/exception_handler_death_test.cc diff --git a/src/client/windows/crash_generation/crash_generation_client.cc b/src/client/windows/crash_generation/crash_generation_client.cc index 197807ab..5e4e3cb9 100644 --- a/src/client/windows/crash_generation/crash_generation_client.cc +++ b/src/client/windows/crash_generation/crash_generation_client.cc @@ -271,7 +271,8 @@ bool CrashGenerationClient::IsRegistered() const { return crash_event_ != NULL; } -bool CrashGenerationClient::RequestDump(EXCEPTION_POINTERS* ex_info) { +bool CrashGenerationClient::RequestDump(EXCEPTION_POINTERS* ex_info, + MDRawAssertionInfo* assert_info) { if (!IsRegistered()) { return false; } @@ -279,33 +280,23 @@ bool CrashGenerationClient::RequestDump(EXCEPTION_POINTERS* ex_info) { exception_pointers_ = ex_info; thread_id_ = GetCurrentThreadId(); - assert_info_.line = 0; - assert_info_.type = 0; - assert_info_.expression[0] = 0; - assert_info_.file[0] = 0; - assert_info_.function[0] = 0; - - return SignalCrashEventAndWait(); -} - -bool CrashGenerationClient::RequestDump(MDRawAssertionInfo* assert_info) { - if (!IsRegistered()) { - return false; - } - - exception_pointers_ = NULL; - if (assert_info) { memcpy(&assert_info_, assert_info, sizeof(assert_info_)); } else { memset(&assert_info_, 0, sizeof(assert_info_)); } - thread_id_ = GetCurrentThreadId(); - return SignalCrashEventAndWait(); } +bool CrashGenerationClient::RequestDump(EXCEPTION_POINTERS* ex_info) { + return RequestDump(ex_info, NULL); +} + +bool CrashGenerationClient::RequestDump(MDRawAssertionInfo* assert_info) { + return RequestDump(NULL, assert_info); +} + bool CrashGenerationClient::SignalCrashEventAndWait() { assert(crash_event_); assert(crash_generated_); diff --git a/src/client/windows/crash_generation/crash_generation_client.h b/src/client/windows/crash_generation/crash_generation_client.h index 81b0e6ca..01d13dde 100644 --- a/src/client/windows/crash_generation/crash_generation_client.h +++ b/src/client/windows/crash_generation/crash_generation_client.h @@ -27,8 +27,8 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#ifndef CLIENT_WINDOWS_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H__ -#define CLIENT_WINDOWS_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H__ +#ifndef CLIENT_WINDOWS_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_ +#define CLIENT_WINDOWS_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_ #include #include @@ -73,6 +73,9 @@ class CrashGenerationClient { // Returns true if the registration is successful; false otherwise. bool Register(); + bool RequestDump(EXCEPTION_POINTERS* ex_info, + MDRawAssertionInfo* assert_info); + // Requests the crash server to generate a dump with the given // exception information. // @@ -156,4 +159,4 @@ class CrashGenerationClient { } // namespace google_breakpad -#endif // CLIENT_WINDOWS_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H__ +#endif // CLIENT_WINDOWS_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_ diff --git a/src/client/windows/crash_generation/minidump_generator.cc b/src/client/windows/crash_generation/minidump_generator.cc index c03b191a..37bd55e4 100644 --- a/src/client/windows/crash_generation/minidump_generator.cc +++ b/src/client/windows/crash_generation/minidump_generator.cc @@ -68,6 +68,23 @@ bool MinidumpGenerator::WriteMinidump(HANDLE process_handle, MINIDUMP_TYPE dump_type, bool is_client_pointers, wstring* dump_path) { + // Just call the full WriteMinidump with NULL as the full_dump_path. + return this->WriteMinidump(process_handle, process_id, thread_id, + requesting_thread_id, exception_pointers, + assert_info, dump_type, is_client_pointers, + dump_path, NULL); +} + +bool MinidumpGenerator::WriteMinidump(HANDLE process_handle, + DWORD process_id, + DWORD thread_id, + DWORD requesting_thread_id, + EXCEPTION_POINTERS* exception_pointers, + MDRawAssertionInfo* assert_info, + MINIDUMP_TYPE dump_type, + bool is_client_pointers, + wstring* dump_path, + wstring* full_dump_path) { MiniDumpWriteDumpType write_dump = GetWriteDump(); if (!write_dump) { return false; @@ -223,6 +240,9 @@ bool MinidumpGenerator::WriteMinidump(HANDLE process_handle, if (result && dump_path) { *dump_path = dump_file_path; } + if (result && full_memory_dump && full_dump_path) { + *full_dump_path = full_dump_file_path; + } return result; } @@ -275,7 +295,7 @@ bool MinidumpGenerator::GenerateDumpFilePath(wstring* file_path) { UUID id = {0}; UuidCreateType create_uuid = GetCreateUuid(); - if(!create_uuid) { + if (!create_uuid) { return false; } diff --git a/src/client/windows/crash_generation/minidump_generator.h b/src/client/windows/crash_generation/minidump_generator.h index 8ab6a8f6..5f9e4b54 100644 --- a/src/client/windows/crash_generation/minidump_generator.h +++ b/src/client/windows/crash_generation/minidump_generator.h @@ -27,8 +27,8 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#ifndef CLIENT_WINDOWS_CRASH_GENERATION_MINIDUMP_GENERATION_H__ -#define CLIENT_WINDOWS_CRASH_GENERATION_MINIDUMP_GENERATION_H__ +#ifndef CLIENT_WINDOWS_CRASH_GENERATION_MINIDUMP_GENERATOR_H_ +#define CLIENT_WINDOWS_CRASH_GENERATION_MINIDUMP_GENERATOR_H_ #include #include @@ -61,6 +61,20 @@ class MinidumpGenerator { bool is_client_pointers, std::wstring* dump_path); + // Writes the minidump with the given parameters. Stores the dump file + // path in the dump_path (and full_dump_path) parameter if dump + // generation succeeds. full_dump_path and dump_path can be NULL. + bool WriteMinidump(HANDLE process_handle, + DWORD process_id, + DWORD thread_id, + DWORD requesting_thread_id, + EXCEPTION_POINTERS* exception_pointers, + MDRawAssertionInfo* assert_info, + MINIDUMP_TYPE dump_type, + bool is_client_pointers, + std::wstring* dump_path, + std::wstring* full_dump_path); + private: // Function pointer type for MiniDumpWriteDump, which is looked up // dynamically. @@ -118,4 +132,4 @@ class MinidumpGenerator { } // namespace google_breakpad -#endif // CLIENT_WINDOWS_CRASH_GENERATION_MINIDUMP_GENERATION_H__ +#endif // CLIENT_WINDOWS_CRASH_GENERATION_MINIDUMP_GENERATOR_H_ diff --git a/src/client/windows/handler/exception_handler.cc b/src/client/windows/handler/exception_handler.cc index 7bccc428..a877b2f1 100644 --- a/src/client/windows/handler/exception_handler.cc +++ b/src/client/windows/handler/exception_handler.cc @@ -490,12 +490,12 @@ void ExceptionHandler::HandleInvalidParameter(const wchar_t* expression, EXCEPTION_POINTERS exception_ptrs = { &exception_record, &exception_context }; RtlCaptureContext(&exception_context); exception_record.ExceptionCode = STATUS_NONCONTINUABLE_EXCEPTION; - + // We store pointers to the the expression and function strings, - // and the line as exception parameters to make them easy to + // and the line as exception parameters to make them easy to // access by the developer on the far side. exception_record.NumberParameters = 3; - exception_record.ExceptionInformation[0] = + exception_record.ExceptionInformation[0] = reinterpret_cast(&assertion.expression); exception_record.ExceptionInformation[1] = reinterpret_cast(&assertion.file); @@ -510,7 +510,7 @@ void ExceptionHandler::HandleInvalidParameter(const wchar_t* expression, &exception_ptrs, &assertion); } else { - success = current_handler->WriteMinidumpOnHandlerThread(&exception_ptrs, + success = current_handler->WriteMinidumpOnHandlerThread(&exception_ptrs, &assertion); } @@ -668,13 +668,7 @@ bool ExceptionHandler::WriteMinidumpWithException( bool success = false; if (IsOutOfProcess()) { - // Use the EXCEPTION_POINTERS overload for RequestDump if - // both exinfo and assertion are NULL. - if (!assertion) { - success = crash_generation_client_->RequestDump(exinfo); - } else { - success = crash_generation_client_->RequestDump(assertion); - } + success = crash_generation_client_->RequestDump(exinfo, assertion); } else { if (minidump_write_dump_) { HANDLE dump_file = CreateFile(next_minidump_path_c_, @@ -690,12 +684,13 @@ bool ExceptionHandler::WriteMinidumpWithException( except_info.ExceptionPointers = exinfo; except_info.ClientPointers = FALSE; - // Add an MDRawBreakpadInfo stream to the minidump, to provide additional - // information about the exception handler to the Breakpad processor. The - // information will help the processor determine which threads are - // relevant. The Breakpad processor does not require this information but - // can function better with Breakpad-generated dumps when it is present. - // The native debugger is not harmed by the presence of this information. + // Add an MDRawBreakpadInfo stream to the minidump, to provide + // additional information about the exception handler to the Breakpad + // processor. The information will help the processor determine which + // threads are relevant. The Breakpad processor does not require this + // information but can function better with Breakpad-generated dumps + // when it is present. The native debugger is not harmed by the + // presence of this information. MDRawBreakpadInfo breakpad_info; breakpad_info.validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID | MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID; diff --git a/src/client/windows/unittests/client_tests.gyp b/src/client/windows/unittests/client_tests.gyp index 2238535b..027ed62b 100755 --- a/src/client/windows/unittests/client_tests.gyp +++ b/src/client/windows/unittests/client_tests.gyp @@ -37,11 +37,15 @@ 'type': 'executable', 'sources': [ 'exception_handler_test.cc', + 'exception_handler_death_test.cc', 'minidump_test.cc', + 'dump_analysis.cc', + 'dump_analysis.h', ], 'dependencies': [ 'gtest.gyp:gtest', '../breakpad_client.gyp:common', + '../crash_generation/crash_generation.gyp:crash_generation_server', '../crash_generation/crash_generation.gyp:crash_generation_client', '../handler/exception_handler.gyp:exception_handler', ] diff --git a/src/client/windows/unittests/dump_analysis.cc b/src/client/windows/unittests/dump_analysis.cc new file mode 100755 index 00000000..936b27fd --- /dev/null +++ b/src/client/windows/unittests/dump_analysis.cc @@ -0,0 +1,184 @@ +// Copyright (c) 2010, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include + +#include "dump_analysis.h" // NOLINT +#include "gtest/gtest.h" + +DumpAnalysis::~DumpAnalysis() { + if (dump_file_view_ != NULL) { + EXPECT_TRUE(::UnmapViewOfFile(dump_file_view_)); + ::CloseHandle(dump_file_mapping_); + dump_file_mapping_ = NULL; + } + + if (dump_file_handle_ != NULL) { + ::CloseHandle(dump_file_handle_); + dump_file_handle_ = NULL; + } +} + +void DumpAnalysis::EnsureDumpMapped() { + if (dump_file_view_ == NULL) { + dump_file_handle_ = ::CreateFile(dump_file_.c_str(), + GENERIC_READ, + 0, + NULL, + OPEN_EXISTING, + 0, + NULL); + ASSERT_TRUE(dump_file_handle_ != NULL); + ASSERT_TRUE(dump_file_mapping_ == NULL); + + dump_file_mapping_ = ::CreateFileMapping(dump_file_handle_, + NULL, + PAGE_READONLY, + 0, + 0, + NULL); + ASSERT_TRUE(dump_file_mapping_ != NULL); + + dump_file_view_ = ::MapViewOfFile(dump_file_mapping_, + FILE_MAP_READ, + 0, + 0, + 0); + ASSERT_TRUE(dump_file_view_ != NULL); + } +} + +bool DumpAnalysis::HasTebs() const { + MINIDUMP_THREAD_LIST* thread_list = NULL; + size_t thread_list_size = GetStream(ThreadListStream, &thread_list); + + if (thread_list_size > 0 && thread_list != NULL) { + for (ULONG i = 0; i < thread_list->NumberOfThreads; ++i) { + if (!HasMemory(thread_list->Threads[i].Teb)) + return false; + } + + return true; + } + + // No thread list, no TEB info. + return false; +} + +bool DumpAnalysis::HasPeb() const { + MINIDUMP_THREAD_LIST* thread_list = NULL; + size_t thread_list_size = GetStream(ThreadListStream, &thread_list); + + if (thread_list_size > 0 && thread_list != NULL && + thread_list->NumberOfThreads > 0) { + FakeTEB* teb = NULL; + if (!HasMemory(thread_list->Threads[0].Teb, &teb)) + return false; + + return HasMemory(teb->peb); + } + + return false; +} + +bool DumpAnalysis::HasStream(ULONG stream_number) const { + void* stream = NULL; + size_t stream_size = GetStreamImpl(stream_number, &stream); + return stream_size > 0 && stream != NULL; +} + +size_t DumpAnalysis::GetStreamImpl(ULONG stream_number, void** stream) const { + MINIDUMP_DIRECTORY* directory = NULL; + ULONG memory_list_size = 0; + BOOL ret = ::MiniDumpReadDumpStream(dump_file_view_, + stream_number, + &directory, + stream, + &memory_list_size); + + return ret ? memory_list_size : 0; +} + +bool DumpAnalysis::HasMemoryImpl(const void *addr_in, size_t structuresize, + void **structure) const { + uintptr_t address = reinterpret_cast(addr_in); + MINIDUMP_MEMORY_LIST* memory_list = NULL; + size_t memory_list_size = GetStream(MemoryListStream, &memory_list); + if (memory_list_size > 0 && memory_list != NULL) { + for (ULONG i = 0; i < memory_list->NumberOfMemoryRanges; ++i) { + MINIDUMP_MEMORY_DESCRIPTOR& descr = memory_list->MemoryRanges[i]; + const uintptr_t range_start = + static_cast(descr.StartOfMemoryRange); + uintptr_t range_end = range_start + descr.Memory.DataSize; + + if (address >= range_start && + address + structuresize < range_end) { + // The start address falls in the range, and the end address is + // in bounds, return a pointer to the structure if requested. + if (structure != NULL) + *structure = RVA_TO_ADDR(dump_file_view_, descr.Memory.Rva); + + return true; + } + } + } + + // We didn't find the range in a MINIDUMP_MEMORY_LIST, so maybe this + // is a full dump using MINIDUMP_MEMORY64_LIST with all the memory at the + // end of the dump file. + MINIDUMP_MEMORY64_LIST* memory64_list = NULL; + memory_list_size = GetStream(Memory64ListStream, &memory64_list); + if (memory_list_size > 0 && memory64_list != NULL) { + // Keep track of where the current descriptor maps to. + RVA64 curr_rva = memory64_list->BaseRva; + for (ULONG i = 0; i < memory64_list->NumberOfMemoryRanges; ++i) { + MINIDUMP_MEMORY_DESCRIPTOR64& descr = memory64_list->MemoryRanges[i]; + uintptr_t range_start = + static_cast(descr.StartOfMemoryRange); + uintptr_t range_end = range_start + static_cast(descr.DataSize); + + if (address >= range_start && + address + structuresize < range_end) { + // The start address falls in the range, and the end address is + // in bounds, return a pointer to the structure if requested. + if (structure != NULL) + *structure = RVA_TO_ADDR(dump_file_view_, curr_rva); + + return true; + } + + // Advance the current RVA. + curr_rva += descr.DataSize; + } + } + + return false; +} diff --git a/src/client/windows/unittests/dump_analysis.h b/src/client/windows/unittests/dump_analysis.h new file mode 100755 index 00000000..0392f5d9 --- /dev/null +++ b/src/client/windows/unittests/dump_analysis.h @@ -0,0 +1,102 @@ +// Copyright (c) 2010, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CLIENT_WINDOWS_UNITTESTS_DUMP_ANALYSIS_H_ +#define CLIENT_WINDOWS_UNITTESTS_DUMP_ANALYSIS_H_ + +#include "../crash_generation/minidump_generator.h" + +// Convenience to get to the PEB pointer in a TEB. +struct FakeTEB { + char dummy[0x30]; + void* peb; +}; + +class DumpAnalysis { + public: + explicit DumpAnalysis(const std::wstring& file_path) + : dump_file_(file_path), dump_file_view_(NULL), dump_file_mapping_(NULL), + dump_file_handle_(NULL) { + EnsureDumpMapped(); + } + ~DumpAnalysis(); + + bool HasStream(ULONG stream_number) const; + + // This is template to keep type safety in the front, but we end up casting + // to void** inside the implementation to pass the pointer to Win32. So + // casting here is considered safe. + template + size_t GetStream(ULONG stream_number, StreamType** stream) const { + return GetStreamImpl(stream_number, reinterpret_cast(stream)); + } + + bool HasTebs() const; + bool HasPeb() const; + bool HasMemory(ULONG64 address) const { + return HasMemory(address, NULL); + } + + bool HasMemory(const void* address) const { + return HasMemory(address, NULL); + } + + template + bool HasMemory(ULONG64 address, StructureType** structure = NULL) const { + // We can't cope with 64 bit addresses for now. + if (address > 0xFFFFFFFFUL) + return false; + + return HasMemory(reinterpret_cast(address), structure); + } + + template + bool HasMemory(const void* addr_in, StructureType** structure = NULL) const { + return HasMemoryImpl(addr_in, sizeof(StructureType), + reinterpret_cast(structure)); + } + + protected: + void EnsureDumpMapped(); + + HANDLE dump_file_mapping_; + HANDLE dump_file_handle_; + void* dump_file_view_; + std::wstring dump_file_; + + private: + // This is the implementation of GetStream<>. + size_t GetStreamImpl(ULONG stream_number, void** stream) const; + + // This is the implementation of HasMemory<>. + bool HasMemoryImpl(const void* addr_in, size_t pointersize, + void** structure) const; +}; + +#endif // CLIENT_WINDOWS_UNITTESTS_DUMP_ANALYSIS_H_ diff --git a/src/client/windows/unittests/exception_handler_death_test.cc b/src/client/windows/unittests/exception_handler_death_test.cc new file mode 100755 index 00000000..a03ad011 --- /dev/null +++ b/src/client/windows/unittests/exception_handler_death_test.cc @@ -0,0 +1,181 @@ +// Copyright 2009, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include + +#include "../../../breakpad_googletest_includes.h" +#include "../crash_generation/crash_generation_server.h" +#include "../handler/exception_handler.h" + +namespace { +const wchar_t kPipeName[] = L"\\\\.\\pipe\\BreakpadCrashTest\\TestCaseServer"; +const char kSuccessIndicator[] = "success"; +const char kFailureIndicator[] = "failure"; + +// Utility function to test for a path's existence. +BOOL DoesPathExist(const TCHAR *path_name); + +class ExceptionHandlerDeathTest : public ::testing::Test { + protected: + // Member variable for each test that they can use + // for temporary storage. + TCHAR temp_path_[MAX_PATH]; + // Actually constructs a temp path name. + virtual void SetUp(); + // A helper method that tests can use to crash. + void DoCrash(); +}; + +void ExceptionHandlerDeathTest::SetUp() { + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + TCHAR temp_path[MAX_PATH] = { '\0' }; + TCHAR test_name_wide[MAX_PATH] = { '\0' }; + // We want the temporary directory to be what the OS returns + // to us, + the test case name. + GetTempPath(MAX_PATH, temp_path); + // THe test case name is exposed to use as a c-style string, + // But we might be working in UNICODE here on Windows. + int dwRet = MultiByteToWideChar(CP_ACP, 0, test_info->name(), + strlen(test_info->name()), + test_name_wide, + MAX_PATH); + if (!dwRet) { + assert(false); + } + StringCchPrintfW(temp_path_, MAX_PATH, L"%s%s", temp_path, test_name_wide); + CreateDirectory(temp_path_, NULL); +} + +BOOL DoesPathExist(const TCHAR *path_name) { + DWORD flags = GetFileAttributes(path_name); + if (flags == INVALID_FILE_ATTRIBUTES) { + return FALSE; + } + return TRUE; +} + +bool MinidumpWrittenCallback(const wchar_t* dump_path, + const wchar_t* minidump_id, + void* context, + EXCEPTION_POINTERS* exinfo, + MDRawAssertionInfo* assertion, + bool succeeded) { + if (succeeded && DoesPathExist(dump_path)) { + fprintf(stderr, kSuccessIndicator); + } else { + fprintf(stderr, kFailureIndicator); + } + // If we don't flush, the output doesn't get sent before + // this process dies. + fflush(stderr); + return succeeded; +} + +TEST_F(ExceptionHandlerDeathTest, InProcTest) { + // For the in-proc test, we just need to instantiate an exception + // handler in in-proc mode, and crash. Since the entire test is + // reexecuted in the child process, we don't have to worry about + // the semantics of the exception handler being inherited/not + // inherited across CreateProcess(). + ASSERT_TRUE(DoesPathExist(temp_path_)); + google_breakpad::ExceptionHandler *exc = + new google_breakpad::ExceptionHandler( + temp_path_, NULL, &MinidumpWrittenCallback, NULL, + google_breakpad::ExceptionHandler::HANDLER_ALL); + int *i = NULL; + ASSERT_DEATH((*i)++, kSuccessIndicator); + delete exc; +} + +static bool gDumpCallbackCalled = false; + +void clientDumpCallback(void *dump_context, + const google_breakpad::ClientInfo *client_info, + const std::wstring *dump_path) { + gDumpCallbackCalled = true; +} + +void ExceptionHandlerDeathTest::DoCrash() { + google_breakpad::ExceptionHandler *exc = + new google_breakpad::ExceptionHandler( + temp_path_, NULL, NULL, NULL, + google_breakpad::ExceptionHandler::HANDLER_ALL, MiniDumpNormal, kPipeName, + NULL); + // Although this is executing in the child process of the death test, + // if it's not true we'll still get an error rather than the crash + // being expected. + ASSERT_TRUE(exc->IsOutOfProcess()); + int *i = NULL; + printf("%d\n", (*i)++); +} + +TEST_F(ExceptionHandlerDeathTest, OutOfProcTest) { + // We can take advantage of a detail of google test here to save some + // complexity in testing: when you do a death test, it actually forks. + // So we can make the main test harness the crash generation server, + // and call ASSERT_DEATH on a NULL dereference, it to expecting test + // the out of process scenario, since it's happening in a different + // process! This is different from the above because, above, we pass + // a NULL pipe name, and we also don't start a crash generation server. + + ASSERT_TRUE(DoesPathExist(temp_path_)); + std::wstring dump_path(temp_path_); + google_breakpad::CrashGenerationServer server( + kPipeName, NULL, NULL, NULL, &clientDumpCallback, NULL, NULL, NULL, true, + &dump_path); + + // This HAS to be EXPECT_, because when this test case is executed in the + // child process, the server registration will fail due to the named pipe + // being the same. + EXPECT_TRUE(server.Start()); + EXPECT_FALSE(gDumpCallbackCalled); + ASSERT_DEATH(this->DoCrash(), ""); + EXPECT_TRUE(gDumpCallbackCalled); +} + +TEST_F(ExceptionHandlerDeathTest, InvalidParameterTest) { + using google_breakpad::ExceptionHandler; + + ASSERT_TRUE(DoesPathExist(temp_path_)); + ExceptionHandler handler(temp_path_, NULL, NULL, NULL, + ExceptionHandler::HANDLER_INVALID_PARAMETER); + + // Disable the message box for assertions + _CrtSetReportMode(_CRT_ASSERT, 0); + + // Call with a bad argument. The invalid parameter will be swallowed + // and a dump will be generated, the process will exit(0). + ASSERT_EXIT(printf(NULL), ::testing::ExitedWithCode(0), ""); +} +} diff --git a/src/client/windows/unittests/exception_handler_test.cc b/src/client/windows/unittests/exception_handler_test.cc index c5a0c679..b8b58762 100755 --- a/src/client/windows/unittests/exception_handler_test.cc +++ b/src/client/windows/unittests/exception_handler_test.cc @@ -36,27 +36,49 @@ #include "../../../breakpad_googletest_includes.h" #include "../crash_generation/crash_generation_server.h" #include "../handler/exception_handler.h" +#include "dump_analysis.h" // NOLINT namespace { const wchar_t kPipeName[] = L"\\\\.\\pipe\\BreakpadCrashTest\\TestCaseServer"; const char kSuccessIndicator[] = "success"; const char kFailureIndicator[] = "failure"; -// Utility function to test for a path's existence. -BOOL DoesPathExist(const TCHAR *path_name); +const MINIDUMP_TYPE kFullDumpType = static_cast( + MiniDumpWithFullMemory | // Full memory from process. + MiniDumpWithProcessThreadData | // Get PEB and TEB. + MiniDumpWithHandleData); // Get all handle information. -class ExceptionHandlerDeathTest : public ::testing::Test { +class ExceptionHandlerTest : public ::testing::Test { protected: // Member variable for each test that they can use // for temporary storage. TCHAR temp_path_[MAX_PATH]; + // Actually constructs a temp path name. virtual void SetUp(); - // A helper method that tests can use to crash. + + // Deletes temporary files. + virtual void TearDown(); + void DoCrash(); + + // Utility function to test for a path's existence. + static BOOL DoesPathExist(const TCHAR *path_name); + + // Client callback. + static void ClientDumpCallback( + void *dump_context, + const google_breakpad::ClientInfo *client_info, + const std::wstring *dump_path); + + static std::wstring dump_file; + static std::wstring full_dump_file; }; -void ExceptionHandlerDeathTest::SetUp() { +std::wstring ExceptionHandlerTest::dump_file; +std::wstring ExceptionHandlerTest::full_dump_file; + +void ExceptionHandlerTest::SetUp() { const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); TCHAR temp_path[MAX_PATH] = { '\0' }; @@ -77,7 +99,18 @@ void ExceptionHandlerDeathTest::SetUp() { CreateDirectory(temp_path_, NULL); } -BOOL DoesPathExist(const TCHAR *path_name) { +void ExceptionHandlerTest::TearDown() { + if (!dump_file.empty()) { + ::DeleteFile(dump_file.c_str()); + dump_file = L""; + } + if (!full_dump_file.empty()) { + ::DeleteFile(full_dump_file.c_str()); + full_dump_file = L""; + } +} + +BOOL ExceptionHandlerTest::DoesPathExist(const TCHAR *path_name) { DWORD flags = GetFileAttributes(path_name); if (flags == INVALID_FILE_ATTRIBUTES) { return FALSE; @@ -85,82 +118,99 @@ BOOL DoesPathExist(const TCHAR *path_name) { return TRUE; } -bool MinidumpWrittenCallback(const wchar_t* dump_path, - const wchar_t* minidump_id, - void* context, - EXCEPTION_POINTERS* exinfo, - MDRawAssertionInfo* assertion, - bool succeeded) { - if (succeeded && DoesPathExist(dump_path)) { - fprintf(stderr, kSuccessIndicator); - } else { - fprintf(stderr, kFailureIndicator); - } - // If we don't flush, the output doesn't get sent before - // this process dies. - fflush(stderr); - return succeeded; +void ExceptionHandlerTest::ClientDumpCallback( + void *dump_context, + const google_breakpad::ClientInfo *client_info, + const std::wstring *dump_path) { + dump_file = *dump_path; + // Create the full dump file name from the dump path. + full_dump_file = dump_file.substr(0, dump_file.length() - 4) + L"-full.dmp"; } -TEST_F(ExceptionHandlerDeathTest, InProcTest) { - // For the in-proc test, we just need to instantiate an exception - // handler in in-proc mode, and crash. Since the entire test is - // reexecuted in the child process, we don't have to worry about - // the semantics of the exception handler being inherited/not - // inherited across CreateProcess(). - ASSERT_TRUE(DoesPathExist(temp_path_)); +void ExceptionHandlerTest::DoCrash() { google_breakpad::ExceptionHandler *exc = - new google_breakpad::ExceptionHandler( - temp_path_, NULL, &MinidumpWrittenCallback, NULL, - google_breakpad::ExceptionHandler::HANDLER_ALL); - int *i = NULL; - ASSERT_DEATH((*i)++, kSuccessIndicator); - delete exc; -} + new google_breakpad::ExceptionHandler( + temp_path_, NULL, NULL, NULL, + google_breakpad::ExceptionHandler::HANDLER_INVALID_PARAMETER, + kFullDumpType, kPipeName, NULL); -static bool gDumpCallbackCalled = false; + // Disable the message box for assertions + _CrtSetReportMode(_CRT_ASSERT, 0); -void clientDumpCallback(void *dump_context, - const google_breakpad::ClientInfo *client_info, - const std::wstring *dump_path) { - gDumpCallbackCalled = true; -} - -void ExceptionHandlerDeathTest::DoCrash() { - google_breakpad::ExceptionHandler *exc = - new google_breakpad::ExceptionHandler( - temp_path_, NULL, NULL, NULL, - google_breakpad::ExceptionHandler::HANDLER_ALL, MiniDumpNormal, kPipeName, - NULL); // Although this is executing in the child process of the death test, // if it's not true we'll still get an error rather than the crash // being expected. ASSERT_TRUE(exc->IsOutOfProcess()); - int *i = NULL; - printf("%d\n", (*i)++); + printf(NULL); } -TEST_F(ExceptionHandlerDeathTest, OutOfProcTest) { - // We can take advantage of a detail of google test here to save some - // complexity in testing: when you do a death test, it actually forks. - // So we can make the main test harness the crash generation server, - // and call ASSERT_DEATH on a NULL dereference, it to expecting test - // the out of process scenario, since it's happening in a different - // process! This is different from the above because, above, we pass - // a NULL pipe name, and we also don't start a crash generation server. +// This test validates that the minidump is written correctly. +TEST_F(ExceptionHandlerTest, InvalidParameterMiniDumpTest) { + ASSERT_TRUE(DoesPathExist(temp_path_)); + // Call with a bad argument ASSERT_TRUE(DoesPathExist(temp_path_)); std::wstring dump_path(temp_path_); google_breakpad::CrashGenerationServer server( - kPipeName, NULL, NULL, NULL, &clientDumpCallback, NULL, NULL, NULL, true, - &dump_path); + kPipeName, NULL, NULL, NULL, ClientDumpCallback, NULL, NULL, NULL, true, + &dump_path); + + ASSERT_TRUE(dump_file.empty() && full_dump_file.empty()); // This HAS to be EXPECT_, because when this test case is executed in the // child process, the server registration will fail due to the named pipe // being the same. EXPECT_TRUE(server.Start()); - EXPECT_FALSE(gDumpCallbackCalled); - ASSERT_DEATH(this->DoCrash(), ""); - EXPECT_TRUE(gDumpCallbackCalled); + EXPECT_EXIT(this->DoCrash(), ::testing::ExitedWithCode(0), ""); + ASSERT_TRUE(!dump_file.empty() && !full_dump_file.empty()); + ASSERT_TRUE(DoesPathExist(dump_file.c_str())); + + // Verify the dump for infos. + DumpAnalysis mini(dump_file); + DumpAnalysis full(full_dump_file); + + // The dump should have all of these streams. + EXPECT_TRUE(mini.HasStream(ThreadListStream)); + EXPECT_TRUE(full.HasStream(ThreadListStream)); + EXPECT_TRUE(mini.HasStream(ModuleListStream)); + EXPECT_TRUE(full.HasStream(ModuleListStream)); + EXPECT_TRUE(mini.HasStream(ExceptionStream)); + EXPECT_TRUE(full.HasStream(ExceptionStream)); + EXPECT_TRUE(mini.HasStream(SystemInfoStream)); + EXPECT_TRUE(full.HasStream(SystemInfoStream)); + EXPECT_TRUE(mini.HasStream(MiscInfoStream)); + EXPECT_TRUE(full.HasStream(MiscInfoStream)); + EXPECT_TRUE(mini.HasStream(HandleDataStream)); + EXPECT_TRUE(full.HasStream(HandleDataStream)); + + // We expect PEB and TEBs in this dump. + EXPECT_TRUE(mini.HasTebs() || full.HasTebs()); + EXPECT_TRUE(mini.HasPeb() || full.HasPeb()); + + // Minidump should have a memory listing, but no 64-bit memory. + EXPECT_TRUE(mini.HasStream(MemoryListStream)); + EXPECT_FALSE(mini.HasStream(Memory64ListStream)); + + EXPECT_FALSE(full.HasStream(MemoryListStream)); + EXPECT_TRUE(full.HasStream(Memory64ListStream)); + + // This is the only place we don't use OR because we want both not + // to have the streams. + EXPECT_FALSE(mini.HasStream(ThreadExListStream)); + EXPECT_FALSE(full.HasStream(ThreadExListStream)); + EXPECT_FALSE(mini.HasStream(CommentStreamA)); + EXPECT_FALSE(full.HasStream(CommentStreamA)); + EXPECT_FALSE(mini.HasStream(CommentStreamW)); + EXPECT_FALSE(full.HasStream(CommentStreamW)); + EXPECT_FALSE(mini.HasStream(FunctionTableStream)); + EXPECT_FALSE(full.HasStream(FunctionTableStream)); + EXPECT_FALSE(mini.HasStream(MemoryInfoListStream)); + EXPECT_FALSE(full.HasStream(MemoryInfoListStream)); + EXPECT_FALSE(mini.HasStream(ThreadInfoListStream)); + EXPECT_FALSE(full.HasStream(ThreadInfoListStream)); + EXPECT_FALSE(mini.HasStream(HandleOperationListStream)); + EXPECT_FALSE(full.HasStream(HandleOperationListStream)); + EXPECT_FALSE(mini.HasStream(TokenStream)); + EXPECT_FALSE(full.HasStream(TokenStream)); } } diff --git a/src/client/windows/unittests/minidump_test.cc b/src/client/windows/unittests/minidump_test.cc index fc7af832..5b2960b0 100755 --- a/src/client/windows/unittests/minidump_test.cc +++ b/src/client/windows/unittests/minidump_test.cc @@ -31,16 +31,13 @@ #include #include +#include "../crash_generation/minidump_generator.h" +#include "dump_analysis.h" // NOLINT + #include "gtest/gtest.h" namespace { -// Convenience to get to the PEB pointer in a TEB. -struct FakeTEB { - char dummy[0x30]; - void* peb; -}; - // Minidump with stacks, PEB, TEB, and unloaded module list. const MINIDUMP_TYPE kSmallDumpType = static_cast( MiniDumpWithProcessThreadData | // Get PEB and TEB. @@ -61,8 +58,10 @@ const MINIDUMP_TYPE kFullDumpType = static_cast( class MinidumpTest: public testing::Test { public: - MinidumpTest() : dump_file_view_(NULL), dump_file_handle_(NULL), - dump_file_mapping_(NULL) { + MinidumpTest() { + wchar_t temp_dir_path[ MAX_PATH ] = {0}; + ::GetTempPath(MAX_PATH, temp_dir_path); + dump_path_ = temp_dir_path; } virtual void SetUp() { @@ -74,58 +73,22 @@ class MinidumpTest: public testing::Test { HMODULE urlmon = ::LoadLibrary(L"urlmon.dll"); ASSERT_TRUE(urlmon != NULL); ASSERT_TRUE(::FreeLibrary(urlmon)); - - wchar_t temp_dir_path[ MAX_PATH ] = {0}; - wchar_t dump_file_name[ MAX_PATH ] = {0}; - ::GetTempPath(MAX_PATH, temp_dir_path); - ::GetTempFileName(temp_dir_path, L"tst", 0, dump_file_name); - dump_file_ = dump_file_name; - dump_file_handle_ = ::CreateFile(dump_file_.c_str(), - GENERIC_WRITE | GENERIC_READ, - 0, - NULL, - OPEN_EXISTING, - 0, - NULL); - ASSERT_TRUE(dump_file_handle_ != NULL); } virtual void TearDown() { - if (dump_file_view_ != NULL) { - EXPECT_TRUE(::UnmapViewOfFile(dump_file_view_)); - ::CloseHandle(dump_file_mapping_); - dump_file_mapping_ = NULL; + if (!dump_file_.empty()) { + ::DeleteFile(dump_file_.c_str()); + dump_file_ = L""; } - - ::CloseHandle(dump_file_handle_); - dump_file_handle_ = NULL; - - EXPECT_TRUE(::DeleteFile(dump_file_.c_str())); - } - - void EnsureDumpMapped() { - ASSERT_TRUE(dump_file_handle_ != NULL); - if (dump_file_view_ == NULL) { - ASSERT_TRUE(dump_file_mapping_ == NULL); - - dump_file_mapping_ = ::CreateFileMapping(dump_file_handle_, - NULL, - PAGE_READONLY, - 0, - 0, - NULL); - ASSERT_TRUE(dump_file_mapping_ != NULL); - - dump_file_view_ = ::MapViewOfFile(dump_file_mapping_, - FILE_MAP_READ, - 0, - 0, - 0); - ASSERT_TRUE(dump_file_view_ != NULL); + if (!full_dump_file_.empty()) { + ::DeleteFile(full_dump_file_.c_str()); + full_dump_file_ = L""; } } bool WriteDump(ULONG flags) { + using google_breakpad::MinidumpGenerator; + // Fake exception is access violation on write to this. EXCEPTION_RECORD ex_record = { STATUS_ACCESS_VIOLATION, // ExceptionCode @@ -140,171 +103,28 @@ class MinidumpTest: public testing::Test { &ex_record, &ctx_record, }; - MINIDUMP_EXCEPTION_INFORMATION ex_info = { - ::GetCurrentThreadId(), - &ex_ptrs, - FALSE, - }; - // Capture our register context. - ::RtlCaptureContext(&ctx_record); + MinidumpGenerator generator(dump_path_); // And write a dump - BOOL result = ::MiniDumpWriteDump(::GetCurrentProcess(), - ::GetCurrentProcessId(), - dump_file_handle_, - static_cast(flags), - &ex_info, - NULL, - NULL); + bool result = generator.WriteMinidump(::GetCurrentProcess(), + ::GetCurrentProcessId(), + ::GetCurrentThreadId(), + ::GetCurrentThreadId(), + &ex_ptrs, + NULL, + static_cast(flags), + TRUE, + &dump_file_, + &full_dump_file_); return result == TRUE; } - bool DumpHasStream(ULONG stream_number) { - EnsureDumpMapped(); - - MINIDUMP_DIRECTORY* directory = NULL; - void* stream = NULL; - ULONG stream_size = 0; - BOOL ret = ::MiniDumpReadDumpStream(dump_file_view_, - stream_number, - &directory, - &stream, - &stream_size); - - return ret != FALSE && stream != NULL && stream_size > 0; - } - - template - size_t GetStream(ULONG stream_number, StreamType** stream) { - EnsureDumpMapped(); - MINIDUMP_DIRECTORY* directory = NULL; - ULONG memory_list_size = 0; - BOOL ret = ::MiniDumpReadDumpStream(dump_file_view_, - stream_number, - &directory, - reinterpret_cast(stream), - &memory_list_size); - - return ret ? memory_list_size : 0; - } - - bool DumpHasTebs() { - MINIDUMP_THREAD_LIST* thread_list = NULL; - size_t thread_list_size = GetStream(ThreadListStream, &thread_list); - - if (thread_list_size > 0 && thread_list != NULL) { - for (ULONG i = 0; i < thread_list->NumberOfThreads; ++i) { - if (!DumpHasMemory(thread_list->Threads[i].Teb)) - return false; - } - - return true; - } - - // No thread list, no TEB info. - return false; - } - - bool DumpHasPeb() { - MINIDUMP_THREAD_LIST* thread_list = NULL; - size_t thread_list_size = GetStream(ThreadListStream, &thread_list); - - if (thread_list_size > 0 && thread_list != NULL && - thread_list->NumberOfThreads > 0) { - FakeTEB* teb = NULL; - if (!DumpHasMemory(thread_list->Threads[0].Teb, &teb)) - return false; - - return DumpHasMemory(teb->peb); - } - - return false; - } - - bool DumpHasMemory(ULONG64 address) { - return DumpHasMemory(address, NULL); - } - - bool DumpHasMemory(const void* address) { - return DumpHasMemory(address, NULL); - } - - template - bool DumpHasMemory(ULONG64 address, StructureType** structure = NULL) { - // We can't cope with 64 bit addresses for now. - if (address > 0xFFFFFFFFUL) - return false; - - return DumpHasMemory(reinterpret_cast(address), structure); - } - - template - bool DumpHasMemory(const void* addr_in, StructureType** structure = NULL) { - uintptr_t address = reinterpret_cast(addr_in); - MINIDUMP_MEMORY_LIST* memory_list = NULL; - size_t memory_list_size = GetStream(MemoryListStream, &memory_list); - if (memory_list_size > 0 && memory_list != NULL) { - for (ULONG i = 0; i < memory_list->NumberOfMemoryRanges; ++i) { - MINIDUMP_MEMORY_DESCRIPTOR& descr = memory_list->MemoryRanges[i]; - const uintptr_t range_start = - static_cast(descr.StartOfMemoryRange); - uintptr_t range_end = range_start + descr.Memory.DataSize; - - if (address >= range_start && - address + sizeof(StructureType) < range_end) { - // The start address falls in the range, and the end address is - // in bounds, return a pointer to the structure if requested. - if (structure != NULL) - *structure = reinterpret_cast( - RVA_TO_ADDR(dump_file_view_, descr.Memory.Rva)); - - return true; - } - } - } - - // We didn't find the range in a MINIDUMP_MEMORY_LIST, so maybe this - // is a full dump using MINIDUMP_MEMORY64_LIST with all the memory at the - // end of the dump file. - MINIDUMP_MEMORY64_LIST* memory64_list = NULL; - memory_list_size = GetStream(Memory64ListStream, &memory64_list); - if (memory_list_size > 0 && memory64_list != NULL) { - // Keep track of where the current descriptor maps to. - RVA64 curr_rva = memory64_list->BaseRva; - for (ULONG i = 0; i < memory64_list->NumberOfMemoryRanges; ++i) { - MINIDUMP_MEMORY_DESCRIPTOR64& descr = memory64_list->MemoryRanges[i]; - uintptr_t range_start = - static_cast(descr.StartOfMemoryRange); - uintptr_t range_end = range_start + static_cast(descr.DataSize); - - if (address >= range_start && - address + sizeof(StructureType) < range_end) { - // The start address falls in the range, and the end address is - // in bounds, return a pointer to the structure if requested. - if (structure != NULL) - *structure = reinterpret_cast( - RVA_TO_ADDR(dump_file_view_, curr_rva)); - - return true; - } - - // Advance the current RVA. - curr_rva += descr.DataSize; - } - } - - - - return false; - } - protected: - HANDLE dump_file_handle_; - HANDLE dump_file_mapping_; - void* dump_file_view_; - std::wstring dump_file_; + std::wstring full_dump_file_; + + std::wstring dump_path_; }; // We need to be able to get file information from Windows @@ -358,125 +178,155 @@ TEST_F(MinidumpTest, Version) { TEST_F(MinidumpTest, Normal) { EXPECT_TRUE(WriteDump(MiniDumpNormal)); + DumpAnalysis mini(dump_file_); // We expect threads, modules and some memory. - EXPECT_TRUE(DumpHasStream(ThreadListStream)); - EXPECT_TRUE(DumpHasStream(ModuleListStream)); - EXPECT_TRUE(DumpHasStream(MemoryListStream)); - EXPECT_TRUE(DumpHasStream(ExceptionStream)); - EXPECT_TRUE(DumpHasStream(SystemInfoStream)); - EXPECT_TRUE(DumpHasStream(MiscInfoStream)); + EXPECT_TRUE(mini.HasStream(ThreadListStream)); + EXPECT_TRUE(mini.HasStream(ModuleListStream)); + EXPECT_TRUE(mini.HasStream(MemoryListStream)); + EXPECT_TRUE(mini.HasStream(ExceptionStream)); + EXPECT_TRUE(mini.HasStream(SystemInfoStream)); + EXPECT_TRUE(mini.HasStream(MiscInfoStream)); - EXPECT_FALSE(DumpHasStream(ThreadExListStream)); - EXPECT_FALSE(DumpHasStream(Memory64ListStream)); - EXPECT_FALSE(DumpHasStream(CommentStreamA)); - EXPECT_FALSE(DumpHasStream(CommentStreamW)); - EXPECT_FALSE(DumpHasStream(HandleDataStream)); - EXPECT_FALSE(DumpHasStream(FunctionTableStream)); - EXPECT_FALSE(DumpHasStream(UnloadedModuleListStream)); - EXPECT_FALSE(DumpHasStream(MemoryInfoListStream)); - EXPECT_FALSE(DumpHasStream(ThreadInfoListStream)); - EXPECT_FALSE(DumpHasStream(HandleOperationListStream)); - EXPECT_FALSE(DumpHasStream(TokenStream)); + EXPECT_FALSE(mini.HasStream(ThreadExListStream)); + EXPECT_FALSE(mini.HasStream(Memory64ListStream)); + EXPECT_FALSE(mini.HasStream(CommentStreamA)); + EXPECT_FALSE(mini.HasStream(CommentStreamW)); + EXPECT_FALSE(mini.HasStream(HandleDataStream)); + EXPECT_FALSE(mini.HasStream(FunctionTableStream)); + EXPECT_FALSE(mini.HasStream(UnloadedModuleListStream)); + EXPECT_FALSE(mini.HasStream(MemoryInfoListStream)); + EXPECT_FALSE(mini.HasStream(ThreadInfoListStream)); + EXPECT_FALSE(mini.HasStream(HandleOperationListStream)); + EXPECT_FALSE(mini.HasStream(TokenStream)); // We expect no PEB nor TEBs in this dump. - EXPECT_FALSE(DumpHasTebs()); - EXPECT_FALSE(DumpHasPeb()); + EXPECT_FALSE(mini.HasTebs()); + EXPECT_FALSE(mini.HasPeb()); // We expect no off-stack memory in this dump. - EXPECT_FALSE(DumpHasMemory(this)); + EXPECT_FALSE(mini.HasMemory(this)); } TEST_F(MinidumpTest, SmallDump) { ASSERT_TRUE(WriteDump(kSmallDumpType)); + DumpAnalysis mini(dump_file_); - EXPECT_TRUE(DumpHasStream(ThreadListStream)); - EXPECT_TRUE(DumpHasStream(ModuleListStream)); - EXPECT_TRUE(DumpHasStream(MemoryListStream)); - EXPECT_TRUE(DumpHasStream(ExceptionStream)); - EXPECT_TRUE(DumpHasStream(SystemInfoStream)); - EXPECT_TRUE(DumpHasStream(UnloadedModuleListStream)); - EXPECT_TRUE(DumpHasStream(MiscInfoStream)); + EXPECT_TRUE(mini.HasStream(ThreadListStream)); + EXPECT_TRUE(mini.HasStream(ModuleListStream)); + EXPECT_TRUE(mini.HasStream(MemoryListStream)); + EXPECT_TRUE(mini.HasStream(ExceptionStream)); + EXPECT_TRUE(mini.HasStream(SystemInfoStream)); + EXPECT_TRUE(mini.HasStream(UnloadedModuleListStream)); + EXPECT_TRUE(mini.HasStream(MiscInfoStream)); // We expect PEB and TEBs in this dump. - EXPECT_TRUE(DumpHasTebs()); - EXPECT_TRUE(DumpHasPeb()); + EXPECT_TRUE(mini.HasTebs()); + EXPECT_TRUE(mini.HasPeb()); - EXPECT_FALSE(DumpHasStream(ThreadExListStream)); - EXPECT_FALSE(DumpHasStream(Memory64ListStream)); - EXPECT_FALSE(DumpHasStream(CommentStreamA)); - EXPECT_FALSE(DumpHasStream(CommentStreamW)); - EXPECT_FALSE(DumpHasStream(HandleDataStream)); - EXPECT_FALSE(DumpHasStream(FunctionTableStream)); - EXPECT_FALSE(DumpHasStream(MemoryInfoListStream)); - EXPECT_FALSE(DumpHasStream(ThreadInfoListStream)); - EXPECT_FALSE(DumpHasStream(HandleOperationListStream)); - EXPECT_FALSE(DumpHasStream(TokenStream)); + EXPECT_FALSE(mini.HasStream(ThreadExListStream)); + EXPECT_FALSE(mini.HasStream(Memory64ListStream)); + EXPECT_FALSE(mini.HasStream(CommentStreamA)); + EXPECT_FALSE(mini.HasStream(CommentStreamW)); + EXPECT_FALSE(mini.HasStream(HandleDataStream)); + EXPECT_FALSE(mini.HasStream(FunctionTableStream)); + EXPECT_FALSE(mini.HasStream(MemoryInfoListStream)); + EXPECT_FALSE(mini.HasStream(ThreadInfoListStream)); + EXPECT_FALSE(mini.HasStream(HandleOperationListStream)); + EXPECT_FALSE(mini.HasStream(TokenStream)); // We expect no off-stack memory in this dump. - EXPECT_FALSE(DumpHasMemory(this)); + EXPECT_FALSE(mini.HasMemory(this)); } TEST_F(MinidumpTest, LargerDump) { ASSERT_TRUE(WriteDump(kLargerDumpType)); + DumpAnalysis mini(dump_file_); // The dump should have all of these streams. - EXPECT_TRUE(DumpHasStream(ThreadListStream)); - EXPECT_TRUE(DumpHasStream(ModuleListStream)); - EXPECT_TRUE(DumpHasStream(MemoryListStream)); - EXPECT_TRUE(DumpHasStream(ExceptionStream)); - EXPECT_TRUE(DumpHasStream(SystemInfoStream)); - EXPECT_TRUE(DumpHasStream(UnloadedModuleListStream)); - EXPECT_TRUE(DumpHasStream(MiscInfoStream)); + EXPECT_TRUE(mini.HasStream(ThreadListStream)); + EXPECT_TRUE(mini.HasStream(ModuleListStream)); + EXPECT_TRUE(mini.HasStream(MemoryListStream)); + EXPECT_TRUE(mini.HasStream(ExceptionStream)); + EXPECT_TRUE(mini.HasStream(SystemInfoStream)); + EXPECT_TRUE(mini.HasStream(UnloadedModuleListStream)); + EXPECT_TRUE(mini.HasStream(MiscInfoStream)); // We expect memory referenced by stack in this dump. - EXPECT_TRUE(DumpHasMemory(this)); + EXPECT_TRUE(mini.HasMemory(this)); // We expect PEB and TEBs in this dump. - EXPECT_TRUE(DumpHasTebs()); - EXPECT_TRUE(DumpHasPeb()); + EXPECT_TRUE(mini.HasTebs()); + EXPECT_TRUE(mini.HasPeb()); - EXPECT_FALSE(DumpHasStream(ThreadExListStream)); - EXPECT_FALSE(DumpHasStream(Memory64ListStream)); - EXPECT_FALSE(DumpHasStream(CommentStreamA)); - EXPECT_FALSE(DumpHasStream(CommentStreamW)); - EXPECT_FALSE(DumpHasStream(HandleDataStream)); - EXPECT_FALSE(DumpHasStream(FunctionTableStream)); - EXPECT_FALSE(DumpHasStream(MemoryInfoListStream)); - EXPECT_FALSE(DumpHasStream(ThreadInfoListStream)); - EXPECT_FALSE(DumpHasStream(HandleOperationListStream)); - EXPECT_FALSE(DumpHasStream(TokenStream)); + EXPECT_FALSE(mini.HasStream(ThreadExListStream)); + EXPECT_FALSE(mini.HasStream(Memory64ListStream)); + EXPECT_FALSE(mini.HasStream(CommentStreamA)); + EXPECT_FALSE(mini.HasStream(CommentStreamW)); + EXPECT_FALSE(mini.HasStream(HandleDataStream)); + EXPECT_FALSE(mini.HasStream(FunctionTableStream)); + EXPECT_FALSE(mini.HasStream(MemoryInfoListStream)); + EXPECT_FALSE(mini.HasStream(ThreadInfoListStream)); + EXPECT_FALSE(mini.HasStream(HandleOperationListStream)); + EXPECT_FALSE(mini.HasStream(TokenStream)); } TEST_F(MinidumpTest, FullDump) { ASSERT_TRUE(WriteDump(kFullDumpType)); + ASSERT_TRUE(dump_file_ != L""); + ASSERT_TRUE(full_dump_file_ != L""); + DumpAnalysis mini(dump_file_); + DumpAnalysis full(full_dump_file_); + + // Either dumps can contain part of the information. // The dump should have all of these streams. - EXPECT_TRUE(DumpHasStream(ThreadListStream)); - EXPECT_TRUE(DumpHasStream(ModuleListStream)); - EXPECT_TRUE(DumpHasStream(Memory64ListStream)); - EXPECT_TRUE(DumpHasStream(ExceptionStream)); - EXPECT_TRUE(DumpHasStream(SystemInfoStream)); - EXPECT_TRUE(DumpHasStream(UnloadedModuleListStream)); - EXPECT_TRUE(DumpHasStream(MiscInfoStream)); - EXPECT_TRUE(DumpHasStream(HandleDataStream)); + EXPECT_TRUE(mini.HasStream(ThreadListStream)); + EXPECT_TRUE(full.HasStream(ThreadListStream)); + EXPECT_TRUE(mini.HasStream(ModuleListStream)); + EXPECT_TRUE(full.HasStream(ModuleListStream)); + EXPECT_TRUE(mini.HasStream(ExceptionStream)); + EXPECT_TRUE(full.HasStream(ExceptionStream)); + EXPECT_TRUE(mini.HasStream(SystemInfoStream)); + EXPECT_TRUE(full.HasStream(SystemInfoStream)); + EXPECT_TRUE(mini.HasStream(UnloadedModuleListStream)); + EXPECT_TRUE(full.HasStream(UnloadedModuleListStream)); + EXPECT_TRUE(mini.HasStream(MiscInfoStream)); + EXPECT_TRUE(full.HasStream(MiscInfoStream)); + EXPECT_TRUE(mini.HasStream(HandleDataStream)); + EXPECT_TRUE(full.HasStream(HandleDataStream)); // We expect memory referenced by stack in this dump. - EXPECT_TRUE(DumpHasMemory(this)); + EXPECT_FALSE(mini.HasMemory(this)); + EXPECT_TRUE(full.HasMemory(this)); // We expect PEB and TEBs in this dump. - EXPECT_TRUE(DumpHasTebs()); - EXPECT_TRUE(DumpHasPeb()); + EXPECT_TRUE(mini.HasTebs() || full.HasTebs()); + EXPECT_TRUE(mini.HasPeb() || full.HasPeb()); - EXPECT_FALSE(DumpHasStream(ThreadExListStream)); - EXPECT_FALSE(DumpHasStream(MemoryListStream)); - EXPECT_FALSE(DumpHasStream(CommentStreamA)); - EXPECT_FALSE(DumpHasStream(CommentStreamW)); - EXPECT_FALSE(DumpHasStream(FunctionTableStream)); - EXPECT_FALSE(DumpHasStream(MemoryInfoListStream)); - EXPECT_FALSE(DumpHasStream(ThreadInfoListStream)); - EXPECT_FALSE(DumpHasStream(HandleOperationListStream)); - EXPECT_FALSE(DumpHasStream(TokenStream)); + EXPECT_TRUE(mini.HasStream(MemoryListStream)); + EXPECT_TRUE(full.HasStream(Memory64ListStream)); + EXPECT_FALSE(mini.HasStream(Memory64ListStream)); + EXPECT_FALSE(full.HasStream(MemoryListStream)); + + // This is the only place we don't use OR because we want both not + // to have the streams. + EXPECT_FALSE(mini.HasStream(ThreadExListStream)); + EXPECT_FALSE(full.HasStream(ThreadExListStream)); + EXPECT_FALSE(mini.HasStream(CommentStreamA)); + EXPECT_FALSE(full.HasStream(CommentStreamA)); + EXPECT_FALSE(mini.HasStream(CommentStreamW)); + EXPECT_FALSE(full.HasStream(CommentStreamW)); + EXPECT_FALSE(mini.HasStream(FunctionTableStream)); + EXPECT_FALSE(full.HasStream(FunctionTableStream)); + EXPECT_FALSE(mini.HasStream(MemoryInfoListStream)); + EXPECT_FALSE(full.HasStream(MemoryInfoListStream)); + EXPECT_FALSE(mini.HasStream(ThreadInfoListStream)); + EXPECT_FALSE(full.HasStream(ThreadInfoListStream)); + EXPECT_FALSE(mini.HasStream(HandleOperationListStream)); + EXPECT_FALSE(full.HasStream(HandleOperationListStream)); + EXPECT_FALSE(mini.HasStream(TokenStream)); + EXPECT_FALSE(full.HasStream(TokenStream)); } } // namespace