Debugger: GB - Added assembler tool

This commit is contained in:
Sour 2020-06-06 22:27:54 -04:00
parent 7852d6f75c
commit 9c4fb9c45c
22 changed files with 608 additions and 57 deletions

View file

@ -415,6 +415,10 @@ Assembler::Assembler(shared_ptr<LabelManager> labelManager)
_labelManager = labelManager;
}
Assembler::~Assembler()
{
}
uint32_t Assembler::AssembleCode(string code, uint32_t startAddress, int16_t* assembledCode)
{
for(uint8_t i = 0; i < 255; i++) {

View file

@ -1,7 +1,7 @@
#pragma once
#include "stdafx.h"
#include <unordered_set>
#include <regex>
#include "IAssembler.h"
#include "CpuDisUtils.h"
class LabelManager;
@ -21,24 +21,7 @@ struct LineData
bool HasOpeningBracket = false;
};
enum AssemblerSpecialCodes
{
OK = 0,
EndOfLine = -1,
ParsingError = -2,
OutOfRangeJump = -3,
LabelRedefinition = -4,
MissingOperand = -5,
OperandOutOfRange = -6,
InvalidHex = -7,
InvalidSpaces = -8,
TrailingText = -9,
UnknownLabel = -10,
InvalidInstruction = -11,
InvalidBinaryValue = -12,
};
class Assembler
class Assembler : public IAssembler
{
private:
std::unordered_map<string, std::unordered_set<int>> _availableModesByOpName;
@ -54,6 +37,7 @@ private:
public:
Assembler(shared_ptr<LabelManager> labelManager);
virtual ~Assembler();
uint32_t AssembleCode(string code, uint32_t startAddress, int16_t* assembledCode);
};

View file

@ -69,6 +69,7 @@
<ClInclude Include="GameboyDisUtils.h" />
<ClInclude Include="GameboyHeader.h" />
<ClInclude Include="GbApu.h" />
<ClInclude Include="GbAssembler.h" />
<ClInclude Include="GbCart.h" />
<ClInclude Include="GbCartFactory.h" />
<ClInclude Include="GbCpu.h" />
@ -86,6 +87,7 @@
<ClInclude Include="GbTimer.h" />
<ClInclude Include="GbTypes.h" />
<ClInclude Include="GbWaveChannel.h" />
<ClInclude Include="IAssembler.h" />
<ClInclude Include="NecDspDebugger.h" />
<ClInclude Include="ForceDisconnectMessage.h" />
<ClInclude Include="GameClient.h" />
@ -279,6 +281,7 @@
<ClCompile Include="Gameboy.cpp" />
<ClCompile Include="GameboyDisUtils.cpp" />
<ClCompile Include="GbApu.cpp" />
<ClCompile Include="GbAssembler.cpp" />
<ClCompile Include="GbCpu.cpp" />
<ClCompile Include="GbDebugger.cpp" />
<ClCompile Include="GbDmaController.cpp" />

View file

@ -488,9 +488,6 @@
<ClInclude Include="Profiler.h">
<Filter>Debugger</Filter>
</ClInclude>
<ClInclude Include="Assembler.h">
<Filter>Debugger</Filter>
</ClInclude>
<ClInclude Include="Spc7110.h">
<Filter>SNES\Coprocessors\SPC7110</Filter>
</ClInclude>
@ -590,6 +587,15 @@
<ClInclude Include="GbDmaController.h">
<Filter>GB</Filter>
</ClInclude>
<ClInclude Include="Assembler.h">
<Filter>Debugger\Assembler</Filter>
</ClInclude>
<ClInclude Include="GbAssembler.h">
<Filter>Debugger\Assembler</Filter>
</ClInclude>
<ClInclude Include="IAssembler.h">
<Filter>Debugger\Assembler</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="stdafx.cpp" />
@ -875,9 +881,6 @@
<ClCompile Include="Profiler.cpp">
<Filter>Debugger</Filter>
</ClCompile>
<ClCompile Include="Assembler.cpp">
<Filter>Debugger</Filter>
</ClCompile>
<ClCompile Include="Spc7110.cpp">
<Filter>SNES\Coprocessors\SPC7110</Filter>
</ClCompile>
@ -938,6 +941,12 @@
<ClCompile Include="GbDmaController.cpp">
<Filter>GB</Filter>
</ClCompile>
<ClCompile Include="Assembler.cpp">
<Filter>Debugger\Assembler</Filter>
</ClCompile>
<ClCompile Include="GbAssembler.cpp">
<Filter>Debugger\Assembler</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Filter Include="SNES">
@ -1027,5 +1036,8 @@
<Filter Include="Debugger\EventManager">
<UniqueIdentifier>{b1753ff0-0c73-4acf-978b-1964222e01c6}</UniqueIdentifier>
</Filter>
<Filter Include="Debugger\Assembler">
<UniqueIdentifier>{8abb14c6-e2ee-48cb-ad91-38402cb8510c}</UniqueIdentifier>
</Filter>
</ItemGroup>
</Project>

View file

@ -20,6 +20,7 @@
#include "Console.h"
#include "MemoryAccessCounter.h"
#include "ExpressionEvaluator.h"
#include "Assembler.h"
CpuDebugger::CpuDebugger(Debugger* debugger, CpuType cpuType)
{
@ -39,6 +40,7 @@ CpuDebugger::CpuDebugger(Debugger* debugger, CpuType cpuType)
_callstackManager.reset(new CallstackManager(debugger));
_breakpointManager.reset(new BreakpointManager(debugger, cpuType, _eventManager.get()));
_step.reset(new StepRequest());
_assembler.reset(new Assembler(_debugger->GetLabelManager()));
if(GetState().PC == 0) {
//Enable breaking on uninit reads when debugger is opened at power on
@ -240,6 +242,11 @@ shared_ptr<EventManager> CpuDebugger::GetEventManager()
return _eventManager;
}
shared_ptr<Assembler> CpuDebugger::GetAssembler()
{
return _assembler;
}
shared_ptr<CallstackManager> CpuDebugger::GetCallstackManager()
{
return _callstackManager;

View file

@ -17,6 +17,7 @@ class EventManager;
class MemoryMappings;
class BreakpointManager;
class Sa1;
class Assembler;
class CpuDebugger final : public IDebugger
{
@ -31,6 +32,7 @@ class CpuDebugger final : public IDebugger
Sa1* _sa1;
shared_ptr<EventManager> _eventManager;
shared_ptr<Assembler> _assembler;
shared_ptr<CallstackManager> _callstackManager;
unique_ptr<BreakpointManager> _breakpointManager;
unique_ptr<StepRequest> _step;
@ -55,6 +57,7 @@ public:
void ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool forNmi);
shared_ptr<EventManager> GetEventManager();
shared_ptr<Assembler> GetAssembler();
shared_ptr<CallstackManager> GetCallstackManager();
BreakpointManager* GetBreakpointManager();
};

View file

@ -42,6 +42,7 @@
#include "InternalRegisters.h"
#include "AluMulDiv.h"
#include "Assembler.h"
#include "GbAssembler.h"
#include "../Utilities/HexUtilities.h"
#include "../Utilities/FolderUtilities.h"
#include "../Utilities/IpsPatcher.h"
@ -74,7 +75,6 @@ Debugger::Debugger(shared_ptr<Console> console)
_memoryAccessCounter.reset(new MemoryAccessCounter(this, console.get()));
_ppuTools.reset(new PpuTools(_console.get(), _ppu.get()));
_scriptManager.reset(new ScriptManager(this));
_assembler.reset(new Assembler(_labelManager));
if(_cart->GetGameboy()) {
_gbDebugger.reset(new GbDebugger(this));
@ -715,9 +715,13 @@ shared_ptr<Console> Debugger::GetConsole()
return _console;
}
shared_ptr<Assembler> Debugger::GetAssembler()
shared_ptr<IAssembler> Debugger::GetAssembler(CpuType cpuType)
{
return _assembler;
if(cpuType == CpuType::Gameboy) {
return std::dynamic_pointer_cast<IAssembler>(_gbDebugger->GetAssembler());
} else {
return std::dynamic_pointer_cast<IAssembler>(_cpuDebugger->GetAssembler());
}
}
template void Debugger::ProcessMemoryRead<CpuType::Cpu>(uint32_t addr, uint8_t value, MemoryOperationType opType);

View file

@ -34,8 +34,8 @@ class NecDspDebugger;
class Cx4Debugger;
class GbDebugger;
class Breakpoint;
class Assembler;
class IEventManager;
class IAssembler;
enum class EventType;
enum class EvalResultType : int32_t;
@ -69,7 +69,6 @@ private:
shared_ptr<Disassembler> _disassembler;
shared_ptr<PpuTools> _ppuTools;
shared_ptr<LabelManager> _labelManager;
shared_ptr<Assembler> _assembler;
unique_ptr<ExpressionEvaluator> _watchExpEval[(int)DebugUtilities::GetLastCpuType() + 1];
@ -144,5 +143,5 @@ public:
shared_ptr<ScriptManager> GetScriptManager();
shared_ptr<CallstackManager> GetCallstackManager(CpuType cpuType);
shared_ptr<Console> GetConsole();
shared_ptr<Assembler> GetAssembler();
shared_ptr<IAssembler> GetAssembler(CpuType cpuType);
};

View file

@ -8,14 +8,14 @@
#include"../Utilities/HexUtilities.h"
constexpr const char* _opTemplate[256] = {
"NOP", "LD BC, e", "LD (BC), A", "INC BC", "INC B", "DEC B", "LD B, d", "RLCA", "LD (b), SP", "ADD HL, BC", "LD A, (BC)", "DEC BC", "INC C", "DEC C", "LD C, d", "RRCA",
"NOP", "LD BC, e", "LD (BC), A", "INC BC", "INC B", "DEC B", "LD B, d", "RLCA", "LD (a), SP", "ADD HL, BC", "LD A, (BC)", "DEC BC", "INC C", "DEC C", "LD C, d", "RRCA",
"STOP", "LD DE, e", "LD (DE), A", "INC DE", "INC D", "DEC D", "LD D, d", "RLA", "JR r", "ADD HL, DE", "LD A, (DE)", "DEC DE", "INC E", "DEC E", "LD E, d", "RRA",
"JR NZ, r", "LD HL, e", "LD (HL+), A", "INC HL", "INC H", "DEC H", "LD H, d", "DAA", "JR Z, r", "ADD HL, HL", "LD A, (HL+)", "DEC HL", "INC L", "DEC L", "LD L, d", "CPL",
"JR NC, r", "LD SP, e", "LD (HL-), A", "INC SP", "INC (HL)", "DEC (HL)", "LD (HL), d", "SCF", "JR C, r", "ADD HL, SP", "LD A, (HL-)", "DEC SP", "INC A", "DEC A", "LD A, d", "CCF",
"LD B, B", "LD B, C", "LD B, D", "LD B, E", "LD B, H", "LD B, L", "LD B, (HL)", "LD B, A", "LD C, B", "LD C, C", "LD C, D", "LD C, E", "LD C, H", "LD C, L", "LD C, (HL)", "LD C, A",
"LD D, B", "LD D, C", "LD D, D", "LD D, E", "LD D, H", "LD D, L", "LD D, (HL)", "LD D, A", "LD E, B", "LD E, C", "LD E, D", "LD E, E", "LD E, H", "LD E, L", "LD E, (HL)", "LD E, A",
"LD H, B", "LD H, C", "LD H, D", "LD H, E", "LD H, H", "LD H, L", "LD H, (HL)", "LD H, A", "LD L, B", "LD L, C", "LD L, D", "LD L, E", "LD L, H", "LD L, L", "LD L, (HL)", "LD L, A",
"LD (HL), B", "LD (HL), C", "LD (HL), D", "LD (HL), E","LD (HL), H", "LD (HL), L", "HALT", "LD (HL), A", "LD A, B", "LD A, C", "LD A, D", "LD A, E", "LD A, H", "LD A, L", "LD A, (HL)", "LD A, A",
"LD (HL), B", "LD (HL), C", "LD (HL), D", "LD (HL), E","LD (HL), H", "LD (HL), L", "HALT", "LD (HL), A","LD A, B", "LD A, C", "LD A, D", "LD A, E", "LD A, H", "LD A, L", "LD A, (HL)", "LD A, A",
"ADD A, B", "ADD A, C", "ADD A, D", "ADD A, E", "ADD A, H", "ADD A, L", "ADD A, (HL)", "ADD A, A", "ADC A, B", "ADC A, C", "ADC A, D", "ADC A, E", "ADC A, H", "ADC A, L", "ADC A, (HL)", "ADC A, A",
"SUB B", "SUB C", "SUB D", "SUB E", "SUB H", "SUB L", "SUB (HL)", "SUB A", "SBC A, B", "SBC A, C", "SBC A, D", "SBC A, E", "SBC A, H", "SBC A, L", "SBC A, (HL)", "SBC A, A",
"AND B", "AND C", "AND D", "AND E", "AND H", "AND L", "AND (HL)", "AND A", "XOR B", "XOR C", "XOR D", "XOR E", "XOR H", "XOR L", "XOR (HL)", "XOR A",
@ -23,7 +23,7 @@ constexpr const char* _opTemplate[256] = {
"RET NZ", "POP BC", "JP NZ, a", "JP a", "CALL NZ, a", "PUSH BC", "ADD A, d", "RST 00H", "RET Z", "RET", "JP Z, a", "PREFIX", "CALL Z, a","CALL a", "ADC A, d", "RST 08H",
"RET NC", "POP DE", "JP NC, a", "ILL_D3", "CALL NC, a", "PUSH DE", "SUB d", "RST 10H", "RET C", "RETI", "JP C, a", "ILL_DB", "CALL C, a","ILL_DD", "SBC A, d", "RST 18H",
"LDH (c), A", "POP HL", "LD ($FF00+C), A","ILL_E3","ILL_E4", "PUSH HL", "AND d", "RST 20H", "ADD SP, d", "JP HL", "LD (a), A", "ILL_EB", "ILL_EC", "ILL_ED", "XOR d", "RST 28H",
"LDH A, (c)", "POP AF", "LD A, ($FF00+C)","DI", "ILL_F4", "PUSH AF", "OR d", "RST 30H", "LD HL, SP+d", "LD SP, HL", "LD A, (a)", "EI", "ILL_FC", "ILL_FD", "CP d", "RST 38H"
"LDH A, (c)", "POP AF", "LD A, ($FF00+C)","DI", "ILL_F4", "PUSH AF", "OR d", "RST 30H", "LD HL, SP+d", "LD SP, HL", "LD A, (a)", "EI", "ILL_FC", "ILL_FD", "CP d", "RST 38H"
};
constexpr const char* _cbTemplate[256] = {
@ -93,8 +93,7 @@ void GameboyDisUtils::GetDisassembly(DisassemblyInfo& info, string& out, uint32_
//Jump addresses, memory addresses
case 'a': getOperand((uint16_t)(byteCode[1] | (byteCode[2] << 8))); break;
case 'b': str.WriteAll('$', HexUtilities::ToHex(byteCode[1])); break;
case 'c': str.WriteAll('$', HexUtilities::ToHex((uint16_t)(0xFF00 | byteCode[1]))); break;
case 'c': getOperand((uint16_t)(0xFF00 | byteCode[1])); break;
//Immediate values
case 'd': str.WriteAll("$", HexUtilities::ToHex(byteCode[1])); break;
@ -113,7 +112,6 @@ int32_t GameboyDisUtils::GetEffectiveAddress(DisassemblyInfo& info, Console* con
return -1;
}
uint8_t GameboyDisUtils::GetOpSize(uint8_t opCode)
{
return _opSize[opCode];
@ -134,4 +132,9 @@ bool GameboyDisUtils::IsReturnInstruction(uint8_t opCode)
opCode == 0xC0 || opCode == 0xC8 || opCode == 0xD0 || opCode == 0xD8 || //Conditional RET
opCode == 0xC9 || opCode == 0xD9 //Unconditional RET/RETI
);
}
string GameboyDisUtils::GetOpTemplate(uint8_t op, bool prefixed)
{
return prefixed ? _cbTemplate[op] : _opTemplate[op];
}

View file

@ -15,4 +15,5 @@ public:
static uint8_t GetOpSize(uint8_t opCode);
static bool IsJumpToSub(uint8_t opCode);
static bool IsReturnInstruction(uint8_t opCode);
static string GetOpTemplate(uint8_t op, bool prefixed);
};

410
Core/GbAssembler.cpp Normal file
View file

@ -0,0 +1,410 @@
#include "stdafx.h"
#include <regex>
#include "GbAssembler.h"
#include "LabelManager.h"
#include "GameboyDisUtils.h"
#include "../Utilities/StringUtilities.h"
#include "../Utilities/HexUtilities.h"
static const std::regex labelRegex = std::regex("^\\s*([@_a-zA-Z][@_a-zA-Z0-9]*)", std::regex_constants::icase);
GbAssembler::GbAssembler(shared_ptr<LabelManager> labelManager)
{
_labelManager = labelManager;
InitAssembler();
}
void GbAssembler::InitAssembler()
{
for(int i = 0; i < 512; i++) {
string op = GameboyDisUtils::GetOpTemplate(i & 0xFF, i >= 256);
size_t spaceIndex = op.find(' ');
size_t commaIndex = op.find(',');
string opName;
OpCodeEntry entry = {};
if(spaceIndex != string::npos) {
opName = op.substr(0, spaceIndex);
entry.ParamCount = commaIndex != string::npos ? 2 : 1;
} else {
opName = op;
entry.ParamCount = 0;
}
entry.OpCode = i < 256 ? i : ((i << 8) | 0xCB);
std::transform(opName.begin(), opName.end(), opName.begin(), ::tolower);
if(_opCodes.find(opName) == _opCodes.end()) {
_opCodes[opName] = vector<OpCodeEntry>();
}
if(entry.ParamCount > 0) {
string operands = op.substr(spaceIndex + 1);
operands.erase(std::remove_if(operands.begin(), operands.end(), isspace), operands.end());
if(entry.ParamCount == 2) {
vector<string> operandList = StringUtilities::Split(operands, ',');
InitParamEntry(entry.Param1, operandList[0]);
InitParamEntry(entry.Param2, operandList[1]);
} else if(entry.ParamCount == 1) {
InitParamEntry(entry.Param1, operands);
}
}
_opCodes[opName].push_back(entry);
}
}
void GbAssembler::InitParamEntry(ParamEntry& entry, string param)
{
if(param == "a") {
entry.Type = ParamType::Short;
} else if(param == "d") {
entry.Type = ParamType::Byte;
} else if(param == "e") {
entry.Type = ParamType::Short;
} else if(param == "r") {
entry.Type = ParamType::RelAddress;
} else if(param == "(a)") {
entry.Type = ParamType::Address;
} else if(param == "(c)") {
entry.Type = ParamType::HighAddress;
} else if(param == "SP+d") {
entry.Type = ParamType::StackOffset;
} else {
std::transform(param.begin(), param.end(), param.begin(), ::tolower);
entry.Type = ParamType::Literal;
entry.Param = param;
}
entry.Param = param;
}
bool GbAssembler::IsRegisterName(string op)
{
return op == "hl" || op == "af" || op == "bc" || op == "de" || op == "a" || op == "b" || op == "c" || op == "d" || op == "e" || op == "f" || op == "l" || op == "h";
}
int GbAssembler::ReadValue(string operand, int min, int max, unordered_map<string, uint16_t>& localLabels, bool firstPass)
{
int value = 0;
switch(operand[0]) {
//Hex
case '$': value = HexUtilities::FromHex(operand.substr(1)); break;
case '%':
//Binary
for(size_t i = 1; i < operand.size(); i++) {
value <<= 1;
value |= operand[i] == '1' ? 1 : 0;
}
break;
default:
if(std::regex_match(operand, labelRegex)) {
if(firstPass) {
return 0;
} else if(localLabels.find(operand) != localLabels.end()) {
value = localLabels.find(operand)->second;
} else {
int labelAddress = _labelManager->GetLabelRelativeAddress(operand, CpuType::Gameboy);
if(labelAddress >= 0) {
//Matching label found
value = labelAddress;
}
}
} else {
//Decimal
for(int i = 0; i < operand.size(); i++) {
if(operand[i] != '-' && (operand[i] < '0' || operand[i] > '9')) {
return -1;
}
}
try {
value = std::stoi(operand);
if(value < 0) {
value = max + value + 1;
}
} catch(std::exception&) {
return -1;
}
}
break;
}
if(value < min || value > max) {
return -1;
}
return value;
}
bool GbAssembler::IsMatch(ParamEntry& entry, string operand, uint32_t address, unordered_map<string, uint16_t>& localLabels, bool firstPass)
{
if(entry.Type != ParamType::Literal && IsRegisterName(operand)) {
return false;
}
switch(entry.Type) {
case ParamType::None: return false;
case ParamType::Literal: {
string param = entry.Param;
std::transform(param.begin(), param.end(), param.begin(), ::tolower);
std::transform(operand.begin(), operand.end(), operand.begin(), ::tolower);
return operand == param;
}
case ParamType::Byte:
return ReadValue(operand, -128, 0xFF, localLabels, firstPass) >= 0;
case ParamType::Short:
return ReadValue(operand, -32768, 0xFFFF, localLabels, firstPass) >= 0;
case ParamType::Address:
if(operand.size() > 2 && operand[0] == '(' && operand[operand.size() - 1] == ')') {
return ReadValue(operand.substr(1, operand.size() - 2), 0, 0xFFFF, localLabels, firstPass) >= 0;
}
return false;
case ParamType::HighAddress:
if(operand.size() > 2 && operand[0] == '(' && operand[operand.size() - 1] == ')') {
return ReadValue(operand.substr(1, operand.size() - 2), 0xFF00, 0xFFFF, localLabels, firstPass) >= 0;
}
return false;
case ParamType::StackOffset:
std::transform(operand.begin(), operand.end(), operand.begin(), ::tolower);
if(operand.size() > 3 && operand.substr(0, 3) == "sp+") {
return ReadValue(operand.substr(3), 0, 0xFF, localLabels, firstPass) >= 0;
}
return false;
case ParamType::RelAddress: {
int value = ReadValue(operand, 0, 0xFFFF, localLabels, firstPass);
if(value >= 0) {
int offset = (value - (address + 2));
return offset >= -128 && offset <= 127;
} else if(firstPass) {
return 0;
}
return false;
}
}
return true;
}
void GbAssembler::PushOp(uint16_t opCode, vector<int16_t>& output, uint32_t& address)
{
if(opCode < 256) {
PushByte((uint8_t)opCode, output, address);
} else {
PushWord((uint16_t)opCode, output, address);
}
}
void GbAssembler::PushByte(uint8_t operand, vector<int16_t>& output, uint32_t& address)
{
output.push_back(operand);
address++;
}
void GbAssembler::PushWord(uint16_t operand, vector<int16_t>& output, uint32_t& address)
{
output.push_back((uint8_t)operand);
output.push_back((operand >> 8));
address += 2;
}
void GbAssembler::ProcessOperand(ParamEntry& entry, string operand, vector<int16_t>& output, uint32_t& address, unordered_map<string, uint16_t>& localLabels, bool firstPass)
{
switch(entry.Type) {
case ParamType::Byte:
PushByte((uint8_t)ReadValue(operand, -128, 255, localLabels, firstPass), output, address);
break;
case ParamType::Short:
PushWord((uint16_t)ReadValue(operand, -32768, 65535, localLabels, firstPass), output, address);
break;
case ParamType::Address:
if(operand.size() > 2 && operand[0] == '(' && operand[operand.size() - 1] == ')') {
PushWord((uint16_t)ReadValue(operand.substr(1, operand.size() - 2), 0, 0xFFFF, localLabels, firstPass), output, address);
}
break;
case ParamType::HighAddress:
if(operand.size() > 2 && operand[0] == '(' && operand[operand.size() - 1] == ')') {
PushByte((uint8_t)ReadValue(operand.substr(1, operand.size() - 2), 0xFF00, 0xFFFF, localLabels, firstPass), output, address);
}
break;
case ParamType::StackOffset:
std::transform(operand.begin(), operand.end(), operand.begin(), ::tolower);
if(operand.size() > 3 && operand.substr(0, 3) == "sp+") {
PushByte((uint8_t)ReadValue(operand.substr(3), 0, 0xFF, localLabels, firstPass), output, address);
}
break;
case ParamType::RelAddress: {
int value = ReadValue(operand, 0, 0xFFFF, localLabels, firstPass);
int offset = (value - (address + 1));
PushByte((uint8_t)offset, output, address);
break;
}
}
}
void GbAssembler::RunPass(vector<int16_t>& output, string code, uint32_t address, int16_t* assembledCode, bool firstPass, unordered_map<string, uint16_t>& localLabels)
{
unordered_set<string> currentPassLabels;
for(string line : StringUtilities::Split(code, '\n')) {
//Remove comment
size_t commentIndex = line.find(';');
if(commentIndex != string::npos) {
line = line.substr(0, commentIndex);
}
//Check if this is a label definition
size_t labelDefIndex = line.find(':');
if(labelDefIndex != string::npos) {
std::smatch match;
string labelName = line.substr(0, labelDefIndex);
if(std::regex_search(labelName, match, labelRegex)) {
string label = match.str(1);
if(firstPass && currentPassLabels.find(label) != currentPassLabels.end()) {
output.push_back(AssemblerSpecialCodes::LabelRedefinition);
continue;
} else {
localLabels[label] = address;
currentPassLabels.emplace(label);
line = line.substr(labelDefIndex + 1);
}
} else {
output.push_back(AssemblerSpecialCodes::InvalidLabel);
continue;
}
}
//Trim left spaces
size_t startIndex = line.find_first_not_of("\t ");
if(startIndex > 0 && startIndex != string::npos) {
line = line.substr(startIndex);
}
//Check if this is a .db statement
if(line.size() > 3 && line.substr(0, 3) == ".db") {
line = line.substr(3);
for(string byte : StringUtilities::Split(line, ' ')) {
if(byte.empty()) {
continue;
}
int value = ReadValue(byte, -128, 255, localLabels, true);
if(value >= 0) {
PushByte((uint8_t)value, output, address);
}
}
output.push_back(AssemblerSpecialCodes::EndOfLine);
continue;
}
//Find op code name
size_t spaceIndex = line.find(' ');
string opName;
if(spaceIndex != string::npos) {
opName = line.substr(0, spaceIndex);
} else {
opName = line;
}
if(opName.empty()) {
output.push_back(AssemblerSpecialCodes::EndOfLine);
continue;
}
std::transform(opName.begin(), opName.end(), opName.begin(), ::tolower);
if(_opCodes.find(opName) == _opCodes.end()) {
//No matching opcode found, mark it as invalid
output.push_back(AssemblerSpecialCodes::InvalidInstruction);
continue;
}
//Find the operands given
int paramCount = 0;
vector<string> operandList;
if(spaceIndex != string::npos) {
string operands = line.substr(spaceIndex + 1);
operands.erase(std::remove_if(operands.begin(), operands.end(), isspace), operands.end());
if(!operands.empty()) {
size_t commaIndex = line.find(',');
if(commaIndex != string::npos) {
paramCount = 2;
operandList = StringUtilities::Split(operands, ',');
bool invalid = operandList.size() > 2;
for(string operand : operandList) {
if(operand.empty()) {
invalid = true;
break;
}
}
if(invalid) {
output.push_back(AssemblerSpecialCodes::InvalidOperands);
continue;
}
} else {
paramCount = 1;
operandList = { operands };
}
}
}
bool matchFound = false;
//Find a matching set of opcode + operands
for(OpCodeEntry& entry : _opCodes.find(opName)->second) {
if(entry.ParamCount == paramCount) {
if(paramCount == 0) {
PushOp(entry.OpCode, output, address);
matchFound = true;
break;
} else if(paramCount == 1) {
if(IsMatch(entry.Param1, operandList[0], address, localLabels, firstPass)) {
PushOp(entry.OpCode, output, address);
ProcessOperand(entry.Param1, operandList[0], output, address, localLabels, firstPass);
matchFound = true;
break;
}
} else if(paramCount == 2) {
if(IsMatch(entry.Param1, operandList[0], address, localLabels, firstPass) && IsMatch(entry.Param2, operandList[1], address, localLabels, firstPass)) {
PushOp(entry.OpCode, output, address);
ProcessOperand(entry.Param1, operandList[0], output, address, localLabels, firstPass);
ProcessOperand(entry.Param2, operandList[1], output, address, localLabels, firstPass);
matchFound = true;
break;
}
}
}
}
if(!matchFound) {
output.push_back(AssemblerSpecialCodes::InvalidOperands);
} else {
output.push_back(AssemblerSpecialCodes::EndOfLine);
}
}
}
uint32_t GbAssembler::AssembleCode(string code, uint32_t address, int16_t* assembledCode)
{
vector<int16_t> output;
unordered_map<string, uint16_t> localLabels;
RunPass(output, code, address, assembledCode, true, localLabels);
output.clear();
RunPass(output, code, address, assembledCode, false, localLabels);
memcpy(assembledCode, output.data(), std::min<int>(100000, (int)output.size()) * sizeof(uint16_t));
return (uint32_t)output.size();
}

55
Core/GbAssembler.h Normal file
View file

@ -0,0 +1,55 @@
#pragma once
#include "stdafx.h"
#include "IAssembler.h"
class LabelManager;
enum class ParamType
{
None,
Literal,
Byte,
Short,
Address,
HighAddress,
RelAddress,
StackOffset
};
struct ParamEntry
{
string Param;
ParamType Type;
};
struct OpCodeEntry
{
uint16_t OpCode;
int ParamCount;
ParamEntry Param1;
ParamEntry Param2;
};
class GbAssembler : public IAssembler
{
private:
unordered_map<string, vector<OpCodeEntry>> _opCodes;
shared_ptr<LabelManager> _labelManager;
void InitParamEntry(ParamEntry& entry, string param);
bool IsRegisterName(string op);
void InitAssembler();
int ReadValue(string operand, int min, int max, unordered_map<string, uint16_t>& localLabels, bool firstPass);
bool IsMatch(ParamEntry& entry, string operand, uint32_t address, unordered_map<string, uint16_t>& localLabels, bool firstPass);
void PushOp(uint16_t opCode, vector<int16_t>& output, uint32_t& address);
void PushByte(uint8_t operand, vector<int16_t>& output, uint32_t& address);
void PushWord(uint16_t operand, vector<int16_t>& output, uint32_t& address);
void ProcessOperand(ParamEntry& entry, string operand, vector<int16_t>& output, uint32_t& address, unordered_map<string, uint16_t>& localLabels, bool firstPass);
void RunPass(vector<int16_t>& output, string code, uint32_t address, int16_t* assembledCode, bool firstPass, unordered_map<string, uint16_t>& localLabels);
public:
GbAssembler(shared_ptr<LabelManager> labelManager);
uint32_t AssembleCode(string code, uint32_t startAddress, int16_t* assembledCode);
};

View file

@ -16,6 +16,7 @@
#include "GameboyDisUtils.h"
#include "GbEventManager.h"
#include "BaseEventManager.h"
#include "GbAssembler.h"
GbDebugger::GbDebugger(Debugger* debugger)
{
@ -31,6 +32,11 @@ GbDebugger::GbDebugger(Debugger* debugger)
_callstackManager.reset(new CallstackManager(debugger));
_breakpointManager.reset(new BreakpointManager(debugger, CpuType::Gameboy, _eventManager.get()));
_step.reset(new StepRequest());
_assembler.reset(new GbAssembler(debugger->GetLabelManager()));
}
GbDebugger::~GbDebugger()
{
}
void GbDebugger::Reset()
@ -159,6 +165,11 @@ shared_ptr<GbEventManager> GbDebugger::GetEventManager()
return _eventManager;
}
shared_ptr<GbAssembler> GbDebugger::GetAssembler()
{
return _assembler;
}
shared_ptr<CallstackManager> GbDebugger::GetCallstackManager()
{
return _callstackManager;

View file

@ -13,6 +13,7 @@ class MemoryManager;
class BreakpointManager;
class EmuSettings;
class GbEventManager;
class GbAssembler;
class GbDebugger final : public IDebugger
{
@ -28,12 +29,14 @@ class GbDebugger final : public IDebugger
shared_ptr<CallstackManager> _callstackManager;
unique_ptr<BreakpointManager> _breakpointManager;
unique_ptr<StepRequest> _step;
shared_ptr<GbAssembler> _assembler;
uint8_t _prevOpCode = 0xFF;
uint32_t _prevProgramCounter = 0;
public:
GbDebugger(Debugger* debugger);
~GbDebugger();
void Reset();
@ -44,6 +47,7 @@ public:
void ProcessInterrupt(uint32_t originalPc, uint32_t currentPc);
shared_ptr<GbEventManager> GetEventManager();
shared_ptr<GbAssembler> GetAssembler();
shared_ptr<CallstackManager> GetCallstackManager();
BreakpointManager* GetBreakpointManager();
};

27
Core/IAssembler.h Normal file
View file

@ -0,0 +1,27 @@
#pragma once
#include "stdafx.h"
class IAssembler
{
public:
virtual uint32_t AssembleCode(string code, uint32_t startAddress, int16_t* assembledCode) = 0;
};
enum AssemblerSpecialCodes
{
OK = 0,
EndOfLine = -1,
ParsingError = -2,
OutOfRangeJump = -3,
LabelRedefinition = -4,
MissingOperand = -5,
OperandOutOfRange = -6,
InvalidHex = -7,
InvalidSpaces = -8,
TrailingText = -9,
UnknownLabel = -10,
InvalidInstruction = -11,
InvalidBinaryValue = -12,
InvalidOperands = -13,
InvalidLabel = -14,
};

View file

@ -106,7 +106,7 @@ extern "C"
DllExport const char* __stdcall GetScriptLog(int32_t scriptId) { return GetDebugger()->GetScriptManager()->GetScriptLog(scriptId); }
//DllExport void __stdcall DebugSetScriptTimeout(uint32_t timeout) { LuaScriptingContext::SetScriptTimeout(timeout); }
DllExport uint32_t __stdcall AssembleCode(char* code, uint32_t startAddress, int16_t* assembledOutput) { return GetDebugger()->GetAssembler()->AssembleCode(code, startAddress, assembledOutput); }
DllExport uint32_t __stdcall AssembleCode(CpuType cpuType, char* code, uint32_t startAddress, int16_t* assembledOutput) { return GetDebugger()->GetAssembler(cpuType)->AssembleCode(code, startAddress, assembledOutput); }
DllExport void __stdcall SaveRomToDisk(char* filename, bool saveIpsFile, CdlStripOption cdlStripOption) { GetDebugger()->SaveRomToDisk(filename, saveIpsFile, cdlStripOption); }
};

View file

@ -63,6 +63,7 @@ SOURCES_CXX := $(LIBRETRO_DIR)/libretro.cpp \
$(CORE_DIR)/GameServer.cpp \
$(CORE_DIR)/GameServerConnection.cpp \
$(CORE_DIR)/Gameboy.cpp \
$(CORE_DIR)/GbAssembler.cpp \
$(CORE_DIR)/GbCpu.cpp \
$(CORE_DIR)/GbDmaController.cpp \
$(CORE_DIR)/GbPpu.cpp \

View file

@ -511,7 +511,7 @@ namespace Mesen.GUI.Debugger.Controls
if(range.Start.Address >= 0 && range.End.Address >= 0 && range.Start.Address <= range.End.Address) {
int length = range.End.Address - range.Start.Address + 1;
DebugWindowManager.OpenAssembler(GetSelectedCode(), range.Start.Address, length);
DebugWindowManager.OpenAssembler(_manager.CpuType, GetSelectedCode(), range.Start.Address, length);
}
}
@ -650,7 +650,9 @@ namespace Mesen.GUI.Debugger.Controls
bool showMarkAs = !_inSourceView && (_manager.CpuType == CpuType.Cpu || _manager.CpuType == CpuType.Sa1);
mnuMarkSelectionAs.Visible = showMarkAs;
mnuEditSelectedCode.Visible = showMarkAs;
bool showEditCode = !_inSourceView && (_manager.CpuType == CpuType.Cpu || _manager.CpuType == CpuType.Sa1 || _manager.CpuType == CpuType.Gameboy);
mnuEditSelectedCode.Visible = showEditCode;
mnuAddToWatch.Enabled = active;
mnuEditInMemoryTools.Enabled = active;

