考虑以下最小 C 程序:
案例编号1:
#include <stdio.h>
#include <string.h>
void foo(char* s)
{
char buffer[10];
strcpy(buffer,s);
}
int main(void)
{
foo("01234567890134567");
}
这不会导致崩溃转储
如果只添加一个字符,则新的 main 为:
案例编号 2:
void main()
{
foo("012345678901345678");
^
}
程序因分段错误而崩溃。
看起来除了堆栈中保留的 10 个字符之外,还有额外的空间可容纳 8 个额外的字符。因此第一个程序不会崩溃。但是,如果您再添加一个字符,您就会开始访问无效内存。我的问题是:
- 为什么我们要在堆栈中保留这额外的 8 个字符?
- 这是否与内存中的 char 数据类型对齐有关?
在这种情况下,我的另一个疑问是操作系统(在本例中为 Windows)如何检测错误的内存访问?通常根据 Windows 文档,默认堆栈大小为 1MB堆栈大小 https://msdn.microsoft.com/en-us/library/windows/desktop/ms686774(v=vs.85).aspx。所以我不明白操作系统如何检测到正在访问的地址位于进程内存之外,特别是当最小页面大小通常为 4k 时。在这种情况下,操作系统是否使用 SP 来检查地址?
PD:我使用以下环境进行测试
Cygwin
海湾合作委员会 4.8.3
Windows 7操作系统
EDIT:
这是生成的程序集http://gcc.godbolt.org/# http://gcc.godbolt.org/#但使用 GCC 4.8.2,我在可用编译器中看不到 GCC 4.8.3。但我想生成的代码应该是类似的。我构建的代码没有任何标志。我希望具有汇编专业知识的人能够阐明 foo 函数中发生的情况以及为什么额外的字符会导致 seg 错误
foo(char*):
pushq %rbp
movq %rsp, %rbp
subq $48, %rsp
movq %rdi, -40(%rbp)
movq %fs:40, %rax
movq %rax, -8(%rbp)
xorl %eax, %eax
movq -40(%rbp), %rdx
leaq -32(%rbp), %rax
movq %rdx, %rsi
movq %rax, %rdi
call strcpy
movq -8(%rbp), %rax
xorq %fs:40, %rax
je .L2
call __stack_chk_fail
.L2:
leave
ret
.LC0:
.string "01234567890134567"
main:
pushq %rbp
movq %rsp, %rbp
movl $.LC0, %edi
call foo(char*)
movl $0, %eax
popq %rbp
ret