Source

gb_emulator / gb_emulator / src / gb_disassembler.cpp

Full commit
/*  Copyright Š 2011 Chris Spencer <spencercw@gmail.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

#include <gb_emulator/gb_disassembler.h>

#include <iomanip>
#include <iostream>

#include <gb_emulator/constants.h>

#include "gb_cpu_opcodes.h"

using boost::shared_ptr;
using std::basic_string;
using std::clog;
using std::hex;
using std::make_pair;
using std::map;
using std::queue;
using std::setfill;
using std::setw;

struct GbOpCode
{
	const char *name;
	const char *operands;
	uint8_t arguments;
};

#include "gb_disassembler_opcodes.cpp.inc"

void GbDisassembler::disassemble(const basic_string<uint8_t> &data, uint32_t entryPoint,
	const CdbFile *cdb)
{
	data_ = &data;

	if (entryPoint >= data.size())
	{
		return;
	}

	// Do disassembly using the debug symbols if available
	if (cdb)
	{
		const map<uint64_t, shared_ptr<CdbFile::Line> > &asmLines = cdb->asmLines();
		for (map<uint64_t, shared_ptr<CdbFile::Line> >::const_iterator line = asmLines.begin(),
			end = asmLines.end(); line != end; ++line)
		{
			uint32_t addr = static_cast<uint32_t>(line->first);
			disassembleOp(addr);
		}
	}
		
	// Do automatic disassembly to pick up anything that wasn't in the symbols
	disassemblyQueue_.push(entryPoint);
	for (; !disassemblyQueue_.empty(); disassemblyQueue_.pop())
	{
		for (uint32_t addr = disassemblyQueue_.front(); ; ++addr)
		{
			if (!disassembleOp(addr))
			{
				break;
			}
		}
	}
}

void GbDisassembler::update(uint32_t addr)
{
	// Start disassembling
	disassemblyQueue_.push(addr);
	for (; !disassemblyQueue_.empty(); disassemblyQueue_.pop())
	{
		for (addr = disassemblyQueue_.front(); ; ++addr)
		{
			if (!disassembleOp(addr))
			{
				break;
			}
		}
	}
}

bool GbDisassembler::disassembleOp(uint32_t &addr)
{
	// Check if the instruction has already been decoded
	{
		map<uint32_t, shared_ptr<GbInstruction> >::const_iterator instruction =
			disassembly_.find(addr);
		if (instruction != disassembly_.end())
		{
			return false;
		}
	}

	// Find the opcode
	if (addr >= data_->size())
	{
		return false;
	}

	uint8_t ch = (*data_)[addr];
	bool multiByte = false;
	GbOpCode opcode;
	if (ch == MULTI_BYTE)
	{
		ch = (*data_)[addr + 1];
		multiByte = true;
		opcode = mbOpcodes[ch];
	}
	else
	{
		opcode = opcodes[ch];
	}

	if (opcode.name == NULL)
	{
		clog << hex << setfill('0') << "bad opcode 0x" << setw(4) << addr << "\n";
		return false;
	}

	// Disassemble the instruction
	shared_ptr<GbInstruction> instruction =
		disassembly_.insert(make_pair(addr, new GbInstruction)).first->second;

	char buf[16];
	uint16_t operand = 0;
	size_t operandAddr = addr + 1;
	if (multiByte)
	{
		++operandAddr;
	}

	switch (opcode.arguments)
	{
	case 0:
		// Save the instruction
		instruction->address = addr;
		instruction->opcode = opcode.name;
		if (multiByte)
		{
			instruction->codeBytesLen = 2;
			instruction->codeBytes[0] = 0xcb;
			instruction->codeBytes[1] = ch;
			instruction->codeBytes[2] = 0;
			instruction->codeBytes[3] = 0;
		}
		else
		{
			instruction->codeBytesLen = 1;
			instruction->codeBytes[0] = ch;
			instruction->codeBytes[1] = 0;
			instruction->codeBytes[2] = 0;
			instruction->codeBytes[3] = 0;
		}
		if (opcode.operands)
		{
			instruction->operands = opcode.operands;
		}
		break;

	case 1:
		assert(opcode.operands);
		{
			// Format the operands string
			operand = (*data_)[operandAddr];
#ifdef _WIN32
			if (sprintf_s(buf, opcode.operands, operand) < 0)
#else
			if (sprintf(buf, opcode.operands, operand) < 0)
#endif
			{
				clog << "sprintf error\n";
				break;
			}

			// Save the instruction
			instruction->address = addr;
			instruction->opcode = opcode.name;
			instruction->operands = buf;
						
			if (multiByte)
			{
				instruction->codeBytesLen = 3;
				instruction->codeBytes[0] = 0xcb;
				instruction->codeBytes[1] = ch;
				instruction->codeBytes[2] = static_cast<uint8_t>(operand);
				instruction->codeBytes[3] = 0;
			}
			else
			{
				instruction->codeBytesLen = 2;
				instruction->codeBytes[0] = ch;
				instruction->codeBytes[1] = static_cast<uint8_t>(operand);
				instruction->codeBytes[2] = 0;
				instruction->codeBytes[3] = 0;
			}

			// Skip over the operand
			++addr;
		}
		break;

	case 2:
		assert(opcode.operands);
		{
			// Format the operands string
			operand = ((*data_)[operandAddr + 1] << 8) | (*data_)[operandAddr];
#ifdef _WIN32
			if (sprintf_s(buf, opcode.operands, operand) < 0)
#else
			if (sprintf(buf, opcode.operands, operand) < 0)
#endif
			{
				clog << "sprintf error\n";
				break;
			}

			// Save the instruction
			instruction->address = addr;
			instruction->opcode = opcode.name;
			instruction->operands = buf;
						
			if (multiByte)
			{
				instruction->codeBytesLen = 4;
				instruction->codeBytes[0] = 0xcb;
				instruction->codeBytes[1] = ch;
				instruction->codeBytes[2] = operand & 0xff;
				instruction->codeBytes[3] = operand >> 8;
			}
			else
			{
				instruction->codeBytesLen = 3;
				instruction->codeBytes[0] = ch;
				instruction->codeBytes[1] = operand & 0xff;
				instruction->codeBytes[2] = operand >> 8;
				instruction->codeBytes[3] = 0;
			}

			// Skip over the operand
			addr += 2;
		}
		break;

	default:
		assert(!"invalid opcode description");
	}
	if (multiByte)
	{
		++addr;
	}

	// Handle jumps
	bool stop = false;
	switch (ch)
	{
	case JP_MHL:
		stop = true;
		break;

	case JP_NN:
		stop = true;
	case JP_NZ_NN:
	case JP_Z_NN:
	case JP_NC_NN:
	case JP_C_NN:
	case CALL_NN:
	case CALL_NZ_NN:
	case CALL_Z_NN:
	case CALL_NC_NN:
	case CALL_C_NN:
		// Can only statically disassemble code in the fixed ROM bank; everything else will be done
		// at runtime
		if (operand < ROM_BANKX)
		{
			disassemblyQueue_.push(operand);
		}
		break;
					
	case JR_N:
		stop = true;
	case JR_NZ_N:
	case JR_Z_N:
	case JR_NC_N:
	case JR_C_N:
		disassemblyQueue_.push(addr + 1 + static_cast<int>(static_cast<int8_t>(operand)));
		break;
					
	case RST_00:
		disassemblyQueue_.push(0x00);
		break;
	case RST_08:
		disassemblyQueue_.push(0x08);
		break;
	case RST_10:
		disassemblyQueue_.push(0x10);
		break;
	case RST_18:
		disassemblyQueue_.push(0x18);
		break;
	case RST_20:
		disassemblyQueue_.push(0x20);
		break;
	case RST_28:
		disassemblyQueue_.push(0x28);
		break;

	case RET:
	case RETI:
		stop = true;
		break;
	}

	if (stop)
	{
		return false;
	}

	return true;
}