root/usr.bin/pmap/main.c
/*      $NetBSD: main.c,v 1.30 2022/08/21 07:46:52 mlelstv Exp $ */

/*
 * Copyright (c) 2002, 2003, 2020 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Andrew Brown.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: main.c,v 1.30 2022/08/21 07:46:52 mlelstv Exp $");
#endif

#include <sys/param.h>

#ifndef __NetBSD_Version__
#error go away, you fool
#elif (__NetBSD_Version__ < 105000000)
#error only works with uvm
#endif

#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <limits.h>
#include <string.h>
#include <signal.h>
#include <util.h>

#include "pmap.h"
#include "main.h"

struct cache_head lcache;
void *uvm_vnodeops, *uvm_deviceops, *aobj_pager, *ubc_pager;
struct vm_map *kmem_map, *phys_map, *exec_map, *pager_map;
struct vm_map *st_map, *pt_map, *module_map, *buf_map;
u_long kernel_map_addr;
int debug, verbose, recurse, page_size;
int print_all, print_map, print_maps, print_solaris, print_ddb;
int tree;
rlim_t maxssiz;

struct nlist ksyms[] = {
        { "_maxsmap", 0, 0, 0, 0 },
#define NL_MAXSSIZ              0
        { "_uvm_vnodeops", 0, 0, 0, 0 },
#define NL_UVM_VNODEOPS         1
        { "_uvm_deviceops", 0, 0, 0, 0 },
#define NL_UVM_DEVICEOPS        2
        { "_aobj_pager", 0, 0, 0, 0 },
#define NL_AOBJ_PAGER           3
        { "_ubc_pager", 0, 0, 0, 0 },
#define NL_UBC_PAGER            4
        { "_kernel_map", 0, 0, 0, 0 },
#define NL_KERNEL_MAP           5
        { NULL, 0, 0, 0, 0 }
};

struct nlist kmaps[] = {
        { "_kmem_map", 0, 0, 0, 0 },
#define NL_kmem_map             0
        { "_phys_map", 0, 0, 0, 0 },
#define NL_phys_map             1
        { "_exec_map", 0, 0, 0, 0 },
#define NL_exec_map             2
        { "_pager_map", 0, 0, 0, 0 },
#define NL_pager_map            3
        { "_st_map", 0, 0, 0, 0 },
#define NL_st_map               4
        { "_pt_map", 0, 0, 0, 0 },
#define NL_pt_map               5
        { "_module_map", 0, 0, 0, 0 },
#define NL_module_map           6
        { "_buf_map", 0, 0, 0, 0 },
#define NL_buf_map              7
        { NULL, 0, 0, 0, 0 },
};

#define VMSPACE_ADDRESS         1
#define VM_MAP_ADDRESS          2
#define VM_MAP_ENTRY_ADDRESS    3
#define AMAP_ADDRESS            4

void check_fd(int);
void load_symbols(kvm_t *);
void cache_enter(u_long, struct namecache *);

int
main(int argc, char *argv[])
{
        kvm_t *kd;
        pid_t pid;
        uid_t uid;
        int which, many, ch, rc;
        char errbuf[_POSIX2_LINE_MAX + 1];
        struct kinfo_proc2 *kproc;
        char *kmem, *kernel, *t;
        gid_t egid;
        struct kbit kbit, *vmspace;
        u_long address;

        uid = getuid();
        egid = getegid();
        if (setegid(getgid()) == -1)
                err(1, "failed to reset privileges");

        check_fd(STDIN_FILENO);
        check_fd(STDOUT_FILENO);
        check_fd(STDERR_FILENO);

        pid = -1;
        which = verbose = debug = 0;
        print_all = print_map = print_maps = print_solaris = print_ddb = 0;
        tree = 0;
        recurse = 0;
        kmem = kernel = NULL;
        address = 0;
        vmspace = &kbit;

        while ((ch = getopt(argc, argv, "A:aD:dE:lM:mN:Pp:RrS:stV:vx")) != -1) {
                switch (ch) {
                case 'A':
                case 'E':
                case 'S':
                case 'V':
                        if (which != 0)
                                errx(1, "use only one of -A, -E, -S, or -V");
                        errno = 0;
                        address = strtoul(optarg, &t, 0);
                        if (*t != '\0')
                                errx(1, "%s is not a valid address", optarg);
                        if (errno != 0)
                                err(1, "%s is not a valid address", optarg);
                        switch (ch) {
                        case 'A':       which = AMAP_ADDRESS;           break;
                        case 'E':       which = VM_MAP_ENTRY_ADDRESS;   break;
                        case 'S':       which = VMSPACE_ADDRESS;        break;
                        case 'V':       which = VM_MAP_ADDRESS;         break;
                        }
                        break;
                case 'a':
                        print_all = 1;
                        break;
                case 'd':
                        print_ddb = 1;
                        break;
                case 'D':
                        errno = 0;
                        debug = strtoul(optarg, &t, 0);
                        if (*t != '\0')
                                errx(1, "%s is not a valid number", optarg);
                        if (errno != 0)
                                err(1, "%s is not a valid number", optarg);
                        break;
                case 'l':
                        print_maps = 1;
                        break;
                case 'm':
                        print_map = 1;
                        break;
                case 'M':
                        kmem = optarg;
                        break;
                case 'N':
                        kernel = optarg;
                        break;
                case 'p':
                        errno = 0;
                        pid = strtol(optarg, &t, 0);
                        if (pid < 0)
                                errno = EINVAL;
                        if (*t != '\0')
                                errx(1, "%s is not a valid pid", optarg);
                        if (errno != 0)
                                err(1, "%s is not a valid pid", optarg);
                        break;
                case 'P':
                        pid = getpid();
                        break;
                case 'R':
                        recurse = 1;
                        break;
                case 's':
                        print_solaris = 1;
                        break;
                case 't':
                        tree = 1;
                        break;
                case 'v':
                        verbose++;
                        break;
                case 'r':
                case 'x':
                        errx(1, "-%c option not implemented, sorry", optopt);
                        /*NOTREACHED*/
                case '?':
                default:
                        fprintf(stderr, "usage: %s [-adlmPRstv] [-A address] "
                                "[-D number] [-E address] [-M core]\n"
                                "\t[-N system] [-p pid] [-S address] "
                                "[-V address] [pid ...]\n",
                                getprogname());
                        exit(1);
                }
        }
        argc -= optind;
        argv += optind;

        /* more than one "process" to dump? */
        many = (argc > 1 - (pid == -1 ? 0 : 1)) ? 1 : 0;

        /* apply default */
        if (print_all + print_map + print_maps + print_solaris +
            print_ddb == 0)
                print_solaris = 1;

        if ((kernel != NULL || kmem != NULL || address != 0 ||
             print_ddb || debug) && uid != 0)
                errx(1, "one or more options specified is restricted to root");

        /* get privs back since it appears to be safe. */
        rc = setegid(egid);
        if (rc == -1)
                err(1, "failed to reset privileges");

        /* start by opening libkvm */
        kd = kvm_openfiles(kernel, kmem, NULL, O_RDONLY, errbuf);

        /* we're completely done with privileges now */
        rc = setgid(getgid());
        if (rc == -1)
                err(1, "failed to reset privileges");

        /* print the kvm_open error, if any */
        errbuf[_POSIX2_LINE_MAX] = '\0';
        if (kd == NULL)
                errx(1, "%s", errbuf);

        /* get "bootstrap" addresses from kernel */
        load_symbols(kd);

        if (address) {
                struct kbit kbit2, *at = &kbit2;

                memset(vmspace, 0, sizeof(*vmspace));
                A(at) = address;
                S(at) = (size_t)-1;

                switch (which) {
                    case VMSPACE_ADDRESS:
                        /* (kd, kproc, vmspace, thing) */
                        (*process_map)(kd, NULL, at, "vm_map");
                        break;
                    case VM_MAP_ADDRESS:
                        /* (kd, proc, vmspace, vm_map, thing) */
                        (*dump_vm_map)(kd, NULL, vmspace, at, "vm_map");
                        break;
                    case VM_MAP_ENTRY_ADDRESS:
                        /* (kd, proc, vmspace, vm_map_entry, 0) */
                        (*dump_vm_map_entry)(kd, NULL, vmspace, at, 0);
                        break;
                    case AMAP_ADDRESS:
                        /* (kd, amap) */
                        (*dump_amap)(kd, at);
                        break;
                }
                exit(0);
        }

        do {
                if (pid == -1) {
                        if (argc == 0)
                                pid = getppid();
                        else {
                                errno = 0;
                                pid = strtol(argv[0], &t, 0);
                                if (pid < 0)
                                        errno = EINVAL;
                                if (*t != '\0')
                                        errx(1, "%s is not a valid pid",
                                            argv[0]);
                                if (errno != 0)
                                        err(1, "%s is not a valid pid",
                                            argv[0]);
                                argv++;
                                argc--;
                        }
                }

                errno = 0;
                /* find the process id */
                if (pid == 0) {
                        kproc = NULL;
                        if (uid != 0) {
                                /* only root can print kernel mappings */
                                errno = EPERM;
                        }
                } else {
                        kproc = kvm_getproc2(kd, KERN_PROC_PID, pid,
                            sizeof(struct kinfo_proc2), &rc);
                        if (kproc == NULL || rc == 0) {
                                errno = ESRCH;
                        } else if (uid != 0 && uid != kproc->p_uid) {
                                /*
                                 * only the real owner of the process and
                                 * root can print process mappings
                                 */
                                errno = EPERM;
                        }
                }

                if (errno != 0) {
                        warn("%d", pid);
                        pid = -1;
                        continue;
                }

                /* dump it */
                if (many) {
                        if (kproc != NULL)
                                printf("process %d:\n", kproc->p_pid);
                        else
                                printf("kernel:\n");
                }

                (*process_map)(kd, kproc, vmspace, NULL);
                pid = -1;
        } while (argc > 0);

        /* done.  go away. */
        rc = kvm_close(kd);
        if (rc == -1)
                err(1, "kvm_close");

        return (0);
}

