at v5.3 497 lines 13 kB view raw
1// SPDX-License-Identifier: GPL-2.0 2/* 3 * Written by Dave Hansen <dave.hansen@intel.com> 4 */ 5 6#include <stdlib.h> 7#include <sys/types.h> 8#include <unistd.h> 9#include <stdio.h> 10#include <errno.h> 11#include <sys/stat.h> 12#include <sys/mman.h> 13#include <string.h> 14#include <fcntl.h> 15#include "mpx-debug.h" 16#include "mpx-mm.h" 17#include "mpx-hw.h" 18 19unsigned long bounds_dir_global; 20 21#define mpx_dig_abort() __mpx_dig_abort(__FILE__, __func__, __LINE__) 22static void inline __mpx_dig_abort(const char *file, const char *func, int line) 23{ 24 fprintf(stderr, "MPX dig abort @ %s::%d in %s()\n", file, line, func); 25 printf("MPX dig abort @ %s::%d in %s()\n", file, line, func); 26 abort(); 27} 28 29/* 30 * run like this (BDIR finds the probably bounds directory): 31 * 32 * BDIR="$(cat /proc/$pid/smaps | grep -B1 2097152 \ 33 * | head -1 | awk -F- '{print $1}')"; 34 * ./mpx-dig $pid 0x$BDIR 35 * 36 * NOTE: 37 * assumes that the only 2097152-kb VMA is the bounds dir 38 */ 39 40long nr_incore(void *ptr, unsigned long size_bytes) 41{ 42 int i; 43 long ret = 0; 44 long vec_len = size_bytes / PAGE_SIZE; 45 unsigned char *vec = malloc(vec_len); 46 int incore_ret; 47 48 if (!vec) 49 mpx_dig_abort(); 50 51 incore_ret = mincore(ptr, size_bytes, vec); 52 if (incore_ret) { 53 printf("mincore ret: %d\n", incore_ret); 54 perror("mincore"); 55 mpx_dig_abort(); 56 } 57 for (i = 0; i < vec_len; i++) 58 ret += vec[i]; 59 free(vec); 60 return ret; 61} 62 63int open_proc(int pid, char *file) 64{ 65 static char buf[100]; 66 int fd; 67 68 snprintf(&buf[0], sizeof(buf), "/proc/%d/%s", pid, file); 69 fd = open(&buf[0], O_RDONLY); 70 if (fd < 0) 71 perror(buf); 72 73 return fd; 74} 75 76struct vaddr_range { 77 unsigned long start; 78 unsigned long end; 79}; 80struct vaddr_range *ranges; 81int nr_ranges_allocated; 82int nr_ranges_populated; 83int last_range = -1; 84 85int __pid_load_vaddrs(int pid) 86{ 87 int ret = 0; 88 int proc_maps_fd = open_proc(pid, "maps"); 89 char linebuf[10000]; 90 unsigned long start; 91 unsigned long end; 92 char rest[1000]; 93 FILE *f = fdopen(proc_maps_fd, "r"); 94 95 if (!f) 96 mpx_dig_abort(); 97 nr_ranges_populated = 0; 98 while (!feof(f)) { 99 char *readret = fgets(linebuf, sizeof(linebuf), f); 100 int parsed; 101 102 if (readret == NULL) { 103 if (feof(f)) 104 break; 105 mpx_dig_abort(); 106 } 107 108 parsed = sscanf(linebuf, "%lx-%lx%s", &start, &end, rest); 109 if (parsed != 3) 110 mpx_dig_abort(); 111 112 dprintf4("result[%d]: %lx-%lx<->%s\n", parsed, start, end, rest); 113 if (nr_ranges_populated >= nr_ranges_allocated) { 114 ret = -E2BIG; 115 break; 116 } 117 ranges[nr_ranges_populated].start = start; 118 ranges[nr_ranges_populated].end = end; 119 nr_ranges_populated++; 120 } 121 last_range = -1; 122 fclose(f); 123 close(proc_maps_fd); 124 return ret; 125} 126 127int pid_load_vaddrs(int pid) 128{ 129 int ret; 130 131 dprintf2("%s(%d)\n", __func__, pid); 132 if (!ranges) { 133 nr_ranges_allocated = 4; 134 ranges = malloc(nr_ranges_allocated * sizeof(ranges[0])); 135 dprintf2("%s(%d) allocated %d ranges @ %p\n", __func__, pid, 136 nr_ranges_allocated, ranges); 137 assert(ranges != NULL); 138 } 139 do { 140 ret = __pid_load_vaddrs(pid); 141 if (!ret) 142 break; 143 if (ret == -E2BIG) { 144 dprintf2("%s(%d) need to realloc\n", __func__, pid); 145 nr_ranges_allocated *= 2; 146 ranges = realloc(ranges, 147 nr_ranges_allocated * sizeof(ranges[0])); 148 dprintf2("%s(%d) allocated %d ranges @ %p\n", __func__, 149 pid, nr_ranges_allocated, ranges); 150 assert(ranges != NULL); 151 dprintf1("reallocating to hold %d ranges\n", nr_ranges_allocated); 152 } 153 } while (1); 154 155 dprintf2("%s(%d) done\n", __func__, pid); 156 157 return ret; 158} 159 160static inline int vaddr_in_range(unsigned long vaddr, struct vaddr_range *r) 161{ 162 if (vaddr < r->start) 163 return 0; 164 if (vaddr >= r->end) 165 return 0; 166 return 1; 167} 168 169static inline int vaddr_mapped_by_range(unsigned long vaddr) 170{ 171 int i; 172 173 if (last_range > 0 && vaddr_in_range(vaddr, &ranges[last_range])) 174 return 1; 175 176 for (i = 0; i < nr_ranges_populated; i++) { 177 struct vaddr_range *r = &ranges[i]; 178 179 if (vaddr_in_range(vaddr, r)) 180 continue; 181 last_range = i; 182 return 1; 183 } 184 return 0; 185} 186 187const int bt_entry_size_bytes = sizeof(unsigned long) * 4; 188 189void *read_bounds_table_into_buf(unsigned long table_vaddr) 190{ 191#ifdef MPX_DIG_STANDALONE 192 static char bt_buf[MPX_BOUNDS_TABLE_SIZE_BYTES]; 193 off_t seek_ret = lseek(fd, table_vaddr, SEEK_SET); 194 if (seek_ret != table_vaddr) 195 mpx_dig_abort(); 196 197 int read_ret = read(fd, &bt_buf, sizeof(bt_buf)); 198 if (read_ret != sizeof(bt_buf)) 199 mpx_dig_abort(); 200 return &bt_buf; 201#else 202 return (void *)table_vaddr; 203#endif 204} 205 206int dump_table(unsigned long table_vaddr, unsigned long base_controlled_vaddr, 207 unsigned long bde_vaddr) 208{ 209 unsigned long offset_inside_bt; 210 int nr_entries = 0; 211 int do_abort = 0; 212 char *bt_buf; 213 214 dprintf3("%s() base_controlled_vaddr: 0x%012lx bde_vaddr: 0x%012lx\n", 215 __func__, base_controlled_vaddr, bde_vaddr); 216 217 bt_buf = read_bounds_table_into_buf(table_vaddr); 218 219 dprintf4("%s() read done\n", __func__); 220 221 for (offset_inside_bt = 0; 222 offset_inside_bt < MPX_BOUNDS_TABLE_SIZE_BYTES; 223 offset_inside_bt += bt_entry_size_bytes) { 224 unsigned long bt_entry_index; 225 unsigned long bt_entry_controls; 226 unsigned long this_bt_entry_for_vaddr; 227 unsigned long *bt_entry_buf; 228 int i; 229 230 dprintf4("%s() offset_inside_bt: 0x%lx of 0x%llx\n", __func__, 231 offset_inside_bt, MPX_BOUNDS_TABLE_SIZE_BYTES); 232 bt_entry_buf = (void *)&bt_buf[offset_inside_bt]; 233 if (!bt_buf) { 234 printf("null bt_buf\n"); 235 mpx_dig_abort(); 236 } 237 if (!bt_entry_buf) { 238 printf("null bt_entry_buf\n"); 239 mpx_dig_abort(); 240 } 241 dprintf4("%s() reading *bt_entry_buf @ %p\n", __func__, 242 bt_entry_buf); 243 if (!bt_entry_buf[0] && 244 !bt_entry_buf[1] && 245 !bt_entry_buf[2] && 246 !bt_entry_buf[3]) 247 continue; 248 249 nr_entries++; 250 251 bt_entry_index = offset_inside_bt/bt_entry_size_bytes; 252 bt_entry_controls = sizeof(void *); 253 this_bt_entry_for_vaddr = 254 base_controlled_vaddr + bt_entry_index*bt_entry_controls; 255 /* 256 * We sign extend vaddr bits 48->63 which effectively 257 * creates a hole in the virtual address space. 258 * This calculation corrects for the hole. 259 */ 260 if (this_bt_entry_for_vaddr > 0x00007fffffffffffUL) 261 this_bt_entry_for_vaddr |= 0xffff800000000000; 262 263 if (!vaddr_mapped_by_range(this_bt_entry_for_vaddr)) { 264 printf("bt_entry_buf: %p\n", bt_entry_buf); 265 printf("there is a bte for %lx but no mapping\n", 266 this_bt_entry_for_vaddr); 267 printf(" bde vaddr: %016lx\n", bde_vaddr); 268 printf("base_controlled_vaddr: %016lx\n", base_controlled_vaddr); 269 printf(" table_vaddr: %016lx\n", table_vaddr); 270 printf(" entry vaddr: %016lx @ offset %lx\n", 271 table_vaddr + offset_inside_bt, offset_inside_bt); 272 do_abort = 1; 273 mpx_dig_abort(); 274 } 275 if (DEBUG_LEVEL < 4) 276 continue; 277 278 printf("table entry[%lx]: ", offset_inside_bt); 279 for (i = 0; i < bt_entry_size_bytes; i += sizeof(unsigned long)) 280 printf("0x%016lx ", bt_entry_buf[i]); 281 printf("\n"); 282 } 283 if (do_abort) 284 mpx_dig_abort(); 285 dprintf4("%s() done\n", __func__); 286 return nr_entries; 287} 288 289int search_bd_buf(char *buf, int len_bytes, unsigned long bd_offset_bytes, 290 int *nr_populated_bdes) 291{ 292 unsigned long i; 293 int total_entries = 0; 294 295 dprintf3("%s(%p, %x, %lx, ...) buf end: %p\n", __func__, buf, 296 len_bytes, bd_offset_bytes, buf + len_bytes); 297 298 for (i = 0; i < len_bytes; i += sizeof(unsigned long)) { 299 unsigned long bd_index = (bd_offset_bytes + i) / sizeof(unsigned long); 300 unsigned long *bounds_dir_entry_ptr = (unsigned long *)&buf[i]; 301 unsigned long bounds_dir_entry; 302 unsigned long bd_for_vaddr; 303 unsigned long bt_start; 304 unsigned long bt_tail; 305 int nr_entries; 306 307 dprintf4("%s() loop i: %ld bounds_dir_entry_ptr: %p\n", __func__, i, 308 bounds_dir_entry_ptr); 309 310 bounds_dir_entry = *bounds_dir_entry_ptr; 311 if (!bounds_dir_entry) { 312 dprintf4("no bounds dir at index 0x%lx / 0x%lx " 313 "start at offset:%lx %lx\n", bd_index, bd_index, 314 bd_offset_bytes, i); 315 continue; 316 } 317 dprintf3("found bounds_dir_entry: 0x%lx @ " 318 "index 0x%lx buf ptr: %p\n", bounds_dir_entry, i, 319 &buf[i]); 320 /* mask off the enable bit: */ 321 bounds_dir_entry &= ~0x1; 322 (*nr_populated_bdes)++; 323 dprintf4("nr_populated_bdes: %p\n", nr_populated_bdes); 324 dprintf4("*nr_populated_bdes: %d\n", *nr_populated_bdes); 325 326 bt_start = bounds_dir_entry; 327 bt_tail = bounds_dir_entry + MPX_BOUNDS_TABLE_SIZE_BYTES - 1; 328 if (!vaddr_mapped_by_range(bt_start)) { 329 printf("bounds directory 0x%lx points to nowhere\n", 330 bounds_dir_entry); 331 mpx_dig_abort(); 332 } 333 if (!vaddr_mapped_by_range(bt_tail)) { 334 printf("bounds directory end 0x%lx points to nowhere\n", 335 bt_tail); 336 mpx_dig_abort(); 337 } 338 /* 339 * Each bounds directory entry controls 1MB of virtual address 340 * space. This variable is the virtual address in the process 341 * of the beginning of the area controlled by this bounds_dir. 342 */ 343 bd_for_vaddr = bd_index * (1UL<<20); 344 345 nr_entries = dump_table(bounds_dir_entry, bd_for_vaddr, 346 bounds_dir_global+bd_offset_bytes+i); 347 total_entries += nr_entries; 348 dprintf5("dir entry[%4ld @ %p]: 0x%lx %6d entries " 349 "total this buf: %7d bd_for_vaddrs: 0x%lx -> 0x%lx\n", 350 bd_index, buf+i, 351 bounds_dir_entry, nr_entries, total_entries, 352 bd_for_vaddr, bd_for_vaddr + (1UL<<20)); 353 } 354 dprintf3("%s(%p, %x, %lx, ...) done\n", __func__, buf, len_bytes, 355 bd_offset_bytes); 356 return total_entries; 357} 358 359int proc_pid_mem_fd = -1; 360 361void *fill_bounds_dir_buf_other(long byte_offset_inside_bounds_dir, 362 long buffer_size_bytes, void *buffer) 363{ 364 unsigned long seekto = bounds_dir_global + byte_offset_inside_bounds_dir; 365 int read_ret; 366 off_t seek_ret = lseek(proc_pid_mem_fd, seekto, SEEK_SET); 367 368 if (seek_ret != seekto) 369 mpx_dig_abort(); 370 371 read_ret = read(proc_pid_mem_fd, buffer, buffer_size_bytes); 372 /* there shouldn't practically be short reads of /proc/$pid/mem */ 373 if (read_ret != buffer_size_bytes) 374 mpx_dig_abort(); 375 376 return buffer; 377} 378void *fill_bounds_dir_buf_self(long byte_offset_inside_bounds_dir, 379 long buffer_size_bytes, void *buffer) 380 381{ 382 unsigned char vec[buffer_size_bytes / PAGE_SIZE]; 383 char *dig_bounds_dir_ptr = 384 (void *)(bounds_dir_global + byte_offset_inside_bounds_dir); 385 /* 386 * use mincore() to quickly find the areas of the bounds directory 387 * that have memory and thus will be worth scanning. 388 */ 389 int incore_ret; 390 391 int incore = 0; 392 int i; 393 394 dprintf4("%s() dig_bounds_dir_ptr: %p\n", __func__, dig_bounds_dir_ptr); 395 396 incore_ret = mincore(dig_bounds_dir_ptr, buffer_size_bytes, &vec[0]); 397 if (incore_ret) { 398 printf("mincore ret: %d\n", incore_ret); 399 perror("mincore"); 400 mpx_dig_abort(); 401 } 402 for (i = 0; i < sizeof(vec); i++) 403 incore += vec[i]; 404 dprintf4("%s() total incore: %d\n", __func__, incore); 405 if (!incore) 406 return NULL; 407 dprintf3("%s() total incore: %d\n", __func__, incore); 408 return dig_bounds_dir_ptr; 409} 410 411int inspect_pid(int pid) 412{ 413 static int dig_nr; 414 long offset_inside_bounds_dir; 415 char bounds_dir_buf[sizeof(unsigned long) * (1UL << 15)]; 416 char *dig_bounds_dir_ptr; 417 int total_entries = 0; 418 int nr_populated_bdes = 0; 419 int inspect_self; 420 421 if (getpid() == pid) { 422 dprintf4("inspecting self\n"); 423 inspect_self = 1; 424 } else { 425 dprintf4("inspecting pid %d\n", pid); 426 mpx_dig_abort(); 427 } 428 429 for (offset_inside_bounds_dir = 0; 430 offset_inside_bounds_dir < MPX_BOUNDS_TABLE_SIZE_BYTES; 431 offset_inside_bounds_dir += sizeof(bounds_dir_buf)) { 432 static int bufs_skipped; 433 int this_entries; 434 435 if (inspect_self) { 436 dig_bounds_dir_ptr = 437 fill_bounds_dir_buf_self(offset_inside_bounds_dir, 438 sizeof(bounds_dir_buf), 439 &bounds_dir_buf[0]); 440 } else { 441 dig_bounds_dir_ptr = 442 fill_bounds_dir_buf_other(offset_inside_bounds_dir, 443 sizeof(bounds_dir_buf), 444 &bounds_dir_buf[0]); 445 } 446 if (!dig_bounds_dir_ptr) { 447 bufs_skipped++; 448 continue; 449 } 450 this_entries = search_bd_buf(dig_bounds_dir_ptr, 451 sizeof(bounds_dir_buf), 452 offset_inside_bounds_dir, 453 &nr_populated_bdes); 454 total_entries += this_entries; 455 } 456 printf("mpx dig (%3d) complete, SUCCESS (%8d / %4d)\n", ++dig_nr, 457 total_entries, nr_populated_bdes); 458 return total_entries + nr_populated_bdes; 459} 460 461#ifdef MPX_DIG_REMOTE 462int main(int argc, char **argv) 463{ 464 int err; 465 char *c; 466 unsigned long bounds_dir_entry; 467 int pid; 468 469 printf("mpx-dig starting...\n"); 470 err = sscanf(argv[1], "%d", &pid); 471 printf("parsing: '%s', err: %d\n", argv[1], err); 472 if (err != 1) 473 mpx_dig_abort(); 474 475 err = sscanf(argv[2], "%lx", &bounds_dir_global); 476 printf("parsing: '%s': %d\n", argv[2], err); 477 if (err != 1) 478 mpx_dig_abort(); 479 480 proc_pid_mem_fd = open_proc(pid, "mem"); 481 if (proc_pid_mem_fd < 0) 482 mpx_dig_abort(); 483 484 inspect_pid(pid); 485 return 0; 486} 487#endif 488 489long inspect_me(struct mpx_bounds_dir *bounds_dir) 490{ 491 int pid = getpid(); 492 493 pid_load_vaddrs(pid); 494 bounds_dir_global = (unsigned long)bounds_dir; 495 dprintf4("enter %s() bounds dir: %p\n", __func__, bounds_dir); 496 return inspect_pid(pid); 497}