利用/proc/pid/pagemap将虚拟地址转换为物理地址

内核文档: Documentation/vm/pagemap.txt

pagemap is a new (as of 2.6.25) set of interfaces in the kernel that allow
userspace programs to examine the page tables and related information by
reading files in /proc.

There are four components to pagemap:

* /proc/pid/pagemap.  This file lets a userspace process find out which
   physical frame each virtual page is mapped to.  It contains one 64-bit
   value for each virtual page, containing the following data (from
   fs/proc/task_mmu.c, above pagemap_read):

* Bits 0-54  page frame number (PFN) if present
    * Bits 0-4   swap type if swapped
    * Bits 5-54  swap offset if swapped
    * Bit  55    pte is soft-dirty (see Documentation/vm/soft-dirty.txt)
    * Bit  56    page exclusively mapped (since 4.2)
    * Bits 57-60 zero
    * Bit  61    page is file-page or shared-anon (since 3.5)
    * Bit  62    page swapped
    * Bit  63    page present

Since Linux 4.0 only users with the CAP_SYS_ADMIN capability can get PFNs.
   In 4.0 and 4.1 opens by unprivileged fail with -EPERM.  Starting from
   4.2 the PFN field is zeroed if the user does not have CAP_SYS_ADMIN.
   Reason: information about PFNs helps in exploiting Rowhammer vulnerability.

If the page is not present but in swap, then the PFN contains an
   encoding of the swap file number and the page‘s offset into the
   swap. Unmapped pages return a null PFN. This allows determining
   precisely which pages are mapped (or in swap) and comparing mapped
   pages between processes.

Efficient users of this interface will use /proc/pid/maps to
   determine which areas of memory are actually mapped and llseek to
   skip over unmapped regions.

下面是一个工具:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <assert.h>
 5 #include <errno.h>
 6 #include <stdint.h>
 7 #include <string.h>
 8
 9 #define PAGEMAP_ENTRY 8
10 #define GET_BIT(X,Y) (X & ((uint64_t)1<<Y)) >> Y
11 #define GET_PFN(X) X & 0x7FFFFFFFFFFFFF
12
13 const int __endian_bit = 1;
14 #define is_bigendian() ( (*(char*)&__endian_bit) == 0 )
15
16 int i, c, pid, status;
17 unsigned long virt_addr;
18 uint64_t read_val, file_offset, page_size;
19 char path_buf [0x100] = {};
20 FILE * f;
21 char *end;
22
23 int read_pagemap(char * path_buf, unsigned long virt_addr);
24
25 int main(int argc, char ** argv){
26     if(argc!=3){
27         printf("Argument number is not correct!\n pagemap PID VIRTUAL_ADDRESS\n");
28         return -1;
29     }
30     if(!memcmp(argv[1],"self",sizeof("self"))){
31         sprintf(path_buf, "/proc/self/pagemap");
32         pid = -1;
33     }
34     else{
35         pid = strtol(argv[1],&end, 10);
36         if (end == argv[1] || *end != ‘\0‘ || pid<=0){
37             printf("PID must be a positive number or ‘self‘\n");
38             return -1;
39         }
40     }
41     virt_addr = strtoll(argv[2], NULL, 16);
42     if(pid!=-1)
43         sprintf(path_buf, "/proc/%u/pagemap", pid);
44
45     page_size = getpagesize();
46     read_pagemap(path_buf, virt_addr);
47     return 0;
48 }
49
50 int read_pagemap(char * path_buf, unsigned long virt_addr){
51     printf("Big endian? %d\n", is_bigendian());
52     f = fopen(path_buf, "rb");
53     if(!f){
54         printf("Error! Cannot open %s\n", path_buf);
55         return -1;
56     }
57
58     //Shifting by virt-addr-offset number of bytes
59     //and multiplying by the size of an address (the size of an entry in pagemap file)
60     file_offset = virt_addr / page_size * PAGEMAP_ENTRY;
61     printf("Vaddr: 0x%lx, Page_size: %lld, Entry_size: %d\n", virt_addr, page_size, PAGEMAP_ENTRY);
62     printf("Reading %s at 0x%llx\n", path_buf, (unsigned long long) file_offset);
63     status = fseek(f, file_offset, SEEK_SET);
64     if(status){
65         perror("Failed to do fseek!");
66         return -1;
67     }
68     errno = 0;
69     read_val = 0;
70     unsigned char c_buf[PAGEMAP_ENTRY];
71     for(i=0; i < PAGEMAP_ENTRY; i++){
72         c = getc(f);
73         if(c==EOF){
74             printf("\nReached end of the file\n");
75             return 0;
76         }
77         if(is_bigendian())
78             c_buf[i] = c;
79         else
80             c_buf[PAGEMAP_ENTRY - i - 1] = c;
81         printf("[%d]0x%x ", i, c);
82     }
83     for(i=0; i < PAGEMAP_ENTRY; i++){
84         //printf("%d ",c_buf[i]);
85         read_val = (read_val << 8) + c_buf[i];
86     }
87     printf("\n");
88     printf("Result: 0x%llx\n", (unsigned long long) read_val);
89     if(GET_BIT(read_val, 63)) {
90         uint64_t pfn = GET_PFN(read_val);
91         printf("PFN: 0x%llx (0x%llx)\n", pfn, pfn * page_size + virt_addr % page_size);
92     } else
93         printf("Page not present\n");
94     if(GET_BIT(read_val, 62))
95         printf("Page swapped\n");
96     fclose(f);
97     return 0;
98 }