View file

@ -111,13 +111,13 @@ namespace Mesen.GUI.Debugger
throw new Exception("Invalid CPU type");
}
public static void OpenAssembler(string code = "", int startAddress = 0, int blockLength = 0)
public static void OpenAssembler(CpuType cpuType, string code = "", int startAddress = 0, int blockLength = 0)
{
if(_openedWindows.Count == 0) {
DebugWorkspaceManager.GetWorkspace();
}
frmAssembler frm = new frmAssembler(code, startAddress, blockLength);
frmAssembler frm = new frmAssembler(cpuType, code, startAddress, blockLength);
frm.Icon = Properties.Resources.Chip;
_openedWindows.Add(frm);
frm.FormClosed += Debugger_FormClosed;

View file

@ -18,6 +18,7 @@ namespace Mesen.GUI.Debugger
{
public partial class frmAssembler : BaseForm
{
private CpuType _cpuType;
private int _startAddress;
private int _blockLength;
private bool _hasParsingErrors = false;
@ -27,9 +28,20 @@ namespace Mesen.GUI.Debugger
private int _textVersion = 0;
private bool _updating = false;
public frmAssembler(string code = "", int startAddress = 0, int blockLength = 0)
public frmAssembler(CpuType? cpuType = null, string code = "", int startAddress = 0, int blockLength = 0)
{
if(cpuType != null) {
_cpuType = cpuType.Value;
} else {
_cpuType = EmuApi.GetRomInfo().CoprocessorType == CoprocessorType.Gameboy ? CpuType.Gameboy : CpuType.Cpu;
}
if(_cpuType == CpuType.Sa1) {
_cpuType = CpuType.Cpu;
}
InitializeComponent();
txtCode.ForeColor = Color.Black;
AssemblerConfig cfg = ConfigManager.Config.Debug.Assembler;
@ -126,7 +138,7 @@ namespace Mesen.GUI.Debugger
return false;
} else {
for(int i = 0; i < ctrlHexBox.ByteProvider.Length; i++) {
if(DebugApi.GetMemoryValue(SnesMemoryType.CpuMemory, (UInt32)(_startAddress + i)) != ctrlHexBox.ByteProvider.ReadByte(i)) {
if(DebugApi.GetMemoryValue(_cpuType.ToMemoryType(), (UInt32)(_startAddress + i)) != ctrlHexBox.ByteProvider.ReadByte(i)) {
return false;
}
}
@ -153,7 +165,7 @@ namespace Mesen.GUI.Debugger
int version = _textVersion;
Task.Run(() => {
short[] byteCode = DebugApi.AssembleCode(text, (UInt32)_startAddress);
short[] byteCode = DebugApi.AssembleCode(_cpuType, text, (UInt32)_startAddress);
this.BeginInvoke((Action)(() => {
_updating = false;
@ -184,6 +196,8 @@ namespace Mesen.GUI.Debugger
case AssemblerSpecialCodes.UnknownLabel: message = "Unknown label"; break;
case AssemblerSpecialCodes.InvalidInstruction: message = "Invalid instruction"; break;
case AssemblerSpecialCodes.InvalidBinaryValue: message = "Invalid binary value"; break;
case AssemblerSpecialCodes.InvalidOperands: message = "Invalid operands for instruction"; break;
case AssemblerSpecialCodes.InvalidLabel: message = "Invalid label name"; break;
}
errorList.Add(new ErrorDetail() { Message = message + " - " + codeLines[line-1], LineNumber = line });
line++;
@ -264,15 +278,22 @@ namespace Mesen.GUI.Debugger
bytes.Add(0xEA);
}
DebugApi.SetMemoryValues(SnesMemoryType.CpuMemory, (UInt32)_startAddress, bytes.ToArray(), bytes.Count);
frmDebugger debugger = null;
if(_cpuType == CpuType.Gameboy) {
DebugApi.SetMemoryValues(SnesMemoryType.GameboyMemory, (UInt32)_startAddress, bytes.ToArray(), bytes.Count);
debugger = DebugWindowManager.OpenDebugger(CpuType.Gameboy);
//TODO: CDL for gameboy
} else {
DebugApi.SetMemoryValues(SnesMemoryType.CpuMemory, (UInt32)_startAddress, bytes.ToArray(), bytes.Count);
AddressInfo absStart = DebugApi.GetAbsoluteAddress(new AddressInfo() { Address = _startAddress, Type = SnesMemoryType.CpuMemory });
AddressInfo absEnd = DebugApi.GetAbsoluteAddress(new AddressInfo() { Address = _startAddress + bytes.Count, Type = SnesMemoryType.CpuMemory });
if(absStart.Type == SnesMemoryType.PrgRom && absEnd.Type == SnesMemoryType.PrgRom && (absEnd.Address - absStart.Address) == bytes.Count) {
DebugApi.MarkBytesAs((uint)absStart.Address, (uint)absEnd.Address, CdlFlags.Code);
AddressInfo absStart = DebugApi.GetAbsoluteAddress(new AddressInfo() { Address = _startAddress, Type = SnesMemoryType.CpuMemory });
AddressInfo absEnd = DebugApi.GetAbsoluteAddress(new AddressInfo() { Address = _startAddress + bytes.Count, Type = SnesMemoryType.CpuMemory });
if(absStart.Type == SnesMemoryType.PrgRom && absEnd.Type == SnesMemoryType.PrgRom && (absEnd.Address - absStart.Address) == bytes.Count) {
DebugApi.MarkBytesAs((uint)absStart.Address, (uint)absEnd.Address, CdlFlags.Code);
}
debugger = DebugWindowManager.OpenDebugger(CpuType.Cpu);
}
frmDebugger debugger = DebugWindowManager.OpenDebugger(CpuType.Cpu);
if(debugger != null) {
debugger.RefreshDisassembly();
}
@ -374,7 +395,9 @@ namespace Mesen.GUI.Debugger
TrailingText = -9,
UnknownLabel = -10,
InvalidInstruction = -11,
InvalidBinaryValue = -12
InvalidBinaryValue = -12,
InvalidOperands = -13,
InvalidLabel = -14,
}
private void lstErrors_DoubleClick(object sender, EventArgs e)

View file

@ -481,8 +481,6 @@ namespace Mesen.GUI.Forms
mnuDebugger.Visible = !isGameboyMode;
mnuSpcDebugger.Enabled = !isGameboyMode;
mnuSpcDebugger.Visible = !isGameboyMode;
mnuAssembler.Enabled = !isGameboyMode;
mnuAssembler.Visible = !isGameboyMode;
sepCoprocessors.Visible = !isGameboyMode;
}

View file

@ -176,12 +176,12 @@ namespace Mesen.GUI
[DllImport(DllPath)] public static extern void SetCdlData([In]byte[] cdlData, Int32 length);
[DllImport(DllPath)] public static extern void MarkBytesAs(UInt32 start, UInt32 end, CdlFlags type);
[DllImport(DllPath, EntryPoint = "AssembleCode")] private static extern UInt32 AssembleCodeWrapper([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))]string code, UInt32 startAddress, [In, Out]Int16[] assembledCodeBuffer);
public static Int16[] AssembleCode(string code, UInt32 startAddress)
[DllImport(DllPath, EntryPoint = "AssembleCode")] private static extern UInt32 AssembleCodeWrapper(CpuType cpuType, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))]string code, UInt32 startAddress, [In, Out]Int16[] assembledCodeBuffer);
public static Int16[] AssembleCode(CpuType cpuType, string code, UInt32 startAddress)
{
code = code.Replace(Environment.NewLine, "\n");
Int16[] assembledCode = new Int16[100000];
UInt32 size = DebugApi.AssembleCodeWrapper(code, startAddress, assembledCode);
UInt32 size = DebugApi.AssembleCodeWrapper(cpuType, code, startAddress, assembledCode);
Array.Resize(ref assembledCode, (int)size);
return assembledCode;
}