Debugger



Introduction


The debugger is a tool for inspecting and manipulating machine instructions and their execution. The debugger window has several components:

  • Execution - a small set of controls for controlling the execution of code.
  • CPU State - display of registers, flags, the stack, cycles and instructions counters, and also the PPU state.
  • Memory disassembly - displays a disassembly of the bytes currently accessible by the CPU data bus.
  • Breakpoints - a list of breakpoints for debugging.
  • Bookmarks - a list of bookmarked addresses for quick navigation.
  • Other - buttons for controlling symbolic debugging, rom patching, etc.



Execution and CPU State


Execution is controlled by the set of buttons at the top-middle of the window. These allow you to break (pause) execution and inspect the current state of the NES.


When an NES ROM is opened, it will be normally be running right away (unless you manually pause the emulator before loading). Most of the debugger window does not update while the game is running. To begin debugging you may click one of the buttons that will break (pause) execution, such as "Step Into".


  • Run - runs the program continuously until the next breakpoint is hit, or the emulator is paused manually. The same effect can be achieved by pressing the Pause hotkey which will unpause emulator when it's paused.
  • Step Into - runs one instruction and then breaks.
  • Step Out - attempt to run until the current subroutine ends with an RTS; in some cases will behave the same as Run.
  • Step Over - runs one instruction, unless it is a JSR instruction, which will run until its RTS.
  • Run Line - runs one scanline before breaking.
  • 128 Lines - runs 128 scanlines before breaking.


The Pause hotkey will break execution or resume it. The Frame Advance hotkey will run the emulator for one frame and then break.


When execution is paused, the disassembly view will begin with the memory near the current program counter location (PC). The ">" mark shows the line which will be executed next. You can scroll the disassembly up or down (using scrollbar or mouse wheel) to observe the code. Then you can click "Seek PC" to return to the program counter at any time.


You can also use "Seek To" button that will navigate to the specified address. Either type a hexadecimal address to the text field or simply left-click on any address in the Disassembly window.


HINT: When entering the address manually, these convenient strings may be used instead of the hexadecimal memory address:

NES special addresses:

  • NMI/VBL - non-maskable interrupt vector (at FFFA)
  • RST     - reset vector (at FFFC)
  • IRQ     - interrupt vector (at FFFE)

FDS special addresses:

  • NMI1 - non-maskable interrupt vector (at DFF6)
  • NMI2 - non-maskable interrupt vector (at DFF8)
  • NMI3 - non-maskable interrupt vector (at DFFA)
  • RST  - reset vector (at DFFC)
  • IRQ  - interrupt vector (at DFFE)

NSF special addresses:

  • LOAD - NSF LOAD address
  • INIT - NSF INIT address
  • PLAY - NSF PLAY address


While execution is broken (emulation is paused), the program counter (PC) can be edited, as well as the three registers A/X/Y, and the status flags. Normally they should be left as-is, but changing them at runtime can be useful for more advanced debugging.


The contents of memory starting at the stack pointer (somewhere in the range $0100-01FF) is displayed in the Stack frame below the A/X/Y registers.


The current PPU memory address, sprite memory address, scanline, and rendering pixel are displayed below the stack and status flags. Examples of Scanline number: -1 means Prerender time, 240 is Idle scanline, 0-239 are visible scanlines, 241-260/310 are VBlank scanlines.


To the right from the PPU section there's Cycles counter and Instructions counter that keep counting while the game is running. You can use the information for keeping statistics, for code profiling or writing PPU-synchronized code (e.g. raster effects). You can also make the debugger break automatically based on the counters values. The "Reset counters" button resets both counters to 0. You can also access the counters via Lua.



Disassembly


This large frame takes up the left side of the debugger window. It displays the current contents of memory accessible by the CPU with an automatic disassembly of that data into assembly instructions. The following memory ranges may contain useful data for inspection:

  • 0000-00FF - zero page (RAM)
  • 0100-01FF - stack (RAM)
  • 0200-07FF - RAM
  • 4018-FFFF - mapper controlled (ROM or RAM, may be bankswitched)


Memory contents are displayed in this form:


0F:C0A8:24 1F     BIT $001F = #$80

bb:mmmm:dd dd dd  iiiiiiiiiiiii...


  • bb - 16k iNES bank, designates which 16k bank from the iNES file is mapped here. Note that the number may be not the same as the actual hardware bank of the mapper.
  • mmmm - physical address on the NES CPU data bus.
  • dd - data bytes belonging to the instruction beginning at this address.
  • iiii - assembly description of the instruction, possibly with symbolic decoration.


