linuxassemblyerror-handlingx86-64nasm

NASM Calculator - Won't print the result


I'm new to learning assembly language, and am trying to program a calculator where a user can input 2 numbers, and use either addition or subtraction and for the result to display. I have tried so many things, but can't seem to find where I've gone wrong! Any advice or solution would be greatly appreciated. Thank you in advance :)

  section .data

        welcome db 0dh, 0ah, 0dh, 0ah, "**Hey, welcome to this calculator!**", 0dh, 0ah
        welcome_length equ $ - welcome
        msg_prompt db "Add your calculation (e.g., 24 + 6): ", 0
        msg_result db "Result: ", 0
        msg_newline db 10, 0    
        invalid_operator db "Invalid input, please use + or - for operations", 0

    section .bss
        buffer resb 32
        num1 resq 1 ; Number 1
        num2 resq 1 ; Number 2

    section .text
        global _start
    

    _start:
    ; Print the welcome message
        mov rsi, welcome
        mov rdx, welcome_length
        mov rax, 1
        mov rdi, 1 
        syscall

    
    ; Print the message prompt
        mov rsi, msg_prompt
        mov rdx, 37
        mov rax, 1
        mov rdi, 1
        syscall
    
    ; Read the input
        mov rsi, buffer
        mov rdx, 32
        mov rax, 0
        mov rdi, 0
        syscall

    ; Convert first number to an int
        mov rsi, buffer
        call parse_number
        mov [num1], rax

    ; Move to the operator (adjusying rsi based on parsed length)
    add rsi, rax
    call skip_spaces

    ; Check the operator
    movzx rbx, byte [rsi]
    cmp rbx, '+'
    je add_numbers
    cmp rbx, '-'
    je subtract_numbers

    ; Invalid operator - print error message
    mov rsi, invalid_operator
    mov rdx, 47
    mov rax, 1
    mov rdi, 1
    syscall
    jmp _exit

    ; Move to the 2nd
add_numbers:
    inc rsi
    call skip_spaces
    call parse_number
    mov [num2], rax

    ; Perform additiom
    mov rax, [num1]
    add rax, [num2]
    call print_result
    jmp _exit

    ; Move to the 2nd
subtract_numbers:
    inc rsi
    call skip_spaces
    call parse_number
    mov [num2], rax

    ; Perform subtraction
    mov rax, [num1]
    sub rax, [num2]
    call print_result
    jmp _exit

print_result:
    ; Printing the results
    mov rsi, msg_result
    mov rdx, 8
    mov rax, 1
    mov rdi, 1
    syscall

    ; Converting results to a string
    call print_number
    ret

parse_number:
    ; Convering a string to a number
    xor rax, rax
    mov rcx, 0

parse_loop:
    movzx rbx, byte [rsi]
    test rbx, rbx
    jz done_parsing
    cmp rbx, '0'
    jb done_parsing
    cmp rbx, '9'
    ja done_parsing
    sub rbx, '0'
    imul rax, rax, 10
    add rax, rbx
    inc rsi
    inc rcx
    jmp parse_loop
done_parsing:
    mov rax, rcx
    ret

print_number:
    ; Convert the number in rax  to ASCII in buffer and display
    mov rsi, buffer + 32
    mov rcx, 10
    add rsi, 1
    mov byte [rsi], 0

    ; If the number is 0, handle it seperately
    cmp rax, 0
    je print_zero

print_loop:
    xor rdx, rdx
    div rcx
    dec rsi
    add dl, '0'
    mov [rsi], dl
    test rax, rax
    jnz print_loop
    jmp print_result_buffer

print_zero:
    dec rsi
    mov byte [rsi], '0'
    jmp print_result_buffer
    
print_result_buffer:
    ; display result from buffer
    mov rdx, buffer + 32
    sub rdx, rsi
    mov rax, 1
    mov rdi, 1
    syscall
    ret

skip_spaces:
    ; skipping any spaces in the buffer
skip_space_loop:
    movzx rbx, byte [rsi]
    cmp rbx, ' ' 
    jne skip_done
    inc rsi
    jmp skip_space_loop
skip_done:
    ret

_exit:
    ; Exit the program
    mov rax, 60
    xor rdi, rdi
    syscall

