x86:为什么一个堆栈分配的数组会覆盖另一个?

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

我想编写一个简单的 x86 程序,它存储 2 个数组:一个列出学生姓名,另一个列出他们的成绩。假设我想要每个 8 个字节,我决定简单地在堆栈上分配两段内存: (1) grade_ptr,它指向学生的成绩并保存 8 * [num_students] 字节; (2) names_ptr,它指向一个学生姓名数组,也持有 8 * [num_students] 字节。

我写了下面的代码来完成这个任务:

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 

当我 运行 程序时,names_ptr 溢出并开始 运行 进入被 grades_ptr 占用的 space,尽管我不确定为什么。以下面的输出为例。

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

如您所见,每个名字都会覆盖上一个成绩。我认为这个问题源于我如何在顶部代码的堆栈上分配内存。有人可以指出我的错误并指出正确的方向吗?

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  "%10s",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

在几个地方有这样的代码:
lea rsi, [names_ptr+rbx*8]

mov rax, [grades_ptr+rbx*8]

这不会像您希望的那样间接通过内存中的指针。它所做的是相对于变量地址 names_ptr 的索引,而不是存储在该变量中的指针。

要解决这个问题,您必须将指针加载到寄存器中,然后进行索引操作。所以你可以用类似的东西替换第一个:

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

更好的方法是利用可用的寄存器。将 names_ptr 放入 r14,将 grades_ptr 放入 r15。然后每次都可以使用lea rsi, [r14+rbx*8]而不需要额外的负载。

一定要在函数的开头按下 r14 和 r15(还有 rbx)。这不是绝对必要的,因为你没有从函数中 return,但这是个好习惯。