Minidump plugin: Adding ProcessMinidump, ThreadMinidump and register the plugin in SystemInitializerFull
Summary: This plugin resembles the already existing Windows-only Minidump plugin. The WinMinidumpPlugin uses the Windows API for parsing Minidumps while this plugin is cross-platform because it includes a Minidump parser (which is already commited) It is able to produce a backtrace, to read the general puprose regiters, inspect local variables, show image list, do memory reads, etc. For now the only arches that this supports are x86_32 and x86_64. This is because I have only written register contexts for those. Others will come in next CLs. I copied the WinMinidump tests and adapted them a little bit for them to work with the new plugin (and they pass) I will add more tests, aiming for better code coverage. There is still functionality to be added, see TODOs in code. Reviewers: labath, zturner Subscribers: beanz, mgorny, modocache, lldb-commits, amccarth Differential Revision: https://reviews.llvm.org/D25905 llvm-svn: 285587
This commit is contained in:
parent
f01f65ea59
commit
7b18dd4f77
|
@ -0,0 +1,100 @@
|
|||
"""
|
||||
Test basics of Minidump debugging.
|
||||
"""
|
||||
|
||||
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 MiniDumpNewTestCase(TestBase):
|
||||
|
||||
mydir = TestBase.compute_mydir(__file__)
|
||||
|
||||
NO_DEBUG_INFO_TESTCASE = True
|
||||
|
||||
def test_process_info_in_minidump(self):
|
||||
"""Test that lldb can read the process information from the Minidump."""
|
||||
# target create -c linux-x86_64.dmp
|
||||
self.dbg.CreateTarget(None)
|
||||
self.target = self.dbg.GetSelectedTarget()
|
||||
self.process = self.target.LoadCore("linux-x86_64.dmp")
|
||||
self.assertTrue(self.process, PROCESS_IS_VALID)
|
||||
self.assertEqual(self.process.GetNumThreads(), 1)
|
||||
self.assertEqual(self.process.GetProcessID(), 29917)
|
||||
|
||||
def test_thread_info_in_minidump(self):
|
||||
"""Test that lldb can read the thread information from the Minidump."""
|
||||
# target create -c linux-x86_64.dmp
|
||||
self.dbg.CreateTarget(None)
|
||||
self.target = self.dbg.GetSelectedTarget()
|
||||
self.process = self.target.LoadCore("linux-x86_64.dmp")
|
||||
# This process crashed due to a segmentation fault in its
|
||||
# one and only thread.
|
||||
self.assertEqual(self.process.GetNumThreads(), 1)
|
||||
thread = self.process.GetThreadAtIndex(0)
|
||||
self.assertEqual(thread.GetStopReason(), lldb.eStopReasonSignal)
|
||||
stop_description = thread.GetStopDescription(256)
|
||||
self.assertTrue("SIGSEGV" in stop_description)
|
||||
|
||||
def test_stack_info_in_minidump(self):
|
||||
"""Test that we can see a trivial stack in a breakpad-generated Minidump."""
|
||||
# target create linux-x86_64 -c linux-x86_64.dmp
|
||||
self.dbg.CreateTarget("linux-x86_64")
|
||||
self.target = self.dbg.GetSelectedTarget()
|
||||
self.process = self.target.LoadCore("linux-x86_64.dmp")
|
||||
self.assertEqual(self.process.GetNumThreads(), 1)
|
||||
thread = self.process.GetThreadAtIndex(0)
|
||||
# frame #0: linux-x86_64`crash()
|
||||
# frame #1: linux-x86_64`_start
|
||||
self.assertEqual(thread.GetNumFrames(), 2)
|
||||
frame = thread.GetFrameAtIndex(0)
|
||||
self.assertTrue(frame.IsValid())
|
||||
pc = frame.GetPC()
|
||||
eip = frame.FindRegister("pc")
|
||||
self.assertTrue(eip.IsValid())
|
||||
self.assertEqual(pc, eip.GetValueAsUnsigned())
|
||||
|
||||
def test_snapshot_minidump(self):
|
||||
"""Test that if we load a snapshot minidump file (meaning the process did not crash) there is no stop reason."""
|
||||
# target create -c linux-x86_64_not_crashed.dmp
|
||||
self.dbg.CreateTarget(None)
|
||||
self.target = self.dbg.GetSelectedTarget()
|
||||
self.process = self.target.LoadCore("linux-x86_64_not_crashed.dmp")
|
||||
self.assertEqual(self.process.GetNumThreads(), 1)
|
||||
thread = self.process.GetThreadAtIndex(0)
|
||||
self.assertEqual(thread.GetStopReason(), lldb.eStopReasonNone)
|
||||
stop_description = thread.GetStopDescription(256)
|
||||
self.assertEqual(stop_description, None)
|
||||
|
||||
def test_deeper_stack_in_minidump(self):
|
||||
"""Test that we can examine a more interesting stack in a Minidump."""
|
||||
# Launch with the Minidump, and inspect the stack.
|
||||
# target create linux-x86_64_not_crashed -c linux-x86_64_not_crashed.dmp
|
||||
target = self.dbg.CreateTarget("linux-x86_64_not_crashed")
|
||||
process = target.LoadCore("linux-x86_64_not_crashed.dmp")
|
||||
thread = process.GetThreadAtIndex(0)
|
||||
|
||||
expected_stack = {1: 'bar', 2: 'foo', 3: '_start'}
|
||||
self.assertGreaterEqual(thread.GetNumFrames(), len(expected_stack))
|
||||
for index, name in iteritems(expected_stack):
|
||||
frame = thread.GetFrameAtIndex(index)
|
||||
self.assertTrue(frame.IsValid())
|
||||
function_name = frame.GetFunctionName()
|
||||
self.assertTrue(name in function_name)
|
||||
|
||||
def test_local_variables_in_minidump(self):
|
||||
"""Test that we can examine local variables in a Minidump."""
|
||||
# Launch with the Minidump, and inspect a local variable.
|
||||
# target create linux-x86_64_not_crashed -c linux-x86_64_not_crashed.dmp
|
||||
target = self.dbg.CreateTarget("linux-x86_64_not_crashed")
|
||||
process = target.LoadCore("linux-x86_64_not_crashed.dmp")
|
||||
thread = process.GetThreadAtIndex(0)
|
||||
frame = thread.GetFrameAtIndex(1)
|
||||
value = frame.EvaluateExpression('x')
|
||||
self.assertEqual(value.GetValueAsSigned(), 3)
|
|
@ -0,0 +1,16 @@
|
|||
#include "client/linux/handler/exception_handler.h"
|
||||
|
||||
static bool dumpCallback(const google_breakpad::MinidumpDescriptor &descriptor,
|
||||
void *context, bool succeeded) {
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
google_breakpad::ExceptionHandler *eh;
|
||||
|
||||
void InstallBreakpad() {
|
||||
google_breakpad::MinidumpDescriptor descriptor("/tmp");
|
||||
eh = new google_breakpad::ExceptionHandler(descriptor, NULL, dumpCallback,
|
||||
NULL, true, -1);
|
||||
}
|
||||
|
||||
void WriteMinidump() { eh->WriteMinidump(); }
|
Binary file not shown.
|
@ -0,0 +1,12 @@
|
|||
void crash() {
|
||||
volatile int *a = (int *)(nullptr);
|
||||
*a = 1;
|
||||
}
|
||||
|
||||
extern "C" void _start();
|
||||
void InstallBreakpad();
|
||||
|
||||
void _start() {
|
||||
InstallBreakpad();
|
||||
crash();
|
||||
}
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,22 @@
|
|||
void InstallBreakpad();
|
||||
void WriteMinidump();
|
||||
|
||||
int global = 42;
|
||||
|
||||
int bar(int x) {
|
||||
WriteMinidump();
|
||||
int y = 4 * x + global;
|
||||
return y;
|
||||
}
|
||||
|
||||
int foo(int x) {
|
||||
int y = 2 * bar(3 * x);
|
||||
return y;
|
||||
}
|
||||
|
||||
extern "C" void _start();
|
||||
|
||||
void _start() {
|
||||
InstallBreakpad();
|
||||
foo(1);
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,29 @@
|
|||
# This makefile aims to make the binaries as small as possible, for us not to
|
||||
# upload huge binary blobs in the repo.
|
||||
# The binary should have debug symbols because stack unwinding doesn't work
|
||||
# correctly using the information in the Minidump only. Also we want to evaluate
|
||||
# local variables, etc.
|
||||
# Breakpad compiles as a static library, so statically linking againts it
|
||||
# makes the binary huge.
|
||||
# Dynamically linking to it does improve things, but we are still #include-ing
|
||||
# breakpad headers (which is a lot of source code for which we generate debug
|
||||
# symbols)
|
||||
# So, install_breakpad.cpp does the #include-ing and defines a global function
|
||||
# "InstallBreakpad" that does all the exception handler registration.
|
||||
# We compile install_breakpad to object file and then link it, alongside the
|
||||
# static libbreakpad, into a shared library.
|
||||
# Then the binaries dynamically link to that lib.
|
||||
# The other optimisation is not using the standard library (hence the _start
|
||||
# instead of main). We only link dynamically to some standard libraries.
|
||||
# This way we have a tiny binary (~8K) that has debug symbols and uses breakpad
|
||||
# to generate a Minidump when the binary crashes/requests such.
|
||||
#
|
||||
CC=g++
|
||||
FLAGS=-g --std=c++11
|
||||
INCLUDE=-I$HOME/breakpad/src/src/
|
||||
LINK=-L. -lbreakpad -lpthread -nostdlib -lc -lstdc++ -lgcc_s -fno-exceptions
|
||||
all:
|
||||
$(CC) $(FLAGS) -fPIC -c install_breakpad.cpp $(INCLUDE) -o install_breakpad.o
|
||||
ld -shared install_breakpad.o libbreakpad_client.a -o libbreakpad.so
|
||||
$(CC) $(FLAGS) -o linux-x86_64 linux-x86_64.cpp $(LINK)
|
||||
$(CC) $(FLAGS) -o linux-x86_64_not_crashed linux-x86_64_not_crashed.cpp $(LINK)
|
|
@ -78,6 +78,7 @@
|
|||
#include "Plugins/Platform/gdb-server/PlatformRemoteGDBServer.h"
|
||||
#include "Plugins/Process/elf-core/ProcessElfCore.h"
|
||||
#include "Plugins/Process/gdb-remote/ProcessGDBRemote.h"
|
||||
#include "Plugins/Process/minidump/ProcessMinidump.h"
|
||||
#include "Plugins/ScriptInterpreter/None/ScriptInterpreterNone.h"
|
||||
#include "Plugins/SymbolFile/DWARF/SymbolFileDWARF.h"
|
||||
#include "Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h"
|
||||
|
@ -304,6 +305,7 @@ void SystemInitializerFull::Initialize() {
|
|||
|
||||
JITLoaderGDB::Initialize();
|
||||
ProcessElfCore::Initialize();
|
||||
minidump::ProcessMinidump::Initialize();
|
||||
#if defined(_MSC_VER)
|
||||
ProcessWinMiniDump::Initialize();
|
||||
#endif
|
||||
|
@ -429,6 +431,7 @@ void SystemInitializerFull::Terminate() {
|
|||
|
||||
JITLoaderGDB::Terminate();
|
||||
ProcessElfCore::Terminate();
|
||||
minidump::ProcessMinidump::Terminate();
|
||||
#if defined(_MSC_VER)
|
||||
ProcessWinMiniDump::Terminate();
|
||||
#endif
|
||||
|
|
|
@ -5,4 +5,6 @@ add_lldb_library(lldbPluginProcessMinidump
|
|||
MinidumpParser.cpp
|
||||
RegisterContextMinidump_x86_32.cpp
|
||||
RegisterContextMinidump_x86_64.cpp
|
||||
ProcessMinidump.cpp
|
||||
ThreadMinidump.cpp
|
||||
)
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
// Project includes
|
||||
#include "MinidumpParser.h"
|
||||
#include "NtStructures.h"
|
||||
#include "RegisterContextMinidump_x86_32.h"
|
||||
|
||||
// Other libraries and framework includes
|
||||
#include "lldb/Target/MemoryRegionInfo.h"
|
||||
|
@ -106,11 +108,42 @@ llvm::ArrayRef<MinidumpThread> MinidumpParser::GetThreads() {
|
|||
llvm::ArrayRef<uint8_t>
|
||||
MinidumpParser::GetThreadContext(const MinidumpThread &td) {
|
||||
if (td.thread_context.rva + td.thread_context.data_size > GetData().size())
|
||||
return llvm::None;
|
||||
return {};
|
||||
|
||||
return GetData().slice(td.thread_context.rva, td.thread_context.data_size);
|
||||
}
|
||||
|
||||
llvm::ArrayRef<uint8_t>
|
||||
MinidumpParser::GetThreadContextWow64(const MinidumpThread &td) {
|
||||
// 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.
|
||||
auto teb_mem = GetMemory(td.teb, sizeof(TEB64));
|
||||
if (teb_mem.empty())
|
||||
return {};
|
||||
|
||||
const TEB64 *wow64teb;
|
||||
Error error = consumeObject(teb_mem, wow64teb);
|
||||
if (error.Fail())
|
||||
return {};
|
||||
|
||||
// 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
|
||||
auto context =
|
||||
GetMemory(wow64teb->tls_slots[1] + 4, sizeof(MinidumpContext_x86_32));
|
||||
if (context.size() < sizeof(MinidumpContext_x86_32))
|
||||
return {};
|
||||
|
||||
return context;
|
||||
// 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]).
|
||||
}
|
||||
|
||||
const MinidumpSystemInfo *MinidumpParser::GetSystemInfo() {
|
||||
llvm::ArrayRef<uint8_t> data = GetStream(MinidumpStreamType::SystemInfo);
|
||||
|
||||
|
@ -229,8 +262,7 @@ llvm::ArrayRef<MinidumpModule> MinidumpParser::GetModuleList() {
|
|||
|
||||
std::vector<const MinidumpModule *> MinidumpParser::GetFilteredModuleList() {
|
||||
llvm::ArrayRef<MinidumpModule> modules = GetModuleList();
|
||||
// mapping module_name to pair(load_address, pointer to module struct in
|
||||
// memory)
|
||||
// map module_name -> pair(load_address, pointer to module struct in memory)
|
||||
llvm::StringMap<std::pair<uint64_t, const MinidumpModule *>> lowest_addr;
|
||||
|
||||
std::vector<const MinidumpModule *> filtered_modules;
|
||||
|
|
|
@ -59,6 +59,8 @@ public:
|
|||
|
||||
llvm::ArrayRef<uint8_t> GetThreadContext(const MinidumpThread &td);
|
||||
|
||||
llvm::ArrayRef<uint8_t> GetThreadContextWow64(const MinidumpThread &td);
|
||||
|
||||
const MinidumpSystemInfo *GetSystemInfo();
|
||||
|
||||
ArchSpec GetArchitecture();
|
||||
|
|
|
@ -439,7 +439,8 @@ static_assert(sizeof(MinidumpModule) == 108,
|
|||
// Exception stuff
|
||||
struct MinidumpException {
|
||||
enum {
|
||||
MaxParams = 15,
|
||||
ExceptonInfoMaxParams = 15,
|
||||
DumpRequested = 0xFFFFFFFF,
|
||||
};
|
||||
|
||||
llvm::support::ulittle32_t exception_code;
|
||||
|
@ -448,7 +449,7 @@ struct MinidumpException {
|
|||
llvm::support::ulittle64_t exception_address;
|
||||
llvm::support::ulittle32_t number_parameters;
|
||||
llvm::support::ulittle32_t unused_alignment;
|
||||
llvm::support::ulittle64_t exception_information[MaxParams];
|
||||
llvm::support::ulittle64_t exception_information[ExceptonInfoMaxParams];
|
||||
};
|
||||
static_assert(sizeof(MinidumpException) == 152,
|
||||
"sizeof MinidumpException is not correct!");
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
//===-- 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_Minidump_NtStructures_h_
|
||||
#define liblldb_Plugins_Process_Minidump_NtStructures_h_
|
||||
|
||||
#include "llvm/Support/Endian.h"
|
||||
|
||||
namespace lldb_private {
|
||||
|
||||
namespace minidump {
|
||||
|
||||
// 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 tls_slots.
|
||||
struct TEB64 {
|
||||
llvm::support::ulittle64_t reserved1[12];
|
||||
llvm::support::ulittle64_t process_environment_block;
|
||||
llvm::support::ulittle64_t reserved2[399];
|
||||
uint8_t reserved3[1952];
|
||||
llvm::support::ulittle64_t tls_slots[64];
|
||||
uint8_t reserved4[8];
|
||||
llvm::support::ulittle64_t reserved5[26];
|
||||
llvm::support::ulittle64_t reserved_for_ole; // Windows 2000 only
|
||||
llvm::support::ulittle64_t reserved6[4];
|
||||
llvm::support::ulittle64_t tls_expansion_slots;
|
||||
};
|
||||
|
||||
#endif // liblldb_Plugins_Process_Minidump_NtStructures_h_
|
||||
} // namespace minidump
|
||||
} // namespace lldb_private
|
|
@ -0,0 +1,293 @@
|
|||
//===-- ProcessMinidump.cpp -------------------------------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
// Project includes
|
||||
#include "ProcessMinidump.h"
|
||||
#include "ThreadMinidump.h"
|
||||
|
||||
// Other libraries and framework includes
|
||||
#include "lldb/Core/DataBufferHeap.h"
|
||||
#include "lldb/Core/Log.h"
|
||||
#include "lldb/Core/Module.h"
|
||||
#include "lldb/Core/ModuleSpec.h"
|
||||
#include "lldb/Core/PluginManager.h"
|
||||
#include "lldb/Core/Section.h"
|
||||
#include "lldb/Core/State.h"
|
||||
#include "lldb/Target/DynamicLoader.h"
|
||||
#include "lldb/Target/MemoryRegionInfo.h"
|
||||
#include "lldb/Target/Target.h"
|
||||
#include "lldb/Target/UnixSignals.h"
|
||||
#include "lldb/Utility/LLDBAssert.h"
|
||||
|
||||
// C includes
|
||||
// C++ includes
|
||||
|
||||
using namespace lldb_private;
|
||||
using namespace minidump;
|
||||
|
||||
ConstString ProcessMinidump::GetPluginNameStatic() {
|
||||
static ConstString g_name("minidump");
|
||||
return g_name;
|
||||
}
|
||||
|
||||
const char *ProcessMinidump::GetPluginDescriptionStatic() {
|
||||
return "Minidump plug-in.";
|
||||
}
|
||||
|
||||
lldb::ProcessSP ProcessMinidump::CreateInstance(lldb::TargetSP target_sp,
|
||||
lldb::ListenerSP listener_sp,
|
||||
const FileSpec *crash_file) {
|
||||
if (!crash_file)
|
||||
return nullptr;
|
||||
|
||||
lldb::ProcessSP process_sp;
|
||||
// Read enough data for the Minidump header
|
||||
const size_t header_size = sizeof(MinidumpHeader);
|
||||
lldb::DataBufferSP data_sp(crash_file->MemoryMapFileContents(0, header_size));
|
||||
if (!data_sp)
|
||||
return nullptr;
|
||||
|
||||
// first, only try to parse the header, beacuse we need to be fast
|
||||
llvm::ArrayRef<uint8_t> header_data(data_sp->GetBytes(), header_size);
|
||||
const MinidumpHeader *header = MinidumpHeader::Parse(header_data);
|
||||
|
||||
if (data_sp->GetByteSize() != header_size || header == nullptr)
|
||||
return nullptr;
|
||||
|
||||
lldb::DataBufferSP all_data_sp(crash_file->MemoryMapFileContents());
|
||||
auto minidump_parser = MinidumpParser::Create(all_data_sp);
|
||||
// check if the parser object is valid
|
||||
// skip if the Minidump file is Windows generated, because we are still
|
||||
// work-in-progress
|
||||
if (!minidump_parser ||
|
||||
minidump_parser->GetArchitecture().GetTriple().getOS() ==
|
||||
llvm::Triple::OSType::Win32)
|
||||
return nullptr;
|
||||
|
||||
return std::make_shared<ProcessMinidump>(target_sp, listener_sp, *crash_file,
|
||||
minidump_parser.getValue());
|
||||
}
|
||||
|
||||
bool ProcessMinidump::CanDebug(lldb::TargetSP target_sp,
|
||||
bool plugin_specified_by_name) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ProcessMinidump::ProcessMinidump(lldb::TargetSP target_sp,
|
||||
lldb::ListenerSP listener_sp,
|
||||
const FileSpec &core_file,
|
||||
MinidumpParser minidump_parser)
|
||||
: Process(target_sp, listener_sp), m_minidump_parser(minidump_parser),
|
||||
m_core_file(core_file), m_is_wow64(false) {}
|
||||
|
||||
ProcessMinidump::~ProcessMinidump() {
|
||||
Clear();
|
||||
// We need to call finalize on the process before destroying ourselves
|
||||
// to make sure all of the broadcaster cleanup goes as planned. If we
|
||||
// destruct this class, then Process::~Process() might have problems
|
||||
// trying to fully destroy the broadcaster.
|
||||
Finalize();
|
||||
}
|
||||
|
||||
void ProcessMinidump::Initialize() {
|
||||
static std::once_flag g_once_flag;
|
||||
|
||||
std::call_once(g_once_flag, []() {
|
||||
PluginManager::RegisterPlugin(GetPluginNameStatic(),
|
||||
GetPluginDescriptionStatic(),
|
||||
ProcessMinidump::CreateInstance);
|
||||
});
|
||||
}
|
||||
|
||||
void ProcessMinidump::Terminate() {
|
||||
PluginManager::UnregisterPlugin(ProcessMinidump::CreateInstance);
|
||||
}
|
||||
|
||||
Error ProcessMinidump::DoLoadCore() {
|
||||
Error error;
|
||||
|
||||
m_thread_list = m_minidump_parser.GetThreads();
|
||||
m_active_exception = m_minidump_parser.GetExceptionStream();
|
||||
ReadModuleList();
|
||||
GetTarget().SetArchitecture(GetArchitecture());
|
||||
|
||||
llvm::Optional<lldb::pid_t> pid = m_minidump_parser.GetPid();
|
||||
if (!pid) {
|
||||
error.SetErrorString("failed to parse PID");
|
||||
return error;
|
||||
}
|
||||
SetID(pid.getValue());
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
DynamicLoader *ProcessMinidump::GetDynamicLoader() {
|
||||
if (m_dyld_ap.get() == nullptr)
|
||||
m_dyld_ap.reset(DynamicLoader::FindPlugin(this, nullptr));
|
||||
return m_dyld_ap.get();
|
||||
}
|
||||
|
||||
ConstString ProcessMinidump::GetPluginName() { return GetPluginNameStatic(); }
|
||||
|
||||
uint32_t ProcessMinidump::GetPluginVersion() { return 1; }
|
||||
|
||||
Error ProcessMinidump::DoDestroy() { return Error(); }
|
||||
|
||||
void ProcessMinidump::RefreshStateAfterStop() {
|
||||
if (!m_active_exception)
|
||||
return;
|
||||
|
||||
if (m_active_exception->exception_record.exception_code ==
|
||||
MinidumpException::DumpRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
lldb::StopInfoSP stop_info;
|
||||
lldb::ThreadSP stop_thread;
|
||||
|
||||
Process::m_thread_list.SetSelectedThreadByID(m_active_exception->thread_id);
|
||||
stop_thread = Process::m_thread_list.GetSelectedThread();
|
||||
ArchSpec arch = GetArchitecture();
|
||||
|
||||
if (arch.GetTriple().getOS() == llvm::Triple::Linux) {
|
||||
stop_info = StopInfo::CreateStopReasonWithSignal(
|
||||
*stop_thread, m_active_exception->exception_record.exception_code);
|
||||
} else {
|
||||
std::string desc;
|
||||
llvm::raw_string_ostream desc_stream(desc);
|
||||
desc_stream << "Exception "
|
||||
<< llvm::format_hex(
|
||||
m_active_exception->exception_record.exception_code, 8)
|
||||
<< " encountered at address "
|
||||
<< llvm::format_hex(
|
||||
m_active_exception->exception_record.exception_address,
|
||||
8);
|
||||
stop_info = StopInfo::CreateStopReasonWithException(
|
||||
*stop_thread, desc_stream.str().c_str());
|
||||
}
|
||||
|
||||
stop_thread->SetStopInfo(stop_info);
|
||||
}
|
||||
|
||||
bool ProcessMinidump::IsAlive() { return true; }
|
||||
|
||||
bool ProcessMinidump::WarnBeforeDetach() const { return false; }
|
||||
|
||||
size_t ProcessMinidump::ReadMemory(lldb::addr_t addr, void *buf, size_t size,
|
||||
lldb_private::Error &error) {
|
||||
// Don't allow the caching that lldb_private::Process::ReadMemory does
|
||||
// since we have it all cached in our dump file anyway.
|
||||
return DoReadMemory(addr, buf, size, error);
|
||||
}
|
||||
|
||||
size_t ProcessMinidump::DoReadMemory(lldb::addr_t addr, void *buf, size_t size,
|
||||
lldb_private::Error &error) {
|
||||
|
||||
llvm::ArrayRef<uint8_t> mem = m_minidump_parser.GetMemory(addr, size);
|
||||
if (mem.empty()) {
|
||||
error.SetErrorString("could not parse memory info");
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::memcpy(buf, mem.data(), mem.size());
|
||||
return mem.size();
|
||||
}
|
||||
|
||||
ArchSpec ProcessMinidump::GetArchitecture() {
|
||||
if (!m_is_wow64) {
|
||||
return m_minidump_parser.GetArchitecture();
|
||||
}
|
||||
|
||||
llvm::Triple triple;
|
||||
triple.setVendor(llvm::Triple::VendorType::UnknownVendor);
|
||||
triple.setArch(llvm::Triple::ArchType::x86);
|
||||
triple.setOS(llvm::Triple::OSType::Win32);
|
||||
return ArchSpec(triple);
|
||||
}
|
||||
|
||||
Error ProcessMinidump::GetMemoryRegionInfo(
|
||||
lldb::addr_t load_addr, lldb_private::MemoryRegionInfo &range_info) {
|
||||
Error error;
|
||||
auto info = m_minidump_parser.GetMemoryRegionInfo(load_addr);
|
||||
if (!info) {
|
||||
error.SetErrorString("No valid MemoryRegionInfo found!");
|
||||
return error;
|
||||
}
|
||||
range_info = info.getValue();
|
||||
return error;
|
||||
}
|
||||
|
||||
void ProcessMinidump::Clear() { Process::m_thread_list.Clear(); }
|
||||
|
||||
bool ProcessMinidump::UpdateThreadList(
|
||||
lldb_private::ThreadList &old_thread_list,
|
||||
lldb_private::ThreadList &new_thread_list) {
|
||||
uint32_t num_threads = 0;
|
||||
if (m_thread_list.size() > 0)
|
||||
num_threads = m_thread_list.size();
|
||||
|
||||
for (lldb::tid_t tid = 0; tid < num_threads; ++tid) {
|
||||
llvm::ArrayRef<uint8_t> context;
|
||||
if (!m_is_wow64)
|
||||
context = m_minidump_parser.GetThreadContext(m_thread_list[tid]);
|
||||
else
|
||||
context = m_minidump_parser.GetThreadContextWow64(m_thread_list[tid]);
|
||||
|
||||
lldb::ThreadSP thread_sp(
|
||||
new ThreadMinidump(*this, m_thread_list[tid], context));
|
||||
new_thread_list.AddThread(thread_sp);
|
||||
}
|
||||
return new_thread_list.GetSize(false) > 0;
|
||||
}
|
||||
|
||||
void ProcessMinidump::ReadModuleList() {
|
||||
std::vector<const MinidumpModule *> filtered_modules =
|
||||
m_minidump_parser.GetFilteredModuleList();
|
||||
|
||||
for (auto module : filtered_modules) {
|
||||
llvm::Optional<std::string> name =
|
||||
m_minidump_parser.GetMinidumpString(module->module_name_rva);
|
||||
|
||||
if (!name)
|
||||
continue;
|
||||
|
||||
Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_MODULES));
|
||||
if (log) {
|
||||
log->Printf(
|
||||
"ProcessMinidump::%s found module: name: %s %#010lx-%#010lx size: %u",
|
||||
__FUNCTION__, name.getValue().c_str(),
|
||||
uint64_t(module->base_of_image),
|
||||
module->base_of_image + module->size_of_image,
|
||||
uint32_t(module->size_of_image));
|
||||
}
|
||||
|
||||
// check if the process is wow64 - a 32 bit windows process running on a
|
||||
// 64 bit windows
|
||||
if (llvm::StringRef(name.getValue()).endswith_lower("wow64.dll")) {
|
||||
m_is_wow64 = true;
|
||||
}
|
||||
|
||||
const auto file_spec = FileSpec(name.getValue(), true);
|
||||
ModuleSpec module_spec = file_spec;
|
||||
Error error;
|
||||
lldb::ModuleSP module_sp = GetTarget().GetSharedModule(module_spec, &error);
|
||||
if (!module_sp || error.Fail()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (log) {
|
||||
log->Printf("ProcessMinidump::%s load module: name: %s", __FUNCTION__,
|
||||
name.getValue().c_str());
|
||||
}
|
||||
|
||||
bool load_addr_changed = false;
|
||||
module_sp->SetLoadAddress(GetTarget(), module->base_of_image, false,
|
||||
load_addr_changed);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
//===-- ProcessMinidump.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_ProcessMinidump_h_
|
||||
#define liblldb_ProcessMinidump_h_
|
||||
|
||||
// Project includes
|
||||
#include "MinidumpParser.h"
|
||||
#include "MinidumpTypes.h"
|
||||
|
||||
// Other libraries and framework includes
|
||||
#include "lldb/Core/ConstString.h"
|
||||
#include "lldb/Core/Error.h"
|
||||
#include "lldb/Target/Process.h"
|
||||
#include "lldb/Target/StopInfo.h"
|
||||
#include "lldb/Target/Target.h"
|
||||
|
||||
#include "llvm/Support/Format.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
|
||||
// C Includes
|
||||
// C++ Includes
|
||||
|
||||
namespace lldb_private {
|
||||
|
||||
namespace minidump {
|
||||
|
||||
class ProcessMinidump : public Process {
|
||||
public:
|
||||
static lldb::ProcessSP CreateInstance(lldb::TargetSP target_sp,
|
||||
lldb::ListenerSP listener_sp,
|
||||
const FileSpec *crash_file_path);
|
||||
|
||||
static void Initialize();
|
||||
|
||||
static void Terminate();
|
||||
|
||||
static ConstString GetPluginNameStatic();
|
||||
|
||||
static const char *GetPluginDescriptionStatic();
|
||||
|
||||
ProcessMinidump(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp,
|
||||
const lldb_private::FileSpec &core_file,
|
||||
MinidumpParser minidump_parser);
|
||||
|
||||
~ProcessMinidump() override;
|
||||
|
||||
bool CanDebug(lldb::TargetSP target_sp,
|
||||
bool plugin_specified_by_name) override;
|
||||
|
||||
Error DoLoadCore() override;
|
||||
|
||||
DynamicLoader *GetDynamicLoader() override;
|
||||
|
||||
ConstString GetPluginName() override;
|
||||
|
||||
uint32_t GetPluginVersion() override;
|
||||
|
||||
Error DoDestroy() override;
|
||||
|
||||
void RefreshStateAfterStop() override;
|
||||
|
||||
bool IsAlive() override;
|
||||
|
||||
bool WarnBeforeDetach() const override;
|
||||
|
||||
size_t ReadMemory(lldb::addr_t addr, void *buf, size_t size,
|
||||
Error &error) override;
|
||||
|
||||
size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size,
|
||||
Error &error) override;
|
||||
|
||||
ArchSpec GetArchitecture();
|
||||
|
||||
Error GetMemoryRegionInfo(lldb::addr_t load_addr,
|
||||
MemoryRegionInfo &range_info) override;
|
||||
|
||||
MinidumpParser m_minidump_parser;
|
||||
|
||||
protected:
|
||||
void Clear();
|
||||
|
||||
bool UpdateThreadList(ThreadList &old_thread_list,
|
||||
ThreadList &new_thread_list) override;
|
||||
|
||||
void ReadModuleList();
|
||||
|
||||
private:
|
||||
FileSpec m_core_file;
|
||||
llvm::ArrayRef<MinidumpThread> m_thread_list;
|
||||
const MinidumpExceptionStream *m_active_exception;
|
||||
bool m_is_wow64;
|
||||
};
|
||||
|
||||
} // namespace minidump
|
||||
} // namespace lldb_private
|
||||
|
||||
#endif // liblldb_ProcessMinidump_h_
|
|
@ -0,0 +1,114 @@
|
|||
//===-- ThreadMinidump.cpp --------------------------------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
// Project includes
|
||||
#include "ThreadMinidump.h"
|
||||
#include "ProcessMinidump.h"
|
||||
|
||||
#include "RegisterContextMinidump_x86_32.h"
|
||||
#include "RegisterContextMinidump_x86_64.h"
|
||||
|
||||
// Other libraries and framework includes
|
||||
#include "Plugins/Process/Utility/RegisterContextLinux_i386.h"
|
||||
#include "Plugins/Process/Utility/RegisterContextLinux_x86_64.h"
|
||||
|
||||
#include "Plugins/Process/elf-core/RegisterContextPOSIXCore_x86_64.h"
|
||||
|
||||
#include "lldb/Core/DataExtractor.h"
|
||||
#include "lldb/Core/Log.h"
|
||||
#include "lldb/Target/RegisterContext.h"
|
||||
#include "lldb/Target/StopInfo.h"
|
||||
#include "lldb/Target/Target.h"
|
||||
#include "lldb/Target/Unwind.h"
|
||||
|
||||
// C Includes
|
||||
// C++ Includes
|
||||
|
||||
using namespace lldb;
|
||||
using namespace lldb_private;
|
||||
using namespace minidump;
|
||||
|
||||
ThreadMinidump::ThreadMinidump(Process &process, const MinidumpThread &td,
|
||||
llvm::ArrayRef<uint8_t> gpregset_data)
|
||||
: Thread(process, td.thread_id), m_thread_reg_ctx_sp(),
|
||||
m_gpregset_data(gpregset_data) {}
|
||||
|
||||
ThreadMinidump::~ThreadMinidump() {}
|
||||
|
||||
void ThreadMinidump::RefreshStateAfterStop() {}
|
||||
|
||||
void ThreadMinidump::ClearStackFrames() {}
|
||||
|
||||
RegisterContextSP ThreadMinidump::GetRegisterContext() {
|
||||
if (!m_reg_context_sp) {
|
||||
m_reg_context_sp = CreateRegisterContextForFrame(nullptr);
|
||||
}
|
||||
return m_reg_context_sp;
|
||||
}
|
||||
|
||||
RegisterContextSP
|
||||
ThreadMinidump::CreateRegisterContextForFrame(StackFrame *frame) {
|
||||
RegisterContextSP reg_ctx_sp;
|
||||
uint32_t concrete_frame_idx = 0;
|
||||
Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_THREAD));
|
||||
|
||||
if (frame)
|
||||
concrete_frame_idx = frame->GetConcreteFrameIndex();
|
||||
|
||||
if (concrete_frame_idx == 0) {
|
||||
if (m_thread_reg_ctx_sp)
|
||||
return m_thread_reg_ctx_sp;
|
||||
|
||||
ProcessMinidump *process =
|
||||
static_cast<ProcessMinidump *>(GetProcess().get());
|
||||
ArchSpec arch = process->GetArchitecture();
|
||||
RegisterInfoInterface *reg_interface = nullptr;
|
||||
|
||||
// TODO write other register contexts and add them here
|
||||
switch (arch.GetMachine()) {
|
||||
case llvm::Triple::x86: {
|
||||
reg_interface = new RegisterContextLinux_i386(arch);
|
||||
lldb::DataBufferSP buf =
|
||||
ConvertMinidumpContext_x86_32(m_gpregset_data, reg_interface);
|
||||
DataExtractor gpregs(buf, lldb::eByteOrderLittle, 4);
|
||||
DataExtractor fpregs;
|
||||
m_thread_reg_ctx_sp.reset(new RegisterContextCorePOSIX_x86_64(
|
||||
*this, reg_interface, gpregs, fpregs));
|
||||
break;
|
||||
}
|
||||
case llvm::Triple::x86_64: {
|
||||
reg_interface = new RegisterContextLinux_x86_64(arch);
|
||||
lldb::DataBufferSP buf =
|
||||
ConvertMinidumpContext_x86_64(m_gpregset_data, reg_interface);
|
||||
DataExtractor gpregs(buf, lldb::eByteOrderLittle, 8);
|
||||
DataExtractor fpregs;
|
||||
m_thread_reg_ctx_sp.reset(new RegisterContextCorePOSIX_x86_64(
|
||||
*this, reg_interface, gpregs, fpregs));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!reg_interface) {
|
||||
if (log)
|
||||
log->Printf("elf-core::%s:: Architecture(%d) not supported",
|
||||
__FUNCTION__, arch.GetMachine());
|
||||
assert(false && "Architecture not supported");
|
||||
}
|
||||
|
||||
reg_ctx_sp = m_thread_reg_ctx_sp;
|
||||
} else if (m_unwinder_ap) {
|
||||
reg_ctx_sp = m_unwinder_ap->CreateRegisterContextForFrame(frame);
|
||||
}
|
||||
|
||||
return reg_ctx_sp;
|
||||
}
|
||||
|
||||
bool ThreadMinidump::CalculateStopInfo() { return false; }
|
|
@ -0,0 +1,52 @@
|
|||
//===-- ThreadMinidump.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_ThreadMinidump_h_
|
||||
#define liblldb_ThreadMinidump_h_
|
||||
|
||||
// Project includes
|
||||
#include "MinidumpTypes.h"
|
||||
|
||||
// Other libraries and framework includes
|
||||
#include "lldb/Target/Thread.h"
|
||||
|
||||
// C Includes
|
||||
// C++ Includes
|
||||
|
||||
namespace lldb_private {
|
||||
|
||||
namespace minidump {
|
||||
|
||||
class ThreadMinidump : public Thread {
|
||||
public:
|
||||
ThreadMinidump(Process &process, const MinidumpThread &td,
|
||||
llvm::ArrayRef<uint8_t> gpregset_data);
|
||||
|
||||
~ThreadMinidump() override;
|
||||
|
||||
void RefreshStateAfterStop() override;
|
||||
|
||||
lldb::RegisterContextSP GetRegisterContext() override;
|
||||
|
||||
lldb::RegisterContextSP
|
||||
CreateRegisterContextForFrame(StackFrame *frame) override;
|
||||
|
||||
void ClearStackFrames() override;
|
||||
|
||||
protected:
|
||||
lldb::RegisterContextSP m_thread_reg_ctx_sp;
|
||||
llvm::ArrayRef<uint8_t> m_gpregset_data;
|
||||
|
||||
bool CalculateStopInfo() override;
|
||||
};
|
||||
|
||||
} // namespace minidump
|
||||
} // namespace lldb_private
|
||||
|
||||
#endif // liblldb_ThreadMinidump_h_
|
|
@ -134,7 +134,7 @@ TEST_F(MinidumpParserTest, GetModuleList) {
|
|||
llvm::Optional<std::string> name =
|
||||
parser->GetMinidumpString(modules[i].module_name_rva);
|
||||
ASSERT_TRUE(name.hasValue());
|
||||
ASSERT_EQ(module_names[i], name.getValue());
|
||||
EXPECT_EQ(module_names[i], name.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -275,8 +275,46 @@ TEST_F(MinidumpParserTest, GetPidWindows) {
|
|||
ASSERT_EQ(4440UL, pid.getValue());
|
||||
}
|
||||
|
||||
// Register stuff
|
||||
// TODO probably split register stuff tests into different file?
|
||||
// wow64
|
||||
TEST_F(MinidumpParserTest, GetPidWow64) {
|
||||
SetUpData("fizzbuzz_wow64.dmp");
|
||||
llvm::Optional<lldb::pid_t> pid = parser->GetPid();
|
||||
ASSERT_TRUE(pid.hasValue());
|
||||
ASSERT_EQ(7836UL, pid.getValue());
|
||||
}
|
||||
|
||||
TEST_F(MinidumpParserTest, GetModuleListWow64) {
|
||||
SetUpData("fizzbuzz_wow64.dmp");
|
||||
llvm::ArrayRef<MinidumpModule> modules = parser->GetModuleList();
|
||||
ASSERT_EQ(16UL, modules.size());
|
||||
std::string module_names[16] = {
|
||||
R"(D:\src\llvm\llvm\tools\lldb\packages\Python\lldbsuite\test\functionalities\postmortem\wow64_minidump\fizzbuzz.exe)",
|
||||
R"(C:\Windows\System32\ntdll.dll)",
|
||||
R"(C:\Windows\System32\wow64.dll)",
|
||||
R"(C:\Windows\System32\wow64win.dll)",
|
||||
R"(C:\Windows\System32\wow64cpu.dll)",
|
||||
R"(D:\src\llvm\llvm\tools\lldb\packages\Python\lldbsuite\test\functionalities\postmortem\wow64_minidump\fizzbuzz.exe)",
|
||||
R"(C:\Windows\SysWOW64\ntdll.dll)",
|
||||
R"(C:\Windows\SysWOW64\kernel32.dll)",
|
||||
R"(C:\Windows\SysWOW64\KERNELBASE.dll)",
|
||||
R"(C:\Windows\SysWOW64\advapi32.dll)",
|
||||
R"(C:\Windows\SysWOW64\msvcrt.dll)",
|
||||
R"(C:\Windows\SysWOW64\sechost.dll)",
|
||||
R"(C:\Windows\SysWOW64\rpcrt4.dll)",
|
||||
R"(C:\Windows\SysWOW64\sspicli.dll)",
|
||||
R"(C:\Windows\SysWOW64\CRYPTBASE.dll)",
|
||||
R"(C:\Windows\System32\api-ms-win-core-synch-l1-2-0.DLL)",
|
||||
};
|
||||
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
llvm::Optional<std::string> name =
|
||||
parser->GetMinidumpString(modules[i].module_name_rva);
|
||||
ASSERT_TRUE(name.hasValue());
|
||||
EXPECT_EQ(module_names[i], name.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
// Register tests
|
||||
#define REG_VAL32(x) *(reinterpret_cast<uint32_t *>(x))
|
||||
#define REG_VAL64(x) *(reinterpret_cast<uint64_t *>(x))
|
||||
|
||||
|
@ -371,3 +409,45 @@ TEST_F(MinidumpParserTest, ConvertMinidumpContext_x86_64) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(MinidumpParserTest, ConvertMinidumpContext_x86_32_wow64) {
|
||||
SetUpData("fizzbuzz_wow64.dmp");
|
||||
llvm::ArrayRef<MinidumpThread> thread_list = parser->GetThreads();
|
||||
const MinidumpThread thread = thread_list[0];
|
||||
llvm::ArrayRef<uint8_t> registers(parser->GetThreadContextWow64(thread));
|
||||
|
||||
ArchSpec arch = parser->GetArchitecture();
|
||||
RegisterInfoInterface *reg_interface = new RegisterContextLinux_i386(arch);
|
||||
lldb::DataBufferSP buf =
|
||||
ConvertMinidumpContext_x86_32(registers, reg_interface);
|
||||
ASSERT_EQ(reg_interface->GetGPRSize(), buf->GetByteSize());
|
||||
|
||||
const RegisterInfo *reg_info = reg_interface->GetRegisterInfo();
|
||||
|
||||
std::map<uint64_t, uint32_t> reg_values;
|
||||
|
||||
reg_values[lldb_eax_i386] = 0x00000000;
|
||||
reg_values[lldb_ebx_i386] = 0x0037f608;
|
||||
reg_values[lldb_ecx_i386] = 0x00e61578;
|
||||
reg_values[lldb_edx_i386] = 0x00000008;
|
||||
reg_values[lldb_edi_i386] = 0x00000000;
|
||||
reg_values[lldb_esi_i386] = 0x00000002;
|
||||
reg_values[lldb_ebp_i386] = 0x0037f654;
|
||||
reg_values[lldb_esp_i386] = 0x0037f5b8;
|
||||
reg_values[lldb_eip_i386] = 0x77ce01fd;
|
||||
reg_values[lldb_eflags_i386] = 0x00000246;
|
||||
reg_values[lldb_cs_i386] = 0x00000023;
|
||||
reg_values[lldb_fs_i386] = 0x00000053;
|
||||
reg_values[lldb_gs_i386] = 0x0000002b;
|
||||
reg_values[lldb_ss_i386] = 0x0000002b;
|
||||
reg_values[lldb_ds_i386] = 0x0000002b;
|
||||
reg_values[lldb_es_i386] = 0x0000002b;
|
||||
|
||||
for (uint32_t reg_index = 0; reg_index < reg_interface->GetRegisterCount();
|
||||
++reg_index) {
|
||||
if (reg_values.find(reg_index) != reg_values.end()) {
|
||||
EXPECT_EQ(reg_values[reg_index],
|
||||
REG_VAL32(buf->GetBytes() + reg_info[reg_index].byte_offset));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue