News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

Validating input that exceeds the size of a 32 bit register

Started by sage112, March 15, 2015, 05:16:47 PM

Previous topic - Next topic

sage112

Hi all,
For a class assignment I've developed a program that will receive a user's string input made up of digits, convert the string to integers and the converts it back to strings. The program also finds the sum and average of the set of numbers (user inputs 10). I having trouble validating if the user inputs a a value that is larger than what a 32 bit register can hold. I've tried using the Jump is Carry condition but can't seem to find where exactly need to put it in order for the flag to be caught. Any direction would be really appreciated.

Code Below:
TITLE Designing low level I/O Procedures     (P6A_ARAUJOJ.asm)

; Author: Jenise Araujo
; Course / Project ID   CS 271              Date:3/7/2015
; Description: This program will ask the user to input 10 unsigned integers
; that fit into a 32-bit register. The program will validate the input.
; The user's input will be read as a string and will be converted to it's
; numeric value. Afterwards the program will calculate and display the sum and averages of the numbers.

INCLUDE Irvine32.inc

MAX = 4294967295

;getString Macro
getString   MACRO   buffer, buffer1, buffer2
   mov      edx, OFFSET buffer
   call           WriteString

   mov      edx, OFFSET   buffer1
   mov      ecx, (SIZEOF buffer1) - 1
   call           ReadString
   mov      buffer2, eax
   
ENDM

;displayString MACRO
displayString   MACRO   buffer
   
      mov   edx, OFFSET buffer
      call   WriteString
ENDM

.data
intro   BYTE   "PROGRAMMING ASSIGNMENT 6A: Designing low-level I/O procedures",0
intro2   BYTE   "Written by: Jenise Araujo",0
intro3   BYTE   "Please provide 10 unsigned decimal integers.",0
intro4   BYTE   "Each number needs to be small enough to fit inside a 32 bit register.",0
intro5   BYTE   "After you have finished inputting the raw numbers I will display a list ",0
intro6   BYTE   "of the integers, their sum, and their average value.",0
EntNum   BYTE   "Please enter an unsigned number: ",0
Invalid   BYTE   "ERROR: You did not enter an unsigned number or your number was too big.",0
Numstr   BYTE   10 DUP(0)
NumSC   DWORD   0
TryAgain BYTE   "Please try again: ",0
DisNum   BYTE   "You entered the following numbers:",0
Sum      BYTE   "The sum of these numbers is: ", 0
Avg      BYTE   "The average is: ",0
Goodbye   BYTE   "Thanks for playing!",0
ArrayN   DWORD   10 DUP(?)
ArrayS   BYTE   20 DUP(?)


.code
main PROC

;Display instructions to the game;
   push   OFFSET intro
   push   OFFSET intro2
   push   OFFSET intro3
   push   OFFSET intro4
   push   OFFSET intro5
   push   OFFSET intro6
   call   Instructions

;Convert a digit string to a numeric value
   push   OFFSET ArrayN
   push   OFFSET Invalid
   push   OFFSET EntNum
   push   OFFSET Numstr
   push   NumSc
   call   ReadVal

;Convert a numeric value to a digit string
   push   OFFSET DisNum
   push   OFFSET ArrayN
   push   OFFSET ArrayS
   call   WriteVal

;Display the sum and average
   push   OFFSET ArrayN
   push   OFFSET Sum
   push   OFFSET Avg
   call   Calculate

;Say goodbye
   push   OFFSET   Goodbye
   call   EndGame


   exit   ; exit to operating system
main ENDP

;---------------------------------------------------------------
;Instructions Procedure - States the program and the author and
;displays the instructions for the program
;Receives: The address of the instruction strings
;Returns: None
;Preconditions: None
;---------------------------------------------------------------
   Instructions   PROC
      push   ebp
      mov      ebp,esp
      pushad

   ;Display the instructions of the program
      mov      edx, [ebp + 28]
      call           WriteString
      call           CrLf

      mov      edx, [ebp + 24]
      call           WriteString
      call           CrLf

      mov      edx, [ebp + 20]
      call           WriteString
      call           CrLf

      mov      edx, [ebp + 16]
      call           WriteString
      call           CrLf

      mov      edx, [ebp + 12]
      call           WriteString
      call           CrLf

      mov      edx, [ebp + 8]
      call           WriteString
      call           CrLf

      popad
      pop      ebp

      RET      2
   Instructions   ENDP