测试:

用Qemu+vexpress-ca9:

内存: 1GB, 物理地址范围: 0x60000000->0x9FFFFFFF

通过查看/proc/pid/maps获得进程的地址空间的内存映射情况:

 1 [[email protected] ~]# cat /proc/746/maps
 2 00008000-001f3000 r-xp 00000000 b3:01 62         /bin/busybox
 3 001fa000-001fc000 rw-p 001ea000 b3:01 62         /bin/busybox
 4 001fc000-00222000 rw-p 00000000 00:00 0          [heap]
 5 b6c7f000-b6c80000 rw-p 00000000 00:00 0
 6 b6c80000-b6c8d000 r-xp 00000000 b3:01 174        /lib/libnss_files-2.18.so
 7 b6c8d000-b6c94000 ---p 0000d000 b3:01 174        /lib/libnss_files-2.18.so
 8 b6c94000-b6c95000 r--p 0000c000 b3:01 174        /lib/libnss_files-2.18.so
 9 b6c95000-b6c96000 rw-p 0000d000 b3:01 174        /lib/libnss_files-2.18.so
10 b6c96000-b6ca1000 r-xp 00000000 b3:01 141        /lib/libnss_nis-2.18.so
11 b6ca1000-b6ca8000 ---p 0000b000 b3:01 141        /lib/libnss_nis-2.18.so
12 b6ca8000-b6ca9000 r--p 0000a000 b3:01 141        /lib/libnss_nis-2.18.so
13 b6ca9000-b6caa000 rw-p 0000b000 b3:01 141        /lib/libnss_nis-2.18.so
14 b6caa000-b6daa000 rw-p 00000000 00:00 0
15 b6daa000-b6dca000 r-xp 00000000 b3:01 129        /lib/ld-2.18.so
16 b6dca000-b6dd1000 ---p 00020000 b3:01 129        /lib/ld-2.18.so
17 b6dd1000-b6dd2000 r--p 0001f000 b3:01 129        /lib/ld-2.18.so
18 b6dd2000-b6dd3000 rw-p 00020000 b3:01 129        /lib/ld-2.18.so
19 b6dd3000-b6f06000 r-xp 00000000 b3:01 170        /lib/libc-2.18.so
20 b6f06000-b6f0d000 ---p 00133000 b3:01 170        /lib/libc-2.18.so
21 b6f0d000-b6f0f000 r--p 00132000 b3:01 170        /lib/libc-2.18.so
22 b6f0f000-b6f10000 rw-p 00134000 b3:01 170        /lib/libc-2.18.so
23 b6f10000-b6f13000 rw-p 00000000 00:00 0
24 b6f13000-b6f26000 r-xp 00000000 b3:01 177        /lib/libnsl-2.18.so
25 b6f26000-b6f2d000 ---p 00013000 b3:01 177        /lib/libnsl-2.18.so
26 b6f2d000-b6f2e000 r--p 00012000 b3:01 177        /lib/libnsl-2.18.so
27 b6f2e000-b6f2f000 rw-p 00013000 b3:01 177        /lib/libnsl-2.18.so
28 b6f2f000-b6f31000 rw-p 00000000 00:00 0
29 b6f31000-b6f39000 r-xp 00000000 b3:01 154        /lib/libnss_compat-2.18.so
30 b6f39000-b6f40000 ---p 00008000 b3:01 154        /lib/libnss_compat-2.18.so
31 b6f40000-b6f41000 r--p 00007000 b3:01 154        /lib/libnss_compat-2.18.so
32 b6f41000-b6f42000 rw-p 00008000 b3:01 154        /lib/libnss_compat-2.18.so
33 be958000-be979000 rw-p 00000000 00:00 0          [stack]
34 bed04000-bed05000 r-xp 00000000 00:00 0          [sigpage]
35 bed05000-bed06000 r--p 00000000 00:00 0          [vvar]
36 bed06000-bed07000 r-xp 00000000 00:00 0          [vdso]
37 ffff0000-ffff1000 r-xp 00000000 00:00 0          [vectors]

