1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2021 Joyent, Inc.
14 */
15
16 #include <mdb/mdb_debug.h>
17 #include <mdb/mdb_errno.h>
18 #include <mdb/mdb_modapi.h>
19 #include <mdb/mdb_err.h>
20 #include <mdb/mdb_ctf.h>
21 #include <mdb/mdb_ctf_impl.h>
22 #include <mdb/mdb_target_impl.h>
23 #include <mdb/mdb.h>
24 #include <sys/errno.h>
25 #include <string.h>
26
27 /*
28 * A linker set is an array of pointers. The start of the set will have a
29 * weak symbol of the form START_PREFIX + name that will have the address
30 * of the first element (pointer) and another weak symbol that points just
31 * past the end of the final element. E.g. for a linker set 'foo', the
32 * first element will have a symbol __start_set_foo, and all __stop_set_foo
33 * will have the address just after the last element (e.g. &(last_element + 1))
34 */
35 #define START_PREFIX "__start_set_"
36 #define STOP_PREFIX "__stop_set_"
37
38 /*
39 * The pointers that comprise the linker set have names that follow
40 * the pattern __set_<setname>_sym_<objname>.
41 */
42 #define SYM_PREFIX "__set_"
43 #define SYM_DELIM "_sym_"
44
45 typedef struct ldset_info {
46 char ldsi_name[MDB_SYM_NAMLEN];
47 uintptr_t ldsi_addr;
48 uintptr_t ldsi_endaddr;
49 size_t ldsi_ptrsize;
50 size_t ldsi_nelem;
51 ssize_t ldsi_elsize;
52 } ldset_info_t;
53
54 /*
55 * Similar to ldset_name_from_start(), except that it uses a linker set item
56 * name (e.g. '__set_foo_set_sym_foo_item') and writes the set name ('foo_set')
57 * into buf.
58 */
59 static int
ldset_name_from_item(const char * item_name,char * buf,size_t buflen)60 ldset_name_from_item(const char *item_name, char *buf, size_t buflen)
61 {
62 const char *startp;
63 const char *endp;
64 size_t setname_len;
65
66 /* The item name must start with '__sym_' */
67 if (strncmp(item_name, SYM_PREFIX, sizeof (SYM_PREFIX) - 1) != 0) {
68 return (set_errno(EINVAL));
69 }
70 startp = item_name + sizeof (SYM_PREFIX) - 1;
71
72 /* The item name must have stuff after '__sym_' */
73 if (*startp == '\0') {
74 return (set_errno(EINVAL));
75 }
76
77 /* Find the start of '_sym_' after the prefix */
78 endp = strstr(startp, SYM_DELIM);
79 if (endp == NULL) {
80 /* '_sym_' not in the name, not a valid item name */
81 return (set_errno(EINVAL));
82 }
83
84 setname_len = (size_t)(endp - startp);
85 if (setname_len + 1 > buflen) {
86 return (set_errno(ENAMETOOLONG));
87 }
88
89 /*
90 * We've verified buf has enough room for the linker set name + NUL.
91 * For sanity, we guarantee any trailing bytes in buf are zero, and
92 * use strncpy() so we copy only the bytes from item_name that are
93 * a part of the linker set name. The result should always be NUL
94 * terminated as a result.
95 */
96 (void) memset(buf, '\0', buflen);
97 (void) strncpy(buf, item_name + sizeof (SYM_PREFIX) - 1, setname_len);
98
99 return (0);
100 }
101
102 static int
ldset_get_sym(const char * prefix,const char * name,GElf_Sym * sym)103 ldset_get_sym(const char *prefix, const char *name, GElf_Sym *sym)
104 {
105 char symname[MDB_SYM_NAMLEN] = { 0 };
106
107 if (mdb_snprintf(symname, sizeof (symname), "%s%s", prefix, name) >
108 sizeof (symname) - 1) {
109 return (set_errno(ENAMETOOLONG));
110 }
111
112 return (mdb_tgt_lookup_by_name(mdb.m_target, MDB_TGT_OBJ_EVERY, symname,
113 sym, NULL));
114 }
115
116 /*
117 * Given the address of a pointer in a linker set, return the address of the
118 * item in the set in *addrp.
119 */
120 static int
ldset_get_entry(uintptr_t addr,uintptr_t * addrp,size_t ptrsize)121 ldset_get_entry(uintptr_t addr, uintptr_t *addrp, size_t ptrsize)
122 {
123 union {
124 uint64_t u64;
125 uint32_t u32;
126 } val;
127 ssize_t n;
128
129 switch (ptrsize) {
130 case sizeof (uint32_t):
131 n = mdb_vread(&val.u32, sizeof (uint32_t), addr);
132 *addrp = (uintptr_t)val.u32;
133 break;
134 case sizeof (uint64_t):
135 n = mdb_vread(&val.u64, sizeof (uint64_t), addr);
136 *addrp = (uintptr_t)val.u64;
137 break;
138 default:
139 return (set_errno(ENOTSUP));
140 }
141
142 if (n != ptrsize) {
143 /* XXX: Better error value? */
144 return (set_errno(ENODATA));
145 }
146
147 return (0);
148 }
149
150 static ssize_t
ldset_item_size(uintptr_t addr)151 ldset_item_size(uintptr_t addr)
152 {
153 mdb_ctf_id_t id;
154 int ret;
155
156 ret = mdb_ctf_lookup_by_addr(addr, &id);
157 if (ret != 0) {
158 return ((ssize_t)ret);
159 }
160
161 return (mdb_ctf_type_size(id));
162 }
163
164 static int
ldset_get_info(uintptr_t addr,ldset_info_t * ldsi)165 ldset_get_info(uintptr_t addr, ldset_info_t *ldsi)
166 {
167 GElf_Sym start_sym = { 0 };
168 GElf_Sym stop_sym = { 0 };
169 char name[MDB_SYM_NAMLEN] = { 0 };
170 uintptr_t item_addr;
171 int ret;
172
173 switch (mdb_tgt_dmodel(mdb.m_target)) {
174 case MDB_TGT_MODEL_LP64:
175 ldsi->ldsi_ptrsize = sizeof (uint64_t);
176 break;
177 case MDB_TGT_MODEL_ILP32:
178 ldsi->ldsi_ptrsize = sizeof (uint32_t);
179 break;
180 default:
181 return (set_errno(ENOTSUP));
182 }
183
184 ret = mdb_tgt_lookup_by_addr(mdb.m_target, addr, MDB_TGT_SYM_EXACT,
185 name, sizeof (name), &start_sym, NULL);
186 if (ret != 0) {
187 return (ret);
188 }
189
190 if (ldset_name_from_item(name, ldsi->ldsi_name,
191 sizeof (ldsi->ldsi_name)) != 0) {
192 return (-1);
193 }
194
195 ret = ldset_get_sym(STOP_PREFIX, ldsi->ldsi_name, &stop_sym);
196 if (ret != 0) {
197 return (-1);
198 }
199
200 if (stop_sym.st_value < addr) {
201 return (set_errno(EINVAL));
202 }
203
204 if (ldset_get_entry(addr, &item_addr, ldsi->ldsi_ptrsize) != 0) {
205 return (-1);
206 }
207
208 ldsi->ldsi_addr = addr;
209 ldsi->ldsi_endaddr = stop_sym.st_value;
210 ldsi->ldsi_nelem = (stop_sym.st_value - addr) / ldsi->ldsi_ptrsize;
211 ldsi->ldsi_elsize = ldset_item_size(item_addr);
212
213 return (0);
214 }
215
216 static int
ldsets_init_cb(void * data,const GElf_Sym * sym,const char * name,const mdb_syminfo_t * sip,const char * obj)217 ldsets_init_cb(void *data, const GElf_Sym *sym, const char *name,
218 const mdb_syminfo_t *sip, const char *obj)
219 {
220 mdb_nv_t *nv = data;
221 const char *ldset_name;
222 GElf_Sym stop_sym = { 0 };
223 int ret;
224
225 if (strncmp(name, START_PREFIX, sizeof (START_PREFIX) - 1) != 0) {
226 return (0);
227 }
228
229 /*
230 * The name of the linker set should follow START_PREFIX. If there's
231 * nothing there, then it's not a linker set, so skip this symbol.
232 */
233 ldset_name = name + sizeof (START_PREFIX) - 1;
234 if (*ldset_name == '\0') {
235 return (0);
236 }
237
238 ret = ldset_get_sym(STOP_PREFIX, ldset_name, &stop_sym);
239 if (ret != 0) {
240 /* If there's no stop symbol, we just ignore */
241 if (errno == ENOENT) {
242 errno = 0;
243 return (0);
244 }
245 return (-1);
246 }
247
248 /*
249 * The stop symbol should be at the same or higher address than
250 * the start symbol. If not, we ignore.
251 */
252 if (stop_sym.st_value < sym->st_value) {
253 return (0);
254 }
255
256 if (mdb_nv_insert(nv, ldset_name, NULL, sym->st_value,
257 MDB_NV_RDONLY) == NULL) {
258 return (-1);
259 }
260
261 return (0);
262 }
263
264 /*
265 * Initialize an mdb_nv_t with the name/addr of all the linkersets found in
266 * the target.
267 */
268 static int
ldsets_nv_init(mdb_nv_t * nv,uint_t flags)269 ldsets_nv_init(mdb_nv_t *nv, uint_t flags)
270 {
271 if (mdb_nv_create(nv, flags) == NULL)
272 return (-1);
273
274 return (mdb_tgt_symbol_iter(mdb.m_target, MDB_TGT_OBJ_EVERY,
275 MDB_TGT_SYMTAB, MDB_TGT_BIND_ANY | MDB_TGT_TYPE_NOTYPE,
276 ldsets_init_cb, nv));
277 }
278
279 int
ldsets_walk_init(mdb_walk_state_t * wsp)280 ldsets_walk_init(mdb_walk_state_t *wsp)
281 {
282 mdb_nv_t *nv;
283 int ret;
284
285 nv = mdb_zalloc(sizeof (*nv), UM_SLEEP | UM_GC);
286 ret = ldsets_nv_init(nv, UM_SLEEP | UM_GC);
287 if (ret != 0) {
288 return (ret);
289 }
290
291 mdb_nv_rewind(nv);
292 wsp->walk_data = nv;
293 return (WALK_NEXT);
294 }
295
296 int
ldsets_walk_step(mdb_walk_state_t * wsp)297 ldsets_walk_step(mdb_walk_state_t *wsp)
298 {
299 mdb_nv_t *nv = wsp->walk_data;
300 mdb_var_t *v = mdb_nv_advance(nv);
301 int status;
302
303 if (v == NULL) {
304 return (WALK_DONE);
305 }
306
307 wsp->walk_addr = mdb_nv_get_value(v);
308 status = wsp->walk_callback(wsp->walk_addr, NULL, wsp->walk_cbdata);
309 return (status);
310 }
311
312 int
ldset_walk_init(mdb_walk_state_t * wsp)313 ldset_walk_init(mdb_walk_state_t *wsp)
314 {
315 ldset_info_t *ldsi;
316 int ret;
317
318 ldsi = mdb_zalloc(sizeof (*ldsi), UM_SLEEP | UM_GC);
319
320 ret = ldset_get_info(wsp->walk_addr, ldsi);
321 if (ret != 0)
322 return (WALK_ERR);
323
324 wsp->walk_data = ldsi;
325 return (WALK_NEXT);
326 }
327
328 int
ldset_walk_step(mdb_walk_state_t * wsp)329 ldset_walk_step(mdb_walk_state_t *wsp)
330 {
331 ldset_info_t *ldsi = wsp->walk_data;
332 uintptr_t addr;
333 int ret;
334
335 if (wsp->walk_addr >= ldsi->ldsi_endaddr) {
336 return (WALK_DONE);
337 }
338
339 ret = ldset_get_entry(wsp->walk_addr, &addr, ldsi->ldsi_ptrsize);
340 if (ret != 0) {
341 return (WALK_ERR);
342 }
343
344 ret = wsp->walk_callback(addr, NULL, wsp->walk_cbdata);
345
346 wsp->walk_addr += ldsi->ldsi_ptrsize;
347 return (ret);
348 }
349
350 static int
linkerset_walk_cb(uintptr_t addr,const void * data,void * cbarg)351 linkerset_walk_cb(uintptr_t addr, const void *data, void *cbarg)
352 {
353 mdb_printf("%lr\n", addr);
354 return (0);
355 }
356
357 static int
linkersets_walk_cb(uintptr_t addr,const void * data,void * cbarg)358 linkersets_walk_cb(uintptr_t addr, const void *data, void *cbarg)
359 {
360 ldset_info_t info = { 0 };
361 int ret;
362 char buf[64]; /* big enough for element size in any radix */
363
364 ret = ldset_get_info(addr, &info);
365 if (ret != 0)
366 return (WALK_ERR);
367
368 if (info.ldsi_elsize > 0) {
369 (void) mdb_snprintf(buf, sizeof (buf), "%#r",
370 info.ldsi_elsize);
371 } else {
372 (void) strlcpy(buf, "?", sizeof (buf));
373 }
374
375 mdb_printf("%-20s %8s %9u\n", info.ldsi_name, buf, info.ldsi_nelem);
376 return (WALK_NEXT);
377 }
378
379 int
cmd_linkerset(uintptr_t addr,uint_t flags,int argc,const mdb_arg_t * argv)380 cmd_linkerset(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
381 {
382 int ret;
383
384 if (argc > 1) {
385 return (DCMD_USAGE);
386 }
387
388 /* Walk a linkerset given by the first argument */
389 if (argc == 1) {
390 const char *setname = argv->a_un.a_str;
391 GElf_Sym start_sym = { 0 };
392 ldset_info_t info = { 0 };
393
394 if (argv->a_type != MDB_TYPE_STRING) {
395 return (DCMD_USAGE);
396 }
397
398 ret = ldset_get_sym(START_PREFIX, setname, &start_sym);
399 if (ret != 0) {
400 mdb_warn("Failed to get address of linkerset");
401 return (-1);
402 }
403
404 ret = ldset_get_info((uintptr_t)start_sym.st_value, &info);
405 if (ret != 0) {
406 mdb_warn("Failed to get information on linkerset");
407 return (-1);
408 }
409
410 return (mdb_pwalk("linkerset", linkerset_walk_cb, NULL,
411 info.ldsi_addr));
412 }
413
414 /* Display all the known linkersets */
415 if (DCMD_HDRSPEC(flags)) {
416 mdb_printf("%<b>%<u>%-20s %-8s %-9s%</u>%</b>\n",
417 "NAME", "ITEMSIZE", "ITEMCOUNT");
418 }
419
420 return (mdb_walk("linkersets", linkersets_walk_cb, NULL));
421 }
422
423 static int
ldset_complete(mdb_var_t * v,void * arg)424 ldset_complete(mdb_var_t *v, void *arg)
425 {
426 mdb_tab_cookie_t *mcp = arg;
427
428 mdb_tab_insert(mcp, mdb_nv_get_name(v));
429 return (0);
430 }
431
432 static int
ldset_tab_complete(mdb_tab_cookie_t * mcp,const char * ldset)433 ldset_tab_complete(mdb_tab_cookie_t *mcp, const char *ldset)
434 {
435 mdb_nv_t nv = { 0 };
436 int ret;
437
438 ret = ldsets_nv_init(&nv, UM_GC | UM_SLEEP);
439 if (ret != 0) {
440 return (ret);
441 }
442
443 if (ldset != NULL) {
444 mdb_tab_setmbase(mcp, ldset);
445 }
446
447 mdb_nv_sort_iter(&nv, ldset_complete, mcp, UM_GC | UM_SLEEP);
448 return (1);
449 }
450
451 int
cmd_linkerset_tab(mdb_tab_cookie_t * mcp,uint_t flags,int argc,const mdb_arg_t * argv)452 cmd_linkerset_tab(mdb_tab_cookie_t *mcp, uint_t flags, int argc,
453 const mdb_arg_t *argv)
454 {
455 if (argc > 1)
456 return (1);
457
458 if (argc == 1) {
459 ASSERT(argv[0].a_type == MDB_TYPE_STRING);
460 return (ldset_tab_complete(mcp, argv[0].a_un.a_str));
461 }
462
463 if (argc == 0 && (flags & DCMD_TAB_SPACE) != 0) {
464 return (ldset_tab_complete(mcp, NULL));
465 }
466
467 return (1);
468 }
469
470 void
linkerset_help(void)471 linkerset_help(void)
472 {
473 static const char ldset_desc[] =
474 "A linker set is an array of pointers to objects in a target that have been\n"
475 "collected by the linker. The start and end location of each linker set\n"
476 "is designated by weak symbols with well known strings prefixed to the\n"
477 "name of the linker set.\n"
478 "\n"
479 "When invoked without any arguments, the ::linkerset command will attempt to\n"
480 "enumerate all linker sets present in the target. For each linker set, the \n"
481 "name, number of objects in the set, as well as the size of each object (when\n"
482 "known) is displayed. The ::linkerset command uses the CTF information to\n"
483 "determine the size of each object. If the CTF data is unavailable for a\n"
484 "given linkerset, '?' will displayed instead of the size.\n"
485 "\n"
486 "The ::linkerset command can also be invoked with a single argument -- the\n"
487 "name of a specific linker set. In this invocation, the ::linkerset command\n"
488 "will display the addresses of each object in the set and can be used as\n"
489 "part of a command pipeline.\n";
490
491 static const char ldset_examples[] =
492 " ::linkerset\n"
493 " ::linkerset sysinit_set | ::print 'struct sysinit'\n";
494
495 mdb_printf("%s\n", ldset_desc);
496 (void) mdb_dec_indent(2);
497 mdb_printf("%<b>EXAMPLES%</b>\n");
498 (void) mdb_inc_indent(2);
499 mdb_printf("%s\n", ldset_examples);
500 }
501