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,但这是个好习惯。
我想编写一个简单的 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,但这是个好习惯。