Click to See Complete Forum and Search --> : need help 8086 ASM


gamblor01
08-26-2007, 11:25 PM
Anybody here that can tell me why this program doesn't output the correct value that I'm expecting (which should be 15)?

I've used 2 different assembly languages in the past, but this is the first time I've ever used 8086 and it's a bit interesting the way it handles registers (ax = ah + al, and so on) and it seems like certain lines do tricky things with the registers (they must be or else this program would output 15).

I just wrote a simple little program that multiplies 2 numbers by repeatedly adding (yes I realize there's a 'mult' operation but I just wanted to try this).

If anyone can tell me what's wrong with this code I'd appreciate it. I just started as a TA for an intro to comp. architecture course and I'm going to have to help teach these undergrads 8086...so you can see why I'd want to know what's going on with my program. ;)

I'm not quite sure how to properly output the integer value in the register bl (which I expect is 15). Can anybody offer any insight?


.model small
.8086
.stack

.data

var1 db 5
var2 db 3
.code

main proc

mov bl, var1
mov cl, var2
dec cl

mult:
add bl, cl
sub cl, 1
jnz mult

mov dx, 0
mov dl, bl
mov ah, 2h
int 21h

mov ax, 4c00h
int 21h
main endp
end main

flukshun
08-27-2007, 07:03 PM
its been a while, ive almost completely forgotten x86 assembly, so i post this at the risk of making myself look stupid. however:

mult:
add bl, cl
sub cl, 1
jnz mult

is this not equivalent to:

while(cl != 0) {
bl = bl + cl;
cl--;
}

when what you actually want is:


int multiplicand = bl;
while(cl != 0) {
bl = bl + multiplicand;
cl--;
}

simple mistake?

gamblor01
08-27-2007, 08:46 PM
Thanks flukshun...that's definitely A problem although it's apparently not the only one. I changed the code to store the value of bl into al. Now I'm doing "add bl, al" in each iteration of the loop which gives another 5 units (var1) each time. Here's the new code and it's still not working:


.model small
.8086
.stack

.data

var1 db 5
var2 db 3
.code

main proc

mov bl, var1
mov cl, var2
dec cl
mov al, bl

mult:
add bl, al
sub cl, 1
jnz mult

mov dl, bl
mov ah, 2h
int 21h

mov ax, 4c00h
int 21h
main endp
end main


I think the problem is that I actually don't know the correct way to output an integer to the console. For example, if I'm outputting a string I can do something like this:


.data

name db 'John Doe', 13, 10, '$'

.code

main proc

mov ax, seg myname
mov ds, ax
mov ah, 09
lea dx, myname
int 21h


That code works and outputs the string "John Doe" to the screen (as well as a newline character, which in Windows is CR+LF).

However, to test my theory that I actually don't know how to output an integer to the screen, I did the following:


mov dl, 65
mov ah, 2h
int 21


Now what happens? Instead of outputting "65" I wind up getting the letter 'A' which means it's converting the value in the register (65 in decimal) to it's corresponding ASCII value. So is there a way to directly output 65 to the screen, or do I need to write some crazy code that determines the values in the 10's place (which is 6) and then load the ASCII value of 6 (54) into dl and output it? Then I would need to determine the value of the 1's place (which is 5) and load 53 into dl and output it. It seems like an awful lot of work and the engineers that designed 8086 ASM must have thought about this and come up with a better way. They allow me to output an entire string of characters all at once...why not an integer as well? I guess the issue is that I just don't know what the correct way is.

bwkaz
08-27-2007, 09:05 PM
Because int 21h is DOS, and DOS is dumb? :p

If you insist on using DOS, you should probably look around at the other subfunctions of int 21h; perhaps there's one to print out a number. But I kinda doubt it; converting between a number and a string is a bit difficult, and DOS needed to use the absolute bare minimum of memory (remember 640K?). Printing a string to the console is really easy compared to converting a number.

flukshun
08-28-2007, 11:58 AM
or do I need to write some crazy code that determines the values in the 10's place (which is 6) and then load the ASCII value of 6 (54) into dl and output it? Then I would need to determine the value of the 1's place (which is 5) and load 53 into dl and output it.

seems quite possible, and it would make for good practice at least :p

definetely look around, but i do seem to recall having to rely on a set of helper libraries to get that kind of functionality in my assembly projects.

gamblor01
08-28-2007, 03:31 PM
Thanks for all of the efforts...but fortunately I finally found a website that had some useful code posted. I'll go ahead and post it below as well. It's taken from emu8086.inc

The best part about it is that it includes two functions, PUTC and PRINT_NUM (which actually calls PUTC if you look at it). PRINT_NUM will print the decimal equivalent of the value in AX (though it only works for 16 bit numbers 0000-FFFF). So you can do things like this:


putc '-'
mov ax, 324
call print_num


Which of course would print "-324" to the screen. So I'm pretty sure I can get things working from here! :)



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; this maro is copied from emu8086.inc ;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; this macro prints a char in AL and advances
; the current cursor position:
PUTC MACRO char
PUSH AX
MOV AL, char
MOV AH, 0Eh
INT 10h
POP AX
ENDM
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; this procedure prints number in AX,
; used with PRINT_NUM_UNS to print signed numbers:
PRINT_NUM PROC NEAR
PUSH DX
PUSH AX

CMP AX, 0
JNZ not_zero

PUTC '0'
JMP printed

not_zero:
; the check SIGN of AX,
; make absolute if it's negative:
CMP AX, 0
JNS positive
NEG AX

PUTC '-'

positive:
CALL PRINT_NUM_UNS
printed:
POP AX
POP DX
RET
PRINT_NUM ENDP



; this procedure prints out an unsigned
; number in AX (not just a single digit)
; allowed values are from 0 to 65535 (FFFF)
PRINT_NUM_UNS PROC NEAR
PUSH AX
PUSH BX
PUSH CX
PUSH DX

; flag to prevent printing zeros before number:
MOV CX, 1

; (result of "/ 10000" is always less or equal to 9).
MOV BX, 10000 ; 2710h - divider.

; AX is zero?
CMP AX, 0
JZ print_zero

begin_print:

; check divider (if zero go to end_print):
CMP BX,0
JZ end_print

; avoid printing zeros before number:
CMP CX, 0
JE calc
; if AX<BX then result of DIV will be zero:
CMP AX, BX
JB skip
calc:
MOV CX, 0 ; set flag.

MOV DX, 0
DIV BX ; AX = DX:AX / BX (DX=remainder).

; print last digit
; AH is always ZERO, so it's ignored
ADD AL, 30h ; convert to ASCII code.
PUTC AL


MOV AX, DX ; get remainder from last div.

skip:
; calculate BX=BX/10
PUSH AX
MOV DX, 0
MOV AX, BX
DIV CS:ten ; AX = DX:AX / 10 (DX=remainder).
MOV BX, AX
POP AX

JMP begin_print

print_zero:
PUTC '0'

end_print:

POP DX
POP CX
POP BX
POP AX
RET
PRINT_NUM_UNS ENDP

ten DW 10 ; used as multiplier/divider by PRINT_NUM_UNS.