/* 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 "..\..\conddebug.h" #include #include #include 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 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 }