Turinys

Strings, shifts and debuggers

String Instructions

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*

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*

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*

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

REP Prefix for string instruction repeats instruction CX times

Syntax:

rep StringInstruction

STOS*

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*

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

Example

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

How to find out the DOS Version

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.

Multiple Pushes and Pops

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.

The PUSHA/PUSHAD and POPA/POPAD Instructions

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 Shifts for faster Multiplication and Division

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.

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.

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 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.

How to use a debugger

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.