Skip to content

Instantly share code, notes, and snippets.

@Subv
Created June 17, 2014 00:49
Show Gist options
  • Save Subv/aa164b886c7e32cf977d to your computer and use it in GitHub Desktop.
Save Subv/aa164b886c7e32cf977d to your computer and use it in GitHub Desktop.
ARM Interpreter
#include <iostream>
#include <stdio.h>
#include <nds.h>
#include "ARM.h"
#include "Thumb.h"
#include "GBA.h"
#include "Opcodes.h"
#include "Utils.h"
CPSR _CPSR;
Registers _Registers;
FILE* ROMFile;
uint8_t GBA_BIOS[16384]; // BIOS 00000000-00003FFF
// 00004000-01FFFFFF not used
uint8_t GBA_WRAM1[262144]; // On-Board Work RAM 02000000-0203FFFF
// 02040000-02FFFFFF not used
uint8_t GBA_WRAM2[32768]; // On-Chip Work RAM 03000000-03007FFF
// 03008000 - 03FFFFFF not used
uint8_t GBA_IO[0x3FF]; // IO Registers 04000000-040003FE
// 04000400-04FFFFFF not used
uint8_t GBA_PRAM[1024]; // Palette RAM 05000000-050003FF
// 05000400-05FFFFFF not used
uint8_t GBA_VRAM[98304]; // Video RAM 06000000-06017FFF
// 06018000-06FFFFFF not used
uint8_t GBA_OAM[1024]; // OAM 07000000-070003FF
// 07000400-07FFFFFF not used
void InitializeCPU()
{
_CPSR.Full = 0;
for (uint8_t i = 0; i < 16; ++i)
_Registers.R[i] = 0;
}
void RunARMInstruction(Instruction instruction);
void RunThumbInstruction(ThumbInstruction instruction);
void LoadROM(FILE* file, GBAHeader& header)
{
std::cout << "Memory: " << mallinfo().arena << " " << mallinfo().uordblks << " " << mallinfo().fordblks << std::endl;
std::cout << "Loading rom " << header.title << std::endl;
std::cout << "Executing " << std::hex << header.entryPoint << std::endl;
ROMFile = file;
RunARMInstruction(Instruction(header.entryPoint));
// The PC is now updated and we can continue execution
while (true)
{
if (_CPSR.bits.T == 0)
{
// We're in 32 bits mode
// Read 4 bytes
uint32_t data = ReadBytesFromROM<uint32_t>(PC);
Instruction instr(data);
RunARMInstruction(instr);
}
else
{
// We're in Thumb mode
// Read 2 bytes
uint16_t data = ReadBytesFromROM<uint16_t>(PC);
ThumbInstruction instr(data);
RunThumbInstruction(instr);
}
std::cout << "Going to next instruction, PC = " << PC << std::endl;
Utils::WaitForKey(KEY_A);
consoleClear();
swiWaitForVBlank();
}
}
bool CheckCondition(uint8_t condition)
{
switch (condition)
{
case 0x0: // Equals, Z = 1
return _CPSR.bits.Z == 1;
case 0x1: // Not Equals, Z = 0
return _CPSR.bits.Z == 0;
case 0x2: // Carry Set, C = 1
return _CPSR.bits.C == 1;
case 0x3: // Carry Not Set, C = 0
return _CPSR.bits.C == 0;
case 0x4: // Negative, N = 1
return _CPSR.bits.N == 1;
case 0x5: // Positive or Zero, N = 0
return _CPSR.bits.N == 0;
case 0x6: // Overflow, V = 1
return _CPSR.bits.V == 1;
case 0x7: // No Overflow, V = 0
return _CPSR.bits.V == 0;
case 0x8: // Unsigned Higher, C = 1 and Z = 0
return _CPSR.bits.C == 1 && _CPSR.bits.Z == 0;
case 0x9: // Unsigned Lower or Same, C = 0 or Z = 1
return _CPSR.bits.C == 0 || _CPSR.bits.Z == 1;
case 0xA: // Greater or Equal, N = V
return _CPSR.bits.N == _CPSR.bits.V;
case 0xB: // Less Than, N != V
return _CPSR.bits.N != _CPSR.bits.V;
case 0xC: // Greater Than, Z = 0 and N = V
return _CPSR.bits.Z == 0 && _CPSR.bits.N == _CPSR.bits.V;
case 0xD: // Less Or Equal, Z = 1 or N != V
return _CPSR.bits.Z == 1 || _CPSR.bits.N == _CPSR.bits.V;
case 0xE: // Always
return true;
default:
{
Utils::Assert(false, "Not supported condition");
return false;
}
}
}
inline void HandleImmediateDataProcessing(Instruction instruction)
{
DataProcessingInmediateInstruction instr(instruction.Full);
std::cout << "Handling a dataProc instruction: Full: " << std::hex << instr.Full << " Opcode: " << (unsigned)instr.Members.Opcode << " Immediate " << std::endl;
// Immediate operation, this means that we have a number and not a register as the second operand
std::cout << std::hex << " Is: " << (unsigned)instr.Members.Is << " and nn: " << (unsigned)instr.Members.nn << std::endl;
std::cout << std::hex << " Affected Register: " << (unsigned)instr.Members.Rd << std::endl;
// Handle the opcode
switch (instr.Members.Opcode)
{
case ARM_MOV:
_Registers.R[instr.Members.Rd] = Utils::RotateRight(instr.Members.nn, instr.Members.Is * 2);
break;
case ARM_ADD:
_Registers.R[instr.Members.Rd] = _Registers.R[instr.Members.Rn] + Utils::RotateRight(instr.Members.nn, instr.Members.Is * 2);
if (instr.Members.Rn == 15) // If we're using PC, add another 8 bytes
_Registers.R[instr.Members.Rd] += 8;
break;
}
std::cout << "Register 0:" << (unsigned)_Registers.R[0] << std::endl;
}
inline void HandleRegisterDataProcessing(Instruction instruction)
{
}
inline void HandleImmediatePSRTransfer(Instruction instruction)
{
PSRTransferInstruction instr(instruction.Full);
std::cout << "PSR Transfer Type: " << std::hex << (unsigned)instr.Members.Opcode << " PSR: " << (unsigned)instr.Members.Psr << " Rd: " << (unsigned)instr.Members.Rd;
}
inline void HandleRegisterPSRTransfer(Instruction instruction)
{
PSRTransferInstruction instr(instruction.Full);
std::cout << "PSR Transfer Type: " << std::hex << (unsigned)instr.Members.Opcode << " PSR: " << (unsigned)instr.Members.Psr << " Rd: " << (unsigned)instr.Members.Rd;
switch (instr.Members.Opcode)
{
case ARM_MSR: // Write to PSR
{
std::cout << "Write C: " << (unsigned)instr.Members.C << " Write X " << (unsigned)instr.Members.X << " Write S " << (unsigned)instr.Members.S << " Write F " << (unsigned)instr.Members.F << std::endl;
std::cout << "Source register: " << (unsigned)(instr.Members.LastBits.Rm >> 4) << std::endl;
uint32_t byteMask = 0;
if (instr.Members.C)
byteMask |= 0x000000FF;
if (instr.Members.X)
byteMask |= 0x0000FF00;
if (instr.Members.S)
byteMask |= 0x00FF0000;
if (instr.Members.F)
byteMask |= 0x00FF0000;
if (instr.Members.Psr == 0) // We only support CPSR for now
_CPSR.Full = (_CPSR.Full & ~byteMask) | (_Registers.R[instr.Members.LastBits.Rm >> 4] & byteMask);
break;
}
case ARM_MRS: // Load PSR
break;
default:
Utils::Assert(false, "Not supported PSR operation");
break;
}
}
void HandleImmediateSingleDataTransfer(Instruction instruction)
{
SingleDataTransferInstruction instr(instruction.Full);
std::cout << "Immediate SDT Load/Store: " << (unsigned)instr.Members.L << std::endl;
switch (instr.Members.L)
{
case ARM_STR: // Store
{
uint32_t address = 0;
uint32_t offset = instr.Members.GetImmediateOffset();
if (instr.Members.U == 0)
offset = -offset;
if (instr.Members.P)
address = _Registers.R[instr.Members.Rn] + offset;
else
{
address = _Registers.R[instr.Members.Rn];
_Registers.R[instr.Members.Rn] += offset;
}
if (address >= IO_REGISTERS_START && address <= IO_REGISTERS_END)
GBA_IO[address - IO_REGISTERS_START] = _Registers.R[instr.Members.Rd];
std::cout << std::hex << "Register: " << (unsigned)instr.Members.Rn << " Register Val: " << _Registers.R[instr.Members.Rn] << " Writing to address " << address << std::endl;
break;
}
case ARM_LDR: // Load
uint32_t address = 0;
uint32_t offset = instr.Members.GetImmediateOffset();
if (instr.Members.U == 0)
offset = -offset;
if (instr.Members.P)
address = _Registers.R[instr.Members.Rn] + offset;
else
{
address = _Registers.R[instr.Members.Rn];
_Registers.R[instr.Members.Rn] += offset;
}
if (instr.Members.Rn == 15) // If we're using PC as the source register
{
if (_CPSR.bits.T)
address += 4; // Add 4 bytes in Thumb mode
else
address += 8; // Add 8 bytes in ARM mode
// Since we're using PC-relative addressing, we have to grab the data from the ROM
_Registers.R[instr.Members.Rd] = ReadBytesFromROM<uint32_t>(address);
}
if (address >= GBA_WRAM2_START && address <= GBA_WRAM2_END)
_Registers.R[instr.Members.Rd] = GBA_WRAM2[address - GBA_WRAM2_START];
std::cout << "Rn: " << (unsigned)instr.Members.Rn << std::endl;
std::cout << "Value of Rn: " << (unsigned)_Registers.R[instr.Members.Rn] << std::endl;
std::cout << "Offset: " << (unsigned)offset << std::endl;
std::cout << "Output to register: " << std::hex << _Registers.R[instr.Members.Rd] << std::endl;
break;
}
}
void HandleRegisterSingleDataTransfer(Instruction instruction)
{
SingleDataTransferInstruction instr(instruction.Full);
std::cout << "Register SDT Load/Store: " << (unsigned)instr.Members.L << std::endl;
switch (instr.Members.L)
{
case ARM_STR: // Store
break;
case ARM_LDR: // Load
break;
}
}
void HandleBranchLinkInstruction(Instruction instruction)
{
BranchLinkInstruction instr(instruction.Full);
PC += 8 + instr.Members.nn * 4;
if (instr.Members.Opcode == 0x1)
LR = PC + 4; // Update LR register
std::cout << "We have a branch! Jumping to " << std::hex << PC << std::endl;
}
void HandleBranchExchangeInstruction(Instruction instruction)
{
std::cout << "WE HAVE A BRANCH EXCHANGE" << std::endl;
BranchExchangeInstruction instr(instruction.Full);
switch (instr.Members.Opcode)
{
case ARM_BX:
_CPSR.bits.T = (_Registers.R[instr.Members.Rn] & 0x1) != 0;
PC = _Registers.R[instr.Members.Rn];
if (_CPSR.bits.T)
{
std::cout << "Switching to Thumb." << std::endl;
PC -= 1;
}
break;
case ARM_BLX:
_CPSR.bits.T = (_Registers.R[instr.Members.Rn] & 0x1) != 0;
LR = PC + 4;
PC = _Registers.R[instr.Members.Rn];
if (_CPSR.bits.T)
PC -= 1;
break;
}
}
void RunARMInstruction(Instruction instruction)
{
uint8_t condition = instruction.Members.Condition; // The condition is the first 4 bits of the instruction
std::cout << "Condition: " << std::hex << (unsigned)instruction.Members.Condition << std::endl;
if (!CheckCondition(condition))
{
std::cout << "Didn't pass the condition" << std::endl;
return;
}
// Now identify the instruction we're dealing with, different types of instructions have different structures.
if (instruction.CheckPass(3, 25, 0x5)) // This is a branch instruction, 1 0 1
HandleBranchLinkInstruction(instruction);
else if (instruction.CheckPass(20, 8, 0x12FFF)) // This is a branch exchange instruction
HandleBranchExchangeInstruction(instruction);
else if (instruction.CheckPass(2, 26, 0) && instruction.CheckPass(2, 23, 0x2) && instruction.CheckPass(1, 20, 0))
{
std::cout << "Found PSR Instruction" << std::endl;
// It is a PSR instruction
if (instruction.Members.I == 1)
HandleImmediatePSRTransfer(instruction);
else
HandleRegisterPSRTransfer(instruction);
PC += 4;
}
else if (instruction.CheckPass(2, 26, 0)) // Data Processing Instruction
{
if (instruction.Members.I == 1)
HandleImmediateDataProcessing(instruction);
else
HandleRegisterDataProcessing(instruction);
PC += 4;
}
else if (instruction.CheckPass(2, 26, 0x1))
{
// We have a single data transfer instruction (LDR, STR, PLD)
if (instruction.Members.I == 0)
HandleImmediateSingleDataTransfer(instruction);
else
HandleRegisterSingleDataTransfer(instruction);
PC += 4;
}
std::cout << "Instruction ran" << std::endl;
}
void HandleThumbMoveShiftedRegister(ThumbInstruction instruction)
{
ThumbMoveShiftedRegisterInstruction instr(instruction.Full);
switch (instr.Members.Opcode)
{
case THUMB_MSR_LSL:
_Registers.R[instr.Members.Rd] = _Registers.R[instr.Members.Rs] << instr.Members.Offset;
break;
case THUMB_MSR_LSR:
_Registers.R[instr.Members.Rd] = _Registers.R[instr.Members.Rs] >> instr.Members.Offset;
break;
case THUMB_MSR_ASR:
_Registers.R[instr.Members.Rd] = _Registers.R[instr.Members.Rs] >> instr.Members.Offset; // TODO: Extend the last bit
break;
default:
Utils::Assert(false, "Thumb: Not implemented Move Shift Register opcode");
break;
}
}
void HandleThumbLoadPCRelative(ThumbInstruction instruction)
{
ThumbLoadPCRelativeInstruction instr(instruction.Full);
_Registers.R[instr.Members.Rd] = ReadBytesFromROM<uint32_t>((PC & 0xFFFFFFFC) + instr.Members.nn * 4 + 4);
std::cout << "THUMB: Register " << std::hex << (unsigned)instr.Members.Rd << " now contains: " << (unsigned)_Registers.R[instr.Members.Rd] << std::endl;
}
void HandleThumbALUInstruction(ThumbInstruction instruction)
{
ThumbALUInstruction instr(instruction.Full);
std::cout << "Thumb ALU Operation, Opcode: " << std::hex << (unsigned)instr.Members.Opcode << std::endl;
switch (instr.Members.Opcode)
{
case THUMB_AND:
_Registers.R[instr.Members.Rd] &= _Registers.R[instr.Members.Rs];
break;
case THUMB_EOR:
_Registers.R[instr.Members.Rd] ^= _Registers.R[instr.Members.Rs];
break;
case THUMB_LSL:
_Registers.R[instr.Members.Rd] <<= _Registers.R[instr.Members.Rs] & 0xFF;
break;
case THUMB_LSR:
_Registers.R[instr.Members.Rd] >>= _Registers.R[instr.Members.Rs] & 0xFF;
break;
default:
Utils::Assert(false, "Thumb: Not implemented ALU opcode");
break;
}
}
bool HandleThumbConditionalBranchInstruction(ThumbInstruction instruction)
{
ThumbConditionalBranchInstruction instr(instruction.Full);
if (!CheckCondition(instr.Members.Opcode))
return false;
std::cout << "Thumb: Taking Branch: " << (unsigned)instr.Members.Offset << std::endl;
PC += (instr.Members.Offset << 1) + 4;
// Why isn't this working? IDA says it should jump from 0000010C to 00000126, the instruction is 0B D2 (0xD20B),
// but it's jumping to 00000116, where does the other 0x10 come from?
return true;
}
bool HandleHiRegisterOperationInstruction(ThumbInstruction instruction)
{
ThumbHiRegisterOperationInstruction instr(instruction.Full);
switch (instr.Members.Opcode)
{
case THUMB_ADD:
_Registers.R[instr.Members.GetRd()] += _Registers.R[instr.Members.GetRs()];
break;
case THUMB_MOV:
_Registers.R[instr.Members.GetRd()] = _Registers.R[instr.Members.GetRs()];
break;
case THUMB_CMP:
break;
case THUMB_BX:
_CPSR.bits.T = Utils::CheckBit(_Registers.R[instr.Members.GetRs()], 0);
if (instr.Members.MSBd == 0)
{
// BX
PC = _Registers.R[instr.Members.GetRs()];
if (!_CPSR.bits.T) // If we're out of Thumb mode, increase the address by 2, the code will add another 2 when this function exits.
PC += 2;
}
else
{
// BLX
LR = (PC + 2) | 1;
PC = _Registers.R[instr.Members.GetRs()] & 0xFFFFFFFE;
}
return true;
break;
default:
Utils::Assert(false, "Thumb: Not supported Hi Register Operation opcode");
break;
}
return false;
}
void RunThumbInstruction(ThumbInstruction instruction)
{
if (instruction.CheckPass(3, 13, 0))
HandleThumbMoveShiftedRegister(instruction);
else if (instruction.CheckPass(5, 11, 0x9))
HandleThumbLoadPCRelative(instruction);
else if (instruction.CheckPass(6, 10, 0x10))
HandleThumbALUInstruction(instruction);
else if (instruction.CheckPass(4, 12, 0xD))
{
if (HandleThumbConditionalBranchInstruction(instruction))
return; // Prevent incrementing the PC if the branch was taken
}
else if (instruction.CheckPass(6, 10, 0x11))
{
if (HandleHiRegisterOperationInstruction(instruction))
return;
}
PC += 2;
}
#ifndef OPCODES_H
#define OPCODES_H
/* ARM OPCODES */
// Branch Exchange opcodes
#define ARM_BX 0x1
#define ARM_BLX 0x3
// Data Processing opcodes
#define ARM_MOV 0xD
#define ARM_ADD 0x4
// PSR Transfer opcodes
#define ARM_MRS 0x0
#define ARM_MSR 0x1
// Single Data Transfer opcodes
#define ARM_STR 0x0
#define ARM_LDR 0x1
/* THUMB OPCODES */
// Move Shifted Register opcodes
#define THUMB_MSR_LSL 0x0
#define THUMB_MSR_LSR 0x1
#define THUMB_MSR_ASR 0x2
// ALU Operation opcodes
#define THUMB_AND 0x0
#define THUMB_EOR 0x1
#define THUMB_LSL 0x2
#define THUMB_LSR 0x3
// Conditional Branch opcodes
#define THUMB_BEQ 0x0
#define THUMB_BNE 0x1
#define THUMB_BCS 0x2
#define THUMB_BCC 0x3
#define THUMB_BMI 0x4
#define THUMB_BPL 0x5
#define THUMB_BVS 0x6
#define THUMB_BVC 0x7
#define THUMB_BHI 0x8
#define THUMB_BLS 0x9
#define THUMB_BGE 0xA
#define THUMB_BLT 0xB
#define THUMB_BGT 0xC
#define THUMB_BLE 0xD
// Hi Register Operation opcodes
#define THUMB_ADD 0x0
#define THUMB_CMP 0x1
#define THUMB_MOV 0x2
#define THUMB_BX 0x3 // Can be BLX
#endif
#ifndef THUMB_H
#define THUMB_H
#include <stdint.h>
union ThumbHiRegisterOperationInstruction
{
struct
{
uint8_t Rd : 3; // Destination Register
uint8_t Rs : 3; // Source Register
uint8_t MSBs : 1; // Source Register most significant bit
uint8_t MSBd : 1; // Destination Register most significant bit (or BL/BLX flag)
uint8_t Opcode : 2;
uint8_t Unused : 6; // = 010001b
uint8_t GetRs()
{
return (MSBs << 3) | Rs;
}
uint8_t GetRd()
{
return (MSBd << 3) | Rd;
}
} __attribute__((packed)) Members;
uint16_t Full;
ThumbHiRegisterOperationInstruction(uint16_t val) { Full = val; }
};
union ThumbConditionalBranchInstruction
{
struct
{
int8_t Offset : 3; // Signed offset
uint8_t Opcode : 4;
uint8_t Unused : 4; // = 1101b
} __attribute__((packed)) Members;
uint16_t Full;
ThumbConditionalBranchInstruction(uint16_t val) { Full = val; }
};
union ThumbALUInstruction
{
struct
{
uint8_t Rd : 3; // Destination register
uint8_t Rs : 3; // Source register
uint8_t Opcode : 4;
uint8_t Unused : 6; // = 010000b
} __attribute__((packed)) Members;
uint16_t Full;
ThumbALUInstruction(uint16_t val) { Full = val; }
};
union ThumbLoadPCRelativeInstruction
{
struct
{
uint8_t nn;
uint8_t Rd : 3; // Destination register
uint8_t Unused : 5;
} __attribute__((packed)) Members;
uint16_t Full;
ThumbLoadPCRelativeInstruction(uint16_t val) { Full = val; }
};
union ThumbMoveShiftedRegisterInstruction
{
struct
{
uint8_t Unused : 3; // = 000
uint8_t Opcode : 2;
uint8_t Offset : 5;
uint8_t Rs : 3; // Source register
uint8_t Rd : 3; // Destination register
} __attribute__((packed)) Members;
uint16_t Full;
ThumbMoveShiftedRegisterInstruction(uint16_t val) { Full = val; }
};
union ThumbInstruction
{
uint16_t Full;
bool CheckPass(uint8_t bits, uint8_t start, uint16_t check)
{
uint16_t extract = 0;
extract = (Full >> start) & ((1 << bits) - 1);
return extract == check;
}
ThumbInstruction(uint16_t val) { Full = val; }
};
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment