Programming in 80x86 assembler

Part 2: the IRQ-vector assigning

HTML-flowcharted program in 8086 assembly

This is part two of a guided tour through the language of unlimited possibilities. The assembler which can digest this source is A86, a shareware assembler made by Eric Isaacson wich you can get here http://eji.com for an evaluation.

In first instance this software was written in Modula-2, my other darling. BUT, the compiler felt it was necessary to redirect some interrupt vectors for internal use. This made the software unusable.
That's why I ported the source to assembly language. That sounds as "difficult" and "a lot of work", but not in reality. If you start out with a tried template in a flimsy macro-language (like all HLL's are, Modula-2 just as much as C or even D) it is easy to transfer the structure of the source code to a real language. A real language. You know: about the difference between the boys and the men. :o)
With A86 assembly language you're always in the drivers seat. No assumptions or other red tape to have the assembler "help you". Here's the HTML-soaked source.

The main loop of the program

Just take a look at the body of the main routine. I cut out the debris which is not absolutely necessary to understand the program so that the general structure gets more evident.

main:    mov   di, ByteF        ; initialise ...
         rep   stosb            ; ... all volatile data
         mov   di, offset Output
         rep   movsb            ; ... the output buffer
         mov   si, 080          ; ... the data-pointer
         call  GetArg           ; retrieve first argument
         call  Convert
         call  GetArg           ; in case only one argument, ShowRange will not do ...
         call  GetArg           ; and get Stop-value
         call  Convert

Exit:    call  ShowRange        ; show the values for the INT vectors
         int   021/40h          ; and print it out on screen, before ...
         mov   ax, 04C00
         int   021              ; ... exiting without errorcode.
   
In essence this is what the software is about: two arguments are fetched from the commandline. These are converted to binary numbers and after that, the respective interrupt vectors are retrieved and displayed on the user console. Unless it is redirected.

Start of the sourcecode

    
name     vector22
title    Show/set 80x86 interrupt vectors
page     80, 120

; version 1.0  : List INT vectors to screen in Modula-2         OK: 22-10-1998
; version 2.0  : complete rewrite from Modula-2 into ASM.       OK: 26-10-1998
; version 2.1  : added some graphics characters                 OK: 26-10-1998
; version 2.2  : add FreeDOS characteristics                    OK: 07-01-1999

stdout   =  1
tab      =  9
lf       = 10 
cr       = 13

SetInt   = bit 0
ShowInts = bit 1


store MACRO
    mov  al, #1
    stosb
    #EM


clr MACRO
    mov  #1, 0
    #EM
    

DATA segment

ByteF = $
dummy    db    ?        ; just to fool D86....

        even
Start    dw    ?
Stop     dw    ?
Segm     dw    ?        ; value for Segment
Offs     dw    ?        ; value for Offset
Flags    dw    ?
h_digit  dw    ?        ; accuracy of hex conversion
ArgNum   dw    ?        ; nr of bytes in this argument
OldClp   dw    ?        ; current pointer into command line

Argument db    80 dup (?)      ; storage for next argument from command-line
Output   db    4K dup (?)      ; buffered output

ByteL = $
; ----------------------

  CODE segment
        jmp   main


HexTable db   '0123456789ABCDEF', 0
        db    'VeRsIoN=Vector 2.2', 0
        db    'CoPyRiGhT=CopyLeft Jan Verhoeven, jverhoeven@bigfoot.com', 0


Mess001 db    'VECTOR prints out the 8086 interrupt vector addresses, or sets them.', cr, lf, lf
        db    'The syntax is:', cr, lf, lf
        db    tab, 'VECTOR <from> - <to> : shows the actual vector addresses.', cr, lf, lf
        db    tab, 'VECTOR <int> = <segment>:<offset> : assigns the vector a new value.', cr, lf, lf
        db    'VECTOR is GNU GPL style FREE software. ', cr, lf
        db    'Please read the GNU GPL if you are in doubt.', cr, lf, lf


Mess002 db    'VECTOR was made by Jan Verhoeven, NL-5012 GH 272, The Netherlands', cr, lf
        db    'E-mail address : aklasse@bigfoot.com', cr, lf, lf
Len001 = $ - Mess001
Len002 = $ - Mess002


Mess003 db    'Int', tab, 'Segm:Offs', cr, lf
        db    'ÄÄÄ', tab, 'ÄÄÄÄ ÄÄÄÄ', cr, lf
Len003 = $ - Mess003


Mess004 db    'All numbers are expected to be either in decimal or hexadecimal.', cr, lf
        db    'If a number starts off with a "0", then it is a hex number. If not, it is', cr, lf
        db    'treated as a decimal number.', cr, lf, lf
Len004 = $ - Mess004
;------------------------


L0:     mov   b [di], 0        ; terminate argument string
        mov   [OldClp], si     ; done, => clean up.
        clc                    ; indicate "No Error"

L3:     pop   di, si, ax       ; restore registers, ...
        ret                    ; ... and leave.


GetArg: push  ax, si, di       ; get next argument from command-line in ASCIIZ format
        mov   si, [OldClp]     ; now, where did we leave last time?
        cmp   si, 0            ; Have we ever used this routine?
        IF  E mov  si, 081             ; if not, prime SI, ...
        mov   di, offset Argument      ; ... DI and ...
        mov   [ArgNum], 0              ; ... nr of chars in argument.

L1:     lodsb                  ; get byte
        cmp   al, ' '          ; skip over spaces, ...
        je    L1
        cmp   al, tab          ; ... and tabs.
        je    L1
        cmp   al, 1            ; ONLY if AL is 0, we get a carry
        jc    L3               ; if CARRY, we're done

L2:     stosb                  ; else store char in Arguments array
        inc   [ArgNum]         ; adjust counter
        lodsb                  ; and get next char
        cmp   al, ' '          ; is it a delimiting space?
        je    L0
        cmp   al, tab          ; or a tab?
        je    L0
        cmp   al, 0            ; or an end-of-line?
        jne   L2               ; if not, loop back,
        mov   si, 0FFFF        ; else make SI ridiculously high, ...
        stc                    ; ... set carry flag, ...
        jmp   L0               ; and get out.
;------------------------

L1:     stc                    ; byte not in table!
        pop   dx
        ret                    ; exit

L2:     sub   bx, dx           ; calculate position in table
        pop   dx
        clc
        ret


TableFind:
        push  dx               ; find AL in ASCIIZ table [BX] and report position
        mov   dx, bx           ; keep value of SI
L0:     cmp   b [bx], 0        ; is it end of table?
        je    L1               ; if so, jump out
        cmp   al, [bx]         ; compare byte with table
        je    L2               ; if same, jump out
        inc   bx               ; else increment pointer
        jmp   L0               ; and loop back
;------------------------


ShowHex: push ax, bx, cx              ; number in ax will be printed to buffer
        mov   bx, offset HexTable
        mov   cx, 4
        sub   cx, [h_digit]    ; determine how many digits to shift out ...
        shl   cx, 2            ;    ... using 4 bits per hex digit, ...
        IF nz shl  ax, cl      ;    ... and dispose them
        mov   cx, [h_digit]


L0:     rol   ax, 4            ; rotate MSB of ax into LSB
        push  ax
        and   al, 0F           ; mask off high nibble
        xlat                   ; compose hex digit
        stosb                  ; and print it
        pop   ax               ; restore value
        loop  L0               ; and loop back
        pop   cx, bx, ax       ; prepare for exit, and
        ret                    ; gone
;------------------------


MakeUpper:
        cmp   al, 'a'
        jb    ret
        cmp   al, 'z'          ; if in range, ...
        ja    ret
        and   al, not bit 5    ; ... make uppercase
        ret
;------------------------

GetVector:
        push  ax, bx, es       ; al = INT number
        cbw                    ; ah = 0
        shl   ax, 2            ; ax = al x 4
        mov   bx, ax
        clr   ax
        mov   es, ax
        les   bx, [es:bx]
        mov   [Segm], es
        mov   [Offs], bx       ; store results
        pop   es, bx, ax
        ret
;------------------------


ShowLine:                      ; buffered output.
        push  ax
        mov   [h_digit], 2     ; 2 digit INT number
        call  ShowHex
        store Tab             ; one tab
        mov   [h_digit], 4     ; 4 digits for ...
        mov   ax, [Segm]
        call  ShowHex         ; printing segment value
        store ':'             ; delimiter
        mov   ax, [Offs]
        call  ShowHex         ; and for offset value
        store cr
        store lf              ; terminate the line
        pop   ax
        ret                   ; and exit
;------------------------


ShowRange:                     ; show INT numbers which are in range
        mov   ax, [Start]      ; easy
        mov   bx, [Stop]
        cmp   ax, bx
        jbe   >L0
        mov   [Stop], ax
        mov   [Start], bx
        mov   ax, bx

L0:     call  GetVector
        call  ShowLine
        inc   ax
        cmp   ax, [Stop]
        jbe   L0
        ret
;------------------------


SetVector:                     ; use DOS to change INT vector, the kind way.
        push  ax, dx, ds
        mov   ax, [Start]      ; al = INT number
        mov   ah, 025
        mov   dx, [Offs]
        mov   ds, [Segm]
        int   021              ; just do it
        pop   ds, dx, ax
        ret
;------------------------


BadNumber:                     ; hey typo, you made a dumbo!
        mov   dx, offset Mess004
        mov   cx, Len004
        mov   bx, StdOut
        mov   ah, 040
        int   021
        mov   ax, 04C02        ; and exit with errorcode 2
        int   021
;------------------------


IntSet: push  si               ; save si, just in case
        mov   si, [OldClp]     ; now, where were we in the command line?
L0:     lodsb                  ; get a char
        cmp   al, 0            ; is it end of string?
        je    >L1              ; if so, we're done
        cmp   al, ':'          ; is it a colon?
        jne   L0
        dec   si               ; if so, make it a space
        mov   b [si], ' '      ; so GetArg can cope with it.
        pop   si
L1:     call GetArg           ; get SEGMENT value
        jc    SyntErr
        mov   si, offset Argument
        call  Convert          ; and convert it to binary
        jc    BadNumber
        mov   [Segm], ax
        call  GetArg           ; get OFFSET value
        jc    SyntErr
        mov   si, offset Argument
        call  Convert
        jc    BadNumber
        mov   [Offs], ax       ; and DONE!

        call  SetVector        ; set the new vector
        jmp   Exit             ; and show it on screen
;------------------------


SyntErr: mov  dx, offset Mess001
        mov   cx, Len001
        mov   bx, StdOut
        mov   ah, 040          ; print out "help" screen and ...
        int   021

        mov   ax, 04C01        ; ... exit with errorcode 1
        int   021
;------------------------

L8:     mov   ax, dx           ; ConvertHex has result in DX, that's why.
L9:     pop   dx, bx
        ret


ConvDec: cmp  al, '0'
        jb    BadNumber
        cmp   al, '9'
        ja    BadNumber
        clr   ah               ; convert ASCII to BINary
        sub   al, '0'          ; make BCD
	mov   bx, 10           ; set up multiplier
L0:     cmp   b [si], 0
        je    L9
        cmp   b [si], '9'      ; next byte is no digit?
        ja    L9
        cmp   b [si], '0'
        jb    L9               ; if so, exit, else ...
        mul   bx               ; ... multiply by 10
        mov   dl, [si]         ; get next byte, ...
        sub   dl, '0'          ; ... make BCD, ...
        inc   si               ; ... point to next byte, and ...
        add   ax, dx           ; ... compose number.
        jmp   L0               ; now get next digit to process.
	

Convert: push bx, dx
        clr   dx
        lodsb
        cmp   al, '0'          ; is the number hex?
        jne   ConvDec          ; if not, it is decimal
L1:     lodsb
        cmp   al, 0            ; end of string?
        je    L8
        call  MakeUpper                ; if not, make uppercase
        mov   bx, offset HexTable
        call  TableFind                ; and lookup in table
        jc    L9
        shl   dx, 4            ; multiply DX by 16
        or    dl, bl           ; bx = index into table
        jmp   L1               ; repeat until done
