linux/tools/testing/selftests/x86/mpx-dig.c
<<
>>
Prefs
   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}
 498