/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2019 Joyent, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__i386) || defined(__amd64) #include #endif #include "avl.h" #include "memory.h" /* * Page walker. * By default, this will walk all pages in the system. If given an * address, it will walk all pages belonging to the vnode at that * address. */ /* * page_walk_data * * pw_hashleft is set to -1 when walking a vnode's pages, and holds the * number of hash locations remaining in the page hash table when * walking all pages. * * The astute reader will notice that pw_hashloc is only used when * reading all pages (to hold a pointer to our location in the page * hash table), and that pw_first is only used when reading the pages * belonging to a particular vnode (to hold a pointer to the first * page). While these could be combined to be a single pointer, they * are left separate for clarity. */ typedef struct page_walk_data { long pw_hashleft; void **pw_hashloc; uintptr_t pw_first; } page_walk_data_t; int page_walk_init(mdb_walk_state_t *wsp) { page_walk_data_t *pwd; void **ptr; size_t hashsz; vnode_t vn; if (wsp->walk_addr == 0) { /* * Walk all pages */ if ((mdb_readvar(&ptr, "page_hash") == -1) || (mdb_readvar(&hashsz, "page_hashsz") == -1) || (ptr == NULL) || (hashsz == 0)) { mdb_warn("page_hash, page_hashsz not found or invalid"); return (WALK_ERR); } /* * Since we are walking all pages, initialize hashleft * to be the remaining number of entries in the page * hash. hashloc is set the start of the page hash * table. Setting the walk address to 0 indicates that * we aren't currently following a hash chain, and that * we need to scan the page hash table for a page. */ pwd = mdb_alloc(sizeof (page_walk_data_t), UM_SLEEP); pwd->pw_hashleft = hashsz; pwd->pw_hashloc = ptr; wsp->walk_addr = 0; } else { /* * Walk just this vnode */ if (mdb_vread(&vn, sizeof (vnode_t), wsp->walk_addr) == -1) { mdb_warn("unable to read vnode_t at %#lx", wsp->walk_addr); return (WALK_ERR); } /* * We set hashleft to -1 to indicate that we are * walking a vnode, and initialize first to 0 (it is * used to terminate the walk, so it must not be set * until after we have walked the first page). The * walk address is set to the first page. */ pwd = mdb_alloc(sizeof (page_walk_data_t), UM_SLEEP); pwd->pw_hashleft = -1; pwd->pw_first = 0; wsp->walk_addr = (uintptr_t)vn.v_pages; } wsp->walk_data = pwd; return (WALK_NEXT); } int page_walk_step(mdb_walk_state_t *wsp) { page_walk_data_t *pwd = wsp->walk_data; page_t page; uintptr_t pp; pp = wsp->walk_addr; if (pwd->pw_hashleft < 0) { /* We're walking a vnode's pages */ /* * If we don't have any pages to walk, we have come * back around to the first one (we finished), or we * can't read the page we're looking at, we are done. */ if (pp == 0 || pp == pwd->pw_first) return (WALK_DONE); if (mdb_vread(&page, sizeof (page_t), pp) == -1) { mdb_warn("unable to read page_t at %#lx", pp); return (WALK_ERR); } /* * Set the walk address to the next page, and if the * first page hasn't been set yet (i.e. we are on the * first page), set it. */ wsp->walk_addr = (uintptr_t)page.p_vpnext; if (pwd->pw_first == 0) pwd->pw_first = pp; } else if (pwd->pw_hashleft > 0) { /* We're walking all pages */ /* * If pp (the walk address) is NULL, we scan through * the page hash table until we find a page. */ if (pp == 0) { /* * Iterate through the page hash table until we * find a page or reach the end. */ do { if (mdb_vread(&pp, sizeof (uintptr_t), (uintptr_t)pwd->pw_hashloc) == -1) { mdb_warn("unable to read from %#p", pwd->pw_hashloc); return (WALK_ERR); } pwd->pw_hashleft--; pwd->pw_hashloc++; } while (pwd->pw_hashleft && (pp == 0)); /* * We've reached the end; exit. */ if (pp == 0) return (WALK_DONE); } if (mdb_vread(&page, sizeof (page_t), pp) == -1) { mdb_warn("unable to read page_t at %#lx", pp); return (WALK_ERR); } /* * Set the walk address to the next page. */ wsp->walk_addr = (uintptr_t)page.p_hash; } else { /* We've finished walking all pages. */ return (WALK_DONE); } return (wsp->walk_callback(pp, &page, wsp->walk_cbdata)); } void page_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (page_walk_data_t)); } /* * allpages walks all pages in the system in order they appear in * the memseg structure */ #define PAGE_BUFFER 128 int allpages_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr != 0) { mdb_warn("allpages only supports global walks.\n"); return (WALK_ERR); } if (mdb_layered_walk("memseg", wsp) == -1) { mdb_warn("couldn't walk 'memseg'"); return (WALK_ERR); } wsp->walk_data = mdb_alloc(sizeof (page_t) * PAGE_BUFFER, UM_SLEEP); return (WALK_NEXT); } int allpages_walk_step(mdb_walk_state_t *wsp) { const struct memseg *msp = wsp->walk_layer; page_t *buf = wsp->walk_data; size_t pg_read, i; size_t pg_num = msp->pages_end - msp->pages_base; const page_t *pg_addr = msp->pages; while (pg_num > 0) { pg_read = MIN(pg_num, PAGE_BUFFER); if (mdb_vread(buf, pg_read * sizeof (page_t), (uintptr_t)pg_addr) == -1) { mdb_warn("can't read page_t's at %#lx", pg_addr); return (WALK_ERR); } for (i = 0; i < pg_read; i++) { int ret = wsp->walk_callback((uintptr_t)&pg_addr[i], &buf[i], wsp->walk_cbdata); if (ret != WALK_NEXT) return (ret); } pg_num -= pg_read; pg_addr += pg_read; } return (WALK_NEXT); } void allpages_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (page_t) * PAGE_BUFFER); } /* * Hash table + LRU queue. * This table is used to cache recently read vnodes for the memstat * command, to reduce the number of mdb_vread calls. This greatly * speeds the memstat command on on live, large CPU count systems. */ #define VN_SMALL 401 #define VN_LARGE 10007 #define VN_HTABLE_KEY(p, hp) ((p) % ((hp)->vn_htable_buckets)) struct vn_htable_list { uint_t vn_flag; /* v_flag from vnode */ uintptr_t vn_ptr; /* pointer to vnode */ struct vn_htable_list *vn_q_next; /* queue next pointer */ struct vn_htable_list *vn_q_prev; /* queue prev pointer */ struct vn_htable_list *vn_h_next; /* hash table pointer */ }; /* * vn_q_first -> points to to head of queue: the vnode that was most * recently used * vn_q_last -> points to the oldest used vnode, and is freed once a new * vnode is read. * vn_htable -> hash table * vn_htable_buf -> contains htable objects * vn_htable_size -> total number of items in the hash table * vn_htable_buckets -> number of buckets in the hash table */ typedef struct vn_htable { struct vn_htable_list *vn_q_first; struct vn_htable_list *vn_q_last; struct vn_htable_list **vn_htable; struct vn_htable_list *vn_htable_buf; int vn_htable_size; int vn_htable_buckets; } vn_htable_t; /* allocate memory, initilize hash table and LRU queue */ static void vn_htable_init(vn_htable_t *hp, size_t vn_size) { int i; int htable_size = MAX(vn_size, VN_LARGE); if ((hp->vn_htable_buf = mdb_zalloc(sizeof (struct vn_htable_list) * htable_size, UM_NOSLEEP|UM_GC)) == NULL) { htable_size = VN_SMALL; hp->vn_htable_buf = mdb_zalloc(sizeof (struct vn_htable_list) * htable_size, UM_SLEEP|UM_GC); } hp->vn_htable = mdb_zalloc(sizeof (struct vn_htable_list *) * htable_size, UM_SLEEP|UM_GC); hp->vn_q_first = &hp->vn_htable_buf[0]; hp->vn_q_last = &hp->vn_htable_buf[htable_size - 1]; hp->vn_q_first->vn_q_next = &hp->vn_htable_buf[1]; hp->vn_q_last->vn_q_prev = &hp->vn_htable_buf[htable_size - 2]; for (i = 1; i < (htable_size-1); i++) { hp->vn_htable_buf[i].vn_q_next = &hp->vn_htable_buf[i + 1]; hp->vn_htable_buf[i].vn_q_prev = &hp->vn_htable_buf[i - 1]; } hp->vn_htable_size = htable_size; hp->vn_htable_buckets = htable_size; } /* * Find the vnode whose address is ptr, and return its v_flag in vp->v_flag. * The function tries to find needed information in the following order: * * 1. check if ptr is the first in queue * 2. check if ptr is in hash table (if so move it to the top of queue) * 3. do mdb_vread, remove last queue item from queue and hash table. * Insert new information to freed object, and put this object in to the * top of the queue. */ static int vn_get(vn_htable_t *hp, struct vnode *vp, uintptr_t ptr) { int hkey; struct vn_htable_list *hent, **htmp, *q_next, *q_prev; struct vn_htable_list *q_first = hp->vn_q_first; /* 1. vnode ptr is the first in queue, just get v_flag and return */ if (q_first->vn_ptr == ptr) { vp->v_flag = q_first->vn_flag; return (0); } /* 2. search the hash table for this ptr */ hkey = VN_HTABLE_KEY(ptr, hp); hent = hp->vn_htable[hkey]; while (hent && (hent->vn_ptr != ptr)) hent = hent->vn_h_next; /* 3. if hent is NULL, we did not find in hash table, do mdb_vread */ if (hent == NULL) { struct vnode vn; if (mdb_vread(&vn, sizeof (vnode_t), ptr) == -1) { mdb_warn("unable to read vnode_t at %#lx", ptr); return (-1); } /* we will insert read data into the last element in queue */ hent = hp->vn_q_last; /* remove last hp->vn_q_last object from hash table */ if (hent->vn_ptr) { htmp = &hp->vn_htable[VN_HTABLE_KEY(hent->vn_ptr, hp)]; while (*htmp != hent) htmp = &(*htmp)->vn_h_next; *htmp = hent->vn_h_next; } /* insert data into new free object */ hent->vn_ptr = ptr; hent->vn_flag = vn.v_flag; /* insert new object into hash table */ hent->vn_h_next = hp->vn_htable[hkey]; hp->vn_htable[hkey] = hent; } /* Remove from queue. hent is not first, vn_q_prev is not NULL */ q_next = hent->vn_q_next; q_prev = hent->vn_q_prev; if (q_next == NULL) hp->vn_q_last = q_prev; else q_next->vn_q_prev = q_prev; q_prev->vn_q_next = q_next; /* Add to the front of queue */ hent->vn_q_prev = NULL; hent->vn_q_next = q_first; q_first->vn_q_prev = hent; hp->vn_q_first = hent; /* Set v_flag in vnode pointer from hent */ vp->v_flag = hent->vn_flag; return (0); } /* Summary statistics of pages */ typedef struct memstat { struct vnode *ms_unused_vp; /* Unused pages vnode pointer */ struct vnode *ms_kvps; /* Cached address of vnode array */ uint64_t ms_kmem; /* Pages of kernel memory */ uint64_t ms_zfs_data; /* Pages of zfs data */ uint64_t ms_vmm_mem; /* Pages of VMM mem */ uint64_t ms_anon; /* Pages of anonymous memory */ uint64_t ms_vnode; /* Pages of named (vnode) memory */ uint64_t ms_exec; /* Pages of exec/library memory */ uint64_t ms_cachelist; /* Pages on the cachelist (free) */ uint64_t ms_bootpages; /* Pages on the bootpages list */ uint64_t ms_total; /* Pages on page hash */ vn_htable_t *ms_vn_htable; /* Pointer to hash table */ struct vnode ms_vn; /* vnode buffer */ } memstat_t; #define MS_PP_ISTYPE(pp, stats, index) \ ((pp)->p_vnode == &(stats->ms_kvps[index])) /* * Summarize pages by type and update stat information */ /* ARGSUSED */ static int memstat_callback(page_t *page, page_t *pp, memstat_t *stats) { struct vnode *vp = &stats->ms_vn; if (PP_ISBOOTPAGES(pp)) stats->ms_bootpages++; else if (pp->p_vnode == NULL || pp->p_vnode == stats->ms_unused_vp) return (WALK_NEXT); else if (MS_PP_ISTYPE(pp, stats, KV_KVP)) stats->ms_kmem++; else if (MS_PP_ISTYPE(pp, stats, KV_ZVP)) stats->ms_zfs_data++; else if (MS_PP_ISTYPE(pp, stats, KV_VVP)) stats->ms_vmm_mem++; else if (PP_ISFREE(pp)) stats->ms_cachelist++; else if (vn_get(stats->ms_vn_htable, vp, (uintptr_t)pp->p_vnode)) return (WALK_ERR); else if (IS_SWAPFSVP(vp)) stats->ms_anon++; else if ((vp->v_flag & VVMEXEC) != 0) stats->ms_exec++; else stats->ms_vnode++; stats->ms_total++; return (WALK_NEXT); } /* ARGSUSED */ int memstat(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { pgcnt_t total_pages, physmem; ulong_t freemem; memstat_t stats; GElf_Sym sym; vn_htable_t ht; uintptr_t vn_size = 0; #if defined(__i386) || defined(__amd64) bln_stats_t bln_stats; ssize_t bln_size; #endif bzero(&stats, sizeof (memstat_t)); /* * -s size, is an internal option. It specifies the size of vn_htable. * Hash table size is set in the following order: * If user has specified the size that is larger than VN_LARGE: try it, * but if malloc failed default to VN_SMALL. Otherwise try VN_LARGE, if * failed to allocate default to VN_SMALL. * For a better efficiency of hash table it is highly recommended to * set size to a prime number. */ if ((flags & DCMD_ADDRSPEC) || mdb_getopts(argc, argv, 's', MDB_OPT_UINTPTR, &vn_size, NULL) != argc) return (DCMD_USAGE); /* Initialize vnode hash list and queue */ vn_htable_init(&ht, vn_size); stats.ms_vn_htable = &ht; /* Total physical memory */ if (mdb_readvar(&total_pages, "total_pages") == -1) { mdb_warn("unable to read total_pages"); return (DCMD_ERR); } /* Artificially limited memory */ if (mdb_readvar(&physmem, "physmem") == -1) { mdb_warn("unable to read physmem"); return (DCMD_ERR); } /* read kernel vnode array pointer */ if (mdb_lookup_by_obj(MDB_OBJ_EXEC, "kvps", (GElf_Sym *)&sym) == -1) { mdb_warn("unable to look up kvps"); return (DCMD_ERR); } stats.ms_kvps = (struct vnode *)(uintptr_t)sym.st_value; /* * If physmem != total_pages, then the administrator has limited the * number of pages available in the system. Excluded pages are * associated with the unused pages vnode. Read this vnode so the * pages can be excluded in the page accounting. */ if (mdb_lookup_by_obj(MDB_OBJ_EXEC, "unused_pages_vp", (GElf_Sym *)&sym) == -1) { mdb_warn("unable to read unused_pages_vp"); return (DCMD_ERR); } stats.ms_unused_vp = (struct vnode *)(uintptr_t)sym.st_value; /* walk all pages, collect statistics */ if (mdb_walk("allpages", (mdb_walk_cb_t)(uintptr_t)memstat_callback, &stats) == -1) { mdb_warn("can't walk memseg"); return (DCMD_ERR); } #define MS_PCT_TOTAL(x) ((ulong_t)((((5 * total_pages) + ((x) * 1000ull))) / \ ((physmem) * 10))) mdb_printf("Page Summary Pages MB" " %%Tot\n"); mdb_printf("------------ ---------------- ----------------" " ----\n"); mdb_printf("Kernel %16llu %16llu %3lu%%\n", stats.ms_kmem, (uint64_t)stats.ms_kmem * PAGESIZE / (1024 * 1024), MS_PCT_TOTAL(stats.ms_kmem)); if (stats.ms_bootpages != 0) { mdb_printf("Boot pages %16llu %16llu %3lu%%\n", stats.ms_bootpages, (uint64_t)stats.ms_bootpages * PAGESIZE / (1024 * 1024), MS_PCT_TOTAL(stats.ms_bootpages)); } if (stats.ms_zfs_data != 0) { mdb_printf("ZFS File Data %16llu %16llu %3lu%%\n", stats.ms_zfs_data, (uint64_t)stats.ms_zfs_data * PAGESIZE / (1024 * 1024), MS_PCT_TOTAL(stats.ms_zfs_data)); } if (stats.ms_vmm_mem != 0) { mdb_printf("VMM Memory %16llu %16llu %3lu%%\n", stats.ms_vmm_mem, (uint64_t)stats.ms_vmm_mem * PAGESIZE / (1024 * 1024), MS_PCT_TOTAL(stats.ms_vmm_mem)); } mdb_printf("Anon %16llu %16llu %3lu%%\n", stats.ms_anon, (uint64_t)stats.ms_anon * PAGESIZE / (1024 * 1024), MS_PCT_TOTAL(stats.ms_anon)); mdb_printf("Exec and libs %16llu %16llu %3lu%%\n", stats.ms_exec, (uint64_t)stats.ms_exec * PAGESIZE / (1024 * 1024), MS_PCT_TOTAL(stats.ms_exec)); mdb_printf("Page cache %16llu %16llu %3lu%%\n", stats.ms_vnode, (uint64_t)stats.ms_vnode * PAGESIZE / (1024 * 1024), MS_PCT_TOTAL(stats.ms_vnode)); mdb_printf("Free (cachelist) %16llu %16llu %3lu%%\n", stats.ms_cachelist, (uint64_t)stats.ms_cachelist * PAGESIZE / (1024 * 1024), MS_PCT_TOTAL(stats.ms_cachelist)); /* * occasionally, we double count pages above. To avoid printing * absurdly large values for freemem, we clamp it at zero. */ if (physmem > stats.ms_total) freemem = physmem - stats.ms_total; else freemem = 0; #if defined(__i386) || defined(__amd64) /* Are we running under Xen? If so, get balloon memory usage. */ if ((bln_size = mdb_readvar(&bln_stats, "bln_stats")) != -1) { if (freemem > bln_stats.bln_hv_pages) freemem -= bln_stats.bln_hv_pages; else freemem = 0; } #endif mdb_printf("Free (freelist) %16lu %16llu %3lu%%\n", freemem, (uint64_t)freemem * PAGESIZE / (1024 * 1024), MS_PCT_TOTAL(freemem)); #if defined(__i386) || defined(__amd64) if (bln_size != -1) { mdb_printf("Balloon %16lu %16llu %3lu%%\n", bln_stats.bln_hv_pages, (uint64_t)bln_stats.bln_hv_pages * PAGESIZE / (1024 * 1024), MS_PCT_TOTAL(bln_stats.bln_hv_pages)); } #endif mdb_printf("\nTotal %16lu %16lu\n", physmem, (uint64_t)physmem * PAGESIZE / (1024 * 1024)); if (physmem != total_pages) { mdb_printf("Physical %16lu %16lu\n", total_pages, (uint64_t)total_pages * PAGESIZE / (1024 * 1024)); } #undef MS_PCT_TOTAL return (DCMD_OK); } void pagelookup_help(void) { mdb_printf( "Finds the page with name { %vp%, %offset% }.\n" "\n" "Can be invoked three different ways:\n\n" " ::pagelookup -v %vp% -o %offset%\n" " %vp%::pagelookup -o %offset%\n" " %offset%::pagelookup -v %vp%\n" "\n" "The latter two forms are useful in pipelines.\n"); } int pagelookup(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t vp = -(uintptr_t)1; uint64_t offset = -(uint64_t)1; uintptr_t pageaddr; int hasaddr = (flags & DCMD_ADDRSPEC); int usedaddr = 0; if (mdb_getopts(argc, argv, 'v', MDB_OPT_UINTPTR, &vp, 'o', MDB_OPT_UINT64, &offset, NULL) != argc) { return (DCMD_USAGE); } if (vp == -(uintptr_t)1) { if (offset == -(uint64_t)1) { mdb_warn( "pagelookup: at least one of -v vp or -o offset " "required.\n"); return (DCMD_USAGE); } vp = addr; usedaddr = 1; } else if (offset == -(uint64_t)1) { offset = mdb_get_dot(); usedaddr = 1; } if (usedaddr && !hasaddr) { mdb_warn("pagelookup: address required\n"); return (DCMD_USAGE); } if (!usedaddr && hasaddr) { mdb_warn( "pagelookup: address specified when both -v and -o were " "passed"); return (DCMD_USAGE); } pageaddr = mdb_page_lookup(vp, offset); if (pageaddr == 0) { mdb_warn("pagelookup: no page for {vp = %p, offset = %llp)\n", vp, offset); return (DCMD_OK); } mdb_printf("%#lr\n", pageaddr); /* this is PIPE_OUT friendly */ return (DCMD_OK); } /*ARGSUSED*/ int page_num2pp(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t pp; if (argc != 0 || !(flags & DCMD_ADDRSPEC)) { return (DCMD_USAGE); } pp = mdb_pfn2page((pfn_t)addr); if (pp == 0) { return (DCMD_ERR); } if (flags & DCMD_PIPE_OUT) { mdb_printf("%#lr\n", pp); } else { mdb_printf("%lx has page_t at %#lx\n", (pfn_t)addr, pp); } return (DCMD_OK); } int page(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { page_t p; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("page", "page", argc, argv) == -1) { mdb_warn("can't walk pages"); return (DCMD_ERR); } return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) { mdb_printf("%%?s %?s %16s %8s %3s %3s %2s %2s %2s%\n", "PAGE", "VNODE", "OFFSET", "SELOCK", "LCT", "COW", "IO", "FS", "ST"); } if (mdb_vread(&p, sizeof (page_t), addr) == -1) { mdb_warn("can't read page_t at %#lx", addr); return (DCMD_ERR); } mdb_printf("%0?lx %?p %16llx %8x %3d %3d %2x %2x %2x\n", addr, p.p_vnode, p.p_offset, p.p_selock, p.p_lckcnt, p.p_cowcnt, p.p_iolock_state, p.p_fsdata, p.p_state); return (DCMD_OK); } int swap_walk_init(mdb_walk_state_t *wsp) { void *ptr; if ((mdb_readvar(&ptr, "swapinfo") == -1) || ptr == NULL) { mdb_warn("swapinfo not found or invalid"); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)ptr; return (WALK_NEXT); } int swap_walk_step(mdb_walk_state_t *wsp) { uintptr_t sip; struct swapinfo si; sip = wsp->walk_addr; if (sip == 0) return (WALK_DONE); if (mdb_vread(&si, sizeof (struct swapinfo), sip) == -1) { mdb_warn("unable to read swapinfo at %#lx", sip); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)si.si_next; return (wsp->walk_callback(sip, &si, wsp->walk_cbdata)); } int swapinfof(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct swapinfo si; char *name; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_walk_dcmd("swapinfo", "swapinfo", argc, argv) == -1) { mdb_warn("can't walk swapinfo"); return (DCMD_ERR); } return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) { mdb_printf("%%?s %?s %9s %9s %s%\n", "ADDR", "VNODE", "PAGES", "FREE", "NAME"); } if (mdb_vread(&si, sizeof (struct swapinfo), addr) == -1) { mdb_warn("can't read swapinfo at %#lx", addr); return (DCMD_ERR); } name = mdb_alloc(si.si_pnamelen, UM_SLEEP | UM_GC); if (mdb_vread(name, si.si_pnamelen, (uintptr_t)si.si_pname) == -1) name = "*error*"; mdb_printf("%0?lx %?p %9d %9d %s\n", addr, si.si_vp, si.si_npgs, si.si_nfpgs, name); return (DCMD_OK); } int memlist_walk_step(mdb_walk_state_t *wsp) { uintptr_t mlp; struct memlist ml; mlp = wsp->walk_addr; if (mlp == 0) return (WALK_DONE); if (mdb_vread(&ml, sizeof (struct memlist), mlp) == -1) { mdb_warn("unable to read memlist at %#lx", mlp); return (WALK_ERR); } wsp->walk_addr = (uintptr_t)ml.ml_next; return (wsp->walk_callback(mlp, &ml, wsp->walk_cbdata)); } int memlist(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct memlist ml; if (!(flags & DCMD_ADDRSPEC)) { uintptr_t ptr; uint_t list = 0; int i; static const char *lists[] = { "phys_install", "phys_avail", "virt_avail" }; if (mdb_getopts(argc, argv, 'i', MDB_OPT_SETBITS, (1 << 0), &list, 'a', MDB_OPT_SETBITS, (1 << 1), &list, 'v', MDB_OPT_SETBITS, (1 << 2), &list, NULL) != argc) return (DCMD_USAGE); if (!list) list = 1; for (i = 0; list; i++, list >>= 1) { if (!(list & 1)) continue; if ((mdb_readvar(&ptr, lists[i]) == -1) || (ptr == 0)) { mdb_warn("%s not found or invalid", lists[i]); return (DCMD_ERR); } mdb_printf("%s:\n", lists[i]); if (mdb_pwalk_dcmd("memlist", "memlist", 0, NULL, ptr) == -1) { mdb_warn("can't walk memlist"); return (DCMD_ERR); } } return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) mdb_printf("%%?s %16s %16s%\n", "ADDR", "BASE", "SIZE"); if (mdb_vread(&ml, sizeof (struct memlist), addr) == -1) { mdb_warn("can't read memlist at %#lx", addr); return (DCMD_ERR); } mdb_printf("%0?lx %16llx %16llx\n", addr, ml.ml_address, ml.ml_size); return (DCMD_OK); } int seg_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr == 0) { mdb_warn("seg walk must begin at struct as *\n"); return (WALK_ERR); } /* * this is really just a wrapper to AVL tree walk */ wsp->walk_addr = (uintptr_t)&((struct as *)wsp->walk_addr)->a_segtree; return (avl_walk_init(wsp)); } /*ARGSUSED*/ int seg(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct seg s; if (argc != 0) return (DCMD_USAGE); if ((flags & DCMD_LOOPFIRST) || !(flags & DCMD_LOOP)) { mdb_printf("%%?s %?s %?s %?s %s%\n", "SEG", "BASE", "SIZE", "DATA", "OPS"); } if (mdb_vread(&s, sizeof (s), addr) == -1) { mdb_warn("failed to read seg at %p", addr); return (DCMD_ERR); } mdb_printf("%?p %?p %?lx %?p %a\n", addr, s.s_base, s.s_size, s.s_data, s.s_ops); return (DCMD_OK); } typedef struct pmap_walk_types { uintptr_t pwt_segvn; uintptr_t pwt_seghole; } pmap_walk_types_t; /*ARGSUSED*/ static int pmap_walk_count_pages(uintptr_t addr, const void *data, void *out) { pgcnt_t *nres = out; (*nres)++; return (WALK_NEXT); } static int pmap_walk_seg(uintptr_t addr, const struct seg *seg, const pmap_walk_types_t *types) { const uintptr_t ops = (uintptr_t)seg->s_ops; mdb_printf("%0?p %0?p %7dk", addr, seg->s_base, seg->s_size / 1024); if (ops == types->pwt_segvn && seg->s_data != NULL) { struct segvn_data svn; pgcnt_t nres = 0; svn.vp = NULL; (void) mdb_vread(&svn, sizeof (svn), (uintptr_t)seg->s_data); /* * Use the segvn_pages walker to find all of the in-core pages * for this mapping. */ if (mdb_pwalk("segvn_pages", pmap_walk_count_pages, &nres, (uintptr_t)seg->s_data) == -1) { mdb_warn("failed to walk segvn_pages (s_data=%p)", seg->s_data); } mdb_printf(" %7ldk", (nres * PAGESIZE) / 1024); if (svn.vp != NULL) { char buf[29]; mdb_vnode2path((uintptr_t)svn.vp, buf, sizeof (buf)); mdb_printf(" %s", buf); } else { mdb_printf(" [ anon ]"); } } else if (ops == types->pwt_seghole && seg->s_data != NULL) { seghole_data_t shd; char name[16]; (void) mdb_vread(&shd, sizeof (shd), (uintptr_t)seg->s_data); if (shd.shd_name == NULL || mdb_readstr(name, sizeof (name), (uintptr_t)shd.shd_name) == 0) { name[0] = '\0'; } mdb_printf(" %8s [ hole%s%s ]", "-", name[0] == '0' ? "" : ":", name); } else { mdb_printf(" %8s [ &%a ]", "?", seg->s_ops); } mdb_printf("\n"); return (WALK_NEXT); } static int pmap_walk_seg_quick(uintptr_t addr, const struct seg *seg, const pmap_walk_types_t *types) { const uintptr_t ops = (uintptr_t)seg->s_ops; mdb_printf("%0?p %0?p %7dk", addr, seg->s_base, seg->s_size / 1024); if (ops == types->pwt_segvn && seg->s_data != NULL) { struct segvn_data svn; svn.vp = NULL; (void) mdb_vread(&svn, sizeof (svn), (uintptr_t)seg->s_data); if (svn.vp != NULL) { mdb_printf(" %0?p", svn.vp); } else { mdb_printf(" [ anon ]"); } } else { mdb_printf(" [ &%a ]", seg->s_ops); } mdb_printf("\n"); return (WALK_NEXT); } /*ARGSUSED*/ int pmap(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { proc_t proc; uint_t quick = FALSE; mdb_walk_cb_t cb = (mdb_walk_cb_t)pmap_walk_seg; pmap_walk_types_t wtypes = { 0 }; GElf_Sym sym; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'q', MDB_OPT_SETBITS, TRUE, &quick, NULL) != argc) return (DCMD_USAGE); if (mdb_vread(&proc, sizeof (proc), addr) == -1) { mdb_warn("failed to read proc at %p", addr); return (DCMD_ERR); } if (mdb_lookup_by_name("segvn_ops", &sym) == 0) wtypes.pwt_segvn = (uintptr_t)sym.st_value; if (mdb_lookup_by_name("seghole_ops", &sym) == 0) wtypes.pwt_seghole = (uintptr_t)sym.st_value; mdb_printf("%?s %?s %8s ", "SEG", "BASE", "SIZE"); if (quick) { mdb_printf("VNODE\n"); cb = (mdb_walk_cb_t)pmap_walk_seg_quick; } else { mdb_printf("%8s %s\n", "RES", "PATH"); } if (mdb_pwalk("seg", cb, (void *)&wtypes, (uintptr_t)proc.p_as) == -1) { mdb_warn("failed to walk segments of as %p", proc.p_as); return (DCMD_ERR); } return (DCMD_OK); } typedef struct anon_walk_data { uintptr_t *aw_levone; uintptr_t *aw_levtwo; size_t aw_minslot; size_t aw_maxslot; pgcnt_t aw_nlevone; pgcnt_t aw_levone_ndx; size_t aw_levtwo_ndx; struct anon_map *aw_ampp; struct anon_map aw_amp; struct anon_hdr aw_ahp; int aw_all; /* report all anon pointers, even NULLs */ } anon_walk_data_t; int anon_walk_init_common(mdb_walk_state_t *wsp, ulong_t minslot, ulong_t maxslot) { anon_walk_data_t *aw; if (wsp->walk_addr == 0) { mdb_warn("anon walk doesn't support global walks\n"); return (WALK_ERR); } aw = mdb_alloc(sizeof (anon_walk_data_t), UM_SLEEP); aw->aw_ampp = (struct anon_map *)wsp->walk_addr; if (mdb_vread(&aw->aw_amp, sizeof (aw->aw_amp), wsp->walk_addr) == -1) { mdb_warn("failed to read anon map at %p", wsp->walk_addr); mdb_free(aw, sizeof (anon_walk_data_t)); return (WALK_ERR); } if (mdb_vread(&aw->aw_ahp, sizeof (aw->aw_ahp), (uintptr_t)(aw->aw_amp.ahp)) == -1) { mdb_warn("failed to read anon hdr ptr at %p", aw->aw_amp.ahp); mdb_free(aw, sizeof (anon_walk_data_t)); return (WALK_ERR); } /* update min and maxslot with the given constraints */ maxslot = MIN(maxslot, aw->aw_ahp.size); minslot = MIN(minslot, maxslot); if (aw->aw_ahp.size <= ANON_CHUNK_SIZE || (aw->aw_ahp.flags & ANON_ALLOC_FORCE)) { aw->aw_nlevone = maxslot; aw->aw_levone_ndx = minslot; aw->aw_levtwo = NULL; } else { aw->aw_nlevone = (maxslot + ANON_CHUNK_OFF) >> ANON_CHUNK_SHIFT; aw->aw_levone_ndx = 0; aw->aw_levtwo = mdb_zalloc(ANON_CHUNK_SIZE * sizeof (uintptr_t), UM_SLEEP); } aw->aw_levone = mdb_alloc(aw->aw_nlevone * sizeof (uintptr_t), UM_SLEEP); aw->aw_all = (wsp->walk_arg == ANON_WALK_ALL); mdb_vread(aw->aw_levone, aw->aw_nlevone * sizeof (uintptr_t), (uintptr_t)aw->aw_ahp.array_chunk); aw->aw_levtwo_ndx = 0; aw->aw_minslot = minslot; aw->aw_maxslot = maxslot; out: wsp->walk_data = aw; return (0); } int anon_walk_step(mdb_walk_state_t *wsp) { anon_walk_data_t *aw = (anon_walk_data_t *)wsp->walk_data; struct anon anon; uintptr_t anonptr; ulong_t slot; /* * Once we've walked through level one, we're done. */ if (aw->aw_levone_ndx >= aw->aw_nlevone) { return (WALK_DONE); } if (aw->aw_levtwo == NULL) { anonptr = aw->aw_levone[aw->aw_levone_ndx]; aw->aw_levone_ndx++; } else { if (aw->aw_levtwo_ndx == 0) { uintptr_t levtwoptr; /* The first time through, skip to our first index. */ if (aw->aw_levone_ndx == 0) { aw->aw_levone_ndx = aw->aw_minslot / ANON_CHUNK_SIZE; aw->aw_levtwo_ndx = aw->aw_minslot % ANON_CHUNK_SIZE; } levtwoptr = (uintptr_t)aw->aw_levone[aw->aw_levone_ndx]; if (levtwoptr == 0) { if (!aw->aw_all) { aw->aw_levtwo_ndx = 0; aw->aw_levone_ndx++; return (WALK_NEXT); } bzero(aw->aw_levtwo, ANON_CHUNK_SIZE * sizeof (uintptr_t)); } else if (mdb_vread(aw->aw_levtwo, ANON_CHUNK_SIZE * sizeof (uintptr_t), levtwoptr) == -1) { mdb_warn("unable to read anon_map %p's " "second-level map %d at %p", aw->aw_ampp, aw->aw_levone_ndx, levtwoptr); return (WALK_ERR); } } slot = aw->aw_levone_ndx * ANON_CHUNK_SIZE + aw->aw_levtwo_ndx; anonptr = aw->aw_levtwo[aw->aw_levtwo_ndx]; /* update the indices for next time */ aw->aw_levtwo_ndx++; if (aw->aw_levtwo_ndx == ANON_CHUNK_SIZE) { aw->aw_levtwo_ndx = 0; aw->aw_levone_ndx++; } /* make sure the slot # is in the requested range */ if (slot >= aw->aw_maxslot) { return (WALK_DONE); } } if (anonptr != 0) { mdb_vread(&anon, sizeof (anon), anonptr); return (wsp->walk_callback(anonptr, &anon, wsp->walk_cbdata)); } if (aw->aw_all) { return (wsp->walk_callback(0, NULL, wsp->walk_cbdata)); } return (WALK_NEXT); } void anon_walk_fini(mdb_walk_state_t *wsp) { anon_walk_data_t *aw = (anon_walk_data_t *)wsp->walk_data; if (aw->aw_levtwo != NULL) mdb_free(aw->aw_levtwo, ANON_CHUNK_SIZE * sizeof (uintptr_t)); mdb_free(aw->aw_levone, aw->aw_nlevone * sizeof (uintptr_t)); mdb_free(aw, sizeof (anon_walk_data_t)); } int anon_walk_init(mdb_walk_state_t *wsp) { return (anon_walk_init_common(wsp, 0, ULONG_MAX)); } int segvn_anon_walk_init(mdb_walk_state_t *wsp) { const uintptr_t svd_addr = wsp->walk_addr; uintptr_t amp_addr; uintptr_t seg_addr; struct segvn_data svd; struct anon_map amp; struct seg seg; if (svd_addr == 0) { mdb_warn("segvn_anon walk doesn't support global walks\n"); return (WALK_ERR); } if (mdb_vread(&svd, sizeof (svd), svd_addr) == -1) { mdb_warn("segvn_anon walk: unable to read segvn_data at %p", svd_addr); return (WALK_ERR); } if (svd.amp == NULL) { mdb_warn("segvn_anon walk: segvn_data at %p has no anon map\n", svd_addr); return (WALK_ERR); } amp_addr = (uintptr_t)svd.amp; if (mdb_vread(&, sizeof (amp), amp_addr) == -1) { mdb_warn("segvn_anon walk: unable to read amp %p for " "segvn_data %p", amp_addr, svd_addr); return (WALK_ERR); } seg_addr = (uintptr_t)svd.seg; if (mdb_vread(&seg, sizeof (seg), seg_addr) == -1) { mdb_warn("segvn_anon walk: unable to read seg %p for " "segvn_data %p", seg_addr, svd_addr); return (WALK_ERR); } if ((seg.s_size + (svd.anon_index << PAGESHIFT)) > amp.size) { mdb_warn("anon map %p is too small for segment %p\n", amp_addr, seg_addr); return (WALK_ERR); } wsp->walk_addr = amp_addr; return (anon_walk_init_common(wsp, svd.anon_index, svd.anon_index + (seg.s_size >> PAGESHIFT))); } typedef struct { u_offset_t svs_offset; uintptr_t svs_page; } segvn_sparse_t; #define SEGVN_MAX_SPARSE ((128 * 1024) / sizeof (segvn_sparse_t)) typedef struct { uintptr_t svw_svdp; struct segvn_data svw_svd; struct seg svw_seg; size_t svw_walkoff; ulong_t svw_anonskip; segvn_sparse_t *svw_sparse; size_t svw_sparse_idx; size_t svw_sparse_count; size_t svw_sparse_size; uint8_t svw_sparse_overflow; uint8_t svw_all; } segvn_walk_data_t; static int segvn_sparse_fill(uintptr_t addr, const void *pp_arg, void *arg) { segvn_walk_data_t *const svw = arg; const page_t *const pp = pp_arg; const u_offset_t offset = pp->p_offset; segvn_sparse_t *const cur = &svw->svw_sparse[svw->svw_sparse_count]; /* See if the page is of interest */ if ((u_offset_t)(offset - svw->svw_svd.offset) >= svw->svw_seg.s_size) { return (WALK_NEXT); } /* See if we have space for the new entry, then add it. */ if (svw->svw_sparse_count >= svw->svw_sparse_size) { svw->svw_sparse_overflow = 1; return (WALK_DONE); } svw->svw_sparse_count++; cur->svs_offset = offset; cur->svs_page = addr; return (WALK_NEXT); } static int segvn_sparse_cmp(const void *lp, const void *rp) { const segvn_sparse_t *const l = lp; const segvn_sparse_t *const r = rp; if (l->svs_offset < r->svs_offset) { return (-1); } if (l->svs_offset > r->svs_offset) { return (1); } return (0); } /* * Builds on the "anon_all" walker to walk all resident pages in a segvn_data * structure. For segvn_datas without an anon structure, it just looks up * pages in the vnode. For segvn_datas with an anon structure, NULL slots * pass through to the vnode, and non-null slots are checked for residency. */ int segvn_pages_walk_init(mdb_walk_state_t *wsp) { segvn_walk_data_t *svw; struct segvn_data *svd; if (wsp->walk_addr == 0) { mdb_warn("segvn walk doesn't support global walks\n"); return (WALK_ERR); } svw = mdb_zalloc(sizeof (*svw), UM_SLEEP); svw->svw_svdp = wsp->walk_addr; svw->svw_anonskip = 0; svw->svw_sparse_idx = 0; svw->svw_walkoff = 0; svw->svw_all = (wsp->walk_arg == SEGVN_PAGES_ALL); if (mdb_vread(&svw->svw_svd, sizeof (svw->svw_svd), wsp->walk_addr) == -1) { mdb_warn("failed to read segvn_data at %p", wsp->walk_addr); mdb_free(svw, sizeof (*svw)); return (WALK_ERR); } svd = &svw->svw_svd; if (mdb_vread(&svw->svw_seg, sizeof (svw->svw_seg), (uintptr_t)svd->seg) == -1) { mdb_warn("failed to read seg at %p (from %p)", svd->seg, &((struct segvn_data *)(wsp->walk_addr))->seg); mdb_free(svw, sizeof (*svw)); return (WALK_ERR); } if (svd->amp == NULL && svd->vp == NULL) { /* make the walk terminate immediately; no pages */ svw->svw_walkoff = svw->svw_seg.s_size; } else if (svd->amp == NULL && (svw->svw_seg.s_size >> PAGESHIFT) >= SEGVN_MAX_SPARSE) { /* * If we don't have an anon pointer, and the segment is large, * we try to load the in-memory pages into a fixed-size array, * which is then sorted and reported directly. This is much * faster than doing a mdb_page_lookup() for each possible * offset. * * If the allocation fails, or there are too many pages * in-core, we fall back to looking up the pages individually. */ svw->svw_sparse = mdb_alloc( SEGVN_MAX_SPARSE * sizeof (*svw->svw_sparse), UM_NOSLEEP); if (svw->svw_sparse != NULL) { svw->svw_sparse_size = SEGVN_MAX_SPARSE; if (mdb_pwalk("page", segvn_sparse_fill, svw, (uintptr_t)svd->vp) == -1 || svw->svw_sparse_overflow) { mdb_free(svw->svw_sparse, SEGVN_MAX_SPARSE * sizeof (*svw->svw_sparse)); svw->svw_sparse = NULL; } else { qsort(svw->svw_sparse, svw->svw_sparse_count, sizeof (*svw->svw_sparse), segvn_sparse_cmp); } } } else if (svd->amp != NULL) { const char *const layer = (!svw->svw_all && svd->vp == NULL) ? "segvn_anon" : "segvn_anon_all"; /* * If we're not printing all offsets, and the segvn_data has * no backing VP, we can use the "segvn_anon" walker, which * efficiently skips NULL slots. * * Otherwise, we layer over the "segvn_anon_all" walker * (which reports all anon slots, even NULL ones), so that * segvn_pages_walk_step() knows the precise offset for each * element. It uses that offset information to look up the * backing pages for NULL anon slots. */ if (mdb_layered_walk(layer, wsp) == -1) { mdb_warn("segvn_pages: failed to layer \"%s\" " "for segvn_data %p", layer, svw->svw_svdp); mdb_free(svw, sizeof (*svw)); return (WALK_ERR); } } wsp->walk_data = svw; return (WALK_NEXT); } int segvn_pages_walk_step(mdb_walk_state_t *wsp) { segvn_walk_data_t *const svw = wsp->walk_data; struct seg *const seg = &svw->svw_seg; struct segvn_data *const svd = &svw->svw_svd; uintptr_t pp; page_t page; /* If we've walked off the end of the segment, we're done. */ if (svw->svw_walkoff >= seg->s_size) { return (WALK_DONE); } /* * If we've got a sparse page array, just send it directly. */ if (svw->svw_sparse != NULL) { u_offset_t off; if (svw->svw_sparse_idx >= svw->svw_sparse_count) { pp = 0; if (!svw->svw_all) { return (WALK_DONE); } } else { segvn_sparse_t *const svs = &svw->svw_sparse[svw->svw_sparse_idx]; off = svs->svs_offset - svd->offset; if (svw->svw_all && svw->svw_walkoff != off) { pp = 0; } else { pp = svs->svs_page; svw->svw_sparse_idx++; } } } else if (svd->amp == NULL || wsp->walk_addr == 0) { /* * If there's no anon, or the anon slot is NULL, look up * . */ if (svd->vp != NULL) { pp = mdb_page_lookup((uintptr_t)svd->vp, svd->offset + svw->svw_walkoff); } else { pp = 0; } } else { const struct anon *const anon = wsp->walk_layer; /* * We have a "struct anon"; if it's not swapped out, * look up the page. */ if (anon->an_vp != NULL || anon->an_off != 0) { pp = mdb_page_lookup((uintptr_t)anon->an_vp, anon->an_off); if (pp == 0 && mdb_get_state() != MDB_STATE_RUNNING) { mdb_warn("walk segvn_pages: segvn_data %p " "offset %ld, anon page <%p, %llx> not " "found.\n", svw->svw_svdp, svw->svw_walkoff, anon->an_vp, anon->an_off); } } else { if (anon->an_pvp == NULL) { mdb_warn("walk segvn_pages: useless struct " "anon at %p\n", wsp->walk_addr); } pp = 0; /* nothing at this offset */ } } svw->svw_walkoff += PAGESIZE; /* Update for the next call */ if (pp != 0) { if (mdb_vread(&page, sizeof (page_t), pp) == -1) { mdb_warn("unable to read page_t at %#lx", pp); return (WALK_ERR); } return (wsp->walk_callback(pp, &page, wsp->walk_cbdata)); } if (svw->svw_all) { return (wsp->walk_callback(0, NULL, wsp->walk_cbdata)); } return (WALK_NEXT); } void segvn_pages_walk_fini(mdb_walk_state_t *wsp) { segvn_walk_data_t *const svw = wsp->walk_data; if (svw->svw_sparse != NULL) { mdb_free(svw->svw_sparse, SEGVN_MAX_SPARSE * sizeof (*svw->svw_sparse)); } mdb_free(svw, sizeof (*svw)); } /* * Grumble, grumble. */ #define SMAP_HASHFUNC(vp, off) \ ((((uintptr_t)(vp) >> 6) + ((uintptr_t)(vp) >> 3) + \ ((off) >> MAXBSHIFT)) & smd_hashmsk) int vnode2smap(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { long smd_hashmsk; int hash; uintptr_t offset = 0; struct smap smp; uintptr_t saddr, kaddr; uintptr_t smd_hash, smd_smap; struct seg seg; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_readvar(&smd_hashmsk, "smd_hashmsk") == -1) { mdb_warn("failed to read smd_hashmsk"); return (DCMD_ERR); } if (mdb_readvar(&smd_hash, "smd_hash") == -1) { mdb_warn("failed to read smd_hash"); return (DCMD_ERR); } if (mdb_readvar(&smd_smap, "smd_smap") == -1) { mdb_warn("failed to read smd_hash"); return (DCMD_ERR); } if (mdb_readvar(&kaddr, "segkmap") == -1) { mdb_warn("failed to read segkmap"); return (DCMD_ERR); } if (mdb_vread(&seg, sizeof (seg), kaddr) == -1) { mdb_warn("failed to read segkmap at %p", kaddr); return (DCMD_ERR); } if (argc != 0) { const mdb_arg_t *arg = &argv[0]; if (arg->a_type == MDB_TYPE_IMMEDIATE) offset = arg->a_un.a_val; else offset = (uintptr_t)mdb_strtoull(arg->a_un.a_str); } hash = SMAP_HASHFUNC(addr, offset); if (mdb_vread(&saddr, sizeof (saddr), smd_hash + hash * sizeof (uintptr_t)) == -1) { mdb_warn("couldn't read smap at %p", smd_hash + hash * sizeof (uintptr_t)); return (DCMD_ERR); } do { if (mdb_vread(&smp, sizeof (smp), saddr) == -1) { mdb_warn("couldn't read smap at %p", saddr); return (DCMD_ERR); } if ((uintptr_t)smp.sm_vp == addr && smp.sm_off == offset) { mdb_printf("vnode %p, offs %p is smap %p, vaddr %p\n", addr, offset, saddr, ((saddr - smd_smap) / sizeof (smp)) * MAXBSIZE + seg.s_base); return (DCMD_OK); } saddr = (uintptr_t)smp.sm_hash; } while (saddr != 0); mdb_printf("no smap for vnode %p, offs %p\n", addr, offset); return (DCMD_OK); } /*ARGSUSED*/ int addr2smap(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t kaddr; struct seg seg; struct segmap_data sd; if (!(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_readvar(&kaddr, "segkmap") == -1) { mdb_warn("failed to read segkmap"); return (DCMD_ERR); } if (mdb_vread(&seg, sizeof (seg), kaddr) == -1) { mdb_warn("failed to read segkmap at %p", kaddr); return (DCMD_ERR); } if (mdb_vread(&sd, sizeof (sd), (uintptr_t)seg.s_data) == -1) { mdb_warn("failed to read segmap_data at %p", seg.s_data); return (DCMD_ERR); } mdb_printf("%p is smap %p\n", addr, ((addr - (uintptr_t)seg.s_base) >> MAXBSHIFT) * sizeof (struct smap) + (uintptr_t)sd.smd_sm); return (DCMD_OK); }