;------------------------


main:   mov   di, ByteF                ; initialise ...
        mov   cx, ByteL
        sub   cx, di
        clr   al
        rep   stosb            ; ... all volatile data
        mov   di, offset Output
        mov   si, offset Mess003
        mov   cx, Len003
        rep   movsb            ; ... the output buffer
        mov   si, 080          ; ... the data-pointer
        lodsb
        cmp   al, 0            ; do we HAVE a command-line?
        IF  E jmp  SyntErr     ; if not, tell the user.
        mov   ah, 0
        mov   bx, si
        add   bx, ax
        mov   b [bx], 0        ; make command tail ASCIIZ

        call  GetArg            ; retrieve first argument
        IF  C jmp  SyntErr     ; if not, tell the user.
        mov   si, offset Argument
        call  Convert
        IF  C jmp  BadNumber
        mov   [Start], ax      ; use first value for Start AND Stop.
        mov   [Stop], ax

        call  GetArg            ; in case only one argument, ShowRange will not do ...
        jc    Exit                    ; ... silly things.
        mov   si, offset Argument
        cmp   b [si], '='      ; if assignment operator, ...
        IF  E jmp  IntSet      ; ... go to IntSet
        cmp   b [si], '-'      ; else look for range indicator
        jne   Exit
        call  GetArg           ; and get Stop-value
        call  Convert
        IF  C jmp  BadNumber
        mov   [Stop], ax


Exit:   call  ShowRange        ; show the values for the INT vectors
        mov   dx, offset Output
        mov   cx, di
        sub   cx, dx
        mov   bx, StdOut
        mov   ah, 040
        int   021              ; and print it out on screen, before ...
        mov   ax, 04C00
        int   021              ; ... exiting without errorcode.
   

JUMP, CALL, MACRO, SUBROUTINE: what to choose?

This program has the structure of a Modula-2 program. This is no coincidence. All well written software is modular. Each module is just a piece of code that can be stand-alone or that can be used in various stages of the program. It is an entity.
If the code fragment is short and used only once, you can either implement it directly in the source, or pour it into the harnass of a macro. The difference between a macro and a subroutine is that a macro is expanded by the assembler and a subroutine is called during runtime. In this last example, the execution of the program is redirected through a jump.
If you want to find out more about this, just assemble the source with A86. Do this with the following command-line:

   A86 vector22.asm +L4
   
A86 will now make a LISTING file with the name VECTOR.LST. In it, you can find the two macro's (store and clr) which are used now and then. Compare the function of these macro's with that of subroutines like GetArg which is also used several times during the software.

Whether you make a code fragment a macro or a subroutine is up to you. It depends on your personal preferences and experience. I mostly choose to make all modules that have an entity (i.e. which are stand-alone and could possibly be used more than once in that program) a callable subroutine.
This makes the software a bit slower because each procedure is accessed via a CALL and it is left through a RET instruction.

On modern Gigahertz CPU's this is no big deal. But not so long ago, it was inconveniant and time consuming to jump around through the program. A macro is less efficient in memory usage. It takes up more disk space (no big deal anymore since the coming of the multi Gigabyte disks) but until not so long ago it was a necessity to keep the memory footprint of a program as small as possible.
The biggest advantage of modularly built programs is ease of debugging. You just set out to debug one subroutine after the other. And in many cases you can tell which module was wrong just by looking at the error. Some experience is needed, but you can get it just by doing a lot of coding.

Assembly language is my most favorite language since it enables me to control the machine to the full. In no place there is a tiny fragment of the compiler, trying to prevent me from stepping into seven ditches at once. The code in tour COM files is what YOU have put in, and no one else. So, if there is an error, then that is your error as well. This makes the hunt for errors easier and more attractive.

If programming in assembly language draws your attention, there is a free website which is dedicated to it. You can download at least 8 issues of a free magazine written by assembly language programmers. Just log on to the site of the APJ (Assembler Programmers Journal) and get the information you need.

Page created around 1998,

Page equipped with FroogleBuster technology