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