Search code examples
assemblyfile-iox86masmirvine32

MASM Assembly Program Outputs Incorrect Value When Reading and Parsing File with Irvine32 Library


I'm working on an assembly language program using MASM and the Irvine32 library for a class project. The program is supposed to:

  1. Prompt the user for the name of a text file containing temperature measurements.
  2. Read the contents of the file.
  3. Parse the temperatures (which are comma-separated values).
  4. Reverse the order of the temperatures.
  5. Display the reversed temperatures.

However, when I run the program, the output is incorrect. Instead of displaying the reversed list of temperatures, it only outputs +67, and then terminates.

Output I'm Getting:

Enter the name of the file to be read: C:/CS271/Project6/Temps090124.txt
Here's the corrected temperature order!
+67,
Hope that helps resolve the issue, goodbye!
Press any key to continue...

Expected Output:

I expect the program to display all the temperatures from the file in reverse order, something like:

Enter the name of the file to be read: C:/CS271/Project6/Temps090124.txt
Here's the corrected temperature order!
-1,+2,+5,+10,+15,+20,+25,+30,+35,+40,+45,+42,+38,+34,+30,+25,+20,+15,+10,+7,+3,+0,-2,-3,
Hope that helps resolve the issue, goodbye!
Press any key to continue...

What I've Tried:

  • Checked Parameter Order in Str_Trim Calls: Ensured that the parameters for Str_Trim are pushed onto the stack in the correct order (character to remove first, then the address of the string).
  • Verified File Path: Confirmed that the file path is correct and that the file exists at the specified location.
  • Adjusted File Path Syntax: Tried using backslashes (\) instead of forward slashes (/) in the file path.
  • Added Debugging Outputs: Inserted debugging statements to display intermediate values like userFilePath, fileHandle, bytesRead, readBuffer, numTempsParsed, and the contents of tempArray.
  • Checked the Contents of the File: Opened Temps090124.txt to ensure it contains the correct comma-separated temperature values without any extra characters or whitespace.
  • Ensured Proper Use of Irvine32 Library: Verified that Irvine32.inc and associated library files are correctly set up in my development environment.
  • Reviewed Parsing Logic: Went through the ParseTempsFromString procedure to confirm that it correctly parses the temperatures.

Despite these efforts, the program still outputs only +67,. I suspect there might be an issue with how the file is being read or how the data is being parsed and stored.

My Code:

Here is the full code of my program:

TITLE String Primitives and Macros     (Proj6_brooksc3.asm)

; Author: Cameron Brooks
; Last Modified: 11/24/2024

; Description: This program reads temperature measurements from a specified
;              text file, parses the temperatures, reverses their order,
;              and displays them with the correct ordering.

INCLUDE Irvine32.inc

; Macro definitions...

; Constants
DELIMITER EQU ','           ; Default delimiter
TEMPS_PER_DAY EQU 24        ; Number of temperature readings per day

; Data Section
.DATA
    ; File-related variables
    userFilePath    BYTE 256 DUP(0)                             ; Buffer to store user input filename
    fileHandle      DWORD ?                                      ; To store the file handle
    bufferSize      EQU 1024                                      ; Size of the read buffer
    readBuffer      BYTE bufferSize DUP(0)                        ; Buffer to store file contents
    bytesRead       DWORD ?                                      ; Number of bytes read

    ; Temperature array
    tempArray       SDWORD TEMPS_PER_DAY DUP(0)                   ; Array to store temperature values

    ; Number of temperatures parsed
    numTempsParsed DWORD ?                                       ; To store the actual number of temperatures parsed

    ; Messages
    successMsg      BYTE "Here's the corrected temperature order!", 0
    promptFile      BYTE "Enter the name of the file to be read: ", 0
    fileOpenError   BYTE "Error: Unable to open the file.", 0
    fileReadError   BYTE "Error: Unable to read from the file.", 0
    fileCloseError  BYTE "Error: Unable to close the file.", 0
    exitMsg         BYTE "Hope that helps resolve the issue, goodbye!", 0

