Index: packages/Python/lldbsuite/test/functionalities/postmortem/wow64_minidump/TestWow64MiniDump.py =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/postmortem/wow64_minidump/TestWow64MiniDump.py @@ -0,0 +1,76 @@ +""" +Test basics of a mini dump taken of a 32-bit process running in WoW64 + +WoW64 is the subsystem that lets 32-bit processes run in 64-bit Windows. If you +capture a mini dump of a process running under WoW64 with a 64-bit debugger, you +end up with a dump of the WoW64 layer. In that case, LLDB must do extra work to +get the 32-bit register contexts. +""" + +from __future__ import print_function +from six import iteritems + + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + +class Wow64MiniDumpTestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + @skipUnlessWindows # for now mini-dump debugging is limited to Windows hosts + @no_debug_info_test + def test_wow64_mini_dump(self): + """Test that lldb can read the process information from the minidump.""" + # target create -c fizzbuzz_wow64.dmp + target = self.dbg.CreateTarget("") + process = target.LoadCore("fizzbuzz_wow64.dmp") + self.assertTrue(process, PROCESS_IS_VALID) + self.assertEqual(process.GetNumThreads(), 1) + self.assertEqual(process.GetProcessID(), 0x1E9C) + + @skipUnlessWindows # for now mini-dump debugging is limited to Windows hosts + @no_debug_info_test + def test_thread_info_in_wow64_mini_dump(self): + """Test that lldb can read the thread information from the minidump.""" + # target create -c fizzbuzz_wow64.dmp + target = self.dbg.CreateTarget("") + process = target.LoadCore("fizzbuzz_wow64.dmp") + # This process crashed due to an access violation (0xc0000005), but the + # minidump doesn't have an exception record--perhaps the crash handler + # ate it. + # TODO: See if we can recover the exception information from the TEB, + # which, according to Windbg, has a pointer to an exception list. + + # In the dump, none of the threads are stopped, so we cannot use + # lldbutil.get_stopped_thread. + thread = process.GetThreadAtIndex(0) + self.assertEqual(thread.GetStopReason(), lldb.eStopReasonNone) + + @skipUnlessWindows # for now mini-dump debugging is limited to Windows hosts + @no_debug_info_test + def test_stack_info_in_wow64_mini_dump(self): + """Test that we can see a trivial stack in a VS-generate mini dump.""" + # target create -c fizzbuzz_no_heap.dmp + target = self.dbg.CreateTarget("") + process = target.LoadCore("fizzbuzz_wow64.dmp") + self.assertGreaterEqual(process.GetNumThreads(), 1) + # This process crashed due to an access violation (0xc0000005), but the + # minidump doesn't have an exception record--perhaps the crash handler + # ate it. + # TODO: See if we can recover the exception information from the TEB, + # which, according to Windbg, has a pointer to an exception list. + + # In the dump, none of the threads are stopped, so we cannot use + # lldbutil.get_stopped_thread. + thread = process.GetThreadAtIndex(0) + # The crash is in main, so there should be at least one frame on the stack. + self.assertGreaterEqual(thread.GetNumFrames(), 1) + frame = thread.GetFrameAtIndex(0) + self.assertTrue(frame.IsValid()) + pc = frame.GetPC() + eip = frame.FindRegister("pc") + self.assertTrue(eip.IsValid()) + self.assertEqual(pc, eip.GetValueAsUnsigned()) Index: packages/Python/lldbsuite/test/functionalities/postmortem/wow64_minidump/fizzbuzz.cpp =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/postmortem/wow64_minidump/fizzbuzz.cpp @@ -0,0 +1,31 @@ +// A sample program for getting minidumps on Windows. + +#include + +bool +fizz(int x) +{ + return x % 3 == 0; +} + +bool +buzz(int x) +{ + return x % 5 == 0; +} + +int +main() +{ + int *buggy = 0; + + for (int i = 1; i <= 100; ++i) + { + if (fizz(i)) std::cout << "fizz"; + if (buzz(i)) std::cout << "buzz"; + if (!fizz(i) && !buzz(i)) std::cout << i; + std::cout << '\n'; + } + + return *buggy; +} Index: source/Plugins/Process/Windows/Common/NtStructures.h =================================================================== --- /dev/null +++ source/Plugins/Process/Windows/Common/NtStructures.h @@ -0,0 +1,32 @@ +//===-- NtStructures.h ------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_Plugins_Process_Windows_Common_NtStructures_h_ +#define liblldb_Plugins_Process_Windows_Common_NtStructures_h_ + +#include "lldb/Host/windows/windows.h" + +// This describes the layout of a TEB (Thread Environment Block) for a 64-bit +// process. It's adapted from the 32-bit TEB in winternl.h. Currently, we care +// only about the position of the TlsSlots. +struct TEB64 +{ + ULONG64 Reserved1[12]; + ULONG64 ProcessEnvironmentBlock; + ULONG64 Reserved2[399]; + BYTE Reserved3[1952]; + ULONG64 TlsSlots[64]; + BYTE Reserved4[8]; + ULONG64 Reserved5[26]; + ULONG64 ReservedForOle; // Windows 2000 only + ULONG64 Reserved6[4]; + ULONG64 TlsExpansionSlots; +}; + +#endif Index: source/Plugins/Process/Windows/MiniDump/ProcessWinMiniDump.cpp =================================================================== --- source/Plugins/Process/Windows/MiniDump/ProcessWinMiniDump.cpp +++ source/Plugins/Process/Windows/MiniDump/ProcessWinMiniDump.cpp @@ -35,6 +35,9 @@ #include "llvm/Support/Format.h" #include "llvm/Support/raw_ostream.h" +#include "Plugins/Process/Windows/Common/NtStructures.h" +#include "Plugins/Process/Windows/Common/ProcessWindowsLog.h" + #include "ExceptionRecord.h" #include "ThreadWinMiniDump.h" @@ -83,6 +86,7 @@ HANDLE m_mapping; // handle to the file mapping for the minidump file void * m_base_addr; // base memory address of the minidump std::shared_ptr m_exception_sp; + bool m_is_wow64; // minidump is of a 32-bit process captured with a 64-bit debugger }; ConstString @@ -195,7 +199,47 @@ auto thread_sp = std::make_shared(*this, mini_dump_thread.ThreadId); if (mini_dump_thread.ThreadContext.DataSize >= sizeof(CONTEXT)) { - const CONTEXT *context = reinterpret_cast(static_cast(m_data_up->m_base_addr) + mini_dump_thread.ThreadContext.Rva); + const CONTEXT *context = reinterpret_cast( + static_cast(m_data_up->m_base_addr) + mini_dump_thread.ThreadContext.Rva); + + if (m_data_up->m_is_wow64) + { + // On Windows, a 32-bit process can run on a 64-bit machine under WOW64. + // If the minidump was captured with a 64-bit debugger, then the CONTEXT + // we just grabbed from the mini_dump_thread is the one for the 64-bit + // "native" process rather than the 32-bit "guest" process we care about. + // In this case, we can get the 32-bit CONTEXT from the TEB (Thread + // Environment Block) of the 64-bit process. + Error error; + TEB64 wow64teb = {0}; + ReadMemory(mini_dump_thread.Teb, &wow64teb, sizeof(wow64teb), error); + if (error.Success()) + { + // Slot 1 of the thread-local storage in the 64-bit TEB points to a structure + // that includes the 32-bit CONTEXT (after a ULONG). + // See: https://msdn.microsoft.com/en-us/library/ms681670.aspx + const size_t addr = wow64teb.TlsSlots[1]; + Range range = {0}; + if (FindMemoryRange(addr, &range)) + { + lldbassert(range.start <= addr); + const size_t offset = addr - range.start + sizeof(ULONG); + if (offset < range.size) + { + const size_t overlap = range.size - offset; + if (overlap >= sizeof(CONTEXT)) + { + context = reinterpret_cast(range.ptr + offset); + } + } + } + } + + // NOTE: We don't currently use the TEB for anything else. If we need it in + // the future, the 32-bit TEB is located according to the address stored in the + // first slot of the 64-bit TEB (wow64teb.Reserved1[0]). + } + thread_sp->SetContext(context); } new_thread_list.AddThread(thread_sp); @@ -347,11 +391,8 @@ return ArchSpec(); } - -ProcessWinMiniDump::Data::Data() : - m_dump_file(INVALID_HANDLE_VALUE), - m_mapping(NULL), - m_base_addr(nullptr) +ProcessWinMiniDump::Data::Data() + : m_dump_file(INVALID_HANDLE_VALUE), m_mapping(NULL), m_base_addr(nullptr), m_is_wow64(false) { } @@ -381,7 +422,8 @@ auto mem_list_stream = static_cast(FindDumpStream(MemoryListStream, &stream_size)); if (mem_list_stream) { - for (ULONG32 i = 0; i < mem_list_stream->NumberOfMemoryRanges; ++i) { + for (ULONG32 i = 0; i < mem_list_stream->NumberOfMemoryRanges; ++i) + { const MINIDUMP_MEMORY_DESCRIPTOR &mem_desc = mem_list_stream->MemoryRanges[i]; const MINIDUMP_LOCATION_DESCRIPTOR &loc_desc = mem_desc.Memory; const lldb::addr_t range_start = mem_desc.StartOfMemoryRange; @@ -485,6 +527,11 @@ { m_data_up->m_exception_sp.reset(new ExceptionRecord(exception_stream_ptr->ExceptionRecord, exception_stream_ptr->ThreadId)); } + else + { + WINLOG_IFALL(WINDOWS_LOG_PROCESS, "Minidump has no exception record."); + // TODO: See if we can recover the exception from the TEB. + } } void @@ -516,7 +563,13 @@ { const auto &module = module_list_ptr->Modules[i]; const auto file_name = GetMiniDumpString(m_data_up->m_base_addr, module.ModuleNameRva); - ModuleSpec module_spec = FileSpec(file_name, true); + const auto file_spec = FileSpec(file_name, true); + if (FileSpec::Compare(file_spec, FileSpec("wow64.dll", false), false) == 0) + { + WINLOG_IFALL(WINDOWS_LOG_PROCESS, "Minidump is for a WOW64 process."); + m_data_up->m_is_wow64 = true; + } + ModuleSpec module_spec = file_spec; lldb::ModuleSP module_sp = GetTarget().GetSharedModule(module_spec); if (!module_sp)