Linux地址映射

“/proc/<pid>/pagemap”解构

  从官方文件中得到如下信息:

“/proc/<pid>/pagemap”这个文件让用户空间进程发现每个虚拟页映射到哪个物理帧。每个虚拟页包含一个64位值,该64位值包含以下数据(文件fs/proc/task_mmu.c中的pagemap_read可见):

  • 0-54位:物理页帧号(PFN)(如果存在);

  • 0-4位:交换类型(如果交换);

  • 5-54位:交换偏移量;

  • 55位:pte用来标记是否是soft-dirty的;

  • 56位:页专属映射(从4.2版本开始)

  • 57-60位:零

  • 61位:页是file-page或shared-anon(从3.5版本开始)

  • 62位:页交换

  • 63位:页是否存在

如果该页不在swap中,则PFN包含交换文件号和该页在swap中的偏移量的编码。未映射页返回一个空PFN。这允许精确地确定哪些页被映射(或在交换区中),并比较进程之间的映射页。

这个接口的高效用户将使用/proc/pid/maps来确定哪些内存区域实际上是映射的,llseek将跳过未映射的区域。

  因此对该文件中所存虚拟页的64位值进行分解,并提取低55即为该虚拟页地址的物理地址值。

代码

  关键信息已在代码中以注释形式展示,如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <stdint.h>
#include <string.h>

#define PAGEMAP_ENTRY 8
#define GET_BIT(X,Y) ((X & ((uint64_t)1<<Y)) >> Y)
#define GET_PFN(X) (X & 0x7FFFFFFFFFFFFF)

const int __endian_bit = 1;
#define is_bigendian() ( (*(char*)&__endian_bit) == 0 )

int i, c, pid, status;
unsigned long virt_addr;
uint64_t read_val, file_offset, page_size;
char path_buf [0x100] = {};
FILE * f;
char *end;

int read_pagemap(char * path_buf, unsigned long virt_addr);

int main(int argc, char ** argv){
    if(argc != 3){
        printf("Argument number is not correct! It must like:\n./VtoP PID VIRTUAL_ADDRESS\n");
        return -1;
    }
    if(!memcmp(argv[1], "self", sizeof("self"))){  //该VtoP进程自身
        sprintf(path_buf, "/proc/self/pagemap");
        pid = -1;
    }
    else{  //指定的进程
        pid = strtol(argv[1], &end, 10);
        if (end == argv[1] || *end != '\0' || pid <= 0){
            printf("PID must be a positive number or 'self'\n");
            return -1;
        }
    }
    virt_addr = strtoll(argv[2], NULL, 16);
    if(pid != -1)
        sprintf(path_buf, "/proc/%u/pagemap", pid);

    page_size = getpagesize();  //获取页面大小
    read_pagemap(path_buf, virt_addr);  //读取页面映射内容
    return 0;
}

int read_pagemap(char * path_buf, unsigned long virt_addr){
    //printf("Big endian? %d\n", is_bigendian());
    f = fopen(path_buf, "rb");
    if(!f){
        printf("Error! Cannot open %s\n", path_buf);
        return -1;
    }

    /* 
     * 根据用户提供的虚拟内存地址计算该地址在文件中的偏移地址,公式为:
     * 文件中偏移地址 = virt_addr偏移的字节数 * pagemap文件中条目的大小
     */ 
    file_offset = virt_addr / page_size * PAGEMAP_ENTRY;
    printf("Vaddr: 0x%lx, Page_size: %lld, Entry_size: %d\n", virt_addr, page_size, PAGEMAP_ENTRY);
    printf("Reading %s at 0x%llx\n", path_buf, (unsigned long long) file_offset);
    status = fseek(f, file_offset, SEEK_SET);
    if(status){
        perror("Failed to do fseek!");
        return -1;
    }
    errno = 0;
    read_val = 0;
    unsigned char c_buf[PAGEMAP_ENTRY];
    for(i = 0; i < PAGEMAP_ENTRY; i++){
        c = getc(f);
        if(c == EOF){
            printf("\nReached end of the file\n");
            return 0;
        }
        if(is_bigendian()) c_buf[i] = c;
        else c_buf[PAGEMAP_ENTRY - i - 1] = c;
        printf("[%d]0x%x ", i, c);
    }
    for(i = 0; i < PAGEMAP_ENTRY; i++){
        read_val = (read_val << 8) + c_buf[i];
    }
    printf("\n");
    printf("Result: 0x%llx\n", (unsigned long long) read_val);
    /*
     * 如果页面不存在,但在交换中,那么PFN包含交换文件编号的编码以及页面在交换中的偏移量。
     * 未映射的页返回空的PFN。这允许精确地确定映射(或交换)哪些页,并比较进程之间的映射页。
     */
    if(GET_BIT(read_val, 63)) {
        uint64_t pfn = GET_PFN(read_val);
        printf("PFN: 0x%llx (0x%llx)\n", pfn, pfn * page_size + virt_addr % page_size);
    }
    else printf("Page not present\n");
    if(GET_BIT(read_val, 62)) printf("Page swapped\n");
    fclose(f);
    return 0;
}

测试

  查看进程情况:
在这里插入图片描述
  查看19238号进程虚拟地址0x400000所对应的物理地址(这里使用sudo提权,否则无法得到物理地址),如下:
在这里插入图片描述
  查看不同虚拟地址的共享库调用的物理地址是否相同。利用cat /proc/<pid>/maps来查看虚拟地址的内存映射情况,如下:

  对于pid=19055的进程,存在如下的lib函数调用,其虚拟地址如下:
在这里插入图片描述
  对于pid=19191的进程,存在如下的lib函数调用,其虚拟地址如下:
在这里插入图片描述
  观察到两进程虽然都调用了同一个函数库,但是两者的虚拟地址映射的起始位置并不相同。但是由于该库是一个共享库,因此其在内存中的物理地址应该相同,使用编写的程序查看,如下:
在这里插入图片描述
在这里插入图片描述
  观察到对于同一lib函数的调用,尽管虚拟地址不同,但两者的物理地址相同。

参考资料

[1]. https://stackoverflow.com/questions/6284810/proc-pid-pagemaps-and-proc-pid-maps-linux

[2]. https://www.cnblogs.com/pengdonglin137/p/6802108.html

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

D-A-X

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值