;---------------------------------------------------------------
;ReadVal Procedure - This procedure will read 10 inputs from the
; the user and convert the string to an integer
;Receives: The dddress to an array of numbers, the address of the
;invalid string, the entered number and the size of the string
;Returns: An array of numbers
;Preconditions: The user enters valid input (non-numeric and > 32 bit register)
;---------------------------------------------------------------
   ReadVal      PROC
      push   ebp
      mov      ebp, esp
      mov      esi, [ebp + 12]      ; points to the user's number
      mov      edi, [ebp + 24]      ; will hold the user's string as a number
      mov      ecx, 10            ;set the outer loop

   L1:
      pushad
      jmp   Try

   Again:
      mov      esi, [ebp + 12]      ; points to the user's number
   Try:
      getString   EntNum, Numstr, [ebp + 8]      ; get the string from the user
      mov      edx, 0
      mov      ecx, [ebp + 8]      ;set the loop = size of the string
      cld

   counter:
      lodsb                  ;load the first byte
      cmp      ecx, 0
      je      continue
      cmp      al, 48            ;is the value less than 0
      jl      badnum
      cmp      al, 57            ; is the value greater than 9
      jg      badnum
      jmp      store   
      
   badnum:                     ;invalid input
      mov      edx, [ebp + 20]
      call   WriteString
      call   CrLf
      jmp      Again
         
   store:                  ;store the value
      sub      al, 48         ;convert to it's ASCII  equivalent
      push   eax               
      push   ecx
      mov      eax, edx         
      mov      ecx, 10         ;multiply edx by 10
      mul      ecx
      mov      edx, eax
      pop      ecx
      pop      eax

      push   ebx            ;add to the accumulator
      movsx   ebx, al
      add      edx, ebx
      pop      ebx
      loop   counter

      mov      [edi],edx      ;store the converted string into an array as a numeric value
      popad
      cmp      ecx, 0         ;stop the outer loop
      je      continue
      add      edi, 4         ;next position in the array
      loop   L1

   continue:
      pop      ebp

      RET      20
   ReadVal      ENDP
