I want to document my current search, mainly for myself. But as I document it anyway – I might as well “make it open” – perhaps others can use it…
Yesterday I had a crash with the new “faulty resistent” Vectorblade. This means I took back all changes, and Vectorblade is in the same state, than about a week ago.
The taking back was more demanding than one would anticipate, since I could not just go back to my old sources – I already changed too much – so in the long run it was easier to just take all “faulty” stuff out manually.
For the faulty handling to be inserted, I had to “free” quite a lot of memory – and I want to put that “new” memory to good use.
Since I am running out of options – I am going back to my NMI handling.
This time – because I have more space available – I chose to “do it right”.
I glued the damn switch to the card – and soldered everything “permanent” – the lose wires were – well lose!
I wrote an NMI handler, that can
- display stack address
- display the bank where the NMI happened
- display all registers
- display the state of VIA
- browse/display memory and give out “hex” values of memory locations (button 1+2)
I had to keep it short due to memory restrictions – but it still took about 400 bytes.
This is the source:
direct $ff if NMI_HANDLER = 1 struct NMI_struct ds ORG_STACK, 2 ds ORG_BANK, 1 ds ORG_VIA_PB, 1 ds ORG_VIA_PA, 1 ds ORG_VIA_DB, 1 ds ORG_VIA_DA, 1 ds ORG_VIA_ACR, 1 ds ORG_VIA_CNTL, 1 ds ORG_VIA_IF, 1 ds ORG_VIA_IE, 1 end struct struct NMI_Stack_struct ds ORG_CC, 1 ds ORG_D, 2 ds ORG_DP, 1 ds ORG_X, 2 ds ORG_Y, 2 ds ORG_U, 2 ds ORG_PC, 2 end struct NMI_RAM = playershotobject_list NMI_STACK = enemyobject_list_end bss org NMI_RAM nmi_struct ds NMI_struct nmi_ypos ds 1 nmi_xpos ds 1 nmi_print_buffer ds 7 nmi_adress_mon ds 2 nmi_tmp ds 1 code NMI_HANDLER_FUNCTION NMI_HANDLER_STACK_DUMMY cmps #NMI_HANDLER_STACK_DUMMY-12 ; minus stored regs length beq bouncy_bouncy sts nmi_struct + ORG_STACK ; game_stack_ptr ;game stack save upon first entry bouncy_bouncy lds #NMI_HANDLER_STACK_DUMMY ldx #60000 bounce_delay leax -1,x bne bounce_delay ldy #NMI_RAM lda VIA_DDR_b ; if $40 set than bit 1 switch is 0 (this is bit 0 of switch) ldb VIA_int_flags ; if $80 set than bit 1 switch is 0 (this is bit 1 of switch) sta ORG_VIA_DB,y stb ORG_VIA_IF,y ; bank in "plain" sight clr ORG_BANK, y bita #$40 bne noBit0 ; if != 0, bit is set, than jump (bit 0 = 0) inc ORG_BANK, y ; bs switch bit 0 = 1 noBit0 bitb #$80 bne noBit1 ; if != 0, bit is set, than jump (bit 1 = 0) inc ORG_BANK, y ; bs switch bit 1 = 1 inc ORG_BANK, y noBit1 ; bit as in "bankswitch bit" 00, 01, 10, 11 (banks) ldd VIA_port_b std ORG_VIA_PB,y lda VIA_DDR_a sta ORG_VIA_DA,y ldd VIA_aux_cntl std ORG_VIA_ACR,y lda VIA_int_enable sta ORG_VIA_IE,y lds #NMI_STACK ; stack frame for nmi handler ldd #$c800 std nmi_adress_mon lda #$30 sta Vec_Text_Width CLR Vec_Music_Flag ; no music is playing ->0 (is placed in rottis! JSR Init_Music_Buf ; shadow regs JSR Do_Sound ; ROM function that does the sound playing, here used to clear all regs jsr Init_VIA nmi_msg JSR Wait_Recal ; Vectrex BIOS recalibration JSR Intensity_5F ; Sets the i JSR Read_Btns ldu #nmi_output_start ldy #nmi_struct bsr outPutFrame ldy ORG_STACK,y bsr outPutFrame lda #16 sta nmi_tmp ldd nmi_adress_mon ldu #nmi_print_buffer jsr dhex_to_uascii ldd # ':'*256 + $80 std ,u+ ldd #$b080 std nmi_ypos ldu #nmi_print_buffer jsr Print_Str_d ldd #$a080-16 std nmi_ypos ldy nmi_adress_mon monLoop lda ,y+ ldu #nmi_print_buffer bsr ahex_to_uascii ldd # ' '*256 + $80 std ,u ldd nmi_ypos addd #16 std nmi_ypos ldu #nmi_print_buffer jsr Print_Str_d dec nmi_tmp bne monLoop lda Vec_Btn_State bita #1 beq noPlus_1 ldd nmi_adress_mon addd #16 std nmi_adress_mon noPlus_1 lda Vec_Btn_State bita #2 beq noMinus_1 ldd nmi_adress_mon subd #16 std nmi_adress_mon noMinus_1 jmp nmi_msg outPutFrame outputContinue lda ,u+ beq nmi_msg1_done deca beq nmi_do_1byte jsr out_2 bra outputContinue nmi_do_1byte jsr out_1 bra outputContinue nmi_msg1_done rts AHEX_TOUASCII macro pshs a lsra lsra lsra lsra adda # '0' cmpa # '9' ble ok1\? adda #( 'A'-'0'-10) ok1\? sta ,u+ lda ,s anda #$f adda # '0' cmpa # '9' ble ok2\? adda #( 'A'-'0'-10) ok2\? sta ,u+ leas 1,s endm BHEX_TOUASCII macro pshs b lsrb lsrb lsrb lsrb addb # '0' cmpb # '9' ble ok3\? addb #( 'A'-'0'-10) ok3\? stb ,u+ ldb ,s andb #$f addb # '0' cmpb # '9' ble ok4\? addb #( 'A'-'0'-10) ok4\? stb ,u+ leas 1,s endm DHEX_TOUASCII macro AHEX_TOUASCII BHEX_TOUASCII endm ahex_to_uascii AHEX_TOUASCII rts out_1 ldd ,u++ pshs d jsr Print_Str_d lda ,u+ pshs u ldu #nmi_print_buffer lda a,y bsr ahex_to_uascii ldd # ' '*256 + $80 sta ,u+ bra entryOut1 out_2 ldd ,u++ pshs d jsr Print_Str_d lda ,u+ pshs u ldu #nmi_print_buffer ldd a,y bsr dhex_to_uascii ldd # ' '*256 + $80 entryOut1 std ,u++ ldd 2,s addd #$30 ldu #nmi_print_buffer jsr Print_Str_d puls u puls d,pc dhex_to_uascii DHEX_TOUASCII rts ; format: ; XX byte count or exit 1,2 or 0 ; y,x pos ; String ; pointer to data that is output (offset to Y reg) nmi_output_start db 2, $70, $a0, "S :", $80, ORG_STACK db 1, $60, $a0, "BS:", $80, ORG_BANK db 1, $40, $10, "PB:", $80, ORG_VIA_PB db 1, $30, $10, "PA:", $80, ORG_VIA_PA db 1, $20, $10, "DB:", $80, ORG_VIA_DB db 1, $10, $10, "DA:", $80, ORG_VIA_DA db 1, $00, $10, "AC:", $80, ORG_VIA_ACR db 1, $f0, $10, "CN:", $80, ORG_VIA_CNTL db 1, $e0, $10, "IF:", $80, ORG_VIA_IF db 1, $d0, $10, "IE:", $80, ORG_VIA_IE db 0 nmi_output_start2 db 1, $40, $a0, "CC:", $80, ORG_CC db 2, $30, $a0, "D :", $80, ORG_D db 1, $20, $a0, "DP:", $80, ORG_DP db 2, $10, $a0, "X :", $80, ORG_X db 2, $00, $a0, "Y :", $80, ORG_Y db 2, $f0, $a0, "U :", $80, ORG_U db 2, $e0, $a0, "PC:", $80, ORG_PC db 0 endif
(The debounce code was Thomas idea – and it works good!)
I played again a couple of hours VB today, to provoke the crash.
First crash that happened – the NMI did not respond. WHAT THE F***!
(corrupt NMI handler address?)
Than I had the game “reset” during play – this never happened before – new bug?
Than finally – I had a crash, where the NMI worked!
The NMI was executed in Bank 3, with PC at: $8429 – which well – first looks sort of “ok”.
All VIA registers look “normal” or at least not suspicous.
AC = $98 is default, CN = $CE means the beam can move. Data direction registers are ok (DB = $9f in this case means (together with the 7bit of IF not set) -> bank 3)… etc
What is slightly suspicious is Stack = $cac4:
The “location” seems to suggest, that S is somewhere “in” an enemy object – that is not so strange, since within the behaviour routines, I reference the enemy data with the stack pointer. What IS strange though, is that I ALLWAYS leave the stack pointer at position 4 of a enemy structure – in above “enemyobjectc” – this should be $cabb.
The stack position is one, that should NOT happen within my program!
The stack is “ok” – I didn’t account in my thoughts (at 1:30 in the night), that the output stack pointer als INCLUDES the 12 bytes stored by the NMI. If I take that into account, the stack actually should read $cad0, which is the propper stack address for an entry to enemyObjectD!.
But which would also suggest, that we actually were able to “reach” enemyObjectD…
Since we are “obviously” near the enemy handling – I chose to investigate further.
Enemies are kept in memory with a “list” structure.
The head of the list is located at $c888 (enemylist_objects_head) – with the NMI handler “browsing” – I investigated that and it pointed to “enemyobjectc” – which is ok, the list addresses get garbeled during execution so it does not start with object 1.
That enemyobject is located at $CACC – again I browsed to that location:
The last 4 bytes shown are the first 4 bytes of enemyobjectc.
This is the corresponding part of the enemyStructure:
So in the above image you see, that enemyC has the position of y=$6d and x= $91 – and that its “behaviour” starts at address $8429.
Wait – WHAT?
$8429 is NOT a behaviour routine of mine!
$8429 is right in the middle of a “shot”-“collision handling” routine.
And since registers X/Y are certainly not set to the right value – the four marked lines constitute an endless loop!
That endless loop, is right where I pressed my NMI button. Somehow the
“stack” and the RAM of my poor enemyC had been corrupted!
- the crash still only occurs on the live system not the emulator
- the crash seems to have to do with memory (RAM) corruption
- those two taken together mean – I still do not have a clue!
What kind of RAM corruption can happen, that is not emulated?
Usually RAM corruption is a a typical coding bug!
to be continued…