I'm currently presented with implementing a program that takes an input of a grade value (ex. 75) and then outputs a letter grade corresponding to it. I've implemented the following requested scale via an array / table:
.data
table BYTE 89d, 'A'
BYTE 79d, 'B'
BYTE 69d, 'C'
BYTE 59d, 'D'
BYTE 0d, 'F'
NumCols = 2
NumRows = 5
user_ip BYTE ?
message1 BYTE "Enter a grade value: ", 0h
message2 BYTE "The Grade of ", 0h
message3 BYTE " Yields A Letter Grade of ", 0h
I'm using the following code to sort through this array / table and output the letter grade.
mov edx, OFFSET message1
call WriteString
call readDec
mov user_ip, al
mov esi, OFFSET user_ip
mov edi, OFFSET table
mov ecx, NumRows
L1:
CMPSB
jae L2
add edi, NumCols
Loop L1
L2:
mov edx, OFFSET message2
call WriteString
mov al, user_ip
call WriteDec
mov edx, OFFSET message3
call WriteString
mov edx, edi
call WriteString
call Crlf
With an input of 75, I'm being presented with: "The Grade of 75 Yields A Letter Grade of EC;D". The Program also temporarily stops working.
I'm confident it has something to do with pointers and data sizes. My ideal goal is to store the value of the letter grade in a variable, but I can't seem to find a way to do it given the data size needed to use pointers. Any ideas of how to do this?
You are calling WriteString but the values in your table are characters not strings. The difference in this case is that they are not zero terminated. Either use WriteChar if you have that, or put a zero in your table but then don't forget to adjust NumCols too.
Also note that CMPSB increments both pointers which means your comparisons will be wrong. You should probably just use the non-string CMP especially since the user_ip is already in register AL.
PS: Finally somebody who uses a table :)
Related
I'm new to Assembly, I need help with an assignment in Assembly Language Irvine32. I want to know where I'm going wrong. I believe my code is 80% right but there's something I'm not seeing or recognizing. Here's the program details.
"Write an assembly language program that has an array of words. The program loads the last element of the array into an appropriately sized register and prints it. (Do not hardcode the index of the last element.)"
INCLUDE Irvine32.inc
.data
val1 word 1,2,3,4,5,6
val2 = ($-val1)/2
.code
main PROC
mov ax, 0
mov ax, val1[val2]
Call WriteDec
Call DumpRegs
exit
main ENDP
END main
First of all, your code has a bug: val1[val2] indexes with the element count in words, not the length in bytes (unless MASM syntax is even more magical than I expect). And it reads from one past the end of the array, since the first element is at val1[0].
To find the end, you either need to know the length (explicit length, like a buffer passed to memcpy(3)), or search it for a sentinel element (implicit length, like a C string passed to strcpy(3)).
Having a function that accepts an explicit length as a parameter seems fine to me. It's obviously much more efficient than a loop scanning for a sentinel element, and the array shown doesn't include one. (See Jose's answer for a suggestion to use '$' (i.e. 36) as a sentinel value. -1 or 0 might be more sensible sentinels/terminators.)
Obviously knowing the length is much better, since there's no need for a loop scanning the whole array.
I'd only call it hard-coding if you wrote val2 = 6, or worse val2 dw 6, rather than having it calculated at assemble-time from the array. If you want to write a function that could work with non-compile-time-constant arrays, you can have it accept the length as a value in memory, instead of an immediate that will be embedded into its load instruction.
e.g.
Length as a parameter in memory
.data
array word 1,2,3,4,5,6
array_len word ($-array)/2 ; some assemblers have syntactic sugar to calc this for you, like a SIZE operator or something.
.code
main PROC ; inputs: array and array_len in static storage
; output: ax = last element of array
; clobbers: si
; mov ax, 0 ; This is useless, the next mov overwrites it.
mov si, [array_len] ; do we need to save/restore si with push/pop in this ABI?
add si,si ; multiply by 2: length in words -> length in bytes
mov ax, [array + si - 2] ; note that the -2 folds into array at assemble time, so it's just a disp16 + index addressing mode
Call WriteDec
Call DumpRegs
exit
main ENDP
END main
You could also write a function to take pointer and length args on the stack or in registers, and have main pass those args.
You could save the add (or shl) by accepting a length in bytes, or a start and one-past-the-end pointer (like C++ STL range functions that take .begin() and .end() iterators). If you have the end pointer, you don't need the start pointer at all, except to return an error if they're equal (size = 0).
Or if you were not stuck with obsolete 16bit code, you could use a scaled index in the addressing mode, like [array + esi * 2]. You include Irvine32.inc...
I think your solution to reach the last element is the most efficient (($-val1)/2), but #zx485 is right and your teacher might believe you are cheating, so, among other solutions, you can reach the last element with a loop and the pointer SI :
INCLUDE Irvine32.inc
.data
val1 word 1,2,3,4,5,6
val2 = ($-val1)/2
.code
main PROC
; mov ax, 0
; mov ax, val1[val2]
mov cx, val2-1 ;COUNTER FOR LOOP (LENGTH-1).
mov si, offset val1 ;SI POINTS TO FIRST WORD IN ARRAY.
repeat:
add si, 2 ;POINT TO NEXT WORD IN ARRAY.
loop repeat ;CX--, IF CX > 0 REPEAT.
mov ax, [ si ] ;LAST WORD!
Call WriteDec
Call DumpRegs
exit
main ENDP
END main
One shorter way would be to get rid of the loop and jump straight to the last element by using the SI pointer (and changing val2 just a little) :
INCLUDE Irvine32.inc
.data
val1 dw 1,2,3,4,5,6
val2 = ($-val1)-2 ;NOW WE GET LENGTH - 2 BYTES.
.code
main PROC
; mov ax, 0
; mov ax, val1[val2]
mov si, offset val1 ;SI POINTS TO FIRST WORD IN ARRAY.
add si, val2 ;SI POINTS TO THE LAST WORD.
mov ax, [ si ] ;LAST WORD!
Call WriteDec
Call DumpRegs
exit
main ENDP
END main
And "Yes", you can join those two lines :
mov si, offset val1 ;SI POINTS TO FIRST WORD IN ARRAY.
add si, val2 ;SI POINTS TO THE LAST WORD.
into one, I separated them to comment each other :
mov si, offset val1 + val2
If you cannot use val2 = ($-val1)/2, one option would be to choose some terminating character for the array, for example, '$', and loop until it's found:
INCLUDE Irvine32.inc
.data
val1 word 1,2,3,4,5,6,'$' ;ARRAY WITH TERMINATING CHARACTER.
;val2 = ($-val1)/2
.code
main PROC
;mov ax, 0
;mov ax, val1[val2]
mov si, offset val1 ;SI POINTS TO VAL1.
mov ax, '$' ;TERMINATING CHARACTER.
repeat:
cmp [ si ], ax
je dollar_found ;IF [ SI ] == '$'
add si, 2 ;NEXT WORD IN ARRAY.
jmp repeat
dollar_found:
sub si, 2 ;PREVIOUS WORD.
mov ax, [ si ] ;FINAL WORD!
Call WriteDec
Call DumpRegs
exit
main ENDP
END main
I am having an issue with this code snippet of a problem. My expected output is (after the intro):
Please enter an unsigned number: (user input)
.
.
.
Repeat ten times
The output that I am getting is :
Please enter an unsigned number: 10
10 <--- this is displaying instead of "Please enter an unsigned number: "
Any idea why this problem might be happening?
;getString should display a prompt, then get the user’s keyboard input into a memory location
getString MACRO buffer
push edx ;Save edx register
mov edx, OFFSET buffer
call WriteString
call ReadString
pop edx
ENDM
;displayString should the string stored in a specified memory location.
displayString MACRO buffer
push edx
mov edx, OFFSET buffer
call WriteString
call crlf
ENDM
.data
intro_1 BYTE "PROGRAMMING ASSIGNMENT 5: Designing low-level I/O procedures",0
intro_2 BYTE "Written by: Eric Walters",0
intro_3 BYTE "Please provide 10 unsigned decimal integers.",0
intro_4 BYTE "Each number needs to be small enough to fit inside a 32 bit register.",0
intro_5 BYTE "After you have finished inputting the raw numbers I will display a list",0
intro_6 BYTE "of the integers, their sum, and their average value.",0
data_1 BYTE "Please enter an unsigned number: ",0
array DWORD 10 DUP(?)
.code
main PROC
;Introduction
displayString intro_1
displayString intro_2
displayString intro_3
displayString intro_4
displayString intro_5
displayString intro_6
push OFFSET array
call getString1
;readVal
exit
main ENDP
getString1 PROC
push ebp
mov ebp, esp
mov ecx, 9
mov esi, [ebp + 8]
getString2:
getString data_1
mov [esi], eax
add esi, 4
call crlf
dec ecx
jmp getString2
ret
getString1 ENDP
END main
I suspect the problem is in some parameter being inadvertently passed into read string pointing at the address where data_1 is stored. For example, if edx is pointing at data_1 when you mov the OFFSET into it, and the WriteString is looking at edx for the address of the data to write, ReadString is looking at edx for the address to store the line in once it is read. So the ReadString is overwriting your sentence in data_1 with the number you entered and an end of line marker. Then the next time the WriteString PROC is called, it is reading in that number.
To fix this, create a .data? field called input_data or whatever you like, and point edx at it after the WriteString call but before the ReadString call.
Just an aside, but I also think you need to change your line after "dec ecx" to read "jnz getString2", not "jmp getString2". Otherwise I cannot see you ever escaping the loop no matter what value ecx reaches.
EDIT: Wait, I see, you have an array set up for that already. I think you just need to point to it before calling ReadString.
I am having a problem understanding why my cmp statements are not working correctly.
When I run this, I enter 0 in first and it goes to storeValue. I enter 0 in for the second value and it goes to the searchArray like it is supposed to.
I have breakpoints on my cmp and jump statements and a watch on AL so I don't understand why it's storing the first 0 when it should prompt for the search value at that point.
Thanks for looking.
.DATA
prompt1 BYTE "Enter a value to put in array or 0 to search array.", 0
prompt2 BYTE "Enter a value to search array for.",0
intArray DWORD ?
numElem DWORD 0
SearchVal DWORD ?
resultNope BYTE "Not in array.",0
.CODE
_MainProc PROC
lea ebx, intArray ;get the address of array.
start: input prompt1, intArray, 50 ;read in integer
atod intArray ;convert to int
mov al, [ebx] ;move int to register
cmp al, 0 ;if integer is positive - store it!
jg storeValue ;JUMP!
cmp al, 0 ;if 0 - time to search array!
je searchArray ;JUMP!
storeValue: add numElem, 1 ;Adds 1 to num of elements in array.
mov [ebx], al ;moves number into array.
add ebx, 1 ;increment to next array address.
jmp start ;get next number for array. JUMP!
searchArray:input prompt2, searchVal, 50 ;What are we searching array for?
atod searchVal ;convert to int
lea ebx, intArray ;get address of array.
mov ecx, 1 ;set loop counter to 1.
You have forgotten to show how input and atod work. Looking into my crystal ball, I guess that input expects a buffer to store the user input as text, and the argument 50 is presumably its size. Notice that you don't have such a buffer and you don't even have 50 bytes space. I also think that since atod apparently only takes 1 argument, which is the text buffer to convert, it presumably returns the value in eax. This is also reinforced by the fact that your storeValue writes from al which would make no sense otherwise.
Long story short:
allocate a buffer of the proper size for the text entered
pass this array to atod
do not clobber al after the call to atod
(Applies to the search part too.)
I am using MASM assembly and I am trying to write a loop that processes the string str1 byte-by-byte, changing each lowercase letter into the corresponding capital letter using bit operations. If the letter is already capital, leave it alone. Nothing seems to happen to my string str1 when I execute my code and I'm having difficulty figuring out why, maybe I shouldn't be processing my array as such, but nonetheless, here's the code:
.386
.MODEL FLAT
str1 dword "aBcD", cr, Lf, 0
....
.code
_start:
output str1
**sub esi, esi ; sum = 0
lea ebx, str1
top: mov al, [ebx + esi] ; attempting to move each character value from
str1 into the register al for comparison and
possible conversion to uppercase
add esi, 5
cmp al, 0
je zero
sub al, 20h** ; convert lowercase to corresponding uppercase
loop top
zero: output zeromsg ; for TESTING of al purposes only
done: output str1value
output str1
Nothing changes , and on top of the conversion not taking place, the string it printing in reverse order. why? prints out as: "DcBa". Any inquiry would be appreciated! Thanks in advance.
You must load the character, process it, and store it back. You don't store it.
Something like:
mov [esi+ebx], al
is missing.
Why do you sub 0x20 from the char? And why do you add 5 to esi?
Update
Before you start coding, you should think about what the required steps are.
Load the character.
If the character is 0 the string is done.
If the character is uppercase, convert it
Store the character
Adavance to the next character and back to 1
That's it. Now when you look at your code example, you can easily see what is missing and where you go wrong.
May help you a bit
.writeLoop2
mov eax,[ebx] ;mov eax start of data block [ebx]
cmp al,&61 ;61hex is "a"
jb dontsub20 ;if its less don't bother because it's a CAPITAL letter
sub al,&20 ;else take off 20 hex
.dontsub20
call "osasci" ;print to screen command. Output the character in eax
inc ebx ;move ebx forward to next character
inc ecx ;ecx is the rolling count
cmp ecx,edx ;when ecx=edx we are at the end of the data block
jb writeLoop2 ;otherwise loop, there are more characters to print
I'm just beginning to toy with masm. I don't understand why this code isn't working.
.data
MsgBoxCaption db "Iczelion Tutorial No.2",0
MsgBoxText db "Win32 Assembly is Great!",0
savedAddr DWORD ?
.code
start:
mov eax, 10
mov savedAddr, OFFSET MsgBoxText
lab:
inc MsgBoxText
MOV MsgBoxText, 'm'
cmp eax, 0
dec eax
jnz lab
invoke MessageBox, NULL, savedAddr, addr MsgBoxCaption, MB_OK
invoke ExitProcess, NULL
end start
Edit: I expect to see the first 10 characters in MsgBoxText be 'm's. Instead, only the first letter is an 'm'. I assume that inc MsgBoxText increments a pointer.
At first glance it seems like this code should do nothing except change the 'W' of Win32 to an 'a' and then to a single 'm'.
You are incrementing the word in memory at MsgBoxText, the same word, in each loop iteration.
To clobber the string using the 'm' characters, a better strategy would be to load the address of the string into a register, start storing 'm' bytes, and then increment the value in the register, as well as decrementing the counter.
Update: Ok, to answer the question in the comment, change the loop to:
lea esi, MsgBoxText
mov bl, 'm'
lab:
mov [esi], bl
inc esi
cmp eax, 0
dec eax
jnz lab