1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-13 15:31:05 +00:00
CLK/Analyser/Static/Disassembler/Z80.cpp
2024-11-29 21:08:35 -05:00

675 lines
22 KiB
C++

//
// Z80.cpp
// Clock Signal
//
// Created by Thomas Harte on 30/12/2017.
// Copyright 2017 Thomas Harte. All rights reserved.
//
#include "Z80.hpp"
#include "Kernel.hpp"
using namespace Analyser::Static::Z80;
namespace {
using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
class Accessor {
public:
Accessor(
const std::vector<uint8_t> &memory,
const std::function<std::size_t(uint16_t)> &address_mapper,
uint16_t address
) :
memory_(memory), address_mapper_(address_mapper), address_(address) {}
uint8_t byte() {
std::size_t mapped_address = address_mapper_(address_);
++address_;
if(mapped_address >= memory_.size()) {
overrun_ = true;
return 0xff;
}
return memory_[mapped_address];
}
uint16_t word() {
uint8_t low = byte();
uint8_t high = byte();
return uint16_t(low | (high << 8));
}
bool overrun() const {
return overrun_;
}
bool at_end() const {
std::size_t mapped_address = address_mapper_(address_);
return mapped_address >= memory_.size();
}
uint16_t address() const {
return address_;
}
private:
const std::vector<uint8_t> &memory_;
const std::function<std::size_t(uint16_t)> &address_mapper_;
uint16_t address_;
bool overrun_ = false;
};
constexpr uint8_t x(uint8_t v) { return v >> 6; }
constexpr uint8_t y(uint8_t v) { return (v >> 3) & 7; }
constexpr uint8_t q(uint8_t v) { return (v >> 3) & 1; }
constexpr uint8_t p(uint8_t v) { return (v >> 4) & 3; }
constexpr uint8_t z(uint8_t v) { return v & 7; }
Instruction::Condition condition_table[] = {
Instruction::Condition::NZ, Instruction::Condition::Z,
Instruction::Condition::NC, Instruction::Condition::C,
Instruction::Condition::PO, Instruction::Condition::PE,
Instruction::Condition::P, Instruction::Condition::M
};
Instruction::Location register_pair_table[] = {
Instruction::Location::BC,
Instruction::Location::DE,
Instruction::Location::HL,
Instruction::Location::SP
};
Instruction::Location register_pair_table2[] = {
Instruction::Location::BC,
Instruction::Location::DE,
Instruction::Location::HL,
Instruction::Location::AF
};
Instruction::Location RegisterTableEntry(
const int offset, Accessor &accessor,
Instruction &instruction,
const bool needs_indirect_offset
) {
constexpr Instruction::Location register_table[] = {
Instruction::Location::B, Instruction::Location::C,
Instruction::Location::D, Instruction::Location::E,
Instruction::Location::H, Instruction::Location::L,
Instruction::Location::HL_Indirect,
Instruction::Location::A
};
const Instruction::Location location = register_table[offset];
if(location == Instruction::Location::HL_Indirect && needs_indirect_offset) {
instruction.offset = accessor.byte() - 128;
}
return location;
}
constexpr Instruction::Operation alu_table[] = {
Instruction::Operation::ADD,
Instruction::Operation::ADC,
Instruction::Operation::SUB,
Instruction::Operation::SBC,
Instruction::Operation::AND,
Instruction::Operation::XOR,
Instruction::Operation::OR,
Instruction::Operation::CP
};
constexpr Instruction::Operation rotation_table[] = {
Instruction::Operation::RLC,
Instruction::Operation::RRC,
Instruction::Operation::RL,
Instruction::Operation::RR,
Instruction::Operation::SLA,
Instruction::Operation::SRA,
Instruction::Operation::SLL,
Instruction::Operation::SRL
};
constexpr Instruction::Operation block_table[][4] = {
{
Instruction::Operation::LDI, Instruction::Operation::CPI,
Instruction::Operation::INI, Instruction::Operation::OUTI
},
{
Instruction::Operation::LDD, Instruction::Operation::CPD,
Instruction::Operation::IND, Instruction::Operation::OUTD
},
{
Instruction::Operation::LDIR, Instruction::Operation::CPIR,
Instruction::Operation::INIR, Instruction::Operation::OTIR
},
{
Instruction::Operation::LDDR, Instruction::Operation::CPDR,
Instruction::Operation::INDR, Instruction::Operation::OTDR
},
};
void DisassembleCBPage(Accessor &accessor, Instruction &instruction, const bool needs_indirect_offset) {
const uint8_t operation = accessor.byte();
if(!x(operation)) {
instruction.operation = rotation_table[y(operation)];
instruction.source = instruction.destination =
RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
} else {
instruction.destination = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
instruction.source = Instruction::Location::Operand;
instruction.operand = y(operation);
switch(x(operation)) {
case 1: instruction.operation = Instruction::Operation::BIT; break;
case 2: instruction.operation = Instruction::Operation::RES; break;
case 3: instruction.operation = Instruction::Operation::SET; break;
}
}
}
void DisassembleEDPage(Accessor &accessor, Instruction &instruction, const bool needs_indirect_offset) {
const uint8_t operation = accessor.byte();
switch(x(operation)) {
default:
instruction.operation = Instruction::Operation::Invalid;
break;
case 2:
if(z(operation) < 4 && y(operation) >= 4) {
instruction.operation = block_table[y(operation)-4][z(operation)];
} else {
instruction.operation = Instruction::Operation::Invalid;
}
break;
case 3:
switch(z(operation)) {
case 0:
instruction.operation = Instruction::Operation::IN;
instruction.source = Instruction::Location::BC_Indirect;
if(y(operation) == 6) {
instruction.destination = Instruction::Location::None;
} else {
instruction.destination =
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
}
break;
case 1:
instruction.operation = Instruction::Operation::OUT;
instruction.destination = Instruction::Location::BC_Indirect;
if(y(operation) == 6) {
instruction.source = Instruction::Location::None;
} else {
instruction.source =
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
}
break;
case 2:
instruction.operation = (y(operation)&1) ? Instruction::Operation::ADC : Instruction::Operation::SBC;
instruction.destination = Instruction::Location::HL;
instruction.source = register_pair_table[y(operation) >> 1];
break;
case 3:
instruction.operation = Instruction::Operation::LD;
if(q(operation)) {
instruction.destination =
RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
instruction.source = Instruction::Location::Operand_Indirect;
} else {
instruction.destination = Instruction::Location::Operand_Indirect;
instruction.source =
RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
}
instruction.operand = accessor.word();
break;
case 4:
instruction.operation = Instruction::Operation::NEG;
break;
case 5:
instruction.operation =
y(operation) == 1 ? Instruction::Operation::RETI : Instruction::Operation::RETN;
break;
case 6:
instruction.operation = Instruction::Operation::IM;
instruction.source = Instruction::Location::Operand;
switch(y(operation)&3) {
case 0: instruction.operand = 0; break;
case 1: instruction.operand = 0; break;
case 2: instruction.operand = 1; break;
case 3: instruction.operand = 2; break;
}
break;
case 7:
switch(y(operation)) {
case 0:
instruction.operation = Instruction::Operation::LD;
instruction.destination = Instruction::Location::I;
instruction.source = Instruction::Location::A;
break;
case 1:
instruction.operation = Instruction::Operation::LD;
instruction.destination = Instruction::Location::R;
instruction.source = Instruction::Location::A;
break;
case 2:
instruction.operation = Instruction::Operation::LD;
instruction.destination = Instruction::Location::A;
instruction.source = Instruction::Location::I;
break;
case 3:
instruction.operation = Instruction::Operation::LD;
instruction.destination = Instruction::Location::A;
instruction.source = Instruction::Location::R;
break;
case 4: instruction.operation = Instruction::Operation::RRD; break;
case 5: instruction.operation = Instruction::Operation::RLD; break;
default: instruction.operation = Instruction::Operation::NOP; break;
}
break;
}
break;
}
}
void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
bool needs_indirect_offset = false;
enum HLSubstitution {
None, IX, IY
} hl_substitution = None;
while(true) {
const uint8_t operation = accessor.byte();
switch(x(operation)) {
case 0:
switch(z(operation)) {
case 0:
switch(y(operation)) {
case 0: instruction.operation = Instruction::Operation::NOP; break;
case 1: instruction.operation = Instruction::Operation::EXAFAFd; break;
case 2:
instruction.operation = Instruction::Operation::DJNZ;
instruction.operand = accessor.byte() - 128;
break;
default:
instruction.operation = Instruction::Operation::JR;
instruction.operand = accessor.byte() - 128;
if(y(operation) >= 4) instruction.condition = condition_table[y(operation) - 4];
break;
}
break;
case 1:
if(y(operation)&1) {
instruction.operation = Instruction::Operation::ADD;
instruction.destination = Instruction::Location::HL;
instruction.source = register_pair_table[y(operation) >> 1];
} else {
instruction.operation = Instruction::Operation::LD;
instruction.destination = register_pair_table[y(operation) >> 1];
instruction.source = Instruction::Location::Operand;
instruction.operand = accessor.word();
}
break;
case 2:
switch(y(operation)) {
case 0:
instruction.operation = Instruction::Operation::LD;
instruction.destination = Instruction::Location::BC_Indirect;
instruction.source = Instruction::Location::A;
break;
case 1:
instruction.operation = Instruction::Operation::LD;
instruction.destination = Instruction::Location::A;
instruction.source = Instruction::Location::BC_Indirect;
break;
case 2:
instruction.operation = Instruction::Operation::LD;
instruction.destination = Instruction::Location::DE_Indirect;
instruction.source = Instruction::Location::A;
break;
case 3:
instruction.operation = Instruction::Operation::LD;
instruction.destination = Instruction::Location::A;
instruction.source = Instruction::Location::DE_Indirect;
break;
case 4:
instruction.operation = Instruction::Operation::LD;
instruction.destination = Instruction::Location::Operand_Indirect;
instruction.source = Instruction::Location::HL;
break;
case 5:
instruction.operation = Instruction::Operation::LD;
instruction.destination = Instruction::Location::HL;
instruction.source = Instruction::Location::Operand_Indirect;
break;
case 6:
instruction.operation = Instruction::Operation::LD;
instruction.destination = Instruction::Location::Operand_Indirect;
instruction.source = Instruction::Location::A;
break;
case 7:
instruction.operation = Instruction::Operation::LD;
instruction.destination = Instruction::Location::A;
instruction.source = Instruction::Location::Operand_Indirect;
break;
}
if(y(operation) > 3) {
instruction.operand = accessor.word();
}
break;
case 3:
if(y(operation)&1) {
instruction.operation = Instruction::Operation::DEC;
} else {
instruction.operation = Instruction::Operation::INC;
}
instruction.source = instruction.destination = register_pair_table[y(operation) >> 1];
break;
case 4:
instruction.operation = Instruction::Operation::INC;
instruction.source = instruction.destination =
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
break;
case 5:
instruction.operation = Instruction::Operation::DEC;
instruction.source = instruction.destination =
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
break;
case 6:
instruction.operation = Instruction::Operation::LD;
instruction.destination =
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
instruction.source = Instruction::Location::Operand;
instruction.operand = accessor.byte();
break;
case 7:
switch(y(operation)) {
case 0: instruction.operation = Instruction::Operation::RLCA; break;
case 1: instruction.operation = Instruction::Operation::RRCA; break;
case 2: instruction.operation = Instruction::Operation::RLA; break;
case 3: instruction.operation = Instruction::Operation::RRA; break;
case 4: instruction.operation = Instruction::Operation::DAA; break;
case 5: instruction.operation = Instruction::Operation::CPL; break;
case 6: instruction.operation = Instruction::Operation::SCF; break;
case 7: instruction.operation = Instruction::Operation::CCF; break;
}
break;
}
break;
case 1:
if(y(operation) == 6 && z(operation) == 6) {
instruction.operation = Instruction::Operation::HALT;
} else {
instruction.operation = Instruction::Operation::LD;
instruction.source =
RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
instruction.destination =
RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
}
break;
case 2:
instruction.operation = alu_table[y(operation)];
instruction.source = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
instruction.destination = Instruction::Location::A;
break;
case 3:
switch(z(operation)) {
case 0:
instruction.operation = Instruction::Operation::RET;
instruction.condition = condition_table[y(operation)];
break;
case 1:
switch(y(operation)) {
default:
instruction.operation = Instruction::Operation::POP;
instruction.source = register_pair_table2[y(operation) >> 1];
break;
case 1:
instruction.operation = Instruction::Operation::RET;
break;
case 3:
instruction.operation = Instruction::Operation::EXX;
break;
case 5:
instruction.operation = Instruction::Operation::JP;
instruction.source = Instruction::Location::HL;
break;
case 7:
instruction.operation = Instruction::Operation::LD;
instruction.destination = Instruction::Location::SP;
instruction.source = Instruction::Location::HL;
break;
}
break;
case 2:
instruction.operation = Instruction::Operation::JP;
instruction.condition = condition_table[y(operation)];
instruction.operand = accessor.word();
break;
case 3:
switch(y(operation)) {
case 0:
instruction.operation = Instruction::Operation::JP;
instruction.source = Instruction::Location::Operand;
instruction.operand = accessor.word();
break;
case 1:
DisassembleCBPage(accessor, instruction, needs_indirect_offset);
break;
case 2:
instruction.operation = Instruction::Operation::OUT;
instruction.source = Instruction::Location::A;
instruction.destination = Instruction::Location::Operand_Indirect;
instruction.operand = accessor.byte();
break;
case 3:
instruction.operation = Instruction::Operation::IN;
instruction.destination = Instruction::Location::A;
instruction.source = Instruction::Location::Operand_Indirect;
instruction.operand = accessor.byte();
break;
case 4:
instruction.operation = Instruction::Operation::EX;
instruction.destination = Instruction::Location::SP_Indirect;
instruction.source = Instruction::Location::HL;
break;
case 5:
instruction.operation = Instruction::Operation::EX;
instruction.destination = Instruction::Location::DE;
instruction.source = Instruction::Location::HL;
break;
case 6:
instruction.operation = Instruction::Operation::DI;
break;
case 7:
instruction.operation = Instruction::Operation::EI;
break;
}
break;
case 4:
instruction.operation = Instruction::Operation::CALL;
instruction.source = Instruction::Location::Operand_Indirect;
instruction.operand = accessor.word();
instruction.condition = condition_table[y(operation)];
break;
case 5:
switch(y(operation)) {
default:
instruction.operation = Instruction::Operation::PUSH;
instruction.source = register_pair_table2[y(operation) >> 1];
break;
case 1:
instruction.operation = Instruction::Operation::CALL;
instruction.source = Instruction::Location::Operand;
instruction.operand = accessor.word();
break;
case 3:
needs_indirect_offset = true;
hl_substitution = IX;
continue; // i.e. repeat loop.
case 5:
DisassembleEDPage(accessor, instruction, needs_indirect_offset);
break;
case 7:
needs_indirect_offset = true;
hl_substitution = IY;
continue; // i.e. repeat loop.
}
break;
case 6:
instruction.operation = alu_table[y(operation)];
instruction.source = Instruction::Location::Operand;
instruction.destination = Instruction::Location::A;
instruction.operand = accessor.byte();
break;
case 7:
instruction.operation = Instruction::Operation::RST;
instruction.source = Instruction::Location::Operand;
instruction.operand = y(operation) << 3;
break;
}
break;
}
// This while(true) isn't an infinite loop for everything except those paths that opt in
// via continue.
break;
}
// Perform IX/IY substitution for HL, if applicable.
if(hl_substitution != None) {
// EX DE, HL is not affected.
if(instruction.operation == Instruction::Operation::EX) return;
// If an (HL) is involved, switch it for IX+d or IY+d.
if( instruction.source == Instruction::Location::HL_Indirect ||
instruction.destination == Instruction::Location::HL_Indirect) {
if(instruction.source == Instruction::Location::HL_Indirect) {
instruction.source =
hl_substitution == IX ?
Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
}
if(instruction.destination == Instruction::Location::HL_Indirect) {
instruction.destination =
hl_substitution == IX ?
Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
}
return;
}
// Otherwise, switch either of H or L for I[X/Y]h and I[X/Y]l.
if(instruction.source == Instruction::Location::H) {
instruction.source = (hl_substitution == IX) ? Instruction::Location::IXh : Instruction::Location::IYh;
}
if(instruction.source == Instruction::Location::L) {
instruction.source = (hl_substitution == IX) ? Instruction::Location::IXl : Instruction::Location::IYl;
}
if(instruction.destination == Instruction::Location::H) {
instruction.destination = (hl_substitution == IX) ? Instruction::Location::IXh : Instruction::Location::IYh;
}
if(instruction.destination == Instruction::Location::L) {
instruction.destination = (hl_substitution == IX) ? Instruction::Location::IXl : Instruction::Location::IYl;
}
}
}
struct Z80Disassembler {
static void AddToDisassembly(
PartialDisassembly &disassembly,
const std::vector<uint8_t> &memory,
const std::function<std::size_t(uint16_t)> &address_mapper,
const uint16_t entry_point
) {
disassembly.disassembly.internal_calls.insert(entry_point);
Accessor accessor(memory, address_mapper, entry_point);
while(!accessor.at_end()) {
Instruction instruction;
instruction.address = accessor.address();
DisassembleMainPage(accessor, instruction);
// If any memory access was invalid, end disassembly.
if(accessor.overrun()) return;
// Store the instruction away.
disassembly.disassembly.instructions_by_address[instruction.address] = instruction;
// Update access tables.
int access_type =
((instruction.source == Instruction::Location::Operand_Indirect) ? 1 : 0) |
((instruction.destination == Instruction::Location::Operand_Indirect) ? 2 : 0);
uint16_t address = uint16_t(instruction.operand);
bool is_internal = address_mapper(address) < memory.size();
switch(access_type) {
default: break;
case 1:
if(is_internal) {
disassembly.disassembly.internal_loads.insert(address);
} else {
disassembly.disassembly.external_loads.insert(address);
}
break;
case 2:
if(is_internal) {
disassembly.disassembly.internal_stores.insert(address);
} else {
disassembly.disassembly.external_stores.insert(address);
}
break;
case 3:
if(is_internal) {
disassembly.disassembly.internal_modifies.insert(address);
} else {
disassembly.disassembly.internal_modifies.insert(address);
}
break;
}
// Add any (potentially) newly-discovered entry point.
if( instruction.operation == Instruction::Operation::JP ||
instruction.operation == Instruction::Operation::JR ||
instruction.operation == Instruction::Operation::CALL ||
instruction.operation == Instruction::Operation::RST) {
disassembly.remaining_entry_points.push_back(uint16_t(instruction.operand));
}
// This is it if: an unconditional RET, RETI, RETN, JP or JR is found.
switch(instruction.operation) {
default: break;
case Instruction::Operation::RET:
case Instruction::Operation::RETI:
case Instruction::Operation::RETN:
case Instruction::Operation::JP:
case Instruction::Operation::JR:
if(instruction.condition == Instruction::Condition::None) {
disassembly.implicit_entry_points.push_back(accessor.address());
return;
}
}
}
}
};
} // end of anonymous namespace
Disassembly Analyser::Static::Z80::Disassemble(
const std::vector<uint8_t> &memory,
const std::function<std::size_t(uint16_t)> &address_mapper,
std::vector<uint16_t> entry_points,
Approach approach)
{
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, Z80Disassembler>(
memory,
address_mapper,
entry_points,
approach == Approach::Exhaustive
);
}