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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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