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