diff --git a/kernel_module/mmap.c b/kernel_module/mmap.c index 188355c..f000f0b 100644 --- a/kernel_module/mmap.c +++ b/kernel_module/mmap.c @@ -74,7 +74,7 @@ static int open(struct inode *inode, struct file *filp) pr_info("open\n"); info = kmalloc(sizeof(struct mmap_info), GFP_KERNEL); - pr_info("virt_to_phys = 0x%llx\n", (unsigned long long)virt_to_phys((void *)info->data)); + pr_info("virt_to_phys = 0x%llx\n", (unsigned long long)virt_to_phys((void *)info)); info->data = (char *)get_zeroed_page(GFP_KERNEL); memcpy(info->data, "asdf", BUFFER_SIZE); filp->private_data = info; diff --git a/kernel_module/user/common.h b/kernel_module/user/common.h new file mode 100644 index 0000000..3d4c27f --- /dev/null +++ b/kernel_module/user/common.h @@ -0,0 +1,74 @@ +#ifndef COMMON_H +#define COMMON_H + +#define _POSIX_C_SOURCE 200809L +#include /* open */ +#include /* uint64_t */ +#include /* size_t */ +#include /* pread, sysconf */ + +typedef struct { + uint64_t pfn : 54; + unsigned int soft_dirty : 1; + unsigned int file_page : 1; + unsigned int swapped : 1; + unsigned int present : 1; +} PagemapEntry; + +/* Parse the pagemap entry for the given virtual address. + * + * @param[out] entry the parsed entry + * @param[in] pagemap_fd file descriptor to an open /proc/pid/pagemap file + * @param[in] vaddr virtual address to get entry for + * @return 0 for success, 1 for failure + */ +int pagemap_get_entry(PagemapEntry *entry, int pagemap_fd, uintptr_t vaddr) +{ + size_t nread; + ssize_t ret; + uint64_t data; + + nread = 0; + while (nread < sizeof(data)) { + ret = pread(pagemap_fd, &data, sizeof(data), + (vaddr / sysconf(_SC_PAGE_SIZE)) * sizeof(data) + nread); + nread += ret; + if (ret <= 0) { + return 1; + } + } + entry->pfn = data & (((uint64_t)1 << 54) - 1); + entry->soft_dirty = (data >> 54) & 1; + entry->file_page = (data >> 61) & 1; + entry->swapped = (data >> 62) & 1; + entry->present = (data >> 63) & 1; + return 0; +} + +/* Convert the given virtual address to physical using /proc/PID/pagemap. + * + * @param[out] paddr physical address + * @param[in] pid process to convert for + * @param[in] vaddr virtual address to get entry for + * @return 0 for success, 1 for failure + */ +int virt_to_phys_user(uintptr_t *paddr, pid_t pid, uintptr_t vaddr) +{ + char pagemap_file[BUFSIZ]; + int pagemap_fd; + + snprintf(pagemap_file, sizeof(pagemap_file), "/proc/%ju/pagemap", (uintmax_t)pid); + pagemap_fd = open(pagemap_file, O_RDONLY); + if (pagemap_fd < 0) { + return 1; + } + PagemapEntry entry; + if (pagemap_get_entry(&entry, pagemap_fd, vaddr)) { + return 1; + } + close(pagemap_fd); + *paddr = (entry.pfn * sysconf(_SC_PAGE_SIZE)) + (vaddr % sysconf(_SC_PAGE_SIZE)); + return 0; +} + +#endif diff --git a/kernel_module/user/mmap.c b/kernel_module/user/mmap.c index fe3979f..7577277 100644 --- a/kernel_module/user/mmap.c +++ b/kernel_module/user/mmap.c @@ -2,10 +2,13 @@ #include #include #include +#include /* uintmax_t */ #include #include #include /* sysconf */ +#include "common.h" /* virt_to_phys_user */ + enum { BUFFER_SIZE = 4 }; int main(int argc, char **argv) @@ -14,6 +17,7 @@ int main(int argc, char **argv) long page_size; char *address1, *address2; char buf[BUFFER_SIZE]; + uintptr_t paddr; if (argc < 2) { printf("Usage: %s \n", argv[0]); @@ -54,6 +58,13 @@ int main(int argc, char **argv) /* Also modified. So both virtual addresses point to the same physical address. */ assert(!strcmp(address2, "qwer")); + /* Check that the physical addresses are the same. + * They are, but TODO why virt_to_phys on kernel gives a different value? */ + assert(!virt_to_phys_user(&paddr, getpid(), (uintptr_t)address1)); + printf("paddr1 = 0x%jx\n", (uintmax_t)paddr); + assert(!virt_to_phys_user(&paddr, getpid(), (uintptr_t)address2)); + printf("paddr2 = 0x%jx\n", (uintmax_t)paddr); + /* Check that modifications made from userland are also visible from the kernel. */ read(fd, buf, BUFFER_SIZE); assert(!memcmp(buf, "qwer", BUFFER_SIZE)); diff --git a/kernel_module/user/pagemap_dump.c b/kernel_module/user/pagemap_dump.c index b5b1c24..9fd66a9 100644 --- a/kernel_module/user/pagemap_dump.c +++ b/kernel_module/user/pagemap_dump.c @@ -3,7 +3,8 @@ Only tested in x86_64. Adapted from: https://github.com/dwks/pagemap/blob/8a25747bc79d6080c8b94eac80807a4dceeda57a/pagemap2.c -https://stackoverflow.com/questions/17021214/how-to-decode-proc-pid-pagemap-entries-in-linux/45126141#45126141 +- https://stackoverflow.com/questions/17021214/how-to-decode-proc-pid-pagemap-entries-in-linux/45126141#45126141 +- https://stackoverflow.com/questions/5748492/is-there-any-api-for-determining-the-physical-address-from-virtual-address-in-li Dump the page map of a given process PID. @@ -13,106 +14,79 @@ Data sources: /proc/PIC/{map,pagemap} #define _POSIX_C_SOURCE 200809L #include #include -#include +#include #include #include #include #include -typedef struct { - uint64_t phys : 54; - int soft_dirty : 1; - int padding : 4; - int padding : 1; - - * Bit 61 page is file-page or shared-anon (since 3.5) - * Bit 62 page swapped - * Bit 63 page present -} PagemapEntry; - - -int parse_pagemap(PagemapEntry &entry, int fd, size_t offset) -{ - if (pread(pagemap, &data, ) != sizeof(data)) { - perror("pread"); - break; - } - - data & 0x7fffffffffffff, - (data >> 55) & 1, - (data >> 61) & 1, - (data >> 62) & 1, - (data >> 63) & 1, - -} +#include "common.h" /* pagemap_get_entry */ int main(int argc, char **argv) { char buffer[BUFSIZ]; char maps_file[BUFSIZ]; char pagemap_file[BUFSIZ]; - int maps; + int maps_fd; int offset = 0; - int pagemap; - long page_size; + int pagemap_fd; + pid_t pid; if (argc < 2) { printf("Usage: %s pid\n", argv[0]); return EXIT_FAILURE; } - pid_t pid = (pid_t)strtoul(argv[1], NULL, 0); - snprintf(maps_file, sizeof(maps_file), "/proc/%lu/maps", (unsigned long)pid); - snprintf(pagemap_file, sizeof(pagemap_file), "/proc/%lu/pagemap", (unsigned long)pid); - maps = open(maps_file, O_RDONLY); - if (maps < 0) { + pid = strtoull(argv[1], NULL, 0); + snprintf(maps_file, sizeof(maps_file), "/proc/%ju/maps", (uintmax_t)pid); + snprintf(pagemap_file, sizeof(pagemap_file), "/proc/%ju/pagemap", (uintmax_t)pid); + maps_fd = open(maps_file, O_RDONLY); + if (maps_fd < 0) { perror("open maps"); return EXIT_FAILURE; } - pagemap = open(pagemap_file, O_RDONLY); - if (pagemap < 0) { + pagemap_fd = open(pagemap_file, O_RDONLY); + if (pagemap_fd < 0) { perror("open pagemap"); return EXIT_FAILURE; } - page_size = sysconf(_SC_PAGE_SIZE); printf("addr pfn soft-dirty file/shared swapped present library\n"); for (;;) { - ssize_t length = read(maps, buffer + offset, sizeof buffer - offset); + ssize_t length = read(maps_fd, buffer + offset, sizeof buffer - offset); if (length <= 0) break; length += offset; for (size_t i = offset; i < (size_t)length; i++) { - unsigned long low = 0, high = 0; + uintptr_t low = 0, high = 0; if (buffer[i] == '\n' && i) { const char *lib_name; size_t y; /* Parse a line from maps. Each line contains a range that contains many pages. */ { size_t x = i - 1; - while(x && buffer[x] != '\n') x --; + while (x && buffer[x] != '\n') x--; if (buffer[x] == '\n') x++; size_t beginning = x; - while(buffer[x] != '-' && x < sizeof buffer) { + while (buffer[x] != '-' && x < sizeof buffer) { char c = buffer[x++]; low *= 16; if (c >= '0' && c <= '9') { low += c - '0'; - } - else if (c >= 'a' && c <= 'f') { + } else if (c >= 'a' && c <= 'f') { low += c - 'a' + 10; } else { break; } } - while(buffer[x] != '-' && x < sizeof buffer) x++; + while (buffer[x] != '-' && x < sizeof buffer) x++; if (buffer[x] == '-') x++; - while(buffer[x] != ' ' && x < sizeof buffer) { + while (buffer[x] != ' ' && x < sizeof buffer) { char c = buffer[x++]; high *= 16; if (c >= '0' && c <= '9') { high += c - '0'; - } - else if (c >= 'a' && c <= 'f') { + } else if (c >= 'a' && c <= 'f') { high += c - 'a' + 10; + } else { + break; } - else break; } lib_name = 0; for (int field = 0; field < 4; field++) { @@ -127,27 +101,27 @@ int main(int argc, char **argv) { } /* Get info about all pages in this page range with pagemap. */ { - unsigned long data; PagemapEntry entry; - for (unsigned long i = low; i < high; i += page_size) { - unsigned long index = (i / page_size) * sizeof(data); - parse_pagemap(&entry, pagemap, sizeof(data) * index); - printf("%lx %lx %d %d %d %d %s\n", - i, - data & 0x7fffffffffffff, - (data >> 55) & 1, - (data >> 61) & 1, - (data >> 62) & 1, - (data >> 63) & 1, - lib_name - ); + for (uintptr_t addr = low; addr < high; addr += sysconf(_SC_PAGE_SIZE)) { + /* TODO always fails for the last page (vsyscall), why? pread returns 0. */ + if (!pagemap_get_entry(&entry, pagemap_fd, addr)) { + printf("%jx %jx %u %u %u %u %s\n", + (uintmax_t)addr, + (uintmax_t)entry.pfn, + entry.soft_dirty, + entry.file_page, + entry.swapped, + entry.present, + lib_name + ); + } } } buffer[y] = '\n'; } } } - close(maps); - close(pagemap); - return 0; + close(maps_fd); + close(pagemap_fd); + return EXIT_SUCCESS; } diff --git a/kernel_module/user/virt_to_phys_user.c b/kernel_module/user/virt_to_phys_user.c new file mode 100644 index 0000000..a082532 --- /dev/null +++ b/kernel_module/user/virt_to_phys_user.c @@ -0,0 +1,29 @@ +/* +Convert a virtual address to physical for a given process PID using /proc/PID/pagemap. + +https://stackoverflow.com/questions/5748492/is-there-any-api-for-determining-the-physical-address-from-virtual-address-in-li/45128487#45128487 +*/ + +#include /* printf */ +#include /* EXIT_SUCCESS, EXIT_FAILURE, strtoull */ + +#include "common.h" /* virt_to_phys_user */ + +int main(int argc, char **argv) +{ + pid_t pid; + uintptr_t vaddr, paddr; + + if (argc < 3) { + printf("Usage: %s pid vaddr\n", argv[0]); + return EXIT_FAILURE; + } + pid = strtoull(argv[1], NULL, 0); + vaddr = strtoull(argv[2], NULL, 0); + if (virt_to_phys_user(&paddr, pid, vaddr)) { + fprintf(stderr, "error: virt_to_phys_user\n"); + return EXIT_FAILURE; + }; + printf("0x%jx\n", (uintmax_t)paddr); + return EXIT_SUCCESS; +}