/* Copyright (C) 2008 Guillaume Duhamel
Copyright (C) 2009-2010 DeSmuME team
This file is part of DeSmuME
DeSmuME 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 2 of the License, or
(at your option) any later version.
DeSmuME 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 DeSmuME; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <algorithm>
#include <cstdarg>
#include <cstdio>
#include "debug.h"
#include "MMU.h"
#include "armcpu.h"
#include "arm_instructions.h"
#include "thumb_instructions.h"
#include "cp15.h"
#include "NDSSystem.h"
#include "utils/xstring.h"
//#include "movie.h"
#ifdef HAVE_LUA
#include "lua-engine.h"
#endif
armcpu_t* TDebugEventData::cpu() { return procnum==0?&NDS_ARM9:&NDS_ARM7; }
TDebugEventData DebugEventData;
uint32_t debugFlag;
//DEBUG CONFIGURATION
const bool debug_acl = false;
static bool acl_check_access(uint32_t adr, uint32_t access) {
//non-user modes get separate access handling, so check that here
if(NDS_ARM9.CPSR.bits.mode != USR)
access |= 1;
if (armcp15_isAccessAllowed((armcp15_t *)NDS_ARM9.coproc[15],adr,access)==false) {
HandleDebugEvent(DEBUG_EVENT_ACL_EXCEPTION);
}
return true;
}
void HandleDebugEvent_ACL_Exception()
{
printf("ACL EXCEPTION!\n");
if(DebugEventData.memAccessType == MMU_AT_CODE)
armcpu_exception(DebugEventData.cpu(),EXCEPTION_PREFETCH_ABORT);
else if(DebugEventData.memAccessType == MMU_AT_DATA)
armcpu_exception(DebugEventData.cpu(),EXCEPTION_DATA_ABORT);
}
/*static bool CheckRange(uint32_t adr, uint32_t min, uint32_t len)
{
return (adr>=min && adr<min+len);
}*/
void HandleDebugEvent_Read()
{
if(!debug_acl) return;
if(DebugEventData.procnum != ARMCPU_ARM9) return; //acl only valid on arm9
acl_check_access(DebugEventData.addr,CP15_ACCESS_READ);
}
void HandleDebugEvent_Write()
{
if(!debug_acl) return;
if(DebugEventData.procnum != ARMCPU_ARM9) return; //acl only valid on arm9
acl_check_access(DebugEventData.addr,CP15_ACCESS_WRITE);
}
void HandleDebugEvent_Execute()
{
//HACKY BREAKPOINTS!
//extern bool nds_debug_continuing[2];
//if(!nds_debug_continuing[DebugEventData.procnum]) //dont keep hitting the same breakpoint
//{
// if((DebugEventData.addr & 0xFFFFFFF0) == 0x02000000)
// {
// void NDS_debug_break();
// NDS_debug_break();
// }
//}
if(!debug_acl) return;
if(DebugEventData.procnum != ARMCPU_ARM9) return; //acl only valid on arm9
acl_check_access(DebugEventData.addr,CP15_ACCESS_EXECUTE);
}
//------------------------------------------------
DebugStatistics DEBUG_statistics;
DebugStatistics::DebugStatistics()
{
}
DebugStatistics::InstructionHits::InstructionHits()
{
memset(&arm,0,sizeof(arm));
memset(&thumb,0,sizeof(thumb));
}
static DebugStatistics::InstructionHits combinedHits[2];
template<int proc, int which>
static bool debugStatsSort(int num1, int num2) {
if(which==0) {
if(combinedHits[proc].arm[num2] == combinedHits[proc].arm[num1]) return false;
if(combinedHits[proc].arm[num1] == 0xFFFFFFFF) return false;
if(combinedHits[proc].arm[num2] == 0xFFFFFFFF) return true;
return combinedHits[proc].arm[num2] < combinedHits[proc].arm[num1];
}
else {
if(combinedHits[proc].thumb[num2] == combinedHits[proc].thumb[num1]) return false;
if(combinedHits[proc].thumb[num1] == 0xFFFFFFFF) return false;
if(combinedHits[proc].thumb[num2] == 0xFFFFFFFF) return true;
return combinedHits[proc].thumb[num2] < combinedHits[proc].thumb[num1];
}
}
void DebugStatistics::print()
{
//consolidate opcodes with the same names
for(int i=0;i<2;i++) {
combinedHits[i] = DEBUG_statistics.instructionHits[i];
for(int j=0;j<4096;j++) {
if(combinedHits[i].arm[j] == 0xFFFFFFFF)
continue;
std::string name = arm_instruction_names[j];
for(int k=j+1;k<4096;k++) {
if(combinedHits[i].arm[k] == 0xFFFFFFFF)
continue;
if(name == arm_instruction_names[k]) {
//printf("combining %s with %d and %d\n",name.c_str(),combinedHits[i].arm[j],combinedHits[i].arm[k]);
combinedHits[i].arm[j] += combinedHits[i].arm[k];
combinedHits[i].arm[k] = 0xFFFFFFFF;
}
}
}
for(int j=0;j<1024;j++) {
if(combinedHits[i].thumb[j] == 0xFFFFFFFF)
continue;
std::string name = thumb_instruction_names[j];
for(int k=j+1;k<1024;k++) {
if(combinedHits[i].thumb[k] == 0xFFFFFFFF)
continue;
if(name == thumb_instruction_names[k]) {
//printf("combining %s with %d and %d\n",name.c_str(),combinedHits[i].arm[j],combinedHits[i].arm[k]);
combinedHits[i].thumb[j] += combinedHits[i].thumb[k];
combinedHits[i].thumb[k] = 0xFFFFFFFF;
}
}
}
}
InstructionHits sorts[2];
for(int i=0;i<2;i++) {
for(int j=0;j<4096;j++) sorts[i].arm[j] = j;
for(int j=0;j<1024;j++) sorts[i].thumb[j] = j;
}
std::sort(sorts[0].arm, sorts[0].arm+4096, debugStatsSort<0,0>);
std::sort(sorts[0].thumb, sorts[0].thumb+1024, debugStatsSort<0,1>);
std::sort(sorts[1].arm, sorts[1].arm+4096, debugStatsSort<1,0>);
std::sort(sorts[1].thumb, sorts[1].thumb+1024, debugStatsSort<1,1>);
for(int i=0;i<2;i++) {
printf("Top arm instructions for ARM%d:\n",7+i*2);
for(int j=0;j<10;j++) {
int val = sorts[i].arm[j];
printf("%08d: %s\n", combinedHits[i].arm[val], arm_instruction_names[val]);
}
printf("Top thumb instructions for ARM%d:\n",7+i*2);
for(int j=0;j<10;j++) {
int val = sorts[i].thumb[j];
printf("%08d: %s\n", combinedHits[i].thumb[val], thumb_instruction_names[val]);
}
}
}
void DebugStatistics::printSequencerExecutionCounters()
{
for(int i=0;i<21;i++) printf("%06d ",sequencerExecutionCounters[i]);
printf("\n");
}
void DEBUG_reset()
{
//for now, just enable all debugging in developer builds
#ifdef DEVELOPER
debugFlag = 1;
#endif
DEBUG_Notify = DebugNotify();
DEBUG_statistics = DebugStatistics();
printf("DEBUG_reset: %08X\n",&DebugStatistics::print); //force a reference to this function
}
/*static void DEBUG_dumpMemory_fill(EMUFILE *fp, uint32_t size)
{
static std::vector<uint8_t> buf;
buf.resize(size);
memset(&buf[0],0,size);
fp->fwrite(&buf[0],size);
}*/
void DEBUG_dumpMemory(EMUFILE* fp)
{
fp->fseek(0x000000,SEEK_SET); fp->fwrite(MMU.MAIN_MEM,0x800000); //arm9 main mem (8192K)
fp->fseek(0x900000,SEEK_SET); fp->fwrite(MMU.ARM9_DTCM,0x4000); //arm9 DTCM (16K)
fp->fseek(0xA00000,SEEK_SET); fp->fwrite(MMU.ARM9_ITCM,0x8000); //arm9 ITCM (32K)
fp->fseek(0xB00000,SEEK_SET); fp->fwrite(MMU.ARM9_LCD,0xA4000); //LCD mem 656K
fp->fseek(0xC00000,SEEK_SET); fp->fwrite(MMU.ARM9_VMEM,0x800); //OAM
fp->fseek(0xD00000,SEEK_SET); fp->fwrite(MMU.ARM7_ERAM,0x10000); //arm7 WRAM (64K)
fp->fseek(0xE00000,SEEK_SET); fp->fwrite(MMU.ARM7_WIRAM,0x10000); //arm7 wifi RAM ?
fp->fseek(0xF00000,SEEK_SET); fp->fwrite(MMU.SWIRAM,0x8000); //arm9/arm7 shared WRAM (32KB)
}
//----------------------------------------------------
std::vector<Logger *> Logger::channels;
static void defaultCallback(const Logger& logger, const char * message) {
logger.getOutput() << message;
}
Logger::Logger() {
out = &std::cout;
callback = defaultCallback;
flags = 0;
}
Logger::~Logger() {
for(int i=0;i<(int)channels.size();i++)
delete channels[i];
}
void Logger::vprintf(const char * format, va_list l, const char * file, unsigned int line) {
char buffer[1024];
char * cur = buffer;
if (flags & Logger::FILE) cur += sprintf(cur, "%s:", file);
if (flags & Logger::LINE) cur += sprintf(cur, "%d:", line);
if (flags) cur += sprintf(cur, " ");
::vsnprintf(cur, 1024, format, l);
callback(*this, buffer);
}
void Logger::setOutput(std::ostream * o) {
out = o;
}
void Logger::setCallback(void (*cback)(const Logger& logger, const char * message)) {
callback = cback;
}
void Logger::setFlag(unsigned int flag) {
this->flags = flag;
}
void Logger::fixSize(unsigned int channel) {
while(channel >= channels.size()) {
channels.push_back(new Logger());
}
}
std::ostream& Logger::getOutput() const {
return *out;
}
void Logger::log(unsigned int channel, const char * file, unsigned int line, const char * format, ...) {
fixSize(channel);
va_list l;
va_start(l, format);
channels[channel]->vprintf(format, l, file, line);
va_end(l);
}
void Logger::log(unsigned int channel, const char *, unsigned int, std::ostream& os) {
fixSize(channel);
channels[channel]->setOutput(&os);
}
void Logger::log(unsigned int channel, const char *, unsigned int, unsigned int flag) {
fixSize(channel);
channels[channel]->setFlag(flag);
}
void Logger::log(unsigned int channel, const char *, unsigned int, void (*callback)(const Logger& logger, const char * message)) {
fixSize(channel);
channels[channel]->setCallback(callback);
}
void IdeasLog(armcpu_t* cpu)
{
uint32_t adr = cpu->R[0];
for(;;) {
uint8_t c = _MMU_read08(cpu->proc_ID, MMU_AT_DEBUG, adr);
adr++;
if(!c) break;
printf("%c",c);
}
//don't emit a newline. that is a pain in the butt.
}
void NocashMessage(armcpu_t* cpu)
{
uint32_t adr = cpu->instruct_adr + 6;
std::string todo;
for(;;) {
uint8_t c = _MMU_read08(cpu->proc_ID, MMU_AT_DEBUG, adr);
adr++;
if(!c) break;
todo.push_back(c);
}
//r0,r1,r2,...,r15 show register content (displayed as 32bit Hex number)
//sp,lr,pc alias for r13,r14,r15
//scanline show current scanline number
//frame show total number of frames since coldboot
//totalclks show total number of clock cycles since coldboot
//lastclks show number of cycles since previous lastclks (or zeroclks)
//zeroclks resets the 'lastclks' counter
//this is very inefficiently coded!
char tmp[100];
todo = mass_replace(todo,"%sp%","%r13%");
todo = mass_replace(todo,"%lr%","%r14%");
todo = mass_replace(todo,"%pc%","%r15%");
sprintf(tmp,"%08X",cpu->R[0]); todo = mass_replace(todo,"%r0%",tmp);
sprintf(tmp,"%08X",cpu->R[1]); todo = mass_replace(todo,"%r1%",tmp);
sprintf(tmp,"%08X",cpu->R[2]); todo = mass_replace(todo,"%r2%",tmp);
sprintf(tmp,"%08X",cpu->R[3]); todo = mass_replace(todo,"%r3%",tmp);
sprintf(tmp,"%08X",cpu->R[4]); todo = mass_replace(todo,"%r4%",tmp);
sprintf(tmp,"%08X",cpu->R[5]); todo = mass_replace(todo,"%r5%",tmp);
sprintf(tmp,"%08X",cpu->R[6]); todo = mass_replace(todo,"%r6%",tmp);
sprintf(tmp,"%08X",cpu->R[7]); todo = mass_replace(todo,"%r7%",tmp);
sprintf(tmp,"%08X",cpu->R[8]); todo = mass_replace(todo,"%r8%",tmp);
sprintf(tmp,"%08X",cpu->R[9]); todo = mass_replace(todo,"%r9%",tmp);
sprintf(tmp,"%08X",cpu->R[10]); todo = mass_replace(todo,"%r10%",tmp);
sprintf(tmp,"%08X",cpu->R[11]); todo = mass_replace(todo,"%r11%",tmp);
sprintf(tmp,"%08X",cpu->R[12]); todo = mass_replace(todo,"%r12%",tmp);
sprintf(tmp,"%08X",cpu->R[13]); todo = mass_replace(todo,"%r13%",tmp);
sprintf(tmp,"%08X",cpu->R[14]); todo = mass_replace(todo,"%r14%",tmp);
sprintf(tmp,"%08X",cpu->R[15]); todo = mass_replace(todo,"%r15%",tmp);
sprintf(tmp,"%d",nds.VCount); todo = mass_replace(todo,"%scanline%",tmp);
/*sprintf(tmp,"%d",currFrameCounter); */todo = mass_replace(todo,"%frame%",tmp);
sprintf(tmp,"%lld",nds_timer); todo = mass_replace(todo,"%totalclks%",tmp);
printf("%s",todo.c_str());
}
//-------
DebugNotify DEBUG_Notify;
//enable bits arent being used right now.
//if you want exhaustive logging, move the print before the early return (or comment the early return)
//the intent of this system is to provide a compact dialog box showing which debug notifies have been
//triggered in this frame (with a glowing LED!) and which debug notifies have been triggered EVER
//which can be cleared, like a clip indicator in an audio tool.
//obviously all this isnt implemented yet.
void DebugNotify::NextFrame()
{
#ifdef DEVELOPER
pingBits.reset();
#endif
}
void DebugNotify::ReadBeyondEndOfCart(uint32_t addr, uint32_t romsize)
{
#ifdef DEVELOPER
if(!ping(DEBUG_NOTIFY_READ_BEYOND_END_OF_CART)) return;
INFO("Reading beyond end of cart! ... %08X >= %08X\n",addr,romsize);
#endif
}
bool DebugNotify::ping(EDEBUG_NOTIFY which)
{
bool wasPinged = pingBits[(int)which];
pingBits[(int)which] = true;
return !wasPinged;
}