assemblyx86-64

Having trouble adding two numbers taken as input from the terminal in assembly


I'm taking in two numbers as strings in assembly, then I'm converting them to integers, adding them, converting the result to a string, then printing it to the terminal. The main issue I have is that I'll be able to enter the two numbers, but after entering them no result is displayed.

.global _start           # Declare the global entry point
.intel_syntax noprefix   # Use Intel assembly syntax without prefixes

.section .bss            # Reserve uninitialized space for buffers
buffer1: .space 20
buffer2: .space 20
result_buffer: .space 20

.section .text
_start:
    # Read the first number
    mov rax, 0          # sys_read
    mov rdi, 0          # File descriptor: stdin  
    lea rsi, [buffer1]
    mov rdx, 20         # Max bytes to read
    syscall
    mov byte ptr [rsi + rax], 0 # null terminate the input buffer

    lea rsi, [buffer1]
    call string_to_int
    mov r8, rax         # Store the first integer in r8

    # Read the second number
    mov rax, 0          # sys_read
    mov rdi, 0          # File descriptor: stdin  
    lea rsi, [buffer2]
    mov rdx, 20         # Max bytes to read
    syscall
    mov byte ptr [rsi + rax], 0

    lea rsi, [buffer2]  # Use buffer2 for the second input
    call string_to_int
    mov r9, rax         # Store the second integer in r9

    add r8, r9          # r8 = r8 + r9

    # Convert the result back to a string
    mov rax, r8         # Move the result into rax
    lea rsi, [result_buffer + 19]  # Point to the end of result_buffer
    call int_to_string

    # Calculate the string length (RCX holds the number of digits)
    mov rdx, rcx

    # Write the result
    mov rax, 1                  # sys_write
    mov rdi, 1                  # File descriptor: stdout
    lea rsi, [result_buffer]    # Address of result_buffer
    mov rdx, rcx                # number of digits in result
    syscall                     # Perform system call

    # Exit
    mov rax, 60         # sys_exit
    xor rdi, rdi        # Exit code: 0
    syscall

# Subroutine: string_to_int
# Converts a null-terminated string to an integer
# Input: RSI points to the string
# Output: RAX contains the integer value
string_to_int:
    xor rax, rax    # Clear rax (result)
    xor rcx, rcx    # Clear rcx (multiplier, initially 0)

convert_loop:
    movzx rdx, byte ptr [rsi]   # Load byte from the string
    cmp rdx, 10                 # Check for newline (ASCII '\n')
    je convert_done             # Exit loop on newline
    sub rdx, '0'                # Convert ASCII to digit (0-9)
    imul rax, rax, 10           # Multiply result by 10
    add rax, rdx                # Add the current digit to the result
    inc rsi                     # Move to the next character
    jmp convert_loop 

convert_done:
    ret

# Subroutine: int_to_string
# Converts an integer to a null-terminated string
# Input: RAX contains the integer, RSI points to the buffer
# Output: The buffer contains the string representation
int_to_string:
    xor rcx, rcx            # Digit counter (RCX will store the length)
    xor rbx, rbx            # Temporary register
convert_to_str:
    xor rdx, rdx            # Clear RDX for division
    mov rbx, 10             # Divisor
    div rbx                 # RAX = RAX / 10, RDX = remainder
    add dl, '0'             # Convert remainder to ASCII
    dec rsi                 # Move backwards in buffer
    mov [rsi], dl           # Store ASCII character
    inc rcx                 # Increment digit counter
    test rax, rax           # Check if RAX is 0
    jnz convert_to_str      # Continue if not zero

    # Null-terminate the string
    mov byte ptr [rsi + rcx], 0  # Null-terminate the string
    ret

