linux 内核页表替换

linux 内核页表替换

一.linux本身关于页表查找机制的代码实现

1. 逻辑地址到页表的查找过程
static int __follow_pte_pmd(struct mm_struct *mm, unsigned long address,
                            unsigned long *start, unsigned long *end,
                            pte_t **ptepp, pmd_t **pmdpp, spinlock_t **ptlp)
{
        pgd_t *pgd;
        p4d_t *p4d;
        pud_t *pud;
        pmd_t *pmd;
        pte_t *ptep;

	/* 进程的Page Global Directory基指针存放在mm->pgd */
        pgd = pgd_offset(mm, address);	  /* 返回address指向该进程的Page Global Directory指针 */
        if (pgd_none(*pgd) || unlikely(pgd_bad(*pgd)))
                goto out;

        p4d = p4d_offset(pgd, address);	  /* 在4级页面机制中,不做任何操作,直接返回pgd */
        if (p4d_none(*p4d) || unlikely(p4d_bad(*p4d)))
                goto out;

        pud = pud_offset(p4d, address);	  /* 返回address指向该进程的Page Upper Directory指针 */
        if (pud_none(*pud) || unlikely(pud_bad(*pud)))
                goto out;

        pmd = pmd_offset(pud, address);	  /* 返回address指向该进程的Page Middle Derectory指针 */
        /* ... */
        if (pmd_none(*pmd) || unlikely(pmd_bad(*pmd)))
                goto out;

        if (start && end) {
                *start = address & PAGE_MASK;
                *end = *start + PAGE_SIZE;
                mmu_notifier_invalidate_range_start(mm, *start, *end);
        }
        ptep = pte_offset_map_lock(mm, pmd, address, ptlp);   /* 返回address指向该进程的Page Table指针 */
	/* 判断Page Table是否保留在物理内存之中 */
        if (!pte_present(*ptep))
                goto unlock;
        *ptepp = ptep;
        *ptepp = ptep;
        return 0;
unlock:
        pte_unmap_unlock(ptep, *ptlp);
        if (start && end)
                mmu_notifier_invalidate_range_end(mm, *start, *end);
out:
        return -EINVAL;
}

2.Page Table转为物理地址

int follow_phys(struct vm_area_struct *vma,
                unsigned long address, unsigned int flags,
                unsigned long *prot, resource_size_t *phys)
{
        /* ... */
	if (follow_pte(vma->vm_mm, address, &ptep, &ptl))
                goto out;
        pte = *ptep;
	/* ... */
        *phys = (resource_size_t)pte_pfn(pte) << PAGE_SHIFT;	/* Page Table转化为物理地址 */

        ret = 0;
unlock:
        pte_unmap_unlock(ptep, ptl);
out:
        return ret;
}

关于分页机制的4级还是几级查找不是我关注的,我的目标是在
在这里插入图片描述
Page_Table到Page物理地址查找的一个替换。

phys = (resource_size_t)pte_pfn(pte) << PAGE_SHIFT; / Page Table转化为物理地址 */
这句即是Page_Table到Page的查找过程。那么其中必然就会涉及到表的查询,我们当然也可以在类似的实现中自己构造新的页表跳转。

3.页表到物理地址的转换

linux-4.14.146/arch/x86/include/asm/pgtable.h:static inline unsigned long pte_pfn(pte_t pte)

static inline unsigned long pte_pfn(pte_t pte)
{
	phys_addr_t pfn = pte_val(pte);     //宏用来把pgd_t类型转换成无符号长整型
	pfn ^= protnone_mask(pfn);
	return (pfn & PTE_PFN_MASK) >> PAGE_SHIFT;
}

/* Get a mask to xor with the page table entry to get the correct pfn. */
static inline u64 protnone_mask(u64 val)
{
	return __pte_needs_invert(val) ?  ~0ull : 0;
}

static inline bool __pte_needs_invert(u64 val)
{
	return val && !(val & _PAGE_PRESENT);
}

#define _PAGE_PRESENT	(_AT(pteval_t, 1) << _PAGE_BIT_PRESENT)

#define _PAGE_BIT_PRESENT	0	/* is present */

#ifdef __ASSEMBLY__
#define _AC(X,Y)	X
#define _AT(T,X)	X
#else
#define __AC(X,Y)	(X##Y)
#define _AC(X,Y)	__AC(X,Y)
#define _AT(T,X)	((T)(X))
#endif

#define PAGE_SHIFT		12


#define PTE_PFN_MASK		((pteval_t)PHYSICAL_PAGE_MASK)
#define PHYSICAL_PAGE_MASK	(((signed long)PAGE_MASK) & __PHYSICAL_MASK)

#ifdef CONFIG_DYNAMIC_PHYSICAL_MASK
extern phys_addr_t physical_mask;
#define __PHYSICAL_MASK		physical_mask
#else
#define __PHYSICAL_MASK		((phys_addr_t)((1ULL << __PHYSICAL_MASK_SHIFT) - 1))
#endif

phys_addr_t physical_mask = (1ULL << __PHYSICAL_MASK_SHIFT) - 1;

#ifdef CONFIG_DYNAMIC_PHYSICAL_MASK
extern phys_addr_t physical_mask;
#define __PHYSICAL_MASK		physical_mask
#else
#define __PHYSICAL_MASK		((phys_addr_t)((1ULL << __PHYSICAL_MASK_SHIFT) - 1))
#endif

#define __PHYSICAL_MASK_SHIFT	52

typedef u64 phys_addr_t;

二.重新构建查表过程


int follow_phys(struct vm_area_struct *vma,
		unsigned long address, unsigned int flags,
		unsigned long *prot, resource_size_t *phys)
{
	int ret = -EINVAL;
	pte_t *ptep, pte;
	spinlock_t *ptl;

	if (!(vma->vm_flags & (VM_IO | VM_PFNMAP)))
		goto out;

	//if (follow_pte(vma->vm_mm, address, &ptep, &ptl))
	//	goto out;

	if (follow_pte_pmd(vma->vm_mm,address, NULL, NULL,
						    &ptep, NULL, &ptl))//page find
		goto out;
	pte = *ptep;

	if ((flags & FOLL_WRITE) && !pte_write(pte))
		goto unlock;

	*prot = pgprot_val(pte_pgprot(pte));
	*phys = (resource_size_t)pte_pfn(pte) << PAGE_SHIFT;

	ret = 0;
unlock:
	pte_unmap_unlock(ptep, ptl);
out:
	return ret;
}

这一段大部分是内核中的代码,但有一点follow_pte没有导出,但是导出了follow_pte_pmd。这两个函数在源码里的实现是一样的。所以完全可以替换。

另一个关键是获取的struct vm_area_struct
如下:

struct vm_area_struct* get_vma(unsigned long addr){
	struct task_struct *cur_task =current;//get_current();

	struct vm_area_struct *cur_vma =  find_vma(cur_task->mm_strcut,addr);
	if (!vma || vma->vm_start > start)
	{
		printk("\n");
		return -EINVAL;
	}
	printk("\n");
	return cur_vma;
}

那么理论上我们通过页表查询的方式获取到了物理地址

	resource_size_t phys;
	resource_size_t phys_addr;
	unsigned long long* target = kmalloc(0x200, GFP_KERNEL);
	unsigned long long phytarget = virt_to_phys(target);
	__u64 phytargets = phytarget;


	struct vm_area_struct * cur_vma = find_vma(target);

	int ret = follow_phys(cur_vma,target,FOLL_WRITE, &prot, &phys);
	if(ret){
		printk("find physic error\n");
	}

这里有个FOLL_WRITE位,内核中定义如下:

#define FOLL_WRITE	0x01	/* check pte is writable */
#define FOLL_TOUCH	0x02	/* mark page accessed */
#define FOLL_GET	0x04	/* do get_page on page */
#define FOLL_DUMP	0x08	/* give error on hole if it would be zero */
#define FOLL_FORCE	0x10	/* get_user_pages read/write w/o permission */
#define FOLL_NOWAIT	0x20	/* if a disk transfer is needed, start the IO
				 * and return without waiting upon it */
#define FOLL_POPULATE	0x40	/* fault in page */
#define FOLL_SPLIT	0x80	/* don't return transhuge pages, split them */
#define FOLL_HWPOISON	0x100	/* check page is hwpoisoned */
#define FOLL_NUMA	0x200	/* force NUMA hinting page fault */
#define FOLL_MIGRATION	0x400	/* wait for page to replace migration entry */
#define FOLL_TRIED	0x800	/* a retry, previous pass started an IO */
#define FOLL_MLOCK	0x1000	/* lock present pages */

可见这里应该是可以更改的。

三.内存描述属性

内核中的地址块因为不是连续的,所以存在多个vma数据块。而且这些块也是通过红黑树来组织的。所以理论上vma----mm-----pte_t的查询路径应该是可行的,但是我在4.14.146版本上进行测试的时候。kmalloc的虚拟地址无法找到对应的vma。find_vma总是NULL。

这里有点不太懂。

好在找到了另一种方式。

 unsigned long init_mm_addr = kallsyms_lookup_name("init_mm");
        struct mm_struct *my_mm;
        my_mm = (struct mm_struct *)init_mm_addr;

		printk(KERN_NOTICE "init_mm_addr %p\n",init_mm_addr);


        pgd = pgd_offset(my_mm, address);
        if (pgd_none(*pgd) || pgd_bad(*pgd))
            goto out;

其他的页表查询方式跟之前的一样。

四.修改页表属性

经过多次尝试,终于在内核中以模块的方式实现了查询页表到物理地址的整个过程,这个过程的意义在于,对于页表属性的更改和页表替换必须依赖以上条件。
arm 64 上有两个函数set_pte_bit,clear_pte_bit。从名字看,我们对页表描述符的修改可以依赖以上两个函数。但是好像x86上没有这两个函数,不过不要紧,我们可以自己实现。

static inline pte_t set_pte_bit(pte_t pte, pgprot_t prot)
{
	pte_val(pte) |= pgprot_val(prot);
	return pte;
}

#define pgprot_val(x)	((x).pgprot)

typedef struct pgprot { pgprotval_t pgprot; } pgprot_t;

typedef unsigned long	pgprotval_t;

#define pte_val(x)	native_pte_val(x)
#define __pte(x)	native_make_pte(x)

static inline pteval_t native_pte_val(pte_t pte)
{
	return pte.pte;
}

typedef struct { pteval_t pte; } pte_t;

typedef unsigned long	pteval_t;
#define pte_pgprot(x) __pgprot(pte_flags(x))

static inline pteval_t pte_flags(pte_t pte)
{
	return native_pte_val(pte) & PTE_FLAGS_MASK;
}

#define __pgprot(x)	((pgprot_t) { (x) } )

static inline pteval_t native_pte_val(pte_t pte)
{
	return pte.pte;
}

五、页表替换

这里走了点弯路,因为kmalloc分配的都是巨页,所以开始的时候老是想怎么能分配一个标准页来着。
问了下总管,说是要看下分配器的内容,开始没找到,后来发现了个直接的调用,用来分配一个物理页。
vpage = get_zeroed_page(GFP_ATOMIC);

当时还有第二种做法,就是全局变量默认都是标准页。

不过想着全局变量不太灵活,所以还是直接分配吧。

我这里以ptep为代表的说明。
这个指针代表页表描述符。主要包含了两个信息。

  • 页帧号(经过位移转换可以得到对应物理页的的物理地址)
  • 页属性(用来控制页的操控属性,读写执行等。)

所以我这里写了两个函数
cp_page(unsigned long * target,unsigned long address)
– lookup_address()
ch_physic(unsigned long destaddr,unsigned long srcaddr)
– lookup_address()
–lookup_address()
这里进行了三次虚拟地址查表的过程,理论上可以减少到两个,因为如果考虑到效率问题。不过替换操作发生不是很频繁,应该可以忽略,暂且这样。

#define PTE_PHY_PAGE  0x0000fffffffff000 
#define PTE_PHY_PAGE_CLEAR 0xffff000000000fff 

		pte=*destptep;
		printk("ch pte %016lx \n",pte );
		pte.pte = pte.pte & PTE_PHY_PAGE_CLEAR;
		printk("ch pte clear phypage %016lx \n",pte );

		pten=*srcptep;
		printk("ch pten %016lx \n",pten);
		pten.pte = pten.pte & PTE_PHY_PAGE;
		printk("ch pten phyget %016lx \n",pten);

		pte.pte = pte.pte + pten.pte ;
		printk("ch pte result %016lx \n",pte );

		destptep->pte=pte.pte;

替换过程如上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值