本文主要记录/proc/pid/pagemap 相关notes,最权威的材料pagemap, from the userspace perspective

In newer kernels there is a really nice virtual file in the /proc file system to get this information.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <stdint.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;
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!\n pagemap PID VIRTUAL_ADDRESS\n");
return -1;
}
if(!memcmp(argv[1],"self",sizeof("self"))){
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 = strtol(argv[2], NULL, 16);
if(pid!=-1)
sprintf(path_buf, "/proc/%u/pagemap", pid);

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;
}

//Shifting by virt-addr-offset number of bytes
//and multiplying by the size of an address (the size of an entry in pagemap file)
file_offset = virt_addr / getpagesize() * PAGEMAP_ENTRY;
printf("Vaddr: 0x%lx, Page_size: %d, Entry_size: %d\n", virt_addr, getpagesize(), 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);
if(GET_BIT(read_val, 63))
printf("PFN: 0x%llx\n",(unsigned long long) GET_PFN(read_val));
else
printf("Page not present\n");
if(GET_BIT(read_val, 62))
printf("Page swapped\n");
fclose(f);
return 0;
}

And now how you use it. It’s very simple. Of course you need to compile it. Then you need to find out what mapping your target process does have. You can do that by reading /proc/pid/maps file. Fortunately that file is human readable.

When you know a valid virtual address, you can pass it to our tool to get actual value from pagemap, including physical frame number. Here is an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$ sudo su
$ # disable aslr
$ echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
$
$ cat /proc/self/maps
555555554000-55555555c000 r-xp 00000000 103:02 14286873 /bin/cat
55555575b000-55555575c000 r--p 00007000 103:02 14286873 /bin/cat
55555575c000-55555575d000 rw-p 00008000 103:02 14286873 /bin/cat
55555575d000-55555577e000 rw-p 00000000 00:00 0 [heap]
7ffff7013000-7ffff79e2000 r--p 00000000 103:02 10623634 /usr/lib/locale/locale-archive
7ffff79e2000-7ffff7bc9000 r-xp 00000000 103:02 4980818 /lib/x86_64-linux-gnu/libc-2.27.so
7ffff7bc9000-7ffff7dc9000 ---p 001e7000 103:02 4980818 /lib/x86_64-linux-gnu/libc-2.27.so
7ffff7dc9000-7ffff7dcd000 r--p 001e7000 103:02 4980818 /lib/x86_64-linux-gnu/libc-2.27.so
7ffff7dcd000-7ffff7dcf000 rw-p 001eb000 103:02 4980818 /lib/x86_64-linux-gnu/libc-2.27.so
7ffff7dcf000-7ffff7dd3000 rw-p 00000000 00:00 0
7ffff7dd3000-7ffff7dfc000 r-xp 00000000 103:02 4980742 /lib/x86_64-linux-gnu/ld-2.27.so
7ffff7fc0000-7ffff7fe4000 rw-p 00000000 00:00 0
7ffff7ff8000-7ffff7ffb000 r--p 00000000 00:00 0 [vvar]
7ffff7ffb000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso]
7ffff7ffc000-7ffff7ffd000 r--p 00029000 103:02 4980742 /lib/x86_64-linux-gnu/ld-2.27.so
7ffff7ffd000-7ffff7ffe000 rw-p 0002a000 103:02 4980742 /lib/x86_64-linux-gnu/ld-2.27.so
7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0
7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
$ #so let's pick 0x555555554000. Now we run our program.
$ #First argument is pid, "self" is a legal option too, the second is virtual address
$ ./pagemap self 0x555555554000
Big endian? 0
Vaddr: 0x555555554000, Page_size: 4096, Entry_size: 8
Reading /proc/self/pagemap at 0x2aaaaaaaa0
[0]0x68 [1]0x65 [2]0x1e [3]0x0 [4]0x0 [5]0x0 [6]0x80 [7]0xa1
Result: 0xa1800000001e6568
PFN: 0x1e6568

We got 0x1e6568 as a result. There are some bits showing that the page is valid, along with the size of the page. You can read more in Linux documentation: pagemap, from the userspace perspective. Basically, the physical page number is 0x1e6568.

The kernel implementation for /proc/pid/pagemap: pagemap_read


参考资料:

  1. pagemap, from the userspace perspective
  2. How to translate virtual to physical addresses through /proc/pid/pagemap
  3. How can I temporarily disable ASLR (Address space layout randomization)?