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