Solution

    1. The argument to int_to_string points to the end of result_buffer
    2. It iterates backward
    3. When it returns, rsi is [correctly] pointing to the resulting ascii string (which is nearer the end of result_buffer than the beginning).

    For example, if we enter 23 and 36, the result of the addition will be 59

    The result_buffer will look like:

    OFFSET 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
    ASCII 5 9
    HEXVAL 35 39 00
    RSI ↑rsi

    So, when printing, we should just use the returned value of rsi and not do:

    lea rsi, [result_buffer]    # Address of result_buffer
    

    Here is the corrected code:

    .global _start           # Declare the global entry point
    .intel_syntax noprefix   # Use Intel assembly syntax without prefixes
    
    .section .bss            # Reserve uninitialized space for buffers
    buffer1: .space 20
    buffer2: .space 20
    result_buffer: .space 20
    
    .section .text
    _start:
        # Read the first number
        mov rax, 0          # sys_read
        mov rdi, 0          # File descriptor: stdin
        lea rsi, [buffer1]
        mov rdx, 20         # Max bytes to read
        syscall
        mov byte ptr [rsi + rax], 0 # null terminate the input buffer
    
        lea rsi, [buffer1]
        call string_to_int
        mov r8, rax         # Store the first integer in r8
    
        # Read the second number
        mov rax, 0          # sys_read
        mov rdi, 0          # File descriptor: stdin
        lea rsi, [buffer2]
        mov rdx, 20         # Max bytes to read
        syscall
        mov byte ptr [rsi + rax], 0
    
        lea rsi, [buffer2]  # Use buffer2 for the second input
        call string_to_int
        mov r9, rax         # Store the second integer in r9
    
        add r8, r9          # r8 = r8 + r9
    
        # Convert the result back to a string
        mov rax, r8         # Move the result into rax
        lea rsi, [result_buffer + 19]  # Point to the end of result_buffer
        call int_to_string
    
        # Calculate the string length (RCX holds the number of digits)
        mov rdx, rcx
    
        # Write the result
        mov rax, 1                  # sys_write
        mov rdi, 1                  # File descriptor: stdout
    # FIX: do _not_ do this
        ###lea rsi, [result_buffer]    # Address of result_buffer
        mov rdx, rcx                # number of digits in result
        syscall                     # Perform system call
    
        # Exit
        mov rax, 60         # sys_exit
        xor rdi, rdi        # Exit code: 0
        syscall
    
    # Subroutine: string_to_int
    # Converts a null-terminated string to an integer
    # Input: RSI points to the string
    # Output: RAX contains the integer value
    string_to_int:
        xor rax, rax    # Clear rax (result)
        xor rcx, rcx    # Clear rcx (multiplier, initially 0)
    
    convert_loop:
        movzx rdx, byte ptr [rsi]   # Load byte from the string
        cmp rdx, 10                 # Check for newline (ASCII '\n')
        je convert_done             # Exit loop on newline
        sub rdx, '0'                # Convert ASCII to digit (0-9)
        imul rax, rax, 10           # Multiply result by 10
        add rax, rdx                # Add the current digit to the result
        inc rsi                     # Move to the next character
        jmp convert_loop
    
    convert_done:
        ret
    
    # Subroutine: int_to_string
    # Converts an integer to a null-terminated string
    # Input: RAX contains the integer, RSI points to the buffer
    # Output: The buffer contains the string representation
    int_to_string:
        xor rcx, rcx            # Digit counter (RCX will store the length)
        xor rbx, rbx            # Temporary register
    convert_to_str:
        xor rdx, rdx            # Clear RDX for division
        mov rbx, 10             # Divisor
        div rbx                 # RAX = RAX / 10, RDX = remainder
        add dl, '0'             # Convert remainder to ASCII
        dec rsi                 # Move backwards in buffer
        mov [rsi], dl           # Store ASCII character
        inc rcx                 # Increment digit counter
        test rax, rax           # Check if RAX is 0
        jnz convert_to_str      # Continue if not zero
    
        # Null-terminate the string
        mov byte ptr [rsi + rcx], 0  # Null-terminate the string
        ret