254 lines
5.6 KiB
C++
254 lines
5.6 KiB
C++
// Copyright 2008 Dolphin Emulator Project
|
|
// Copyright 2005 Duddie
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "Core/DSP/DSPDisassembler.h"
|
|
|
|
#include <algorithm>
|
|
#include <limits>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <fmt/format.h>
|
|
|
|
#include "Common/CommonTypes.h"
|
|
#include "Common/Logging/Log.h"
|
|
#include "Common/StringUtil.h"
|
|
|
|
#include "Core/DSP/DSPTables.h"
|
|
|
|
namespace DSP
|
|
{
|
|
DSPDisassembler::DSPDisassembler(const AssemblerSettings& settings) : settings_(settings)
|
|
{
|
|
}
|
|
|
|
bool DSPDisassembler::Disassemble(const std::vector<u16>& code, std::string& text)
|
|
{
|
|
if (code.size() > std::numeric_limits<u16>::max())
|
|
{
|
|
text.append("; code too large for 16-bit addressing\n");
|
|
return false;
|
|
}
|
|
|
|
for (u16 pc = 0; pc < code.size();)
|
|
{
|
|
bool failed = !DisassembleOpcode(code, &pc, text);
|
|
text.append("\n");
|
|
if (failed)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::string DSPDisassembler::DisassembleParameters(const DSPOPCTemplate& opc, u16 op1, u16 op2)
|
|
{
|
|
std::string buf;
|
|
|
|
for (int j = 0; j < opc.param_count; j++)
|
|
{
|
|
if (j > 0)
|
|
buf += ", ";
|
|
|
|
u32 val = (opc.params[j].loc >= 1) ? op2 : op1;
|
|
val &= opc.params[j].mask;
|
|
if (opc.params[j].lshift < 0)
|
|
val = val << (-opc.params[j].lshift);
|
|
else
|
|
val = val >> opc.params[j].lshift;
|
|
|
|
u32 type = opc.params[j].type;
|
|
if ((type & 0xff) == 0x10)
|
|
type &= 0xff00;
|
|
|
|
if (type & P_REG)
|
|
{
|
|
// Check for _D parameter - if so flip.
|
|
if ((type == P_ACC_D) || (type == P_ACCM_D)) // Used to be P_ACCM_D TODO verify
|
|
val = (~val & 0x1) | ((type & P_REGS_MASK) >> 8);
|
|
else
|
|
val |= (type & P_REGS_MASK) >> 8;
|
|
type &= ~P_REGS_MASK;
|
|
}
|
|
|
|
switch (type)
|
|
{
|
|
case P_REG:
|
|
if (settings_.decode_registers)
|
|
buf += fmt::format("${}", pdregname(val));
|
|
else
|
|
buf += fmt::format("${}", val);
|
|
break;
|
|
|
|
case P_PRG:
|
|
if (settings_.decode_registers)
|
|
buf += fmt::format("@${}", pdregname(val));
|
|
else
|
|
buf += fmt::format("@${}", val);
|
|
break;
|
|
|
|
case P_VAL:
|
|
case P_ADDR_I:
|
|
case P_ADDR_D:
|
|
if (settings_.decode_names)
|
|
{
|
|
buf += pdname(val);
|
|
}
|
|
else
|
|
{
|
|
buf += fmt::format("0x{:04x}", val);
|
|
}
|
|
break;
|
|
|
|
case P_IMM:
|
|
if (opc.params[j].size != 2)
|
|
{
|
|
// LSL, LSR, ASL, ASR
|
|
if (opc.params[j].mask == 0x003f)
|
|
{
|
|
// Left and right shifts function essentially as a single shift by a 7-bit signed value,
|
|
// but are split into two intructions for clarity.
|
|
buf += fmt::format("#{}", (val & 0x20) != 0 ? (int(val) - 64) : int(val));
|
|
}
|
|
else
|
|
{
|
|
buf += fmt::format("#0x{:02x}", val);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
buf += fmt::format("#0x{:04x}", val);
|
|
}
|
|
break;
|
|
|
|
case P_MEM:
|
|
if (opc.params[j].size != 2)
|
|
val = (u16)(s16)(s8)val;
|
|
|
|
if (settings_.decode_names)
|
|
buf += fmt::format("@{}", pdname(val));
|
|
else
|
|
buf += fmt::format("@0x{:04x}", val);
|
|
break;
|
|
|
|
default:
|
|
ERROR_LOG_FMT(DSPLLE, "Unknown parameter type: {:x}", opc.params[j].type);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
bool DSPDisassembler::DisassembleOpcode(const std::vector<u16>& code, u16* pc, std::string& dest)
|
|
{
|
|
return DisassembleOpcode(code.data(), code.size(), pc, dest);
|
|
}
|
|
|
|
bool DSPDisassembler::DisassembleOpcode(const u16* binbuf, size_t binbuf_size, u16* pc,
|
|
std::string& dest)
|
|
{
|
|
const u16 wrapped_pc = (*pc & 0x7fff);
|
|
if (wrapped_pc >= binbuf_size)
|
|
{
|
|
++pc;
|
|
dest.append("; outside memory");
|
|
return false;
|
|
}
|
|
|
|
const u16 op1 = binbuf[wrapped_pc];
|
|
|
|
// Find main opcode
|
|
const DSPOPCTemplate* opc = FindOpInfoByOpcode(op1);
|
|
if (!opc)
|
|
opc = &cw;
|
|
|
|
bool is_extended = false;
|
|
bool is_only_7_bit_ext = false;
|
|
|
|
if (((opc->opcode >> 12) == 0x3) && (op1 & 0x007f))
|
|
{
|
|
is_extended = true;
|
|
is_only_7_bit_ext = true;
|
|
}
|
|
else if (((opc->opcode >> 12) > 0x3) && (op1 & 0x00ff))
|
|
{
|
|
is_extended = true;
|
|
}
|
|
|
|
const DSPOPCTemplate* opc_ext = nullptr;
|
|
if (is_extended)
|
|
{
|
|
// opcode has an extension
|
|
const u16 extended_opcode = is_only_7_bit_ext ? op1 & 0x7F : op1;
|
|
opc_ext = FindExtOpInfoByOpcode(extended_opcode);
|
|
}
|
|
|
|
// printing
|
|
|
|
if (settings_.show_pc)
|
|
dest += fmt::format("{:04x} ", wrapped_pc);
|
|
|
|
u16 op2;
|
|
|
|
// Size 2 - the op has a large immediate.
|
|
if (opc->size == 2)
|
|
{
|
|
if (wrapped_pc + 1 >= binbuf_size)
|
|
{
|
|
if (settings_.show_hex)
|
|
dest += fmt::format("{:04x} ???? ", op1);
|
|
dest += fmt::format("; Insufficient data for large immediate");
|
|
*pc += opc->size;
|
|
return false;
|
|
}
|
|
|
|
op2 = binbuf[wrapped_pc + 1];
|
|
if (settings_.show_hex)
|
|
dest += fmt::format("{:04x} {:04x} ", op1, op2);
|
|
}
|
|
else
|
|
{
|
|
op2 = 0;
|
|
if (settings_.show_hex)
|
|
dest += fmt::format("{:04x} ", op1);
|
|
}
|
|
|
|
std::string opname = opc->name;
|
|
if (is_extended)
|
|
opname += fmt::format("{}{}", settings_.ext_separator, opc_ext->name);
|
|
if (settings_.lower_case_ops)
|
|
Common::ToLower(&opname);
|
|
|
|
if (settings_.print_tabs)
|
|
dest += fmt::format("{}\t", opname);
|
|
else
|
|
dest += fmt::format("{:<12}", opname);
|
|
|
|
if (opc->param_count > 0)
|
|
dest += DisassembleParameters(*opc, op1, op2);
|
|
|
|
// Handle opcode extension.
|
|
if (is_extended)
|
|
{
|
|
if (opc->param_count > 0)
|
|
dest += " ";
|
|
|
|
dest += ": ";
|
|
|
|
if (opc_ext->param_count > 0)
|
|
dest += DisassembleParameters(*opc_ext, op1, op2);
|
|
}
|
|
|
|
if (opc->opcode_mask == 0)
|
|
{
|
|
// unknown opcode
|
|
dest += "\t\t; *** UNKNOWN OPCODE ***";
|
|
}
|
|
|
|
*pc += is_extended ? opc_ext->size : opc->size;
|
|
|
|
return true;
|
|
}
|
|
} // namespace DSP
|