I'm currently trying to learn Assembly, and one of the tasks I am given is to take user input integers and insert those numbers into an array. Once the array has 7 integers, I will loop through the array and print out the numbers. However, I'm currently stuck on how to insert the numbers into the array. Here is the code I have right now:
.DATA
inputIntMessage BYTE "Enter an integer: ", 0
inputStringMessage BYTE "Enter a string: ", 0
intArray DWORD 0,0,0,0,0,0,0
intCounter DWORD 0
user_input DWORD ?
.CODE
main PROC
mov eax, intCounter
mov edx, 0
top:
cmp eax, 7
je final1
jl L1
L1: intInput inputIntMessage, user_input
mov ebx, user_input
mov intArray[edx], ebx ;This is where I think the problem is.
add edx, 4
inc eax
jmp top
final1:
mov ecx, 0
mov edx, 0
printarrayloop:
cmp edx,7
jl L2
je next
L2: intOutput intArray[ecx]
add ecx, 4
inc edx
next:
next: just goes to the next problem; irrelevant to this inserting into an array problem. My thinking is that I should use the offset of the array, so I can access the address of each element in the array and directly change that, but I do not know how. Can someone point me in the right direction?
EDIT: When I run the program the window prompts the user to enter an integer 7 times (which is as intended), and then prints out the first number the user entered. However, the window should be printing out all of the numbers the user entered.
The primary reason why your code only prints one number is because the code that displays the array of numbers does this:
mov ecx, 0
mov edx, 0
printarrayloop:
cmp edx,7
jl L2
je next
L2: intOutput intArray[ecx]
add ecx, 4
inc edx
next:
What is missing is that you do not continue the loop after displaying the first number. You need to jump back to printarrayloop to process the next number. Add this right below inc edx:
jmp printarrayloop
There are some other things you may wish to consider. In this code:
top:
cmp eax, 7
je final1
jl L1
L1: intInput inputIntMessage, user_input
[snip]
final1:
You do cmp eax, 7. If it is equal you jump out. If it is less then you just branch to label L1 anyway. You can modify that code by removing the extraneous jl L1 branch and label. So you would have this:
top:
cmp eax, 7
je final1
intInput inputIntMessage, user_input
In this code there are some extra instructions that can be removed:
mov ecx, 0
mov edx, 0
printarrayloop:
cmp edx,7
jl L2
je next
L2: intOutput intArray[ecx]
add ecx, 4
inc edx
jmp printarrayloop
next:
Similar to the previous comment I made to compare EDX to 7 using cmp edx,7. You can simply say that after comparing if it is equal to 7 then you jump out of the loop to next . If it is less than 7 it will just continue and print the number out. So your code could look like this:
mov ecx, 0
mov edx, 0
printarrayloop:
cmp edx,7
je next
intOutput intArray[ecx]
add ecx, 4
inc edx
jmp printarrayloop
next:
x86 32-bit code has a scaled addressing mode (with displacement). You can find all the addressing modes described in this summary here and more detailed description here.
You can using a scaling factor (multiply a register by 1,2,4, or 8) when doing addressing. You have code that looks like this:
mov intArray[edx], ebx
add edx, 4
EDX points to the element number you wish to display, multiplying it by 4 will account for the fact that the size of a DWORD is 4 bytes. So you can remove the add edx, 4and change the code accessing the array to:
mov intArray[edx*4], ebx
intArray[edx*4] is an address that is equivalent to intArray+(edx*4)
You can make a similar change when you output. Delete this line:
add ecx, 4
And use scaled addressing with:
intOutput intArray[ecx*4]
Related
This is being compiled on Visual Studios 2015 with Kip Irvine.
The code is supposed to switch the 1st element with the 2nd element and the 3rd element with the 4th element and so on. It switches the values forward instead of just switching the two values. I added the index register with 2 to skip the 2nd element since it should not be switched. Is there something I am missing? Am I supposed to increment the array index differently or am I putting the wrong values in the wrong registers? Thanks in advance! Please don't just give me the answer.
The output is
Dump of offset 00AD6880
00020000 00050000 00090000 0000000A 0000000C
INCLUDE Irvine32.inc
.data
dwarray dword 0,2,5,9,10,12
.code
main proc
mov ebx, OFFSET dwarray
mov ecx, LENGTHOF dwarray
L1: cmp ebx, ecx
mov eax, [ebx]
mov edx, [ebx+1]
mov [ebx+1], eax
mov [ebx], edx
add ebx, 2
loop L1
; The four instructions below are fixed, the only variable is the name of the array
mov esi,OFFSET dwarray
mov ecx,LENGTHOF dwarray
mov ebx,TYPE dwarray
call DumpMem
call WaitMsg
exit
main ENDP
END main
Your array elements are dword, double word, that means 4 bytes. So, in order to point to the elements you need to increase your pointer by 4 :
dwarray dword 0,2,5,9,10,12
.code
main proc
mov ebx, OFFSET dwarray
mov ecx, 3 ◄■■■ THE ARRAY CONTAINS 6 ELEMENTS, BUT THEY ARE PROCESSED
IN PAIRS, SO THE LOOP SHOULD REPEAT HALF 6 (3).
L1: ;cmp ebx, ecx ◄■■■ UNNECESSARY?
mov eax, [ebx]
mov edx, [ebx+4] ◄■■■ THE NEXT ELEMENT IS 4 BYTES AWAY.
mov [ebx+4], eax ◄■■■ AGAIN, THE NEXT ELEMENT IS 4 BYTES.
mov [ebx], edx
add ebx, 8 ◄■■■ INCREASE BY 8 (THE SIZE OF 2 ELEMENTS PROCESSED).
loop L1
I'm having trouble figuring out how to do the indexing in my loops. I know esi is used for indexing, so I'm attempting to use that...
scores DWORD MAXIMUMSCORES DUP(0)
optionPromptMsg byte "Type 1 to continue or -1 to exit: ", 0
scorePromptMsg byte "Enter your numeric score (0-100): ", 0
scoreErrorMsg byte "Score out of range (0-100)!", 0
optionErrorMsg byte "Only 0 or 1 allowed in option specification!", 0
resultMsg byte " scores have been entered.", 0
.code
main proc
mov esi, 0
L1:
mov edx, offset optionPromptMsg
call WriteString
call ReadInt
mov ebx, 1
cmp eax, ebx
je L2
mov ebx, -1 //99% sure my main is okay
cmp eax, ebx
je L3
mov ebx, -2
mov ecx, 2
cmp ebx, eax
ja L4
cmp eax, ecx
ja L4
L2: call NextScore
jmp L5
L4: mov edx, offset optionErrorMsg
call WriteString
call Crlf
jmp L5
L5:
loop L1
L3 : call WriteScore
exit
main ENDP
WriteScore PROC USES esi //Thought was somehow make esi global?
mov eax, lengthof scores //total number of items added to array
call writeInt
mov edx, offset resultMsg
call WriteString
mov esi,0
L1:
mov eax, scores[esi *4]
call writeInt //writes the numbers in the array
inc esi
loop L1
mov eax, 5000
call Delay
ret
WriteScore ENDP
NextScore PROC USES esi
mov edx, offset scorePromptMsg
call WriteString
call ReadInt
mov ebx, 0
mov ecx, 100
cmp ebx, eax
ja L1
cmp eax,ecx
ja L1
jmp L2
L1:
mov edx, offset scoreErrorMsg
call WriteString
call Crlf
L2:
mov scores[esi*4], eax //gets the next number and puts it in the array
inc esi
ret
NextScore ENDP
When I run it, and add 3 items to the array, it for whatever reason says the lengthof scores is 20, and then when it prints out the array, the numbers aren't even close to what I'm expecting, normally in the millions or just 0.
Any suggestions are much appreciated!
You have a couple issues. One is that you don't seem to understand what the USES directive on a procedure/function is for. If you use USES and list a register(s) then that tells the assembler to save the value of those registers on the stack, and restore them just before the function exits. This means that any change you make to that register in that function will not be seen by the function that called it.
The MASM manual says this about USES:
Syntax: USES reglist
Description:
An optional keyword used with PROC. Generates code to push the
value of registers that should be preserved (and that will be
altered by your procedure) on the stack and pop them off when the
procedure returns.
The <reglist> parameter is a list of one or more registers. Separate
multiple registers with spaces.
Since you seem to want changes to ESI made in the function NextScore to be seen by the calling function you will want to remove the USES statement from that procedure. Change:
NextScore PROC USES esi
to:
NextScore PROC
Now when you increment ESI in next score it won't be undone when the function exits.
Another issue is that the lengthof pseudo-opcode does:
lengthof: Returns the number of items in array variable.
It may not be clear but this pseudo-opcode is the number of elements in the array when the code was assembled. You define the array of scores like this:
scores DWORD MAXIMUMSCORES DUP(0)
The scores array will always have a lengthof value of MAXIMUMSCORES. Rather than use lengthof what you should be doing is simply using the ESI register. You already use ESI to keep a count of elements you have added to the array. So this code:
WriteScore PROC USES esi ; Thought was somehow make esi global?
mov eax, lengthof scores ; total number of items added to array
call WriteInt
Can be changed to:
WriteScore PROC USES esi ; Thought was somehow make esi global?
mov eax, esi ; esi = number of items added to array
call WriteInt
Another issue is that it appears you don't know how the loop instruction works. From the [x86 Instruction Set] the loop instruction does:
Performs a loop operation using the ECX or CX register as a counter.
Each time the LOOP instruction is executed, the count register is
decremented, then checked for 0. If the count is 0, the loop is
terminated and program execution continues with the instruction
following the LOOP instruction. If the count is not zero, a near jump
is performed to the destination (target) operand, which is presumably
the instruction at the beginning of the loop.
In your code you never set ECX to the number of times you wish to loop, so it will use whatever value happens to be in ECX . This is why you have a lot of extra numbers printed out. ECX needs to be initialized. Since you want to loop through all the scores entered, you simply move ESI to ECX. Your WriteScore function did:
mov esi,0
L1:
mov eax, scores[esi *4]
call WriteInt ; writes the numbers in the array
inc esi
loop L1
We can modify it to be:
mov ecx,esi ; Initialize ECX loop counter to number of scores added
; to the array.
mov esi,0
L1:
mov eax, scores[esi *4]
call WriteInt ; writes the numbers in the array
inc esi
loop L1
Now we just loop through the number of scores (ESI) the user actually entered.
With these changes in mind your program could look something like this:
INCLUDE Irvine32.inc
INCLUDELIB Irvine32.lib
INCLUDELIB user32.lib
INCLUDELIB kernel32.lib
MAXIMUMSCORES equ 20
.data
scores DWORD MAXIMUMSCORES DUP(0)
optionPromptMsg byte "Type 1 to continue or -1 to exit: ", 0
scorePromptMsg byte "Enter your numeric score (0-100): ", 0
scoreErrorMsg byte "Score out of range (0-100)!", 0
optionErrorMsg byte "Only 0 or 1 allowed in option specification!", 0
resultMsg byte " scores have been entered.", 0
.code
main proc
mov esi, 0
L1:
mov edx, offset optionPromptMsg
call WriteString
call ReadInt
mov ebx, 1
cmp eax, ebx
je L2
mov ebx, -1 ; 99% sure my main is okay
cmp eax, ebx
je L3
mov ebx, -2
mov ecx, 2
cmp ebx, eax
ja L4
cmp eax, ecx
ja L4
L2: call NextScore
jmp L5
L4: mov edx, offset optionErrorMsg
call WriteString
call Crlf
jmp L5
L5:
loop L1
L3: call WriteScore
exit
main ENDP
WriteScore PROC USES esi ; We make changes to ESI not visible to caller
; since we don't intend to change the number of scores
; with this function. Any change to ESI in this function
; will not appear to the caller of this function
mov eax, esi ; total number of items added to array in ESI
call WriteInt
mov edx, offset resultMsg
call WriteString
mov ecx,esi
mov esi,0
L1:
mov eax, scores[esi *4]
call WriteInt ; writes the numbers in the array
inc esi
loop L1
mov eax, 5000
call Delay
ret
WriteScore ENDP
NextScore PROC ; We want changes to ESI to exist after we exit this function. ESI
; will effectively act as a global register.
mov edx, offset scorePromptMsg
call WriteString
call ReadInt
mov ebx, 0
mov ecx, 100
cmp ebx, eax
ja L1
cmp eax,ecx
ja L1
jmp L2
L1:
mov edx, offset scoreErrorMsg
call WriteString
call Crlf
L2:
mov scores[esi*4], eax ; gets the next number and puts it in the array
inc esi
ret
NextScore ENDP
END
Sample output of this:
Type 1 to continue or -1 to exit: 1
Enter your numeric score (0-100): 10
Type 1 to continue or -1 to exit: 1
Enter your numeric score (0-100): 20
Type 1 to continue or -1 to exit: 1
Enter your numeric score (0-100): 40
Type 1 to continue or -1 to exit: 1
Enter your numeric score (0-100): 650
Score out of range (0-100)!
Type 1 to continue or -1 to exit: 99
Only 0 or 1 allowed in option specification!
Type 1 to continue or -1 to exit: 1
Enter your numeric score (0-100): 100
Type 1 to continue or -1 to exit: -1
+5 scores have been entered.+10+20+40+650+100
I'm having some difficulty with my array accessing in MASM. I've got a very large array and a temp variable like so:
.data
array DWORD 65000 DUP (0)
temp DWORD 0
In my main, I've got this to fill it:
mov esi, offset array
mov edi, 0
mov ecx, 0
fill:
mov [esi], ecx
add esi, 4
inc ecx
cmp ecx, 65000
jl fill
mov esi, offset array ;reset the array location to the start
After, I want to access the array with this loop:
mark:
mov temp, 4 ;get another 4 to temp to move along the array
add esi, temp ;add temp to esi to keep moving
mov edx, [esi] ;access the current value
cmp esi, 20 ;just trying to get first few elements
jmp mark
exit
main ENDP
END main
I always have an access violation error, with the break at the line where I try to access the current value. This occurs on the very first loop as well. Any idea why this is happening? Thanks!
Your code will work to an extent, however:
mark:
mov temp, 4
add esi, temp ;Incrementing before first loop means you miss the first stored value
mov edx, [esi]
cmp esi, 20 ;ESI is the address your are accessing, not a count
jmp mark ;This loop will be infinite
I have to create a random range of 100 counts of numbers from 27 to 58 and then add up all the numbers in the 100 positions for a total amount. However, when I do that I get a random number and ninety-nine 32's as a result. I've searched everywhere and tried possible solutions but I'm either getting the same result or random garbage. Can someone offer some help?
INCLUDE irvine32.inc
.data
a DWORD 27
b DWORD 58
delta DWORD ?
source DWORD 100 DUP(?)
prompt BYTE "The sum of the 100 counts in array is ",0
.code
main PROC
Call Randomize
mov edi, 0
mov edi, OFFSET delta
mov esi, OFFSET source
mov eax, b
sub eax, a
inc eax
mov delta, eax
mov ecx, LENGTHOF source
mov eax, 0
L1:
mov eax, delta
call randomrange
add eax, a
mov source, eax
call writedec
mov al, " "
call writechar
loop L1
call crlf
call crlf
mov ecx, SIZEOF source
mov edx, OFFSET prompt
call writestring
l2:
add eax,[esi]
add esi, TYPE delta
call writedec
mov al, " "
call writechar
loop l2
exit
main ENDP
END main
I assume randomrange leaves its random number in EAX.
In the L1 loop, you add A to EAX to get your random value and then copy it to the first element of SOURCE every time through the L1 loop. That's why the first element is random, but the rest of the array isn't touched. (Note that you have the same problem in L2 -- you always get the value to print from the first element of SOURCE.)
I am working to take input from a user twice, and compare the input. If they are the same, the program exits. If not, it reprints the input from the first time, and waits for the user to type something. If it is the same, the same thing as before occurs. If not, the same thing as before occurs.
Input and looping is not the problem. The main problem is the result I am getting from the program. My following is what I am doing codewise:
%include "system.inc"
section .data
greet: db 'Hello!', 0Ah, 'Please enter a word or character:', 0Ah
greetL: equ $-greet ;length of string
inform: db 'I will now repeat this until you type it back to me.', 0Ah
informL: equ $-inform
finish: db 'Good bye!', 0Ah
finishL: equ $-finish
newline: db 0Ah
newlineL: equ $-newline
section .bss
input: resb 40 ;first input buffer
check: resb 40 ;second input buffer
section .text
global _start
_start:
greeting:
mov eax, 4
mov ebx, 1
mov ecx, greet
mov edx, greetL %include "system.inc"
section .data
greet: db 'Hello!', 0Ah, 'Please enter a word or character:', 0Ah
greetL: equ $-greet ;length of string
inform: db 'I will now repeat this until you type it back to me.', 0Ah
informL: equ $-inform
finish: db 'Good bye!', 0Ah
finishL: equ $-finish
newline: db 0Ah
newlineL: db $-newline
section .bss
input: resb 40 ;first input buffer
check: resb 40 ;second input buffer
section .text
global _start
_start:
greeting:
mov eax, 4
mov ebx, 1
mov ecx, greet
mov edx, greetL
sys.write
getword:
mov eax, 3
mov ebx, 0
mov ecx, input
mov edx, 40
sys.read
sub eax, 1 ;remove the newline
push eax ;store length for later
instruct:
mov eax, 4
mov ebx, 1
mov ecx, inform
mov edx, informL
sys.write
pop edx ;pop length into edx
mov ecx, edx ;copy into ecx
push ecx ;store ecx again (needed multiple times)
mov eax, 4
mov ebx, 1
mov ecx, input
sys.write
mov eax, 4 ;print newline
mov ebx, 1
mov ecx, newline
mov edx, newlineL
sys.write
mov eax, 3 ;get the user's word
mov ebx, 0
mov ecx, check
mov edx, 40
sys.read
xor eax, eax
checker:
mov ebx, check
mov ecx, input
cmp ebx, ecx ;see if input was the same as before
jne loop ;if not the same go to input again
je done ;else go to the end
pop edx
mov ecx, edx
push ecx
mov eax, 4
mov ebx, 1
mov ecx, check
sys.write ;repeat the word
mov eax, 4
mov ebx, 1
mov ecx, newline
mov edx, newlineL
sys.write
loop:
mov eax, 3 ;replace new input with old
mov ebx, 0
mov ecx, check
mov edx, 40
sys.read
jmp checker
done:
mov eax, 1
mov ebx, 0
sys.exit
sys.write
getword:
mov eax, 3
mov ebx, 0
mov ecx, input
mov edx, 40
sys.read
My result is now: EDITED
Hello!
Please enter a word or character:
Nick
I will now repeat this until you type it back to me.
Nick
(I input) Magerko
(I get) M
(I input)Nick
(I get)
(I input)Nick
(I get)
EDITED
And this continues. My checks do not work as intended in the code above, and I eventually don't even get the program to print anything but a newline. Is there a reason for this?
Thanks.
Apart from what #Joshua is pointing out, you're not comparing your strings correctly.
checker:
mov ebx, check ; Moves the *address* of check into ebx
mov ecx, input ; Similarly for input
cmp ebx, ecx ; Checks if the addresses are the same (they never are)
Firstly, when you have e.g. label dd 1234 in your data segment mov eax, label will move the address of label to eax while mov eax, [label] will move the contents stored at label (in this case 1234) into eax.
Note that in the above example I deliberately used a 32-bit variable so that it would fit neatly into eax. If you're using byte sized variables (like ascii characters) e.g. mybyte db 0xfe you'll either have to use byte sized register (al, ah, dh etc.) or use the move with zero/sign extend opcodes: movzx eax, byte [mybyte] will set eax to 254, while movsx eax, byte [mybyte] will set eax to -2 (0xfffffffe).
You also need to do a character by character comparison of the strings. Assuming you save the read string length (you really should be checking for negative return values - meaning errors) in input_len and check_len it could look something like:
mov eax, [input_len]
cmp eax, [check_len]
jne loop ; Strings of different length, do loop again
mov ebx, check
mov ecx, input
.checkloop:
mov dl, [ebx] ; Read a character from check
cmp dl, [ecx] ; Equal to the character from input?
jne loop ; Nope, jump to `loop`
inc ebx ; Move ebx to point at next character in check
inc ecx ; and ecx to next character in input
dec eax ; one less character to check
jnz .checkloop ; done?
; the strings are equal if we reach this point in the code
jmp done
If you're interested in another way of doing this in fewer instructions look up rep cmpsb.
There are a few other problems in the code immediately following your checker code. The pop edx instruction (and the code following, down to the loop label) will not be execute as you're always jumping either to loop or done.
jne loop ;if not the same go to input again
je done ;else go to the end
pop edx ; Will never be reached!
The reason you're getting funny characters is from newlineL: db $-newline This should be equ instead of db or you should replace mov edx, newlineL with movzx edx, byte [newlineL]. Since newlineL unlike the other *L names refers to a variable and not a constant equ mov edx, newlineL will use the address of the newlineL variable as the number of bytes to write, when you wanted it to be 1.
You are assuming sys.read returns the entire line. It is not required to do so. It may return after only one character, or even possibly after part of the second line.
You know, this kind of thing kind of ticks me off. This looks like a homework problem in writing in assembly, but the problem is not with the assembly, but with the assumptions in how the system calls work.
I really wish the instructors would provide an fgets library function for stuff like this.
Anyway, the stupid way to fix it is to read one byte at a time, looking for LF (byte 10) to end the loop.