mirror of https://github.com/stella-emu/stella.git
Updated debugger documentation
git-svn-id: svn://svn.code.sf.net/p/stella/code/trunk@693 8b62c5a3-ac7e-4cc8-8f21-d9a121418aba
This commit is contained in:
parent
c768b9256a
commit
ce7e272547
|
@ -17,7 +17,7 @@ What the debugger can do:
|
||||||
Accumulator value is $7f and the Carry flag is true, no matter where
|
Accumulator value is $7f and the Carry flag is true, no matter where
|
||||||
in the program this happens). Unlike the cond breaks in PCAE, Stella's
|
in the program this happens). Unlike the cond breaks in PCAE, Stella's
|
||||||
are *fast*: the emulation will run at full speed unless you use lots
|
are *fast*: the emulation will run at full speed unless you use lots
|
||||||
of breakif's at the same time.
|
of breakif's at the same time, or have a slow CPU
|
||||||
- Watches - View contents of a location/register before every
|
- Watches - View contents of a location/register before every
|
||||||
debugger prompt.
|
debugger prompt.
|
||||||
- Traps - Like breakpoints, but break on read/write/any access to
|
- Traps - Like breakpoints, but break on read/write/any access to
|
||||||
|
@ -71,12 +71,14 @@ Planned features for Stella 2.0 release:
|
||||||
- Saving the current debugger state to a script file (including
|
- Saving the current debugger state to a script file (including
|
||||||
breakpoints, traps, etc).
|
breakpoints, traps, etc).
|
||||||
- Source-level debugging: if a DASM .lst file is available, we'll show
|
- Source-level debugging: if a DASM .lst file is available, we'll show
|
||||||
the listing in the ROM tab instead of a disassembly.
|
the listing in the ROM tab instead of a disassembly. This is already
|
||||||
|
availabe in a very crude form ("loadlist" and "list" commands).
|
||||||
- Save patched ROM.
|
- Save patched ROM.
|
||||||
- More "special variables" for the expression parser. Currently, you can
|
- More "special variables" for the expression parser. Currently, you can
|
||||||
use all the CPU registers and flags in expressions (e.g. "print a+1" does
|
use all the CPU registers and flags in expressions (e.g. "print a+1" does
|
||||||
what you expect). Need to add TIA, RIOT registers too (so you can say
|
what you expect), and the pseudo-variables "_scan" and "_bank" (which
|
||||||
"breakif _scanline==30" or such).
|
evaluate the the current scanline and bank number). Need to add more TIA,
|
||||||
|
RIOT registers too.
|
||||||
|
|
||||||
Future plans (post 2.0):
|
Future plans (post 2.0):
|
||||||
- Possibly a mini-assembler
|
- Possibly a mini-assembler
|
||||||
|
@ -98,6 +100,18 @@ or the ` key is pressed again. Pressing Ctrl-Tab cycles between tabs
|
||||||
from left to right, and Shift-Ctrl-Tab cycles from right to left.
|
from left to right, and Shift-Ctrl-Tab cycles from right to left.
|
||||||
Pressing Tab cycles between widgets in the current tab.
|
Pressing Tab cycles between widgets in the current tab.
|
||||||
|
|
||||||
|
You can also enter the debugger by giving a breakpoint on the command line:
|
||||||
|
|
||||||
|
; will enter the debugger the first time the instruction at "kernel" runs
|
||||||
|
stella -break kernel mygame.bin
|
||||||
|
|
||||||
|
; $fffc is the 6502/6507 init vector. This command will break and enter the
|
||||||
|
; debugger before the first 6507 instruction runs, so you can debug the
|
||||||
|
; startup code:
|
||||||
|
stella -break "*($fffc)" mygame.bin
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Tabs:
|
Tabs:
|
||||||
|
|
||||||
The top-level user interface uses tabs to select the current debugger
|
The top-level user interface uses tabs to select the current debugger
|
||||||
|
@ -187,7 +201,9 @@ The tabs that are implemented so far:
|
||||||
and your partial name will be completed as far as possible.
|
and your partial name will be completed as far as possible.
|
||||||
|
|
||||||
Tab completion works on all labels: built-in, loaded from a symbol file,
|
Tab completion works on all labels: built-in, loaded from a symbol file,
|
||||||
or set during debugging with the "define" command.
|
or set during debugging with the "define" command. However, it does not
|
||||||
|
yet work on functions defined with the "function" command, nor does it
|
||||||
|
work on filenames.
|
||||||
|
|
||||||
- Expressions
|
- Expressions
|
||||||
|
|
||||||
|
@ -197,9 +213,41 @@ The tabs that are implemented so far:
|
||||||
can be as a hex constant ($ff, $1234), or as complex as
|
can be as a hex constant ($ff, $1234), or as complex as
|
||||||
"the low byte of the 16-bit value located at the address
|
"the low byte of the 16-bit value located at the address
|
||||||
pointed to by the binary number 1010010110100101" (which
|
pointed to by the binary number 1010010110100101" (which
|
||||||
would be "@<%1010010110100101"). You can also use registers
|
would be "@<\1010010110100101"). You can also use registers
|
||||||
and labels in expressions.
|
and labels in expressions.
|
||||||
|
|
||||||
|
You can use arithmetic and boolean operators in expressions. The
|
||||||
|
syntax is very C-like. The operators supported are:
|
||||||
|
|
||||||
|
+ - * / (add, subtract, multiply, divide: 2+2 is 4)
|
||||||
|
% (modulus/remainder: 3%2 is 1)
|
||||||
|
& | ^ ~ (bitwise AND, OR, XOR, NOT: 2&3 is 2)
|
||||||
|
&& || ! (logical AND, OR, NOT: 2&&3 is 1, 2||0 is 0)
|
||||||
|
( ) (parentheses for grouping: (2+2)*3 is 12)
|
||||||
|
* @ (byte and word pointer dereference: *$80 is the byte stored
|
||||||
|
at location $80)
|
||||||
|
[ ] (array-style byte pointer dereference: $80[1] is the byte
|
||||||
|
stored at location ($80+1) or $81)
|
||||||
|
< > (prefix versions: low and high byte. <$abcd is $cd)
|
||||||
|
== < > <= >= !=
|
||||||
|
(comparison: equality, less-than, greater-than, less-or-equals,
|
||||||
|
greater-or-equals, not-equals)
|
||||||
|
<< >> (bit shifts, left and right: 1<<1 is 2, 2>>1 is 1)
|
||||||
|
|
||||||
|
Division by zero is not an error: it results in zero instead.
|
||||||
|
|
||||||
|
None of the operators change the values of their operands. There
|
||||||
|
are no variable-assignment or increment/decrement operators. This
|
||||||
|
may change in the future, which is why we used "==" for equality
|
||||||
|
instead of just "=".
|
||||||
|
|
||||||
|
The bitwise and logical boolean operators are different in that the
|
||||||
|
bitwise operators operate on all the bits of the operand (just like
|
||||||
|
AND, ORA, EOR in 6502 asm), while the logical operators treat their
|
||||||
|
operands as 0 for false, non-zero for true, and return either 0 or 1.
|
||||||
|
So $1234&$5678 results in $1230, whereas $1234&&$5678 results in 1.
|
||||||
|
This is just like C or C++...
|
||||||
|
|
||||||
Like some programming languages, the debugger uses prefixed characters
|
Like some programming languages, the debugger uses prefixed characters
|
||||||
to change the meaning of an expression. The prefixes are:
|
to change the meaning of an expression. The prefixes are:
|
||||||
|
|
||||||
|
@ -209,16 +257,24 @@ The tabs that are implemented so far:
|
||||||
Dereference a byte pointer. "*a" means "the byte at the address that
|
Dereference a byte pointer. "*a" means "the byte at the address that
|
||||||
the A register points to". If A is 255 (hex $ff), the result will be
|
the A register points to". If A is 255 (hex $ff), the result will be
|
||||||
the value currently stored in memory location 255. This operator
|
the value currently stored in memory location 255. This operator
|
||||||
will be very familiar to you if you're a C or C++ programmer.
|
will be very familiar to you if you're a C or C++ programmer. It's
|
||||||
|
equivalent to the PEEK() function in most 8-bit BASICs. Also, the
|
||||||
|
debugger supports array-like byte dereferences: *address can be
|
||||||
|
written as address[0]. *(address+1) can be written as address[1],
|
||||||
|
etc.
|
||||||
|
|
||||||
- "@"
|
- "@"
|
||||||
Dereference a word pointer. This is just like the "*" byte deref,
|
Dereference a pointer to a word. This is just like the "*" byte deref,
|
||||||
except it refers to a 16-bit value, occupying 2 locations, in
|
except it refers to a 16-bit value, occupying 2 locations, in
|
||||||
low-byte-first format (standard for the 6507).
|
low-byte-first format (standard for the 6507).
|
||||||
|
|
||||||
Only one or the other of the "*" and "@" prefixes is allowed in a
|
The following are equivalent:
|
||||||
given expression. If you're going to use a dereference, it needs
|
|
||||||
to come first.
|
@address
|
||||||
|
*address+$100**(address+1)
|
||||||
|
address[0]+#256*address[1]
|
||||||
|
|
||||||
|
(TODO: add (indirect),y and (indirect,x) syntax)
|
||||||
|
|
||||||
- Hi/Lo Byte Prefixes
|
- Hi/Lo Byte Prefixes
|
||||||
|
|
||||||
|
@ -231,12 +287,6 @@ The tabs that are implemented so far:
|
||||||
the Accumulator, this will always result in zero. For 16-bit values,
|
the Accumulator, this will always result in zero. For 16-bit values,
|
||||||
"<$1234" = "$12".
|
"<$1234" = "$12".
|
||||||
|
|
||||||
Only one or the other of the "<" and ">" prefixes is allowed in
|
|
||||||
the same expression. If you're going to use one, it needs to come
|
|
||||||
after the dereference, if there is one. "*<myLabel" is legal,
|
|
||||||
but "<*myLabel" is not (yet). These operators behave just like
|
|
||||||
they do in DASM.
|
|
||||||
|
|
||||||
- Number Base Prefixes
|
- Number Base Prefixes
|
||||||
|
|
||||||
- "#"
|
- "#"
|
||||||
|
@ -245,15 +295,13 @@ The tabs that are implemented so far:
|
||||||
- "$"
|
- "$"
|
||||||
Treat the input as a hex number.
|
Treat the input as a hex number.
|
||||||
|
|
||||||
- "%"
|
- "\"
|
||||||
Treat the input as a binary number.
|
Treat the input as a binary number.
|
||||||
|
|
||||||
These only have meaning when they come before a number, not a
|
These only have meaning when they come before a number, not a
|
||||||
label or a register. "%1010" means 10 decimal. So do "$0a" and
|
label or a register. "\1010" means 10 decimal. So do "$0a" and
|
||||||
"#10". You can only use one of the "#" "$" "%" prefixes per
|
"#10". "a" by itself is always the Accumulator, no matter what
|
||||||
expression. When used, they must come immediately before the
|
the default base is set to.
|
||||||
number in the expression, after any dereference or hi/lo byte
|
|
||||||
operators.
|
|
||||||
|
|
||||||
If you don't specify any number base prefix, the number is
|
If you don't specify any number base prefix, the number is
|
||||||
assumed to be in the default base. When you first start Stella,
|
assumed to be in the default base. When you first start Stella,
|
||||||
|
@ -264,9 +312,6 @@ The tabs that are implemented so far:
|
||||||
command that takes arguments (except the ones that take filenames,
|
command that takes arguments (except the ones that take filenames,
|
||||||
like "loadsym").
|
like "loadsym").
|
||||||
|
|
||||||
(Future versions of the debugger may allow arithmetic and boolean
|
|
||||||
operators in expressions)
|
|
||||||
|
|
||||||
|
|
||||||
- Breakpoints, watches and traps, oh my!
|
- Breakpoints, watches and traps, oh my!
|
||||||
|
|
||||||
|
@ -296,6 +341,72 @@ The tabs that are implemented so far:
|
||||||
You could also use "clearbreaks" to remove all the breakpoints. Also,
|
You could also use "clearbreaks" to remove all the breakpoints. Also,
|
||||||
there is a "listbreaks" command that will list them all.
|
there is a "listbreaks" command that will list them all.
|
||||||
|
|
||||||
|
- Conditional Breaks
|
||||||
|
|
||||||
|
A conditional breakpoint causes the emulator to enter the debugger when
|
||||||
|
some arbitrary condition becomes true. "True" means "not zero" here:
|
||||||
|
"2+2" is considered true because it's not zero. "2-2" is false, because
|
||||||
|
it evaluates to zero. This is exactly how things work in C and lots
|
||||||
|
of other languages, but it might take some getting used to if you've
|
||||||
|
never used such a language.
|
||||||
|
|
||||||
|
Suppose you want to enter the debugger when the Game Reset switch is
|
||||||
|
pressed. Looking at the Stella Programmers' Guide, we see that this
|
||||||
|
switch is read at bit 0 of SWCHB. This bit will be 0 if the switch is
|
||||||
|
pressed, or 1 otherwise.
|
||||||
|
|
||||||
|
To have an expression read the contents of an address, we use the
|
||||||
|
dereference operator "*". Since we're looking at SWCHB, we need
|
||||||
|
"*SWCHB".
|
||||||
|
|
||||||
|
We're only wanting to look at bit 0, so let's mask off all the other
|
||||||
|
bits: "*SWCHB&1". The expression now evaluates to bit 0 of SWCHB. We're
|
||||||
|
almost there: this will be 1 (true) if the switch is NOT pressed. We
|
||||||
|
want to break if it IS pressed...
|
||||||
|
|
||||||
|
So we invert the sense of the test with a logical NOT operator (which
|
||||||
|
is the "!" operator): !(*SWCHB&1). The parentheses are necessary as
|
||||||
|
we want to apply the ! to the result of the &, not just the first term
|
||||||
|
(the "*SWCHB").
|
||||||
|
|
||||||
|
"breakif !(*SWCHB&1)" will do the job for us. However, it's an ugly mess
|
||||||
|
of letters, numbers, and punctuation. We can do a little better:
|
||||||
|
|
||||||
|
"breakif { !(*SWCHB & 1 ) }" is a lot more readable, isn't it? If
|
||||||
|
you're going to use readable expressions with spaces in them,
|
||||||
|
enclose the entire expression in curly braces {}.
|
||||||
|
|
||||||
|
There is one annoyance about this complex expression: once we
|
||||||
|
remove the conditional break with "delbreakif" or "clearbreaks",
|
||||||
|
we'd have to retype it (or search backwards with the up-arrow key)
|
||||||
|
if we wanted to use it again.
|
||||||
|
|
||||||
|
We can avoid this by defining the expression as a function, then using
|
||||||
|
"breakif function_name":
|
||||||
|
|
||||||
|
function gameReset { !(*SWCHB & 1 ) }
|
||||||
|
breakif gameReset
|
||||||
|
|
||||||
|
Now we have a meaningful name for the condition, so we can use it again.
|
||||||
|
Not only that: we can use the function as part of a bigger expression.
|
||||||
|
Suppose we've also defined a gameSelect function that evaluates to true
|
||||||
|
if the Game Select switch is pressed. We want to break when the user
|
||||||
|
presses both Select and Reset:
|
||||||
|
|
||||||
|
breakif { gameReset && gameSelect }
|
||||||
|
|
||||||
|
If you've defined a lot of complex functions, you probably will
|
||||||
|
want to re-use them in future runs of the debugger. You can save
|
||||||
|
all your functions, breakpoints, conditional breaks, and watches
|
||||||
|
with the "save" command. If you name your saved file the same
|
||||||
|
as the ROM filename, it'll be auto-loaded next time you load the
|
||||||
|
same ROM in Stella. The save file is just a plain text file called
|
||||||
|
"filename.stella", so you can edit it and add new functions, etc.
|
||||||
|
|
||||||
|
Conditional breaks appear in "listbreaks", numbered starting from
|
||||||
|
zero. You can remove a cond. break with "delbreakif number", where
|
||||||
|
the number comes from "listbreaks".
|
||||||
|
|
||||||
- Watches
|
- Watches
|
||||||
|
|
||||||
A watch is an expression that gets evaluated and printed before
|
A watch is an expression that gets evaluated and printed before
|
||||||
|
@ -307,10 +418,10 @@ The tabs that are implemented so far:
|
||||||
registers: "watch *y" will show you the contents of the location
|
registers: "watch *y" will show you the contents of the location
|
||||||
pointed to by the Y register, even if the Y register changes.
|
pointed to by the Y register, even if the Y register changes.
|
||||||
|
|
||||||
The watches are numbered 1 to 10. The numbers are printed along with
|
The watches are numbered. The numbers are printed along with the
|
||||||
the watches, so you can tell which is which. To delete a watch use
|
watches, so you can tell which is which. To delete a watch use the
|
||||||
the "delwatch" command with the watch number (1 to 10). You can also
|
"delwatch" command with the watch number (1 to whatever). You can
|
||||||
delete them all with the "clearwatches" command.
|
also delete them all with the "clearwatches" command.
|
||||||
|
|
||||||
Note that there's no real point in watching a label or CPU register
|
Note that there's no real point in watching a label or CPU register
|
||||||
without dereferencing it: Labels are constants, and CPU registers
|
without dereferencing it: Labels are constants, and CPU registers
|
||||||
|
@ -346,6 +457,7 @@ The tabs that are implemented so far:
|
||||||
to set it again: the commands actually toggle the trap on & off. You
|
to set it again: the commands actually toggle the trap on & off. You
|
||||||
can also get rid of all traps at once with the "cleartraps" command.
|
can also get rid of all traps at once with the "cleartraps" command.
|
||||||
|
|
||||||
|
Use "listtraps" to see all enabled traps.
|
||||||
|
|
||||||
- Prompt commands:
|
- Prompt commands:
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue