Skip to content

Commit

Permalink
Get register context for the 32-bit process in a WoW64 process minidump
Browse files Browse the repository at this point in the history
32-bit processes on 64-bit Windows run in a layer called WoW64 (Windows-on-Windows64). If you capture a mini dump of such a process from a 32-bit debugger, you end up with a register context for the 64-bit WoW64 process rather than the 32-bit one you probably care about.

This detects WoW64 by looking to see if there's a module named wow64.dll loaded. For such processes, it then looks in the 64-bit Thread Environment Block (TEB) to locate a copy of the 32-bit CONTEXT record that the plugin needs for the register context.

Added some rudimentary tests.  I'd like to improve these later once we figure out how to get the exception information from these mini dumps.

Differential Revision: http://reviews.llvm.org/D17465

llvm-svn: 261808
  • Loading branch information
amccarth-google committed Feb 25, 2016
1 parent ce16649 commit 0a75082
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 8 deletions.
@@ -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())
@@ -0,0 +1,31 @@
// A sample program for getting minidumps on Windows.

#include <iostream>

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;
}
Binary file not shown.
32 changes: 32 additions & 0 deletions lldb/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
Expand Up @@ -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"

Expand Down Expand Up @@ -83,6 +86,7 @@ class ProcessWinMiniDump::Data
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<ExceptionRecord> m_exception_sp;
bool m_is_wow64; // minidump is of a 32-bit process captured with a 64-bit debugger
};

ConstString
Expand Down Expand Up @@ -195,7 +199,47 @@ ProcessWinMiniDump::UpdateThreadList(ThreadList &old_thread_list, ThreadList &ne
auto thread_sp = std::make_shared<ThreadWinMiniDump>(*this, mini_dump_thread.ThreadId);
if (mini_dump_thread.ThreadContext.DataSize >= sizeof(CONTEXT))
{
const CONTEXT *context = reinterpret_cast<const CONTEXT *>(static_cast<const char *>(m_data_up->m_base_addr) + mini_dump_thread.ThreadContext.Rva);
const CONTEXT *context = reinterpret_cast<const CONTEXT *>(
static_cast<const char *>(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<const CONTEXT *>(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);
Expand Down Expand Up @@ -347,11 +391,8 @@ ProcessWinMiniDump::GetArchitecture()
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)
{
}

Expand Down Expand Up @@ -381,7 +422,8 @@ ProcessWinMiniDump::FindMemoryRange(lldb::addr_t addr, Range *range_out) const
auto mem_list_stream = static_cast<const MINIDUMP_MEMORY_LIST *>(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;
Expand Down Expand Up @@ -485,6 +527,11 @@ ProcessWinMiniDump::ReadExceptionRecord()
{
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
Expand Down Expand Up @@ -516,7 +563,13 @@ ProcessWinMiniDump::ReadModuleList()
{
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)
Expand Down

0 comments on commit 0a75082

Please sign in to comment.