可以看看0x8000这个虚拟地址对应的物理地址:

1 [[email protected] ~]# ./translate 746 0x8000
2 Big endian? 0
3 Vaddr: 0x8000, Page_size: 4096, Entry_size: 8
4 Reading /proc/746/pagemap at 0x40
5 [0]0x0 [1]0xf8 [2]0x9 [3]0x0 [4]0x0 [5]0x0 [6]0x0 [7]0xa0
6 Result: 0xa00000000009f800
7 PFN: 0x9f800 (0x9f800000)

可以看到, 对应的物理页帧是0x9F800,那么物理地址就是0x9F800000.

下面我们再做一个实验, 进程746的地址空间有一部分用来映射libc:

1 b6dd3000-b6f06000 r-xp 00000000 b3:01 170        /lib/libc-2.18.so
2 b6f06000-b6f0d000 ---p 00133000 b3:01 170        /lib/libc-2.18.so
3 b6f0d000-b6f0f000 r--p 00132000 b3:01 170        /lib/libc-2.18.so
4 b6f0f000-b6f10000 rw-p 00134000 b3:01 170        /lib/libc-2.18.so

此外, 进程835也会用到libc:

1 [[email protected] ~]# cat /proc/835/maps
2 ... ...
3 b6e0b000-b6f3e000 r-xp 00000000 b3:01 170        /lib/libc-2.18.so
4 b6f3e000-b6f45000 ---p 00133000 b3:01 170        /lib/libc-2.18.so
5 b6f45000-b6f47000 r--p 00132000 b3:01 170        /lib/libc-2.18.so
6 b6f47000-b6f48000 rw-p 00134000 b3:01 170        /lib/libc-2.18.so
7 ... ...

可以看到, 进程746和835虽然都用了libc,但是对应的虚拟地址却不同,前者是0xb6dd3000, 而后者是0xb6e0b000, 我们知道对于共享库, 在内存只会存在一份, 那么物理地址也就是唯一的, 那么进程746的虚拟地址空间的0xb6dd3000跟进程835的虚拟地址空间的0xb6e0b000对应的物理地址应该是同一个, 下面验证一下:

进程746:

1 [[email protected] ~]# ./translate 746 0xb6dd3000
2 virt_addr: 0xb6dd3000
3 Big endian? 0
4 Vaddr: 0xb6dd3000, Page_size: 4096, Entry_size: 8
5 Reading /proc/746/pagemap at 0x5b6e98
6 [0]0x68 [1]0xfa [2]0x9 [3]0x0 [4]0x0 [5]0x0 [6]0x0 [7]0xa0
7 Result: 0xa00000000009fa68
8 PFN: 0x9fa68 (0x9fa68000)

可以看到,物理地址是0x9FA68000

进程835:

1 [[email protected] ~]# ./translate 835 0xb6e0b000
2 virt_addr: 0xb6e0b000
3 Big endian? 0
4 Vaddr: 0xb6e0b000, Page_size: 4096, Entry_size: 8
5 Reading /proc/835/pagemap at 0x5b7058
6 [0]0x68 [1]0xfa [2]0x9 [3]0x0 [4]0x0 [5]0x0 [6]0x0 [7]0xa0
7 Result: 0xa00000000009fa68
8 PFN: 0x9fa68 (0x9fa68000)

可以看到, 物理地址也是0x9FA68000, 从而证明了我们的猜想。

完。

时间: 2024-08-24 04:54:09

利用/proc/pid/pagemap将虚拟地址转换为物理地址的相关文章

虚拟地址转换为物理地址【转】

转自:https://blog.csdn.net/shuningzhang/article/details/38090621 应用程序只能提供一个虚拟地址,也可以通过如下方法获取物理地址,当然得调用驱动. Linux采用页表的概念来管理虚拟空间,内核在处理虚拟地址时都必须将其转换为物理地址,然后处理器才能够访问.虚拟地址可以通过Linux的页表操作宏逐层查找到物理地址,简单来说需要将虚拟地址分段,每段地址都作为索引指向页表,最后一级页表指向物理地址. Linux在2.6.11以后版本为了兼容各种

自己学驱动13——内存管理单元MMU(虚拟地址和物理地址)

