forked from OSchip/llvm-project
1347 lines
45 KiB
C++
1347 lines
45 KiB
C++
//===-- Disassembler.cpp ----------------------------------------*- C++ -*-===//
|
|
//
|
|
// The LLVM Compiler Infrastructure
|
|
//
|
|
// This file is distributed under the University of Illinois Open Source
|
|
// License. See LICENSE.TXT for details.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "lldb/Core/Disassembler.h"
|
|
|
|
// C Includes
|
|
// C++ Includes
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <limits>
|
|
|
|
// Other libraries and framework includes
|
|
// Project includes
|
|
#include "lldb/lldb-private.h"
|
|
#include "lldb/Core/Error.h"
|
|
#include "lldb/Core/DataBufferHeap.h"
|
|
#include "lldb/Core/DataExtractor.h"
|
|
#include "lldb/Core/Debugger.h"
|
|
#include "lldb/Core/EmulateInstruction.h"
|
|
#include "lldb/Core/Module.h"
|
|
#include "lldb/Core/PluginManager.h"
|
|
#include "lldb/Core/RegularExpression.h"
|
|
#include "lldb/Core/Timer.h"
|
|
#include "lldb/Interpreter/OptionValue.h"
|
|
#include "lldb/Interpreter/OptionValueArray.h"
|
|
#include "lldb/Interpreter/OptionValueDictionary.h"
|
|
#include "lldb/Interpreter/OptionValueString.h"
|
|
#include "lldb/Interpreter/OptionValueUInt64.h"
|
|
#include "lldb/Symbol/Function.h"
|
|
#include "lldb/Symbol/ObjectFile.h"
|
|
#include "lldb/Target/ExecutionContext.h"
|
|
#include "lldb/Target/Process.h"
|
|
#include "lldb/Target/SectionLoadList.h"
|
|
#include "lldb/Target/StackFrame.h"
|
|
#include "lldb/Target/Target.h"
|
|
|
|
#define DEFAULT_DISASM_BYTE_SIZE 32
|
|
|
|
using namespace lldb;
|
|
using namespace lldb_private;
|
|
|
|
DisassemblerSP
|
|
Disassembler::FindPlugin (const ArchSpec &arch, const char *flavor, const char *plugin_name)
|
|
{
|
|
Timer scoped_timer (__PRETTY_FUNCTION__,
|
|
"Disassembler::FindPlugin (arch = %s, plugin_name = %s)",
|
|
arch.GetArchitectureName(),
|
|
plugin_name);
|
|
|
|
DisassemblerCreateInstance create_callback = nullptr;
|
|
|
|
if (plugin_name)
|
|
{
|
|
ConstString const_plugin_name (plugin_name);
|
|
create_callback = PluginManager::GetDisassemblerCreateCallbackForPluginName (const_plugin_name);
|
|
if (create_callback)
|
|
{
|
|
DisassemblerSP disassembler_sp(create_callback(arch, flavor));
|
|
|
|
if (disassembler_sp)
|
|
return disassembler_sp;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (uint32_t idx = 0; (create_callback = PluginManager::GetDisassemblerCreateCallbackAtIndex(idx)) != nullptr; ++idx)
|
|
{
|
|
DisassemblerSP disassembler_sp(create_callback(arch, flavor));
|
|
|
|
if (disassembler_sp)
|
|
return disassembler_sp;
|
|
}
|
|
}
|
|
return DisassemblerSP();
|
|
}
|
|
|
|
DisassemblerSP
|
|
Disassembler::FindPluginForTarget(const TargetSP target_sp, const ArchSpec &arch, const char *flavor, const char *plugin_name)
|
|
{
|
|
if (target_sp && flavor == nullptr)
|
|
{
|
|
// FIXME - we don't have the mechanism in place to do per-architecture settings. But since we know that for now
|
|
// we only support flavors on x86 & x86_64,
|
|
if (arch.GetTriple().getArch() == llvm::Triple::x86
|
|
|| arch.GetTriple().getArch() == llvm::Triple::x86_64)
|
|
flavor = target_sp->GetDisassemblyFlavor();
|
|
}
|
|
return FindPlugin(arch, flavor, plugin_name);
|
|
}
|
|
|
|
static void
|
|
ResolveAddress (const ExecutionContext &exe_ctx,
|
|
const Address &addr,
|
|
Address &resolved_addr)
|
|
{
|
|
if (!addr.IsSectionOffset())
|
|
{
|
|
// If we weren't passed in a section offset address range,
|
|
// try and resolve it to something
|
|
Target *target = exe_ctx.GetTargetPtr();
|
|
if (target)
|
|
{
|
|
if (target->GetSectionLoadList().IsEmpty())
|
|
{
|
|
target->GetImages().ResolveFileAddress (addr.GetOffset(), resolved_addr);
|
|
}
|
|
else
|
|
{
|
|
target->GetSectionLoadList().ResolveLoadAddress (addr.GetOffset(), resolved_addr);
|
|
}
|
|
// We weren't able to resolve the address, just treat it as a
|
|
// raw address
|
|
if (resolved_addr.IsValid())
|
|
return;
|
|
}
|
|
}
|
|
resolved_addr = addr;
|
|
}
|
|
|
|
size_t
|
|
Disassembler::Disassemble(Debugger &debugger,
|
|
const ArchSpec &arch,
|
|
const char *plugin_name,
|
|
const char *flavor,
|
|
const ExecutionContext &exe_ctx,
|
|
SymbolContextList &sc_list,
|
|
uint32_t num_instructions,
|
|
uint32_t num_mixed_context_lines,
|
|
uint32_t options,
|
|
Stream &strm)
|
|
{
|
|
size_t success_count = 0;
|
|
const size_t count = sc_list.GetSize();
|
|
SymbolContext sc;
|
|
AddressRange range;
|
|
const uint32_t scope = eSymbolContextBlock | eSymbolContextFunction | eSymbolContextSymbol;
|
|
const bool use_inline_block_range = true;
|
|
for (size_t i = 0; i < count; ++i)
|
|
{
|
|
if (!sc_list.GetContextAtIndex(i, sc))
|
|
break;
|
|
for (uint32_t range_idx = 0; sc.GetAddressRange(scope, range_idx, use_inline_block_range, range); ++range_idx)
|
|
{
|
|
if (Disassemble (debugger,
|
|
arch,
|
|
plugin_name,
|
|
flavor,
|
|
exe_ctx,
|
|
range,
|
|
num_instructions,
|
|
num_mixed_context_lines,
|
|
options,
|
|
strm))
|
|
{
|
|
++success_count;
|
|
strm.EOL();
|
|
}
|
|
}
|
|
}
|
|
return success_count;
|
|
}
|
|
|
|
bool
|
|
Disassembler::Disassemble(Debugger &debugger,
|
|
const ArchSpec &arch,
|
|
const char *plugin_name,
|
|
const char *flavor,
|
|
const ExecutionContext &exe_ctx,
|
|
const ConstString &name,
|
|
Module *module,
|
|
uint32_t num_instructions,
|
|
uint32_t num_mixed_context_lines,
|
|
uint32_t options,
|
|
Stream &strm)
|
|
{
|
|
SymbolContextList sc_list;
|
|
if (name)
|
|
{
|
|
const bool include_symbols = true;
|
|
const bool include_inlines = true;
|
|
if (module)
|
|
{
|
|
module->FindFunctions(name,
|
|
nullptr,
|
|
eFunctionNameTypeAuto,
|
|
include_symbols,
|
|
include_inlines,
|
|
true,
|
|
sc_list);
|
|
}
|
|
else if (exe_ctx.GetTargetPtr())
|
|
{
|
|
exe_ctx.GetTargetPtr()->GetImages().FindFunctions (name,
|
|
eFunctionNameTypeAuto,
|
|
include_symbols,
|
|
include_inlines,
|
|
false,
|
|
sc_list);
|
|
}
|
|
}
|
|
|
|
if (sc_list.GetSize ())
|
|
{
|
|
return Disassemble (debugger,
|
|
arch,
|
|
plugin_name,
|
|
flavor,
|
|
exe_ctx,
|
|
sc_list,
|
|
num_instructions,
|
|
num_mixed_context_lines,
|
|
options,
|
|
strm);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
lldb::DisassemblerSP
|
|
Disassembler::DisassembleRange(const ArchSpec &arch,
|
|
const char *plugin_name,
|
|
const char *flavor,
|
|
const ExecutionContext &exe_ctx,
|
|
const AddressRange &range,
|
|
bool prefer_file_cache)
|
|
{
|
|
lldb::DisassemblerSP disasm_sp;
|
|
if (range.GetByteSize() > 0 && range.GetBaseAddress().IsValid())
|
|
{
|
|
disasm_sp = Disassembler::FindPluginForTarget(exe_ctx.GetTargetSP(), arch, flavor, plugin_name);
|
|
|
|
if (disasm_sp)
|
|
{
|
|
size_t bytes_disassembled = disasm_sp->ParseInstructions(&exe_ctx, range, nullptr, prefer_file_cache);
|
|
if (bytes_disassembled == 0)
|
|
disasm_sp.reset();
|
|
}
|
|
}
|
|
return disasm_sp;
|
|
}
|
|
|
|
lldb::DisassemblerSP
|
|
Disassembler::DisassembleBytes (const ArchSpec &arch,
|
|
const char *plugin_name,
|
|
const char *flavor,
|
|
const Address &start,
|
|
const void *src,
|
|
size_t src_len,
|
|
uint32_t num_instructions,
|
|
bool data_from_file)
|
|
{
|
|
lldb::DisassemblerSP disasm_sp;
|
|
|
|
if (src)
|
|
{
|
|
disasm_sp = Disassembler::FindPlugin(arch, flavor, plugin_name);
|
|
|
|
if (disasm_sp)
|
|
{
|
|
DataExtractor data(src, src_len, arch.GetByteOrder(), arch.GetAddressByteSize());
|
|
|
|
(void)disasm_sp->DecodeInstructions (start,
|
|
data,
|
|
0,
|
|
num_instructions,
|
|
false,
|
|
data_from_file);
|
|
}
|
|
}
|
|
|
|
return disasm_sp;
|
|
}
|
|
|
|
bool
|
|
Disassembler::Disassemble(Debugger &debugger,
|
|
const ArchSpec &arch,
|
|
const char *plugin_name,
|
|
const char *flavor,
|
|
const ExecutionContext &exe_ctx,
|
|
const AddressRange &disasm_range,
|
|
uint32_t num_instructions,
|
|
uint32_t num_mixed_context_lines,
|
|
uint32_t options,
|
|
Stream &strm)
|
|
{
|
|
if (disasm_range.GetByteSize())
|
|
{
|
|
lldb::DisassemblerSP disasm_sp (Disassembler::FindPluginForTarget(exe_ctx.GetTargetSP(), arch, flavor, plugin_name));
|
|
|
|
if (disasm_sp)
|
|
{
|
|
AddressRange range;
|
|
ResolveAddress (exe_ctx, disasm_range.GetBaseAddress(), range.GetBaseAddress());
|
|
range.SetByteSize (disasm_range.GetByteSize());
|
|
const bool prefer_file_cache = false;
|
|
size_t bytes_disassembled = disasm_sp->ParseInstructions (&exe_ctx, range, &strm, prefer_file_cache);
|
|
if (bytes_disassembled == 0)
|
|
return false;
|
|
|
|
bool result = PrintInstructions (disasm_sp.get(),
|
|
debugger,
|
|
arch,
|
|
exe_ctx,
|
|
num_instructions,
|
|
num_mixed_context_lines,
|
|
options,
|
|
strm);
|
|
|
|
// FIXME: The DisassemblerLLVMC has a reference cycle and won't go away if it has any active instructions.
|
|
// I'll fix that but for now, just clear the list and it will go away nicely.
|
|
disasm_sp->GetInstructionList().Clear();
|
|
return result;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
Disassembler::Disassemble(Debugger &debugger,
|
|
const ArchSpec &arch,
|
|
const char *plugin_name,
|
|
const char *flavor,
|
|
const ExecutionContext &exe_ctx,
|
|
const Address &start_address,
|
|
uint32_t num_instructions,
|
|
uint32_t num_mixed_context_lines,
|
|
uint32_t options,
|
|
Stream &strm)
|
|
{
|
|
if (num_instructions > 0)
|
|
{
|
|
lldb::DisassemblerSP disasm_sp (Disassembler::FindPluginForTarget(exe_ctx.GetTargetSP(),
|
|
arch,
|
|
flavor,
|
|
plugin_name));
|
|
if (disasm_sp)
|
|
{
|
|
Address addr;
|
|
ResolveAddress (exe_ctx, start_address, addr);
|
|
const bool prefer_file_cache = false;
|
|
size_t bytes_disassembled = disasm_sp->ParseInstructions (&exe_ctx,
|
|
addr,
|
|
num_instructions,
|
|
prefer_file_cache);
|
|
if (bytes_disassembled == 0)
|
|
return false;
|
|
bool result = PrintInstructions (disasm_sp.get(),
|
|
debugger,
|
|
arch,
|
|
exe_ctx,
|
|
num_instructions,
|
|
num_mixed_context_lines,
|
|
options,
|
|
strm);
|
|
|
|
// FIXME: The DisassemblerLLVMC has a reference cycle and won't go away if it has any active instructions.
|
|
// I'll fix that but for now, just clear the list and it will go away nicely.
|
|
disasm_sp->GetInstructionList().Clear();
|
|
return result;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
Disassembler::PrintInstructions(Disassembler *disasm_ptr,
|
|
Debugger &debugger,
|
|
const ArchSpec &arch,
|
|
const ExecutionContext &exe_ctx,
|
|
uint32_t num_instructions,
|
|
uint32_t num_mixed_context_lines,
|
|
uint32_t options,
|
|
Stream &strm)
|
|
{
|
|
// We got some things disassembled...
|
|
size_t num_instructions_found = disasm_ptr->GetInstructionList().GetSize();
|
|
|
|
if (num_instructions > 0 && num_instructions < num_instructions_found)
|
|
num_instructions_found = num_instructions;
|
|
|
|
const uint32_t max_opcode_byte_size = disasm_ptr->GetInstructionList().GetMaxOpcocdeByteSize ();
|
|
uint32_t offset = 0;
|
|
SymbolContext sc;
|
|
SymbolContext prev_sc;
|
|
AddressRange sc_range;
|
|
const Address *pc_addr_ptr = nullptr;
|
|
StackFrame *frame = exe_ctx.GetFramePtr();
|
|
|
|
TargetSP target_sp (exe_ctx.GetTargetSP());
|
|
SourceManager &source_manager = target_sp ? target_sp->GetSourceManager() : debugger.GetSourceManager();
|
|
|
|
if (frame)
|
|
{
|
|
pc_addr_ptr = &frame->GetFrameCodeAddress();
|
|
}
|
|
const uint32_t scope = eSymbolContextLineEntry | eSymbolContextFunction | eSymbolContextSymbol;
|
|
const bool use_inline_block_range = false;
|
|
|
|
const FormatEntity::Entry *disassembly_format = nullptr;
|
|
FormatEntity::Entry format;
|
|
if (exe_ctx.HasTargetScope())
|
|
{
|
|
disassembly_format = exe_ctx.GetTargetRef().GetDebugger().GetDisassemblyFormat ();
|
|
}
|
|
else
|
|
{
|
|
FormatEntity::Parse("${addr}: ", format);
|
|
disassembly_format = &format;
|
|
}
|
|
|
|
// First pass: step through the list of instructions,
|
|
// find how long the initial addresses strings are, insert padding
|
|
// in the second pass so the opcodes all line up nicely.
|
|
size_t address_text_size = 0;
|
|
for (size_t i = 0; i < num_instructions_found; ++i)
|
|
{
|
|
Instruction *inst = disasm_ptr->GetInstructionList().GetInstructionAtIndex (i).get();
|
|
if (inst)
|
|
{
|
|
const Address &addr = inst->GetAddress();
|
|
ModuleSP module_sp (addr.GetModule());
|
|
if (module_sp)
|
|
{
|
|
const uint32_t resolve_mask = eSymbolContextFunction | eSymbolContextSymbol;
|
|
uint32_t resolved_mask = module_sp->ResolveSymbolContextForAddress(addr, resolve_mask, sc);
|
|
if (resolved_mask)
|
|
{
|
|
StreamString strmstr;
|
|
Debugger::FormatDisassemblerAddress(disassembly_format, &sc, nullptr, &exe_ctx, &addr, strmstr);
|
|
size_t cur_line = strmstr.GetSizeOfLastLine();
|
|
if (cur_line > address_text_size)
|
|
address_text_size = cur_line;
|
|
}
|
|
sc.Clear(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < num_instructions_found; ++i)
|
|
{
|
|
Instruction *inst = disasm_ptr->GetInstructionList().GetInstructionAtIndex (i).get();
|
|
if (inst)
|
|
{
|
|
const Address &addr = inst->GetAddress();
|
|
const bool inst_is_at_pc = pc_addr_ptr && addr == *pc_addr_ptr;
|
|
|
|
prev_sc = sc;
|
|
|
|
ModuleSP module_sp (addr.GetModule());
|
|
if (module_sp)
|
|
{
|
|
uint32_t resolved_mask = module_sp->ResolveSymbolContextForAddress(addr, eSymbolContextEverything, sc);
|
|
if (resolved_mask)
|
|
{
|
|
if (num_mixed_context_lines)
|
|
{
|
|
if (!sc_range.ContainsFileAddress (addr))
|
|
{
|
|
sc.GetAddressRange (scope, 0, use_inline_block_range, sc_range);
|
|
|
|
if (sc != prev_sc)
|
|
{
|
|
if (offset != 0)
|
|
strm.EOL();
|
|
|
|
sc.DumpStopContext(&strm, exe_ctx.GetProcessPtr(), addr, false, true, false, false, true);
|
|
strm.EOL();
|
|
|
|
if (sc.comp_unit && sc.line_entry.IsValid())
|
|
{
|
|
source_manager.DisplaySourceLinesWithLineNumbers (sc.line_entry.file,
|
|
sc.line_entry.line,
|
|
num_mixed_context_lines,
|
|
num_mixed_context_lines,
|
|
((inst_is_at_pc && (options & eOptionMarkPCSourceLine)) ? "->" : ""),
|
|
&strm);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sc.Clear(true);
|
|
}
|
|
}
|
|
|
|
const bool show_bytes = (options & eOptionShowBytes) != 0;
|
|
inst->Dump(&strm, max_opcode_byte_size, true, show_bytes, &exe_ctx, &sc, &prev_sc, nullptr, address_text_size);
|
|
strm.EOL();
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
Disassembler::Disassemble(Debugger &debugger,
|
|
const ArchSpec &arch,
|
|
const char *plugin_name,
|
|
const char *flavor,
|
|
const ExecutionContext &exe_ctx,
|
|
uint32_t num_instructions,
|
|
uint32_t num_mixed_context_lines,
|
|
uint32_t options,
|
|
Stream &strm)
|
|
{
|
|
AddressRange range;
|
|
StackFrame *frame = exe_ctx.GetFramePtr();
|
|
if (frame)
|
|
{
|
|
SymbolContext sc(frame->GetSymbolContext(eSymbolContextFunction | eSymbolContextSymbol));
|
|
if (sc.function)
|
|
{
|
|
range = sc.function->GetAddressRange();
|
|
}
|
|
else if (sc.symbol && sc.symbol->ValueIsAddress())
|
|
{
|
|
range.GetBaseAddress() = sc.symbol->GetAddressRef();
|
|
range.SetByteSize (sc.symbol->GetByteSize());
|
|
}
|
|
else
|
|
{
|
|
range.GetBaseAddress() = frame->GetFrameCodeAddress();
|
|
}
|
|
|
|
if (range.GetBaseAddress().IsValid() && range.GetByteSize() == 0)
|
|
range.SetByteSize (DEFAULT_DISASM_BYTE_SIZE);
|
|
}
|
|
|
|
return Disassemble (debugger,
|
|
arch,
|
|
plugin_name,
|
|
flavor,
|
|
exe_ctx,
|
|
range,
|
|
num_instructions,
|
|
num_mixed_context_lines,
|
|
options,
|
|
strm);
|
|
}
|
|
|
|
Instruction::Instruction(const Address &address, AddressClass addr_class) :
|
|
m_address (address),
|
|
m_address_class (addr_class),
|
|
m_opcode(),
|
|
m_calculated_strings(false)
|
|
{
|
|
}
|
|
|
|
Instruction::~Instruction() = default;
|
|
|
|
AddressClass
|
|
Instruction::GetAddressClass ()
|
|
{
|
|
if (m_address_class == eAddressClassInvalid)
|
|
m_address_class = m_address.GetAddressClass();
|
|
return m_address_class;
|
|
}
|
|
|
|
void
|
|
Instruction::Dump (lldb_private::Stream *s,
|
|
uint32_t max_opcode_byte_size,
|
|
bool show_address,
|
|
bool show_bytes,
|
|
const ExecutionContext* exe_ctx,
|
|
const SymbolContext *sym_ctx,
|
|
const SymbolContext *prev_sym_ctx,
|
|
const FormatEntity::Entry *disassembly_addr_format,
|
|
size_t max_address_text_size)
|
|
{
|
|
size_t opcode_column_width = 7;
|
|
const size_t operand_column_width = 25;
|
|
|
|
CalculateMnemonicOperandsAndCommentIfNeeded (exe_ctx);
|
|
|
|
StreamString ss;
|
|
|
|
if (show_address)
|
|
{
|
|
Debugger::FormatDisassemblerAddress (disassembly_addr_format, sym_ctx, prev_sym_ctx, exe_ctx, &m_address, ss);
|
|
ss.FillLastLineToColumn (max_address_text_size, ' ');
|
|
}
|
|
|
|
if (show_bytes)
|
|
{
|
|
if (m_opcode.GetType() == Opcode::eTypeBytes)
|
|
{
|
|
// x86_64 and i386 are the only ones that use bytes right now so
|
|
// pad out the byte dump to be able to always show 15 bytes (3 chars each)
|
|
// plus a space
|
|
if (max_opcode_byte_size > 0)
|
|
m_opcode.Dump (&ss, max_opcode_byte_size * 3 + 1);
|
|
else
|
|
m_opcode.Dump (&ss, 15 * 3 + 1);
|
|
}
|
|
else
|
|
{
|
|
// Else, we have ARM or MIPS which can show up to a uint32_t
|
|
// 0x00000000 (10 spaces) plus two for padding...
|
|
if (max_opcode_byte_size > 0)
|
|
m_opcode.Dump (&ss, max_opcode_byte_size * 3 + 1);
|
|
else
|
|
m_opcode.Dump (&ss, 12);
|
|
}
|
|
}
|
|
|
|
const size_t opcode_pos = ss.GetSizeOfLastLine();
|
|
|
|
// The default opcode size of 7 characters is plenty for most architectures
|
|
// but some like arm can pull out the occasional vqrshrun.s16. We won't get
|
|
// consistent column spacing in these cases, unfortunately.
|
|
if (m_opcode_name.length() >= opcode_column_width)
|
|
{
|
|
opcode_column_width = m_opcode_name.length() + 1;
|
|
}
|
|
|
|
ss.PutCString (m_opcode_name.c_str());
|
|
ss.FillLastLineToColumn (opcode_pos + opcode_column_width, ' ');
|
|
ss.PutCString (m_mnemonics.c_str());
|
|
|
|
if (!m_comment.empty())
|
|
{
|
|
ss.FillLastLineToColumn (opcode_pos + opcode_column_width + operand_column_width, ' ');
|
|
ss.PutCString (" ; ");
|
|
ss.PutCString (m_comment.c_str());
|
|
}
|
|
s->Write (ss.GetData(), ss.GetSize());
|
|
}
|
|
|
|
bool
|
|
Instruction::DumpEmulation (const ArchSpec &arch)
|
|
{
|
|
std::unique_ptr<EmulateInstruction> insn_emulator_ap(EmulateInstruction::FindPlugin(arch, eInstructionTypeAny, nullptr));
|
|
if (insn_emulator_ap)
|
|
{
|
|
insn_emulator_ap->SetInstruction(GetOpcode(), GetAddress(), nullptr);
|
|
return insn_emulator_ap->EvaluateInstruction (0);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
Instruction::HasDelaySlot ()
|
|
{
|
|
// Default is false.
|
|
return false;
|
|
}
|
|
|
|
OptionValueSP
|
|
Instruction::ReadArray (FILE *in_file, Stream *out_stream, OptionValue::Type data_type)
|
|
{
|
|
bool done = false;
|
|
char buffer[1024];
|
|
|
|
OptionValueSP option_value_sp (new OptionValueArray (1u << data_type));
|
|
|
|
int idx = 0;
|
|
while (!done)
|
|
{
|
|
if (!fgets (buffer, 1023, in_file))
|
|
{
|
|
out_stream->Printf ("Instruction::ReadArray: Error reading file (fgets).\n");
|
|
option_value_sp.reset ();
|
|
return option_value_sp;
|
|
}
|
|
|
|
std::string line (buffer);
|
|
|
|
size_t len = line.size();
|
|
if (line[len-1] == '\n')
|
|
{
|
|
line[len-1] = '\0';
|
|
line.resize (len-1);
|
|
}
|
|
|
|
if ((line.size() == 1) && line[0] == ']')
|
|
{
|
|
done = true;
|
|
line.clear();
|
|
}
|
|
|
|
if (!line.empty())
|
|
{
|
|
std::string value;
|
|
static RegularExpression g_reg_exp ("^[ \t]*([^ \t]+)[ \t]*$");
|
|
RegularExpression::Match regex_match(1);
|
|
bool reg_exp_success = g_reg_exp.Execute (line.c_str(), ®ex_match);
|
|
if (reg_exp_success)
|
|
regex_match.GetMatchAtIndex (line.c_str(), 1, value);
|
|
else
|
|
value = line;
|
|
|
|
OptionValueSP data_value_sp;
|
|
switch (data_type)
|
|
{
|
|
case OptionValue::eTypeUInt64:
|
|
data_value_sp.reset (new OptionValueUInt64 (0, 0));
|
|
data_value_sp->SetValueFromString (value);
|
|
break;
|
|
// Other types can be added later as needed.
|
|
default:
|
|
data_value_sp.reset (new OptionValueString (value.c_str(), ""));
|
|
break;
|
|
}
|
|
|
|
option_value_sp->GetAsArray()->InsertValue (idx, data_value_sp);
|
|
++idx;
|
|
}
|
|
}
|
|
|
|
return option_value_sp;
|
|
}
|
|
|
|
OptionValueSP
|
|
Instruction::ReadDictionary (FILE *in_file, Stream *out_stream)
|
|
{
|
|
bool done = false;
|
|
char buffer[1024];
|
|
|
|
OptionValueSP option_value_sp (new OptionValueDictionary());
|
|
static ConstString encoding_key ("data_encoding");
|
|
OptionValue::Type data_type = OptionValue::eTypeInvalid;
|
|
|
|
|
|
while (!done)
|
|
{
|
|
// Read the next line in the file
|
|
if (!fgets (buffer, 1023, in_file))
|
|
{
|
|
out_stream->Printf ("Instruction::ReadDictionary: Error reading file (fgets).\n");
|
|
option_value_sp.reset ();
|
|
return option_value_sp;
|
|
}
|
|
|
|
// Check to see if the line contains the end-of-dictionary marker ("}")
|
|
std::string line (buffer);
|
|
|
|
size_t len = line.size();
|
|
if (line[len-1] == '\n')
|
|
{
|
|
line[len-1] = '\0';
|
|
line.resize (len-1);
|
|
}
|
|
|
|
if ((line.size() == 1) && (line[0] == '}'))
|
|
{
|
|
done = true;
|
|
line.clear();
|
|
}
|
|
|
|
// Try to find a key-value pair in the current line and add it to the dictionary.
|
|
if (!line.empty())
|
|
{
|
|
static RegularExpression g_reg_exp ("^[ \t]*([a-zA-Z_][a-zA-Z0-9_]*)[ \t]*=[ \t]*(.*)[ \t]*$");
|
|
RegularExpression::Match regex_match(2);
|
|
|
|
bool reg_exp_success = g_reg_exp.Execute (line.c_str(), ®ex_match);
|
|
std::string key;
|
|
std::string value;
|
|
if (reg_exp_success)
|
|
{
|
|
regex_match.GetMatchAtIndex (line.c_str(), 1, key);
|
|
regex_match.GetMatchAtIndex (line.c_str(), 2, value);
|
|
}
|
|
else
|
|
{
|
|
out_stream->Printf ("Instruction::ReadDictionary: Failure executing regular expression.\n");
|
|
option_value_sp.reset();
|
|
return option_value_sp;
|
|
}
|
|
|
|
ConstString const_key (key.c_str());
|
|
// Check value to see if it's the start of an array or dictionary.
|
|
|
|
lldb::OptionValueSP value_sp;
|
|
assert (value.empty() == false);
|
|
assert (key.empty() == false);
|
|
|
|
if (value[0] == '{')
|
|
{
|
|
assert (value.size() == 1);
|
|
// value is a dictionary
|
|
value_sp = ReadDictionary (in_file, out_stream);
|
|
if (!value_sp)
|
|
{
|
|
option_value_sp.reset ();
|
|
return option_value_sp;
|
|
}
|
|
}
|
|
else if (value[0] == '[')
|
|
{
|
|
assert (value.size() == 1);
|
|
// value is an array
|
|
value_sp = ReadArray (in_file, out_stream, data_type);
|
|
if (!value_sp)
|
|
{
|
|
option_value_sp.reset ();
|
|
return option_value_sp;
|
|
}
|
|
// We've used the data_type to read an array; re-set the type to Invalid
|
|
data_type = OptionValue::eTypeInvalid;
|
|
}
|
|
else if ((value[0] == '0') && (value[1] == 'x'))
|
|
{
|
|
value_sp.reset (new OptionValueUInt64 (0, 0));
|
|
value_sp->SetValueFromString (value);
|
|
}
|
|
else
|
|
{
|
|
size_t len = value.size();
|
|
if ((value[0] == '"') && (value[len-1] == '"'))
|
|
value = value.substr (1, len-2);
|
|
value_sp.reset (new OptionValueString (value.c_str(), ""));
|
|
}
|
|
|
|
if (const_key == encoding_key)
|
|
{
|
|
// A 'data_encoding=..." is NOT a normal key-value pair; it is meta-data indicating the
|
|
// data type of an upcoming array (usually the next bit of data to be read in).
|
|
if (strcmp (value.c_str(), "uint32_t") == 0)
|
|
data_type = OptionValue::eTypeUInt64;
|
|
}
|
|
else
|
|
option_value_sp->GetAsDictionary()->SetValueForKey (const_key, value_sp, false);
|
|
}
|
|
}
|
|
|
|
return option_value_sp;
|
|
}
|
|
|
|
bool
|
|
Instruction::TestEmulation (Stream *out_stream, const char *file_name)
|
|
{
|
|
if (!out_stream)
|
|
return false;
|
|
|
|
if (!file_name)
|
|
{
|
|
out_stream->Printf ("Instruction::TestEmulation: Missing file_name.");
|
|
return false;
|
|
}
|
|
|
|
FILE *test_file = fopen (file_name, "r");
|
|
if (!test_file)
|
|
{
|
|
out_stream->Printf ("Instruction::TestEmulation: Attempt to open test file failed.");
|
|
return false;
|
|
}
|
|
|
|
char buffer[256];
|
|
if (!fgets (buffer, 255, test_file))
|
|
{
|
|
out_stream->Printf ("Instruction::TestEmulation: Error reading first line of test file.\n");
|
|
fclose (test_file);
|
|
return false;
|
|
}
|
|
|
|
if (strncmp (buffer, "InstructionEmulationState={", 27) != 0)
|
|
{
|
|
out_stream->Printf ("Instructin::TestEmulation: Test file does not contain emulation state dictionary\n");
|
|
fclose (test_file);
|
|
return false;
|
|
}
|
|
|
|
// Read all the test information from the test file into an OptionValueDictionary.
|
|
|
|
OptionValueSP data_dictionary_sp (ReadDictionary (test_file, out_stream));
|
|
if (!data_dictionary_sp)
|
|
{
|
|
out_stream->Printf ("Instruction::TestEmulation: Error reading Dictionary Object.\n");
|
|
fclose (test_file);
|
|
return false;
|
|
}
|
|
|
|
fclose (test_file);
|
|
|
|
OptionValueDictionary *data_dictionary = data_dictionary_sp->GetAsDictionary();
|
|
static ConstString description_key ("assembly_string");
|
|
static ConstString triple_key ("triple");
|
|
|
|
OptionValueSP value_sp = data_dictionary->GetValueForKey (description_key);
|
|
|
|
if (!value_sp)
|
|
{
|
|
out_stream->Printf ("Instruction::TestEmulation: Test file does not contain description string.\n");
|
|
return false;
|
|
}
|
|
|
|
SetDescription (value_sp->GetStringValue());
|
|
|
|
value_sp = data_dictionary->GetValueForKey (triple_key);
|
|
if (!value_sp)
|
|
{
|
|
out_stream->Printf ("Instruction::TestEmulation: Test file does not contain triple.\n");
|
|
return false;
|
|
}
|
|
|
|
ArchSpec arch;
|
|
arch.SetTriple (llvm::Triple (value_sp->GetStringValue()));
|
|
|
|
bool success = false;
|
|
std::unique_ptr<EmulateInstruction> insn_emulator_ap(EmulateInstruction::FindPlugin(arch, eInstructionTypeAny, nullptr));
|
|
if (insn_emulator_ap)
|
|
success = insn_emulator_ap->TestEmulation (out_stream, arch, data_dictionary);
|
|
|
|
if (success)
|
|
out_stream->Printf ("Emulation test succeeded.");
|
|
else
|
|
out_stream->Printf ("Emulation test failed.");
|
|
|
|
return success;
|
|
}
|
|
|
|
bool
|
|
Instruction::Emulate (const ArchSpec &arch,
|
|
uint32_t evaluate_options,
|
|
void *baton,
|
|
EmulateInstruction::ReadMemoryCallback read_mem_callback,
|
|
EmulateInstruction::WriteMemoryCallback write_mem_callback,
|
|
EmulateInstruction::ReadRegisterCallback read_reg_callback,
|
|
EmulateInstruction::WriteRegisterCallback write_reg_callback)
|
|
{
|
|
std::unique_ptr<EmulateInstruction> insn_emulator_ap(EmulateInstruction::FindPlugin(arch, eInstructionTypeAny, nullptr));
|
|
if (insn_emulator_ap)
|
|
{
|
|
insn_emulator_ap->SetBaton(baton);
|
|
insn_emulator_ap->SetCallbacks(read_mem_callback, write_mem_callback, read_reg_callback, write_reg_callback);
|
|
insn_emulator_ap->SetInstruction(GetOpcode(), GetAddress(), nullptr);
|
|
return insn_emulator_ap->EvaluateInstruction(evaluate_options);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
uint32_t
|
|
Instruction::GetData (DataExtractor &data)
|
|
{
|
|
return m_opcode.GetData(data);
|
|
}
|
|
|
|
InstructionList::InstructionList() :
|
|
m_instructions()
|
|
{
|
|
}
|
|
|
|
InstructionList::~InstructionList() = default;
|
|
|
|
size_t
|
|
InstructionList::GetSize() const
|
|
{
|
|
return m_instructions.size();
|
|
}
|
|
|
|
uint32_t
|
|
InstructionList::GetMaxOpcocdeByteSize () const
|
|
{
|
|
uint32_t max_inst_size = 0;
|
|
collection::const_iterator pos, end;
|
|
for (pos = m_instructions.begin(), end = m_instructions.end();
|
|
pos != end;
|
|
++pos)
|
|
{
|
|
uint32_t inst_size = (*pos)->GetOpcode().GetByteSize();
|
|
if (max_inst_size < inst_size)
|
|
max_inst_size = inst_size;
|
|
}
|
|
return max_inst_size;
|
|
}
|
|
|
|
InstructionSP
|
|
InstructionList::GetInstructionAtIndex (size_t idx) const
|
|
{
|
|
InstructionSP inst_sp;
|
|
if (idx < m_instructions.size())
|
|
inst_sp = m_instructions[idx];
|
|
return inst_sp;
|
|
}
|
|
|
|
void
|
|
InstructionList::Dump (Stream *s,
|
|
bool show_address,
|
|
bool show_bytes,
|
|
const ExecutionContext* exe_ctx)
|
|
{
|
|
const uint32_t max_opcode_byte_size = GetMaxOpcocdeByteSize();
|
|
collection::const_iterator pos, begin, end;
|
|
|
|
const FormatEntity::Entry *disassembly_format = nullptr;
|
|
FormatEntity::Entry format;
|
|
if (exe_ctx && exe_ctx->HasTargetScope())
|
|
{
|
|
disassembly_format = exe_ctx->GetTargetRef().GetDebugger().GetDisassemblyFormat ();
|
|
}
|
|
else
|
|
{
|
|
FormatEntity::Parse("${addr}: ", format);
|
|
disassembly_format = &format;
|
|
}
|
|
|
|
for (begin = m_instructions.begin(), end = m_instructions.end(), pos = begin;
|
|
pos != end;
|
|
++pos)
|
|
{
|
|
if (pos != begin)
|
|
s->EOL();
|
|
(*pos)->Dump(s, max_opcode_byte_size, show_address, show_bytes, exe_ctx, nullptr, nullptr, disassembly_format, 0);
|
|
}
|
|
}
|
|
|
|
void
|
|
InstructionList::Clear()
|
|
{
|
|
m_instructions.clear();
|
|
}
|
|
|
|
void
|
|
InstructionList::Append (lldb::InstructionSP &inst_sp)
|
|
{
|
|
if (inst_sp)
|
|
m_instructions.push_back(inst_sp);
|
|
}
|
|
|
|
uint32_t
|
|
InstructionList::GetIndexOfNextBranchInstruction(uint32_t start, Target &target) const
|
|
{
|
|
size_t num_instructions = m_instructions.size();
|
|
|
|
uint32_t next_branch = std::numeric_limits<uint32_t>::max();
|
|
size_t i;
|
|
for (i = start; i < num_instructions; i++)
|
|
{
|
|
if (m_instructions[i]->DoesBranch())
|
|
{
|
|
next_branch = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Hexagon needs the first instruction of the packet with the branch.
|
|
// Go backwards until we find an instruction marked end-of-packet, or
|
|
// until we hit start.
|
|
if (target.GetArchitecture().GetTriple().getArch() == llvm::Triple::hexagon)
|
|
{
|
|
// If we didn't find a branch, find the last packet start.
|
|
if (next_branch == std::numeric_limits<uint32_t>::max())
|
|
{
|
|
i = num_instructions - 1;
|
|
}
|
|
|
|
while (i > start)
|
|
{
|
|
--i;
|
|
|
|
Error error;
|
|
uint32_t inst_bytes;
|
|
bool prefer_file_cache = false; // Read from process if process is running
|
|
lldb::addr_t load_addr = LLDB_INVALID_ADDRESS;
|
|
target.ReadMemory(m_instructions[i]->GetAddress(),
|
|
prefer_file_cache,
|
|
&inst_bytes,
|
|
sizeof(inst_bytes),
|
|
error,
|
|
&load_addr);
|
|
// If we have an error reading memory, return start
|
|
if (!error.Success())
|
|
return start;
|
|
// check if this is the last instruction in a packet
|
|
// bits 15:14 will be 11b or 00b for a duplex
|
|
if (((inst_bytes & 0xC000) == 0xC000) ||
|
|
((inst_bytes & 0xC000) == 0x0000))
|
|
{
|
|
// instruction after this should be the start of next packet
|
|
next_branch = i + 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (next_branch == std::numeric_limits<uint32_t>::max())
|
|
{
|
|
// We couldn't find the previous packet, so return start
|
|
next_branch = start;
|
|
}
|
|
}
|
|
return next_branch;
|
|
}
|
|
|
|
uint32_t
|
|
InstructionList::GetIndexOfInstructionAtAddress (const Address &address)
|
|
{
|
|
size_t num_instructions = m_instructions.size();
|
|
uint32_t index = std::numeric_limits<uint32_t>::max();
|
|
for (size_t i = 0; i < num_instructions; i++)
|
|
{
|
|
if (m_instructions[i]->GetAddress() == address)
|
|
{
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
return index;
|
|
}
|
|
|
|
uint32_t
|
|
InstructionList::GetIndexOfInstructionAtLoadAddress (lldb::addr_t load_addr, Target &target)
|
|
{
|
|
Address address;
|
|
address.SetLoadAddress(load_addr, &target);
|
|
return GetIndexOfInstructionAtAddress(address);
|
|
}
|
|
|
|
size_t
|
|
Disassembler::ParseInstructions (const ExecutionContext *exe_ctx,
|
|
const AddressRange &range,
|
|
Stream *error_strm_ptr,
|
|
bool prefer_file_cache)
|
|
{
|
|
if (exe_ctx)
|
|
{
|
|
Target *target = exe_ctx->GetTargetPtr();
|
|
const addr_t byte_size = range.GetByteSize();
|
|
if (target == nullptr || byte_size == 0 || !range.GetBaseAddress().IsValid())
|
|
return 0;
|
|
|
|
DataBufferHeap *heap_buffer = new DataBufferHeap (byte_size, '\0');
|
|
DataBufferSP data_sp(heap_buffer);
|
|
|
|
Error error;
|
|
lldb::addr_t load_addr = LLDB_INVALID_ADDRESS;
|
|
const size_t bytes_read = target->ReadMemory (range.GetBaseAddress(),
|
|
prefer_file_cache,
|
|
heap_buffer->GetBytes(),
|
|
heap_buffer->GetByteSize(),
|
|
error,
|
|
&load_addr);
|
|
|
|
if (bytes_read > 0)
|
|
{
|
|
if (bytes_read != heap_buffer->GetByteSize())
|
|
heap_buffer->SetByteSize (bytes_read);
|
|
DataExtractor data (data_sp,
|
|
m_arch.GetByteOrder(),
|
|
m_arch.GetAddressByteSize());
|
|
const bool data_from_file = load_addr == LLDB_INVALID_ADDRESS;
|
|
return DecodeInstructions(range.GetBaseAddress(), data, 0, std::numeric_limits<uint32_t>::max(), false,
|
|
data_from_file);
|
|
}
|
|
else if (error_strm_ptr)
|
|
{
|
|
const char *error_cstr = error.AsCString();
|
|
if (error_cstr)
|
|
{
|
|
error_strm_ptr->Printf("error: %s\n", error_cstr);
|
|
}
|
|
}
|
|
}
|
|
else if (error_strm_ptr)
|
|
{
|
|
error_strm_ptr->PutCString("error: invalid execution context\n");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
size_t
|
|
Disassembler::ParseInstructions (const ExecutionContext *exe_ctx,
|
|
const Address &start,
|
|
uint32_t num_instructions,
|
|
bool prefer_file_cache)
|
|
{
|
|
m_instruction_list.Clear();
|
|
|
|
if (exe_ctx == nullptr || num_instructions == 0 || !start.IsValid())
|
|
return 0;
|
|
|
|
Target *target = exe_ctx->GetTargetPtr();
|
|
// Calculate the max buffer size we will need in order to disassemble
|
|
const addr_t byte_size = num_instructions * m_arch.GetMaximumOpcodeByteSize();
|
|
|
|
if (target == nullptr || byte_size == 0)
|
|
return 0;
|
|
|
|
DataBufferHeap *heap_buffer = new DataBufferHeap (byte_size, '\0');
|
|
DataBufferSP data_sp (heap_buffer);
|
|
|
|
Error error;
|
|
lldb::addr_t load_addr = LLDB_INVALID_ADDRESS;
|
|
const size_t bytes_read = target->ReadMemory (start,
|
|
prefer_file_cache,
|
|
heap_buffer->GetBytes(),
|
|
byte_size,
|
|
error,
|
|
&load_addr);
|
|
|
|
const bool data_from_file = load_addr == LLDB_INVALID_ADDRESS;
|
|
|
|
if (bytes_read == 0)
|
|
return 0;
|
|
DataExtractor data (data_sp,
|
|
m_arch.GetByteOrder(),
|
|
m_arch.GetAddressByteSize());
|
|
|
|
const bool append_instructions = true;
|
|
DecodeInstructions (start,
|
|
data,
|
|
0,
|
|
num_instructions,
|
|
append_instructions,
|
|
data_from_file);
|
|
|
|
return m_instruction_list.GetSize();
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Disassembler copy constructor
|
|
//----------------------------------------------------------------------
|
|
Disassembler::Disassembler(const ArchSpec& arch, const char *flavor) :
|
|
m_arch (arch),
|
|
m_instruction_list(),
|
|
m_base_addr(LLDB_INVALID_ADDRESS),
|
|
m_flavor ()
|
|
{
|
|
if (flavor == nullptr)
|
|
m_flavor.assign("default");
|
|
else
|
|
m_flavor.assign(flavor);
|
|
|
|
// If this is an arm variant that can only include thumb (T16, T32)
|
|
// instructions, force the arch triple to be "thumbv.." instead of
|
|
// "armv..."
|
|
if ((arch.GetTriple().getArch() == llvm::Triple::arm || arch.GetTriple().getArch() == llvm::Triple::thumb)
|
|
&& (arch.GetCore() == ArchSpec::Core::eCore_arm_armv7m
|
|
|| arch.GetCore() == ArchSpec::Core::eCore_arm_armv7em
|
|
|| arch.GetCore() == ArchSpec::Core::eCore_arm_armv6m))
|
|
{
|
|
std::string thumb_arch_name (arch.GetTriple().getArchName().str());
|
|
// Replace "arm" with "thumb" so we get all thumb variants correct
|
|
if (thumb_arch_name.size() > 3)
|
|
{
|
|
thumb_arch_name.erase(0, 3);
|
|
thumb_arch_name.insert(0, "thumb");
|
|
}
|
|
m_arch.SetTriple (thumb_arch_name.c_str());
|
|
}
|
|
}
|
|
|
|
Disassembler::~Disassembler() = default;
|
|
|
|
InstructionList &
|
|
Disassembler::GetInstructionList ()
|
|
{
|
|
return m_instruction_list;
|
|
}
|
|
|
|
const InstructionList &
|
|
Disassembler::GetInstructionList () const
|
|
{
|
|
return m_instruction_list;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Class PseudoInstruction
|
|
//----------------------------------------------------------------------
|
|
|
|
PseudoInstruction::PseudoInstruction () :
|
|
Instruction (Address(), eAddressClassUnknown),
|
|
m_description ()
|
|
{
|
|
}
|
|
|
|
PseudoInstruction::~PseudoInstruction() = default;
|
|
|
|
bool
|
|
PseudoInstruction::DoesBranch ()
|
|
{
|
|
// This is NOT a valid question for a pseudo instruction.
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
PseudoInstruction::HasDelaySlot ()
|
|
{
|
|
// This is NOT a valid question for a pseudo instruction.
|
|
return false;
|
|
}
|
|
|
|
size_t
|
|
PseudoInstruction::Decode (const lldb_private::Disassembler &disassembler,
|
|
const lldb_private::DataExtractor &data,
|
|
lldb::offset_t data_offset)
|
|
{
|
|
return m_opcode.GetByteSize();
|
|
}
|
|
|
|
void
|
|
PseudoInstruction::SetOpcode (size_t opcode_size, void *opcode_data)
|
|
{
|
|
if (!opcode_data)
|
|
return;
|
|
|
|
switch (opcode_size)
|
|
{
|
|
case 8:
|
|
{
|
|
uint8_t value8 = *((uint8_t *) opcode_data);
|
|
m_opcode.SetOpcode8 (value8, eByteOrderInvalid);
|
|
break;
|
|
}
|
|
case 16:
|
|
{
|
|
uint16_t value16 = *((uint16_t *) opcode_data);
|
|
m_opcode.SetOpcode16 (value16, eByteOrderInvalid);
|
|
break;
|
|
}
|
|
case 32:
|
|
{
|
|
uint32_t value32 = *((uint32_t *) opcode_data);
|
|
m_opcode.SetOpcode32 (value32, eByteOrderInvalid);
|
|
break;
|
|
}
|
|
case 64:
|
|
{
|
|
uint64_t value64 = *((uint64_t *) opcode_data);
|
|
m_opcode.SetOpcode64 (value64, eByteOrderInvalid);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
PseudoInstruction::SetDescription (const char *description)
|
|
{
|
|
if (description && strlen (description) > 0)
|
|
m_description = description;
|
|
}
|