0%

mmu详解

0x00 前言

前几天暑假实习面试,虽然事前准备了mmu但是还是面崩了,归根到底就是没有理解其原理,一些设置的必要之处,只是在理论上学习了,一些实现并不清楚,记录一下没有回答出来的问题。

0x01 全局描述符表

在整个系统中,全局描述表GDT只有一张,然后,如果有多个cpu的话,就每个cpu对应一个GDT,然后有个寄存器GDTR来保存GDT的基地址

0x02 局部描述符

每个cpu可以有多个局部描述符LDT,每个LDT都对应一个进程,一般LDT并不是全局可见的,有个一个专门的LDTR寄存器来储存当前进程的LDT的值

0x03 虚拟地址vs线性地址

之前一直以为线性地址和虚拟地址是一个概念,虚拟地址的格式为[段寄存器:偏移量],通过段寄存器,在GDT或者LDT找到某一表项,LDT或者GDT的表项存着线性地址的基地址,偏移量+线性地址的基地址就是线性地址了。(由于linux的线性基地址均为0,所以,很容易混淆。。。。。)

0x04 CR3寄存器

我们都知道linux是三级分页机制,每个进程都有自己的页面目录指针PGD定义于mm_struct,PGD指向页面目录的物理地址,寄存器CR3用于存放当前进程正在使用的页目录基地址,所以,当访问内存的时候,内核就会把CR3寄存器的值改成当前进程的PGD指向的物理地址

0x05 当我访问虚拟地址的时候,怎么定位到物理地址

首先会根据虚拟地址算出线性地址(还是自己),然后,把cr3的值赋值成当前PGD的值,内核(转换成实时模式),根据三级页表定位到真正的物理地址页,那么问题来了,如果访问的虚拟地址不存在物理映射怎么办呢,没有物理映射有以下几种情况

  • 访问的虚拟地址没有没有虚拟映射
  • 访问的虚拟地址有虚拟映射但是没有物理映射(就是mmap了之后没有访问过)

其实在虚拟是否被mmap取决的task_struct中的mm_struct中的vm_area_struct

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/*
* This struct defines a memory VMM memory area. There is one of these
* per VM-area/task. A VM area is any part of the process virtual memory
* space that has a special rule for the page-fault handlers (ie a shared
* library, the executable area etc).
*/
struct vm_area_struct {
/* The first cache line has the info for VMA tree walking. */

unsigned long vm_start; /* Our start address within vm_mm. */
unsigned long vm_end; /* The first byte after our end address
within vm_mm. */

/* linked list of VM areas per task, sorted by address */
struct vm_area_struct *vm_next, *vm_prev;

struct rb_node vm_rb;

/*
* Largest free memory gap in bytes to the left of this VMA.
* Either between this VMA and vma->vm_prev, or between one of the
* VMAs below us in the VMA rbtree and its ->vm_prev. This helps
* get_unmapped_area find a free area of the right size.
*/
unsigned long rb_subtree_gap;

/* Second cache line starts here. */

struct mm_struct *vm_mm; /* The address space we belong to. */
pgprot_t vm_page_prot; /* Access permissions of this VMA. */
unsigned long vm_flags; /* Flags, see mm.h. */

/*
* For areas with an address space and backing store,
* linkage into the address_space->i_mmap interval tree.
*/
struct {
struct rb_node rb;
unsigned long rb_subtree_last;
} shared;

/*
* A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma
* list, after a COW of one of the file pages. A MAP_SHARED vma
* can only be in the i_mmap tree. An anonymous MAP_PRIVATE, stack
* or brk vma (with NULL file) can only be in an anon_vma list.
*/
struct list_head anon_vma_chain; /* Serialized by mmap_sem &
* page_table_lock */
struct anon_vma *anon_vma; /* Serialized by page_table_lock */

/* Function pointers to deal with this struct. */
const struct vm_operations_struct *vm_ops;

/* Information about our backing store: */
unsigned long vm_pgoff; /* Offset (within vm_file) in PAGE_SIZE
units */
struct file * vm_file; /* File we map to (can be NULL). */
void * vm_private_data; /* was vm_pte (shared mem) */

#ifdef CONFIG_SWAP
atomic_long_t swap_readahead_info;
#endif
#ifndef CONFIG_MMU
struct vm_region *vm_region; /* NOMMU mapping region */
#endif
#ifdef CONFIG_NUMA
struct mempolicy *vm_policy; /* NUMA policy for the VMA */
#endif
struct vm_userfaultfd_ctx vm_userfaultfd_ctx;
} __randomize_layout;

这个结构体记录了虚拟内存的映射情况,每一个vma_area_struct记录了一段虚拟内存的映射情况,其中:

  • vm_start为此段虚拟地址开始
  • vm_end为此段虚拟地址的结束
  • vm_next指向下一段vma_area_struct结构体
  • vm_prev指向上一段vma_area_struct结构体

每个task_struct有许多vma_area_struct组成一个链表或者数为当前进程的虚拟地址空间映射分布,当vma_area_struct的结构体数量超过32,就会以AVL树的形式组织,主要是考虑到搜索速度

那么这样就清楚了,如果,你mmap一段地址,那么,在task_struct就会有一个vma_area_struct结构体对应,所以,当我访问一个虚拟内存地址时,发现他并没有对应的目录页或者页表项时,就会发生缺页异常,然后,内核就会检查vma_area_struct列表或者树,有没有包涵这个虚拟内存的段,如果有的话,就是mmap了,没有物理映射这种情况,如果没有的话,就是非法访问,就会真的pagefault

其实对于没有对应的虚拟内存段,还有一种比较特殊的情况,就是,这个虚拟地址在stack段下面的时候,我们都知道stack段是向下自动扩展的,如果,你访问的虚拟地址在stack一点点,正好没有被映射的时候,内核就会映射整个页,实现自动向下扩展

我可以仔细考虑一下这种情况,如果,我们可以合法的把stack段(stack段的vma_area_struct独有的标志)移动到一个低地址(比如说0x10000),那么,当stack空间被用完的时候,他就需要向下扩展,所以,就有可能突破0地址映射的保护。。。。

0x06 保护模式比实时模式安全在那些方面

  • 保护模式拥有特权指令,而保护模式没有特权指令的概念,在实时模式下,进程改变的自己的段寄存器,不算什么特权指令,也就是说一个进程可以通过改自己的段寄存器实现任意地址访问