When debugging an NSF program, the bank designation will be a 4k NSF bank instead of the 16k iNES bank.


A single instruction may be one to three bytes, and will all appear on the line before the assembly code description of that instruction. An instruction with "= #$xx" at the end conveniently indicates the value currently in memory at the address referenced by the instruction.


Hovering the mouse over the disassembly will display at the bottom of the window more detailed information about the location of this code in the iNES file.


There is narrow column to the left of the Disassembly window. Left clicking on this column will open the Inline Assembler, which allows you to patch the ROM at runtime. Right clicking on this column will open the Hex Editor, which allows you to directly edit the ROM. Middle-clicking on this column will bring up the Game Genie Encoder at that address, so you can easily make Game Genie codes.

Also, when Code/Data Logger is running, this small column displays whether the respective line of the disassembled memory was executed ("c") or it was read as Data ("d"), or it wasn't logged yet (empty space). This way you can easily distinguish which branches of the game code were executed and which weren't.



Symbolic Debugging


FCEUX allows you to label any address of RAM or ROM with a human-readable symbolic name.

For example, when you've figured out that at the address $C022 there's a subroutine which refills player HP, you can right-click the address and type a name like "AddHealthpoints". You can also add a comment, which will be seen while browsing the code near this address. From now on, the address will be substituted by the name everywhere - in all instructions referencing this address, in Hex Editor window, in the log produced by Trace Logger. E.g., JSR $C022 will look like JSR AddHealthpoints.


You can also label a series of adresses as an array by specifiying the array size, every address label in range will be followed with an array index. For example, if you label $C000 with name "ArrayName", and set the size to 0xF, then $C000 is named "ArrayName[0]", $C001 is "ArrayName[1]"... and so on, the last labeled address is $C00E with name "ArrayName[E]". The start index number can be manually set to a value other than zero.

A name must be specified in array labeling, or the debugger only show the comment of the addresses.


When entering the name, you can use any symbols except #. It's also recommended to avoid whitespaces in names.

To rename an address, just right-click the name.

Note that each label is individual, when you rename one of the labels in an array, the other label won't change simutaneously. To change all the names in an array, you should edit the name of the first element in the array, specify the array size and check "Overwrite names in array body".


To delete a label, check the "Delete" checkbox and click OK.

The array size specified in delete mode indicates labels of how many bytes will be deleted. If you select $C000 and set array size to 0xF, all the labels form $C000 to $C00E are deleted. 


The data for Symbolic Debugging is stored in NL files in the same folder as the ROM. You can edit the files in any text editor (to reload all NL files of the currently active ROM file press the "Reload Symbols" button), but it's more convenient to use right-clicks.


You can enable and disable symbolic debugging by clicking the checkbox "Symbolic debug" in the lower right corner. In general, there's no need to disable this feature. If you need to see the actual address which got substituted by a name, you can simply left-click the name and watch its address in the "Seek To" text field. This also works when clicking a name in the Trace Logger window.



Breakpoints


Breakpoints will automatically break execution when chosen conditions are met. To create a breakpoint, click the Add button in the Breakpoints frame in the upper right corner of the debugger.


Each breakpoint has an address range to watch. Use only the left address field if you wish to watch a single byte address. When entering the address of a breakpoint, you can also use the aforementioned convenient strings (such as IRQ) instead of hexadecimal memory addresses.


Check one or more of the options to watch for Read, Write, or Execute at the given address. Note that fetching of code from an address will not break as a Read; so use the Execute box for this case. Breakpoints can be given a name that will appear in the breakpoints list. The condition field can be used to break only on particular conditions; see "Conditional Breakpoints" below.


Double click on a breakpoint in the Breakpoints list to quickly disable or enable this breakpoint. So you don't have to delete breakpoints to stop them from causing the debugger to halt the game.


A special kind of breakpoints with the "Forbid" option will prevent any breakpoints from occurring within the specified memory address range. This can be enabled and disabled like other breakpoints.


A quicker way to add PC breakpoints is to double click on any address in the Disassembly when you want to set the breakpoint to that address. Example: when you need to quickly advance emulation to a given line of code, double-click on the address part of the line, and the "Add Execute breakpoint here" dialog will appear, just click "OK" and then hit "Run", Debugger will break at this line of code.


There is also an option to Break on Bad Opcodes, which will halt execution if a bad instruction opcode is reached.


