DOH!



First thing today is a correction.  When I posted the following code a couple of days ago, I missed a very important INCR instruction.  This means that the "address of interest" captured as the current value of R7, is the contents of R5 (the return address of the function) + 1!
  ; ------------------------------------------------------------------------------
  ; Common continuation of handling

  L_D4A5:
      MVI     .PSG0.lft_hand, R0      ; wait for both controllers to be
      AND     .PSG0.rgt_hand, R0      ;   released
      CMPI    #$ff, R0                ; 
      BNEQ    L_D4A5                  ; 
                                      ;
      MOVR    R6, R5                  ; copy stack pointer
      SUBI    #$4, R5                 ; look back 4 entries - func return address
      MVI@    R5, R0                  ; load return address
      SDBD                            ; store return address in $d28c, $d28d
      MVII    #$d28c, R5              ; 
      INCR    R0                      ; <<<< DOH! MISSED THE INCREMENT
      MVO@    R0, R5                  ; 
      SWAP    R0, 1                   ; 
      MVO@    R0, R5                  ;
      ...

And this means that instead of the debugger starting our test program like this:
  ; ------------------------------------------------------------------------------
  ; Periodic debug routine

  Debug

      BEGIN                           ;
      CALL    L_D491                  ; call into the debugger
                                      ;
      NOP                             ; <<< DEBUGGER STARTS INSPECTING HERE
      NOP                             ;
      ...

It actually starts like this:
  ; ------------------------------------------------------------------------------
  ; Periodic debug routine

  Debug

      BEGIN                           ;
      CALL    L_D491                  ; call into the debugger
                                      ;
      NOP                             ; 
      NOP                             ; <<< DEBUGGER STARTS INSPECTING HERE
      ...


This was difficult to see in the chain of NOPs I had in the test program, but if we change the first instruction to one with multiple decles the implications become clear:
  ; ------------------------------------------------------------------------------
  ; Periodic debug routine

  Debug

      BEGIN                           ;
      CALL    L_D491                  ; call into the debugger
                                      ;
      MVII    #$a5, R0                ; 
      NOP                             ; 
      ...

This causes some confusion. Notice how the opcode of the first instruction is being interpreted as $A5, a MOVR R4, R5 instruction rather than MVII, in the following screen shot:


This happens because the debugger is skipping over the first decle of the opcode (which encodes MVII) and is finding the operand $a5.

Why am I explaining this in such detail? Well, because this is also the cause of the RETURN crashes. Stupidly, I was holding it wrong.

To correctly call the debugger we have to do something like this:
  ; ------------------------------------------------------------------------------
  ; Periodic debug routine

  Debug

      BEGIN                           ; 
                                      ;
      BEGIN                           ; call into the debugger
      CALL    L_D491                  ; 
      PULR    R5                      ;
                                      ;
      MVII    #$A5, R0                ; <<< DEBUGGER STARTS HERE
      NOP                             ;
      ...

Notice how the call to the debugger is wrapped in its own BEGIN and PULR R5.  This is needed because otherwise the call to the debugger will corrupt the value of R5 seen by the following code.  The code under debug can then follow on when the debugger returns (in this case with the MVII).  And this explains why the debugger adds one to the cached program counter - it is stepping over the PULR R5 instruction, which is technically part of its invocation, rather than the program under test.  Furthermore, missing out the BEGIN, PULR R5 wrapper also causes an off by one error in the debugger's handling of the stack (it is expecting the value of R5 to be the last value on there) and it is this that causes the crash on the processing of RETURN.

So here we are, the next version of the debugger test program seems to work rather better.


We can now test all the control inputs without causing JzIntv to halt. I've also included a JzIntv keyboard hack file to make using the debugger easier.  Unfortunately this totally screws the keyboard set up for actually playing a game, but that is not our focus.  Here is the full list of controls in the format:

Inty controller input (JzIntv hack key) = Meaning
  • Both bottom action buttons (LSHIFT + RSHIFT) = Enter the debugger
  • Any disc (SPACE) = Show the game screen while disc held
  • Any top action (S or RETURN) = Executes a (S)ingle instruction and returns to the debugger
  • Any bottom left action (COMMA or LSHIFT) = Cycle the register focus (yellow highlight) backwards by one
  • Any bottom right action (PERIOD or RSHIFT)  = Cycle the register focus forwards by one
  • Left 0-9 (0-9) = Enter the key value into the least significant digit of the current register, shifting all other digits left
  • Right 1-6 (A-F) = Enter values A-F into the least significant digit of the current register, shifting all other digits left
  • Right 7 (MINUS) = Decrement the current register by 1
  • Right 8 (Z) = Sets the current register to (Z)ero
  • Right 9 (EQUALS) = Increment the current register by 1
  • Right Clear (R) = (R)un (effectively quit the debugger)
  • Right 0 (Y) = Run (effectively quit the debugger)
  • Right Enter (T) = (T)race instructions (i.e. run showing the CPU state - can be interrupted by presssing both bottom action buttons)
  • Left Clear (X) = Trace to the e(X)it of the current function
  • Left Enter (U) = Trace to the next (U)ser (non-EXEC) instruction
As you can see there are some pretty sophisticated commands in there, such as tracing to the end of the subroutine (Left controller Clear) or tracing to the next user instruction (Left controller Enter).  I suspect that one of the two commands that seem to quit the debugger (Right controller Clear and 0) is actually doing "run to a breakpoint", as opposed to an unconstrained "run", ignoring breakpoints.

I think this points us in the next direction of investigation, breakpoints...

Comments