In assembly there are some very useful instructions for dealing with strings. Here is a list of the instructions and the syntax for using them:
MOV*
Move String: moves byte, word or double word at DS:SI to ES:DI
Syntax:
movsb ; move byte movsw ; move word movsd ; move double word
CMPS*
Compare string: compares byte, word or double word at DS:SI to ES:DI
Syntax:
cmpsb ; compare byte cmpsw ; compare word cmpsd ; compare double word
Note: This instruction is normally used with the REP prefix.
SCAS*
Search string: search for AL, AX, or EAX in string at ES:DI
Syntax:
scasb ; search for AL scasw ; search for AX scasd ; search for EAX
Note: This instruction is usually used with the REPZ or REPNZ prefix.
REP
Prefix for string instruction repeats instruction CX times
Syntax:
rep StringInstruction
STOS*
Move byte, word or double word from AL, AX or EAX to ES:DI
Syntax:
stosb ; move AL into ES:DI stosw ; move AX into ES:DI stosd ; move EAX into ES:DI
LODS*
Move byte, word or double word from DS:SI to AL, AX or EAX
Syntax:
lodsb ; move ES:DI into AL lodsw ; move ES:DI into AX lodsd ; move ES:DI into EAX
The next example demonstrates how to use these instructions.
.model small .stack .code mov ax,@data ; ax points to of data segment mov ds,ax ; put it into ds mov es,ax ; put it in es too mov ah,9 ; function 9 - display string mov dx,OFFSET Message1 ; ds:dx points to message int 21h ; call dos function cld ; clear direction flag mov si,OFFSET String1 ; make ds:si point to String1 mov di,OFFSET String2 ; make es:di point to String2 mov cx,18 ; length of strings rep movsb ; copy string1 into string2 mov ah,9 ; function 9 - display string mov dx,OFFSET Message2 ; ds:dx points to message int 21h ; call dos function mov dx,OFFSET String1 ; display String1 int 21h ; call DOS service mov dx,OFFSET Message3 ; ds:dx points to message int 21h ; call dos function mov dx,OFFSET String2 ; display String2 int 21h ; call DOS service mov si,OFFSET Diff1 ; make ds:si point to Diff1 mov di,OFFSET Diff2 ; make es:di point to Diff2 mov cx,39 ; length of strings repz cmpsb ; compare strings jnz Not_Equal ; jump if they are not the same mov ah,9 ; function 9 - display string mov dx,OFFSET Message4 ; ds:dx points to message int 21h ; call dos function jmp Next_Operation Not_Equal: mov ah,9 ; function 9 - display string mov dx,OFFSET Message5 ; ds:dx points to message int 21h ; call dos function Next_Operation: mov di,OFFSET SearchString ; make es:di point to string mov cx,36 ; length of string mov al,'H' ; character to search for repne scasb ; find first match jnz Not_Found mov ah,9 ; function 9 - display string mov dx,OFFSET Message6 ; ds:dx points to message int 21h ; call dos function jmp Lodsb_Example Not_Found: mov ah,9 ; function 9 - display string mov dx,OFFSET Message7 ; ds:dx points to message int 21h ; call dos function Lodsb_Example: mov ah,9 ; function 9 - display string mov dx,OFFSET NewLine ; ds:dx points to message int 21h ; call dos function mov cx,17 ; length of string mov si,OFFSET Message ; DS:SI - address of string xor bh,bh ; video page - 0 mov ah,0Eh ; function 0Eh - write character NextChar: lodsb ; AL = next character in string int 10h ; call BIOS service loop NextChar mov ax,4C00h ; return to DOS int 21h .data CR equ 13 LF equ 10 NewLine db CR,LF,"$" String1 db "This is a string!$" String2 db 18 dup(0) Diff1 db "This string is nearly the same as Diff2$" Diff2 db "This string is nearly the same as Diff1$" Equal1 db "The strings are equal$" Equal2 db "The strings are not equal$" Message db "This is a message" SearchString db "1293ijdkfjiu938uHello983fjkfjsi98934$" Message1 db "Using String instructions example program.$" Message2 db CR,LF,"String1 is now: $" Message3 db CR,LF,"String2 is now: $" Message4 db CR,LF,"Strings are equal!$" Message5 db CR,LF,"Strings are not equal!$" Message6 db CR,LF,"Character was found.$" Message7 db CR,LF,"Character was not found.$" 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.
mov ah,30h ; function 30h - get MS-DOS version int 21h ; call DOS function
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.
mov ah,33h ; function 33h - actual DOS version mov al,06h ; subfunction 06h int 21h ; call interrupt 21h
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.
You can push and pop more than one register on a line in TASM and A86. This makes your code easier to understand.
push ax bx cx dx ; save registers pop dx cx bx ax ; restore registers
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.
PUSHA is a useful instruction that pushes all general purpose registers onto the stack. It accomplishes the same as the following:
temp = SP push ax push cx push dx push bx push temp push bp push si push di
The main advantage is that it is less typing, a smaller instruction and it is a lot faster. POPA does the reverse and pops these registers off the stack. PUSHAD and POPAD do the same but with the 32-bit registers ESP, EAX, ECX, EDX, EBX, EBP, ESI and EDI.
Using MUL's and DIV's is very slow and should be only used when speed is not needed. For faster multiplication and division you can shift numbers left or right one or more binary positions. Each shift is to a power of 2. This is the same as the «
and »
operators in C.
There are four different ways of shifting numbers either left or right one binary position.
SHL
Unsigned multiple by two SHR
Unsigned divide by two SAR
Signed divide by two SAL
same as SHL The syntax for all four is the same:
SHL operand1,operand2
Note: The 8086 cannot have the value of operand2
other than 1. The 286/386 cannot have operand2
higher than 31.
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 reaches the loop statement it decrements CX (CX-1) and then does a short jump to the label indicated. A short jump means that it can only 128 bytes before or 127 bytes after the LOOP instruction.
Syntax:
mov cx,100 ; 100 times to loop Label: . . . Loop Label: ; decrement CX and loop to Label
This is exactly the same as the following piece of code without using loop:
mov cx,100 ; 100 times to loop Label: dec cx ; CX = CX-1 jnz Label ; continue until done
Which do you think is easier to understand? Using DEC/JNZ is faster on 486's and above and it is useful as you don't have to use CX.
This works because JNZ will jump if the zero flag has not been set. Setting CX to 0 will set this flag.
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 what the program is actually doing. First we need a program which we can look at.
; example program to demonstrate how to use a debugger .model tiny .code org 100h start: push ax ; save value of ax push bx ; save value of bx push cx ; save value of cx mov ax,10 ; first parameter is 10 mov bx,20 ; second parameter is 20 mov cx,3 ; third parameter is 3 Call ChangeNumbers pop cx ; restore cx pop bx ; restore bx pop ax ; restore dx mov ax,4C00h ; exit to dos int 21h ChangeNumbers PROC add ax,bx ; adds number in bx to ax mul cx ; multiply ax by cx mov dx,ax ; return answer in dx ret ChangeNumbers ENDP end start
Now compile it to a .COM file and then type:
td name of file
Turbo Debugger then loads. You can see the instructions that make up your program.
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
Some useful keys for Turbo Debugger:
F5 | Size Window |
---|---|
F7 | Next Instruction |
F9 | Run |
ALT F4 | Step Backwards |
The numbers that are moved into the registers are different that the ones that in the source code 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 5A, 1E multiplied by 3. Press F7 again and you will see that DX now also contains 5A. Press it three more times and you can see that CX, BX and AX are all set back to their original values of zero.