;---------------------------------------------------------------
;WriteVal Procedure - Will convert an array of numbers to an array
;of strings and display the values
;Receives: The address of an array of numbers, the address of an array
;of characters and the address of an instruction
;Returns: An array of characters
;Preconditions: The user has entered valid input
;---------------------------------------------------------------
   WriteVal   PROC
      push   ebp
      mov      ebp, esp
      mov      esi, [ebp + 12]      ;Store the integer array
      mov      edi, [ebp + 8]      ; Store the string array

      mov      edx, [ebp + 16]      ;Display the converted ints as a string
      call           WriteString
      call           CrLf

      pushad   
      mov      ecx, 10            ;loop counter
      
   Check:
      push   ecx
      mov      ebx, 1000000000   ;set to 1billion since the max of a 32 bit register is 4billion+

   Divide:
      mov      eax, [esi]         ;get the first number from the array
      cmp      eax, 0            ;if the number is equal to zero just store
      je      zero
      cmp      eax, ebx         
      jg      Compute            ;if the divisor is less than the number process
      cmp      eax, ebx
      je      Same
      jmp      Reduce            ;if not divide the divisor by 10

   Reduce:
      mov      eax, ebx         ;divide the divisor by 10
      mov      ebx, 10
      mov      edx, 0
      div      ebx
      mov      ebx, eax
      jmp      Divide

   Compute:
      mov      edx, 0            ;divide the number by ebx
      div      ebx
      add      eax, 48            ;convert to ascii
      cld
      stosb                  ;store the number as a string byte
      cmp      ebx, 100         ;the number is 100 or greater so jump to the special case
      jge      Div2
      cmp      edx, 0            ;is there is a remainder repeat if not skip to the end
      je      LastSteps
      jmp      DoAgain

   DoAgain:
      mov      eax, ebx      ;reduce ebx to match the number of 10s place is the number
      push           ecx
      mov      ecx, edx      ;save the remainder
      mov          edx, 0
      mov      ebx, 10
      div      ebx
      mov      ebx, eax      ;update ebx
      mov      eax, ecx      ;now divide the remainder by the updated ebx
      pop      ecx
      mov      edx, 0
      div      ebx
      add      eax, 48
      cld
      stosb
      jmp      Continue2
      
   LastSteps:
      mov      al, ' '         ;store a space into the string
      stosb
      add      esi,  4
      pop      ecx
      loop   Check
      cmp      ecx, 0
      je      Finish

   Continue2:
      cmp      edx, 9            
      jg      DoAgain            ;if the remainder is greater than 9 than reduce further
      cmp      edx, 0            ;is there is a remainder repeat if not skip to the end
      je      LastSteps
      mov      eax, edx         ;if not convert to ascii and store
      add      eax, 48
      cld
      stosb
      jmp      LastSteps

   Zero:
      add      eax, 48
      cld
      stosb
      jmp      LastSteps

   Same:       
      cmp      eax,1
      je      zero            ;if the number is 1 just store it
      mov      edx, 0            ;divide the number by ebx
      div      ebx
      add      eax, 48            ;convert to ascii
      cld
      stosb
      jmp      Div2

   Div2:
      push           ecx
      mov      ecx, edx         ;save the remainder
      mov      eax, ebx         ;divide the divisor by 10
      mov      ebx, 10
      mov      edx, 0
      div      ebx
      mov      ebx, eax         ;update the divisor
      mov      eax, ecx         ;restore the remainder
      pop      ecx
      mov      edx, 0
      div      ebx
      add      eax, 48
      cld
      stosb
      cmp      ebx, 1
      je      LastSteps
      jmp      Div2

   
   Finish:
      displayString   ArrayS   ;display string MACRO

      popad
      pop      ebp

      RET      12
   WriteVal   ENDP
;---------------------------------------------------------------
;Calculate Procedure - Calculates the average and sum of a set of
; numbers
;Receives: The address of the sum and average strings
; and the address of an array of numbers
;Returns: The sum and average of an array
;Preconditions: The user has entered valid input
;---------------------------------------------------------------
   Calculate   PROC
      push   ebp
      mov      ebp, esp
      mov      esi, [ebp + 16]
      pushad

      call           CrLf
      mov      ecx, 10
      mov      eax, 0

   CalcSum:
      add      eax, [esi]         ;accumulate the sum
      add      esi, 4
      loop   CalcSum

      mov      edx, [ebp + 12]      
      call   WriteString         ;display the sum
      call   WriteDec
      call   CrLf

      mov      ebx, 10
      mov      edx, 0
      div      ebx               ;find the average
      mov      edx, [ebp + 8]
      call   WriteString         ;display the average
      call   WriteDec
      call   CrLf

      popad
      pop   ebp

      RET      12
   Calculate   ENDP
;---------------------------------------------------------------
;Endgame Procedure - Say goodbye to the user
;Receives: Offset of Goodbye string
;Returns: None
;Preconditions: None
;---------------------------------------------------------------
   EndGame      PROC
      push   ebp
      mov      ebp, esp
      push   edx

      mov      edx, [ebp + 8]      ;say goodbye to the user
      call   WriteString
      call   CrLf

      pop      edx
      pop      ebp
      RET      4
   EndGame      ENDP

END main

dedndave

when you multiply the accumulated value by 10, before adding the new digit.....

   store:                  ;store the value
      sub      al, 48         ;convert to it's ASCII  equivalent
      push   eax               
      push   ecx
      mov      eax, edx         
      mov      ecx, 10         ;multiply edx by 10
      mul      ecx

;check EDX here

      mov      edx, eax
      pop      ecx
      pop      eax


the MUL instruction multiplies the value in EAX by the specified operand (ECX, in this case)
the result of a 32-bit MUL is 64 bits wide, and resides in EDX:EAX (treated as a 64-bit value)

so, after MUL, check to see if EDX is still 0 - if not, the number is too large

