精华内容
下载资源
问答
  • MDL 内存映射实现HOOK

    2020-04-11 03:45:28
    /*IoAllocateMdl的作用是分配一个MDL结构,也就是将系统的一段内存空间映射到另外一个地方,然后修改这部分内存的保护属性,并修改其内容,以达到修改受保护内存的目的。第一参数为MDL内存的起始地址,第二个参数...

    这种hook方式和ssdt HOOK有本质区别,不要搞混。

    typedef NTSTATUS (*REALZWQUERYDIRECTORYFILE)(IN HANDLE hFile,
    IN HANDLE hEvent OPTIONAL,
    IN PIO_APC_ROUTINE IoApcRoutine OPTIONAL,
    IN PVOID IoApcContext OPTIONAL,
    OUT PIO_STATUS_BLOCK pIoStatusBlock,
    OUT PVOID FileInformationBuffer,
    IN ULONG FileInformationBufferLength,
    IN FILE_INFORMATION_CLASS FileInfoClass,
    IN BOOLEAN bReturnOnlyOneEntry,
    IN PUNICODE_STRING PathMask OPTIONAL,
    IN BOOLEAN bRestartQuery);
    
    //定义一个原函数指针
    REALZWQUERYSYSTEMINFORMATION RealZwQuerySystemInformation;
    void DriverUnload(PDRIVER_OBJECT pDriveObj)
    {
    	DbgPrint("driver unloaded ...\n");
    }
    NTSTATUS DriverEntry(PDRIVER_OBJECT driver,PUNICODE_STRING reg)
    {
    	UNREFERENCED_PARAMETER(reg); //不用将编译警告等级设为3,不使用 就Unreferenced即可
    	
    	PMDL mdl;
    	PVOID addrMm;
    	ULONG addrKdEnterDebugger;
    	ULONG addrKdEnteredDebugger;
    	UNICODE_STRING func;
    	ULONG addr = 0;
    
    	RtlInitUnicodeString(&func, L"ZwQueryDirectoryFile");
    	RealZwQueryDirectoryFile = (REALZWQUERYDIRECTORYFILE)MmGetSystemRoutineAddress(&func);
    	DbgPrint("RealZwQueryDirectoryFile的地址:%x\n", (ULONG)RealZwQueryDirectoryFile);
    
    	driver->DriverUnload = DriverUnload;
    	/*IoAllocateMdl的作用是分配一个MDL结构,也就是将系统的一段内存空间映射到另外一个地方,然后修改这部分内存的保护属性,并修改其内容,以达到修改受保护内存的目的。第一参数为MDL内存的起始地址,第二个参数为MDL的长度。
    	由于IoAllocateMdl创建的MDL都是指向非分页的虚拟内存的buffer中的,所以需要MmBuildMdlForNonPagedPool函数来在物理内存上更新这个MDL。*/
    	
    	//1、IoAllocateMdl创建一个mdl
    	mdl=IoAllocateMdl((PVOID)RealZwQueryDirectoryFile, 5, FALSE, FALSE, NULL);
    	//2、调用MmBuildMdlForNonPagedPool更新创建在非分页内存上的MDL
    	//通过MmBuildMdlForNonPagedPool后,也可以使用 MmGetSystemAddressForMdl 宏获得系统空间地址。
    	MmBuildMdlForNonPagedPool(mdl);
    	DbgPrint("mdl->StartVa:%x,mdl->ByteCount:%d,mdl->MappedSystemVa:%x,mdl->ByteOffset:%d\n", mdl->StartVa, mdl->ByteCount,mdl->MappedSystemVa,mdl->ByteOffset);
    	//3、调用MmProbeAndLockPages将内存页锁定,防止内容被修改,要在try,exception结构里面执行
    	__try{
    	MmProbeAndLockPages(mdl, KernelMode, IoWriteAccess);//锁定内存
    	}//IoWriteAccess指定为写权限
    	__except (EXCEPTION_EXECUTE_HANDLER)
    	{
    	DbgPrint("MmProbeAndLockPages exception\n");
    	}
    	//4、调用MmMapLockedPagesSpecifyCache函数生成用户模式下对应的虚拟地址,然后就能修改这个地址的内容来达到修改内核内容的目的
    	addrMm = MmMapLockedPagesSpecifyCache(mdl, KernelMode, MmCached, 0, FALSE, NormalPagePriority);//创建一个内核模式下的虚拟内存对应ZwQueryDirectoryFile函数
    	DbgPrint("addrMm=%x\n", addrMm);
    	addrMm = MmMapLockedPagesSpecifyCache(mdl, UserMode, MmCached, 0, FALSE, NormalPagePriority);//创建一个用户模式下的虚拟内存对应ZwQueryDirectoryFile函数
    	DbgPrint("addrMm=%x\n", addrMm);
    	*((ULONG *)addrMm) = 0x90;//修改用户模式下的虚拟地址的值到修改内核模式下的//ZwQueryDirectoryFile函数前5个字节的目的
    	*((ULONG *)((ULONG)addrMm + 1)) = 0x90909090;
    	
    	DbgPrint("RealZwQueryDirectoryFile=%x\n", *((ULONG *)RealZwQueryDirectoryFile));//打印修改以
    	//后的内核ZwQueryDirectoryFile函数的前5个字节的内容
    	// 5、调用MmUnmapLockedPages解除映射
    	MmUnmapLockedPages(addrMm, mdl);
    
    	// 6、MmFreePagesFromMdl释放MDL锁定的物理页
    	MmFreePagesFromMdl(mdl);
    
    	//7、调用IoFreeMdl 释放MDL
    	IoFreeMdl(mdl);
    }
    
    展开全文
  •   本文为内存部分最后一篇,介绍内存映射内存映射不仅是物理内存和虚拟内存间的映射,也包括将文件中的内容映射到虚拟内存空间。这个时候,访问内存空间就能够访问到文件里面的数据。而仅有物理内存和虚拟内存的...

    一. 前言

      本文为内存部分最后一篇,介绍内存映射。内存映射不仅是物理内存和虚拟内存间的映射,也包括将文件中的内容映射到虚拟内存空间。这个时候,访问内存空间就能够访问到文件里面的数据。而仅有物理内存和虚拟内存的映射,是一种特殊情况。本文首先分析用户态在堆中申请小块内存的brk和申请大块内存的mmap,之后会分析内核态的内存映射机制vmallockmap_atomicswapper_pg_dir以及内核态缺页异常。

    二. 用户态内存映射

      用户态调用malloc会分配堆内存空间,而实际上则是完成了一次用户态的内存映射,根据分配空间的大小,内存映射主要有brkmmap,下面分别详细分析。

    2.1 小块内存申请

      brk系统调用为sys_brk()函数,其参数brk是新的堆顶位置,而mm->brk是原堆顶位置。该函数主要逻辑如下

    • 将原来的堆顶和现在的堆顶按照页对齐地址比较大小,判断是否在同一页中
      • 如果同一页则不需要分配新页,直接跳转至set_brk,设置mm->brk为新的brk即可
      • 如果不在同一页
        • 如果新堆顶小于旧堆顶,则说明不是新分配内存而是释放内存,由此调用__do_munmap()释放
        • 如果是新分配内存,则调用find_vma(),查找vm_area_struct红黑树中原堆顶所在vm_area_struct的下一个结构体,如果在二者之间有足够的空间分配一个页则调用do_brk_flags()分配堆空间。如果不可以则分配失败。
    SYSCALL_DEFINE1(brk, unsigned long, brk)
    {
    	unsigned long retval;
    	unsigned long newbrk, oldbrk, origbrk;
    	struct mm_struct *mm = current->mm;
    	struct vm_area_struct *next;
    ......
    	newbrk = PAGE_ALIGN(brk);
    	oldbrk = PAGE_ALIGN(mm->brk);
    	if (oldbrk == newbrk) {
    		mm->brk = brk;
    		goto success;
    	}
    	/*
    	 * Always allow shrinking brk.
    	 * __do_munmap() may downgrade mmap_sem to read.
    	 */
    	if (brk <= mm->brk) {
    		int ret;
    		/*
    		 * mm->brk must to be protected by write mmap_sem so update it
    		 * before downgrading mmap_sem. When __do_munmap() fails,
    		 * mm->brk will be restored from origbrk.
    		 */
    		mm->brk = brk;
    		ret = __do_munmap(mm, newbrk, oldbrk-newbrk, &uf, true);
    		if (ret < 0) {
    			mm->brk = origbrk;
    			goto out;
    		} else if (ret == 1) {
    			downgraded = true;
    		}
    		goto success;
    	}
    	/* Check against existing mmap mappings. */
    	next = find_vma(mm, oldbrk);
    	if (next && newbrk + PAGE_SIZE > vm_start_gap(next))
    		goto out;
    	/* Ok, looks good - let it rip. */
    	if (do_brk_flags(oldbrk, newbrk-oldbrk, 0, &uf) < 0)
    		goto out;
    	mm->brk = brk;
    success:
    	populate = newbrk > oldbrk && (mm->def_flags & VM_LOCKED) != 0;
    	if (downgraded)
    		up_read(&mm->mmap_sem);
    	else
    		up_write(&mm->mmap_sem);
    	userfaultfd_unmap_complete(mm, &uf);
    	if (populate)
    		mm_populate(oldbrk, newbrk - oldbrk);
    	return brk;
    out:
    	retval = origbrk;
    	up_write(&mm->mmap_sem);
    	return retval;
    }
    

      在 do_brk_flags() 中,调用 find_vma_links() 找到将来的 vm_area_struct 节点在红黑树的位置,找到它的父节点、前序节点。接下来调用 vma_merge(),看这个新节点是否能够和现有树中的节点合并。如果地址是连着的,能够合并,则不用创建新的 vm_area_struct 了,直接跳到 out,更新统计值即可;如果不能合并,则创建新的 vm_area_struct,既加到 anon_vma_chain 链表中,也加到红黑树中。

    /*
     *  this is really a simplified "do_mmap".  it only handles
     *  anonymous maps.  eventually we may be able to do some
     *  brk-specific accounting here.
     */
    static int do_brk_flags(unsigned long addr, unsigned long len, unsigned long flags, struct list_head *uf)
    {
    	struct mm_struct *mm = current->mm;
    	struct vm_area_struct *vma, *prev;
    	struct rb_node **rb_link, *rb_parent;
    ......
    	/*
    	 * Clear old maps.  this also does some error checking for us
    	 */
    	while (find_vma_links(mm, addr, addr + len, &prev, &rb_link,
    			      &rb_parent)) {
    		if (do_munmap(mm, addr, len, uf))
    			return -ENOMEM;
    	}
    ......
    	/* Can we just expand an old private anonymous mapping? */
    	vma = vma_merge(mm, prev, addr, addr + len, flags,
    			NULL, NULL, pgoff, NULL, NULL_VM_UFFD_CTX);
    	if (vma)
    		goto out;
    	/*
    	 * create a vma struct for an anonymous mapping
    	 */
    	vma = vm_area_alloc(mm);
    	if (!vma) {
    		vm_unacct_memory(len >> PAGE_SHIFT);
    		return -ENOMEM;
    	}
    	vma_set_anonymous(vma);
    	vma->vm_start = addr;
    	vma->vm_end = addr + len;
    	vma->vm_pgoff = pgoff;
    	vma->vm_flags = flags;
    	vma->vm_page_prot = vm_get_page_prot(flags);
    	vma_link(mm, vma, prev, rb_link, rb_parent);
    out:
    	perf_event_mmap(vma);
    	mm->total_vm += len >> PAGE_SHIFT;
    	mm->data_vm += len >> PAGE_SHIFT;
    	if (flags & VM_LOCKED)
    		mm->locked_vm += (len >> PAGE_SHIFT);
    	vma->vm_flags |= VM_SOFTDIRTY;
    	return 0;
    }
    

    2.2 大内存块申请

      大块内存的申请通过mmap系统调用实现,mmap既可以实现虚拟内存向物理内存的映射,也可以映射文件到自己的虚拟内存空间。映射文件时,实际是映射虚拟内存到物理内存再到文件。

    SYSCALL_DEFINE6(mmap, unsigned long, addr, unsigned long, len,
    		unsigned long, prot, unsigned long, flags,
    		unsigned long, fd, unsigned long, off)
    {
    	long error;
    	error = -EINVAL;
    	if (off & ~PAGE_MASK)
    		goto out;
    	error = ksys_mmap_pgoff(addr, len, prot, flags, fd, off >> PAGE_SHIFT);
    out:
    	return error;
    }
    

      这里主要调用ksys_mmap_pgoff()函数,这里逻辑如下

    • 判断类型是否为匿名映射,如果不是则为文件映射,调用fget()获取文件描述符
    • 如果是匿名映射,判断是否为大页,如果是则进行对齐处理并调用hugetlb_file_setup()获取文件描述符
    • 调用vm_mmap_pgoff()函数找寻可以映射的区域并建立映射
    unsigned long ksys_mmap_pgoff(unsigned long addr, unsigned long len,
    			      unsigned long prot, unsigned long flags,
    			      unsigned long fd, unsigned long pgoff)
    {
    	struct file *file = NULL;
    	unsigned long retval;
    	if (!(flags & MAP_ANONYMOUS)) {
    		audit_mmap_fd(fd, flags);
    		file = fget(fd);
    		if (!file)
    			return -EBADF;
    		if (is_file_hugepages(file))
    			len = ALIGN(len, huge_page_size(hstate_file(file)));
    		retval = -EINVAL;
    		if (unlikely(flags & MAP_HUGETLB && !is_file_hugepages(file)))
    			goto out_fput;
    	} else if (flags & MAP_HUGETLB) {
    		struct user_struct *user = NULL;
    		struct hstate *hs;
    		hs = hstate_sizelog((flags >> MAP_HUGE_SHIFT) & MAP_HUGE_MASK);
    		if (!hs)
    			return -EINVAL;
    		len = ALIGN(len, huge_page_size(hs));
    		/*
    		 * VM_NORESERVE is used because the reservations will be
    		 * taken when vm_ops->mmap() is called
    		 * A dummy user value is used because we are not locking
    		 * memory so no accounting is necessary
    		 */
    		file = hugetlb_file_setup(HUGETLB_ANON_FILE, len,
    				VM_NORESERVE,
    				&user, HUGETLB_ANONHUGE_INODE,
    				(flags >> MAP_HUGE_SHIFT) & MAP_HUGE_MASK);
    		if (IS_ERR(file))
    			return PTR_ERR(file);
    	}
    	flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE);
    	retval = vm_mmap_pgoff(file, addr, len, prot, flags, pgoff);
    out_fput:
    	if (file)
    		fput(file);
    	return retval;
    }
    

      vm_mmap_pgoff()函数调用do_mmap_pgoff(),实际调用do_mmap()函数。这里get_unmapped_area()函数负责寻找可映射的区域,mmap_region()负责映射该区域。

    /*
     * The caller must hold down_write(&current->mm->mmap_sem).
     */
    unsigned long do_mmap(struct file *file, unsigned long addr,
    			unsigned long len, unsigned long prot,
    			unsigned long flags, vm_flags_t vm_flags,
    			unsigned long pgoff, unsigned long *populate,
    			struct list_head *uf)
    {
    	struct mm_struct *mm = current->mm;
    	int pkey = 0;
    	*populate = 0;
    ......
    	/* Obtain the address to map to. we verify (or select) it and ensure
    	 * that it represents a valid section of the address space.
    	 */
    	addr = get_unmapped_area(file, addr, len, pgoff, flags);
    ......
    	addr = mmap_region(file, addr, len, vm_flags, pgoff, uf);
    	if (!IS_ERR_VALUE(addr) &&
    	    ((vm_flags & VM_LOCKED) ||
    	     (flags & (MAP_POPULATE | MAP_NONBLOCK)) == MAP_POPULATE))
    		*populate = len;
    	return addr;
    }
    
    

      首先来看看寻找映射区的函数get_unmapped_area()

    • 如果是匿名映射,则调用get_umapped_area函数指针,这个函数其实是 arch_get_unmapped_area()。它会调用 find_vma_prev(),在表示虚拟内存区域的 vm_area_struct 红黑树上找到相应的位置。之所以叫 prev,是说这个时候虚拟内存区域还没有建立,找到前一个 vm_area_struct
    • 如果是映射到一个文件,在 Linux 里面每个打开的文件都有一个 struct file 结构,里面有一个 file_operations用来表示和这个文件相关的操作。如果是我们熟知的 ext4 文件系统,调用的也是get_unmapped_area 函数指针。
    unsigned long
    get_unmapped_area(struct file *file, unsigned long addr, unsigned long len,
    		unsigned long pgoff, unsigned long flags)
    {
    	unsigned long (*get_area)(struct file *, unsigned long,
    				  unsigned long, unsigned long, unsigned long);
    	unsigned long error = arch_mmap_check(addr, len, flags);
    	if (error)
    		return error;
    	/* Careful about overflows.. */
    	if (len > TASK_SIZE)
    		return -ENOMEM;
    	get_area = current->mm->get_unmapped_area;
    	if (file) {
    		if (file->f_op->get_unmapped_area)
    			get_area = file->f_op->get_unmapped_area;
    	} else if (flags & MAP_SHARED) {
    		/*
    		 * mmap_region() will call shmem_zero_setup() to create a file,
    		 * so use shmem's get_unmapped_area in case it can be huge.
    		 * do_mmap_pgoff() will clear pgoff, so match alignment.
    		 */
    		pgoff = 0;
    		get_area = shmem_get_unmapped_area;
    	}
    	addr = get_area(file, addr, len, pgoff, flags);
    	if (IS_ERR_VALUE(addr))
    		return addr;
    	if (addr > TASK_SIZE - len)
    		return -ENOMEM;
    	if (offset_in_page(addr))
    		return -EINVAL;
    	error = security_mmap_addr(addr);
    	return error ? error : addr;
    }
    

      mmap_region()首先会再次检测地址空间是否满足要求,然后清除旧的映射,校验内存的可用性,在一切均满足的情况下调用vma_link()将新创建的vm_area_struct结构挂在mm_struct内的红黑树上。

    unsigned long mmap_region(struct file *file, unsigned long addr,
    		unsigned long len, vm_flags_t vm_flags, unsigned long pgoff,
    		struct list_head *uf)
    {
    	struct mm_struct *mm = current->mm;
    	struct vm_area_struct *vma, *prev;
    	int error;
    	struct rb_node **rb_link, *rb_parent;
    	unsigned long charged = 0;
    ......
    	vma_link(mm, vma, prev, rb_link, rb_parent);
    	/* Once vma denies write, undo our temporary denial count */
    	if (file) {
    		if (vm_flags & VM_SHARED)
    			mapping_unmap_writable(file->f_mapping);
    		if (vm_flags & VM_DENYWRITE)
    			allow_write_access(file);
    	}
    	file = vma->vm_file;
    ......
    }
    

      vma_link()本身是__vma_link()__vma_link_file()的包裹函数

    static void vma_link(struct mm_struct *mm, struct vm_area_struct *vma,
    			struct vm_area_struct *prev, struct rb_node **rb_link,
    			struct rb_node *rb_parent)
    {
    	struct address_space *mapping = NULL;
    	if (vma->vm_file) {
    		mapping = vma->vm_file->f_mapping;
    		i_mmap_lock_write(mapping);
    	}
    	__vma_link(mm, vma, prev, rb_link, rb_parent);
    	__vma_link_file(vma);
    	if (mapping)
    		i_mmap_unlock_write(mapping);
    	mm->map_count++;
    	validate_mm(mm);
    }
    

      其中__vma_link()主要是链表和红黑表的插入,这属于基本数据结构操作,不展开讲解。

    static void
    __vma_link(struct mm_struct *mm, struct vm_area_struct *vma,
    	struct vm_area_struct *prev, struct rb_node **rb_link,
    	struct rb_node *rb_parent)
    {
    	__vma_link_list(mm, vma, prev, rb_parent);
    	__vma_link_rb(mm, vma, rb_link, rb_parent);
    }
    

      而__vma_link_file()会对文件映射进行处理,在file结构体中成员f_mapping指向address_space结构体,该结构体中存储红黑树i_mmap挂载vm_area_struct

    static void __vma_link_file(struct vm_area_struct *vma)
    {
    	struct file *file;
    	file = vma->vm_file;
    	if (file) {
    		struct address_space *mapping = file->f_mapping;
    		if (vma->vm_flags & VM_DENYWRITE)
    			atomic_dec(&file_inode(file)->i_writecount);
    		if (vma->vm_flags & VM_SHARED)
    			atomic_inc(&mapping->i_mmap_writable);
    		flush_dcache_mmap_lock(mapping);
    		vma_interval_tree_insert(vma, &mapping->i_mmap);
    		flush_dcache_mmap_unlock(mapping);
    	}
    }
    

      至此,我们完成了用户态内存的映射,但是此处仅在虚拟内存中建立了新的区域,尚未真正访问物理内存。物理内存的访问只有在调度到该进程时才会真正分配,即发生缺页异常时分配。

    三. 用户态缺页异常

      一旦开始访问虚拟内存的某个地址,如果我们发现,并没有对应的物理页,那就触发缺页中断,调用 do_page_fault()。这里的逻辑如下

    • 判断是否为内核缺页中断fault_in_kernel_space(),如果是则调用vmalloc_fault()
    • 如果是用户态缺页异常,则调用find_vma()找到地址所在vm_area_struct区域
    • 调用handle_mm_fault()映射找到的区域
    /*
     * This routine handles page faults.  It determines the address,
     * and the problem, and then passes it off to one of the appropriate
     * routines.
     */
    asmlinkage void __kprobes do_page_fault(struct pt_regs *regs,
    					unsigned long error_code,
    					unsigned long address)
    {
    ......
    	/*
    	 * We fault-in kernel-space virtual memory on-demand. The
    	 * 'reference' page table is init_mm.pgd.
    	 *
    	 * NOTE! We MUST NOT take any locks for this case. We may
    	 * be in an interrupt or a critical region, and should
    	 * only copy the information from the master page table,
    	 * nothing more.
    	 */
    	if (unlikely(fault_in_kernel_space(address))) {
    		if (vmalloc_fault(address) >= 0)
    			return;
    		if (notify_page_fault(regs, vec))
    			return;
    		bad_area_nosemaphore(regs, error_code, address);
    		return;
    	}
    ......
    	vma = find_vma(mm, address);
    ......
    	/*
    	 * If for any reason at all we couldn't handle the fault,
    	 * make sure we exit gracefully rather than endlessly redo
    	 * the fault.
    	 */
    	fault = handle_mm_fault(vma, address, flags);
    ......
    }
    
    
    

      find_vma()为红黑树查找操作,在此不做展开描述,下面重点看看handle_mm_fault()。这里经过一系列校验之后会根据是否是大页而选择调用hugetlb_fault()或者__handle_mm_fault()

    vm_fault_t handle_mm_fault(struct vm_area_struct *vma, unsigned long address,
    		unsigned int flags)
    {
    ......
    	if (unlikely(is_vm_hugetlb_page(vma)))
    		ret = hugetlb_fault(vma->vm_mm, vma, address, flags);
    	else
    		ret = __handle_mm_fault(vma, address, flags);
    ......
    	return ret;
    }
    

      __handle_mm_fault()完成实际上的映射操作。这里涉及到了由pgd, p4g, pud, pmd, pte组成的五级页表,页表索引填充完后调用handle_pte_fault()创建页表项。

    img
    static vm_fault_t __handle_mm_fault(struct vm_area_struct *vma,
    		unsigned long address, unsigned int flags)
    {
    	struct vm_fault vmf = {
    		.vma = vma,
    		.address = address & PAGE_MASK,
    		.flags = flags,
    		.pgoff = linear_page_index(vma, address),
    		.gfp_mask = __get_fault_gfp_mask(vma),
    	};
    	unsigned int dirty = flags & FAULT_FLAG_WRITE;
    	struct mm_struct *mm = vma->vm_mm;
    	pgd_t *pgd;
    	p4d_t *p4d;
    	vm_fault_t ret;
    	pgd = pgd_offset(mm, address);
    	p4d = p4d_alloc(mm, pgd, address);
    ......
    	vmf.pud = pud_alloc(mm, p4d, address);
    ......
    	vmf.pmd = pmd_alloc(mm, vmf.pud, address);
    ......
    	return handle_pte_fault(&vmf);
    }
    

      handle_pte_fault()处理以下三种情况

    • 页表项从未出现过,即新映射页表项
      • 匿名页映射,则映射到物理内存页,调用do_anonymous_page()
      • 文件映射,调用do_fault()
    • 页表项曾出现过,则为从物理内存换出的页,调用do_swap_page()换回来
    /*
     * These routines also need to handle stuff like marking pages dirty
     * and/or accessed for architectures that don't do it in hardware (most
     * RISC architectures).  The early dirtying is also good on the i386.
     *
     * There is also a hook called "update_mmu_cache()" that architectures
     * with external mmu caches can use to update those (ie the Sparc or
     * PowerPC hashed page tables that act as extended TLBs).
     *
     * We enter with non-exclusive mmap_sem (to exclude vma changes, but allow
     * concurrent faults).
     *
     * The mmap_sem may have been released depending on flags and our return value.
     * See filemap_fault() and __lock_page_or_retry().
     */
    static vm_fault_t handle_pte_fault(struct vm_fault *vmf)
    {
    	pte_t entry;
    ......
    		/*
    		 * A regular pmd is established and it can't morph into a huge
    		 * pmd from under us anymore at this point because we hold the
    		 * mmap_sem read mode and khugepaged takes it in write mode.
    		 * So now it's safe to run pte_offset_map().
    		 */
    		vmf->pte = pte_offset_map(vmf->pmd, vmf->address);
    		vmf->orig_pte = *vmf->pte;
    ......
    	if (!vmf->pte) {
    		if (vma_is_anonymous(vmf->vma))
    			return do_anonymous_page(vmf);
    		else
    			return do_fault(vmf);
    	}
    	if (!pte_present(vmf->orig_pte))
    		return do_swap_page(vmf);
    ......
    }
    

    3.1 匿名页映射

      对于匿名页映射,流程如下

    • 调用pte_alloc()分配页表项
    • 通过 alloc_zeroed_user_highpage_movable() 分配一个页,该函数会调用 alloc_pages_vma(),并最终调用 __alloc_pages_nodemask()。该函数是伙伴系统的核心函数,用于分配物理页面,在上文中已经详细分析过了。
    • 调用mk_pte()将新分配的页表项指向分配的页
    • 调用set_pte_at()将页表项加入该页
    /*
     * We enter with non-exclusive mmap_sem (to exclude vma changes,
     * but allow concurrent faults), and pte mapped but not yet locked.
     * We return with mmap_sem still held, but pte unmapped and unlocked.
     */
    static vm_fault_t do_anonymous_page(struct vm_fault *vmf)
    {
    	struct vm_area_struct *vma = vmf->vma;
    	struct mem_cgroup *memcg;
    	struct page *page;
    	vm_fault_t ret = 0;
    	pte_t entry;
    ......
    	/*
    	 * Use pte_alloc() instead of pte_alloc_map().  We can't run
    	 * pte_offset_map() on pmds where a huge pmd might be created
    	 * from a different thread.
    	 *
    	 * pte_alloc_map() is safe to use under down_write(mmap_sem) or when
    	 * parallel threads are excluded by other means.
    	 *
    	 * Here we only have down_read(mmap_sem).
    	 */
    	if (pte_alloc(vma->vm_mm, vmf->pmd))
    		return VM_FAULT_OOM;
    ......
    	page = alloc_zeroed_user_highpage_movable(vma, vmf->address);
    ......
    	entry = mk_pte(page, vma->vm_page_prot);
    	if (vma->vm_flags & VM_WRITE)
    		entry = pte_mkwrite(pte_mkdirty(entry));
    	vmf->pte = pte_offset_map_lock(vma->vm_mm, vmf->pmd, vmf->address,
    			&vmf->ptl);
    ......
    	set_pte_at(vma->vm_mm, vmf->address, vmf->pte, entry);
    ......
    }
    
    #define __alloc_zeroed_user_highpage(movableflags, vma, vaddr) \
    	alloc_page_vma(GFP_HIGHUSER | __GFP_ZERO | movableflags, vma, vaddr)
    

    3.2 文件映射

      映射文件do_fault()函数调用了fault函数,该函数实际会根据不同的文件系统调用不同的函数,如ext4文件系统中vm_ops指向ext4_file_vm_ops,实际调用ext4_filemap_fault()函数,该函数会调用filemap_fault()完成实际的文件映射操作。

    static vm_fault_t do_fault(struct vm_fault *vmf)
    {
    	struct vm_area_struct *vma = vmf->vma;
    	struct mm_struct *vm_mm = vma->vm_mm;
    	vm_fault_t ret;
    
    	if (!vma->vm_ops->fault) {
    ......
    }
    
    vm_fault_t ext4_filemap_fault(struct vm_fault *vmf)
    {
    ......
    	ret = filemap_fault(vmf);
    ......
    }
    

    file_map_fault()主要逻辑为

    • 调用find_ge_page()找到映射文件vm_file对应的物理内存缓存页面
      • 如果找到了,则调用do_async_mmap_readahead(),预读一些数据到内存里面
      • 否则调用pagecache_get_page()分配一个缓存页,将该页加入LRU表中,并在address_space中调用
    vm_fault_t filemap_fault(struct vm_fault *vmf)
    {
    	int error;
    	struct file *file = vmf->vma->vm_file;
    	struct file *fpin = NULL;
    	struct address_space *mapping = file->f_mapping;
    	struct file_ra_state *ra = &file->f_ra;
    	struct inode *inode = mapping->host;
    ......
    	/*
    	 * Do we have something in the page cache already?
    	 */
    	page = find_get_page(mapping, offset);
    	if (likely(page) && !(vmf->flags & FAULT_FLAG_TRIED)) {
    		/*
    		 * We found the page, so try async readahead before
    		 * waiting for the lock.
    		 */
    		fpin = do_async_mmap_readahead(vmf, page);
    	} else if (!page) {
    		/* No page in the page cache at all */
    ......       
    }
        
    struct page *pagecache_get_page(struct address_space *mapping, pgoff_t offset,
    	int fgp_flags, gfp_t gfp_mask)
    {
    ......
    		page = __page_cache_alloc(gfp_mask);
    ......
    		err = add_to_page_cache_lru(page, mapping, offset, gfp_mask);
    ......
    }    
    

    3.3 页交换

      前文提到了我们会通过主动回收或者被动回收的方式将物理内存已映射的页面回收至硬盘中,当数据再次访问时,我们又需要通过do_swap_page()将其从硬盘中读回来。do_swap_page() 函数逻辑流程如下:查找 swap 文件有没有缓存页。如果没有,就调用 swapin_readahead()swap 文件读到内存中来形成内存页,并通过 mk_pte() 生成页表项。set_pte_at 将页表项插入页表,swap_freeswap 文件清理。因为重新加载回内存了,不再需要 swap 文件了。

    vm_fault_t do_swap_page(struct vm_fault *vmf)
    {
    ......
    	entry = pte_to_swp_entry(vmf->orig_pte);
    ......
    	page = lookup_swap_cache(entry, vma, vmf->address);
    	swapcache = page;
    	if (!page) {
    		struct swap_info_struct *si = swp_swap_info(entry);
    		if (si->flags & SWP_SYNCHRONOUS_IO &&
    				__swap_count(si, entry) == 1) {
    			/* skip swapcache */
    			page = alloc_page_vma(GFP_HIGHUSER_MOVABLE, vma,
    							vmf->address);
    ......
    		} else {
    			page = swapin_readahead(entry, GFP_HIGHUSER_MOVABLE,
    						vmf);
    			swapcache = page;
    		}
    ......
    	pte = mk_pte(page, vma->vm_page_prot);
    ......
    	set_pte_at(vma->vm_mm, vmf->address, vmf->pte, pte);
    	arch_do_swap_page(vma->vm_mm, vma, vmf->address, pte, vmf->orig_pte);
    	vmf->orig_pte = pte;
    ......
    	swap_free(entry);
    ......
    }
    

      通过以上步骤,用户态的缺页异常就处理完毕了。物理内存中有了页面,页表也建立好了映射。接下来,用户程序在虚拟内存空间里面可以通过虚拟地址顺利经过页表映射的访问物理页面上的数据了。页表一般都很大,只能存放在内存中。操作系统每次访问内存都要折腾两步,先通过查询页表得到物理地址,然后访问该物理地址读取指令、数据。为了加快映射速度,我们引入了 [TLB](https://en.wikipedia.org/wiki/Translation_lookaside_buffer#:~:text=A%20translation%20lookaside%20buffer%20(TLB,called%20an%20address%2Dtranslation%20cache.)(Translation Lookaside Buffer),我们经常称为快表,专门用来做地址映射的硬件设备。它不在内存中,可存储的数据比较少,但是比内存要快。所以我们可以想象,TLB 就是页表的 Cache,其中存储了当前最可能被访问到的页表项,其内容是部分页表项的一个副本。有了 TLB 之后,我们先查块表,块表中有映射关系,然后直接转换为物理地址。如果在 TLB 查不到映射关系时,才会到内存中查询页表。

    四. 内核态内存映射及缺页异常

      和用户态使用malloc()类似,内核态也有相应的内存映射函数:vmalloc()可用于分配不连续物理页(使用伙伴系统),kmem_cache_alloc()kmem_cache_create()使用slub分配器分配小块内存,而kmalloc()类似于malloc(),在分配大内存的时候会使用伙伴系统,分配小内存则使用slub分配器。分配内存后会转换为虚拟地址,保存在内核页表中进行映射,有需要时直接访问。由于vmalloc()会带来虚拟连续页和物理不连续页的映射,因此一般速度较慢,使用较少,相比而言kmalloc()使用的更为频繁。而kmem_cache_alloc()kmem_cache_create()会分配更为精准的小内存块用于特定任务,因此也比较常用。

      相对于用户态,内核态还有一种特殊的映射:临时映射。内核态高端内存地区为了节省空间会选择临时映射,采用kmap_atomic()实现。如果是 32 位有高端地址的,就需要调用 set_pte 通过内核页表进行临时映射;如果是 64 位没有高端地址的,就调用 page_address,里面会调用 lowmem_page_address。其实低端内存的映射,会直接使用 __va 进行临时映射。

    void *kmap_atomic_prot(struct page *page, pgprot_t prot)
    {
    ......
        if (!PageHighMem(page))
            return page_address(page);
    ......
        vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx);
        set_pte(kmap_pte-idx, mk_pte(page, prot));
    ......
        return (void *)vaddr;
    }
    
    void *kmap_atomic(struct page *page)
    {
        return kmap_atomic_prot(page, kmap_prot);
    }
    
    static __always_inline void *lowmem_page_address(const struct page *page)
    {
        return page_to_virt(page);
    }
    
    #define page_to_virt(x)  __va(PFN_PHYS(page_to_pfn(x)
    

      kmap_atomic ()发现没有页表的时候会直接创建页表进行映射。而 vmalloc ()只分配了内核的虚拟地址。所以访问它的时候,会产生缺页异常。内核态的缺页异常还是会调用 do_page_fault(),最终进入vmalloc_fault()。在这里会实现内核页表项的关联操作,从而完成分配,整体流程和用户态相似。

    static noinline int vmalloc_fault(unsigned long address)
    {
        unsigned long pgd_paddr;
        pmd_t *pmd_k;
        pte_t *pte_k;
        /* Make sure we are in vmalloc area: */
        if (!(address >= VMALLOC_START && address < VMALLOC_END))
            return -1;
    
        /*
         * Synchronize this task's top level page-table
         * with the 'reference' page table.
         *
         * Do _not_ use "current" here. We might be inside
         * an interrupt in the middle of a task switch..
         */
        pgd_paddr = read_cr3_pa();
        pmd_k = vmalloc_sync_one(__va(pgd_paddr), address);
        if (!pmd_k)
            return -1;
    
        pte_k = pte_offset_kernel(pmd_k, address);
        if (!pte_present(*pte_k))
            return -1;
    
      return 0
    }
    

    五. 总结

      至此,我们分析了内存物理地址和虚拟地址的映射关系,结合前文页的分配和管理,内存部分的主要功能就算是大致分析清楚了,最后引用极客时间中的一幅图作为总结,算是全部知识点的汇总。

    img

    代码资料

    [1] brk

    [2] mmap

    [3] page_fault

    参考文献

    [1] wiki

    [2] elixir.bootlin.com/linux

    [3] woboq

    [4] Linux-insides

    [5] 深入理解Linux内核

    [6] Linux内核设计的艺术

    [7] 极客时间 趣谈Linux操作系统

    展开全文
  • 目的是让wps进程创建时,不要建立“\Sessions\2\BaseNamedObjects\shared_memory_content_wps_starup_object_{DEB796DA-F98E-48A4-AE1E-71411184820E}” 这个内存映射文件,可是MyCreateFileMappingW函数里并没有...
  • 内存映射文件

    2014-02-12 17:47:35
    内存文件映射是Windows的一种内存管理方法,提供了一个统一的内存管理特征,使应用程序可以通过内存指针对磁盘上的文件进行访问,其过程就如同对加载了文件的内存的访问。通过文件映射这种使磁盘文件的全部或部分...


    概念介绍:
    内存文件映射是Windows的一种内存管理方法,提供了一个统一的内存管理特征,使应用程序可以通过内存指针对磁盘上的文件进行访问,其过程就如同对加载了文件的内存的访问。通过文件映射这种使磁盘文件的全部或部分内容与进程虚拟地址空间的某个区域建立映射关联的能力,可以直接对被映射的文件进行访问,而不必执行文件I/O操作也无需对文件内容进行缓冲处理。内存文件映射的这种特性是非常适合于用来管理大尺寸文件的。


      在使用内存映射文件进行I/O处理时,系统对数据的传输按页面来进行。至于内部的所有内存页面则是由虚拟内存管理器来负责管理,由其来决定内存页面何时被分页到磁盘,哪些页面应该被释放以便为其它进程提供空闲空间,以及每个进程可以拥有超出实际分配物理内存之外的多少个页面空间等等。由于虚拟内存管理器是以一种统一的方式来处理所有磁盘I/O的(以页面为单位对内存数据进行读写),因此这种优化使其有能力以足够快的速度来处理内存操作。

      使用内存映射文件时所进行的任何实际I/O交互都是在内存中进行并以标准的内存地址形式来访问。磁盘的周期性分页也是由操作系统在后台隐蔽实现的,对应用程序而言是完全透明的。内存映射文件的这种特性在进行大文件的磁盘事务操作时将获得很高的效益。

      需要说明的是,在系统的正常的分页操作过程中,内存映射文件并非一成不变的,它将被定期更新。如果系统要使用的页面目前正被某个内存映射文件所占用,系统将释放此页面,如果页面数据尚未保存,系统将在释放页面之前自动完成页面数据到磁盘的写入。

      对于使用页虚拟存储管理的Windows操作系统,内存映射文件是其内部已有的内存管理组件的一个扩充。由可执行代码页面和数据页面组成的应用程序可根据需要由操作系统来将这些页面换进或换出内存。如果内存中的某个页面不再需要,操作系统将撤消此页面原拥用者对它的控制权,并释放该页面以供其它进程使用。只有在该页面再次成为需求页面时,才会从磁盘上的可执行文件重新读入内存。同样地,当一个进程初始化启动时,内存的页面将用来存储该应用程序的静态、动态数据,一旦对它们的操作被提交,这些页面也将被备份至系统的页面文件,这与可执行文件被用来备份执行代码页面的过程是很类似的。


    内存映射文件可以用于3个目的

    ? 系统使用内存映射文件,以便加载和执行. exe和DLL文件。这可以大大节省页文件空间和应用程序启动运行所需的时间。

    ? 可以使用内存映射文件来访问磁盘上的数据文件。这使你可以不必对文件执行I/O操作,并且可以不必对文件内容进行缓存。

    ? 可以使用内存映射文件,使同一台计算机上运行的多个进程能够相互之间共享数据。Windows确实提供了其他一些方法,以便在进程之间进行数据通信,但是这些方法都是使用内存映射文件来实现的,这使得内存映射文件成为单个计算机上的多个进程互相进行通信的最有效的方法。


    要使用内存映射文件,必须执行下列操作步骤:

    1) 创建或打开一个文件内核对象,该对象用于标识磁盘上你想用作内存映射文件的文件。

    2) 创建一个文件映射内核对象,告诉系统该文件的大小和你打算如何访问该文件。

    3) 让系统将文件映射对象的全部或一部分映射到你的进程地址空间中。

    当完成对内存映射文件的使用时,必须执行下面这些步骤将它清除:

    1) 告诉系统从你的进程的地址空间中撤消文件映射内核对象的映像。

    2) 关闭文件映射内核对象。

    3) 关闭文件内核对象。

    相关函数如下图



    <script type=text/javascript charset=utf-8 src="http://static.bshare.cn/b/buttonLite.js#style=-1&uuid=&pophcol=3&lang=zh"></script> <script type=text/javascript charset=utf-8 src="http://static.bshare.cn/b/bshareC0.js"></script>
    阅读(36) | 评论(0) | 转发(0) |
    给主人留下些什么吧!~~
    评论热议
    展开全文
  • MFC-内存映射实现数据共享

    千次阅读 2016-03-31 19:51:55
    做两个进程,一个进程把数据写到内存空间,一个进程从内存空间中读取数据.写数据的进程: 1.定义两个成员变量 HANDLE...在初始化对话框中获取一块内存空间以便写入//创建映射对象 m_hMapObject = CreateFileMapping( (H

    ==> 学习汇总(持续更新)
    ==> 从零搭建后端基础设施系列(一)-- 背景介绍


    做两个进程,一个进程把数据写到内存空间,一个进程从内存空间中读取数据.

    写数据的进程:
    1.定义两个成员变量

        HANDLE m_hMapObject;//映射对象句柄
    	LPTSTR m_pszMapView;//指向映射的地址空间
    

    2.在初始化对话框中获取一块内存空间以便写入

    //创建映射对象
    	m_hMapObject = CreateFileMapping(
    		(HANDLE)0xffffffff, //因为不是读写文件,所以不需要文件句柄
    		nullptr,			//默认安全属性
    		PAGE_READWRITE, 	//可读写
    		0,					//高32位不用
    		0x1000,				//用低32位,大小为4k
    		TEXT("ShareData")	//共享数据的名字
    		);
        if (!m_hMapObject)
        {
    		AfxMessageBox(TEXT("创建映射对象失败"));
    		return FALSE;
        }
    	//获取一块内存空间以便写入数据
    	m_pszMapView = (LPTSTR)MapViewOfFile(m_hMapObject, FILE_SHARE_WRITE, 0, 0, 0);
    	if (!m_pszMapView)
    	{
    		AfxMessageBox(TEXT("映射数据失败"));
    		return FALSE;
    	}
    

    3.写入数据

    	//把编辑框的数据读到内存空间
    	UpdateData();
    	lstrcpy(m_pszMapView, m_send);
    

    读取数据进程:
    1.定义两个成员变量

        HANDLE m_hMapObject;//映射对象句柄
    	LPTSTR	m_pszMapView;
    

    2.在初始化对话框中打开映射对象,并获取共享数据的内存地址,然后定时显示获取的数据

    	//打开映射对象
    	m_hMapObject = OpenFileMapping(
    		FILE_MAP_READ,   //可读映射
    		FALSE, 			 //不被继承
    		TEXT("ShareData")//共享数据的名称
    		);
    	if (!m_hMapObject)
    	{
    		AfxMessageBox(TEXT("打开映射失败"));
    		return FALSE;
    	}
    	//读取共享数据,只要得到这块共享内存的地址,只要里面的数据发生改变,m_pszMapView也会跟着改变
    	m_pszMapView = (LPTSTR)MapViewOfFile(m_hMapObject, FILE_SHARE_READ, 0, 0, 0);
    	if (!m_pszMapView)
    	{
    		AfxMessageBox(TEXT("读取映射数据失败"));
    		return FALSE;
    	}
    
    	//设置一个定时器,一定间隔时间显示所接收的数据
    	SetTimer(1, 50, nullptr);
    

    3.在定时器函数中定时显示共享数据

    
    void CMFCTEST1Dlg::OnTimer(UINT_PTR nIDEvent)
    {
    	// TODO: 在此添加消息处理程序代码和/或调用默认值
    
    	SetDlgItemText(IDC_STATIC_RECV, m_pszMapView);
    
    	CDialogEx::OnTimer(nIDEvent);
    }
    

    4.窗口销毁时关闭定时器

    void CMFCTEST1Dlg::OnDestroy()
    {
    	CDialogEx::OnDestroy();
    
    	// TODO: 在此处添加消息处理程序代码
    	KillTimer(1);
    }
    

    同理,可以把一些大的文件放到共享内存中,然后给其它进程读取.

    展开全文
  • 内存映射

    2011-03-21 14:42:57
    以下部分代码出自:windows核心编程 主要应用: 主界面只有一个,并可以注入多个窗口,主界面管理和配置各个被...其实并非hook,使用远程注入,所以还要在目标进程的消息循环上做手脚,即修改汇编代码。 JMP到d...
  • SSDT HOOK 内存写保护

    2016-06-14 15:32:33
    有些人说不去掉也不会蓝屏,照样能HOOK成功 确实,我当时也是这样过。。。 不过拿给别人机器一测试就蓝了 网上找到了MJ给出的答案: 当使用大页面映射内核文件时,代码段和数据段在一块儿,所以页必须是可写的...
  • 使用MDL读写内存的SSDT HOOK

    千次阅读 2019-05-30 21:15:21
    //MDL是一个对物理内存的描述,负责把虚拟内存映射到物理内存 PMDL pmdl = IoAllocateMdl(Dst, Length, 0, 0, NULL);//分配mdl if(pmdl==NULL) return STATUS_UNSUCCESSFUL; MmBuildMdlForNonPagedPool(pmdl);...
  • SSDT Hook之修改内存保护

    千次阅读 2010-12-27 15:35:00
    Windows系统对部分内存写了保护,防止内存也被修改,比如xp 和 2003,它们使得SSDT变成只读的表,以此来防止任何应用程序来修改这个表。   有两种方法可以绕过写保护,一种是修改控制寄存器的CR0...
  • 关于SSDT HOOK取消内存写保护的问题

    千次阅读 2011-10-02 15:49:54
    有些人说不去掉也不会蓝屏,照样能HOOK成功 确实,我当时也是这样过。。。 不过拿给别人机器一测试就蓝了 网上找到了MJ给出的答案: 当使用大页面映射内核文件时,代码段和数据段在一块儿,所以页必须是可写的...
  • hook

    2010-06-05 12:36:00
    Hook解释 Hook是Windows中提供的一种用以替换DOS下“中断”的系统机制,中文译为“挂钩”或“钩子”。在对特定的系统事件进行hook后,一旦发生已hook事件,对该事件进行hook的程序就会受到系统的通知,这时程序就...
  • HOOK

    2010-08-24 14:31:00
    Hook解释  Hook是Windows中提供的一种用以替换DOS下“中断”的系统机制,中文译为“挂钩”或“钩子”。在对特定的系统事件进行hook后,一旦发生已hook事件,对该事件进行hook的程序就会受到系统的通知,...
  • 原帖关于SSDT HOOK取消内存写保护的问题 有些人说不去掉也不会蓝屏,照样能HOOK成功确实,我当时也是这样过。。。不过拿给别人机器一测试就蓝了网上找到了MJ给出的答案:当使用大页面映射内核文件时,代码段和数据...
  • 在之前一篇文章中,已经介绍了Android中如何修改内存指令改变方法执行逻辑,当时那篇文章的大致流程很简单,在程序运行起来,dex文件被加载到内存中之后,通过读取maps文件,获取dex文件的内存其实地址,然后通过...
  • Hook

    2013-10-07 17:00:45
    HOOK专题 目录 基本概念 运行机制 钩子类型 作者 基本概念 钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所...
  • 子程序 读内存字节集, 字节集, 公开, 从内存中读取字节集数据(返回字节集,失败返回0字节长度的空字节集) .参数 进程ID, 整数型, , 进程ID .参数 地址, 整数型, , 内存地址 .参数 长度, 整数型, , 欲读取...
  • SSDTHook实例--编写稳定的Hook过滤函数

    千次阅读 2015-07-04 13:45:16
    讲解如何写Hook过滤函数,比如NewZwOpenProcess。打开进程。很多游戏保护都会对这个函数进行Hook。由于我们没有游戏保护的代码,无法得知游戏公司是如何编写这个过滤函数。   我看到很多奇形怪状的Hook过滤函数的...
  • 子程序 读内存字节集, 字节集, 公开, 从内存中读取字节集数据(返回字节集,失败返回0字节长度的空字节集) .参数 进程ID, 整数型, , 进程ID .参数 地址, 整数型, , 内存地址 .参数 长度, 整数型, , 欲读取内存数据的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 11,953
精华内容 4,781
关键字:

内存映射hook