Skip to content

Instantly share code, notes, and snippets.

@Keno
Created February 24, 2015 20:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Keno/ef471f766d8dddf074e7 to your computer and use it in GitHub Desktop.
Save Keno/ef471f766d8dddf074e7 to your computer and use it in GitHub Desktop.
UnwindAssembly-mips.cpp
//===-- UnwindAssembly-mips.cpp ----------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "UnwindAssembly-mips.h"
#define GET_INSTRINFO_ENUM
#include "MipsGenInstrInfo.inc"
#include "llvm-c/Disassembler.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/TargetRegistry.h"
#include "llvm/MC/MCAsmInfo.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCDisassembler.h"
#include "llvm/MC/MCRegisterInfo.h"
#include "llvm/MC/MCContext.h"
#include "lldb/Core/Address.h"
#include "lldb/Core/Error.h"
#include "lldb/Core/ArchSpec.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Symbol/UnwindPlan.h"
#include "lldb/Target/ExecutionContext.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/RegisterContext.h"
#include "lldb/Target/Thread.h"
#include "lldb/Target/Target.h"
#include "lldb/Target/UnwindAssembly.h"
using namespace lldb;
using namespace lldb_private;
enum CPU
{
k_mips32,
k_mips64
};
//-----------------------------------------------------------------------------------------------
// AssemblyParse_mips local-file class definition & implementation functions
//-----------------------------------------------------------------------------------------------
class AssemblyParse_mips
{
public:
AssemblyParse_mips (const ExecutionContext &exe_ctx, int cpu, ArchSpec &arch, AddressRange func);
~AssemblyParse_mips ();
bool get_non_call_site_unwind_plan (UnwindPlan &unwind_plan);
bool augment_unwind_plan_from_call_site (AddressRange& func, UnwindPlan &unwind_plan);
bool get_fast_unwind_plan (AddressRange& func, UnwindPlan &unwind_plan);
bool find_first_non_prologue_insn (Address &address);
private:
bool nonvolatile_reg_p (uint32_t machine_regno);
bool add_imm_sp_pattern_p (const llvm::MCInst &Inst, int& amount);
bool store_reg_pattern_p (const llvm::MCInst &Inst, uint32_t& regno, int& offset);
bool mov_sp_to_fp_pattern_p (const llvm::MCInst &Inst);
bool mov_fp_to_sp_pattern_p (const llvm::MCInst &Inst);
const ExecutionContext m_exe_ctx;
AddressRange m_func_bounds;
Address m_cur_insn;
uint32_t m_machine_ra_regnum;
uint32_t m_machine_pc_regnum;
uint32_t m_machine_sp_regnum;
uint32_t m_machine_fp_regnum;
int m_cpu;
ArchSpec m_arch;
std::unique_ptr<llvm::MCDisassembler> m_disasm;
std::unique_ptr<llvm::MCRegisterInfo> m_MRI;
std::unique_ptr<llvm::MCAsmInfo> m_MAI;
std::unique_ptr<llvm::MCSubtargetInfo> m_MSI;
std::unique_ptr<llvm::MCContext> m_Ctx;
DISALLOW_COPY_AND_ASSIGN (AssemblyParse_mips);
};
static inline uint32_t generic_reg_to_machine(RegisterContext *reg_ctx, uint32_t kind)
{
uint32_t reg_idx = reg_ctx->ConvertRegisterKindToRegisterNumber(eRegisterKindGeneric,kind);
if (reg_idx == LLDB_INVALID_REGNUM)
return LLDB_INVALID_REGNUM;
return reg_ctx->GetRegisterInfoAtIndex(reg_idx)->kinds[eRegisterKindGCC];
}
AssemblyParse_mips::AssemblyParse_mips (const ExecutionContext &exe_ctx, int cpu, ArchSpec &arch, AddressRange func) :
m_exe_ctx (exe_ctx),
m_func_bounds(func),
m_cur_insn (),
m_machine_ra_regnum (LLDB_INVALID_REGNUM),
m_machine_sp_regnum (LLDB_INVALID_REGNUM),
m_machine_fp_regnum (LLDB_INVALID_REGNUM),
m_cpu(cpu),
m_arch(arch)
{
// we only look at prologue - it will be complete earlier than 512 bytes into func
if (m_func_bounds.GetByteSize() == 0)
m_func_bounds.SetByteSize(512);
Thread *thread = m_exe_ctx.GetThreadPtr();
if (thread)
{
RegisterContext *reg_ctx = thread->GetRegisterContext().get();
if (reg_ctx)
{
m_machine_pc_regnum = generic_reg_to_machine(reg_ctx,LLDB_REGNUM_GENERIC_PC);
m_machine_ra_regnum = generic_reg_to_machine(reg_ctx,LLDB_REGNUM_GENERIC_RA);
m_machine_sp_regnum = generic_reg_to_machine(reg_ctx,LLDB_REGNUM_GENERIC_SP);
m_machine_fp_regnum = generic_reg_to_machine(reg_ctx,LLDB_REGNUM_GENERIC_FP);
}
}
// Create the disassembler
std::string Error;
llvm::Triple TheTriple = m_arch.GetTriple();
const llvm::Target *TheTarget = llvm::TargetRegistry::lookupTarget(TheTriple.getTriple(), Error);
assert(TheTarget);
m_MRI.reset(TheTarget->createMCRegInfo(TheTriple.getTriple()));
assert(m_MRI.get());
m_MAI.reset(TheTarget->createMCAsmInfo(*m_MRI, TheTriple.getTriple()));
m_MSI.reset(TheTarget->createMCSubtargetInfo(TheTriple.getTriple(), "", ""));
assert(m_MAI.get() && m_MSI.get());
m_Ctx.reset(new llvm::MCContext(m_MAI.get(), m_MRI.get(), nullptr));
assert(m_Ctx.get());
// Set up disassembler
m_disasm.reset(TheTarget->createMCDisassembler(*m_MSI, *m_Ctx));
assert(m_disasm.get());
}
AssemblyParse_mips::~AssemblyParse_mips ()
{
}
// This function expects an mips native register number (i.e. the bits stripped out of the
// actual instruction), not an lldb register number.
bool
AssemblyParse_mips::nonvolatile_reg_p (uint32_t machine_regno)
{
Process *proc = m_exe_ctx.GetProcessPtr();
Thread *thread = m_exe_ctx.GetThreadPtr();
if (proc && thread) {
const ABISP abi = proc->GetABI();
RegisterContextSP reg_ctx = thread->GetRegisterContext();
if (abi && reg_ctx) {
uint32_t reg_idx = reg_ctx->ConvertRegisterKindToRegisterNumber(eRegisterKindGCC,machine_regno);
if (reg_idx != LLDB_INVALID_REGNUM)
return !abi->RegisterIsVolatile(reg_ctx->GetRegisterInfoAtIndex(reg_idx));
}
}
// Fall back path in case any of the above do not exit, note at least sp fp ra as callee-saved
if (machine_regno == m_machine_sp_regnum ||
machine_regno == m_machine_fp_regnum ||
machine_regno == m_machine_ra_regnum ||
machine_regno == m_machine_pc_regnum)
return true;
return false;
}
// daddiu sp,sp,-X
bool AssemblyParse_mips::add_imm_sp_pattern_p (const llvm::MCInst &Inst, int& amount)
{
uint32_t opc = Inst.getOpcode();
if (opc == llvm::Mips::DADDiu || opc == llvm::Mips::ADDiu)
{
if (m_machine_sp_regnum == m_MRI->getEncodingValue(Inst.getOperand(0).getReg()))
{
if (m_machine_sp_regnum == m_MRI->getEncodingValue(Inst.getOperand(1).getReg()))
{
/* Get the immediate operand */
amount = Inst.getOperand(2).getImm();
return true;
}
}
}
return false;
}
/* sd ra/fp,xx(sp) */
bool AssemblyParse_mips::store_reg_pattern_p (const llvm::MCInst &Inst, uint32_t& regnum, int& offset)
{
uint32_t opc = Inst.getOpcode();
uint32_t regno;
if (opc == llvm::Mips::SD || opc == llvm::Mips::SW)
{
if(m_machine_sp_regnum == m_MRI->getEncodingValue(Inst.getOperand(1).getReg()))
{
regno = m_MRI->getEncodingValue(Inst.getOperand(0).getReg());
if (nonvolatile_reg_p(regno))
{
offset = Inst.getOperand(2).getImm();
regnum = regno;
return true;
}
}
}
return false;
}
/* move s8, sp is actually ADDU s8, sp, $0 */
bool AssemblyParse_mips::mov_sp_to_fp_pattern_p (const llvm::MCInst &Inst)
{
uint32_t opc = Inst.getOpcode();
if ((opc == llvm::Mips::ADDu || opc == llvm::Mips::DADDu) &&
m_machine_fp_regnum == m_MRI->getEncodingValue(Inst.getOperand(0).getReg()) &&
m_machine_sp_regnum == m_MRI->getEncodingValue(Inst.getOperand(1).getReg()) &&
0 == Inst.getOperand(2).getImm())
return true;
return false;
}
/* move s8, sp is actually ADDU s8, sp, $0 */
bool AssemblyParse_mips::mov_fp_to_sp_pattern_p (const llvm::MCInst &Inst)
{
uint32_t opc = Inst.getOpcode();
if ((opc == llvm::Mips::ADDu || opc == llvm::Mips::DADDu) &&
m_machine_sp_regnum == m_MRI->getEncodingValue(Inst.getOperand(0).getReg()) &&
m_machine_fp_regnum == m_MRI->getEncodingValue(Inst.getOperand(1).getReg()) &&
0 == Inst.getOperand(2).getImm())
return true;
return false;
}
bool
AssemblyParse_mips::get_non_call_site_unwind_plan (UnwindPlan &unwind_plan)
{
UnwindPlan::RowSP row(new UnwindPlan::Row);
m_cur_insn = m_func_bounds.GetBaseAddress ();
int current_func_text_offset = 0;
int bytes_from_initial_sp = 0;
UnwindPlan::Row::RegisterLocation initial_regloc;
Error error;
if (!m_cur_insn.IsValid())
{
return false;
}
unwind_plan.SetPlanValidAddressRange (m_func_bounds);
unwind_plan.SetRegisterKind (eRegisterKindGCC);
// At the start of the function, CFA is previous SP register value
row->SetOffset (current_func_text_offset);
row->SetCFARegister (m_machine_sp_regnum);
row->SetCFAOffset (0);
// stack pointer hasn't changed yet
initial_regloc.SetIsCFAPlusOffset (0);
row->SetRegisterInfo (m_machine_sp_regnum, initial_regloc);
unwind_plan.AppendRow (row);
// Allocate a new Row, populate it with the existing Row contents.
UnwindPlan::Row *newrow = new UnwindPlan::Row;
*newrow = *row.get();
row.reset(newrow);
const bool prefer_file_cache = true;
Target *target = m_exe_ctx.GetTargetPtr();
if (!target)
return false;
uint64_t Size;
llvm::MCDisassembler::DecodeStatus S;
int stack_offset;
uint32_t machine_regno; // register numbers extracted directly out of instructions
while (m_func_bounds.ContainsFileAddress (m_cur_insn))
{
llvm::MCInst Inst;
if (!m_cur_insn.IsValid())
return false;
// Read the instruction from memory
const uint32_t max_op_byte_size = m_arch.GetMaximumOpcodeByteSize();
llvm::SmallVector <uint8_t, 32> opcode_data;
opcode_data.resize (max_op_byte_size);
if (target->ReadMemory (m_cur_insn, prefer_file_cache, opcode_data.data(),
max_op_byte_size, error) == static_cast<size_t>(-1))
{
return false;
}
// Disassemble the instruction
const addr_t PC = m_cur_insn.GetFileAddress();
S = m_disasm->getInstruction(Inst, Size, opcode_data, PC, llvm::nulls(), llvm::nulls());
if (S != llvm::MCDisassembler::Success)
return false;
if (!m_cur_insn.IsValid())
return false;
/* check addiu sp,sp,XX */
if (add_imm_sp_pattern_p (Inst,stack_offset))
{
bytes_from_initial_sp += (-stack_offset); // The bytes allocated on stack
if (bytes_from_initial_sp == 0)
{
//SP is restored
//Create a fresh, empty Row and RegisterLocation - don't mention any other registers
UnwindPlan::RowSP last_row(new UnwindPlan::Row);
UnwindPlan::Row::RegisterLocation last_regloc;
last_row->SetOffset (current_func_text_offset + Size);
last_row->SetCFARegister (m_machine_sp_regnum);
last_row->SetCFAOffset (0);
last_regloc.SetIsCFAPlusOffset (0);
last_row->SetRegisterInfo (m_machine_sp_regnum, last_regloc);
unwind_plan.AppendRow (last_row);
break;
}
else if (row->GetCFARegister() == m_machine_sp_regnum)
{
row->SetOffset (current_func_text_offset + Size); // Set offset of row in the function
UnwindPlan::Row::RegisterLocation regloc;
regloc.SetIsCFAPlusOffset (bytes_from_initial_sp);
row->SetRegisterInfo (m_machine_sp_regnum, regloc);
unwind_plan.AppendRow (row);
goto loopnext;
}
}
/* Check sw <ra/fp>,xx(sp) */
if (store_reg_pattern_p (Inst, machine_regno, stack_offset))
{
row->SetOffset (current_func_text_offset + Size);
UnwindPlan::Row::RegisterLocation regloc;
regloc.SetAtCFAPlusOffset (stack_offset);
row->SetRegisterInfo (machine_regno, regloc);
unwind_plan.AppendRow (row);
goto loopnext;
}
loopnext:
m_cur_insn.SetOffset (m_cur_insn.GetOffset() + Size);
current_func_text_offset += Size;
// Allocate a new Row, populate it with the existing Row contents.
newrow = new UnwindPlan::Row;
*newrow = *row.get();
row.reset(newrow);
}
unwind_plan.SetSourceName ("assembly insn profiling");
unwind_plan.SetSourcedFromCompiler (eLazyBoolNo);
unwind_plan.SetReturnAddressRegister(m_machine_ra_regnum);
unwind_plan.SetUnwindPlanValidAtAllInstructions (eLazyBoolYes);
return true;
}
bool
AssemblyParse_mips::augment_unwind_plan_from_call_site (AddressRange& func, UnwindPlan &unwind_plan)
{
return false;
}
bool
AssemblyParse_mips::get_fast_unwind_plan (AddressRange& func, UnwindPlan &unwind_plan)
{
return false;
}
bool
AssemblyParse_mips::find_first_non_prologue_insn (Address &address)
{
m_cur_insn = m_func_bounds.GetBaseAddress ();
if (!m_cur_insn.IsValid())
{
return false;
}
const bool prefer_file_cache = true;
Target *target = m_exe_ctx.GetTargetPtr();
while (m_func_bounds.ContainsFileAddress (m_cur_insn))
{
int offset;
uint32_t regno;
uint64_t Size;
llvm::MCInst Inst;
llvm::MCDisassembler::DecodeStatus S;
Error error;
if (!m_cur_insn.IsValid())
return false;
// Read the instruction from memory
const uint32_t max_op_byte_size = m_arch.GetMaximumOpcodeByteSize();
llvm::SmallVector <uint8_t, 32> opcode_data;
opcode_data.resize (max_op_byte_size);
if (target->ReadMemory (m_cur_insn, prefer_file_cache, opcode_data.data(),
max_op_byte_size, error) == static_cast<size_t>(-1))
{
return false;
}
// Disassemble the instruction
const addr_t PC = m_cur_insn.GetFileAddress();
S = m_disasm->getInstruction(Inst, Size, opcode_data, PC, llvm::nulls(), llvm::nulls());
if (S != llvm::MCDisassembler::Success)
return false;
if (add_imm_sp_pattern_p (Inst,offset)
|| store_reg_pattern_p (Inst,regno, offset)
|| mov_sp_to_fp_pattern_p (Inst))
{
m_cur_insn.SetOffset (m_cur_insn.GetOffset() + Size);
continue;
}
// Unknown non-prologue instruction - stop scanning
break;
}
address = m_cur_insn;
return true;
}
//-----------------------------------------------------------------------------------------------
// UnwindAssemblyParser_mips method definitions
//-----------------------------------------------------------------------------------------------
UnwindAssembly_mips::UnwindAssembly_mips (const ArchSpec &arch, int cpu) :
lldb_private::UnwindAssembly(arch),
m_cpu(cpu),
m_arch(arch)
{
}
UnwindAssembly_mips::~UnwindAssembly_mips ()
{
}
bool
UnwindAssembly_mips::GetNonCallSiteUnwindPlanFromAssembly (AddressRange& func, Thread& thread, UnwindPlan& unwind_plan)
{
ExecutionContext exe_ctx (thread.shared_from_this());
AssemblyParse_mips asm_parse(exe_ctx, m_cpu, m_arch, func);
return asm_parse.get_non_call_site_unwind_plan (unwind_plan);
}
bool
UnwindAssembly_mips::AugmentUnwindPlanFromCallSite (AddressRange& func, Thread& thread, UnwindPlan& unwind_plan)
{
ExecutionContext exe_ctx (thread.shared_from_this());
AssemblyParse_mips asm_parse(exe_ctx, m_cpu, m_arch, func);
return asm_parse.augment_unwind_plan_from_call_site (func, unwind_plan);
}
bool
UnwindAssembly_mips::GetFastUnwindPlan (AddressRange& func, Thread& thread, UnwindPlan &unwind_plan)
{
ExecutionContext exe_ctx (thread.shared_from_this());
AssemblyParse_mips asm_parse(exe_ctx, m_cpu, m_arch, func);
return asm_parse.get_fast_unwind_plan (func, unwind_plan);
}
bool
UnwindAssembly_mips::FirstNonPrologueInsn (AddressRange& func, const ExecutionContext &exe_ctx, Address& first_non_prologue_insn)
{
AssemblyParse_mips asm_parse(exe_ctx, m_cpu, m_arch, func);
return asm_parse.find_first_non_prologue_insn (first_non_prologue_insn);
}
UnwindAssembly *
UnwindAssembly_mips::CreateInstance (const ArchSpec &arch)
{
const llvm::Triple::ArchType cpu = arch.GetMachine ();
if (cpu == llvm::Triple::mips64)
return new UnwindAssembly_mips (arch, k_mips64);
else if (cpu == llvm::Triple::mips)
return new UnwindAssembly_mips (arch, k_mips32);
return NULL;
}
//------------------------------------------------------------------
// PluginInterface protocol in UnwindAssemblyParser_mips
//------------------------------------------------------------------
ConstString
UnwindAssembly_mips::GetPluginName()
{
return GetPluginNameStatic();
}
uint32_t
UnwindAssembly_mips::GetPluginVersion()
{
return 1;
}
void
UnwindAssembly_mips::Initialize()
{
PluginManager::RegisterPlugin (GetPluginNameStatic(),
GetPluginDescriptionStatic(),
CreateInstance);
}
void
UnwindAssembly_mips::Terminate()
{
PluginManager::UnregisterPlugin (CreateInstance);
}
lldb_private::ConstString
UnwindAssembly_mips::GetPluginNameStatic()
{
static ConstString g_name("mips");
return g_name;
}
const char *
UnwindAssembly_mips::GetPluginDescriptionStatic()
{
return "mips assembly language profiler plugin.";
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment