进程内内存生命周期疑问:返回栈内存引发段错误原因探究
关于栈内存非法访问引发段错误的疑问与分析
问题代码
#include <stdio.h> #include <string.h> #include <stdlib.h> #define SIZE 100 char* memory(){ char ch[SIZE] = {0}; return ch; } void copy(char *string){ char *new_string = memory(); strncpy(new_string, string, strlen(string)); } int main(){ char *string = "This is going to be copied"; copy(string); }
运行上述代码触发了内核段错误,以下是我的理解与疑问:
我的理解
- 虚拟内存与页管理:
由于操作系统以页为单位管理内存,如果进程所用页面的虚拟内存与物理内存映射关系未变,应该可以访问该页内所有内存(内容可能是垃圾数据或被进程内其他线程覆盖)。 - 页级重分配:
若操作系统要重新分配内存,会以页为单位进行,而非字节级。 - 页重映射与段错误:
除非char数组所在内存位于不同页面且操作系统将该页重映射给其他进程,否则访问此内存不应引发段错误。
我的疑问
- 内核非法内存访问检测:
内核如何识别这是非法内存访问?若内存页尚未被重分配,为何会标记此情况? - 页有效性:
内核是否会在元数据中将整个页面标记为有效或无效?这是否用于指示页面是否仍被当前进程使用?
环境信息
- 操作系统:Ubuntu 24.04.1 LTS
- 内核:Linux 6.8.0-49-generic
- 架构:x86-64
- 编译参数:
gcc ptr.c -g -o ptr -O0(优化级别O0) - gcc版本:gcc (Ubuntu 13.2.0-23ubuntu4) 13.2.0
理解误区与问题解答
核心错误根源
你遇到的段错误和页重分配无关,本质是返回栈内存指针后对其执行写操作,栈内存的管理逻辑和你对虚拟内存页的理解存在偏差。
你的理解偏差点
- 虚拟内存页的访问权限不只是物理映射:
栈所在页面确实仍属于进程,物理映射也没被内核回收,但页表权限位+用户态栈保护机制会限制访问。栈帧销毁时,栈指针上移标记该区域可复用,但glibc等运行时会在栈帧末尾设置guard page(保护页),或通过栈金丝雀(canary)检测非法写入,这些机制会直接触发内存访问错误。 - 页级重分配不是触发段错误的原因:
根本等不到内核重分配页面,当你用strncpy往已释放的栈内存写数据时,要么直接写到了无权限的保护页,要么破坏了其他栈帧的金丝雀值,触发用户态的错误检查,最终由内核发送段错误信号。
疑问解答
内核如何识别非法访问?
内核通过页表权限位和访问类型判断:- 若写入到保护页:保护页被标记为
PROT_NONE(无任何权限),触发页错误,内核发送SIGSEGV信号。 - 若写入到已复用的栈区域:虽然页面权限是可读可写,但破坏了栈金丝雀值后,glibc的
stack_chk_fail函数会主动调用abort(),内核处理该信号时表现为段错误。
- 若写入到保护页:保护页被标记为
内核是否标记整个页面有效/无效?
是的,内核通过页表项的存在位标记页面有效性,但这只是基础条件。栈页面通常是有效的,已释放栈区域的访问限制更多是由用户态的栈管理机制(编译器、glibc)实现的,而非内核直接标记整个页面无效。
内容的提问来源于stack exchange,提问作者Gokulanandan S
相关产品推荐
相关产品推荐

