Watchpoints get a wrong PC during interrupts
Versatile Commodore Emulator
Brought to you by:
blackystardust,
gpz
Activate the monitor and enter:
a 1000 sei
lda #$00
sta $d012
lda #$01
sta $d01a
lda #$00
sta $0314
lda #$30
sta $0315
cli
inc $2000
jmp $1016
a 3000 inc $d019
jmp $ea31
r pc = 1000
watch 2000 if (pc >= $3000)
x
The watchpoint is triggered even though the program counter is below $3000:
#1 (Stop on load 2000) 256/$100, 14/$0e
.C:1016 EE 00 20 INC $2000 - A:30 X:00 Y:0A SP:f0 ..-..I.. 5873630
(C:$ff48) x
#1 (Stop on store 2000) 256/$100, 14/$0e
.C:1016 EE 00 20 INC $2000 - A:30 X:00 Y:0A SP:f0 ..-..I.. 5873630
(C:$ff48) watch
WATCH: 1 C:$2000 (Stop on load store)
Condition: ( PC >= $3000 )
This is annoying when you want to watch memory accesses from a specific range of code.
I just noticed it also affects subroutine jumps:
The monitor thinks that loading the address high byte from $6002 happens when PC == $1003:
the second example at least doesn't seem unexpected to me. Note that any registers used in the condition are "live" - the condition triggers when the expression becomes true. So when an instruction changes/updates PC (like JSR) that will cause this condition to become true mid-instruction (indeed after fetching the 3rd byte)
the first case is a bit odd, it looks like a similar thing, but i wouldn't expect it to be this stable (is it really?)
perhaps (i dont know if this is realistic) some special cases like this can be checked for, and then when the watchpoint hits, it also disasassembles the instruction after the PC changed
is it correct to assume that in this code from
mon_breakpoint_check_checkpoint:instpcis $1003 andloadstorepcis $6000 for the second example? Cant check myself without debugging symbols for WinDbg.If this assumption is correct then maybe it can be special cased with something like this:
I have digged a bit.... first of all, it looks like the code in mon_breakpoint_check_checkpoint() actually works correctly. ultimatively, it is getting the wrong value for the PC via mon_register_get_val().
I have a similar (a bit cleaner) patch locally for the is_loadstore case...
which makes complete sense to me (dummy fetch from return address -1)
does that make sense? any other special case we should check? :)
comitted in r46128, please test
The first case (interrupts) works fine, thanks.
I don't think that discarded dummy fetches should trigger watchpoints (that's only relevant for a handful of registers where some action is taken on access, or is there some other special case?), but even ignoring that, I don't see the dummy fetch from return address - 1 you're referring to. The dummy fetch in RTS is done before pulling the return address from the stack:
I think that'd access $1004 in this case. Are you sure that the load watchpoint isn't triggered by the previous JSR instruction?
$6002 is fetched in cycle 6 while PC is still in the state set in cycle 2. It seems that
mon_breakpoint_check_checkpoint()is called after the new PC is set and the current patch isn't 100% sufficient to fix this case. Maybe the passedlastpcis already wrong?oookay, so this part of RTS description (coming from the ancient 64doc.txt) turned out to be incomplete:
RTS does actually read from PC before incrementing it in cycle 6 (i.e. the operation is the same as in cycle 1) and that's in fact what causes the watchpoint to trigger.
That being said, it still shouldn't trigger. The monitor maintains the
dummyoption which defaults to being disabled:The bug is in
6510dtvcore.c:LOAD(tmp);should be replaced withLOAD_DUMMY(tmp);oh wow, another good catch. actually cycle 2 and 3 are dummy cycles too, will check in detail asap
applied in r46201 (can we close this now?)
cycle 3 is handled correctly in
STACK_PEEK():functionally the same thing as
LOAD_DUMMY:cycle 2 is where things get hairy... cycles 1 & 2 are handled generically in
FETCH_OPCODE():There are two branches where one is optimized, the other is not, and they're functionally different. In the fast case (
if (((int)reg_pc) < bank_limit)) the memory access is direct and bypasses the generic load handlers ((*((uint32_t *)(bank_base + reg_pc)) & 0xffffff);). As a result, cycles 1 & 2 can't possibly trigger any load watchpoints, even ifdummyis on.In the slow case (
else), the loads in cycles 1 & 2 always trigger load watchpoints, even ifdummyis off. This difference between the fast and slow case is observable, for example in these two test cases:the load watchpoints will trigger for the second case but not for the first one :)
The question is not only what's correct, but also what's useful for users. Let's assume this code:
$1000 LDA #$00When a user sets a load watchpoint on $1001, do they really want the watchpoint to trigger when the code at $1000 is executed? From my experience with debugging stuff, what's really useful is catching accesses made externally, i.e. it's meaningful to know that there's
$9500 LDA $1001, but a watchpoint triggered when $1000 is executed would be noise even if valid.I'd personally change all three LOADs in
FETCH_OPCODE()toLOAD_DUMMYfor a better watchpoint experience, but I can see how that could be perceived as incorrect :)