summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason King <jason.king@joyent.com>2020-12-23 19:05:24 -0600
committerJason King <jason.king@joyent.com>2021-03-10 01:27:46 -0600
commit089ef9c1879ed031043f7bb75bd1f472558dcbbf (patch)
tree40be54981374faff4bced71d96db8951ba2e31d4
parentf6c47fdeea63a9c5d1abefdffb93d3f87e12b7d0 (diff)
downloadillumos-joyent-089ef9c1879ed031043f7bb75bd1f472558dcbbf.tar.gz
13576 Add mdb ::linkerset command
Reviewed by: Richard Lowe <richlowe@richlowe.net> Reviewed by: Toomas Soome <tsoome@me.com> Approved by: Dan McDonald <danmcd@joyent.com>
-rw-r--r--usr/src/cmd/mdb/Makefile.kmdb.files8
-rw-r--r--usr/src/cmd/mdb/Makefile.mdb4
-rw-r--r--usr/src/cmd/mdb/common/mdb/mdb.c9
-rw-r--r--usr/src/cmd/mdb/common/mdb/mdb_cmds.c5
-rw-r--r--usr/src/cmd/mdb/common/mdb/mdb_linkerset.c500
-rw-r--r--usr/src/cmd/mdb/common/mdb/mdb_linkerset.h42
-rw-r--r--usr/src/cmd/mdb/common/mdb/mdb_walkers.c24
7 files changed, 585 insertions, 7 deletions
diff --git a/usr/src/cmd/mdb/Makefile.kmdb.files b/usr/src/cmd/mdb/Makefile.kmdb.files
index ca10ca72a1..caf3050fee 100644
--- a/usr/src/cmd/mdb/Makefile.kmdb.files
+++ b/usr/src/cmd/mdb/Makefile.kmdb.files
@@ -25,7 +25,7 @@
#
# Copyright (c) 2012 by Delphix. All rights reserved.
-# Copyright (c) 2018 Joyent, Inc. All rights reserved.
+# Copyright 2021 Joyent, Inc.
#
KMDBSRCS += \
@@ -60,6 +60,7 @@ KMDBSRCS += \
kmdb_kdi.c \
kmdb_kvm.c \
mdb_logio.c \
+ mdb_linkerset.c \
mdb_list.c \
mdb_macalias.c \
kmdb_main.c \
@@ -88,6 +89,7 @@ KMDBSRCS += \
kmdb_umemglue.c \
mdb_value.c \
mdb_vcb.c \
+ mdb_walkers.c \
mdb_wcb.c \
mdb_whatis.c \
kmdb_wr.c
@@ -96,7 +98,7 @@ KMDBML +=
KMDBOBJS = $(KMDBSRCS:%.c=%.o) $(KMDBML:%.s=%.o)
-PROMSRCS +=
+PROMSRCS +=
PROMOBJS = $(PROMSRCS:%.c=%.o)
@@ -109,7 +111,7 @@ KCTLSRCS += \
kctl_string.c \
kctl_wr.c
-KCTLML +=
+KCTLML +=
KCTLOBJS = $(KCTLSRCS:%.c=%.o) $(KCTLML:%.s=%.o)
diff --git a/usr/src/cmd/mdb/Makefile.mdb b/usr/src/cmd/mdb/Makefile.mdb
index 5eb6f4e977..5772bed1fd 100644
--- a/usr/src/cmd/mdb/Makefile.mdb
+++ b/usr/src/cmd/mdb/Makefile.mdb
@@ -26,7 +26,7 @@
#
# Copyright 2011 Nexenta Systems, Inc. All rights reserved.
# Copyright (c) 2012 by Delphix. All rights reserved.
-# Copyright 2019 Joyent, Inc.
+# Copyright 2021 Joyent, Inc.
# Copyright 2018 Jason King
#
@@ -61,6 +61,7 @@ SRCS += \
mdb_kb_kvm.c \
mdb_kproc.c \
mdb_kvm.c \
+ mdb_linkerset.c \
mdb_logio.c \
mdb_list.c \
mdb_macalias.c \
@@ -90,6 +91,7 @@ SRCS += \
mdb_umem.c \
mdb_value.c \
mdb_vcb.c \
+ mdb_walkers.c \
mdb_wcb.c \
mdb_whatis.c
diff --git a/usr/src/cmd/mdb/common/mdb/mdb.c b/usr/src/cmd/mdb/common/mdb/mdb.c
index eb6b46083f..aafca64f3a 100644
--- a/usr/src/cmd/mdb/common/mdb/mdb.c
+++ b/usr/src/cmd/mdb/common/mdb/mdb.c
@@ -24,7 +24,7 @@
*/
/*
* Copyright (c) 2012 by Delphix. All rights reserved.
- * Copyright (c) 2018, Joyent, Inc.
+ * Copyright 2021 Joyent, Inc.
*/
/*
@@ -83,6 +83,7 @@
#define DCMD_ABORTED(x) ((x) == DCMD_USAGE || (x) == DCMD_ABORT)
extern const mdb_dcmd_t mdb_dcmd_builtins[];
+extern const mdb_walker_t mdb_walker_builtins[];
extern mdb_dis_ctor_f *const mdb_dis_builtins[];
/*
@@ -461,6 +462,7 @@ mdb_create(const char *execname, const char *arg0)
static char rootdir[MAXPATHLEN];
const mdb_dcmd_t *dcp;
+ const mdb_walker_t *wcp;
int i;
bzero(&mdb, sizeof (mdb_t));
@@ -503,7 +505,7 @@ mdb_create(const char *execname, const char *arg0)
mdb.m_rminfo.mi_dvers = MDB_API_VERSION;
mdb.m_rminfo.mi_dcmds = mdb_dcmd_builtins;
- mdb.m_rminfo.mi_walkers = NULL;
+ mdb.m_rminfo.mi_walkers = mdb_walker_builtins;
(void) mdb_nv_create(&mdb.m_rmod.mod_walkers, UM_SLEEP);
(void) mdb_nv_create(&mdb.m_rmod.mod_dcmds, UM_SLEEP);
@@ -545,6 +547,9 @@ mdb_create(const char *execname, const char *arg0)
for (dcp = &mdb_dcmd_builtins[0]; dcp->dc_name != NULL; dcp++)
(void) mdb_module_add_dcmd(&mdb.m_rmod, dcp, 0);
+ for (wcp = &mdb_walker_builtins[0]; wcp->walk_name != NULL; wcp++)
+ (void) mdb_module_add_walker(&mdb.m_rmod, wcp, 0);
+
for (i = 0; mdb_dis_builtins[i] != NULL; i++)
(void) mdb_dis_create(mdb_dis_builtins[i]);
diff --git a/usr/src/cmd/mdb/common/mdb/mdb_cmds.c b/usr/src/cmd/mdb/common/mdb/mdb_cmds.c
index 10c622443a..616f69e954 100644
--- a/usr/src/cmd/mdb/common/mdb/mdb_cmds.c
+++ b/usr/src/cmd/mdb/common/mdb/mdb_cmds.c
@@ -26,7 +26,7 @@
/*
* Copyright (c) 2012 by Delphix. All rights reserved.
- * Copyright (c) 2019 Joyent, Inc. All rights reserved.
+ * Copyright 2021 Joyent, Inc.
* Copyright (c) 2013 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
* Copyright (c) 2015, 2017 by Delphix. All rights reserved.
* Copyright 2018 OmniOS Community Edition (OmniOSce) Association.
@@ -71,6 +71,7 @@
#include <mdb/mdb_macalias.h>
#include <mdb/mdb_tab.h>
#include <mdb/mdb_typedef.h>
+#include <mdb/mdb_linkerset.h>
#ifdef _KMDB
#include <kmdb/kmdb_kdi.h>
#endif
@@ -3130,6 +3131,8 @@ const mdb_dcmd_t mdb_dcmd_builtins[] = {
head_help },
{ "help", "[cmd]", "list commands/command help", cmd_help, NULL,
cmd_help_tab },
+ { "linkerset", "[name]", "display linkersets", cmd_linkerset,
+ linkerset_help, cmd_linkerset_tab },
{ "list", "?type member [variable]",
"walk list using member as link pointer", cmd_list, NULL,
mdb_tab_complete_mt },
diff --git a/usr/src/cmd/mdb/common/mdb/mdb_linkerset.c b/usr/src/cmd/mdb/common/mdb/mdb_linkerset.c
new file mode 100644
index 0000000000..771f8f1b95
--- /dev/null
+++ b/usr/src/cmd/mdb/common/mdb/mdb_linkerset.c
@@ -0,0 +1,500 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2021 Joyent, Inc.
+ */
+
+#include <mdb/mdb_debug.h>
+#include <mdb/mdb_errno.h>
+#include <mdb/mdb_modapi.h>
+#include <mdb/mdb_err.h>
+#include <mdb/mdb_ctf.h>
+#include <mdb/mdb_ctf_impl.h>
+#include <mdb/mdb_target_impl.h>
+#include <mdb/mdb.h>
+#include <sys/errno.h>
+#include <string.h>
+
+/*
+ * A linker set is an array of pointers. The start of the set will have a
+ * weak symbol of the form START_PREFIX + name that will have the address
+ * of the first element (pointer) and another weak symbol that points just
+ * past the end of the final element. E.g. for a linker set 'foo', the
+ * first element will have a symbol __start_set_foo, and all __stop_set_foo
+ * will have the address just after the last element (e.g. &(last_element + 1))
+ */
+#define START_PREFIX "__start_set_"
+#define STOP_PREFIX "__stop_set_"
+
+/*
+ * The pointers that comprise the linker set have names that follow
+ * the pattern __set_<setname>_sym_<objname>.
+ */
+#define SYM_PREFIX "__set_"
+#define SYM_DELIM "_sym_"
+
+typedef struct ldset_info {
+ char ldsi_name[MDB_SYM_NAMLEN];
+ uintptr_t ldsi_addr;
+ uintptr_t ldsi_endaddr;
+ size_t ldsi_ptrsize;
+ size_t ldsi_nelem;
+ ssize_t ldsi_elsize;
+} ldset_info_t;
+
+/*
+ * Similar to ldset_name_from_start(), except that it uses a linker set item
+ * name (e.g. '__set_foo_set_sym_foo_item') and writes the set name ('foo_set')
+ * into buf.
+ */
+static int
+ldset_name_from_item(const char *item_name, char *buf, size_t buflen)
+{
+ const char *startp;
+ const char *endp;
+ size_t setname_len;
+
+ /* The item name must start with '__sym_' */
+ if (strncmp(item_name, SYM_PREFIX, sizeof (SYM_PREFIX) - 1) != 0) {
+ return (set_errno(EINVAL));
+ }
+ startp = item_name + sizeof (SYM_PREFIX) - 1;
+
+ /* The item name must have stuff after '__sym_' */
+ if (*startp == '\0') {
+ return (set_errno(EINVAL));
+ }
+
+ /* Find the start of '_sym_' after the prefix */
+ endp = strstr(startp, SYM_DELIM);
+ if (endp == NULL) {
+ /* '_sym_' not in the name, not a valid item name */
+ return (set_errno(EINVAL));
+ }
+
+ setname_len = (size_t)(endp - startp);
+ if (setname_len + 1 > buflen) {
+ return (set_errno(ENAMETOOLONG));
+ }
+
+ /*
+ * We've verified buf has enough room for the linker set name + NUL.
+ * For sanity, we guarantee any trailing bytes in buf are zero, and
+ * use strncpy() so we copy only the bytes from item_name that are
+ * a part of the linker set name. The result should always be NUL
+ * terminated as a result.
+ */
+ (void) memset(buf, '\0', buflen);
+ (void) strncpy(buf, item_name + sizeof (SYM_PREFIX) - 1, setname_len);
+
+ return (0);
+}
+
+static int
+ldset_get_sym(const char *prefix, const char *name, GElf_Sym *sym)
+{
+ char symname[MDB_SYM_NAMLEN] = { 0 };
+
+ if (mdb_snprintf(symname, sizeof (symname), "%s%s", prefix, name) >
+ sizeof (symname) - 1) {
+ return (set_errno(ENAMETOOLONG));
+ }
+
+ return (mdb_tgt_lookup_by_name(mdb.m_target, MDB_TGT_OBJ_EVERY, symname,
+ sym, NULL));
+}
+
+/*
+ * Given the address of a pointer in a linker set, return the address of the
+ * item in the set in *addrp.
+ */
+static int
+ldset_get_entry(uintptr_t addr, uintptr_t *addrp, size_t ptrsize)
+{
+ union {
+ uint64_t u64;
+ uint32_t u32;
+ } val;
+ ssize_t n;
+
+ switch (ptrsize) {
+ case sizeof (uint32_t):
+ n = mdb_vread(&val.u32, sizeof (uint32_t), addr);
+ *addrp = (uintptr_t)val.u32;
+ break;
+ case sizeof (uint64_t):
+ n = mdb_vread(&val.u64, sizeof (uint64_t), addr);
+ *addrp = (uintptr_t)val.u64;
+ break;
+ default:
+ return (set_errno(ENOTSUP));
+ }
+
+ if (n != ptrsize) {
+ /* XXX: Better error value? */
+ return (set_errno(ENODATA));
+ }
+
+ return (0);
+}
+
+static ssize_t
+ldset_item_size(uintptr_t addr)
+{
+ mdb_ctf_id_t id;
+ int ret;
+
+ ret = mdb_ctf_lookup_by_addr(addr, &id);
+ if (ret != 0) {
+ return ((ssize_t)ret);
+ }
+
+ return (mdb_ctf_type_size(id));
+}
+
+static int
+ldset_get_info(uintptr_t addr, ldset_info_t *ldsi)
+{
+ GElf_Sym start_sym = { 0 };
+ GElf_Sym stop_sym = { 0 };
+ char name[MDB_SYM_NAMLEN] = { 0 };
+ uintptr_t item_addr;
+ int ret;
+
+ switch (mdb_tgt_dmodel(mdb.m_target)) {
+ case MDB_TGT_MODEL_LP64:
+ ldsi->ldsi_ptrsize = sizeof (uint64_t);
+ break;
+ case MDB_TGT_MODEL_ILP32:
+ ldsi->ldsi_ptrsize = sizeof (uint32_t);
+ break;
+ default:
+ return (set_errno(ENOTSUP));
+ }
+
+ ret = mdb_tgt_lookup_by_addr(mdb.m_target, addr, MDB_TGT_SYM_EXACT,
+ name, sizeof (name), &start_sym, NULL);
+ if (ret != 0) {
+ return (ret);
+ }
+
+ if (ldset_name_from_item(name, ldsi->ldsi_name,
+ sizeof (ldsi->ldsi_name)) != 0) {
+ return (-1);
+ }
+
+ ret = ldset_get_sym(STOP_PREFIX, ldsi->ldsi_name, &stop_sym);
+ if (ret != 0) {
+ return (-1);
+ }
+
+ if (stop_sym.st_value < addr) {
+ return (set_errno(EINVAL));
+ }
+
+ if (ldset_get_entry(addr, &item_addr, ldsi->ldsi_ptrsize) != 0) {
+ return (-1);
+ }
+
+ ldsi->ldsi_addr = addr;
+ ldsi->ldsi_endaddr = stop_sym.st_value;
+ ldsi->ldsi_nelem = (stop_sym.st_value - addr) / ldsi->ldsi_ptrsize;
+ ldsi->ldsi_elsize = ldset_item_size(item_addr);
+
+ return (0);
+}
+
+static int
+ldsets_init_cb(void *data, const GElf_Sym *sym, const char *name,
+ const mdb_syminfo_t *sip, const char *obj)
+{
+ mdb_nv_t *nv = data;
+ const char *ldset_name;
+ GElf_Sym stop_sym = { 0 };
+ int ret;
+
+ if (strncmp(name, START_PREFIX, sizeof (START_PREFIX) - 1) != 0) {
+ return (0);
+ }
+
+ /*
+ * The name of the linker set should follow START_PREFIX. If there's
+ * nothing there, then it's not a linker set, so skip this symbol.
+ */
+ ldset_name = name + sizeof (START_PREFIX) - 1;
+ if (*ldset_name == '\0') {
+ return (0);
+ }
+
+ ret = ldset_get_sym(STOP_PREFIX, ldset_name, &stop_sym);
+ if (ret != 0) {
+ /* If there's no stop symbol, we just ignore */
+ if (errno == ENOENT) {
+ errno = 0;
+ return (0);
+ }
+ return (-1);
+ }
+
+ /*
+ * The stop symbol should be at the same or higher address than
+ * the start symbol. If not, we ignore.
+ */
+ if (stop_sym.st_value < sym->st_value) {
+ return (0);
+ }
+
+ if (mdb_nv_insert(nv, ldset_name, NULL, sym->st_value,
+ MDB_NV_RDONLY) == NULL) {
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * Initialize an mdb_nv_t with the name/addr of all the linkersets found in
+ * the target.
+ */
+static int
+ldsets_nv_init(mdb_nv_t *nv, uint_t flags)
+{
+ if (mdb_nv_create(nv, flags) == NULL)
+ return (-1);
+
+ return (mdb_tgt_symbol_iter(mdb.m_target, MDB_TGT_OBJ_EVERY,
+ MDB_TGT_SYMTAB, MDB_TGT_BIND_ANY | MDB_TGT_TYPE_NOTYPE,
+ ldsets_init_cb, nv));
+}
+
+int
+ldsets_walk_init(mdb_walk_state_t *wsp)
+{
+ mdb_nv_t *nv;
+ int ret;
+
+ nv = mdb_zalloc(sizeof (*nv), UM_SLEEP | UM_GC);
+ ret = ldsets_nv_init(nv, UM_SLEEP | UM_GC);
+ if (ret != 0) {
+ return (ret);
+ }
+
+ mdb_nv_rewind(nv);
+ wsp->walk_data = nv;
+ return (WALK_NEXT);
+}
+
+int
+ldsets_walk_step(mdb_walk_state_t *wsp)
+{
+ mdb_nv_t *nv = wsp->walk_data;
+ mdb_var_t *v = mdb_nv_advance(nv);
+ int status;
+
+ if (v == NULL) {
+ return (WALK_DONE);
+ }
+
+ wsp->walk_addr = mdb_nv_get_value(v);
+ status = wsp->walk_callback(wsp->walk_addr, NULL, wsp->walk_cbdata);
+ return (status);
+}
+
+int
+ldset_walk_init(mdb_walk_state_t *wsp)
+{
+ ldset_info_t *ldsi;
+ int ret;
+
+ ldsi = mdb_zalloc(sizeof (*ldsi), UM_SLEEP | UM_GC);
+
+ ret = ldset_get_info(wsp->walk_addr, ldsi);
+ if (ret != 0)
+ return (WALK_ERR);
+
+ wsp->walk_data = ldsi;
+ return (WALK_NEXT);
+}
+
+int
+ldset_walk_step(mdb_walk_state_t *wsp)
+{
+ ldset_info_t *ldsi = wsp->walk_data;
+ uintptr_t addr;
+ int ret;
+
+ if (wsp->walk_addr >= ldsi->ldsi_endaddr) {
+ return (WALK_DONE);
+ }
+
+ ret = ldset_get_entry(wsp->walk_addr, &addr, ldsi->ldsi_ptrsize);
+ if (ret != 0) {
+ return (WALK_ERR);
+ }
+
+ ret = wsp->walk_callback(addr, NULL, wsp->walk_cbdata);
+
+ wsp->walk_addr += ldsi->ldsi_ptrsize;
+ return (ret);
+}
+
+static int
+linkerset_walk_cb(uintptr_t addr, const void *data, void *cbarg)
+{
+ mdb_printf("%lr\n", addr);
+ return (0);
+}
+
+static int
+linkersets_walk_cb(uintptr_t addr, const void *data, void *cbarg)
+{
+ ldset_info_t info = { 0 };
+ int ret;
+ char buf[64]; /* big enough for element size in any radix */
+
+ ret = ldset_get_info(addr, &info);
+ if (ret != 0)
+ return (WALK_ERR);
+
+ if (info.ldsi_elsize > 0) {
+ (void) mdb_snprintf(buf, sizeof (buf), "%#r",
+ info.ldsi_elsize);
+ } else {
+ (void) strlcpy(buf, "?", sizeof (buf));
+ }
+
+ mdb_printf("%-20s %8s %9u\n", info.ldsi_name, buf, info.ldsi_nelem);
+ return (WALK_NEXT);
+}
+
+int
+cmd_linkerset(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
+{
+ int ret;
+
+ if (argc > 1) {
+ return (DCMD_USAGE);
+ }
+
+ /* Walk a linkerset given by the first argument */
+ if (argc == 1) {
+ const char *setname = argv->a_un.a_str;
+ GElf_Sym start_sym = { 0 };
+ ldset_info_t info = { 0 };
+
+ if (argv->a_type != MDB_TYPE_STRING) {
+ return (DCMD_USAGE);
+ }
+
+ ret = ldset_get_sym(START_PREFIX, setname, &start_sym);
+ if (ret != 0) {
+ mdb_warn("Failed to get address of linkerset");
+ return (-1);
+ }
+
+ ret = ldset_get_info((uintptr_t)start_sym.st_value, &info);
+ if (ret != 0) {
+ mdb_warn("Failed to get information on linkerset");
+ return (-1);
+ }
+
+ return (mdb_pwalk("linkerset", linkerset_walk_cb, NULL,
+ info.ldsi_addr));
+ }
+
+ /* Display all the known linkersets */
+ if (DCMD_HDRSPEC(flags)) {
+ mdb_printf("%<b>%<u>%-20s %-8s %-9s%</u>%</b>\n",
+ "NAME", "ITEMSIZE", "ITEMCOUNT");
+ }
+
+ return (mdb_walk("linkersets", linkersets_walk_cb, NULL));
+}
+
+static int
+ldset_complete(mdb_var_t *v, void *arg)
+{
+ mdb_tab_cookie_t *mcp = arg;
+
+ mdb_tab_insert(mcp, mdb_nv_get_name(v));
+ return (0);
+}
+
+static int
+ldset_tab_complete(mdb_tab_cookie_t *mcp, const char *ldset)
+{
+ mdb_nv_t nv = { 0 };
+ int ret;
+
+ ret = ldsets_nv_init(&nv, UM_GC | UM_SLEEP);
+ if (ret != 0) {
+ return (ret);
+ }
+
+ if (ldset != NULL) {
+ mdb_tab_setmbase(mcp, ldset);
+ }
+
+ mdb_nv_sort_iter(&nv, ldset_complete, mcp, UM_GC | UM_SLEEP);
+ return (1);
+}
+
+int
+cmd_linkerset_tab(mdb_tab_cookie_t *mcp, uint_t flags, int argc,
+ const mdb_arg_t *argv)
+{
+ if (argc > 1)
+ return (1);
+
+ if (argc == 1) {
+ ASSERT(argv[0].a_type == MDB_TYPE_STRING);
+ return (ldset_tab_complete(mcp, argv[0].a_un.a_str));
+ }
+
+ if (argc == 0 && (flags & DCMD_TAB_SPACE) != 0) {
+ return (ldset_tab_complete(mcp, NULL));
+ }
+
+ return (1);
+}
+
+void
+linkerset_help(void)
+{
+ static const char ldset_desc[] =
+"A linker set is an array of pointers to objects in a target that have been\n"
+"collected by the linker. The start and end location of each linker set\n"
+"is designated by weak symbols with well known strings prefixed to the\n"
+"name of the linker set.\n"
+"\n"
+"When invoked without any arguments, the ::linkerset command will attempt to\n"
+"enumerate all linker sets present in the target. For each linker set, the \n"
+"name, number of objects in the set, as well as the size of each object (when\n"
+"known) is displayed. The ::linkerset command uses the CTF information to\n"
+"determine the size of each object. If the CTF data is unavailable for a\n"
+"given linkerset, '?' will displayed instead of the size.\n"
+"\n"
+"The ::linkerset command can also be invoked with a single argument -- the\n"
+"name of a specific linker set. In this invocation, the ::linkerset command\n"
+"will display the addresses of each object in the set and can be used as\n"
+"part of a command pipeline.\n";
+
+ static const char ldset_examples[] =
+" ::linkerset\n"
+" ::linkerset sysinit_set | ::print 'struct sysinit'\n";
+
+ mdb_printf("%s\n", ldset_desc);
+ (void) mdb_dec_indent(2);
+ mdb_printf("%<b>EXAMPLES%</b>\n");
+ (void) mdb_inc_indent(2);
+ mdb_printf("%s\n", ldset_examples);
+}
diff --git a/usr/src/cmd/mdb/common/mdb/mdb_linkerset.h b/usr/src/cmd/mdb/common/mdb/mdb_linkerset.h
new file mode 100644
index 0000000000..9cf6a46c20
--- /dev/null
+++ b/usr/src/cmd/mdb/common/mdb/mdb_linkerset.h
@@ -0,0 +1,42 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2021 Joyent, Inc.
+ */
+
+#ifndef _MDB_LINKERSET_H
+#define _MDB_LINKERSET_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef _MDB
+
+extern int ldsets_walk_init(mdb_walk_state_t *);
+extern int ldsets_walk_step(mdb_walk_state_t *);
+
+extern int ldset_walk_init(mdb_walk_state_t *);
+extern int ldset_walk_step(mdb_walk_state_t *);
+
+extern int cmd_linkerset(uintptr_t, uint_t, int, const mdb_arg_t *);
+extern void linkerset_help(void);
+extern int cmd_linkerset_tab(mdb_tab_cookie_t *, uint_t, int,
+ const mdb_arg_t *);
+
+#endif /* _MDB */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _MDB_LINKERSET_H */
diff --git a/usr/src/cmd/mdb/common/mdb/mdb_walkers.c b/usr/src/cmd/mdb/common/mdb/mdb_walkers.c
new file mode 100644
index 0000000000..2daa1d2ea2
--- /dev/null
+++ b/usr/src/cmd/mdb/common/mdb/mdb_walkers.c
@@ -0,0 +1,24 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2021 Joyent, Inc.
+ */
+
+#include <mdb/mdb_modapi.h>
+#include <mdb/mdb_linkerset.h>
+
+const mdb_walker_t mdb_walker_builtins[] = {
+ { "linkerset", "walk a linkerset", ldset_walk_init, ldset_walk_step },
+ { "linkersets", "walk all linkersets", ldsets_walk_init,
+ ldsets_walk_step },
+ NULL
+};