Finally, you can make the debugger break after executing a certain number of instructions or CPU cycles.


More advanced breakpoints conditions and full automation may be achieved through Lua script breakpoints. See the Lua reference for more information.


Breakpoints are listed in the following form:


aaaa EmRWXF nnnn cccc

or

aaaa-aaaa EmRWXF nnnn cccc


  • aaaa - address of breakpoint
  • E    - enabled
  • m    - memory area: C = CPU, P = PPU, S = sprite
  • R    - read
  • W    - write
  • X    - execute
  • F    - Forbid
  • nnnn - (optional) name of breakpoint
  • nnnn - (optional) condition of breakpoint



Conditional Breakpoints


Breakpoints may also have a conditional statement that causes them to execute only if that statement evaluates to true. The conditional breakpoint grammar has this form:


  • Connect   -> Compare { ('||' | '&&') Compare }
  • Compare   -> Sum { ('==' | '!=' | '<=' | '>=' | '<' | '>') Sum }
  • Sum       -> Product { ('+' | '-') Product }
  • Product   -> Primitive { ('*' | '/') Primitive }
  • Primitive -> Number | Address | Register | Flag | PC Bank | Data Bank | '(' Connect ')'
  • Number    -> '#' [0123456789ABCDEF]*
  • Address   -> '$' [0123456789ABCDEF]* | '$' '[' Connect ']'
  • Register  -> 'A' | 'X' | 'Y' | 'P'
  • Flag      -> 'N' | 'C' | 'Z' | 'I' | 'B' | 'V' | 'D' | 'U'
  • PC Bank   -> 'K'
  • Data Bank   -> 'T'
  • Read/Write Value   -> 'R' | 'W'


The parser is very strict. All numbers are hexadecimal. Always prefix a number with # for an immediate value, or $ for a memory address. If a memory address needs to be calculated use $[] with the calculation inside the brackets.


Registers A/X/Y are 8-bit unsigned values. Register P is the 16-bit program counter.

Flags evaluate to 1 if set, 0 if clear. (U is the unused bit of the status register, and D is the unused decimal flag.)

For instructions that read or write a single byte (e.g. LDA, STY, PHA, ASL abs), condition R evaluates to the value that will be read by the instruction, and condition W evaluates to the value that will be written.


Connecting operators || or && combine boolean terms. Parentheses dictate order of operations.


Example conditions:


Break only if register A is less than value at memory address $0005:

A < $0005


Break only if the value at the indirect address is not equal to FF:

#FF != $[$10+($11*#100)]


Break only if flag N is clear or A is not equal to 00:

(N==#0 || A!=#0)


Break only when accessing a data from bank 2 (the condiition is relevant when using with Read/Write-type breakpoints):

T==#2



Bookmarks


A list of bookmarked addresses can be kept in the Address Bookmarks frame to make memory navigation easier. Simply type a hexadecimal address (or a convenient string, such as "NMI") and click "Add" to add it to your bookmarks. Alternatively, just left-click any address in the Disassembly window, and the address will appear in the Bookmark Add field, so you don't have to type it.

Next time you wish to go to this address just double click on the bookmark.

You can also name bookmarks.

When you exit the emulator, bookmarks are saved in a .deb file named after the ROM of the debugged game. Next time you return to debugging the game, the list of bookmarks will be automatically loaded from the file.



Inline Assembler


Open the inline assembler by left-clicking in the empty column to the left of the memory view.


The starting memory address is displayed in the PC field at the top of the inline assembler window. Type a line of assembly to add in the empty field just below this, and hit enter. The assembled code of your patch will appear below as you enter each line.


Click Apply to apply your patch to the ROM in memory. Click Undo to remove the last assembled line. After applying a patch, clicking Save will allow you to save this patch directly to the ROM file.



Other


If the ".DEB files" checkbox in the lower right corner of the debugger window is checked, the emulator will automatically save debug settings such as breakpoints and bookmarks in a .deb file alongside the NES ROM, and load these settings next time you open the ROM.


There is a "Rom Patcher" button that may be used to apply a small patch to a ROM, although Hex Editor is more convenient in general.


The "ROM offsets" option will display ROM offsets instead of CPU addresses in the Disassembly window.


The "Restore Original Window Size" button will restore the original size of the debugger window if you resized it manually.


The "Auto-open" checkbox causes the debugger window to open automatically whenever an NES ROM is opened.





Created with the Personal Edition of HelpNDoc: Easily create HTML Help documents