.CODE
main PROC
    ; Initialize Program
    CALL ClrScr

    ; Step 1: Get Filename from User
    mGetString OFFSET promptFile, OFFSET userFilePath, 255, OFFSET bytesRead

    ; Null-terminate the userFilePath string
    MOV EAX, [bytesRead]
    MOV EBX, OFFSET userFilePath
    ADD EBX, EAX
    MOV BYTE PTR [EBX], 0

    ; Trim any trailing whitespace or newline characters from userFilePath
    PUSH ' '
    PUSH OFFSET userFilePath
    CALL Str_Trim

    PUSH 0Dh
    PUSH OFFSET userFilePath
    CALL Str_Trim

    PUSH 0Ah
    PUSH OFFSET userFilePath
    CALL Str_Trim

    ; Step 2: Open the Text File
    MOV EDX, OFFSET userFilePath
    CALL OpenInputFile
    CMP EAX, INVALID_HANDLE_VALUE
    JE FileOpenErrorHandler
    MOV fileHandle, EAX

    ; Step 3: Read from the File
    MOV EAX, fileHandle
    MOV EDX, OFFSET readBuffer
    MOV ECX, bufferSize
    CALL ReadFromFile
    JC FileReadErrorHandler
    MOV bytesRead, EAX

    ; Null-terminate the readBuffer
    MOV EAX, [bytesRead]
    MOV EBX, OFFSET readBuffer
    ADD EBX, EAX
    MOV BYTE PTR [EBX], 0

    ; Step 4: Parse Temperatures from String
    PUSH OFFSET readBuffer
    PUSH OFFSET tempArray
    CALL ParseTempsFromString
    ADD ESP, 8

    ; Step 5: Write Temperatures in Reverse Order
    mDisplayString OFFSET successMsg
    CALL CrLf

    PUSH OFFSET tempArray
    CALL WriteTempsReverse
    ADD ESP, 4

    ; Step 6: Close the File
    MOV EAX, fileHandle
    CALL CloseFile
    CMP EAX, 0
    JE FileCloseErrorHandler

    ; Program Completion
    mDisplayString OFFSET exitMsg
    CALL CrLf
    CALL WaitMsg
    EXIT

FileOpenErrorHandler:
    mDisplayString OFFSET fileOpenError
    CALL CrLf
    JMP ExitProgram

FileReadErrorHandler:
    mDisplayString OFFSET fileReadError
    CALL CrLf
    ; Attempt to close the file before exiting
    MOV EAX, fileHandle
    CALL CloseFile
    JMP ExitProgram

FileCloseErrorHandler:
    mDisplayString OFFSET fileCloseError
    CALL CrLf
    JMP ExitProgram

ExitProgram:
    CALL WaitMsg
    EXIT

main ENDP

; Procedure: ParseTempsFromString
; Description: Parses temperature values from a comma-delimited string.

ParseTempsFromString PROC STDCALL
    ; Prologue
    PUSH EBP
    MOV EBP, ESP
    PUSH ESI
    PUSH EDI
    PUSH EBX

    ; Retrieve parameters
    MOV ESI, [EBP + 8]     ; ESI = fileBuffer
    MOV EDI, [EBP + 12]    ; EDI = tempArray

    ; Initialize index
    XOR ECX, ECX           ; ECX = 0 (index into tempArray)

ParseLoop:
    ; Skip delimiters and whitespace
SkipDelimiters:
    CMP BYTE PTR [ESI], 0
    JE ParseEnd
    MOV AL, [ESI]
    CMP AL, DELIMITER
    JE SkipChar
    CMP AL, ' '
    JE SkipChar
    CMP AL, 0Dh           ; Carriage return
    JE SkipChar
    CMP AL, 0Ah           ; Line feed
    JE SkipChar
    JMP CheckSign

SkipChar:
    INC ESI
    JMP SkipDelimiters

CheckSign:
    ; Check for sign
    MOV EDX, 0             ; EDX = 0 (positive)
    CMP BYTE PTR [ESI], '-'
    JNE CheckPlus
    MOV EDX, 1             ; EDX = 1 (negative)
    INC ESI                ; Skip '-'
    JMP ParseNumber

CheckPlus:
    CMP BYTE PTR [ESI], '+'
    JNE ParseNumber
    INC ESI                ; Skip '+'

ParseNumber:
    ; Initialize number accumulator
    XOR EBX, EBX

ParseDigits:
    CMP BYTE PTR [ESI], 0
    JE StoreNumber
    MOV AL, [ESI]
    CALL IsDigit
    JZ IsADigit
    ; Not a digit
    JMP StoreNumber

IsADigit:
    MOV AL, [ESI]
    SUB AL, '0'
    MOVZX EAX, AL
    IMUL EBX, EBX, 10
    ADD EBX, EAX
    INC ESI
    JMP ParseDigits

