天天看點

利用/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 [root@vexpress ~]# 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 [root@vexpress ~]# ./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 [root@vexpress ~]# 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, 我們知道對于共享庫, 在記憶體隻會存在一份代碼, 那麼實體位址也就是唯一的(代碼段是唯一的,所有調用libc的程序共享,而資料段每個程序一個), 那麼程序746的虛拟位址空間的0xb6dd3000(代碼段)跟程序835的虛拟位址空間的0xb6e0b000(代碼段)對應的實體位址應該是同一個, 下面驗證一下:

程序746:

1 [root@vexpress ~]# ./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 [root@vexpress ~]# ./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, 進而證明了我們的猜想。

完。