void
check_fd(int fd)
{
        struct stat st;
        int n;

        if (fstat(fd, &st) == -1) {
                (void)close(fd);
                n = open("/dev/null", O_RDWR);
                if (n == fd || n == -1)
                        /* we're either done or we can do no more */
                        return;
                /* if either of these fail, there's not much we can do */
                (void)dup2(n, fd);
                (void)close(n);
                /* XXX should we exit if it fails? */
        }
}

void
load_symbols(kvm_t *kd)
{
        int rc, i, mib[2];
        size_t sz;

        rc = kvm_nlist(kd, &ksyms[0]);
        if (rc != 0) {
                for (i = 0; ksyms[i].n_name != NULL; i++)
                        if (ksyms[i].n_value == 0)
                                warnx("symbol %s: not found", ksyms[i].n_name);
                exit(1);
        }

        uvm_vnodeops =  (void*)ksyms[NL_UVM_VNODEOPS].n_value;
        uvm_deviceops = (void*)ksyms[NL_UVM_DEVICEOPS].n_value;
        aobj_pager =    (void*)ksyms[NL_AOBJ_PAGER].n_value;
        ubc_pager =     (void*)ksyms[NL_UBC_PAGER].n_value;

        _KDEREF(kd, ksyms[NL_MAXSSIZ].n_value, &maxssiz,
                sizeof(maxssiz));
        _KDEREF(kd, ksyms[NL_KERNEL_MAP].n_value, &kernel_map_addr,
                sizeof(kernel_map_addr));

        /*
         * Some of these may be missing from some platforms, for
         * example sparc, sh3, and most powerpc platforms don't
         * have a "phys_map", etc.
         */
        (void)kvm_nlist(kd, &kmaps[0]);

#define get_map_address(m) do {\
        if (kmaps[__CONCAT(NL_,m)].n_value != 0) \
                _KDEREF(kd, kmaps[__CONCAT(NL_,m)].n_value, &m, sizeof(m)); \
        } while (0/*CONSTCOND*/)

        get_map_address(kmem_map);
        get_map_address(phys_map);
        get_map_address(exec_map);
        get_map_address(pager_map);
        get_map_address(st_map);
        get_map_address(pt_map);
        get_map_address(module_map);
        get_map_address(buf_map);

        mib[0] = CTL_HW;
        mib[1] = HW_PAGESIZE;
        sz = sizeof(page_size);
        if (sysctl(&mib[0], 2, &page_size, &sz, NULL, 0) == -1)
                err(1, "sysctl: hw.pagesize");
}

const char *
mapname(void *addr)
{

        if (addr == (void*)kernel_map_addr)
                return ("kernel_map");
        else if (addr == kmem_map)
                return ("kmem_map");
        else if (addr == phys_map)
                return ("phys_map");
        else if (addr == exec_map)
                return ("exec_map");
        else if (addr == pager_map)
                return ("pager_map");
        else if (addr == st_map)
                return ("st_map");
        else if (addr == pt_map)
                return ("pt_map");
        else if (addr == module_map)
                return ("module_map");
        else if (addr == buf_map)
                return ("buf_map");
        else
                return (NULL);
}