StoreNumber:
    ; Apply sign
    MOV EAX, EBX
    CMP EDX, 1
    JNE StorePositive
    NEG EAX
StorePositive:
    ; Store EAX in tempArray[ECX]
    MOV [EDI + ECX*4], EAX
    ; Increment ECX
    INC ECX
    CMP ECX, TEMPS_PER_DAY
    JL ContinueParsing
    JMP ParseEnd

ContinueParsing:
    JMP ParseLoop

ParseEnd:
    MOV [numTempsParsed], ECX  ; Store the number of temperatures parsed
    ; Epilogue
    POP EBX
    POP EDI
    POP ESI
    POP EBP
    RET 8
ParseTempsFromString ENDP

; Procedure: WriteTempsReverse
; Description: Writes the temperature values from the provided array in reverse order.

WriteTempsReverse PROC STDCALL
    ; Prologue: Save registers that will be used
    PUSH EBP
    MOV EBP, ESP
    PUSH EBX
    PUSH ESI
    PUSH EDI

    ; Retrieve parameter from stack
    MOV ESI, [EBP + 8]          ; ESI = tempArray

    ; Initialize loop counter
    MOV ECX, [numTempsParsed]    ; ECX = number of temperatures parsed
    DEC ECX                      ; ECX = numTempsParsed - 1

WriteLoop:
    CMP ECX, -1                  ; If ECX < 0, exit loop
    JL WriteEnd

    ; Load temperature value
    MOV EAX, [ESI + ECX*4]       ; EAX = tempArray[ECX]

    ; Display the temperature with sign
    CALL WriteInt                ; Write the integer in EAX with sign

    ; Display the delimiter
    mDisplayChar DELIMITER         ; Display the delimiter

    ; Decrement counter
    DEC ECX
    JMP WriteLoop

WriteEnd:
    ; Display a newline for readability
    CALL CrLf

    ; Epilogue: Restore registers and return
    POP EDI
    POP ESI
    POP EBX
    POP EBP
    RET 4                           ; Clean up 1 parameter (4 bytes)
WriteTempsReverse ENDP

END main

Additional Information:

  • File Contents (Temps090124.txt):

    The file contains the following temperatures:

    -3,-2,0,3,7,10,15,20,25,30,35,40,45,42,38,34,30,25,20,15,10,5,2,-1,
    
  • Development Environment:

    • Assembler: MASM
    • Irvine32 Library version compatible with MASM
    • Running on Windows 11 in Visual Studio 2022

Questions:

  • Why is my program only outputting +67, instead of the reversed list of temperatures?
  • Is there an issue with how I'm reading the file or parsing the data?
  • Are there any mistakes in my use of the Irvine32 library procedures?
  • Could there be a problem with register usage or stack management in my code?

What I've Tried So Far:

  • Corrected the parameter order in Str_Trim calls.
  • Ensured the file path is correct and tried using both forward and backslashes.
  • Added debugging outputs (though not included in the code above) to check intermediate values.
  • Verified the file contents and that the file is accessible.
  • Checked that all procedures clean up the stack correctly.

I'm stuck at this point and would appreciate any guidance on what might be causing this issue.


