]>
Commit | Line | Data |
---|---|---|
b2441318 | 1 | // SPDX-License-Identifier: GPL-2.0 |
e754aedc DH |
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/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 | } |