ASMTUT
ASMTUT
this will hopefully be the final version of this unless somebody finds some
mistakes in it. i have spent over 620 minutes creating this document (not
including writing or testing the code or the original text document) so i hope
you will find it useful.
this version is still in testing which means that i cannot be sure that all the
code
will compile and work correctly. i have done my best to make sure that all the
information contained in this document is correct. if any mistakes are found
please could you notify me be email.
if someone who has a computer that doesn�t have a vga card/monitor could you
please compile the code on p29 that checks what graphics the computer can handle
and tell me if it returned dx as 0.
this document was written by gavin estey. no part of this can be reproduced and
sold
in any commercial product without my written permission (email me for more
information). i am not responsible for any damage caused in anyway from this
document.
firstly you will need a suitable assembler to compile your programs. all examples
have
been tested with two compilers: a86 and turbo assembler� ver 4. a86 is a very
good shareware assembler capable of producing code up to 80286. this can be found
on your local simtel mirror (try ftp.demon.co.uk or ftp.cdrom.com) under
simtel/msdos/asmutil called a86v322.zip.
firstly i am going to talk about segments and offsets. this (for me anyway) is
probably the hardest part of assembly to understand.
the original designers of the 8088 decided that nobody will every need to use more
that one megabyte of memory. so they built the chip so it couldn't access above
that.
the problem is to access a whole megabyte 20 bits are needed (one bit being either
a
one or a zero). registers only have 16 bits and they didn't want to use two
because
that would be 32 bits and they thought that this would be too much for anyone.
they
decided to do the addressing with two registers but not 32 bits. is this
confusing?
blame the designers of the 8088.
offset = segment * 16
segment = offset / 16 (the lower 4 bits are lost)
(note ds and si overlap). this is how ds:si is used to make a 20 bit address. the
segment is in ds and the offset is in si.
segment registers are: cs, ds, es, ss. on the 386+ there are also fs & gs
offset registers are: bx, di, si, bp, sp, ip. in 386+ protected mode, any
general
register (not a segment register) can be used as an offset register. (except ip,
which
you can't access.)
registers:
ax, bx, cx and dx are general purpose registers. on a 386+ they are 32 bits; eax,
ebx, ecx and edx. the all can be split up into high and low parts, each being 8
bits.
eax 32 bits
ax 16 bits
ah 8 bits
al 8 bits
this means that you can use ah and al to store different numbers and treat them as
bx (bh/bl): same as ax
cx (ch/cl): same as ax (used for loops)
dx (dh/dl): same as ax (used for multiplication/division)
sp: stack pointer. does just that. points to the current position in the stack.
don't
alter unless you really know what you are doing or want to crash your computer.
the stack:
this is and area of memory that is like a stack of plates. the last one you put on
is the
first one that you take off (lofo). if another piece of data is put on the stack
it grows
downwards.
diagram 1: this shows how the stack is organised
push puts a value onto the stack and pop takes it back off. here's some code. (you
some instructions:
syntax:
mov destination, source
for example:
mov ax,10 ;moves an immediate value into ax
mov bx,cx ;moves value from cx into bx
int: calls a dos or bios function, mainly to do something you would rather not
write a function for e.g. change video mode, open a file etc.
syntax:
int interrupt number
all interrupts need a value to specify what subroutine to use. this is usually in
ah. to
print a message on the screen all you need to do is this:
but first you have to specify what to print. this function needs ds:dx to be a far
pointer to where the string is. the string has to be terminated with a dollar sign
($).
this example shows how it works:
mymessage db "this is a message!$"
.
.
.
mov dx,offset mymessage
mov ax,seg mymessage
mov ds,ax
mov ah,9
int 21h
this is your first assembly program. cut this in and then assemble it. if you are
using
a86, type:
a86 name of file
if you are using turbo assembler then type:
tasm name of file
tlink name of file minus extention
with tlink you can make a .com file by using a /t switch.
this is just a list of some basic assembly instructions that are very important
and are
used often.
syntax:
add operand1,operand2
this adds operand2 to operand1. the answer is stored in operand1. immediate data
cannot be used as operand1 but can be used as operand2.
syntax:
sub operand1,operand2
this subtracts operand2 from operand1. immediate data cannot be used as operand1
but can be used as operand2.
syntax:
mul register or variable
imul register or variable
this multiples the register given by the number in al or ax depending on the size
of
the operand. the answer is given in ax. if the answer is bigger than 16 bits then
the
answer is in dx:ax (the high 16 bits in dx and the low 16 bits in ax).
on a 386, 486 or pentium the eax register can be used and the answer is stored in
edx:eax.
syntax:
div register or variable
idiv register or variable
this works in the same way as mul and imul by dividing the number in ax by the
register or variable given. the answer is stored in two places. al stores the
answer
and the remainder is in ah. if the operand is a 16 bit register than the number in
dx:ax is divided by the operand and the answer is stored in ax and remainder in
dx.
the way we entered the address of the message we wanted to print was a bit
cumbersome. it took three lines and it isn�t the easiest thing to remember
we can replace all this with just one line. this makes the code easier to read and
it
easier to remember. this only works if the data is only in the data is in one
segment i.e.
small memory model.
lea dx,mymessage
or mov dx,offset mymessage
using lea is slightly slower and results in code which is larger.
syntax:
lea destination,source
desination can be any 16 bit register and the source must be a memory operand (bit
of
data in memory). it puts the offset address of the source in the destination.
keyboard input:
we are going to use interrupt 16h, function 00h to read the keyboard. this gets a
key
from the keyboard buffer. if there isn't one, it waits until there is. it returns
the scan
code in ah and the ascii translation in al.
all we need to worry about for now is the ascii value which is in al.
printing a character:
the problem is that we have the key that has been pressed in ah. how do we display
it? we can't use function 9h because for that we need to have already defined the
string
which has to end with a dollar sign. this is what we do instead:
if you want to save the value of ah then push it before and pop it afterwards.
control flow:
firstly, the most basic command:
jmp label
this is the same as goto in basic.
jmp alabel
.
.
alabel:
;code to do something
what do we do if we want to compare somthing. we have just got a key from the user
but we want to do something with it. lets print something out if it is equal to
somethine else. how do we do that? its easy. we use the jump on condition
commands. here is a list of them:
ja
jumps if the first number was above the second number
jae
same as above, but will also jump if they are equal
jb
jumps if the first number was below the second
jbe
same as above, but will also jump if they are equal
jna
jumps if the first number was not above (same as jbe)
jnae
jumps if the first number was not above or the same as (same as jb)
jnb
jumps if the first number was not below (same as jae)
jnbe
jumps if the first number was not below or the same as (same as ja)
jz
jumps if the two numbers were equal
je
same as jz, just a different name
jnz
jumps if the two numbers are not equal
jne
same as above
[note: there are quite a few more but these are the most useful. if you want the
full
list then get a good assembly book]
syntax:
cmp register containing value, a value
jump command destination
the following program is an example of how to use control and input and output.
.model small
.stack ;define a stack
.code
start: ;a good place to start.
end
procedures:
assembly, like c and pascal can have procedures. these are very useful for series
of
commands that have to be repeated often.
proc aprocedure
.
. ;some code to do something
.
ret ;if this is not here then your computer
;will crash
endp aprocedure
you can specify how you want the procedure to be called by adding a far or a
near after the procedure name. otherwise it defaults to the memory model you are
using. for now you are better off not doing this until you become more
experianced. i
usually add a near in as compilers can�t decide between a near and a far very
well.
this means if the jump needs to be far the compiler will warn you and you can
change
it.
.model tiny
.code
org 100h
main proc
jmp start ;skip the data
hi db "hello there!$" ;define a message
start: ;a good place to start.
call display_hi ;call the procedure
mov ax,4c00h ;terminate program and return
;to dos using
int 21h ;interrupt 21h function 4ch
main endp
end main
memory models:
we have been using the .model directive to specify what type of memory model we
use, but what does this mean?
syntax:
.model memorymodel
tiny:
this means that there is only one segment for both code and data. this type of
program can be a .com file.
small:
this means that by default all code is place in one physical segment and likewise
all
data declared in the data segment is also placed in one physical segment. this
means
that all proedures and variables are addressed as near by pointing at offsets
only.
compact:
this means that by default all elements of code (procedures) are placed in one
physical
segment but each element of data can be placed in its own physical segment. this
means that data elements are addressed by pointing at both at the segment and
offset
addresses. code elements (procedures) are near and varaibles are far.
medium:
this is the opposite to compact. data elements are near and procedures are far.
large:
this means that both procedures and variables are far. you have to point at both
the
segment and offset addresses.
flat:
this isn�t used much as it is for 32 bit unsegmented memory space. for this you
need a
dos extender. this is what you would have to use if you were writing a program to
interface with a c/c++ program that used a dos extender such as dos4gw or
pharlap.
macros:
(all code examples given are for macros in turbo assembler. for a86 either see the
macros are very useful for doing something that is done often but for which a
procedure can�t be use. macros are substituted when the program is compiled to the
name_of_macro macro
;
;a sequence of instructions
;
endm
these two examples are for macros that take away the boring job of pushing and
popping certain registers:
saveregs macro
pop ax
pop bx
pop cx
pop dx
endm
restoreregs macro
pop dx
pop cx
pop bx
pop ax
endm
note that the registers are popped in the reverse order to they wer popped.
to use a macro in you program you just use the name of the macro as an ordinary
macro instruction:
saveregs
;some other instructions
restoreregs
this example shows how you can use a macro to save typing in. this macro simply
prints out a variable to the screen.
the only problems with macros is that if you overuse them it leads to you program
getting bigger and bigger and that you have problems with multiple definition of
labels
and variables. the correct way to solve this problem is to use the local directive
for
declaring names inside macros.
syntax:
local name
where �name� is the name of a local variable or label.
if you have comments in a macro everytime you use that macro the comments will be
added again into your source code. this means that it will become unesescarily
long.
the way to get round this is to define comments with a ;; instead of a ;. this
example
illustrates this.
;a normal comment
;;a comment in a macro to save space
syntax:
name_of_macro macro par1,par2,par3
;
;commands go here
endm
this is an example that adds the first and second parameters and puts the result
in the
third:
on the next page there is an example of some a useful macro to exit to dos with a
specified . there are two versions of this program because both a86 and turbo
assembler handle macros differently.
.model small
.stack
.code ;start the code segment
in registers:
this is very easy to do, all you have to do is to is move the parameters into
registers
before calling the procedure. this example adds two numbers together and then
divides by the third it then returns the answer in dx.
this is a good time to use a debugger to find out what your program is actually
doing.
i am going to demonstrate how to use turbo debugger to check if this program is
working properly. first we need to compile this program to either a .exe or .com
file. then type:
td name of file
turbo debugger then loads. you can see the instructions that make up your
programs,
for example the first few lines of this program is shown as:
cs:0000 50 push ax
cs:0001 53 push bx
cs:0002 51 push cx
(this might be slighly different than is shown on your screen but hopefully you
will get
the main idea)
this diagram shows what the turbo debugger� screen looks like
the numbers that are moved into the registers are different that the ones that we
typed
in because they are represented in their hex form (base 16) as this is the easiest
base to
convert to and from binary and that it is easier to understand than binary also.
at the left of this display there is a box showing the contents of the registers.
at this
time all the main registers are empty. now press f7 this means that the first line
of the
program is run. as the first line pushed the ax register into the stack, you can
see that
the stack pointer (sp) has changed. press f7 until the line which contains mov
ax,000a is highlighted. now press it again. now if you look at the box which
contains
the contents of the registers you can see that ax contains a. press it again and
bx
now contains 14, press it again and cx contains 3. now if you press f7 again you
can
see that ax now contains 1e which is a+14. press it again and now ax contains a
again, a being 1e divided by 3 (30/3 = 10). press f7 again and you will see that
dx
now also contains a. press it three more times and you can see that cx,bx and ax
are all set back to their origional values of zero.
to pass parameters through memory all you need to do is copy them to a variable
which is stored in memory. you can use a varable in the same way that you can use
a
register but commands with registers are a lot faster. this table shows the timing
for
the mov command with registers and then variables and then the amount of clock
cycles (the speed - smaller faster) it takes to do them.
instruction
486
386
286
86
mov reg/mem8,reg8
1
2/2
2/3
2/9
mov reg,mem16,reg16
1
2/2
2/3
2/9
mov reg8,reg/mem8
1
2/4
2/5
2/8
mov reg16,reg/mem16
1
2/4
2/5
2/8
mov reg8,imm8
1
2
2
4
mov reg16,imm16
1
2
2
4
mov reg/mem8,imm8
1
2/2
2/3
4/10
mov reg/mem16,imm16
1
2/2
2/3
4/10
these timings are taking from the �borland� Turbo assembler� Quick reference�
this shows that on the 8086 using variables in memory can make the instuction four
times as slow. this means that you should avoid using too many variables
unnecessarily. on the 486 it doesn�t matter as both instructions take the same
amount
of time.
this way may seem more complicated but it is not really suited for small numbers
of
this type of parameters. it is much more useful when dealing with strings or large
this is the most powerful and flexible method of passing parameters. this example
shows the changenumbers procedure that has been rewritten to pass its parameters
through the stack.
mov ax,10 ;first parameter is 10
mov bx,20 ;second parameter is 20
mov cx,3 ;third parameter is 3
push ax ;put first parameter on stack
push bx ;put second parameter on stack
push cx ;put third parameter on stack
call changenumbers
.....
changenumbers proc ;defines start of procedure
push bp
mov bp,sp
mov ax,[bp+8] ;get the parameters from bp
mov bx,[bp+6] ;remember that first it is last out
mov cx,[bp+4] ;so number is larger
add ax,bx ;adds number in bx to ax
div cx ;divides ax by cx
mov dx,ax ;return answer in dx
ret
changenumbers endp ;defines end of procedure
this diagram shows the contents of the stack for a program with two parameters:
to get a parameter from the stack all you need to do is work out where it is. the
last
parameter is at bp+2 and then the next and bp+4.
files can be opened, read and written to. dos has some ways of doing this which
save
us the trouble of writing our own routines. yes, more interrupts. here is a list
of
helpful functions of interrupt 21h that we are going to need to use for our simple
file
viewer.
input:
ah = 3dh
al = access mode
bits 0-2 000 = read only
001 = write only
010 = read/write
bits 4-6 sharing mode (dos 3+)
000 = compatibility mode
001 = deny all (only current program can access file)
010 = deny write (other programs can only read it)
011 = deny read (other programs can only write to it)
100 = deny none
ds:dx = segment:offset of asciiz pathname
output:
cf = 0 function is succesful
ax = handle
cf = 1 error has occured
ax = error code
01h missing file sharing software
02h file not found
03h path not found or file does not exist
04h no handle available
05h access denied
0ch access mode not permitted
what does asciiz mean? an asciiz string is a ascii string with a zero on the end
(instead of a dollar sign).
important: remember to save the file handle it is needed for later.
copy the file handle into another register and don't use that register.
copy it to a variable in memory.
the disadvantages with the first method is that you will have to remember not to
use
the register you saved it in and it wastes a register that can be used for
something more
useful. we are going to use the second. this is how it is done:
input:
ax = 3eh
bx = file handle
output:
cf = 0 function is sucsessful
ax = destroyed
cf = 1 function not sucsessful
ax = error code - 06h file not opened or unautorized handle.
important: don't call this function with a zero handle because that will close the
standard input (the keyboard) and you won't be able to enter anything.
input:
ah = 3fh
bx = handle
cx = number of bytes to be read
ds:dx = segment:offset of a buffer
output:
cf = 0 function is successful
ax = number of bytes read
cf = 1 an error has occured
05h access denied
06h illegal handle or file not opened
if cf = 0 and ax = 0 then the file pointer was already at the end of the file and
no
more can be read. if cf = 0 and ax is smaller than cx then only part was read
because the end of the file was reached or an error occured.
this function can also be used to get input from the keyboard. use a handle of 0,
and it
stops reading after the first carriage return, or once a specified number of
characters
have been read. this is a good and easy method to use to only let the user enter a
note: if you are using a86 this will cause an error. change @data to data to make
it
work.
.model small
.stack
.code
mov ax,@data ;base jaddress of data
mov ds,ax ;segment
;dos using
int 21h ;interrupt 21h function 4ch
erroropening:
mov dx,offset openerror ;display an error
errorreading:
mov dx,offset readerror ;display an error
.data
input:
ah = 3ch
cx = file attribute
bit 0 = 1 read-only file
bit 1 = 1 hidden file
bit 2 = 1 system file
bit 3 = 1 volume (ignored)
bit 4 = 1 reserved (0) - directory
bit 5 = 1 archive bit
bits 6-15 reserved (0)
ds:dx = segment:offset of asciiz pathname
output:
cf = 0 function is successuful
ax = handle
cf = 1 an error has occured
03h path not found
04h no availible handle
05h access denied
important: if a file of the same name exists then it will be lost. make sure that
there
is no file of the same name. this can be done with the function below.
function 4eh: find first matching file
searches for the first file that matches the filename given.
input:
ah = 4eh
cx = file attribute mask (bits can be combined)
bit 0 = 1 read only
bit 1 = 1 hidden
bit 2 = 1 system
bit 3 = 1 volume label
bit 4 = 1 directory
bit 5 = 1 archive
bit 6-15 reserved
ds:dx = segment:offset of asciiz pathname
output:
cf = 0 function is successful
[dta] disk transfer area = findfirst data block
returntodos:
mov ax,4c00h ;terminate program and return to dos
int 21h ;using interrupt 21h function 4ch
error:
mov dx,offset errormessage ;display an error message
;on the screen
mov ah,09h ;using function 09h
int 21h ;call dos service
jmp returntodos ;lets end this now
.data
startmessage db "this program creates a file called�,
�new.txt in the c: directory.$"
endmessage db 0ah,0dh,"file create ok, look at�,
�file to be sure.$"
handle dw ? ;variable to store file handle
errormessage db "an error has occurred!$"
writeme db "hello, this is a test, has it�,
�worked?",0 ;asciiz
filename db "c:\new.txt",0
end
in many programs it is necessary to find out what the dos version is. this could
be
because you are using a dos function that needs the revision to be over a certain
level.
firstly this method simply finds out what the version is.
this function returns the major version number in al and the minor version number
in
ah. for example if it was version 4.01, al would be 4 and ah would be 01. the
problem is that if on dos 5 and higher setver can change the version that is
returned. the way to get round this is to use this method.
this will only work on dos version 5 and above so you need to check using the
former method. this will return the actual version of dos even if setver has
changed the version. this returns the major version in bl and the minor version in
bh.
we have been using a dos service, function 9 of interrupt 21h to print a string on
the
screen. this isn�t too fast nor does it allow us to use different colours or
position the
text. there is another way to print a string to the screen - direct to memory.
this is
harder as you have to set up everything manually but it has a lot of benifits
mainly
speed.
in this procedure there was several things that you have not come across before.
firsly
the lines:
this is just an easier way of pushing and popping more than one register. when
tasm
(or a86) compiles these lines it translates it into separate pushes an pops. this
way
just saves you time typing and makes it easier to understand.
note: to make these lines compile in a86 you need to put commas (,) in between the
registers.
this line might cause difficulty to you at first but they are quite easy to
understand.
what this does is to move the number stored in ds at the location stored in si
into
ah. it is easier to think of ds being like an array in this command. it is the
same as
this line in c.
ah = ds[si];
shifts:
there are four different ways of shifting numbers either left or right one binary
position.
syntax:
shl operand1,operand2
note: the 8086 cannot have the value of opperand2 other than 1. 286/386 cannot
have operand2 higher than 31.
loops:
using loop is a better way of making a loop then using jmp�s. you place the amount
of times you want it to loop in the cx register and every time it reackes the loop
statement it decrements cx (cx-1) and then does a short jump to the label
indicated.
a short jum means that it can only 128 bytes before or 127 bytes after the loop
instuction.
syntax:
loop label
mode 13h is only availible on vga, mcga cards and above. the reason that i am
talking about this card is that it is very easy to use for graphics because of how
the
memory is arranged.
it would be polite to tell the user if his computer cannot support mode 13h
instead of
just crashing his computer without warning. this is how it is done.
checkmode13h:
;returns: dx=0 not supported, dx=1 supported
mov ax,1a00h ;request video info for vga
int 10h ;get display combination code
cmp al,1ah ;is vga or mcga present?
je mode13hsupported ;mode 13h is supported
xor dx,dx ;mode 13h isn�t supported dx=0
mode13hsupported:
mov dx,1 ;return mode13h supported
just use this to check if mode 13h is supported at the beginning of your program
to
make sure that you can go into that mode.
note: i have not tested this on a computer that doesn�t hav vga as i don�t have
any.
in theory this should work but you should test this on computers that don�t have
vga
and see if it works this out.
once we are in mode 13h and have finished what we are doing we need to we need to
set it to the video mode that it was in previously. this is done in two stages.
firstly we
need to save the video mode and then reset it to that mode.
videomode db ?
....
mov ah,0fh ;function 0fh - get current mode
int 10h ;bios video service call
mov videomode,al ;save current mode
now that we can get into mode 13h lets do something. firstly lets put some pixels
on
the screen.
input:
ah = 0ch
al = color of the dot
cx = screen column (x coordinate)
dx = screen row (y coordinate)
output:
nothing except pixel on screen.
note: this function performes exclusive or (xor) with the new color value and the
current context of the pixel of bit 7 of al is set.
this example puts a pixel into the middle of the screen in a the color grey. the
problem with this method is that calling interrupts is really slow and should be
avoided
in speed critical areas. with pixel plotting if you wanted to display a picture
the size of
the screen you would have to call this procedure 64,000 times (320 x 200).
some optimizations:
this method isn�t too fast and we could make it a lot faster. how? by writing
direct to
video memory. this is done quite easily.
the vga memory starts at 0a000h. to work out where each pixel goes you use this
simple formula:
this procedure is quite a fast way to put a pixel onto the screen. thanks go to
denthor
of asphyxia as i based this on his code.
putpixel proc
.286 ;enable 286 instructions for shifts remove if
;you have less than an 286.
;========================================================
;input: bx=x postion
; dx=y position
; cl=colour
;output: none
;========================================================
;this can be optimized by not pushing ax if you don�t
;need to save it. for a86 change push ds es ax to push
;ds,es,ax and do the same thing with pop.
push ds es ax ;save ds,es and ax
mov ax,0a000h ;ax contains address of video
mov es,ax ;es contains address of video
mov di,bx ;move x position into di
mov bx,dx ;mov y postion into bx
shl dx,8 ;shift dx 8 places to the left
shl bx,6 ;shift bx 6 places to the left
add dx,bx ;add dx and bx together
add di,bx ;add di and bx together
mov al,cl ;put colour in al
stosb ;transfer to video memory
pop ax es ds ;restore ds,es and ax
ret
putpixel endp
thank you for reading. i hope that you have learnt something from this. if you
need any more help then email me.