Hi I was assigned to this (MS DOS 16 bit :( ) exercise:
"Create a program which asks the user for a number (1-3 digit, range 0-255) and stores it in memory. Make a check on every error".
I decided to break the program in 2 routines: one just takes the string and another makes a check on the entered string.
Could you please take a look at the first one and tell me if it's ok?It's not very complicated/long:
;arguments: BX (contains memory base address where to store string)
PROC_READ_NUMBER proc near
push AX
push DI
xor DI,DI
mov AH,1
READ_CHAR: int 21h
cmp al,08h ;verify if a backspace is pressed
jne STORE_CHAR
cmp DI,0
je READ_CHAR
dec DI ;if DI is not zero decrement it
jmp READ_CHAR
STORE_CHAR: mov [BX+DI],al
inc DI
cmp al,0dh ;if it isn't carrier (aka enter pressed) continue reading
jne READ_CHAR
mov [BX+DI],0Ah ;put line feed
mov [BX+DI+1],'$' ;put end string terminator (for further visualization using dos output string function)
mov al, 0Ah
mov ah, 0eh ;line feed on video, this way next string will go on next line
int 10h
pop DI
pop ax
ret
PROC_READ_NUMBER endp
I think it's already good, the only problem is that when user press backspace the current char is not deleted. Furthermore if user presses backspace when SI=0 cursor overrides previous string (though SI=0 control makes sure I don't go in another memory area other than string reserved one).
For example if I print
QuoteEnter a number:
pressing too much times backspace makes cursor go on this string !
mov AH,1
READ_CHAR: int 21h
ouch !!!!
whenever you jump back up to READ_CHAR, you should reload AH with 1
the contents of that register are likely destroyed
READ_CHAR:
mov AH,1
int 21h
with DOS interrupts, it is hard to know which registers are preserved and which are not - lol
it takes experience, is all
it varies widely from one function to the next
for a beginner, the best way to go is to assume that all registers are destroyed when using INT
unless you find info otherwise :P
Ralf Brown keeps a list of DOS interrupts
but, i don't remember seeing to much info on register preservation in there
show me all the code :P
Yes I know it, but I already checked it out so other AL no other register is modified!
Can you help me with the rest of the function?The backspace thing is really irritating...how can I clean the characters when I press backspace?
For example if I write
Quote123
and press bs I'd like to have
Quote12
, not 123 with cursor on 3...and how can I deactivate bs when I'm at 1?If not the cursor will go on the previous string...
Code is really simple: I call the DOS print string function on a the string "Insert a number: $", then I call this function.
well - the way i did it was to display a backspace, then a space, then another backspace
the first backspace and the space get rid of the old char
the second backspace is to move the cursor
as for backing up over previous text, not sure about that
i'd have to play with the code
could you post all of it so i don't have to write it ?
Sure thing :
;----------------------------------------------------------------------------------------
SSEG SEGMENT PARA STACK 'STACK'
db 128 dup(?)
SSEG ENDS
;-----------------------------------------------------------------------------------------
DSEG segment para public 'DATA'
FIRSTR: db 'First number: $'
SECSTR: db 'Second number: $'
n1 db 20 dup('$') ;area which will contain the first number
n2 db 20 dup('$') ;area for second number
DSEG ENDS
;-----------------------------------------------------------------------------------
CSEG SEGMENT PARA PUBLIC 'CODE'
;----------------------------------------------------------------------------------------------------------------------
; Main program
;----------------------------------------------------------------------------------------------------------------------
MAIN:
mov ax,DSEG
mov ds,ax
mov dx, offset FIRSTR
call PROC_PRINT_STRING
mov bx, offset n1
call PROC_READ_NUMBER
mov dx, offset SECSTR
call PROC_PRINT_STRING
mov bx, offset n2
call PROC_READ_NUMBER
mov ah,4ch
int 21h
; Exit program, return to OS
;#######################################################################################
;prints the string which address is contained in DX
PROC_PRINT_STRING proc near
push ax
mov ah,9h
int 21h
pop ax
ret
PROC_PRINT_STRING endp
;################################################################
; arguments: bx (area which will contain number)
PROC_READ_NUMBER proc near
push ax
push DI
mov ah,1
READ_CHAR: int 21h
cmp al,08h ;verify if a backspace was pressed
jne STORE_CHAR
cmp DI,0
je READ_CHAR
dec DI
jmp READ_CHAR
STORE_CHAR: mov [BX+DI],al
inc DI
cmp al,0dh
jne READ_CHAR
mov [BX+DI],0Ah ;line feed
mov [BX+DI+1],'$'
mov al, 0Ah
mov ah, 0eh ;line feed on video
int 10h
pop DI
pop ax
ret
PROC_READ_NUMBER endp
CSEG ends
;---------------------------------------------------------------------------------------
ASSUME CS:CSEG,DS:DSEG,SS:SSEG
end MAIN
hang on - there is no check for buffer overflow :P
the buffers are 20 bytes long
that means you can fit 17 characters, 1 carriage return, 1 line feed, and 1 terminator into the buffer
normally, we would write the routine so the buffer could be variable length
then, we would pass the length
you did not write it that way, so i limit it to 17 characters
here we go...
.MODEL Small
;----------------------------------------------------------------------------------------
SSEG SEGMENT PARA STACK 'STACK'
dw 256 dup(?)
SSEG ENDS
;-----------------------------------------------------------------------------------------
DSEG SEGMENT PARA PUBLIC 'DATA'
FIRSTR db 'First number: $'
SECSTR db 'Second number: $'
n1 db 20 dup('$') ;area which will contain the first number
n2 db 20 dup('$') ;area for second number
DSEG ENDS
;-----------------------------------------------------------------------------------
CSEG SEGMENT PARA PUBLIC 'CODE'
ASSUME CS:CSEG,DS:DSEG,SS:SSEG
;----------------------------------------------------------------------------------------------------------------------
; Main program
;----------------------------------------------------------------------------------------------------------------------
MAIN:
mov ax,DSEG
mov ds,ax
mov dx,offset FIRSTR
call PROC_PRINT_STRING
mov bx,offset n1
call PROC_READ_NUMBER
mov dx,offset SECSTR
call PROC_PRINT_STRING
mov bx,offset n2
call PROC_READ_NUMBER
mov ax,4C00h
int 21h ;Exit program, return to OS
;#######################################################################################
;prints the string which address is contained in DX
PROC_PRINT_STRING proc near
push ax
mov ah,9
int 21h
pop ax
ret
PROC_PRINT_STRING endp
;################################################################
; arguments: bx (area which will contain number)
PROC_READ_NUMBER proc near
push ax
push DI
mov DI,0
READ_CHAR:
mov ah,1
int 21h
cmp al,8 ;verify if a backspace was pressed
jne STORE_CHAR
cmp DI,0
jne BACKSPACE_CHAR
push BX
mov bh,0
mov ah,8 ;get character at cursor
int 10h
pop BX
mov ah,0Eh ;re-display the character
int 10h
jmp READ_CHAR
BACKSPACE_CHAR:
dec DI
mov ax,0E20h ;display a space
int 10h
mov ax,0E08h ;display a backspace
int 10h
jmp READ_CHAR
STORE_CHAR:
cmp al,0Dh
je SAVE_CHAR
cmp DI,17
jb SAVE_CHAR
mov ax,0E08h ;display a backspace
int 10h
mov ax,0E20h ;display a space
int 10h
mov ax,0E08h ;display a backspace
int 10h
jmp READ_CHAR
SAVE_CHAR:
mov [BX+DI],al
inc DI
cmp al,0Dh
jne READ_CHAR
mov word ptr [BX+DI],240Ah ;line feed and terminator
mov ax,0E0Ah ;line feed on video
int 10h
pop DI
pop ax
ret
PROC_READ_NUMBER endp
CSEG ENDS
;---------------------------------------------------------------------------------------
END MAIN
there are many ways to improve on this code
you should look into the STOSB instruction, for example
also, there are shortcuts that can be used so you do not have to open and close all those segments
but, i didn't want to make 100 changes
the code would not be recognizable :P
as you become more proficient, you will learn those things
the 16-bit sub-forum has many code examples to learn from
Thanks a bunch dave you really saved my day!
By the way,as in the exercise is asked to have a 3-digit number (in ascii format, so each digit is a letter), I think I could modify the buffer overflow check with 3 instead of 17 (using a buffer of 6, 3 for the number +0D+0A+$), what do you think?
Moreover some little questions ^^
1)
Quotemov word ptr [BX+DI],240Ah ;line feed and terminator
this was because 8086 is little endian?I would've put 0A24 instead
2)Stosb isn't made to set a memory area to the same constant value?I read it's used to initialize, for example, with zeros...so it's not for this case...am I wrong?
3)I like writing every segment...it mades my mind clearer about what I am doing...is there a valid reason to use specific assembler directives?
STOSB puts the value in AL into the address at which DI points to, then increments DI by one. You are talking about filling a memory area with a value (Usually but not always zero) contained in al. This is achieved by placing REP in front of STOSB & a count in CX of the number of bytes to be written by that value.
Hi,
1) Right. Little endian. The bytes in registers are "numbered"
right to left (sort of). Most memory display programs go left
to right. So using words and double words to make a string
of bytes takes some getting used to.
2) The string instruction STOSB works along the following
lines.
; Use string instuction.
MOV DI,OFFSET Destination
MOV AL,Value
STOSB
; Use other instuctions to do the same thing.
MOV DI,OFFSET Destination
MOV AL,Value
MOV [Value],AL
INC DI
; Use string instuction to clear memory.
MOV DI,OFFSET Destination
MOV CX,Count
MOV AL,0
REP STOSB
; Use other instuctions to do the same thing.
MOV DI,OFFSET Destination
MOV CX,Count
MOV AL,0
Label_1:
MOV [Value],AL
INC DI
LOOP Label_1:
For STOSB it's not a big deal, but CMPS and MOVS are a
bit more complex.
3) You can use the .MODEL directive or not. Don't know why
Dave put one in as you had everything defined already. The
basic idea seems to be less typing and standardized results.
And of course personal preferences.
Regards,
Steve N.
here is an example with some of the changes we mentioned
for the keyboard entry routine, you must now pass the length of the buffer in CX
the address of the buffer is passed in DI
also, the routine returns the number of characters entered by the user in CX
STOSB is used
simple segments are used
the buffers are uninitialized
AX is not preserved
.MODEL Small
.STACK 512
;-----------------------------------------------------------------------------------
.DATA
FIRSTR db 'First number: $'
SECSTR db 'Second number: $'
;-----------------------------------------------------------------------------------
.DATA?
n1 db 20 dup(?) ;area which will contain the first number
n2 db 20 dup(?) ;area for second number
;-----------------------------------------------------------------------------------
.CODE
ASSUME DS:DGROUP
MAIN PROC FAR
mov ax,@DATA
mov ds,ax
mov es,ax
mov dx,offset FIRSTR
call PROC_PRINT_STRING
mov di,offset n1
mov cx,sizeof n1
call PROC_READ_NUMBER
mov dx,offset SECSTR
call PROC_PRINT_STRING
mov di,offset n2
mov cx,sizeof n2
call PROC_READ_NUMBER
mov ax,4C00h
int 21h ;Exit program, return to OS
MAIN ENDP
;-----------------------------------------------------------------------------------
PROC_PRINT_STRING PROC NEAR
;prints the string which address is contained in DX
mov ah,9
int 21h
ret
PROC_PRINT_STRING ENDP
;-----------------------------------------------------------------------------------
PROC_READ_NUMBER PROC NEAR
;arguments: DI (area which will contain number)
;CX = buffer length
;
;returns: CX = number of characters entered (not including cr/lf/term)
; DI preserved
sub cx,3
jnc BUFF_OK
ret
BUFF_OK:
push di
push bp
mov bp,sp
push di ;[BP-2] = buffer start
add cx,di
push cx ;[BP-4] = buffer end
READ_CHAR:
mov ah,1
int 21h
cmp al,8 ;verify if a backspace was pressed
jnz STORE_CHAR
cmp di,[bp-2] ;[BP-2] = buffer start
ja BACKSPACE_CHAR
mov bh,0
mov ah,8 ;get character at cursor
int 10h
mov ah,0Eh ;re-display the character
int 10h
jmp READ_CHAR
BACKSPACE_CHAR:
dec di
mov ax,0E20h ;display a space
int 10h
mov ax,0E08h ;display a backspace
int 10h
jmp READ_CHAR
STORE_CHAR:
cmp al,0Dh
jz SAVE_CHAR
cmp di,[bp-4] ;[BP-4] = buffer end
jb SAVE_CHAR
mov ax,0E08h ;display a backspace
int 10h
mov ax,0E20h ;display a space
int 10h
mov ax,0E08h ;display a backspace
int 10h
jmp READ_CHAR
SAVE_CHAR:
stosb
cmp al,0Dh
jnz READ_CHAR
mov word ptr [di],240Ah ;line feed and terminator
mov ax,0E0Ah ;display line feed
int 10h
dec di
pop ax ;discard buffer end
pop cx ;CX = buffer start
sub di,cx
mov cx,di ;CX = user string length
pop bp
pop di
ret
PROC_READ_NUMBER ENDP
;-----------------------------------------------------------------------------------
END MAIN
Mmm..it does pretty much the same thing, I don't see any advantage using stsob in this case...
well - it simplifies register use
notice that BX is not used as a base
[BX+DI] is not necessary
we don't have to preserve BX across the INT 10h, AH=8 call
there is some added code to use the stack frame
but, we also added functionality in the use of variable-length buffers
Ah that's right!! Thank you again!
i had to correct the code above
i added the line....
mov es,ax
STOSB stores the byte in AL at ES:[DI], then adjusts the DI register according to DF
the DF is always cleared at program start, so CLD is not required in this case, as we never STD