Recently I ran into a quite strange bug which I will try to describe
below, hoping that somebody will be able to explain it to me.
I started developing a procedure which displays a red blinking cursor
in SCREEN 12, hooking up interrupt &H1C. (I used the code posted in
the source code section of this forum which connects an interrupt
to PB code.)
The procedure itself worked fine, but the problem started when I
combined it with a procedure which accepts string parameters. Under
Windows98, the program shuts down and I get a message: "This program
attempted to execute an invalid instruction." In DOS mode, the system
freezes and I have to reboot.
When tracing the program line by line, the error ocurred upon the
very last "END SUB" statement executing. The alert shown by Windows
indicated as error location the address 0001:6E0C. The offset value
(6E0C) was exactly the value of the CS register in the PB watch
window when debugging the program. This causes me to think that
something in this code is altering the stack, so that the segment
value is popped off in place of the offset value when returning from
the SUB. (This is only my assumption; I do not have enough knowledge
to find out what exactly caused the error.)
I tried to isolate the bug in a relatively small amount of code,
which follows below. (This code does not do something very
meaningful apart from reproducing the mentioned error; the original
program was much longer of course.)
There was NO ERROR when I made ONLY ONE out of the following changes:
- when leaving out the "csrshow" call in SUB "stringtest"
- when leaving out the whole SELECT CASE block in SUB "stringtest".
- when leaving out any single other line in this SUB, except
"FOR...NEXT" and "PRINT char".
- even when I changed the parameter string in the "stringtest" call
(e.g. "This is only a small text" instead of "This is only a small
line"), while the rest of the code remained the same.
Strange, not?
I tried it on two different systems, one under Windows98, another under
Windows95 (and also in DOS mode), the result was the same.
I would be very thankful if somebody could give me an explanation
about this strange bug, so I would know how to prevent this problem.
Hans Ruegg.
Code follows:
'=================================================================
$ERROR ALL ON
DEFINT A-Z
DECLARE SUB csrinit ()
DECLARE SUB csrend ()
DECLARE SUB csrshow (BYVAL x1??, BYVAL y1??)
DECLARE SUB csrhide ()
DECLARE SUB csrint ()
DECLARE SUB stringtest (txt$)
SCREEN 12
LINE (10,10)-(630,470), 3, BF
stringtest "This is only a small line"
SCREEN 0
END
'============ This is the test SUB which produces the error. ===============
SUB stringtest (txt$)
csrinit
FOR ch = 1 TO 80
IF ch > LEN(txt$) THEN EXIT FOR 'When commenting out either this line
char = ASCII(txt$, ch) 'OR this one, no error occurs.
PRINT char;
SELECT CASE char 'When commenting out this SELECT block,
CASE 174: COLOR 14 'no error occurs.
CASE 175: COLOR 12
END SELECT
NEXT ch
csrshow 400, 150
LOCATE 25,1: PRINT "Press any key to continue - or to crash"
DO: m$=INKEY$: LOOP WHILE m$="" 'When commenting out this loop,
'no error occurs.
csrend
END SUB
'========== Following SUBs are for the graphic cursor. ======================
SUB csrinit () 'Initializes the interrupt
DIM cx1 AS SHARED WORD
DIM cy1 AS SHARED WORD
DIM cflag AS SHARED BYTE
DIM scrs?(12)
cflag=0: cx1=0: cy1=0 'Initialize cursor to coordinates (0,0)
GET (0,0)-(7,1), scrs? 'Save screen area under the cursor
DEF SEG = VARSEG(scrs?(0)) 'Copy this array
buf$ = PEEK$(VARPTR(scrs?(0)),12) 'to the area reserved
DEF SEG = CODESEG(Scrsave) 'in the code segment
POKE$ CODEPTR(Scrsave), buf$
DEF SEG
! MOV WORD CS:SaveDS, DS 'Save PowerBasic DS for the Interrupt handler
DEF SEG = 0
OldOff = PEEKI(&H70) 'Save old Interrupt Vector (Int. &H1C)
OldSeg = PEEKI(&H72)
! CLI
POKEL &H70, CODEPTR32(CsrISR) 'Set new Interrupt Vector
! STI
DEF SEG
! PUSH AX ;Copy old Vector to the area
! MOV AX, OldOff ;reserved in the code segment
! MOV WORD CS:OldO, AX
! MOV AX, OldSeg
! MOV WORD CS:OldS, AX
! POP AX
END SUB
SUB csrend () 'Restores the original Int &H1C handler
! PUSH AX
! PUSH BX
! PUSH ES
! MOV AX, WORD CS:OldO
! MOV BX, 0
! MOV ES, BX
! MOV BX, &H70
! CLI
! MOV WORD ES:[BX], AX
! MOV AX, WORD CS:OldS
! MOV WORD ES:[BX+2], AX
! STI
! POP ES
! POP BX
! POP AX
END SUB
SUB csrshow (BYVAL x1??, BYVAL y1??) 'Makes the cursor visible
DIM cx1 AS SHARED WORD 'at given coordinates
DIM cy1 AS SHARED WORD
DIM cflag AS SHARED BYTE
DIM scrs?(12)
IF x1??>632 OR y1??>478 THEN EXIT SUB '(Cursor outside screen area)
! CLI
GET (x1??,y1??)-(x1??+7,y1??+1), scrs? 'Save screen area
DEF SEG = VARSEG(scrs?(0)) 'at cursor location
buf$ = PEEK$(VARPTR(scrs?(0)),12)
DEF SEG = CODESEG(Scrsave)
POKE$ CODEPTR(Scrsave), buf$
DEF SEG
cx1 = x1??: cy1 = y1??: cflag = 2 'Set new coordinates
! STI
END SUB
SUB csrhide () 'Hides the cursor while the
DIM cx1 AS SHARED WORD 'interrupt handler remains active
DIM cy1 AS SHARED WORD
DIM cflag AS SHARED BYTE
DIM scrs?(12)
! CLI
DEF SEG = CODESEG(Scrsave)
buf$ = PEEK$(CODEPTR(Scrsave),12)
DEF SEG = VARSEG(scrs?(0))
POKE$ VARPTR(scrs?(0)), buf$
DEF SEG 'Restore screen area
PUT (cx1,cy1), scrs?, PSET 'at cursor location
cflag=0
! STI
END SUB
SUB csrint () 'Cursor interrupt handler
DIM ISRStack AS STATIC STRING*2048
DIM scrs(12) AS STATIC BYTE
DIM ccounter AS STATIC BYTE
DIM cx1 AS SHARED WORD
DIM cy1 AS SHARED WORD
DIM cflag AS SHARED BYTE
Scrsave: '12 bytes to save screen area
! DD 0 ;at cursor location
! DD 0 ;(8x2 pixels)
! DD 0
OldO:
! DW 0 ;Variable: Old Handler offset
OldS: 'and segment
! DW 0
SaveDS: 'Variable: saves PB Data Segment
! DW 0
SaveSS: 'Variable: saves PB Stack Segment
! DW 0
SaveSP: 'Variable: saves PB Stack Pointer
! DW 0
CsrISR: 'Interrupt handler starts here
! PUSH AX
! PUSH BX
! PUSH CX
! PUSH DX
! PUSH SI
! PUSH DI
! PUSH BP
! PUSH DS
! PUSH ES
! PUSHF
! CLI
! CLD
! MOV WORD CS:SaveSS, SS ; Save PB environment
! MOV WORD CS:SaveSP, SP
! MOV DS, WORD CS:SaveDS
! MOV SS, WORD CS:SaveDS ; Set up local stack frame
! LEA BX, ISRStack
! ADD BX, 2048
! MOV SP, BX
IF cflag=2 THEN 'Cursor location has changed
DEF SEG = CODESEG(Scrsave) '-> copy saved screen area
buf$ = PEEK$(CODEPTR(Scrsave),12) 'in an array to be used by PUT
DEF SEG = VARSEG(scrs(0))
POKE$ VARPTR(scrs(0)), buf$
DEF SEG
cflag=1
END IF
IF cflag=0 THEN GOTO nocursor 'Cursor invisible, do nothing
INCR ccounter
IF (ccounter AND &H7F) >= 8 THEN
IF (ccounter AND &H80) = 0 THEN 'cursor is off
GET (cx1,cy1)-(cx1+7,cy1+1), scrs '-> save screen area at cursor
DEF SEG = VARSEG(scrs(0)) ' location...
buf$ = PEEK$(VARPTR(scrs(0)),12)
DEF SEG = CODESEG(Scrsave)
POKE$ CODEPTR(Scrsave), buf$
DEF SEG
LINE (cx1,cy1)-(cx1+7,cy1+1), 12, BF '...and turn cursor on
ccounter = &H80
ELSE 'cursor is on
PUT (cx1,cy1), scrs, PSET '-> turn cursor off by restoring
ccounter=0 ' the original screen
END IF
END IF
nocursor:
! MOV SP, WORD CS:SaveSP ; Cleanup at end
! MOV SS, WORD CS:SaveSS
! POPF
! POP ES
! POP DS
! POP BP
! POP DI
! POP SI
! POP DX
! POP CX
! POP BX
! POP AX
! JMP DWORD CS:OldO
! STI
! IRET
END SUB
below, hoping that somebody will be able to explain it to me.
I started developing a procedure which displays a red blinking cursor
in SCREEN 12, hooking up interrupt &H1C. (I used the code posted in
the source code section of this forum which connects an interrupt
to PB code.)
The procedure itself worked fine, but the problem started when I
combined it with a procedure which accepts string parameters. Under
Windows98, the program shuts down and I get a message: "This program
attempted to execute an invalid instruction." In DOS mode, the system
freezes and I have to reboot.
When tracing the program line by line, the error ocurred upon the
very last "END SUB" statement executing. The alert shown by Windows
indicated as error location the address 0001:6E0C. The offset value
(6E0C) was exactly the value of the CS register in the PB watch
window when debugging the program. This causes me to think that
something in this code is altering the stack, so that the segment
value is popped off in place of the offset value when returning from
the SUB. (This is only my assumption; I do not have enough knowledge
to find out what exactly caused the error.)
I tried to isolate the bug in a relatively small amount of code,
which follows below. (This code does not do something very
meaningful apart from reproducing the mentioned error; the original
program was much longer of course.)
There was NO ERROR when I made ONLY ONE out of the following changes:
- when leaving out the "csrshow" call in SUB "stringtest"
- when leaving out the whole SELECT CASE block in SUB "stringtest".
- when leaving out any single other line in this SUB, except
"FOR...NEXT" and "PRINT char".
- even when I changed the parameter string in the "stringtest" call
(e.g. "This is only a small text" instead of "This is only a small
line"), while the rest of the code remained the same.
Strange, not?
I tried it on two different systems, one under Windows98, another under
Windows95 (and also in DOS mode), the result was the same.
I would be very thankful if somebody could give me an explanation
about this strange bug, so I would know how to prevent this problem.
Hans Ruegg.
Code follows:
'=================================================================
$ERROR ALL ON
DEFINT A-Z
DECLARE SUB csrinit ()
DECLARE SUB csrend ()
DECLARE SUB csrshow (BYVAL x1??, BYVAL y1??)
DECLARE SUB csrhide ()
DECLARE SUB csrint ()
DECLARE SUB stringtest (txt$)
SCREEN 12
LINE (10,10)-(630,470), 3, BF
stringtest "This is only a small line"
SCREEN 0
END
'============ This is the test SUB which produces the error. ===============
SUB stringtest (txt$)
csrinit
FOR ch = 1 TO 80
IF ch > LEN(txt$) THEN EXIT FOR 'When commenting out either this line
char = ASCII(txt$, ch) 'OR this one, no error occurs.
PRINT char;
SELECT CASE char 'When commenting out this SELECT block,
CASE 174: COLOR 14 'no error occurs.
CASE 175: COLOR 12
END SELECT
NEXT ch
csrshow 400, 150
LOCATE 25,1: PRINT "Press any key to continue - or to crash"
DO: m$=INKEY$: LOOP WHILE m$="" 'When commenting out this loop,
'no error occurs.
csrend
END SUB
'========== Following SUBs are for the graphic cursor. ======================
SUB csrinit () 'Initializes the interrupt
DIM cx1 AS SHARED WORD
DIM cy1 AS SHARED WORD
DIM cflag AS SHARED BYTE
DIM scrs?(12)
cflag=0: cx1=0: cy1=0 'Initialize cursor to coordinates (0,0)
GET (0,0)-(7,1), scrs? 'Save screen area under the cursor
DEF SEG = VARSEG(scrs?(0)) 'Copy this array
buf$ = PEEK$(VARPTR(scrs?(0)),12) 'to the area reserved
DEF SEG = CODESEG(Scrsave) 'in the code segment
POKE$ CODEPTR(Scrsave), buf$
DEF SEG
! MOV WORD CS:SaveDS, DS 'Save PowerBasic DS for the Interrupt handler
DEF SEG = 0
OldOff = PEEKI(&H70) 'Save old Interrupt Vector (Int. &H1C)
OldSeg = PEEKI(&H72)
! CLI
POKEL &H70, CODEPTR32(CsrISR) 'Set new Interrupt Vector
! STI
DEF SEG
! PUSH AX ;Copy old Vector to the area
! MOV AX, OldOff ;reserved in the code segment
! MOV WORD CS:OldO, AX
! MOV AX, OldSeg
! MOV WORD CS:OldS, AX
! POP AX
END SUB
SUB csrend () 'Restores the original Int &H1C handler
! PUSH AX
! PUSH BX
! PUSH ES
! MOV AX, WORD CS:OldO
! MOV BX, 0
! MOV ES, BX
! MOV BX, &H70
! CLI
! MOV WORD ES:[BX], AX
! MOV AX, WORD CS:OldS
! MOV WORD ES:[BX+2], AX
! STI
! POP ES
! POP BX
! POP AX
END SUB
SUB csrshow (BYVAL x1??, BYVAL y1??) 'Makes the cursor visible
DIM cx1 AS SHARED WORD 'at given coordinates
DIM cy1 AS SHARED WORD
DIM cflag AS SHARED BYTE
DIM scrs?(12)
IF x1??>632 OR y1??>478 THEN EXIT SUB '(Cursor outside screen area)
! CLI
GET (x1??,y1??)-(x1??+7,y1??+1), scrs? 'Save screen area
DEF SEG = VARSEG(scrs?(0)) 'at cursor location
buf$ = PEEK$(VARPTR(scrs?(0)),12)
DEF SEG = CODESEG(Scrsave)
POKE$ CODEPTR(Scrsave), buf$
DEF SEG
cx1 = x1??: cy1 = y1??: cflag = 2 'Set new coordinates
! STI
END SUB
SUB csrhide () 'Hides the cursor while the
DIM cx1 AS SHARED WORD 'interrupt handler remains active
DIM cy1 AS SHARED WORD
DIM cflag AS SHARED BYTE
DIM scrs?(12)
! CLI
DEF SEG = CODESEG(Scrsave)
buf$ = PEEK$(CODEPTR(Scrsave),12)
DEF SEG = VARSEG(scrs?(0))
POKE$ VARPTR(scrs?(0)), buf$
DEF SEG 'Restore screen area
PUT (cx1,cy1), scrs?, PSET 'at cursor location
cflag=0
! STI
END SUB
SUB csrint () 'Cursor interrupt handler
DIM ISRStack AS STATIC STRING*2048
DIM scrs(12) AS STATIC BYTE
DIM ccounter AS STATIC BYTE
DIM cx1 AS SHARED WORD
DIM cy1 AS SHARED WORD
DIM cflag AS SHARED BYTE
Scrsave: '12 bytes to save screen area
! DD 0 ;at cursor location
! DD 0 ;(8x2 pixels)
! DD 0
OldO:
! DW 0 ;Variable: Old Handler offset
OldS: 'and segment
! DW 0
SaveDS: 'Variable: saves PB Data Segment
! DW 0
SaveSS: 'Variable: saves PB Stack Segment
! DW 0
SaveSP: 'Variable: saves PB Stack Pointer
! DW 0
CsrISR: 'Interrupt handler starts here
! PUSH AX
! PUSH BX
! PUSH CX
! PUSH DX
! PUSH SI
! PUSH DI
! PUSH BP
! PUSH DS
! PUSH ES
! PUSHF
! CLI
! CLD
! MOV WORD CS:SaveSS, SS ; Save PB environment
! MOV WORD CS:SaveSP, SP
! MOV DS, WORD CS:SaveDS
! MOV SS, WORD CS:SaveDS ; Set up local stack frame
! LEA BX, ISRStack
! ADD BX, 2048
! MOV SP, BX
IF cflag=2 THEN 'Cursor location has changed
DEF SEG = CODESEG(Scrsave) '-> copy saved screen area
buf$ = PEEK$(CODEPTR(Scrsave),12) 'in an array to be used by PUT
DEF SEG = VARSEG(scrs(0))
POKE$ VARPTR(scrs(0)), buf$
DEF SEG
cflag=1
END IF
IF cflag=0 THEN GOTO nocursor 'Cursor invisible, do nothing
INCR ccounter
IF (ccounter AND &H7F) >= 8 THEN
IF (ccounter AND &H80) = 0 THEN 'cursor is off
GET (cx1,cy1)-(cx1+7,cy1+1), scrs '-> save screen area at cursor
DEF SEG = VARSEG(scrs(0)) ' location...
buf$ = PEEK$(VARPTR(scrs(0)),12)
DEF SEG = CODESEG(Scrsave)
POKE$ CODEPTR(Scrsave), buf$
DEF SEG
LINE (cx1,cy1)-(cx1+7,cy1+1), 12, BF '...and turn cursor on
ccounter = &H80
ELSE 'cursor is on
PUT (cx1,cy1), scrs, PSET '-> turn cursor off by restoring
ccounter=0 ' the original screen
END IF
END IF
nocursor:
! MOV SP, WORD CS:SaveSP ; Cleanup at end
! MOV SS, WORD CS:SaveSS
! POPF
! POP ES
! POP DS
! POP BP
! POP DI
! POP SI
! POP DX
! POP CX
! POP BX
! POP AX
! JMP DWORD CS:OldO
! STI
! IRET
END SUB