fceux/drivers/win/debuggersp.cpp

876 lines
19 KiB
C++

/* FCEUXD SP - NES/Famicom Emulator
*
* Copyright notice for this file:
* Copyright (C) 2005 Sebastian Porst
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "common.h"
#include "debuggersp.h"
#include "debugger.h"
#include "../../debug.h"
#include "../../conddebug.h"
#include <stdio.h>
#include <string.h>
#include <assert.h>
int GetNesFileAddress(int A);
Name* lastBankNames = 0;
Name* loadedBankNames = 0;
Name* ramBankNames = 0;
int lastBank = -1;
int loadedBank = -1;
extern char LoadedRomFName[2048];
char symbDebugEnabled = 0;
int debuggerWasActive = 0;
/**
* Tests whether a char is a valid hexadecimal character.
*
* @param c The char to test
* @return True or false.
**/
int isHex(char c)
{
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
}
/**
* Replaces all occurences of a substring in a string with a new string.
* The maximum size of the string after replacing is 1000.
* The caller must ensure that the src buffer is large enough.
*
* @param src Source string
* @param r String to replace
* @param w New string
**/
void replaceString(char* src, const char* r, const char* w)
{
char buff[1001] = {0};
char* pos = src;
char* beg = src;
// Check parameters
if (!src || !r || !w)
{
MessageBox(0, "Error: Invalid parameter in function replaceString", "Error", MB_OK | MB_ICONERROR);
return;
}
// Replace sub strings
while ((pos = strstr(src, r)))
{
*pos = 0;
strcat(buff, src);
strcat(buff, w ? w : r);
src = pos + strlen(r);
}
strcat(buff, src);
strcpy(beg, buff);
}
/**
* Returns the bank for a given offset.
* Technically speaking this function does not calculate the actual bank
* where the offset resides but the 0x4000 bytes large chunk of the ROM of the offset.
*
* @param offs The offset
* @return The bank of that offset or -1 if the offset is not part of the ROM.
**/
int getBank(int offs)
{
int addr = 0; //MBG TODO GetNesFileAddress(offs);
return addr != -1 ? addr / 0x4000 : -1;
}
/**
* Parses a line from a NL file.
* @param line The line to parse
* @param n The name structure to write the information to
*
* @return 0 if everything went OK. Otherwise an error code is returned.
**/
int parseLine(char* line, Name* n)
{
char* pos;
int llen;
// Check parameters
if (!line)
{
MessageBox(0, "Invalid parameter \"line\" in function parseLine", "Error", MB_OK | MB_ICONERROR);
return 1;
}
if (!n)
{
MessageBox(0, "Invalid parameter \"n\" in function parseLine", "Error", MB_OK | MB_ICONERROR);
return 2;
}
// Allow empty lines
if (*line == '\r' || *line == '\n')
{
return -1;
}
// Attempt to tokenize the given line
pos = strstr(line, "#");
if (!pos)
{
// Found an invalid line
return 3;
}
// Check if the first tokenized part (the offset) is valid
*pos = 0;
llen = (int)strlen(line);
if (llen == 5) // Offset size of normal lines of the form $XXXX
{
if (line[0] != '$'
|| !isHex(line[1])
|| !isHex(line[2])
|| !isHex(line[3])
|| !isHex(line[4])
)
{
return 4;
}
}
else if (llen >= 7) // Offset size of array definition lines of the form $XXXX/YY
{
int i;
if (line[0] != '$'
|| !isHex(line[1])
|| !isHex(line[2])
|| !isHex(line[3])
|| !isHex(line[4])
|| line[5] != '/'
)
{
return 5;
}
for (i=6;line[i];i++)
{
if (!isHex(line[i]))
{
return 6;
}
}
}
else // Lines that have an invalid size
{
return 7;
}
// TODO: Validate if the offset is in the correct NL file.
// After validating the offset it's OK to store it
n->offset = (char*)malloc(strlen(line) + 1);
strcpy(n->offset, line);
line = pos + 1;
// Attempt to tokenize the string again to find the name of the address
pos = strstr(line, "#");
if (!pos)
{
// Found an invalid line
return 8;
}
*pos = 0;
// The only requirement for a name of an address is that it's not of size 0
if (*line)
{
n->name = (char*)malloc(strlen(line) + 1);
strcpy(n->name, line);
}
else
{
// Properly initialize zero-length names too
n->name = 0;
}
// Now it's clear that the line was valid. The rest of the line is the comment
// that belongs to that line. Once again a real string is required though.
line = pos + 1;
if (*line > 0x0D)
{
n->comment = (char*)malloc(strlen(line) + 1);
strcpy(n->comment, line);
}
else
{
// Properly initialize zero-length comments too
n->comment = 0;
}
// Everything went fine.
return 0;
}
/**
* Parses an array of lines read from a NL file.
* @param lines The lines to parse
* @param filename The name of the file the lines were read from.
*
* @return A pointer to the first Name structure built from the parsed lines.
**/
Name* parse(char* lines, const char* filename)
{
char* pos, *size;
Name* prev = 0, *cur, *first = 0;
// Check the parameters
if (!lines)
{
MessageBox(0, "Invalid parameter \"lines\" in function parse", "Error", MB_OK | MB_ICONERROR);
return 0;
}
if (!filename)
{
MessageBox(0, "Invalid parameter \"filename\" in function parse", "Error", MB_OK | MB_ICONERROR);
return 0;
}
// Begin the actual parsing
do
{
int fail;
// Allocate a name structure to hold the parsed data from the next line
cur = (Name*)malloc(sizeof(Name));
cur->offset = 0;
cur->next = 0;
cur->name = 0;
cur->comment = 0;
pos = lines;
// This first loop attempts to read potential multi-line comments and add them
// into a single comment.
for(;;)
{
// Get the end of the next line
pos = strstr(pos, "\n");
// If there's no end of line or if the next line does not begin with a \ character
// we can stop.
if (!pos || pos[1] != '\\') break;
// At this point we have the following situation. pos[0] and pos[1] can be overwritten
/*
pos -1 0 1 2
? \n \ ?
*/
// \r\n is needed in text boxes
if (pos[-1] != '\r')
{
pos[0] = '\r';
pos[1] = '\n';
pos += 2;
}
else
{
// Remove backslash
pos[1] = ' ';
pos += 1;
}
}
if (!pos)
{
// All lines were parsed
break;
}
*pos = 0;
// Attempt to parse the current line
fail = parseLine(lines, cur);
if (fail == -1)
{
continue;
}
else if (fail) // Show an error to allow the user to correct the defect line
{
const char* fmtString = "Error (Code: %d): Invalid line \"%s\" in NL file \"%s\"";
char* msg = (char*)malloc(strlen(fmtString) + 8 + strlen(lines) + strlen(filename) + 1);
sprintf(msg, fmtString, fail, lines, filename);
MessageBox(0, msg, "Error", MB_OK | MB_ICONERROR);
free(msg);
lines = pos + 1;
MessageBox(0, lines, "Error", MB_OK | MB_ICONERROR);
continue;
}
lines = pos + 1;
// Check if the line is an array definition line
size = strstr(cur->offset, "/");
if (size) // Array definition line
{
int arrlen, offset;
*size = 0;
// Attempt to read the length of the array and the array offset
if (sscanf(size + 1, "%x", &arrlen) > 0 && sscanf(cur->offset + 1, "%x", &offset) > 0)
{
Name* nn = 0;
int i;
// Create a node for each element of the array
for (i=0;i<=arrlen;i++)
{
char numbuff[10] = {0};
nn = (Name*)malloc(sizeof(Name));
nn->next = 0;
// The comment is the same for each array element
nn->comment = strdup(cur->comment);
// The offset of the node
nn->offset = (char*)malloc(10);
sprintf(nn->offset, "$%04X", offset + i);
// The name of an array address is of the form NAME[INDEX]
sprintf(numbuff, "[%X]", i);
nn->name = (char*)malloc(strlen(cur->name) + strlen(numbuff) + 1);
strcpy(nn->name, cur->name);
strcat(nn->name, numbuff);
// Add the new node to the list of address nodes
if (prev)
{
prev->next = nn;
prev = prev->next;
}
else
{
first = prev = nn;
}
}
// Free the allocated node
free(cur->name);
free(cur->comment);
free(cur->offset);
free(cur);
cur = nn;
}
else
{
// I don't think it's possible to get here as the validity of
// offset and array size has already been validated in parseLine
continue;
}
}
else
{
// Add the node to the list of address nodes
if (prev)
{
prev->next = cur;
prev = prev->next;
}
else
{
first = prev = cur;
}
}
} while (pos);
// Return the first node in the list of address nodes
return first;
}
/**
* Load and parse an entire NL file
* @param filename Name of the file to parse
*
* @return A pointer to the first Name structure built from the file data.
**/
Name* parseNameFile(const char* filename)
{
char* buffer;
Name* n = 0;
// Attempt to read the file
FILE* f = fopen(filename, "rb");
if (f)
{
// __asm (".byte 0xcc");
// Get the file size
int lSize;
fseek (f , 0 , SEEK_END);
lSize = ftell(f) + 1;
// Allocate sufficient buffer space
rewind (f);
buffer = (char*)malloc(lSize);
if (buffer)
{
// Read the file and parse it
memset(buffer, 0, lSize);
fread(buffer, 1, lSize - 1, f);
n = parse(buffer, filename);
fclose(f);
free(buffer);
}
}
return n;
}
/**
* Frees an entire list of Name nodes starting with the given node.
*
* @param n The node to start with (0 is a valid value)
**/
void freeList(Name* n)
{
Name* next;
while (n)
{
if (n->offset) free(n->offset);
if (n->name) free(n->name);
if (n->comment) free(n->comment);
next = n->next;
free(n);
n = next;
}
}
/**
* Replaces all offsets in a string with the names that were given to those offsets
* The caller needs to make sure that str is large enough.
*
* @param list NL list of address definitions
* @param str The string where replacing takes place.
**/
void replaceNames(Name* list, char* str)
{
Name* beg = list;
if (!str)
{
MessageBox(0, "Error: Invalid parameter \"str\" in function replaceNames", "Error", MB_OK | MB_ICONERROR);
return;
}
while (beg)
{
if (beg->name)
{
replaceString(str, beg->offset, beg->name);
}
beg = beg->next;
}
}
/**
* Searches an address node in a list of address nodes. The found node
* has the same offset as the passed parameter offs.
*
* @param node The address node list
* @offs The offset to search
* @return The node that has the given offset or 0.
**/
Name* searchNode(Name* node, const char* offs)
{
while (node)
{
if (!strcmp(node->offset, offs))
{
return node;
}
node = node->next;
}
return 0;
}
/**
* Loads the necessary NL files
**/
void loadNameFiles()
{
int cb;
char* fn = (char*)malloc(strlen(LoadedRomFName) + 20);
if (ramBankNames)
free(ramBankNames);
// The NL file for the RAM addresses has the name nesrom.nes.ram.nl
strcpy(fn, LoadedRomFName);
strcat(fn, ".ram.nl");
// Load the address descriptions for the RAM addresses
ramBankNames = parseNameFile(fn);
free(fn);
// Find out which bank is loaded at 0xC000
cb = getBank(0xC000);
if (cb == -1) // No bank was loaded at that offset
{
free(lastBankNames);
lastBankNames = 0;
}
else if (cb != lastBank)
{
char* fn = (char*)malloc(strlen(LoadedRomFName) + 12);
// If the bank changed since loading the NL files the last time it's necessary
// to load the address descriptions of the new bank.
lastBank = cb;
// Get the name of the NL file
sprintf(fn, "%s.%X.nl", LoadedRomFName, lastBank);
if (lastBankNames)
freeList(lastBankNames);
// Load new address definitions
lastBankNames = parseNameFile(fn);
free(fn);
}
// Find out which bank is loaded at 0x8000
cb = getBank(0x8000);
if (cb == -1) // No bank is loaded at that offset
{
free(loadedBankNames);
loadedBankNames = 0;
}
else if (cb != loadedBank)
{
char* fn = (char*)malloc(strlen(LoadedRomFName) + 12);
// If the bank changed since loading the NL files the last time it's necessary
// to load the address descriptions of the new bank.
loadedBank = cb;
// Get the name of the NL file
sprintf(fn, "%s.%X.nl", LoadedRomFName, loadedBank);
if (loadedBankNames)
freeList(loadedBankNames);
// Load new address definitions
loadedBankNames = parseNameFile(fn);
free(fn);
}
}
/**
* Adds label and comment to an offset in the disassembly output string
*
* @param addr Address of the currently processed line
* @param str Disassembly output string
* @param chr Address in string format
* @param decorate Flag that indicates whether label and comment should actually be added
**/
void decorateAddress(unsigned int addr, char* str, const char* chr, UINT decorate)
{
if (decorate)
{
Name* n;
if (addr < 0x8000)
{
// Search address definition node for a RAM address
n = searchNode(ramBankNames, chr);
}
else
{
// Search address definition node for a ROM address
n = addr >= 0xC000 ? searchNode(lastBankNames, chr) : searchNode(loadedBankNames, chr);
}
// If a node was found there's a name or comment to add do so
if (n && (n->name || n->comment))
{
// Add name
if (n->name && *n->name)
{
strcat(str, "Name: ");
strcat(str, n->name);
strcat(str,"\r\n");
}
// Add comment
if (n->comment && *n->comment)
{
strcat(str, "Comment: ");
strcat(str, n->comment);
strcat(str, "\r\n");
}
}
}
// Add address
strcat(str, chr);
strcat(str, ":");
}
/**
* Checks whether a breakpoint condition is syntactically valid
* and creates a breakpoint condition object if everything's OK.
*
* @param condition Condition to parse
* @param num Number of the breakpoint in the BP list the condition belongs to
* @return 0 in case of an error; 2 if everything went fine
**/
int checkCondition(char* condition, int num)
{
char* b = condition;
// Check if the condition isn't just all spaces.
int onlySpaces = 1;
while (*b)
{
if (*b != ' ')
{
onlySpaces = 0;
break;
}
++b;
}
// Remove the old breakpoint condition before
// adding a new condition.
if (watchpoint[num].cond)
{
freeTree(watchpoint[num].cond);
free(watchpoint[num].condText);
watchpoint[num].cond = 0;
watchpoint[num].condText = 0;
}
// If there's an actual condition create the BP condition object now
if (*condition && !onlySpaces)
{
Condition* c = generateCondition(condition);
// If the creation of the BP condition object was succesful
// the condition is apparently valid. It can be added to the
// breakpoint now.
if (c)
{
watchpoint[num].cond = c;
watchpoint[num].condText = (char*)malloc(strlen(condition) + 1);
strcpy(watchpoint[num].condText, condition);
}
else
{
watchpoint[num].cond = 0;
}
return watchpoint[num].cond == 0 ? 2 : 0;
}
else
{
return 0;
}
}
/**
* Returns the bookmark address of a CPU bookmark identified by its index.
* The caller must make sure that the index is valid.
*
* @param hwnd HWND of the debugger window
* @param index Index of the bookmark
**/
unsigned int getBookmarkAddress(HWND hwnd, unsigned int index)
{
int n;
char buffer[5] = {0};
SendDlgItemMessage(hwnd, 701, LB_GETTEXT, index, (LPARAM)buffer);
sscanf(buffer, "%x", &n);
return n;
}
unsigned int bookmarks;
unsigned short* bookmarkData = 0;
/**
* Stores all CPU bookmarks in a simple array to be able to store
* them between debugging sessions.
*
* @param hwnd HWND of the debugger window.
**/
void dumpBookmarks(HWND hwnd)
{
unsigned int i;
if (bookmarkData)
free(bookmarkData);
bookmarks = SendDlgItemMessage(hwnd, 701, LB_GETCOUNT, 0, 0);
bookmarkData = (unsigned short*)malloc(bookmarks * sizeof(unsigned short));
for (i=0;i<bookmarks;i++)
{
bookmarkData[i] = getBookmarkAddress(hwnd, i);
}
}
/**
* Adds a debugger bookmark to the list on the debugger window.
*
* @param hwnd HWMD of the debugger window
* @param buffer Text of the debugger bookmark
**/
void AddDebuggerBookmark2(HWND hwnd, char* buffer)
{
if (!buffer)
{
MessageBox(0, "Error: Invalid parameter \"buffer\" in function AddDebuggerBookmark2", "Error", MB_OK | MB_ICONERROR);
return;
}
SendDlgItemMessage(hwnd, 701, LB_ADDSTRING, 0, (LPARAM)buffer);
dumpBookmarks(hwnd);
}
/**
* Takes the offset from the debugger bookmark edit field and adds a debugger
* bookmark with that offset to the bookmark list if the offset is valid.
*
* @param hwnd HWMD of the debugger window
**/
void AddDebuggerBookmark(HWND hwnd)
{
int result;
unsigned int n;
char buffer[5] = {0};
GetDlgItemText(hwnd, 312, buffer, 5);
result = sscanf(buffer, "%x", &n);
// Make sure the offset is valid
if (result != 1 || n > 0xFFFF)
{
MessageBox(hwnd, "Invalid offset", "Error", MB_OK | MB_ICONERROR);
return;
}
AddDebuggerBookmark2(hwnd, buffer);
}
/**
* Removes a debugger bookmark
*
* @param hwnd HWND of the debugger window
**/
void DeleteDebuggerBookmark(HWND hwnd)
{
// Get the selected bookmark
int selectedItem = SendDlgItemMessage(hwnd, 701, LB_GETCURSEL, 0, 0);
if (selectedItem == LB_ERR)
{
MessageBox(hwnd, "Please select a bookmark from the list", "Error", MB_OK | MB_ICONERROR);
return;
}
else
{
// Remove the selected bookmark
SendDlgItemMessage(hwnd, 701, LB_DELETESTRING, selectedItem, 0);
dumpBookmarks(hwnd);
}
}
void Disassemble(HWND hWnd, int id, int scrollid, unsigned int addr);
/**
* Shows the code at the bookmark address in the disassembly window
*
* @param hwnd HWND of the debugger window
**/
void GoToDebuggerBookmark(HWND hwnd)
{
unsigned int n;
int selectedItem = SendDlgItemMessage(hwnd, 701, LB_GETCURSEL, 0, 0);
// If no bookmark is selected just return
if (selectedItem == LB_ERR) return;
n = getBookmarkAddress(hwnd, selectedItem);
//Disassemble(hwnd, 300, 301, n);
//MBG TODO
}