越界访问


页式存储管理机制通过页面目录和页面表将每个线性地址(也可以理解为虚拟地址)转换成物理地址。如果在这个过程中遇到某种阻碍而是CPU无法最终访问到相应的物理内存单元,映射便失败了,而当前的指令也就不能执行完成。此时CPU会产生一个页面错误(page fault)异常(exception)(也称缺页中断),进而执行预定的页面异常处理程序,使应用程序得意从映射失败而暂停的指令处开始恢复执行,或进行一些善后处理。这里所说的阻碍可以有以下几种情况:

1) 相应的页面目录或页面表项为空,也就是该线性地址与物理地址的映射尚未建立,或者已撤销。

2)相应的物理页面不在内存中。

3)指令中规定的访问方式与页面权限不符,例如企图写一个“只读”页面。

当CPU的运行已经到达了页面异常服务程序的主体do_page_fault()的入口处。

fastcall void do_page_fault(struct pt_regs *regs, unsigned long error_code)
{
/*pt_regs结构指针regs,它指向CPU发生前夕的各寄存器内容的一个副本;这是由内核的中断响应机制保存下来的"现场"
error_code则进一步指明映射失败的具体原因。*/

	struct task_struct *tsk;
	struct mm_struct *mm;
	struct vm_area_struct * vma;
	unsigned long address;
	unsigned long page;
	int write;
	siginfo_t info;


	/* get the address */
	/**
	 * 读取引起异常的线性地址。CPU控制单元把这个值存放在cr2控制寄存器中。
	 */
	__asm__("movl %%cr2,%0":"=r" (address));

	if (notify_die(DIE_PAGE_FAULT, "page fault", regs, error_code, 14,SIGSEGV) == NOTIFY_STOP)
		return;
	/* It's safe to allow irq's after cr2 has been saved */
	/**
	 * 只在保存了cr2就可以打开中断了。
	 * 如果中断发生前是允许中断的,或者运行在虚拟8086模式,就打开中断。
	 */
	if (regs->eflags & (X86_EFLAGS_IF|VM_MASK))
		local_irq_enable();
	tsk = current;  //取得当前进程的task_struct结构的地址
	info.si_code = SEGV_MAPERR;
	if (in_atomic() || !mm)
		goto bad_area_nosemaphore;
	vma = find_vma(mm, address);
	/**
	 * 如果vma为空,说明在出错地址后面没有线性区了,说明错误的地址肯定是无效的。
	 */ 
	if (!vma)
		goto bad_area;
	/**
	 * vma在address后面,并且它的起始地址在address前面,说明线性区包含了这个地址。
	 * 谢天谢地,这很可能不是真的错误,可能是COW机制起作用了,也可能是需要调页了。
	 */
	if (vma->vm_start <= address)//表示映射已经建立
		goto good_area;
	if (!(vma->vm_flags & VM_GROWSDOWN)) 当VM_GROWSDOWN为0的话。表示此区间不是堆栈区。

		goto bad_area;
	 * 运行到此,说明address地址后面的vma有VM_GROWSDOWN标志,表示它是一个堆栈区
	 * 请注意,如果是内核态访问用户态的堆栈空间,就应该直接扩展堆栈,而不判断if (address + 32 < regs->esp)
	 */
	if (error_code & 4) {
		/*
		 * accessing the stack below %esp is always a bug.
		 * The "+ 32" is there due to some instructions (like
		 * pusha) doing post-decrement on the stack and that
		 * doesn't show up until later..
		 */
		/**
		 * 虽然下一个线性区是堆栈,可是离非法地址太远了,不可能是操作堆栈引起的错误
		 * xie.baoyou注:32而不是4是考虑到pusha的存在。
		 */
		if (address + 32 < regs->esp)
			goto bad_area;
	}
	/**
	 * 线程堆栈空间不足,就扩展一下,一般会成功的,不会运行到bad_area.
	 * 注意:如果异常发生在内核态,说明内核正在访问用户态的栈,就直接扩展用户栈。
	 */
	if (expand_stack(vma, address))
		goto bad_area;

当运行到expand_stack()时,表示属于正常的堆栈扩展请求,那就从缺页的地方开始分配若干页面并建立映射,并将其并入堆栈区间,使其得以扩展。
expand_stack()中的操作(不粘源码了):
将地址按页面边界对齐,并计算需要增长几个页面才能把给定的地址包括进去(通常不是一个)。
在rlim结构数组,规定对各种资源分配使用的限制,所以需要进行检查;如果扩展后以后的空间大小超过了可用于堆栈的资源,或者使动态分配的页面总量超过了可用于该进程的资源限制,那就不能扩展,就会返回一个负的出错代码-ENOMEME,在do_page_fault()中也会返回bad_area;
在正常的请求下(改变了堆栈的结构数据),将转交给good_area完成(完成对新扩展的页面对物理内存的映射)。
good_area:先检查权限问题(堆栈可写),然后采用相应函数进行页面的分配。再分配页面表的时候,先看缓冲池(内核将释放的页面表会先保存在内存池中)中是否为空;
空了通过handle_pte_fault()来分配。-》do_no_page()-》do_anonymous_page()-》alloc_pages()为其分配一个的物理内存页面,通过set_pte()将分配到物理页面连同所有状态及标志位设置进page_table所指的页面表项。至此映射成功。
(中间有一部分代码因为在此情景不涉及没有说明)
特别指出,当CPU从一次页面错误异常处理返回到用户空间时,将会先重新执行因映射失败而中途夭折的那条指令,然后才继续往下执行。(不同于中断)


查看失败的具体原因。(bad_area)

bad_area_nosemaphore:
	/* User mode accesses just cause a SIGSEGV */
	/**
	 * 发生在用户态的错误地址。
	 * 就发生一个SIGSEGV信号给current进程,并结束函数。
	 */
	if (error_code & 4) {
		/* 
		 * Valid to do another page fault here because this one came 
		 * from user space.
		 */
		if (is_prefetch(regs, address, error_code))
			return;

		tsk->thread.cr2 = address;
		/* Kernel addresses are always protection faults */
		tsk->thread.error_code = error_code | (address >= TASK_SIZE);
		tsk->thread.trap_no = 14;
		info.si_signo = SIGSEGV;
		info.si_errno = 0;
		/* info.si_code has been set above */
		info.si_addr = (void __user *)address;
		/**
		 * force_sig_info确信进程不忽略或阻塞SIGSEGV信号
		 * SEGV_MAPERR或SEGV_ACCERR已经被设置在info.si_code中。
		 */
		force_sig_info(SIGSEGV, &info, tsk);
		return;
	}

当error_code的bit0为0,表示没有物理页面;bit1为1表示写操作;bit2为1时,表示失败是当CPU处于用户模式发生的。(VM_GROWSDOWN为1属于特殊情况)
对当前进程的task_struct结构内的一些成分进行设置后,就向该进程发出一个强制的信号“SIGSEGV”,(显示屏显示"Segment Fault"),然后使进程撤销。本次例外任务结束。
在每次从中断/异常返回之前,都要检查当前进程是否有悬而未决的信号需要处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值