情景假设:

在堆内存中申请了一块内存,然后释放掉该内存,然后再去访问这块内存。也就是所说的野指针访问。

当cpu产生页面错误时,会把失败的线性地址放在cr2寄存器.线性地址缺页异常的4种情况
1.如果cpu访问的行现地址在内核态,那么很可能访问的是非连续区,需要vmalloc_fault处理.
2.缺页异常发生在中断或者内核线程时,直接失败,因为不可修改页表
3.地址在一个区间内,那就可能是已经物理地址映射了但权限问题(错误处理)或者其物理地址没有分配(分配物理内存)
4.如果找到一个在线性地址其后面的vma(线性地址在空洞).那么可能是空洞上面的区间是
堆栈区,他表示动态分配而没有分配出去的空间,有一种特殊情况,可以缺页异常使得获取物理页框
5.如果找到一个线性地址气候的vma(线性地址在空洞),那么可能是空洞上面的区间不是堆栈区,说明
这个空洞是由于一个映射区被撤销而留下的,那样直接错误处理
 
    1. ==================== arch/i386/mm/fault.c 106 152 ====================
    1. 106 asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code)
    1. 107 {
    1. 108 struct task_struct *tsk;
    1. 109 struct mm_struct *mm;
    1. 110 struct vm_area_struct * vma;
    1. 111 unsigned long address;
    1. 112 unsigned long page;
    1. 113 unsigned long fixup;
    1. 114 int write;
    1. 115 siginfo_t info;
    1. 116
    1. 117 /* get the address */
    1. 118 __asm__("movl %%cr2,%0":"=r" (address));//得到失败的线性地址
    1. 119
    1. 120 tsk = current;//获取当前描述符
    1. 121
    1. 122 /*
    1. 123 * We fault-in kernel-space virtual memory on-demand. The
    1. 124 * 'reference' page table is init_mm.pgd.
    1. 125 *
    1. 126 * NOTE! We MUST NOT take any locks for this case. We may
    1. 127 * be in an interrupt or a critical region, and should
    1. 128 * only copy the information from the master page table,
    1. 129 * nothing more.
    1. 130 */
    1. //如果大于3G,表示缺页异常时,访问的是内核空间,很有可能是访问了非连续的内核空间,转到vmalloc_fault处理
    1. 131 if (address >= TASK_SIZE)
    1. 132 goto vmalloc_fault;
    1. 133
    1. 134 mm = tsk->mm;
    1. 135 info.si_code = SEGV_MAPERR;
    1. 136
    1. 137 /*
    1. 138 缺页异常发生在中断时,是错误不可以的,表示是内核线程,不可以对其页表进行修改
    1. 140 */
    1. 141 if (in_interrupt() || !mm)
    1. 142 goto no_context;
    1. 143
    1. 144 down(&mm->mmap_sem);
    1. 145
    1. 146 vma = find_vma(mm, address);//查到end大于address的地址
    1. 147 if (!vma)//是否在行现地址内,不在转为错误处理
    1. 148 goto bad_area;
    1. //在线性区内,跳到正常处理部分,可能是由于权限问题,也有可能是对应的物理地址没有分配2种情况
    1. 149 if (vma->vm_start <= address)
    1. 150 goto good_area;
    1. 151 if (!(vma->vm_flags & VM_GROWSDOWN))//如果发生在一空洞上方的区间不是堆栈区,那么此地址是由于撤销映射留下的,进行错误处理
    1. 152 goto bad_area;

    1. 220 /*
    1. 221 * Something tried to access memory that isn't in our memory map..
    1. 222 * Fix it, but check if it's kernel or user first..
    1. 223 */
    1. 224 bad_area:
    1. 225 up(&mm->mmap_sem);
    1. 226 //用户态的错误处理
    1. 227 bad_area_nosemaphore:
    1. 228 /* User mode accesses just cause a SIGSEGV */
    1. 229 if (error_code & 4) {//判断错误发生在用户态
    1. 230 tsk->thread.cr2 = address;
    1. 231 tsk->thread.error_code = error_code;
    1. 232 tsk->thread.trap_no = 14;
    1. 233 info.si_signo = SIGSEGV;//强制发送SIGEGV信号

 
上面提到的第4种情况,因为越界访问而照成堆栈区间扩展的情况
比如一进城要调用某个子程序,cpu需要把返回地址压栈,然而返回地址写入的是空洞地址,会引发一次页面异常错误
VM_GROWDOWN表示为1表示上面是堆栈区
    1. if (!(vma->vm_flags & VM_GROWSDOWN))
    1. 152 goto bad_area;
    1. 153 if (error_code & 4) {
    1. 154 /*
    1. 155 * 还要检查异常地址是否紧挨着esp指针,如果远超过32,那就是非法越界,错误处理
    1. 32是因为pusha(一次把32个字节压入栈中)
    1. 159 */
    1. 160 if (address + 32 < regs->esp)
    1. 161 goto bad_area;
    1. 162 }
    1. //扩展堆栈
    1. 163 if (expand_stack(vma, address))
    1. 164 goto bad_area;
有下面注释可知,expand_stack只是更改了堆栈区的vm_area_struct结构,没有建立物理内存映射
    1. static inline int expand_stack(struct vm_area_struct * vma, unsigned long address)
    1. 490 {
    1. 491 unsigned long grow;
    1. 492
    1. 493 address &= PAGE_MASK;//边界对齐
    1. 494 grow = (vma->vm_start - address) >> PAGE_SHIFT;//增长几个页框
    1. //判断是否超过了用户堆栈空间大小限制
    1. 495 if (vma->vm_end - address > current->rlim[RLIMIT_STACK].rlim_cur ||
    1. 496 ((vma->vm_mm->total_vm + grow) << PAGE_SHIFT) > current->rlim[RLIMIT_AS].rlim_cur)
    1. 497 return -ENOMEM;
    1. 498 vma->vm_start = address;//重新设置虚拟地址
    1. 499 vma->vm_pgoff -= grow;//偏移减去grow
    1. 500 vma->vm_mm->total_vm += grow;//地址空间大小
    1. 501 if (vma->vm_flags & VM_LOCKED)
    1. 502 vma->vm_mm->locked_vm += grow;
    1. 503 return 0;
    1. 504 }

 
    1. [do_page_fault()]
    1. 165 /*
    1. 166 * Ok, we have a good vm_area for this memory access, so
    1. 167 * we can handle it..
    1. 168 */
    1. 169 good_area:
    1. 170 info.si_code = SEGV_ACCERR;
    1. 171 write = 0;
    1. 172 switch (error_code & 3) {
    1. 173 default: /* 3: write, present */
    1. 174 #ifdef TEST_VERIFY_AREA
    1. 175 if (regs->cs == KERNEL_CS)
    1. 176 printk("WP fault at %08lx\n", regs->eip);
    1. 177 #endif
    1. 178 /* fall through */
    1. 179 case 2: /* write, not present */
    1. 180 if (!(vma->vm_flags & VM_WRITE))//堆栈段可读可写,调到196行
    1. 181 goto bad_area;
    1. 182 write++;
    1. 183 break;
    1. 184 case 1: /* read, present */

    1. 185 goto bad_area;
    1. 186 case 0: /* read, not present */
    1. 187 if (!(vma->vm_flags & (VM_READ | VM_EXEC)))
    1. 188 goto bad_area;
    1. 189 }
    1. 190
    1. 191 /*
    1. 192 * If for any reason at all we couldn't handle the fault,
    1. 193 * make sure we exit gracefully rather than endlessly redo
    1. 194 * the fault.
    1. 195 */
    1. 196 switch (handle_mm_fault(mm, vma, address, write)) {
    1. 197 case 1:
    1. 198 tsk->min_flt++;
    1. 199 break;
    1. 200 case 2:
    1. 201 tsk->maj_flt++;
    1. 202 break;
    1. 203 case 0:
    1. 204 goto do_sigbus;
    1. 205 default:
    1. 206 goto out_of_memory;
    1. 207 }

    1. [do_page_fault()>handle_mm_fault()]
    1. 1189 /*
    1. 1190 * By the time we get here, we already hold the mm semaphore
    1. 1191 */
    1. 1192 int handle_mm_fault(struct mm_struct *mm, struct vm_area_struct * vma,
    1. 1193 unsigned long address, int write_access)
    1. 1194 {
    1. 1195 int ret = -1;
    1. 1196 pgd_t *pgd;
    1. 1197 pmd_t *pmd;
    1. 1198
    1. 1199 pgd = pgd_offset(mm, address);//获取该地址所在页面目录项的指针(页表的地址)
    1. 1200 pmd = pmd_alloc(pgd, address);//分配pmd目录
    1. 1201
    1. 1202 if (pmd) {
    1. 1203 pte_t * pte = pte_alloc(pmd, address);//分配pte表现
    1. 1204 if (pte)
    1. //分配物理地址
    1. 1205 ret = handle_pte_fault(mm, vma, address, write_access, pte);
    1. 1206 }
    1. 1207 return ret;
    1. 1208 }

    1. [do_page_fault()>handle_mm_fault()>pte_alloc()]
    1. 120 extern inline pte_t * pte_alloc(pmd_t * pmd, unsigned long address)
    1. 121 {
    1. 122 address = (address >> PAGE_SHIFT) & (PTRS_PER_PTE - 1);//给定地址转换为页表的下标,用于定位页表项
    1. 124 if (pmd_none(*pmd))//如果pmd所指向的页表为空,那就转到getnew分配
    1. 125 goto getnew;
    1. 126 if (pmd_bad(*pmd))
    1. 127 goto fix;
    1. 128 return (pte_t *)pmd_page(*pmd) + address;
    1. 129 getnew:
    1. 130 {
    1. 131 unsigned long page = (unsigned long) get_pte_fast();//从缓冲池获取(释放页表,并非一定会释放物理地址)
    1. 132
    1. 133 if (!page)
    1. 134 return get_pte_slow(pmd, address);
    1. 135 set_pmd(pmd, __pmd(_PAGE_TABLE + __pa(page)));//写入中间pmd
    1. 136 return (pte_t *)page + address;
    1. 137 }
    1. 138 fix:
    1. 139 __handle_bad_pmd(pmd);
    1. 140 return NULL;
    1. 141 }

    1. [do_page_fault()>handle_mm_fault()>handle_pte_fault()]
    1. 1135 /*
    1. 1136 * These routines also need to handle stuff like marking pages dirty
    1. 1137 * and/or accessed for architectures that don't do it in hardware (most
    1. 1138 * RISC architectures). The early dirtying is also good on the i386.
    1. 1139 *
    1. 1140 * There is also a hook called "update_mmu_cache()" that architectures
    1. 1141 * with external mmu caches can use to update those (ie the Sparc or
    1. 1142 * PowerPC hashed page tables that act as extended TLBs).
    1. 1143 *
    1. 1144 * Note the "page_table_lock". It is to protect against kswapd removing
    1. 1147 * we can drop the lock early.
    1. 1148 *
    1. 1149 * The adding of pages is protected by the MM semaphore (which we hold),
    1. 1150 * so we don't need to worry about a page being suddenly been added into
    1. 1151 * our VM.
    1. 1152 */
    1. 1153 static inline int handle_pte_fault(struct mm_struct *mm,
    1. 1154 struct vm_area_struct * vma, unsigned long address,
    1. 1155 int write_access, pte_t * pte)
    1. 1156 {
    1. 1157 pte_t entry;
    1. 1158
    1. 1159 /*
    1. 1160 * We need the page table lock to synchronize with kswapd
    1. 1161 * and the SMP-safe atomic PTE updates.
    1. 1162 */
    1. 1163 spin_lock(&mm->page_table_lock);
    1. 1164 entry = *pte;//pte对应的物理地址当然没有,所以为null
    1. 1165 if (!pte_present(entry)) {//检查其对应的物理地址否为空
    1. 1166 /*
    1. 1167 * If it truly wasn't present, we know that kswapd
    1. 1168 * and the PTE updates will not touch it later. So
    1. 1169 * drop the lock.
    1. 1170 */
    1. 1171 spin_unlock(&mm->page_table_lock);
    1. 1172 if (pte_none(entry))//页表项内容为0,表明进程未访问过该页
    1. 1173 return do_no_page(mm, vma, address, write_access, pte);//调用do_no_page分配
    1. //否则换出
    1. 1174 return do_swap_page(mm, vma, address, pte, pte_to_swp_entry(entry), write_access);
    1. 1175 }
    1. 1176
    1. 1177 if (write_access) {
    1. 1178 if (!pte_write(entry))
    1. 1179 return do_wp_page(mm, vma, address, pte, entry);
    1. 1180
    1. 1181 entry = pte_mkdirty(entry);
    1. 1182 }
    1. 1183 entry = pte_mkyoung(entry);
    1. 1184 establish_pte(vma, address, pte, entry);
    1. 1185 spin_unlock(&mm->page_table_lock);
    1. 1186 return 1;
    1. 1187 }
    1. 1145 * pages from under us. Note that kswapd only ever _removes_ pages, never
    1. 1146 * adds them. As such, once we have noticed that the page is not present

    1. [do_page_fault()>handle_mm_fault()>handle_pte_fault()>do_no_page()]
    1. 1080 /*
    1. 1081 * do_no_page() tries to create a new page mapping. It aggressively
    1. 1082 * tries to share with existing pages, but makes a separate copy if
    1. 1083 * the "write_access" parameter is true in order to avoid the next
    1. 1084 * page fault.
    1. 1085 *
    1. 1086 * As this is called only for pages that do not currently exist, we
    1. 1087 * do not need to flush old virtual caches or the TLB.
    1. 1088 *
    1. 1089 * This is called with the MM semaphore held.
    1. 1090 */
    1. 1091 static int do_no_page(struct mm_struct * mm, struct vm_area_struct * vma,
    1. 1092 unsigned long address, int write_access, pte_t *page_table)
    1. 1093 {
    1. 1094 struct page * new_page;
    1. 1095 pte_t entry;
    1. 1096
    1. 1097 if (!vma->vm_ops || !vma->vm_ops->nopage)
    1. 1098 return do_anonymous_page(mm, vma, page_table, write_access, address);//只是其封装而已
    1. ......
    1. ==================== mm/memory.c 1133 1133 ====================
    1. 1133 }

    1. [do_page_fault()>handle_mm_fault()>handle_pte_fault()>do_no_page()>do_anonymous_page()]
    1. 1058 /*
    1. 1059 * This only needs the MM semaphore
    1. 1060 */
    1. 1061 static int do_anonymous_page(struct mm_struct * mm, struct vm_area_struct * vma, pte_t *page_table,
    1. int write_access, unsigned long addr)
    1. 1062 {
    1. 1063 struct page *page = NULL;
    1. //如果引起页面异常是一次读操作,那么由mk_pte构建的映射表项要通过pte_wrprotect修正,只读属性
    1. //同时对于只读的页面,一律映射ZERO_PAGE同一个物理内存页面,也即是内容全部为0,只有可写才独立分配内存
    1. 1064 pte_t entry = pte_wrprotect(mk_pte(ZERO_PAGE(addr), vma->vm_page_prot));
    1. 1065 if (write_access) {
    1. 1066 page = alloc_page(GFP_HIGHUSER);//分配独立物理页面
    1. 1067 if (!page)
    1. 1068 return -1;
    1. 1069 clear_user_highpage(page, addr);
    1. //下面相同.只可写属性
    1. 1070 entry = pte_mkwrite(pte_mkdirty(mk_pte(page, vma->vm_page_prot)));
    1. 1071 mm->rss++;
    1. 1072 flush_page_to_ram(page);
    1. 1073 }
    1. //虚拟页面到物理内存页面映射建立
    1. 1074 set_pte(page_table, entry);
    1. 1075 /* No need to invalidate - it was non-present before */
    1. 1076 update_mmu_cache(vma, addr, entry);
    1. 1077 return 1; /* Minor fault */
    1. 1078 }






Linux内核情景分析之异常访问,用户堆栈的扩展的更多相关文章

  1. linux内核情景分析之execve()

    用来描述用户态的cpu寄存器在内核栈中保存情况.可以获取用户空间的信息 struct pt_regs { long ebx; //可执行文件路径的指针(regs.ebx中 long ecx; //命令 ...

  2. Linux内核情景分析之消息队列

    早期的Unix通信只有管道与信号,管道的缺点: 所载送的信息是无格式的字节流,不知道分界线在哪,也没通信规范,另外缺乏控制手段,比如保温优先级,管道机制的大小只有1页,管道很容易写满而读取没有及时,发 ...

  3. linux内核情景分析之信号实现

    信号在进程间通信是异步的,每个进程的task_struct结构有一个sig指针,指向一个signal_struct结构 定义如下 struct signal_struct { atomic_t cou ...

  4. Linux内核情景分析的alloc_pages

    NUMA结构的alloc_pages ==================== mm/numa.c 43 43 ==================== 43 #ifdef CONFIG_DISCON ...

  5. linux内核情景分析之exit与Wait

    //第一层系统调用 asmlinkage long sys_exit(int error_code) { do_exit((error_code&0xff)<<8); } 其主体是 ...

  6. linux内核情景分析之强制性调度

    从系统调用返回到用户空间是否调度,从ret_with_reschedule可看出,是否真正调度,取决于当前进程的pcb中的need_resched是否设置为1,那如何设置为1取决于以下几种情况: 时间 ...

  7. linux内核情景分析之内核中的互斥操作

    信号量机制: struct sempahore是其结构,定义如下 struct semaphore { atomic_t count;//资源数目 int sleepers;//等待进程数目 wait ...

  8. linux内核情景分析之匿名管道

    管道的机制由pipe()创建,由pipe()所建立的管道两端都在同一进程.所以必须在fork的配合下,才可以在具有亲缘关系的进程通信 /* * sys_pipe() is the normal C c ...

  9. linux内核情景分析之命名管道

    管道是一种"无名","无形文件,只可以近亲进程使用,不可以再任意两个进程通信使用,所以只能实现"有名","有形"的文件来实现就可以 ...

随机推荐

  1. HyperLedger Fabric 1.4 区块链技术形成(1.2)

    在比特币诞生之时,没有区块链技术概念,当人们看到比特币在无中心干预的前提下,还能安全.可靠的运行,比特币网络打开了人们的想象空间:技术专家们开始研究比特币的底层技术,并抽象提取出来,形成区块链技术,或 ...

  2. Nginx 高级配置

    nginx官方网站:http://nginx.org/ 1.  Nginx连接后端的方式:反向代理(proxy_pass).直连fastcgi(fastcgi_pass) 例子: fastcgi_pa ...

  3. Pycharm设置Python的路径

    1. 打开文件->默认设置 2. 找到Python的路径即可,如果没有的话,这里也可以安装一个,只是时间比较久. 3. 选择本地 4. 选择文件

  4. MyEclipse - 问题集 - Workspace in use or cannot be created, choose a different one(转)

    转:http://wsfly.iteye.com/blog/1044986 eclipse 使用一段时间后,有时会因为一些故障自己就莫名奇妙的关闭了,再打开时有时没有问题,有时有会提示错误 Works ...

  5. IOS客户端的个人中心可以查看自己的博客了。

    IOS客户端的个人中心可以查看自己的博客了. 写这篇是为了在客户端显示之用. 下一步实现在客户端发博客.

  6. 用Chrome浏览器,学会这27个超好用功能

    一些非常有用的隐藏捷径 1. 想要在后台打开一个新的标签页而不离开现有的页面,这样就不会打断目前的工作了?按住 Ctrl 键或 Cmd 并点击它.如果你要在一个全新的窗口中打开一个链接,那就按 Shi ...

  7. python之列表/元组/字典/字符串

    一.列表 格式:list = ['xxx','xxx','xxx'] 性质:可以修改列表内容 copy用法: import copy names = ['] names01 = names #直接引用 ...

  8. Python网络编程(weekly summary1)

    网络的目的是什么?     用于信息传输.接受  能把各个点.面.体的信息链接到一起 实现资源的共享 OSI模型:     应用层:提供程序服务     表示层:数据加密.优化.压缩     会话层: ...

  9. 孤荷凌寒自学python第六十五天学习mongoDB的基本操作并进行简单封装4

    孤荷凌寒自学python第六十五天学习mongoDB的基本操作并进行简单封装4 (完整学习过程屏幕记录视频地址在文末) 今天是学习mongoDB数据库的第十一天. 今天继续学习mongoDB的简单操作 ...

  10. 【视觉SLAM14讲】ch3课后题答案

    1.验证旋转矩阵是正交矩阵 感觉下面这篇博客写的不错 http://www.cnblogs.com/caster99/p/4703033.html 总结一下:旋转矩阵是一个完美的矩阵——正交矩阵.①行 ...