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/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
  21unsigned long bounds_dir_global;
  22
  23#define mpx_dig_abort() __mpx_dig_abort(__FILE__, __func__, __LINE__)
  24static 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
  42long 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
  65int 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
  78struct vaddr_range {
  79        unsigned long start;
  80        unsigned long end;
  81};
  82struct vaddr_range *ranges;
  83int nr_ranges_allocated;
  84int nr_ranges_populated;
  85int last_range = -1;
  86
  87int __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
 129int 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
 162static 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
 171static 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
 189const int bt_entry_size_bytes = sizeof(unsigned long) * 4;
 190
 191void *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
 208int 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
 291int 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
 361int proc_pid_mem_fd = -1;
 362
 363void *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}
 380void *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
 413int 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
 464int 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
 491long 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