| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Written by Dave Hansen <dave.hansen@intel.com> | |
| */ | |
| #include <stdlib.h> | |
| #include <sys/types.h> | |
| #include <unistd.h> | |
| #include <stdio.h> | |
| #include <errno.h> | |
| #include <sys/types.h> | |
| #include <sys/stat.h> | |
| #include <unistd.h> | |
| #include <sys/mman.h> | |
| #include <string.h> | |
| #include <fcntl.h> | |
| #include "mpx-debug.h" | |
| #include "mpx-mm.h" | |
| #include "mpx-hw.h" | |
| unsigned long bounds_dir_global; | |
| #define mpx_dig_abort() __mpx_dig_abort(__FILE__, __func__, __LINE__) | |
| static void inline __mpx_dig_abort(const char *file, const char *func, int line) | |
| { | |
| fprintf(stderr, "MPX dig abort @ %s::%d in %s()\n", file, line, func); | |
| printf("MPX dig abort @ %s::%d in %s()\n", file, line, func); | |
| abort(); | |
| } | |
| /* | |
| * run like this (BDIR finds the probably bounds directory): | |
| * | |
| * BDIR="$(cat /proc/$pid/smaps | grep -B1 2097152 \ | |
| * | head -1 | awk -F- '{print $1}')"; | |
| * ./mpx-dig $pid 0x$BDIR | |
| * | |
| * NOTE: | |
| * assumes that the only 2097152-kb VMA is the bounds dir | |
| */ | |
| long nr_incore(void *ptr, unsigned long size_bytes) | |
| { | |
| int i; | |
| long ret = 0; | |
| long vec_len = size_bytes / PAGE_SIZE; | |
| unsigned char *vec = malloc(vec_len); | |
| int incore_ret; | |
| if (!vec) | |
| mpx_dig_abort(); | |
| incore_ret = mincore(ptr, size_bytes, vec); | |
| if (incore_ret) { | |
| printf("mincore ret: %d\n", incore_ret); | |
| perror("mincore"); | |
| mpx_dig_abort(); | |
| } | |
| for (i = 0; i < vec_len; i++) | |
| ret += vec[i]; | |
| free(vec); | |
| return ret; | |
| } | |
| int open_proc(int pid, char *file) | |
| { | |
| static char buf[100]; | |
| int fd; | |
| snprintf(&buf[0], sizeof(buf), "/proc/%d/%s", pid, file); | |
| fd = open(&buf[0], O_RDONLY); | |
| if (fd < 0) | |
| perror(buf); | |
| return fd; | |
| } | |
| struct vaddr_range { | |
| unsigned long start; | |
| unsigned long end; | |
| }; | |
| struct vaddr_range *ranges; | |
| int nr_ranges_allocated; | |
| int nr_ranges_populated; | |
| int last_range = -1; | |
| int __pid_load_vaddrs(int pid) | |
| { | |
| int ret = 0; | |
| int proc_maps_fd = open_proc(pid, "maps"); | |
| char linebuf[10000]; | |
| unsigned long start; | |
| unsigned long end; | |
| char rest[1000]; | |
| FILE *f = fdopen(proc_maps_fd, "r"); | |
| if (!f) | |
| mpx_dig_abort(); | |
| nr_ranges_populated = 0; | |
| while (!feof(f)) { | |
| char *readret = fgets(linebuf, sizeof(linebuf), f); | |
| int parsed; | |
| if (readret == NULL) { | |
| if (feof(f)) | |
| break; | |
| mpx_dig_abort(); | |
| } | |
| parsed = sscanf(linebuf, "%lx-%lx%s", &start, &end, rest); | |
| if (parsed != 3) | |
| mpx_dig_abort(); | |
| dprintf4("result[%d]: %lx-%lx<->%s\n", parsed, start, end, rest); | |
| if (nr_ranges_populated >= nr_ranges_allocated) { | |
| ret = -E2BIG; | |
| break; | |
| } | |
| ranges[nr_ranges_populated].start = start; | |
| ranges[nr_ranges_populated].end = end; | |
| nr_ranges_populated++; | |
| } | |
| last_range = -1; | |
| fclose(f); | |
| close(proc_maps_fd); | |
| return ret; | |
| } | |
| int pid_load_vaddrs(int pid) | |
| { | |
| int ret; | |
| dprintf2("%s(%d)\n", __func__, pid); | |
| if (!ranges) { | |
| nr_ranges_allocated = 4; | |
| ranges = malloc(nr_ranges_allocated * sizeof(ranges[0])); | |
| dprintf2("%s(%d) allocated %d ranges @ %p\n", __func__, pid, | |
| nr_ranges_allocated, ranges); | |
| assert(ranges != NULL); | |
| } | |
| do { | |
| ret = __pid_load_vaddrs(pid); | |
| if (!ret) | |
| break; | |
| if (ret == -E2BIG) { | |
| dprintf2("%s(%d) need to realloc\n", __func__, pid); | |
| nr_ranges_allocated *= 2; | |
| ranges = realloc(ranges, | |
| nr_ranges_allocated * sizeof(ranges[0])); | |
| dprintf2("%s(%d) allocated %d ranges @ %p\n", __func__, | |
| pid, nr_ranges_allocated, ranges); | |
| assert(ranges != NULL); | |
| dprintf1("reallocating to hold %d ranges\n", nr_ranges_allocated); | |
| } | |
| } while (1); | |
| dprintf2("%s(%d) done\n", __func__, pid); | |
| return ret; | |
| } | |
| static inline int vaddr_in_range(unsigned long vaddr, struct vaddr_range *r) | |
| { | |
| if (vaddr < r->start) | |
| return 0; | |
| if (vaddr >= r->end) | |
| return 0; | |
| return 1; | |
| } | |
| static inline int vaddr_mapped_by_range(unsigned long vaddr) | |
| { | |
| int i; | |
| if (last_range > 0 && vaddr_in_range(vaddr, &ranges[last_range])) | |
| return 1; | |
| for (i = 0; i < nr_ranges_populated; i++) { | |
| struct vaddr_range *r = &ranges[i]; | |
| if (vaddr_in_range(vaddr, r)) | |
| continue; | |
| last_range = i; | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| const int bt_entry_size_bytes = sizeof(unsigned long) * 4; | |
| void *read_bounds_table_into_buf(unsigned long table_vaddr) | |
| { | |
| #ifdef MPX_DIG_STANDALONE | |
| static char bt_buf[MPX_BOUNDS_TABLE_SIZE_BYTES]; | |
| off_t seek_ret = lseek(fd, table_vaddr, SEEK_SET); | |
| if (seek_ret != table_vaddr) | |
| mpx_dig_abort(); | |
| int read_ret = read(fd, &bt_buf, sizeof(bt_buf)); | |
| if (read_ret != sizeof(bt_buf)) | |
| mpx_dig_abort(); | |
| return &bt_buf; | |
| #else | |
| return (void *)table_vaddr; | |
| #endif | |
| } | |
| int dump_table(unsigned long table_vaddr, unsigned long base_controlled_vaddr, | |
| unsigned long bde_vaddr) | |
| { | |
| unsigned long offset_inside_bt; | |
| int nr_entries = 0; | |
| int do_abort = 0; | |
| char *bt_buf; | |
| dprintf3("%s() base_controlled_vaddr: 0x%012lx bde_vaddr: 0x%012lx\n", | |
| __func__, base_controlled_vaddr, bde_vaddr); | |
| bt_buf = read_bounds_table_into_buf(table_vaddr); | |
| dprintf4("%s() read done\n", __func__); | |
| for (offset_inside_bt = 0; | |
| offset_inside_bt < MPX_BOUNDS_TABLE_SIZE_BYTES; | |
| offset_inside_bt += bt_entry_size_bytes) { | |
| unsigned long bt_entry_index; | |
| unsigned long bt_entry_controls; | |
| unsigned long this_bt_entry_for_vaddr; | |
| unsigned long *bt_entry_buf; | |
| int i; | |
| dprintf4("%s() offset_inside_bt: 0x%lx of 0x%llx\n", __func__, | |
| offset_inside_bt, MPX_BOUNDS_TABLE_SIZE_BYTES); | |
| bt_entry_buf = (void *)&bt_buf[offset_inside_bt]; | |
| if (!bt_buf) { | |
| printf("null bt_buf\n"); | |
| mpx_dig_abort(); | |
| } | |
| if (!bt_entry_buf) { | |
| printf("null bt_entry_buf\n"); | |
| mpx_dig_abort(); | |
| } | |
| dprintf4("%s() reading *bt_entry_buf @ %p\n", __func__, | |
| bt_entry_buf); | |
| if (!bt_entry_buf[0] && | |
| !bt_entry_buf[1] && | |
| !bt_entry_buf[2] && | |
| !bt_entry_buf[3]) | |
| continue; | |
| nr_entries++; | |
| bt_entry_index = offset_inside_bt/bt_entry_size_bytes; | |
| bt_entry_controls = sizeof(void *); | |
| this_bt_entry_for_vaddr = | |
| base_controlled_vaddr + bt_entry_index*bt_entry_controls; | |
| /* | |
| * We sign extend vaddr bits 48->63 which effectively | |
| * creates a hole in the virtual address space. | |
| * This calculation corrects for the hole. | |
| */ | |
| if (this_bt_entry_for_vaddr > 0x00007fffffffffffUL) | |
| this_bt_entry_for_vaddr |= 0xffff800000000000; | |
| if (!vaddr_mapped_by_range(this_bt_entry_for_vaddr)) { | |
| printf("bt_entry_buf: %p\n", bt_entry_buf); | |
| printf("there is a bte for %lx but no mapping\n", | |
| this_bt_entry_for_vaddr); | |
| printf(" bde vaddr: %016lx\n", bde_vaddr); | |
| printf("base_controlled_vaddr: %016lx\n", base_controlled_vaddr); | |
| printf(" table_vaddr: %016lx\n", table_vaddr); | |
| printf(" entry vaddr: %016lx @ offset %lx\n", | |
| table_vaddr + offset_inside_bt, offset_inside_bt); | |
| do_abort = 1; | |
| mpx_dig_abort(); | |
| } | |
| if (DEBUG_LEVEL < 4) | |
| continue; | |
| printf("table entry[%lx]: ", offset_inside_bt); | |
| for (i = 0; i < bt_entry_size_bytes; i += sizeof(unsigned long)) | |
| printf("0x%016lx ", bt_entry_buf[i]); | |
| printf("\n"); | |
| } | |
| if (do_abort) | |
| mpx_dig_abort(); | |
| dprintf4("%s() done\n", __func__); | |
| return nr_entries; | |
| } | |
| int search_bd_buf(char *buf, int len_bytes, unsigned long bd_offset_bytes, | |
| int *nr_populated_bdes) | |
| { | |
| unsigned long i; | |
| int total_entries = 0; | |
| dprintf3("%s(%p, %x, %lx, ...) buf end: %p\n", __func__, buf, | |
| len_bytes, bd_offset_bytes, buf + len_bytes); | |
| for (i = 0; i < len_bytes; i += sizeof(unsigned long)) { | |
| unsigned long bd_index = (bd_offset_bytes + i) / sizeof(unsigned long); | |
| unsigned long *bounds_dir_entry_ptr = (unsigned long *)&buf[i]; | |
| unsigned long bounds_dir_entry; | |
| unsigned long bd_for_vaddr; | |
| unsigned long bt_start; | |
| unsigned long bt_tail; | |
| int nr_entries; | |
| dprintf4("%s() loop i: %ld bounds_dir_entry_ptr: %p\n", __func__, i, | |
| bounds_dir_entry_ptr); | |
| bounds_dir_entry = *bounds_dir_entry_ptr; | |
| if (!bounds_dir_entry) { | |
| dprintf4("no bounds dir at index 0x%lx / 0x%lx " | |
| "start at offset:%lx %lx\n", bd_index, bd_index, | |
| bd_offset_bytes, i); | |
| continue; | |
| } | |
| dprintf3("found bounds_dir_entry: 0x%lx @ " | |
| "index 0x%lx buf ptr: %p\n", bounds_dir_entry, i, | |
| &buf[i]); | |
| /* mask off the enable bit: */ | |
| bounds_dir_entry &= ~0x1; | |
| (*nr_populated_bdes)++; | |
| dprintf4("nr_populated_bdes: %p\n", nr_populated_bdes); | |
| dprintf4("*nr_populated_bdes: %d\n", *nr_populated_bdes); | |
| bt_start = bounds_dir_entry; | |
| bt_tail = bounds_dir_entry + MPX_BOUNDS_TABLE_SIZE_BYTES - 1; | |
| if (!vaddr_mapped_by_range(bt_start)) { | |
| printf("bounds directory 0x%lx points to nowhere\n", | |
| bounds_dir_entry); | |
| mpx_dig_abort(); | |
| } | |
| if (!vaddr_mapped_by_range(bt_tail)) { | |
| printf("bounds directory end 0x%lx points to nowhere\n", | |
| bt_tail); | |
| mpx_dig_abort(); | |
| } | |
| /* | |
| * Each bounds directory entry controls 1MB of virtual address | |
| * space. This variable is the virtual address in the process | |
| * of the beginning of the area controlled by this bounds_dir. | |
| */ | |
| bd_for_vaddr = bd_index * (1UL<<20); | |
| nr_entries = dump_table(bounds_dir_entry, bd_for_vaddr, | |
| bounds_dir_global+bd_offset_bytes+i); | |
| total_entries += nr_entries; | |
| dprintf5("dir entry[%4ld @ %p]: 0x%lx %6d entries " | |
| "total this buf: %7d bd_for_vaddrs: 0x%lx -> 0x%lx\n", | |
| bd_index, buf+i, | |
| bounds_dir_entry, nr_entries, total_entries, | |
| bd_for_vaddr, bd_for_vaddr + (1UL<<20)); | |
| } | |
| dprintf3("%s(%p, %x, %lx, ...) done\n", __func__, buf, len_bytes, | |
| bd_offset_bytes); | |
| return total_entries; | |
| } | |
| int proc_pid_mem_fd = -1; | |
| void *fill_bounds_dir_buf_other(long byte_offset_inside_bounds_dir, | |
| long buffer_size_bytes, void *buffer) | |
| { | |
| unsigned long seekto = bounds_dir_global + byte_offset_inside_bounds_dir; | |
| int read_ret; | |
| off_t seek_ret = lseek(proc_pid_mem_fd, seekto, SEEK_SET); | |
| if (seek_ret != seekto) | |
| mpx_dig_abort(); | |
| read_ret = read(proc_pid_mem_fd, buffer, buffer_size_bytes); | |
| /* there shouldn't practically be short reads of /proc/$pid/mem */ | |
| if (read_ret != buffer_size_bytes) | |
| mpx_dig_abort(); | |
| return buffer; | |
| } | |
| void *fill_bounds_dir_buf_self(long byte_offset_inside_bounds_dir, | |
| long buffer_size_bytes, void *buffer) | |
| { | |
| unsigned char vec[buffer_size_bytes / PAGE_SIZE]; | |
| char *dig_bounds_dir_ptr = | |
| (void *)(bounds_dir_global + byte_offset_inside_bounds_dir); | |
| /* | |
| * use mincore() to quickly find the areas of the bounds directory | |
| * that have memory and thus will be worth scanning. | |
| */ | |
| int incore_ret; | |
| int incore = 0; | |
| int i; | |
| dprintf4("%s() dig_bounds_dir_ptr: %p\n", __func__, dig_bounds_dir_ptr); | |
| incore_ret = mincore(dig_bounds_dir_ptr, buffer_size_bytes, &vec[0]); | |
| if (incore_ret) { | |
| printf("mincore ret: %d\n", incore_ret); | |
| perror("mincore"); | |
| mpx_dig_abort(); | |
| } | |
| for (i = 0; i < sizeof(vec); i++) | |
| incore += vec[i]; | |
| dprintf4("%s() total incore: %d\n", __func__, incore); | |
| if (!incore) | |
| return NULL; | |
| dprintf3("%s() total incore: %d\n", __func__, incore); | |
| return dig_bounds_dir_ptr; | |
| } | |
| int inspect_pid(int pid) | |
| { | |
| static int dig_nr; | |
| long offset_inside_bounds_dir; | |
| char bounds_dir_buf[sizeof(unsigned long) * (1UL << 15)]; | |
| char *dig_bounds_dir_ptr; | |
| int total_entries = 0; | |
| int nr_populated_bdes = 0; | |
| int inspect_self; | |
| if (getpid() == pid) { | |
| dprintf4("inspecting self\n"); | |
| inspect_self = 1; | |
| } else { | |
| dprintf4("inspecting pid %d\n", pid); | |
| mpx_dig_abort(); | |
| } | |
| for (offset_inside_bounds_dir = 0; | |
| offset_inside_bounds_dir < MPX_BOUNDS_TABLE_SIZE_BYTES; | |
| offset_inside_bounds_dir += sizeof(bounds_dir_buf)) { | |
| static int bufs_skipped; | |
| int this_entries; | |
| if (inspect_self) { | |
| dig_bounds_dir_ptr = | |
| fill_bounds_dir_buf_self(offset_inside_bounds_dir, | |
| sizeof(bounds_dir_buf), | |
| &bounds_dir_buf[0]); | |
| } else { | |
| dig_bounds_dir_ptr = | |
| fill_bounds_dir_buf_other(offset_inside_bounds_dir, | |
| sizeof(bounds_dir_buf), | |
| &bounds_dir_buf[0]); | |
| } | |
| if (!dig_bounds_dir_ptr) { | |
| bufs_skipped++; | |
| continue; | |
| } | |
| this_entries = search_bd_buf(dig_bounds_dir_ptr, | |
| sizeof(bounds_dir_buf), | |
| offset_inside_bounds_dir, | |
| &nr_populated_bdes); | |
| total_entries += this_entries; | |
| } | |
| printf("mpx dig (%3d) complete, SUCCESS (%8d / %4d)\n", ++dig_nr, | |
| total_entries, nr_populated_bdes); | |
| return total_entries + nr_populated_bdes; | |
| } | |
| #ifdef MPX_DIG_REMOTE | |
| int main(int argc, char **argv) | |
| { | |
| int err; | |
| char *c; | |
| unsigned long bounds_dir_entry; | |
| int pid; | |
| printf("mpx-dig starting...\n"); | |
| err = sscanf(argv[1], "%d", &pid); | |
| printf("parsing: '%s', err: %d\n", argv[1], err); | |
| if (err != 1) | |
| mpx_dig_abort(); | |
| err = sscanf(argv[2], "%lx", &bounds_dir_global); | |
| printf("parsing: '%s': %d\n", argv[2], err); | |
| if (err != 1) | |
| mpx_dig_abort(); | |
| proc_pid_mem_fd = open_proc(pid, "mem"); | |
| if (proc_pid_mem_fd < 0) | |
| mpx_dig_abort(); | |
| inspect_pid(pid); | |
| return 0; | |
| } | |
| #endif | |
| long inspect_me(struct mpx_bounds_dir *bounds_dir) | |
| { | |
| int pid = getpid(); | |
| pid_load_vaddrs(pid); | |
| bounds_dir_global = (unsigned long)bounds_dir; | |
| dprintf4("enter %s() bounds dir: %p\n", __func__, bounds_dir); | |
| return inspect_pid(pid); | |
| } |