I have got some experience with FASM and I learned that so well. Now, I wanted to learn TASM syntax. I wrote an example program which is TSR. Here`s my code
.model tiny
.8086
.stack 200h
.data
Message db 'Example0 is loaded in memory',0,'$'
.code
main proc ;'main' is proc and code region
mov ax,@data ;Initialize data segment
mov ds,ax
push es
xor bx,bx
mov es,bx
;Check interrupt vector 0f5h
cmp word ptr es:[3d6h],0
je init ;If null initialize program and stay resident
int 0f5h
cmp ax,'ID' ;Check string in ax
jne init ;If AX != 'GG' initialize TSR
mov ah,9
mov dx,offset Message
int 21h
init:
;Set interrupt vector 0f5h
mov word ptr es:[3d4h],offset interruptroute
mov word ptr es:[3d6h],cs
pop es
mov ax,3100h
mov dx,64 ;Reserve 1KB (My .exe is lower than this)
int 21h
interruptroute proc far
shl bx,2
add bx,offset g0
call far [cs:bx] ;I assume this array is in code segment
;Here maybe fault in call far
iret
endp interruptroute
g0:
dw getID
dw @code
getID proc far
mov ax,'ID'
retf
endp getID
endp
end main
And my VirtualBox`s screenshot:
Additional I show my command line:
tasm src\gdos.asm,bin\gdos.obj tlink bin\gdos.obj,bin\gdos.exe
Note: GDOS is my planned OS to build.
TL;DR: FASM's syntax and semantics can be quite different from that of MASM/TASM.
In Turbo Assembler (TASM) the FAR
modifier by itself should not be used on a JMP
or CALL
instruction. Instead use FAR PTR procname
where procname
is the name of a procedure defined with PROC
. The code provided uses:
call far [cs:bx]
Since this is a CALL
instruction a person may be inclined to try:
call far ptr [cs:bx]
As I stated above FAR PTR
should only be used if the operand is a label. [cs:bx]
isn't a label. The question is - what syntax can be used to do an indirect FAR JMP without a label? The answer is DWORD PTR
:
call dword ptr [cs:bx]
The second problem is in this section of code:
mov ah,9
mov dx,offset Message
int 21h
init:
;Set interrupt vector 0f5h
mov word ptr es:[3d4h],offset interruptroute
mov word ptr es:[3d6h],cs
Int 21h/AH=9
is being used to print a message to standard output. When finished it continues execution into the initialization code. An exit is needed after printing the already loaded message. Add a call to Int 21h/AH=4C
after displaying the message. The code could look like:
mov ah,9
mov dx,offset Message
int 21h
mov ax, 4c00h ; DOS exit function return 0
int 21h
init:
;Set interrupt vector 0f5h
mov word ptr es:[3d4h],offset interruptroute
mov word ptr es:[3d6h],cs
Int 2F
multiplexer interrupt. It can be used for detecting the presence of an existing TSR. Randall Hyde has good information on this subject in his book Art of Assembly. Chapter 18.5 Installing a TSR covers this subject in detail.Although not a bug, there is this code that defines getID
:
getID proc far
mov ax,'ID'
retf
endp getID
I recommend not using retf
here. In any function defined with PROC
TASM/MASM are smart enough to convert any ret
they encounter to a retf
if the function is defined FAR
. They will convert any ret
found in a NEAR
function to a retn
. This can be useful if the function is changed from NEAR
to FAR
and vice-versa as the assembler will encode the proper return instruction.
The jump table g0
is defined as:
g0:
dw getID
dw @code
This works. Instead it could be:
g0:
dw getID
dw seg GetID
This method doesn't rely on explicitly using a fixed segment @code
. If GetID
is placed in a different segment other than @code
in the future then there is no modification required to the table.
If all of the functions like getID
are in the same segment as the interrupt handler then near pointers (offsets) are all that are needed in the jump table g0
. Using just a table of NEAR offsets an indirect near jump in interruptroute
can be used:
shl bx,1 ; The table would have 2 byte NEAR pointers relative to CS
; So multiply the index by 2 instead of 4
add bx,offset g0
jmp [cs:bx] ; This does a NEAR jmp to the address at [cs:bx]
The jump table g0
would now simply look like this:
g0:
dw getID
Functions like getID
would be marked as being NEAR
with:
getID proc near
This only works if all the functions called by the interruptroute
interrupt handler are in the same code segment as getID
. I assume the intention is to have a table of call vectors and BX is an index into a call table (g0
). In this code it would work, but I don't know the design intent of the code to say that this would be suitable for the actual project being worked on.