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