Author Topic: Jump table not working as expected  (Read 6192 times)

Offline mik3ca

  • Jr. Member
  • *
  • Posts: 30
Jump table not working as expected
« on: January 18, 2021, 04:38:08 PM »
I have a problem with some addressing issues. I made large assembly code that is about 2KB in size and copying the whole code into dos QuickBasic is tedious and takes up precious local space. Due to the complexity of the code, I will only show a portion.

For me to get the jump table to work, I created the following assembly code, compiled it as a 16-bit DOS binary, and extracted the hex values (with linux xxd utility)

Code: [Select]
push BP       ;save register (Qbasic stack is changed)
mov BP,SP  ;save pointer here to freeze Qbasic stat
push ES ;save other frequently used pointer registers
push DI
push DS
push SI
mov AX,[BP+06h] ;Load our function number
mov BP,[BP+08h] ;Load offset of this code
cmp AX,3h ;See if function number is from 0 - 3
jg endit ;Function number is too high so exit
shl AX,1 ;Multiply function number by 2
add AX,BP ;Add this code offset
mov SI,AX ;Save as SI
mov SI,[CS:SI+jt] ;Load address of our option
add SI,BP ;Add this code offset since nasm doesn't automatically do it
jmp SI ;Go to chosen function

;Our jump table
jt:
dw option0
dw option1
dw option2
dw option3

option0:
;insert code here
jmp endit

option1:
;insert code here
jmp endit

option2:
;insert code here
jmp endit

option3:
;insert code here
jmp endit

;common ending from all functions
endit:
pop SI ;restore all registers in order
pop DS
pop DI
pop ES
pop BP
retf 4h ;release our used paramters (2 ints) to QB

The Qbasic code is as follows:

Code: [Select]
dim c as string * 2000 'reserve enough space to store binary version of above code
c=acode$ 'load the code in
def seg=varseg(c) 'set segment to code
func%=4 'we want to try function 4
codeoffs%=varptr(c)
call absolute(byval codeoffs%,byval func%, codeoffs%) 'run the code
end

'My apologies for not using meaningful variable names but this function converts the hexadecimal code to the binary version of the code above. I wanted to avoid making the binary code above as a separate file required by this program.
FUNCTION acode$
w$ = <insert binary code here as hex digits>
cd$ = "": soc% = LEN(w$): FOR z% = 1 TO soc% STEP 2: cd$ = cd$ + CHR$(VAL("&H" + MID$(w$, z%, 2))): NEXT:  acode$ = cd$
END FUNCTION

Note in the call absolute, the first parameter is at BP+08 which is the offset. without this, the jump table would not execute and some random spot of memory would be accessed instead.

So anyways, I tried to make some changes to make the assembly code resident in memory. The code is as follows:

Code: [Select]
org 100h ;this is a COM file we are keeping in memory
jmp installer

'We add a word so outside program can see if int is installed
db 'INTCHECK'
;Here we expect outside program to pass in AX as function number and BP as offset to this code
;along with ES:DI as a pointer to an struct made in QuickBasic.
myinterrupt:
push BP  ;Save outside pointer registers that we modify
push ES
push DI
push DS
push SI
pusha
cmp AX,3h ;See if function number is from 0 - 3
jg endit ;Function number is too high so exit
shl AX,1 ;Multiply function number by 2
add AX,BP ;Add this code offset
mov SI,AX ;Save as SI
mov SI,[CS:SI+jt] ;Load address of our option
add SI,BP ;Add this code offset since nasm doesn't automatically do it
jmp SI ;Go to chosen function

;Our jump table
jt:
dw option0
dw option1
dw option2
dw option3

option0:
;insert code here
jmp endit

option1:
;insert code here
jmp endit

option2:
;insert code here
jmp endit

option3:
;insert code here
jmp endit

;common ending from all functions
endit:
popa
pop SI ;restore all registers in order
pop DS
pop DI
pop ES
pop BP
iret  ;exit from interrupt

;Here we install the interrupt

installer:
;To keep things simple, I made a hard-coded interrupt value and omitted the interrupt saving code.

mov AL,63h ;we will make our interrupt #63h

mov AH,25h ;install our interrupt
push DS ;save original DS
push CS
pop DS           
mov DX,myint ;DS:DX -> address to our handler
int 21h ;install
pop DS  ;restore original DS
mov AX,3100h ;return code to DOS: AL=0
mov DX,0F00h ;hog about 60kb
int 21h ;exit as a TSR

I compiled this as a dos binary, renamed it as a .COM file and executing that does work.

So now I make a short assembly routine in QuickBasic to call the newly installed interrupt:

Code: [Select]
org 0h
PUSH BP ;save registers as usual
MOV BP, SP
push ES
push DI
push DS
push SI
mov DI,[BP+06h] ;load values directly
mov ES,[BP+08h]
mov BX,[BP+0Ah] ;BX=TSR code offset (for debugging)
mov AX,[BP+0Ch] ;our function
int 63h ;We made our int as 63h in assembly code, so we have to call it as that.
pop SI ;restore registers
pop DS
pop DI
pop ES
pop BP
retf 6h ;return to QuickBasic

And in quickbasic, the code has minor changes as follows:

Code: [Select]
type struct
whatever as integer
anything as string * 20
something as long
end type

dim s as struct
dim c as string * 2000 'reserve enough space to store binary version of above code
c=acode$ 'load the code in
def seg=varseg(c) 'set segment to code
func%=4 'we want to try function 4
codeoffs%=varptr(c)

'can't figure out the correct value. Its the value (BP) that is added to the assembly jump table before the jump is executed. If I get this wrong, the code crashes on most options.
addr%=0

call absolute(byval func%,byval addr%,byval varseg(s),byval varptr(s), codeoffs%) 'run the code
end

FUNCTION acode$
'note here, I can reduce the w$ = lines to one and not have the value so long.
w$ = <insert binary code here as hex digits>

cd$ = "": soc% = LEN(w$): FOR z% = 1 TO soc% STEP 2: cd$ = cd$ + CHR$(VAL("&H" + MID$(w$, z%, 2))): NEXT:  acode$ = cd$
END FUNCTION

Now what works in both versions is that if I use a function outside of number 0, 1, 2, or 3, then no function is performed and the assembly routine exits normally.

But if I use any of the functions, the code immediately crashes in the new version (when installed and called as an interrupt), but when I put all the code in one space (and not use it as an interrupt, see above) then the code executes fine. So I'm guessing that with the interrupt version that I am sending the wrong address to SI for the jump but I do not know how to calculate the correct address. Maybe nasm is doing something to the addresses.

I even made another attempt to make things work by adding the following after saving all registers in my interrupt code:

Code: [Select]
mov BP,myinterrupt

And I still am not successful.


How do I calculate the addresses properly so the interrupt routine that is stored in memory can successfully execute the jump without the executing the wrong things?

Offline mik3ca

  • Jr. Member
  • *
  • Posts: 30
Re: Jump table not working as expected
« Reply #1 on: January 18, 2021, 09:15:53 PM »
Ok I solved my issue from experimentation, trial and error.

the offset value (BP) needed to be zero in the com file when doing the math for the jump table.