BASICS OF ASSEMBLER PROGRAMMING.
Turbo Assembler's reserved words have a special meaning to the assembler
and can not be used by the programmer for defining equates or labels or
procedure names. Table 4.1 on page 82 of the User's Guide (version 1.0)
lists the 250 reserved words, which include operators (+,*,-,/),
directives (.DATA,MASM) and predefined symbols (??time,@WordSize).
The Format of an Assembly Language source code line.
The format is: <label> <instruction/directive> <operand(s)> <;comment>
where <label> is an optional symbolic name.
<instruction/directive> is either the mnemonic for an instruction
or a directive.
<operand(s)> is a combination of zero, one, two, or more
constants, memory references, or text strings, as
required by a particular instruction or directive.
<;comment> is an optional comment.
Labels are the names used for referring to numbers and characters strings
or memory locations within a program (memory variables, values and
locations of particular instructions), as the following commented program
.MODEL small ;segment directive for memory size.
.STACK 200h ;segment directive for stack size.
.DATA ;directive for start of data segment.
FactorialValue DW ? ;labels defining the addresses of two word-
Factorial DW ? ;sized data variables, uninitialized.
.CODE ;directive for start of code segment.
FiveFactorial PROC ;name of subroutine or procedure.
mov ax,@data ;load ax register with address of data for
mov ds,ax ;transfer to data segment register.
mov [FactorialValue],1 ;move 1 into variable FactorialValue.
mov [Factorial],2 ;move 2 into variable Factorial.
mov cx,4 ;move 4 to count register to control loops.
FiveFactorialLoop: ;label giving address of next instruction.
mov ax,[FactorialValue] ;move variable FactorialValue to ax register
mul [Factorial] ;for multiplication by variable Factorial.
mov [FactorialValue],ax ;move result of multiplication to variable.
inc [Factorial] ;increment variable Factorial by 1.
loop FiveFactorialLoop ;loop instruction to label FiveFactorialLoop.
ret ;return from subroutine.
FiveFactorial ENDP ;directive to mark the end of the procedure.
END ;directive to end assembler code.
The labels FactorialValue and Factorial are equivalent to the addresses of
two 16-bit variables. The label FiveFactorial is the name of the
subroutine (or function or procedure) containing code called from another
part of the program. Finally, the label FiveFactorialLoop is equivalent to
the address of the instruction: move ax,[FactorialValue]
so the LOOP at the end of the code can branch back to that instruction.
Labels can consist of the following characters:
A-Z a-z _ @ $ ? 0-9
The digits 0-9 cannot be used as the first character of a label. A single
$ or ? has a special meaning, so they may not be employed as a user symbol
name. Each label must be defined only once (see exceptions on page 84 of
User's Guide version 1.0). Labels may be used as operands any number of
A label may appear on a line by itself, terminating in a colon, in which
case it is the address of the instruction or directive on the next line of
the program. See pp 84-7 of the User's Guide (version 1.0) for more
details of labels.
Instruction Mnemonics and Directives.
Typical instruction mnemonics are: MOV, ADD, MUL, JMP, referring to data
movement, addition, multiplication and branching. A full list of mnemonics
is given in 'The 8086 Book' and is presented separately in the notes on the
In assembler, the directives indicate the size and location of the segments
of memory used for code, data and the stack, etc. Segment directives are
discussed in a separate note. There is also an END directive, which is
used to mark the end of the program's source code and is essential. The
END directive can also optionally indicate where execution should begin
when the program is run, by giving a label after END (eg END ProgramStart),
which may not always be at the start of the CODE segment (see example on
page 88 of the User's Guide, version 1.0).
Operands indicate which registers, parameters, memory locations, constant
values, labels and text strings to associate with the instruction or
directive. Instructions require zero, one, two, or even more operands,
whilst directives can accept as many operands as will fit on one line.
nop ;no operation - no operands
push ax ;push AX onto the stack
mov ax,bx ;move contents of BX into AX
More details are given in the notes on 8086 Instruction Set and Operands.
Data Allocation and Movement.
The data definition directives are:
DB 1 byte 0 to 255
DW 2 bytes = 1 word 0 to 65535 or -32768 to +32767
DD 4 bytes = 1 doubleword 0 to 4,294,967,295
DQ 8 bytes = 1 quadword
DT 10 bytes
Data can be defined and initialized as follows:
ByteVar DB 'Z'
WordVar DW 101b
DWordVar DD 2BFh
QWordVar DQ 307o
TWordVar DT 100
SampleArray DW 0, 1, 2, 3, 4
String DB 'ABCD'
BlankArray DW 100h DUP (0) ;DUP operator - 256 words init. to 0
UnInit DB 10 DUP (?) ;10 bytes starting at UnInit, not set
to any value.
Data is moved with the MOV instruction, which actually copies the data
without affecting the source, as in the following examples:
mov ax,0 ;store constant 0 in AX register
mov bx,9 ;store constant 9 in BX register
mov ax,bx ;copy the contents of BX to AX, so both contain 9
The source (right hand) operand to MOV can be:
an expression that resolves to a constant
a general purpose register
a memory location
The destination (left hand) operand to MOV can be:
a general-purpose register
a memory location
More detail is given in the notes 'Data Allocation and Movement'.
The 8086 CPU can perform 8- and 16-bit signed and unsigned addition,
subtraction, multiplication and division of integers and has fast
instructions for incrementing and decrementing operands. An 8087
coprocessor performs floating-point arithmetic with a single speedy
instruction, otherwise floating-point operations must be performed by a
slow involved series of shift, add and test instructions on the 8086 chip.
Operations with values larger than 16-bits require the use of multiple
ADD adds the contents of the source (right-hand) operand to the contents of
the destination operand and stores result back in the destination operand.
SUB similarly subtracts the source operand from the destination.
ADC (add with carry) and SBB (subtract with borrow) are used for 32-bit
operands and take account of the value of the carry flag set by a previous
To add two values that are larger than a word, add the lower (least
significant) words of the values together first with ADD, then add the
remaining words of the values together with one or more ADC instructions,
adding the most-significant words last. Thus to add doubleword in CX:BX to
doubleword in DX:AX the code is:
Similarly the code for a doubleword subtraction is: sub ax,bx
Of course, care must be taken not to intersperse any instruction that would
change the carry flag during such operations.
INC and DEC are used to increment and decrement registers and memory
variables by 1, efficiently. (eg inc bx rather than add bx,1)
MUL multiplies two 8- or 16-bit unsigned factors together, generating a 16-
or 32-bit product. For 8-bit-by-8-bit multiplication, one factor is stored
in AL and the other in any 8-bit general-purpose register or memory
operand. The result is stored in AX. For 16-bit-by-16-bit multiplication,
one factor is stored in AX and the other in any 16-bit register or memory
operand. The result is stored in DX:AX, with most significant bits in DX.
IMUL is similarly used for 8- or 16-bit signed factors.
DIV provides for 16-bit-by-8-bit division or 32-bit-by-16-bit division.
For the former the dividend is stored in AX and the 8-bit divisor in any
8-bit general-purpose register or memory variable. The 8-bit quotient is
placed in AL and the remainder in AH. If the quotient is too large (<255)
interrupt 0 (divide-by-zero) is generated.
For 32-bit-by-16-bit division the dividend is stored in DX:AX, the divisor
in any 16-bit general-purpose register or memory variable, with the
quotient in AX and the remainder in DX.
IDIV is similar to DIV, but for use with signed operands.
NEG reverses the sign of the contents of a general-purpose register or
AND, OR, XOR and NOT perform the appropriate bit-wise operations on the
corresponding bits of the source operands. These operations are used to
manipulate the individual bits within a byte or word and for performing
Boolean algebra. See pages 157-159 of the User's Guide (version 1.0)
Shifts and Rotates.
The 8086 processor provides means for moving bits left or right in a
register or memory variable. The logical shift left SHL moves each bit in
the destination one place left or towards the most-significant bit, with a
zero shifted into the least-significant bit and the most-significant bit
moving to the carry flag. Multiplying by shifts is faster than using MUL.
Normally the instruction takes the form: shl dx,1
but multiple shifts can be effected by use of the Cl register as follows:
SHR (shift right) is similar to SHL, shifting the bits right by 1 or CL
bits, with 0 moving into the most significant bit and the least-significant
bit moving to the carry flag.
SAR (arithmetic shift right) is similar to SHR, except that the most-
significant bit is shifted right and then back into itself. This has the
effect of preserving the sign of the operand and so is useful for
performing signed division by powers or 2.
There are four rotate instructions: ROR, ROL, RCR and RCL. The first is
like SHR except that the least-significant bit is shifted back into the
most- significant bit, as well as to the carry flag. ROL reverses the
action of ROR.
RCR and RCL are rotates involving the carry flag in the rotation action,
with the carry value shifted into the most-signicant bit in the case of RCR
and the carry value shifted into the least-significant bit with RCL.
Loops and Jumps.
To be of real value a computer must allow the program to jump or branch to
an instruction other than the one that follows immediately. There is thus
the unconditional jump instruction JMP so that: jmp RemoteInstruction
sets IP to the offset of the label RemoteInstruction. Whereas, JMP uses a
16-bit displacement, JMP SHORT uses an 8-bit displacement.
There are also 30 conditional jumps, listed on page 169 of the User's Guide
(version 1.0), frequently used with other instructions like INC, DEC and CMP.
These include: JE jump if equal to }
JL jump if less than }
JNGE jump if not greater than or equal to }
All the other mathematical comparisons are covered, together with checks
for parity, sign, carry and overflow. The appropriate flags CF, ZF, SF, PF
and OF are checked in these processes.
The looping instruction LOOP achieves the effect of the two instructions,
DEC with JNZ, the CX register being used in the automatic counting process.
LOOPE and LOOPNE are similar except that LOOPE will end the loop if either
CX = 0 or ZF = 1 and LOOPNE will end the loop if either CX = 0 or ZF = 0.
JCXZ branches only if CX = 0 and is a useful way to test CX before
beginning a loop.
Subroutines allow programs to be built up in a convenient modular way. The
code that calls a subroutine executes a CALL <SubroutineName> instruction,
which pushes the address of the next instruction onto the stack and then
loads the IP with the address of the desired subroutine, thereby branching
to that subroutine. The subroutine then executes its own code, eventually
including the RET instruction, which pops into IP the address pushed by the
original CALL instruction to allow resumption at the next instruction after
Subroutines may call other subroutines or even call themselves (Recursion).
The program LINKLIST.ASM is an example of code, which contains a subroutine
push ax ;
push dx ;preserve registers in this subroutine
mov ah,9 ;DOS print string function #
mov dx,bx ;point DS:DX to the string to print
int 21h ;invoke DOS to print the string
pop dx ;restore registers we changed
pop ax ;
The register bx may contain the address of different strings, so that the
subroutine can be used several times.
PROC is used to start a procedure, whose label is the name preceding PROC.
After the ret instruction the procedure label is repeated with ENDP to
indicate the end of the subroutine.
Procedures are either defined as NEAR (within the current code segment) or
FAR (in another 64k segment). For NEAR calls only the IP is loaded with a
new value, whereas for a FAR call both CS and IP are loaded with new
values, which therefore means that CS and IP must be pushed onto the stack
before a FAR call compared with just IP for NEAR calls.
Both the call and the return processes are slower with FAR calls, so
generally NEAR calls are better. If using the simplified segment
directives, it is only necessary to specify PROC without a directive (NEAR
or FAR) as the memory model is already selected by the .MODEL directive
(see notes on Directives).
In assembler, parameter passing to subroutines is achieved via registers,
as in the above example, where the BX register is used to pass a pointer to
the PrintString subroutine.
Whereas with high-level languages there are defined conventions for
returning values from subroutines, there is freedom in assembler code to
choose any register or even status information in the flags. It is
recommended that the convention is established to always return 8-bit
values via AL and 16-bit values via AX. Of course care must be taken not
to destroy vital information in the return register and for other returns
memory location indicated by pointers are recommended.
It is possible to push all registers before a subroutine call, but this
slows the program, so that the best approach is to comment each procedure
with a list of registers destroyed and to refer to these comments whenever
a CALL instruction is used, so that only vital information is pushed onto