Marcus Aseth
Utente Attivo
- Messaggi
- 407
- Reazioni
- 138
- Punteggio
- 60
Salve gente, sto provando a creare un'emulatore per il GBA (codice sotto per chi è interessato) il chè si traduce nell'emulare un processore ARM7TDMI, ed avrei una domanda al riguardo per chi ha familiarità con questa architettura.
Date le diverse pattern che può assumere un'istruzione (immagine sotto) come faccio a distinguere che tipo di pattern sto cercando di decodificare?
Ad esempio, nel codice sotto ho lo switch pronto per gestire tutte le "Data processing and FSR transfer" che come vedete ha un Opcode (a differenza della maggior parte delle altre), ma se ricevo una delle altre codifiche senza Opcode come faccio a rendermi conto?!(i valori da 0000 a 1111 nell'opcode sono tutti usati)
PS: se possibile preferirei risposte certe e non speculative, altrimenti si rischia di generare piu confusione che altro.(speculazioni basate su fatti và bene, ma non tirare ad indovinare in sostanza.) Nel peggiore dei casi, prima o poi troverò la giusta pagina nel manuale da oltre 1000 pagine che risponde a questo quesito :D
EDIT:Cmq così a occhio, i bit 27:26:25 potrebbero essere la risposta corretta? mi son reso conto mentre rileggevo il post, provo un pò a cercare il manuale su quei 3 nel frattempo
.h
.cpp
Date le diverse pattern che può assumere un'istruzione (immagine sotto) come faccio a distinguere che tipo di pattern sto cercando di decodificare?
Ad esempio, nel codice sotto ho lo switch pronto per gestire tutte le "Data processing and FSR transfer" che come vedete ha un Opcode (a differenza della maggior parte delle altre), ma se ricevo una delle altre codifiche senza Opcode come faccio a rendermi conto?!(i valori da 0000 a 1111 nell'opcode sono tutti usati)
PS: se possibile preferirei risposte certe e non speculative, altrimenti si rischia di generare piu confusione che altro.(speculazioni basate su fatti và bene, ma non tirare ad indovinare in sostanza.) Nel peggiore dei casi, prima o poi troverò la giusta pagina nel manuale da oltre 1000 pagine che risponde a questo quesito :D
EDIT:Cmq così a occhio, i bit 27:26:25 potrebbero essere la risposta corretta? mi son reso conto mentre rileggevo il post, provo un pò a cercare il manuale su quei 3 nel frattempo
.h
C++:
#pragma once
template<typename T, typename Parameter>
class NamedType
{
public:
explicit NamedType(T v) :value(v) {}
operator T() const { return value; }
T value;
};
template<typename E>
constexpr decltype(auto) Code(E val)
{
return static_cast<typename std::underlying_type<E>::type>(val);
}
using Byte = uint8_t;
using HalfWord = uint16_t;
using Word = uint32_t;
using DoubleWord = uint64_t;
using Instruction = NamedType < uint32_t, struct InstructionType>;
enum EQUIT_VAL
{
QUIT_EMU,
QUIT_DISASSEMBLER
};
class ClientWindow;
class Disassembler;
class GBAEmulator
{
public:
GBAEmulator(HINSTANCE hInstance);
~GBAEmulator();
HINSTANCE GetHInstance()const;
bool GetIsRunning()const;
void SetIsRunning(bool state);
const std::array<Word, 16>& GetRegisters()const;
Word GetCPSR()const;
std::unique_ptr<Disassembler>& GetDisassembler();
int Run();
static LRESULT WINAPI ClientWinProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
static LRESULT WINAPI DisassemblerWinProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
private:
bool LoadRom(const std::string& pathName = "");
void ClearMemory();
void ReleaseMemory();
bool UnhandledInstructionError(Instruction instruction)const;
void DebugDumpMemoryToTxt(Byte* begin, UINT size, UINT entryPerLine);
std::string InstructionToString(Word instruction)const;
inline Instruction FetchInstruction()const;
inline Byte ToCond(Instruction instruction)const;
inline Byte ToOpcode(Instruction instruction)const;
bool IsAllowedToExecute(Instruction instruction)const;
bool DecodeInstruction(Instruction instruction);
bool m_isRunning;
HINSTANCE m_hInstance;
std::unique_ptr<ClientWindow> m_window;
std::unique_ptr<Disassembler> m_disassembler;
const UINT memorySize = 34'000'000;
Byte* memory;
std::array<Word, 16> REG; //General Purpose Registers
std::array<Word, 5> SPSR; //Saved Program Status Register
Word CPSR; //Current Program Status Register
//31 23 20 19 16 15 10 7 4 0
//╔═╦═╦═╦═╦═╦═══╦═╦════════╦════════╦══════════╦═╦═╦═╦═╦═╦══════╗
//|N|Z|C|V|Q|Res|J|RESERVED|GE[3:0] | RESERVED |E|A|I|F|T|M[4:0]| << CPSR
//╚═╩═╩═╩═╩═╩═══╩═╩════════╩════════╩══════════╩═╩═╩═╩═╩═╩══════╝
};
.cpp
C++:
#include "GBAEmulator_PCH.h"
#include "GBAEmulator.h"
#include "ClientWindow.h"
#include "Disassembler.h"
#include "Console.h"
#include "Menu.h"
#include "GuiFunctions.h"
using namespace std;
enum class ECond
{
EQ, // Equal | Z set
NE, // Not equal--------| Z clear
CS_HS, //Carry set/unsigned higher or same | C set
CC_LO, //Carry clear/unsigned lower--------| C clear
MI, // Minus/negative | N set
PL, // Plus/positive or zero--------| N clear
VS, // Overflow | V set
VC, // No overflow--------| V clear
HI, // Unsigned higher | C set and Z clear
LS, // Unsigned lower or same--------| C clear or Z set
GE, //Signed greater than or equal | N set and V set, or N clear and V clear (N == V)
LT, // Signed less than--------| N set and V clear, or N clear and V set (N != V)
GT, // Signed greater than | Z clear, and either N set and V set, or N clear and V clear (Z == 0,N == V)
LE, // Signed less than or equal--------| Z set, or N set and V clear, or N clear and V set (Z == 1 or N != V)
AL // Always (unconditional) |
};
enum class EOpcode
{
AND, // Logical AND Rd := Rn AND shifter_operand
EOR, // Logical Exclusive OR--------Rd := Rn EOR shifter_operand
SUB, // Subtract Rd := Rn - shifter_operand
RSB, // Reverse Subtract------------Rd := shifter_operand - Rn
ADD, // Add Rd := Rn + shifter_operand
ADC, // Add with Carry--------------Rd := Rn + shifter_operand + Carry Flag
SBC, // Subtract with Carry Rd := Rn - shifter_operand - NOT(Carry Flag)
RSC, // Reverse Subtract with Carry-Rd := shifter_operand - Rn - NOT(Carry Flag)
TST, // Test Update flags after Rn AND shifter_operand
TEQ, // Test Equivalence------------Update flags after Rn EOR shifter_operand
CMP, // Compare Update flags after Rn - shifter_operand
CMN, // Compare Negated-------------Update flags after Rn + shifter_operand
ORR, // Logical (inclusive) OR Rd := Rn OR shifter_operand
MOV, // Move------------------------Rd := shifter_operand (no first operand)
BIC, // Bit Clear Rd := Rn AND NOT(shifter_operand)
MVN // Move Not--------------------Rd := NOT shifter_operand (no first operand)
};
GBAEmulator::GBAEmulator(HINSTANCE hInstance) :
REG{}, SPSR{}, CPSR{},
memory(new Byte[memorySize]()),
m_isRunning{ true },
m_hInstance{ hInstance }
{
#ifdef _DEBUG
CreateConsole();
SetConsoleTitleA("Debug Console");
#endif
//GBA resolution: 240x160
LONG clientWidth = 480, clientHeight = 320;
m_window = std::make_unique<ClientWindow>(clientWidth, clientHeight, hInstance, this);
}
GBAEmulator::~GBAEmulator()
{
ReleaseMemory();
}
HINSTANCE GBAEmulator::GetHInstance()const
{
return m_hInstance;
}
bool GBAEmulator::GetIsRunning()const
{
return m_isRunning;
}
void GBAEmulator::SetIsRunning(bool state)
{
m_isRunning = state;
}
std::unique_ptr<Disassembler>& GBAEmulator::GetDisassembler()
{
return m_disassembler;
}
const std::array<Word, 16>& GBAEmulator::GetRegisters()const
{
return REG;
}
Word GBAEmulator::GetCPSR()const
{
return CPSR;
}
int GBAEmulator::Run()
{
if (!m_window->IsValid())
{
return -1;
}
//DEBUG setting
LoadRom("TestInstructions.gba");
Gui::OpenDisassembler(this);
MSG msg{};
while (m_isRunning)
{
//Window Messages
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
if ((msg.message == WM_QUIT) && (msg.wParam == EQUIT_VAL::QUIT_DISASSEMBLER))
{
m_disassembler.release();
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//Update
if (m_disassembler) m_disassembler->Updade(0.0f);
//Render
if (m_window) m_window->Draw();
if (m_disassembler) m_disassembler->Draw();
//Fetch - Decode - Execute
m_isRunning = DecodeInstruction(FetchInstruction());
}
return 0;
}
bool GBAEmulator::LoadRom(const string& pathName)
{
string path(pathName);
bool isValidPath = true;
if (pathName.empty())
{
char filename[MAX_PATH]{};
OPENFILENAME openFile{};
openFile.lStructSize = sizeof(OPENFILENAME);
openFile.hwndOwner = m_window->Hwnd();
openFile.lpstrFilter = "GBA .gba\0*.gba\0\0";
openFile.lpstrFile = filename;
openFile.nMaxFile = MAX_PATH;
openFile.lpstrTitle = "Open rom";
openFile.Flags = OFN_DONTADDTORECENT | OFN_FILEMUSTEXIST;
isValidPath = GetOpenFileNameA(&openFile);
path = string(filename);
}
if (isValidPath)
{
ifstream file(path, ifstream::in | ifstream::binary);
if (file.fail())
{
return Error("Input File Stream failed" + path);
}
ClearMemory();
file.read(reinterpret_cast<char*>(memory), memorySize);
file.close();
}
else
{
return Error("Failed to open " + path);
}
return true;
}
void GBAEmulator::ClearMemory()
{
if (memory) SecureZeroMemory(memory, memorySize);
}
void GBAEmulator::ReleaseMemory()
{
if (memory) delete[] memory;
}
LRESULT WINAPI GBAEmulator::ClientWinProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
static GBAEmulator* emu = nullptr;
switch (Msg)
{
case WM_CREATE:
{
emu = static_cast<GBAEmulator*>(reinterpret_cast<CREATESTRUCT*>(lParam)->lpCreateParams);
break;
}
case WM_DESTROY:
{
emu->m_isRunning = false;
if (emu->m_disassembler.get())
PostQuitMessage(EQUIT_VAL::QUIT_DISASSEMBLER);
PostQuitMessage(EQUIT_VAL::QUIT_EMU);
break;
}
case WM_SYSCOMMAND:
{
if (wParam != SC_MAXIMIZE)
{
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
break;
}
case WM_COMMAND:
{
int menuID = LOWORD(wParam);
switch (menuID)
{
case static_cast<int>(EMENU_ELEMENT::OPEN)
:
{
emu->LoadRom();
break;
}
case static_cast<int>(EMENU_ELEMENT::EXIT)
:
{
Gui::Exit(emu);
break;
}
case static_cast<int>(EMENU_ELEMENT::DISASSEMBLER)
:
{
Gui::OpenDisassembler(emu);
break;
}
}
break;
}
default:
{
return DefWindowProc(hWnd, Msg, wParam, lParam);
break;
}
}
return 0;
}
LRESULT WINAPI GBAEmulator::DisassemblerWinProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
static GBAEmulator* emu = nullptr;
switch (Msg)
{
case WM_DESTROY:
{
Gui::CloseDisassembler();
break;
}
case WM_SYSCOMMAND:
{
if (wParam != SC_MAXIMIZE)
{
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
break;
}
default:
{
return DefWindowProc(hWnd, Msg, wParam, lParam);
break;
}
}
return 0;
}
void GBAEmulator::DebugDumpMemoryToTxt(Byte* begin, UINT size, UINT entryPerLine = 0)
{
//TODO: REDO THIS
ofstream output("MemoryDump.txt");
output << hex;
for (size_t i = 0; i < size; i += 4)
{
Byte* mem = begin + i;
for (int offset = 3; offset >= 0; offset--)
{
long high = static_cast<long>(*(mem + offset) >> 4);
long low = static_cast<long>(*(mem + offset) & 0x0F);
if (high != 0) output << high;
else output << "0";
if (low != 0) output << low;
else output << "0";
std::cout << " ";
}
entryPerLine++;
if (entryPerLine >= 1)
{
output << endl;
entryPerLine = 0;
}
}
}
Instruction GBAEmulator::FetchInstruction()const
{
//Program counter: Register 15 is the (PC)
return Instruction((memory[REG[15]] << 24) | (memory[REG[15] + 1] << 16) | (memory[REG[15] + 2] << 8) | (memory[REG[15] + 3]));
}
Byte GBAEmulator::ToOpcode(Instruction instruction)const
{
//Bit [24:23:22:21]
return Byte((instruction & 0x01E00000) >> 21);
}
Byte GBAEmulator::ToCond(Instruction instruction)const
{
//Bit [31:30:29:28]
return Byte((instruction & 0xF0000000) >> 28);
}
bool GBAEmulator::IsAllowedToExecute(Instruction instruction)const
{
static constexpr Byte N{ 8 };
static constexpr Byte Z{ 4 };
static constexpr Byte C{ 2 };
static constexpr Byte V{ 1 };
Byte state = (CPSR & 0xF0000000) >> 28;
switch (ToCond(instruction))
{
// Z set
case Code(ECond::EQ):
return (state & Z);
// Z clear
case Code(ECond::NE):
return (state ^ Z);
// C set
case Code(ECond::CS_HS):
return (state & C);
// C clear
case Code(ECond::CC_LO):
return (state ^ C);
// N set
case Code(ECond::MI):
return (state & N);
// N clear
case Code(ECond::PL):
return (state ^ N);
// V set
case Code(ECond::VS):
return(state & V);
// V clear
case Code(ECond::VC):
return (state ^ V);
// C set and Z clear
case Code(ECond::HI):
return ((state & C) && (state ^ Z));
// C clear or Z set
case Code(ECond::LS):
return ((state ^ C) || (state & Z));
// N set and V set, or N clear and V clear (N == V)
case Code(ECond::GE):
return ((state & N) == (state & V));
// N set and V clear, or N clear and V set (N != V)
case Code(ECond::LT):
return ((state & N) != (state & V));
//Z clear, and either N set and V set, or N clear and V clear(Z == 0, N == V)
case Code(ECond::GT):
return ((state ^ Z) && ((state & N) == (state & V)));
//Z set, or N set and V clear, or N clear and V set (Z == 1 or N != V)
case Code(ECond::LE):
return ((state & Z) || ((state & N) != (state & V)));
//(unconditional)
case Code(ECond::AL):
return true;
}
return true;
}
bool GBAEmulator::DecodeInstruction(Instruction instruction)
{
Byte opcode = ToOpcode(instruction);
switch (instruction)
{
// Data processing and FSR transfer
switch (opcode)
{
/*
31 28 24 21 19 16 15 12 11 0
╔════════╦══╦═╦════════╦═╦════════╦════════╦════════════╗
| cond |00|I| opcode |S| Rn | Rd | shifter_op |
╚════════╩══╩═╩════════╩═╩════════╩════════╩════════════╝
I bit [25] Distinguishes between the immediate and register forms of <shifter_operand>.
S bit [20] Signifies that the instruction updates the condition codes.
Rn Specifies the first source operand register.
Rd Specifies the destination register.
shifter_operand Specifies the second source operand. See Addressing Mode 1 - Data-processing
operands on page A5-2 for details of the shifter operands.
*/
case Code(EOpcode::AND):
{
return UnhandledInstructionError(instruction);
break;
}
case Code(EOpcode::EOR):
{
return UnhandledInstructionError(instruction);
break;
}
case Code(EOpcode::SUB):
{
return UnhandledInstructionError(instruction);
break;
}
case Code(EOpcode::RSB):
{
return UnhandledInstructionError(instruction);
break;
}
//ADD{<cond>}{S} <Rd>, <Rn>, <shifter_operand>
case Code(EOpcode::ADD):
{
if (IsAllowedToExecute(instruction))
{
}
break;
}
case Code(EOpcode::ADC):
{
return UnhandledInstructionError(instruction);
break;
}
case Code(EOpcode::SBC):
{
return UnhandledInstructionError(instruction);
break;
}
case Code(EOpcode::RSC):
{
return UnhandledInstructionError(instruction);
break;
}
case Code(EOpcode::TST):
{
return UnhandledInstructionError(instruction);
break;
}
case Code(EOpcode::TEQ):
{
return UnhandledInstructionError(instruction);
break;
}
case Code(EOpcode::CMP):
{
return UnhandledInstructionError(instruction);
break;
}
case Code(EOpcode::CMN):
{
return UnhandledInstructionError(instruction);
break;
}
case Code(EOpcode::ORR):
{
return UnhandledInstructionError(instruction);
break;
}
case Code(EOpcode::MOV):
{
return UnhandledInstructionError(instruction);
break;
}
case Code(EOpcode::BIC):
{
return UnhandledInstructionError(instruction);
break;
}
case Code(EOpcode::MVN):
{
return UnhandledInstructionError(instruction);
break;
}
default:
return UnhandledInstructionError(instruction);
}//END Data processing and FSR transfer
break;
default:
return UnhandledInstructionError(instruction);
}
return true;
}
string GBAEmulator::InstructionToString(Word instruction)const
{
stringstream stream;
stream << hex;
for (int stride = 3; stride >= 0; stride--)
{
long high = ((instruction >> (stride * 8)) & 0xF0) >> 4;
long low = ((instruction >> (stride * 8)) & 0x0F);
if (high != 0) stream << high;
else stream << "0";
if (low != 0) stream << low;
else stream << "0";
stream << " ";
}
return stream.str();
}
bool GBAEmulator::UnhandledInstructionError(Instruction instruction)const
{
string message("Instruction not yet implemented. INSTRUCTION: " + InstructionToString(instruction));
ConOut(message);
MessageBoxA(m_window->Hwnd(), message.c_str(), "Error", MB_OK);
return false;
}
Ultima modifica: