Linux mprotect函数 -- control the protection of pages

  • 函数原型

    int mprotect(void *addr, size_t len, int prot);
    
  • 头文件

    #include <sys/mman.h>
    #include <unistd.h>
    
  • 描述

    control the protection of pages.

    The mprotect() system call changes the specified pages to have protection prot. When a program violates the protections of a page, it gets a SIGBUS or SIGSEGV signal.

    主要是用来修改一段指定内存区域的保护属性。

    使用mprotect这里最重要的一点是被保护的内存是按页对齐的,范围也是按页来的。这是因为Linux管理进程地址空间是一VMA(Virtual Memory Area)为单位来管理进程虚拟地址空间的,而VMA必须是page size大小的整数倍。

  • 参数

     PROT_NONE   No permissions at all.
     PROT_READ   The pages can be read.
     PROT_WRITE  The pages can be written.
     PROT_EXEC   The pages can be executed.
    
  • 返回值

    Upon successful completion, a value of 0 is returned. Otherwise, a value of -1 is returned and errno is set to indicate the error.

    常见的错误有:

    • EACCES

      该内存不能设置为相应权限。这是可能发生的,比如,如果你 mmap(2) 映射一个文件为只读的,接着使用 mprotect() 标志为 PROT_WRITE。

    • EINVAL

      start 不是一个有效的指针,指向的不是某个内存页的开头。

    • ENOMEM

      内核内部的结构体无法分配。

  • 举个例子,修改虚函数表

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/mman.h>
    
    void print()
    {
    	printf("print\n");
    }
    
    class Base 
    {
    public:
    	virtual void print(){
    		printf("Base::print\n");
    	} 
    };
    
    void changeVtable()
    {
    	Base base;
    	void** vtable= *(void***)&base;
    	// 获取页大小
    	unsigned long pagesize = sysconf(_SC_PAGE_SIZE);
    	// 内存按页对齐
    	void* align_page_addr = reinterpret_cast<void*>(reinterpret_cast<unsigned long>(&vtable[0]) & (~(pagesize-1)));
    	// 修改权限
    	int ret = mprotect(align_page_addr, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC);
    	if(ret != 0) {
    		printf("mprotect error\n");
    		return;
    	}
    	// 修改虚函数表内容
    	vtable[0] = reinterpret_cast<void*>(print);
    }
    
    int main()
    {
    
    	Base* pbase = new Base();
    	Base base;
    	printf("%p\n",print);
    	printf("%p\n",reinterpret_cast<void*>(**(void***)(&base)));
    	printf("%p\n",reinterpret_cast<void*>(**(void***)(pbase)));
    
    	print();
    	base.print();
    	pbase->print();
    
    	changeVtable();
    
    	printf("%p\n",print);
    	printf("%p\n",reinterpret_cast<void*>(**(void***)(&base)));
    	printf("%p\n",reinterpret_cast<void*>(**(void***)(pbase)));
    
    	print();
    	base.print();
    	pbase->print();
    
    	delete pbase;
    	return 0;
    }
    

    输出结果为:

    0x10f7f1cd0
    0x10f7f1ee0
    0x10f7f1ee0
    print
    Base::print
    Base::print
    0x10f7f1cd0
    0x10f7f1cd0
    0x10f7f1cd0
    print
    Base::print
    print
    

    注意虚函数的调用时刻。

发布了55 篇原创文章 · 获赞 3 · 访问量 6610
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章