1.MMU简介 MMU负责完成虚拟地址到物理地址的映射,并提供硬件机制的内存访问权限检查.现代的多用户多进程操作系统通过MMU使得各个用户进程都拥有自己独立的地址空间:地址映射功能使得各个进程拥有"看起来"一样的地址空间,而内存访问权限的检查可以保护每个进程所使用的内存不会被其他进程所破坏.MMU增加了底层的复杂性,但是为上层程序开发提供了极大的方便. 2.虚拟地址与物理地址 虚拟地址最终需要转换为物理地址才能读写实际的数据,这通过将虚拟地址空间.物理地址空间划分为同样大小的一块块空间

cpu为什么使用虚拟地址到物理地址的空间映射,解决了什么样的问题?

当处理器读或写入内存位置时,它会使用虚拟地址.作为读或写操作的一部分,处理器将虚拟地址转换为物理地址.通过虚拟地址访问内存有以下优势: 程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区. 程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区.当物理内存的供应量变小时,内存管理器会将物理内存页(通常大小为 4 KB)保存到磁盘文件.数据或代码页会根据需要在物理内存与磁盘之间移动. 不同进程使用的虚拟地址彼此隔离.一个进程中的代码无法更改正在由另一进程使用的物理内存.

X86下逻辑地址、线性地址、虚拟地址和物理地址的理解

参考:http://bbs.chinaunix.net/thread-2083672-1-1.html 本贴涉及的硬件平台是X86,如果是其它平台,不保证能一一对号入座,但是举一反三,我想是完全可行的. 一.概念 物理地址(physical address) 用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应. --这个概念应该是这几个概念中最好理解的一个,但是值得一提的是,虽然可以直接把物理地址理解成插在机器上那根内存本身,把内存看成一个从0字节一直到最大空量逐字节的编号的大数组,

虚拟存储器--虚拟地址与物理地址

计算机在运行程序时,需将代码加载入内存中,CPU读取内存中的代码并执行. 早期的计算机在没有引入 虚拟存储器之前,需将整个待运行的程序加载到内存中,因为内存空间有限,当待加载的程序过大时就会出现问题(多进程,则需要占用更多的内存空间). 现代计算机引入虚拟存储器的概念,通过将对内存进行抽象,将其作为存储在硬盘上数据的高速缓存,只将当前进程部分代码缓存到主存中(当前进程的程序较少时,可以全部缓存在主存中),从而提高了主存的利用率,使其同时可以容纳更多的进程同时运行. 简述虚拟存储器是一个抽象概念,

Linux 虚拟地址与物理地址的映射关系分析

Ordeder原创文章,原文链接: http://blog.csdn.net/ordeder/article/details/41630945 源码版本 2.4.0 1. 虚拟空间 0-3G 用户空间  0x00000000  ~ 0xbfffffff 3-4G 内核空间     0xc0000000 ~ 0xffffffff 每个用户进程都有独立的用户空间(虚拟地址0-3),而内核空间是唯一的(相当于共享) 每个进程的用户空间用mm_struct描述,即task_struct.mm. 2.进程

[置顶] Linux 虚拟地址与物理地址的映射关系分析【转】

转自:http://blog.csdn.net/ordeder/article/details/41630945 版权声明:本文为博主(http://blog.csdn.net/ordeder)原创文章,未经博主允许不得转载. 目录(?)[-] 虚拟空间 进程虚拟地址的组织 1 虚拟空间用户空间 2 内存区间 系统物理地址的组织 1 用户空间页面目录映射关系 2用户空间的映射 3内核空间虚拟地址的映射 相关数据结构关系图 Ordeder原创文章,原文链接: http://blog.csdn.ne

Linux /proc/pid目录下相应文件的信息说明和含义

Proc是一个虚拟文件系统,在Linux系统中它被挂载于/proc目录之上.Proc有多个功能 ,这其中包括用户可以通过它访问内核信息或用于排错,这其中一个非常有 用的功能,也是Linux变得更加特别的功能就是以文本流的形式来访问进程信息.很Linux命令(比如 ps.toPpstree等)都需要使用这个文件系统的信息.注意,本文就是向用户介绍一些访问这些信息的方法 .需要说明的是,本文所述的内容并不 一定适用所有内核版本,有部分操作只适用于2.6内核.一.进程信息在/proc文件系统中,每一个

netstat统计的tcp连接数与?proc?pid?fd下socket类型fd数量不一致的分析

最近,线上一个应用,发现socket数缓慢增长,并且不回收,超过警告线之后,被运维监控自动重启了. 首先到zabbix上观察JVM历史记录,发现JVM-Perm space最近两周没有数据,猜测是程序从JDK7切换到JDK8了.问过开发人员之后,程序已经很久没有重启了,最近才重新发布的.而在这期间,线上的Java运行环境已经从JDK7升级到JDK8了. 因为jdk8里没有Perm space了,换成了Metaspace. ###netstat到线上服务器上,用netstat来统计进程的conne