I've lost track of all the variations I've tried. The output does allow a user to put the equation, 3 + 2 for example, and Result: prints, but doesn't actually show the numerical calculation


Solution

  • First, the bugs:

    1. done_parsing overwrites the number parsed with the length
    2. print_result destroys the number before it can be printed
    3. print_result_buffer prints 1 less characters than needed
    4. (adjusying rsi based on parsed length) is not needed because rsi is automatically pointing where parsing stopped
    5. you write an otherwise useless 0 outside of the buffer

    Next, the missed optimizations:

    1. you use 64 bit operations unnecessarily in many places
    2. you don't need to handle 0 specially, you can divide 0 by 10 just fine
    3. you should use rip relative addressing
    4. parse_number does not need to count the length
    5. you can read in the second number before checking the operator

    An alternate version could look like:

    default rel
    section .data
    
            welcome db 0dh, 0ah, 0dh, 0ah, "**Hey, welcome to this calculator!**", 0dh, 0ah
            welcome_length equ $ - welcome
            msg_prompt db "Add your calculation (e.g., 24 + 6): ", 0
            msg_result db "Result: ", 0
            msg_newline db 10, 0    
            invalid_operator db "Invalid input, please use + or - for operations", 0
    
        section .bss
            buffer resb 32
            num1 resq 1 ; Number 1
            num2 resq 1 ; Number 2
    
        section .text
            global _start
        
    
        _start:
        ; Print the welcome message
            lea rsi, [welcome]
            mov edx, welcome_length
            mov eax, 1
            mov edi, 1 
            syscall
    
        ; Print the message prompt
            lea rsi, [msg_prompt]
            mov edx, 37
            mov eax, 1
            mov edi, 1
            syscall
    
        ; Read the input
            lea rsi, [buffer]
            mov edx, 32
            xor eax, eax
            xor edi, edi
            syscall
    
        ; Convert first number to an int
            call parse_number
            mov [num1], rax
    
        ; Move to the operator
        call skip_spaces
    
        ; Check the operator
        mov bl, [rsi]
        inc rsi
        call skip_spaces
        call parse_number
        mov [num2], rax
        cmp bl, '+'
        je add_numbers
        cmp bl, '-'
        je subtract_numbers
    
        ; Invalid operator - print error message
        lea rsi, [invalid_operator]
        mov edx, 47
        mov eax, 1
        mov edi, 1
        syscall
        jmp _exit
    
        ; Move to the 2nd
    add_numbers:
    
        ; Perform additiom
        mov rax, [num1]
        add rax, [num2]
        call print_result
        jmp _exit
    
        ; Move to the 2nd
    subtract_numbers:
        ; Perform subtraction
        mov rax, [num1]
        sub rax, [num2]
        call print_result
        jmp _exit
    
    print_result:
        push rax
        ; Printing the results
        lea rsi, [msg_result]
        mov edx, 8
        mov eax, 1
        mov edi, 1
        syscall
    
        ; Converting results to a string
        pop rax
        jmp print_number
    
    parse_number:
        ; Convering a string to a number
        xor eax, eax
    
    parse_loop:
        movzx ecx, byte [rsi]
        sub cl, '0'
        cmp cl, '9'
        ja done_parsing
        imul rax, rax, 10
        add rax, rcx
        inc rsi
        jmp parse_loop
    done_parsing:
        ret
    
    print_number:
        ; Convert the number in rax  to ASCII in buffer and display
        lea rsi, [buffer + 32]
        mov ecx, 10
    
    print_loop:
        xor edx, edx
        div rcx
        dec rsi
        add dl, '0'
        mov [rsi], dl
        test rax, rax
        jnz print_loop
    
    print_result_buffer:
        ; display result from buffer
        lea rdx, [buffer + 32]
        sub rdx, rsi
        mov eax, 1
        mov edi, 1
        syscall
        ret
    
    skip_spaces:
        ; skipping any spaces in the buffer
    skip_space_loop:
        cmp byte [rsi], ' '
        jne skip_done
        inc rsi
        jmp skip_space_loop
    skip_done:
        ret
    
    _exit:
        ; Exit the program
        mov eax, 60
        xor edi, edi
        syscall