You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

进程内内存生命周期疑问:返回栈内存引发段错误原因探究

关于栈内存非法访问引发段错误的疑问与分析

问题代码

#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);
}

运行上述代码触发了内核段错误,以下是我的理解与疑问:

我的理解

  1. 虚拟内存与页管理:
    由于操作系统以页为单位管理内存,如果进程所用页面的虚拟内存与物理内存映射关系未变,应该可以访问该页内所有内存(内容可能是垃圾数据或被进程内其他线程覆盖)。
  2. 页级重分配:
    若操作系统要重新分配内存,会以页为单位进行,而非字节级。
  3. 页重映射与段错误:
    除非char数组所在内存位于不同页面且操作系统将该页重映射给其他进程,否则访问此内存不应引发段错误。

我的疑问

  1. 内核非法内存访问检测:
    内核如何识别这是非法内存访问?若内存页尚未被重分配,为何会标记此情况?
  2. 页有效性:
    内核是否会在元数据中将整个页面标记为有效或无效?这是否用于指示页面是否仍被当前进程使用?

环境信息

  • 操作系统: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

理解误区与问题解答

核心错误根源

你遇到的段错误和页重分配无关,本质是返回栈内存指针后对其执行写操作,栈内存的管理逻辑和你对虚拟内存页的理解存在偏差。

你的理解偏差点

  1. 虚拟内存页的访问权限不只是物理映射:
    栈所在页面确实仍属于进程,物理映射也没被内核回收,但页表权限位+用户态栈保护机制会限制访问。栈帧销毁时,栈指针上移标记该区域可复用,但glibc等运行时会在栈帧末尾设置guard page(保护页),或通过栈金丝雀(canary)检测非法写入,这些机制会直接触发内存访问错误。
  2. 页级重分配不是触发段错误的原因:
    根本等不到内核重分配页面,当你用strncpy往已释放的栈内存写数据时,要么直接写到了无权限的保护页,要么破坏了其他栈帧的金丝雀值,触发用户态的错误检查,最终由内核发送段错误信号。

疑问解答

  1. 内核如何识别非法访问?
    内核通过页表权限位和访问类型判断:

    • 若写入到保护页:保护页被标记为PROT_NONE(无任何权限),触发页错误,内核发送SIGSEGV信号。
    • 若写入到已复用的栈区域:虽然页面权限是可读可写,但破坏了栈金丝雀值后,glibc的stack_chk_fail函数会主动调用abort(),内核处理该信号时表现为段错误。
  2. 内核是否标记整个页面有效/无效?
    是的,内核通过页表项的存在位标记页面有效性,但这只是基础条件。栈页面通常是有效的,已释放栈区域的访问限制更多是由用户态的栈管理机制(编译器、glibc)实现的,而非内核直接标记整个页面无效。

内容的提问来源于stack exchange,提问作者Gokulanandan S

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 14:04:52