Objective: I intend to create a program that appends digits from user input, convert from ASCII to number and into an array (intArray), and also get their sum all using a loop that iterates 5 times. I am very new to assembly language programming so I'm having a really difficult time debugging the program.
So far this is the code I've been able to come up with. It compiles and links successfully. After execution upon the second iteration, it fails with an error:
Enter an integer: 49
Enter an integer: 50
zsh: segmentation fault out/solution
Please what am I doing wrong?
section .bss
input RESB 10 ; reserve 10 bytes for input
section .data
list db 1,2,3,4,5
prompt db "Enter an integer: ", 0
prompt_len equ $ - prompt ; length of the prompt
result_msg db "The sum is: ", 0
result_msg_len equ $ - result_msg
buffer db 12 dup(0) ; buffer for input
intArray dd 5 dup(0)
arraySize db 0
sum dd 0
section .text
global _start
_start:
mov ecx, 5 ; loop 5 times
loop_prompt_handler:
; print the prompt to enter input
mov eax, 4 ; sys_write
mov ebx, 1 ; file descriptor 1 (stdout)
mov ecx, prompt ; message to print
mov edx, prompt_len ; number of bytes to read
int 0x80
; read the input
mov eax, 3 ; sys_read
mov ebx, 0 ; file descriptor 0 (stdin)
mov ecx, input ; message to read
mov edx, 10 ; number of bytes to read
int 0x80
; convert ASCII input to integer
call str_to_int
; append to array
mov ebx, [arraySize] ; load current size of the array
mov edx, intArray ; load address of the array
add edx, ebx ; point to the end of the array
mov [edx], eax ; store the input number at the end of the array
; increment array size
inc byte [arraySize] ; increment array size
; add the input number to the sum
add dword [sum], eax
loop loop_prompt_handler ; loop_prompt_handler label if ecx is not zero
; print the result
mov eax, 4 ; sys_write
mov ebx, 1 ; file descriptor 1 (stdout)
mov ecx, result_msg ; message to print
mov edx, result_msg_len ; number of bytes to read
int 0x80
; convert sum to string
mov eax, [sum]
mov ecx, buffer ; buffer containing the sum as a string
call int_to_string
; print the sum
mov eax, 4 ; sys_write
mov ebx, 1 ; file descriptor 1 (stdout)
mov edx, 12 ; length of the buffer
int 0x80
; exit
mov eax, 1 ; sys_exit
xor ebx, ebx ; exit code
int 0x80
str_to_int:
; ecx: pointer to input string
; eax: result
xor eax, eax ; clear eax
xor edx, edx ; clear edx
str_to_int_loop:
movzx edx, byte [ecx]
test dl, dl
jz str_to_int_end
sub dl, '0'
imul eax, eax, 10
add eax, edx
inc ecx
jmp str_to_int_loop
str_to_int_end:
ret
int_to_string:
; eax contains the string to convert
; ecx points to the buffer to store the string
mov edi, ecx
add edi, 11
mov byte [edi], 0
dec edi
.convert_loop:
xor edx, edx
mov ebx, 10
div ebx
add dl, '0'
mov [edi], dl
dec edi
test eax, eax
jnz .convert_loop
inc edi
mov ecx, edi
ret
The code is using the ECX register for two different purposes at the same time: the address for the buffer and the loop counter. Since you can't choose where to put the address, better pick a register like ESI for controlling the loop. In doing so, you will also have avoided the use of the slow loop
instruction:
mov esi, 5 ; loop 5 times
loop_prompt_handler:
...
dec esi
jnz loop_prompt_handler
prompt db "Enter an integer: ", 0 prompt_len equ $ - prompt ; length of the prompt result_msg db "The sum is: ", 0 result_msg_len equ $ - result_msg
There's no need to zero-terminate these messages. Currently, the print service will be outputting these as a superfluous space character. Just remove the zero or keep it and deduct 1 from the length:
prompt db "Enter an integer: "
prompt_len equ $ - prompt
result_msg db "The sum is: "
result_msg_len equ $ - result_msg
or:
prompt db "Enter an integer: ", 0
prompt_len equ $ - prompt - 1
result_msg db "The sum is: ", 0
result_msg_len equ $ - result_msg - 1
As an example, just these:
mov edx, prompt_len ; number of bytes to read ... mov ecx, input ; message to read
These should really read: "number of bytes to write" and "buffer to read".
sys_read
reports about the number of characters; convert ASCII input to integer call str_to_int
If you want your str_to_int to keep working from a zero-terminated string, then you'll have to zero-terminate the string yourself:
mov eax, 3 ; sys_read
int 0x80 ; -> EAX
; Assuming no errors here, convert ASCII input to integer
mov byte [input + eax], 0
call str_to_int
Reading from the terminal (stdin) the count in EAX includes the linefeed (10) that ends input.
Therefore, in your str_to_int, have the sub dl, '0'
instruction followed by jb str_to_int_end
so as to not erroneously treat the newline as a decimal digit.
mov ebx, [arraySize] ; load current size of the array mov edx, intArray ; load address of the array add edx, ebx ; point to the end of the array mov [edx], eax ; store the input number at the end of the array inc byte [arraySize] ; increment array size
The arraySize variable is defined as a byte, therefore it is wrong to read is as a dword. Just as bad, you store a dword but only increment the variable by 1 instead of by 4.
Two solutions:
movzx ebx, byte [arraySize] ; {0,4,8,12,16}
mov [intArray + ebx], eax
add byte [arraySize], 4
or:
movzx ebx, byte [arraySize] ; {0,1,2,3,4}
mov [intArray + ebx * 4], eax
inc byte [arraySize]
sys_write
works length-basedThere's no need to have int_to_string zero-terminate the string. The print service will output the number of bytes specified in EDX, so you should calculate that number instead of just passing the length of the whole buffer.
; print the sum
mov eax, [sum]
mov ecx, buffer
call int_to_string ; -> ECX
mov edx, buffer + 12
sub edx, ecx
mov ebx, 1 ; file descriptor 1 (stdout)
mov eax, 4 ; sys_write
int 0x80