Solution

  • I'm thrilled to report that after applying the suggested fixes, my assembly language program is now working perfectly! It's incredibly satisfying to see the temperatures being parsed and displayed correctly without any unexpected values. I'd like to share how I resolved the issues, in case others encounter similar problems.

    RECAP:

    • Incorrect parsing of temperatures.
    • An unexpected +67 at the end of the output.
    • An extra comma after the last temperature.
    • Inconsistencies in stack cleanup.

    Solution:

    After some debugging and with the helpful hints from @Jester, I think I've figured out and fixed the issues.

    1. Swapped Arguments in ParseTempsFromString

    Issue:

    I realized that the arguments passed to the ParseTempsFromString procedure were swapped. The procedure expected the parameters in a specific order, but I had pushed them onto the stack incorrectly.

    Incorrect Code:

    ; Original (incorrect) parameter order
    PUSH OFFSET readBuffer        ; Should be second parameter
    PUSH OFFSET tempArray         ; Should be first parameter
    CALL ParseTempsFromString
    

    Fix:

    By swapping the order of the parameters when pushing them onto the stack, I believe the procedure receives the correct arguments.

    Corrected Code:

    ; Correct parameter order
    PUSH OFFSET tempArray         ; First parameter (output array)
    PUSH OFFSET readBuffer        ; Second parameter (input string)
    CALL ParseTempsFromString
    

    Explanation:

    From what I understand, in assembly language, when using the stdcall calling convention, parameters are pushed onto the stack in reverse order. The callee retrieves them based on this order. By swapping the parameters, ParseTempsFromString gets the correct arguments, and the temperatures are parsed correctly.

    2. Misuse of IsDigit Macro

    Issue:

    I was incorrectly using CALL IsDigit in the ParseTempsFromString procedure. Since IsDigit is a macro in the Irvine32 library and not a function, calling it like this was causing problems.

    Incorrect Code:

    CALL IsDigit
    JZ IsADigit
    ; Not a digit
    JMP StoreNumber
    

    Fix:

    I replaced the CALL IsDigit with direct character comparisons to check if a character is a digit.

    Corrected Code:

    ParseDigits:
        CMP BYTE PTR [ESI], 0
        JE StoreNumber
        MOV AL, [ESI]
        CMP AL, '0'
        JL StoreNumber        ; Not a digit
        CMP AL, '9'
        JG StoreNumber        ; Not a digit
        SUB AL, '0'           ; Convert ASCII to numeric value
        MOVZX EAX, AL
        IMUL EBX, EBX, 10
        ADD EBX, EAX
        INC ESI
        JMP ParseDigits
    

    Explanation:

    By directly comparing the character in AL to '0' and '9', I can determine if it's a digit. This prevents non-digit characters from being misinterpreted, which was causing the unexpected +67 in the output.

    3. Incorrect Loop Condition in WriteTempsReverse

    Issue:

    In the WriteTempsReverse procedure, the loop condition wasn't properly exiting when ECX reached -1, which led to an out-of-bounds access and the +67 issue.

    Incorrect Code:

    CMP ECX, -1
    JL WriteEnd
    

    Fix:

    I changed the comparison to use JLE (Jump if Less or Equal) to ensure the loop exits when ECX is -1.

    Corrected Code:

    CMP ECX, -1
    JLE WriteEnd
    

    Explanation:

    Using JLE ensures that when ECX is less than or equal to -1, the loop exits, preventing invalid memory access.

    4. Extra Comma After the Last Temperature

    Issue:

    There was an extra comma displayed after the last temperature.

    Fix:

    I modified the WriteTempsReverse procedure to add the delimiter only between temperatures, not after the last one.

    Corrected Code:

    ; After displaying the temperature
    CMP ECX, 0
    JG AddDelimiter    ; If more temperatures, add delimiter
    JMP WriteLoop      ; If last temperature, skip adding delimiter
    
    AddDelimiter:
        mDisplayChar DELIMITER
        JMP WriteLoop
    

    Explanation:

    By checking if there are more temperatures to display, I avoided adding a comma after the last one.

    5. Consistent Stack Cleanup

    Issue:

    There was inconsistency in how the stack was being cleaned up. I was using RET x in my procedures but also adjusting ESP in the caller.

    Fix:

    I decided to let the callee clean up the stack and removed the ADD ESP, x from the caller.

    Corrected Code:

    ; No need to adjust ESP after the call since the callee cleans up the stack
    CALL ParseTempsFromString
    

    Explanation:

    By letting the callee handle the stack cleanup, I ensured that the stack remains balanced, preventing potential issues.

    6. Credit to Jester's Hint

    Jester's Hint:

    "Hint: If ECX < 0, exit loop ... but that's not what you have coded."

    Impact:

    Jester's hint was really helpful. It made me realize that my loop condition in WriteTempsReverse wasn't correct. Changing JL to JLE fixed the loop, and the unexpected +67 disappeared.

    Final Working Code:

    After applying all these fixes, the program now works correctly. Here's the output:

    Enter the name of the file to be read: C:/CS271/Project6/Temps090124.txt
    Here's the corrected temperature order!
    -1,+2,+5,+10,+15,+20,+25,+30,+35,+40,+45,+42,+38,+34,+30,+25,+20,+15,+10,+7,+3,+0,-2,-3
    Hope that helps resolve the issue, goodbye!
    Press any key to continue...
    

    Conclusion:

    I'm really happy that the program is now functioning as expected. The process taught me a lot about the importance of parameter order, proper use of macros, and correct loop conditions in assembly language. Thanks to Jester for the helpful hint!

    Takeaway:

    Small mistakes in assembly can cause big problems, so paying attention to details is crucial. Also, community help can be invaluable when debugging.