now, you are going to add the new digit
after you add the new digit, check the carry flag for 32-bit overflow

      push   ebx            ;add to the accumulator
      movsx   ebx, al
      add      edx, ebx
      pop      ebx


in this case, ADD EDX,EBX will set the carry flag if too large

      push   ebx            ;add to the accumulator
      movsx   ebx, al
      add      edx, ebx
      pop      ebx
      jc      too_large  ;jump if carry

sage112

Thanks that makes a lot of sense, I tried implementing your suggestion but the carry flag is never set with numbers that are clearly too large for the register (like 15616148561615630).

dedndave

you have to make both tests

      sub      al, 48         ;convert to it's ASCII  equivalent
      push   eax               
      push   ecx
      mov      eax, edx         
      mov      ecx, 10         ;multiply edx by 10
      mul      ecx

;check EDX here
      cmp      edx,0
      jnz     too_large

      mov      edx, eax
      pop      ecx
      pop      eax


that test will catch most values
however, there will be a few cases where adding the new digit to the accumulated value may cause overflow
so - you need to check carry, as well

the code could be re-written so that both tests are made at the same time
but, i didn't want to re-write all your code   :P

here's a basic idea, though

    mul     ecx

;result of MUL is in EDX:EAX

    add     eax,NewDigit
    adc     edx,0
    jnz     too_large


now, both tests are made with one conditional branch

sage112

Thanks for working with me through this. I'm implementing both tests and for some reason the flag is never caught.  I even tried to doing a hard test of comparing the full number right before it's added to the array as an integer and in that manner it always sets the badnum flag even if the number is 1.

;Check edx
      mov      eax, edx         
      mov      ecx, 10         ;multiply edx by 10
      mul      ecx
      cmp     edx,0         ;check if there is overflow to edx
      jnz     badnum     


;Check Flag
push             ebx            ;add to the accumulator
movzx   ebx, al
add      edx, ebx
pop      ebx
jc      badnum     


;Hard check to see if the number is greater than the max size of a 32 bit register

mov      eax, edx
cmp      eax, 4294967294
jle      Store2
jmp      badnum

dedndave

that last bit of code shouldn't be necessary - doesn't make sense, actually

here's why: if the number is greater than a 32-bit register will hold, it won't fit in a 32-bit register   :biggrin:
(also - JLE is for signed comparison, and you have an unsigned value - JBE would be the one to use)

i have some things to do for the next couple hours - then i'll have another look

dedndave


dedndave

well - one problem is not your fault - that should make you feel better
in fact, it's not Kip's fault, either
microsoft has poorly documented the behaviour of the ReadConsole function (used by Kip in the ReadString function)

i played with this several months ago, and i found that the best thing to do is to provide an input buffer of 256 characters
(i changed the size of Numstr to 256 bytes)

however, you're not off the hook - lol
i have found a few errors and still have more to find

2 big ones....

first, one of the arguments you pass to your ReadVal function is NumSC
oops - NumSC in the data section is set to 0

at the end of the Instructions function....

    RET     2
it should be RET 24, as you have passed 6 dword arguments

i did the checks this way

   store:                  ;store the value
      sub      al, 48         ;convert to it's ASCII  equivalent
      push   eax               
      push   ecx
      mov      eax, edx         
      mov      ecx, 10         ;multiply edx by 10
      mul      ecx
cmp edx,0                                                                 ;;;;added this line
      mov      edx, eax
      pop      ecx
      pop      eax
jnz badnum                                                                ;;;;added this line

      push   ebx            ;add to the accumulator
      movsx   ebx, al
      add      edx, ebx
      pop      ebx
jc badnum                                                                  ;;;;added this line
      loop   counter


that part seems to work - so, that will give you something to play with while i look at the rest of the code   :t

sage112

Thanks for the feedback, I've changed my code so many times in the process of building it that I forgot to update certain areas once I make a change. Hopefully I can figure this out soon.

dedndave

i think i'm worn out for today
i'll spend a little more time on it tomorrow   :t

sage112

Oh no worries, the assignment is due tonight. Thanks for all your help! I'll keep cracking at it until the last minute.  :biggrin:

sage112

Once I implemented user 256 bytes for Numstr the catch worked, thanks for all your help!

dedndave