Home > database >  x86: Why does one stack-allocated array overwrite the other?
x86: Why does one stack-allocated array overwrite the other?

Time:11-22

I want to write a simple x86 program which stores 2 arrays: one listing the names of students, and the other listing their grades. Assuming I want 8 bytes for each, I decided to simply allocate two segments of memory on the stack: (1) grade_ptr, which points to students' grades and holds 8 * [num_students] bytes; and (2) names_ptr, which points to an array of students' names and also holds holds 8 * [num_students] bytes.

I wrote the following code to accomplish this task:

mov rdi, [num_students]
    mov rbp, rsp
    shl rdi, 3
    sub rsp, rdi
    and rsp, -16            ; Align stack by rounding rsp down to multiple of 16.
    mov [names_ptr], rsp
    
    mov rdi, [num_students]
    shl rdi, 3
    sub rsp, rdi
    and rsp, -16
    mov [grades_ptr], rsp 

When I run the program, names_ptr overflows and begins to run into the space occupied by grades_ptr, though I am unsure why. See the below output as an example.

Enter number students: 3
Enter student 0's name: 1
Enter student 0's grade: 60
Enter student 1's name: 2
Enter student 1's grade: 70
Enter student 2's name: 3
Enter student 2's grade: 80
Grade is 50 (ASCII code for "2")
Grade is 51 (ASCII code for "3")
Grade is 80

As you can see, each name overwrites the previous grade. I think the issue stems from how I allocated memory on the stack in the code at the top. Can someone identify my error and point me in the right direction?

section .data
    num_students_prompt     db  "Enter number students: ",0
    int_format          db  "%lld",0            ; lld to read 8-byte int.
    stud_name_prompt        db  "Enter student %d's name: ",0
    stud_name_format        db  "s",0            ; 10-char(byte) str.
    stud_grade_prompt       db  "Enter student %d's grade: ",0
    
    min_grade_format        db  "Min grade is: %d",10,0
    max_grade_format        db  "Max grade is: %d",10,0
    avg_grade_format        db  "Avg grade is: %d",10,0
    ptr_format          db  "Ptr is: %d",10,0
    grade_format            db  "Grade is %d",10,0
    sum_format          db  "Sum grade is: %d",10,0 

section .bss
    num_students            resq    1
    grade_sum           resq    1
    min_grade           resq    1
    max_grade           resq    1
    avg_grade           resq    1
    names_ptr           resq    2
    grades_ptr          resq    2
    
section .text
    global main
    extern scanf, printf, malloc

main:
    push rbp
    mov rbp, rsp
    
    call _prompt_num_els

    xor rax, rax            ; al stores number vector params to scanf, so it must be empty
    mov rdi, int_format     ; before a printf/scanf call.
    lea rsi, [num_students]
    call scanf
    
    mov rdi, [num_students]
    shl rdi, 3
    sub rsp, rdi
    and rsp, -16
    mov [names_ptr], rax

    xor eax, eax
    mov rdi, ptr_format
    mov rsi, [names_ptr]
    call printf

    mov rdi, [num_students]
    mov rbp, rsp
    shl rdi, 3
    sub rsp, rdi
    and rsp, -16            ; Solve stack alignment by rounding stack ptr down to multiple of 16.
    mov [grades_ptr], rsp
        
    xor eax, eax
    mov rdi, ptr_format
    mov rsi, [grades_ptr]
    call printf

    xor rbx, rbx
    _get_student:
        call _prompt_name
        xor rax, rax
        mov rdi, stud_name_format
        lea rsi, [names_ptr rbx*8]
        call scanf
    
        call _prompt_grade
        xor rax, rax
        mov rdi, int_format
        lea rsi, [grades_ptr rbx*8]
        call scanf

        inc rbx
        cmp rbx, [num_students]
        jl _get_student

    mov rax, [grades_ptr]
    mov [min_grade], rax        ; Before loop, we'll set min/max equal to first grade.
    mov [max_grade], rax
    xor rbx, rbx
    _get_stats:
        mov rax, [grades_ptr rbx*8]
        add [grade_sum], rax

        xor eax, eax
        mov rdi, grade_format
        mov rsi, [grades_ptr rbx*8]
        call printf
        
        cmp [min_grade], rax
        jg _grade_is_min

        cmp [max_grade], rax
        jl _grade_is_max
        jmp _loop_logic

        _grade_is_min:
            mov [min_grade], rax
            jmp _loop_logic
        _grade_is_max:
            mov [max_grade], rax
        _loop_logic:
            inc rbx
            cmp rbx, [num_students]
            jl _get_stats
    
    
    xor edx, edx                ; rdx contains upper 64 bits of dividend.
    mov rax, [grade_sum]            ; rax contains lower 64 bits.
    mov rcx, [num_students]
    div rcx                 ; Compute (rdx:rax)/[num_students].
    mov [avg_grade], rax
    
    xor edx, edx
    mov rdi, sum_format
    mov rsi, [grade_sum]
    call printf

    xor rax, rax
    mov rdi, max_grade_format
    mov rsi, [max_grade]
    call printf

    xor rax, rax
    mov rdi, min_grade_format
    mov rsi, [min_grade]
    call printf
    
    xor rax, rax
    mov rdi, avg_grade_format
    mov rsi, [avg_grade]
    call printf
    

    mov rsp, rbp
    pop rbp
    
    mov rax, 60
    mov rdi, 0
    syscall             ; Exit w/ return code 0.

_prompt_name:
    xor rax, rax
    mov rdi, stud_name_prompt
    mov rsi, rbx
    call printf
    ret

_prompt_grade:
    xor rax, rax
    mov rdi, stud_grade_prompt
    mov rsi, rbx
    call printf
    ret

_prompt_num_els:
    xor eax, eax
    mov rdi, num_students_prompt
    call printf
    ret

CodePudding user response:

In several places there is code like this:
lea rsi, [names_ptr rbx*8]
or
mov rax, [grades_ptr rbx*8]

That doesn't indirect through a pointer in memory like you want. What it does is index relative to the address of the variable names_ptr, rather than to the pointer stored in that variable.

To fix this, you have to load the pointer into a register and then do the index operation. So you could replace the first one with something like:

mov rsi, [names_ptr]
lea rsi, [rsi rbx*8]

Even better would be to take advantage of the registers available. Put names_ptr in r14 and grades_ptr in r15. Then you can use lea rsi, [r14 rbx*8] without an additional load each time.

Be sure to push r14 and r15 (and also rbx) at the beginning of the function. It's not strictly necessary since you don't return from the function, but it's good habit.

  • Related