1

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.

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
micheal007
  • 137
  • 9
  • Do not ignore warnings of your assembler. I assume line 37 is the `call far [cs:bx] ` and the error is because you need `call far cs:[bx]`. If `cs:` is ignored the code will attempt to fetch a pointer from `ds` and go to some random location. – Jester Aug 02 '19 at 13:39
  • But call far cs:[bx] sends error: `Illegal immediate` – micheal007 Aug 02 '19 at 14:14
  • Thanks, I forget add a exit, and I use not correct syntax for call. – micheal007 Aug 02 '19 at 14:34
  • 1
    TASM is much different than FASM. It has similiar syntax to MASM. In fasm line call far [cs:bx] works but not in tasm. – micheal007 Aug 02 '19 at 14:36
  • Yes, I want to put far vectors because I plan add a next function to attach a .exe plug-ins. (DS:DX is function pointer BX=1 int 0f5h, This function has to edit g0 vector) from other external program. – micheal007 Aug 02 '19 at 14:48
  • If you'll ever want to improve you TSR with uninstalation, or with upper-memory installation, you might like my TSR skeleton at https://euroassembler.eu/prodos16/tsrup.htm – vitsoft Aug 02 '19 at 20:31

1 Answers1

3

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

Other Observations

  • In the provided code the TSR checks if it is already loaded by examining the segment of interrupt 0F5h (in the IVT) and comparing with 0. If it is 0 it is assumed the TSR isn't installed. That may work in the environments being tested with, but it may not work in a wider array of DOS/hardware environments. Consider looking at the 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.

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
  • 2
    "It will use ret if the procedure is NEAR." This could be clearer by referring to retf and retn, and noting that ret is taken as the one matching the procedure nearness/farness. (Unlike NASM, which makes ret always an alias for retn.) – ecm Aug 02 '19 at 18:51