assemblyx86-64nasmstack-tracecontrol-flow

Why does the assembly code stop functioning right when I remove an unused variable?


I'm a beginner in assembly and I'm encountering unexpected behavior in my code. The code works as expected, printing numbers from contador variable to 1, but when I removed the unused variable loops (which is not used anywhere in the code), it started printing random numbers infinitely. I don't understand why this happens.

Here’s the relevant part of the code:

extern print
extern input
extern string_to_int
extern int_to_string
extern print_num

section .data
contador dq 30
loops dq 10
v4 db " ", 10

section .bss
atual resb 1

section .text
    global main

main:
    push rbp
    mov rbp, rsp 

print_loop:
    mov rax, [contador] ; pass the address 
    call int_to_string
    push rsi
    push rdi

    mov rdx, [rsi]
    mov [atual], rdx
    mov rdi, atual
    call print_num
    mov rdi, v4
    call print
    
    dec qword [contador] 
    cmp qword [contador], 0             
    jne print_loop

    pop rdi
    pop rsi

    mov rsp, rbp
    pop rbp

    mov rax, 60
    mov rdi, 0
    syscall

Why does the code start printing random numbers infinitely when I remove the loops variable? Also, I would appreciate any tips or references to help improve my understanding and skills in assembly, particularly regarding variable manipulation and control flow.

This is the code of my extern functions, that I did before:

section .text
    global print
    global print_num
    global input
    global int_to_string
    global string_to_int

print:
    call print_str
    jmp exit_success

str_len:
    push rbp
    mov rbp, rsp
    mov rax, 0
    .str_len_loop:
        cmp [rdi], byte 0
        je .str_len_end
        inc rdi
        inc rax
        jmp .str_len_loop
    .str_len_end:
        mov rsp, rbp
        pop rbp
        ret

print_str:
    push rbp
    mov rbp, rsp
    push rdi
    call str_len
    pop rsi
    mov rdx, rax
    mov rax, 1
    mov rdi, 1
    syscall
    mov rsp, rbp
    pop rbp
    ret

exit_success:
    xor rdi, rdi
    mov rax, 0
    ret

print_num:
    call print_str_num
    jmp exit_success_num

str_len_num:
    push rbp
    mov rbp, rsp
    mov rax, 0
    .str_len_loop_num:
        cmp [rdi], byte 0
        je .str_len_end_num
        cmp [rdi], byte 48
        jl .str_len_end_num
        cmp [rdi], byte 57
        jg .str_len_end_num
        inc rdi
        inc rax
        jmp .str_len_loop_num
    .str_len_end_num:
        mov rsp, rbp
        pop rbp
        ret

print_str_num:
    push rbp
    mov rbp, rsp
    push rdi
    call str_len_num
    pop rsi
    mov rdx, rax
    mov rax, 1
    mov rdi, 1
    syscall
    mov rsp, rbp
    pop rbp
    ret

exit_success_num:
    xor rdi, rdi
    mov rax, 0
    ret

input:
    push rbp
    mov rbp, rsp
    sub rsp, 8
    mov rdi, 0
    lea rsi, [rsp]
    mov rdx, 8
    mov rax, 0
    syscall
    mov r8, rax
    mov rsp, rbp
    pop rbp
    ret

string_to_int:
    mov rax, 0
.next_digit:
    mov dl, byte [rsi]
    inc rsi
    cmp dl, 48
    jl .fim
    cmp dl, 57
    jg .fim
    sub dl, 48
    imul rax, 10
    add rax, rdx
    jmp .next_digit

.fim:
    mov rdx, 0
    ret

int_to_string:
    mov rbx, 10
.prox_digit:
    mov rdx, 0
    div rbx
    add dl, '0'
    dec rsi
    mov byte [rsi], dl
    cmp rax, 0
    jnz .prox_digit
    ret

I just tried to remove the variable, because I was using it in a wrong logic and was trying just to clean the code after I got the right output. Push/pop rdi and rsi doesn't fix the problem.


Solution

  • Why does the code start printing random numbers infinitely when I remove the loops variable?

    Because your int_to_string was working from a non-initialized pointer RSI, the program was overwriting random memory, but not to a degree that it would crash the program. However, once the loops variable was removed, memory layout changed and this time the memory clobber was less forgiving.

    Your number-printing solution is convoluted!

    Once you have converted the number into a string, you should no longer be using those special case versions print_str_num and str_len_num. You already have text, so simply use the normal versions print_str and str_len.

    First reserve some room for a small general-purpose buffer:

    section .data
    contador dq 30
    
    section .bss
    buffer resb 32
    

    Modify the conversion routine so it takes in RDI the address beyond where the rightmost digit must go (and where you have prepared a space character, newline, and zero-terminator). Upon return RDI will conveniently point at the first leftmost digit in the string. Document any registers that get clobbered and/or preserve those that need preserving:

    ; IN (rax,rdi) OUT (rdi) MOD (rax,rbx,rdx)
    int_to_string:
        mov  rbx, 10
    .prox_digit:
        xor  edx, edx
        div  rbx
        add  dl, '0'
        dec  rdi
        mov  [rdi], dl
        test rax, rax
        jnz  .prox_digit
        ret
    

    The main loop now becomes:

    print_loop:
        mov  rdi, Buffer+28
        mov  dword [rdi], 00000A20h    ; " ", 10, 0, 0
        mov  rax, [contador]
        call int_to_string             ; -> RDI (RAX RBX RDX)
        call print
        dec  qword [contador] 
        jnz  print_loop
    

    Push/pop rdi and rsi doesn't fix the problem.

    Indeed. Additionally you were pushing RSI/RDI multiple times inside the loop, and popping RDI/RSI just a single time outside the loop. That will have left a lot of RDIs and RSIs on the stack. You will always want to keep the stack balanced, what goes up must come down!