Programming in 80x86 assembler
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 +L4A86 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