/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski, Nik Henson
**
** This file is part of Supermodel.
**
** Supermodel 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.
**
** Supermodel 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 Supermodel. If not, see .
**/
/*
* CodeAnalyser.cpp
*/
#ifdef SUPERMODEL_DEBUGGER
#include "CodeAnalyser.h"
#include "CPUDebug.h"
#include "Label.h"
#include
#include
namespace Debugger
{
CEntryPoint::CEntryPoint(const CEntryPoint &other) : addr(other.addr), autoFlag(other.autoFlag)
{
if (other.autoLabel != NULL)
{
strncpy(autoLabelStr, other.autoLabel, 254);
autoLabelStr[254] = '\0';
autoLabel = autoLabelStr;
}
else
autoLabel = NULL;
}
CEntryPoint::CEntryPoint(UINT32 eAddr, ELabelFlags eAutoFlag, const char *eAutoLabel) :
addr(eAddr), autoFlag(eAutoFlag)
{
if (eAutoLabel != NULL)
{
strncpy(autoLabelStr, eAutoLabel, 254);
autoLabelStr[254] = '\0';
autoLabel = autoLabelStr;
}
else
autoLabel = NULL;
}
CEntryPoint &CEntryPoint::operator=(const CEntryPoint &other)
{
addr = other.addr;
autoFlag = other.autoFlag;
if (other.autoLabel != NULL)
{
strncpy(autoLabelStr, other.autoLabel, 254);
autoLabelStr[254] = '\0';
autoLabel = autoLabelStr;
}
else
autoLabel = NULL;
return *this;
}
bool CEntryPoint::operator==(const CEntryPoint &other)
{
return addr == other.addr && autoFlag == other.autoFlag;
}
bool CEntryPoint::operator!=(const CEntryPoint &other)
{
return addr != other.addr || autoFlag != other.autoFlag;
}
const char *CAutoLabel::s_defaultLabelFmts[] = { "Entry%s", "Ex%s", "Int%s", "Jmp%s", "Loop%s", "Sub%s", NULL };
const unsigned CAutoLabel::numLabelFlags = sizeof(s_defaultLabelFmts) / sizeof(char*);
ELabelFlags CAutoLabel::GetLabelFlag(int index)
{
if (index < 0 || index >= numLabelFlags)
return LFNone;
return (ELabelFlags)(1 << index);
}
int CAutoLabel::GetFlagIndex(ELabelFlags flag)
{
switch (flag)
{
case LFEntryPoint: return 0;
case LFExcepHandler: return 1;
case LFInterHandler: return 2;
case LFJumpTarget: return 3;
case LFLoopPoint: return 4;
case LFSubroutine: return 5;
case LFUnseenCode: return 6;
default: return -1;
}
}
const char *CAutoLabel::GetFlagString(ELabelFlags flag)
{
switch (flag)
{
case LFEntryPoint: return "Entry Point";
case LFExcepHandler: return "Exception Handler";
case LFInterHandler: return "Interrupt Handler";
case LFJumpTarget: return "Jump Target";
case LFLoopPoint: return "Loop Point";
case LFSubroutine: return "Subroutine";
case LFUnseenCode: return "Unseen Code";
default: return "";
}
}
CAutoLabel::CAutoLabel(CCPUDebug *lCPU, UINT32 lAddr) : CAddressRef(lCPU, lAddr), flags(LFNone), m_acquired(0)
{
memset(m_subLabels, NULL, sizeof(m_subLabels));
}
CAutoLabel::~CAutoLabel()
{
// Delete all sub-labels
for (int index = 0; index < numLabelFlags; index++)
{
if (m_subLabels[index] != NULL)
{
delete[] m_subLabels[index];
m_subLabels[index] = NULL;
}
}
}
void CAutoLabel::Acquire()
{
m_acquired++;
}
void CAutoLabel::Release()
{
if (--m_acquired == 0)
delete this;
}
void CAutoLabel::AddFlag(ELabelFlags flag, const char *subLabel)
{
int index = GetFlagIndex(flag);
if (index == -1)
return;
flags = (ELabelFlags)((unsigned)flags | (unsigned)flag);
if (subLabel != NULL)
{
size_t len = strlen(subLabel);
char *label = new char[len + 1];
strncpy(label, subLabel, len);
label[len] = '\0';
m_subLabels[index] = label;
}
else
m_subLabels[index] = CreateDefaultSubLabel(flag);
}
bool CAutoLabel::GetLabel(char *labelStr, ELabelFlags subFlags)
{
char *p = labelStr;
*p = '\0';
for (int index = 0; index < numLabelFlags; index++)
{
ELabelFlags flag = GetLabelFlag(index);
if (!(subFlags & flag))
continue;
const char *subLabel = m_subLabels[index];
if (subLabel == NULL)
continue;
if (p > labelStr)
{
(*p++) = '/';
*p = '\0';
}
strcat(p, subLabel);
p += strlen(subLabel);
*p = '\0';
}
return p > labelStr;
}
bool CAutoLabel::ContainsSubLabel(const char *subLabel)
{
for (int i = 0; i < numLabelFlags; i++)
{
if (m_subLabels[i] != NULL && stricmp(subLabel, m_subLabels[i]) == 0)
return true;
}
return false;
}
const char *CAutoLabel::CreateDefaultSubLabel(ELabelFlags flag)
{
int index = GetFlagIndex(flag);
if (index == -1)
return NULL;
const char *labelFmt = s_defaultLabelFmts[index];
if (labelFmt == NULL)
return NULL;
char addrStr[50];
cpu->debugger->FormatData(addrStr, Hex, (unsigned)(cpu->memBusWidth / 8), (UINT64)addr);
char *label = new char[255];
sprintf(label, labelFmt, addrStr);
return label;
}
CCodeAnalysis::CCodeAnalysis(CCodeAnalyser *aAnalyser) : analyser(aAnalyser)
{
//
}
CCodeAnalysis::CCodeAnalysis(CCodeAnalyser *aAnalyser, unsigned aTotalIndices, vector &entryPoints, vector &unseenEntryAddrs) :
analyser(aAnalyser), m_entryPoints(entryPoints), m_unseenEntryAddrs(unseenEntryAddrs),
m_seenIndices(aTotalIndices), m_validIndices(aTotalIndices), m_acquired(0)
{
//
}
CCodeAnalysis::CCodeAnalysis(CCodeAnalysis *oldAnalysis, vector &entryPoints, vector &unseenEntryAddrs) :
analyser(oldAnalysis->analyser), m_entryPoints(entryPoints), m_unseenEntryAddrs(unseenEntryAddrs),
m_seenIndices(oldAnalysis->m_seenIndices), m_validIndices(oldAnalysis->m_validIndices),
m_autoLabelsMap(oldAnalysis->m_autoLabelsMap), m_acquired(0), validIndexSet(oldAnalysis->validIndexSet)
{
for (map::iterator it = m_autoLabelsMap.begin(); it != m_autoLabelsMap.end(); it++)
it->second->Acquire();
}
CCodeAnalysis::~CCodeAnalysis()
{
for (vector::iterator it = autoLabels.begin(); it != autoLabels.end(); it++)
(*it)->Release();
autoLabels.clear();
m_autoLabelsMap.clear();
}
void CCodeAnalysis::Acquire()
{
m_acquired++;
}
void CCodeAnalysis::Release()
{
if (--m_acquired == 0)
delete this;
}
void CCodeAnalysis::FinishAnalysis()
{
for (map::iterator it = m_autoLabelsMap.begin(); it != m_autoLabelsMap.end(); it++)
autoLabels.push_back(it->second);
}
bool CCodeAnalysis::IsAddrValid(UINT32 addr)
{
unsigned index;
if (!analyser->GetIndexOfAddr(addr, index))
return false;
return IsIndexValid(index);
}
bool CCodeAnalysis::GetNextValidAddr(UINT32 &addr)
{
unsigned index;
if (!analyser->GetIndexOfAddr(addr, index))
return false;
if (IsIndexValid(index))
return true;
set::iterator it = validIndexSet.lower_bound(index);
if (it == validIndexSet.end())
return false;
return analyser->GetAddrOfIndex(*it, addr);
}
bool CCodeAnalysis::IsIndexValid(unsigned index)
{
return index < m_validIndices.size() && m_validIndices[index];
}
bool CCodeAnalysis::GetNextValidIndex(unsigned &index)
{
if (IsIndexValid(index))
return true;
set::iterator it = validIndexSet.lower_bound(index);
if (it == validIndexSet.end())
return false;
index = *it;
return true;
}
bool CCodeAnalysis::HasSeenAddr(UINT32 addr)
{
unsigned index;
if (!analyser->GetIndexOfAddr(addr, index))
return false;
return HaveSeenIndex(index);
}
bool CCodeAnalysis::HaveSeenIndex(unsigned index)
{
return index < m_seenIndices.size() && m_seenIndices[index];
}
CAutoLabel *CCodeAnalysis::GetAutoLabel(UINT32 addr)
{
map::iterator it = m_autoLabelsMap.find(addr);
if (it == m_autoLabelsMap.end())
return NULL;
return it->second;
}
CAutoLabel *CCodeAnalysis::GetAutoLabel(const char *subLabel)
{
for (vector::iterator it = autoLabels.begin(); it != autoLabels.end(); it++)
{
if ((*it)->ContainsSubLabel(subLabel))
return *it;
}
return NULL;
}
vector CCodeAnalysis::GetAutoLabels(ELabelFlags flag)
{
vector matched;
for (vector::iterator it = autoLabels.begin(); it != autoLabels.end(); it++)
{
if ((*it)->flags & flag)
matched.push_back(*it);
}
return matched;
}
CCodeAnalyser::CCodeAnalyser(CCPUDebug *aCPU) : cpu(aCPU), emptyAnalysis(this), analysis(&emptyAnalysis)
{
instrAlign = cpu->minInstrLen;
totalIndices = 0;
for (vector::iterator it = cpu->regions.begin(); it != cpu->regions.end(); it++)
{
if (!(*it)->isCode)
continue;
m_codeRegions.push_back(*it);
totalIndices += (*it)->size / instrAlign;
m_indexBounds.push_back(totalIndices);
}
}
CCodeAnalyser::~CCodeAnalyser()
{
if (analysis != &emptyAnalysis)
analysis->Release();
}
void CCodeAnalyser::Reset()
{
CCodeAnalysis *oldAnalysis = analysis;
analysis = &emptyAnalysis;
if (oldAnalysis != &emptyAnalysis)
oldAnalysis->Release();
}
bool CCodeAnalyser::GetAddrOfIndex(unsigned index, UINT32 &addr)
{
unsigned regIndex = 0;
unsigned prevBound = 0;
for (vector::iterator it = m_indexBounds.begin(); it != m_indexBounds.end(); it++)
{
if (*it > index)
{
addr = m_codeRegions[regIndex]->addr + (UINT32)(index - prevBound) * instrAlign;
return true;
}
prevBound = *it;
regIndex++;
}
return false;
}
bool CCodeAnalyser::GetIndexOfAddr(UINT32 addr, unsigned &index)
{
unsigned regIndex = 0;
for (vector::iterator it = m_codeRegions.begin(); it != m_codeRegions.end(); it++)
{
if ((*it)->addr <= addr && addr <= (*it)->addrEnd)
{
unsigned offset = (unsigned)((addr - (*it)->addr) / instrAlign);
index = (regIndex > 0 ? m_indexBounds[regIndex - 1] + offset : (unsigned)offset);
return true;
}
regIndex++;
}
return false;
}
void CCodeAnalyser::CheckEntryPoints(vector &entryPoints, vector &unseenEntryAddrs, vector &prevPoints,
bool &needsAnalysis, bool &reanalyse)
{
needsAnalysis = false;
// Gather entry points
GatherEntryPoints(entryPoints, unseenEntryAddrs, reanalyse);
if (reanalyse)
{
needsAnalysis = true;
return;
}
// Compare new entry points with previous ones
for (size_t i = 0; i < entryPoints.size(); i++)
{
// Check if have more than before
if (i >= prevPoints.size())
{
needsAnalysis = true;
return;
}
// Check if any have changed
if (entryPoints[i] != prevPoints[i])
{
// If entry points, exception handlers or interrupt handlers have changed, then force reanalysis
if (entryPoints[i].autoFlag == LFEntryPoint || prevPoints[i].autoFlag == LFEntryPoint ||
entryPoints[i].autoFlag == LFExcepHandler || prevPoints[i].autoFlag == LFExcepHandler ||
entryPoints[i].autoFlag == LFInterHandler || prevPoints[i].autoFlag == LFInterHandler)
reanalyse = true;
needsAnalysis = true;
return;
}
}
// Check if have less than before
if (entryPoints.size() < prevPoints.size())
{
// If so, force reanalysis
reanalyse = true;
needsAnalysis = true;
return;
}
}
void CCodeAnalyser::GatherEntryPoints(vector &entryPoints, vector &unseenEntryAddrs, bool &reanalyse)
{
char labelStr[255];
UINT32 addr;
unsigned index;
entryPoints.clear();
reanalyse = false;
// Add reset address as main entry point
AddEntryPoint(entryPoints, cpu->GetResetAddr(), LFEntryPoint, "MainEntry");
// Add exception handlers as entry points
for (vector::iterator it = cpu->exceps.begin(); it != cpu->exceps.end(); it++)
{
if (!cpu->GetHandlerAddr(*it, addr))
continue;
sprintf(labelStr, "Ex%s", (*it)->id);
AddEntryPoint(entryPoints, addr, LFExcepHandler, labelStr);
}
// Add interrupt handlers as entry points
for (vector::iterator it = cpu->inters.begin(); it != cpu->inters.end(); it++)
{
if (!cpu->GetHandlerAddr(*it, addr))
continue;
sprintf(labelStr, "Int%s", (*it)->id);
AddEntryPoint(entryPoints, addr, LFInterHandler, labelStr);
}
// Add custom entry addresses
unsigned i = 0;
for (vector::iterator it = m_customEntryAddrs.begin(); it != m_customEntryAddrs.end(); it++)
{
sprintf(labelStr, "Custom%u", i++);
AddEntryPoint(entryPoints, *it, LFEntryPoint, labelStr);
}
// If current PC address is at an unseen location or at location that was previously invalid, then add address as unseen entry point
if (cpu->instrCount > 0 && GetIndexOfAddr(cpu->pc, index) && (!analysis->HaveSeenIndex(index) || !analysis->IsIndexValid(index)))
{
// If at location that was previously seen and was invalid, then force reanalysis (ie because code may have been modified)
if (analysis->HaveSeenIndex(index) && !analysis->IsIndexValid(index))
reanalyse = true;
// Check that address not already included in previous entry points
bool unseen = true;
for (vector::iterator it = entryPoints.begin(); it != entryPoints.end(); it++)
{
if ((*it).addr == cpu->pc)
{
unseen = false;
break;
}
}
if (unseen && find(unseenEntryAddrs.begin(), unseenEntryAddrs.end(), cpu->pc) == unseenEntryAddrs.end())
unseenEntryAddrs.push_back(cpu->pc);
}
// Add unseen entry points
for (vector::iterator it = unseenEntryAddrs.begin(); it != unseenEntryAddrs.end(); it++)
AddEntryPoint(entryPoints, *it, LFUnseenCode, NULL);
}
void CCodeAnalyser::AddEntryPoint(vector &entryPoints, UINT32 addr, ELabelFlags autoFlag, const char *autoLabel)
{
CEntryPoint entryPoint(addr, autoFlag, autoLabel);
if (find(entryPoints.begin(), entryPoints.end(), entryPoint) == entryPoints.end())
entryPoints.push_back(entryPoint);
}
bool CCodeAnalyser::NeedsAnalysis()
{
vector entryPoints;
vector unseenEntryAddrs(analysis->m_unseenEntryAddrs);
bool needsAnalysis;
bool reanalyse;
CheckEntryPoints(entryPoints, unseenEntryAddrs, analysis->m_entryPoints, needsAnalysis, reanalyse);
return needsAnalysis;
}
bool CCodeAnalyser::AnalyseCode()
{
m_abortAnalysis = false;
CCodeAnalysis *oldAnalysis = analysis;
vector entryPoints;
vector unseenEntryAddrs(oldAnalysis->m_unseenEntryAddrs);
bool needsAnalysis;
bool reanalyse;
CheckEntryPoints(entryPoints, unseenEntryAddrs, oldAnalysis->m_entryPoints, needsAnalysis, reanalyse);
if (!needsAnalysis)
return false;
CCodeAnalysis *newAnalysis;
if (reanalyse || oldAnalysis == &emptyAnalysis)
newAnalysis = new CCodeAnalysis(this, totalIndices, entryPoints, unseenEntryAddrs);
else
newAnalysis = new CCodeAnalysis(oldAnalysis, entryPoints, unseenEntryAddrs);
newAnalysis->Acquire();
for (vector::iterator it = newAnalysis->m_entryPoints.begin(); it != newAnalysis->m_entryPoints.end(); it++)
{
AddFlagToAddr(newAnalysis->m_autoLabelsMap, it->addr, it->autoFlag, it->autoLabel);
AnalyseCode(newAnalysis->m_seenIndices, newAnalysis->m_validIndices, newAnalysis->validIndexSet, newAnalysis->m_autoLabelsMap, it->addr);
}
newAnalysis->FinishAnalysis();
if (m_abortAnalysis)
{
newAnalysis->Release();
return false;
}
analysis = newAnalysis;
if (oldAnalysis != &emptyAnalysis)
oldAnalysis->Release();
cpu->debugger->AnalysisUpdated(this);
return true;
}
void CCodeAnalyser::AnalyseCode(vector &seenIndices, vector &validIndices, set &validIndexSet,
map &autoLabelsMap, UINT32 addr)
{
if (m_abortAnalysis)
return;
unsigned index;
if (!GetIndexOfAddr(addr, index) || seenIndices[index])
return;
CRegion *region = cpu->GetRegion(addr);
if (region == NULL || !region->isCode)
return;
set::iterator setIt = validIndexSet.end();
unsigned startIndex = index;
do
{
if (m_abortAnalysis)
return;
// Flag that have seen this address index
seenIndices[index] = true;
// If unit is not valid (ie doesn't disassemble) then code block must be invalid (TODO - invalidate whole code block?)
int codesLen = cpu->GetOpLength(addr);
if (codesLen <= 0)
return;
validIndices[index] = true;
if (setIt != validIndexSet.end())
setIt = validIndexSet.insert(setIt, index);
else
setIt = validIndexSet.insert(index).first;
UINT32 opcode = cpu->GetOpcode(addr);
EOpFlags opFlags = cpu->GetOpFlags(addr, opcode);
// See if instruction is jump
if (opFlags & (JumpSimple|JumpLoop|JumpSub))
{
// If so, see if address is valid (ie known at disassemble time)
UINT32 jumpAddr;
if (cpu->GetJumpAddr(addr, opcode, jumpAddr))
{
// If so, add flags to jump address and analyse destination code block too
if (opFlags & JumpSub) AddFlagToAddr(autoLabelsMap, jumpAddr, LFSubroutine, NULL);
else if (opFlags & JumpLoop) AddFlagToAddr(autoLabelsMap, jumpAddr, LFLoopPoint, NULL);
else AddFlagToAddr(autoLabelsMap, jumpAddr, LFJumpTarget, NULL);
AnalyseCode(seenIndices, validIndices, validIndexSet, autoLabelsMap, jumpAddr);
}
}
// Finish if instruction terminates code block (ie is not conditional and is either a non-returning jump, a return or some sort
// of reset/halting instruction)
if (!(opFlags & Conditional) && (opFlags & (JumpSimple|JumpLoop|ReturnEx|ReturnSub|HaltExec)))
return;
// Move to next index
index += (unsigned)codesLen / instrAlign;
// If reach end of address indices, code block must be invalid (TODO - invalidate whole code block?)
if (index >= totalIndices)
return;
// Move to next address
addr += (UINT32)codesLen;
// If move between regions, check new region is valid
if (addr > region->addrEnd)
{
region = cpu->GetRegion(addr);
if (region == NULL || !region->isCode) // (TODO - invalidate whole code block?)
return;
}
}
while (!seenIndices[index]);
}
void CCodeAnalyser::AddFlagToAddr(map &autoLabelsMap, UINT32 addr, ELabelFlags flag, const char *subLabel)
{
if (flag == LFNone)
return;
map::iterator it = autoLabelsMap.find(addr);
CAutoLabel *label;
if (it == autoLabelsMap.end())
{
label = new CAutoLabel(cpu, addr);
label->Acquire();
autoLabelsMap[addr] = label;
}
else
label = it->second;
label->AddFlag(flag, subLabel);
}
void CCodeAnalyser::AbortAnalysis()
{
m_abortAnalysis = true;
}
void CCodeAnalyser::ClearCustomEntryAddrs()
{
m_customEntryAddrs.clear();
}
void CCodeAnalyser::AddCustomEntryAddr(UINT32 entryAddr)
{
if (find(m_customEntryAddrs.begin(), m_customEntryAddrs.end(), entryAddr) == m_customEntryAddrs.end())
m_customEntryAddrs.push_back(entryAddr);
}
bool CCodeAnalyser::RemoveCustomEntryAddr(UINT32 entryAddr)
{
vector::iterator it = find(m_customEntryAddrs.begin(), m_customEntryAddrs.end(), entryAddr);
if (it == m_customEntryAddrs.end())
return false;
m_customEntryAddrs.erase(it);
return true;
}
#ifdef DEBUGGER_HASBLOCKFILE
bool CCodeAnalyser::LoadState(CBlockFile *state)
{
// Load custom entry addresses
char blockStr[255];
sprintf(blockStr, "%s.entryaddrs", cpu->name);
if (state->FindBlock(blockStr) == OKAY)
{
m_customEntryAddrs.clear();
UINT32 numAddrs;
state->Read(&numAddrs, sizeof(numAddrs));
for (UINT32 i = 0; i < numAddrs; i++)
{
UINT32 addr;
state->Read(&addr, sizeof(addr));
m_customEntryAddrs.push_back(addr);
}
}
return true;
}
bool CCodeAnalyser::SaveState(CBlockFile *state)
{
// Save custom entry addresses
char blockStr[255];
sprintf(blockStr, "%s.entryaddrs", cpu->name);
state->NewBlock(blockStr, __FILE__);
UINT32 numAddrs = m_customEntryAddrs.size();
state->Write(&numAddrs, sizeof(numAddrs));
for (UINT32 i = 0; i < numAddrs; i++)
{
UINT32 addr = m_customEntryAddrs[i];
state->Write(&addr, sizeof(addr));
}
return true;
}
#endif // DEBUGGER_HASBLOCKFILE
}
#endif // SUPERMODEL_DEBUGGER