1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2005 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 /* 29 * Utilities to handle shared object dependency graph. 30 * 31 * The algorithms used in this file are taken from the following book: 32 * Algorithms in C 33 * Robert Sedgewick 34 * Addison-Wesley Publishing company 35 * ISBN 0-201-51425-7 36 * From the following chapters: 37 * Chapter 29 Elementary Graph Algorithms 38 * Chapter 32 Directed Graph 39 */ 40 #include "_synonyms.h" 41 42 #include <sys/types.h> 43 #include <stdarg.h> 44 #include <stdio.h> 45 #include <dlfcn.h> 46 #include <signal.h> 47 #include <locale.h> 48 #include <string.h> 49 #include <libintl.h> 50 #include "_rtld.h" 51 #include "msg.h" 52 #include "debug.h" 53 54 /* 55 * Structure for maintaining sorting state. 56 */ 57 typedef struct { 58 Rt_map **s_lmpa; /* link-map[] (returned to caller) */ 59 Rt_map *s_lmp; /* originating link-map */ 60 Rt_map **s_stack; /* strongly connected component stack */ 61 Alist *s_scc; /* cyclic list */ 62 Alist *s_queue; /* depth queue for cyclic components */ 63 int s_sndx; /* present stack index */ 64 int s_lndx; /* present link-map index */ 65 int s_num; /* number of objects to sort */ 66 int s_initfirst; /* no. of INITFIRST entries */ 67 } Sort; 68 69 #define AL_CNT_SCC 10 70 71 /* 72 * qsort(3c) comparison function. 73 */ 74 static int 75 compare(const void * lmpp1, const void * lmpp2) 76 { 77 Rt_map *lmp1 = *((Rt_map **)lmpp1); 78 Rt_map *lmp2 = *((Rt_map **)lmpp2); 79 80 if (IDX(lmp1) > IDX(lmp2)) 81 return (-1); 82 if (IDX(lmp1) < IDX(lmp2)) 83 return (1); 84 return (0); 85 } 86 87 /* 88 * This routine is called when a cyclic dependency is detected between strongly 89 * connected components. The nodes within the cycle are reverse breadth-first 90 * sorted. 91 */ 92 static int 93 sort_scc(Sort * sort, int fndx, int flag) 94 { 95 static const char *tfmt = 0, *ffmt; 96 static int cnt = 1; 97 int ndx; 98 Rt_map *lmp; 99 Word lmflags = LIST(sort->s_lmp)->lm_flags; 100 Word init, unref; 101 102 /* 103 * If this is the first cyclic dependency traverse the new objects that 104 * have been added to the link-map list and for each object establish 105 * a unique depth index. We build this dynamically as we have no idea 106 * of the number of objects that will be inspected (logic matches that 107 * used by dlsym() to traverse lazy dependencies). 108 */ 109 if (sort->s_queue == 0) { 110 Aliste off; 111 Rt_map *lmp, **lmpp; 112 113 lmp = sort->s_lmp; 114 ndx = 1; 115 116 if (alist_append(&(sort->s_queue), &lmp, sizeof (Rt_map *), 117 sort->s_num) == 0) 118 return (0); 119 120 IDX(lmp) = ndx++; 121 122 for (ALIST_TRAVERSE(sort->s_queue, off, lmpp)) { 123 Bnd_desc **bdpp; 124 Aliste off; 125 126 for (ALIST_TRAVERSE(DEPENDS(*lmpp), off, bdpp)) { 127 Rt_map *lmp = (*bdpp)->b_depend; 128 129 if (IDX(lmp)) 130 continue; 131 132 /* 133 * If we're .init processing and this depend- 134 * encies .init has been called, skip it. 135 */ 136 if ((flag & RT_SORT_REV) && 137 (FLAGS(lmp) & FLG_RT_INITCALL)) 138 continue; 139 140 if (alist_append(&(sort->s_queue), &lmp, 141 sizeof (Rt_map *), sort->s_num) == 0) 142 return (0); 143 144 IDX(lmp) = ndx++; 145 } 146 } 147 } 148 149 /* 150 * Sort the cyclics. 151 */ 152 qsort(&(sort->s_lmpa[fndx]), sort->s_lndx - fndx, sizeof (Rt_map *), 153 compare); 154 155 /* 156 * Under ldd -i, or debugging, print this cycle. Under ldd -i/-U assign 157 * each object a group identifier so that cyclic dependent callers can 158 * be better traced (see trace_sort()), or analyzed for non-use. 159 */ 160 if (((init = (lmflags & LML_FLG_TRC_INIT)) == 0) && 161 ((unref = (lmflags & LML_FLG_TRC_UNREF)) == 0) && (dbg_mask == 0)) 162 return (1); 163 164 if (init) { 165 if (tfmt == 0) { 166 tfmt = MSG_INTL(MSG_LDD_INIT_FMT_01); 167 ffmt = MSG_ORIG(MSG_LDD_INIT_FMT_FILE); 168 } 169 (void) printf(tfmt, cnt); 170 } 171 DBG_CALL(Dbg_scc_title(flag & RT_SORT_REV)); 172 173 /* 174 * Identify this cyclic group, and under ldd -i print the cycle in the 175 * order its components will be run. 176 */ 177 if (flag & RT_SORT_REV) { 178 for (ndx = fndx; ndx < sort->s_lndx; ndx++) { 179 lmp = sort->s_lmpa[ndx]; 180 CYCGROUP(lmp) = cnt; 181 182 if (init) 183 (void) printf(ffmt, NAME(lmp)); 184 DBG_CALL(Dbg_scc_entry(IDX(lmp), NAME(lmp))); 185 } 186 cnt++; 187 188 } else if (dbg_mask) { 189 for (ndx = sort->s_lndx - 1; ndx >= fndx; ndx--) { 190 lmp = sort->s_lmpa[ndx]; 191 DBG_CALL(Dbg_scc_entry(IDX(lmp), NAME(lmp))); 192 } 193 } 194 195 /* 196 * If we're looking for unused dependencies determine if any of these 197 * cyclic components are referenced from outside of the cycle. 198 */ 199 if (unref || dbg_mask) { 200 Bnd_desc ** bdpp; 201 202 for (ndx = fndx; ndx < sort->s_lndx; ndx++) { 203 Aliste off; 204 205 lmp = sort->s_lmpa[ndx]; 206 207 /* 208 * If this object has a handle then it can be in use by 209 * anyone. 210 */ 211 if (HANDLES(lmp)) 212 return (1); 213 214 /* 215 * Traverse this objects callers looking for outside 216 * references. 217 */ 218 for (ALIST_TRAVERSE(CALLERS(lmp), off, bdpp)) { 219 Bnd_desc *bdp = *bdpp; 220 Rt_map *clmp = bdp->b_caller; 221 222 if ((bdp->b_flags & BND_REFER) == 0) 223 continue; 224 225 if (CYCGROUP(lmp) != CYCGROUP(clmp)) 226 return (1); 227 } 228 } 229 230 /* 231 * If we're here then none of the cyclic dependents have been 232 * referenced from outside of the cycle, mark them as unused. 233 */ 234 for (ndx = fndx; ndx < sort->s_lndx; ndx++) { 235 lmp = sort->s_lmpa[ndx]; 236 FLAGS1(lmp) &= ~FL1_RT_USED; 237 } 238 } 239 return (1); 240 } 241 242 /* 243 * Take elements off of the stack and move them to the link-map array. Typically 244 * this routine just pops one strongly connected component (individual link-map) 245 * at a time. When a cyclic dependency has been detected the stack will contain 246 * more than just the present object to process, and will trigger the later call 247 * to sort_scc() to sort these elements. 248 */ 249 static int 250 visit(Lm_list *lml, Rt_map * lmp, Sort * sort, int flag) 251 { 252 Alist *alpp = 0; 253 int num = sort->s_lndx; 254 Word tracing = lml->lm_flags & LML_FLG_TRC_ENABLE; 255 Rt_map *tlmp; 256 257 do { 258 tlmp = sort->s_stack[--(sort->s_sndx)]; 259 sort->s_lmpa[(sort->s_lndx)++] = tlmp; 260 261 if (flag & RT_SORT_REV) { 262 FLAGS(tlmp) |= FLG_RT_INITCLCT; 263 lml->lm_init--; 264 } else 265 FLAGS(tlmp) |= FLG_RT_FINICLCT; 266 267 SORTVAL(sort->s_stack[sort->s_sndx]) = sort->s_num; 268 269 /* 270 * If tracing, save the strongly connected component. 271 */ 272 if (tracing && (alist_append(&alpp, &tlmp, sizeof (Rt_map *), 273 AL_CNT_SCC) == 0)) 274 return (0); 275 } while (tlmp != lmp); 276 277 /* 278 * Determine if there are cyclic dependencies to process. If so, sort 279 * the components, and retain them for tracing output. 280 */ 281 if (sort->s_lndx > (num + 1)) { 282 if (sort_scc(sort, num, flag) == 0) 283 return (0); 284 285 if (tracing && (alist_append(&(sort->s_scc), &alpp, 286 sizeof (Alist *), AL_CNT_SCC) == 0)) 287 return (0); 288 } else if (alpp) 289 free(alpp); 290 291 return (1); 292 } 293 294 /* 295 * Visit the dependencies of each object. 296 */ 297 static uint_t 298 dep_visit(Rt_map *lmp, Lm_list *lml, Sort *sort, uint_t *id, int flag) 299 { 300 uint_t min; 301 Aliste off; 302 Bnd_desc ** bdpp; 303 304 min = SORTVAL(lmp) = ++(*id); 305 306 sort->s_stack[(sort->s_sndx)++] = lmp; 307 308 if (FLAGS(lmp) & FLG_RT_INITFRST) 309 sort->s_initfirst++; 310 311 /* 312 * Traverse both explicit and implicit dependencies. 313 */ 314 for (ALIST_TRAVERSE(DEPENDS(lmp), off, bdpp)) { 315 Rt_map * dlmp = (*bdpp)->b_depend; 316 uint_t _min; 317 318 /* 319 * Only collect objects that belong to the callers link-map. 320 */ 321 if (LIST(dlmp) != lml) 322 continue; 323 324 if (flag & RT_SORT_REV) { 325 /* 326 * For .init processing, only collect objects that have 327 * been relocated and haven't already been collected. 328 */ 329 if ((FLAGS(dlmp) & (FLG_RT_RELOCED | 330 FLG_RT_INITCLCT)) != FLG_RT_RELOCED) 331 continue; 332 } else { 333 /* 334 * For .fini processing only collect objects that have 335 * had their .init collected, and haven't already been 336 * .fini collected. 337 */ 338 if ((FLAGS(dlmp) & (FLG_RT_INITCLCT | 339 FLG_RT_FINICLCT)) != FLG_RT_INITCLCT) 340 continue; 341 342 /* 343 * If we're deleting a subset of objects only collect 344 * those marked for deletion. 345 */ 346 if ((flag & RT_SORT_DELETE) && 347 ((FLAGS(dlmp) & FLG_RT_DELETE) == 0)) 348 continue; 349 } 350 351 if ((_min = SORTVAL(dlmp)) == 0) { 352 if ((_min = dep_visit(dlmp, lml, sort, id, flag)) == 0) 353 return (0); 354 } 355 if (_min < min) 356 min = _min; 357 } 358 359 if (min == SORTVAL(lmp)) { 360 if (visit(lml, lmp, sort, flag) == 0) 361 return (0); 362 } 363 return (min); 364 } 365 366 367 #ifndef LD_BREADTH_DISABLED 368 /* 369 * Reverse LD_BREATH search (used to fire .init's the old fashioned way). 370 */ 371 static void 372 rb_visit(Rt_map * lmp, Sort * sort) 373 { 374 Rt_map * nlmp; 375 376 if ((nlmp = (Rt_map *)NEXT(lmp)) != 0) 377 rb_visit(nlmp, sort); 378 379 /* 380 * Only collect objects that have been relocated and haven't already 381 * been collected. 382 */ 383 if ((FLAGS(lmp) & (FLG_RT_RELOCED | FLG_RT_INITCLCT)) == 384 FLG_RT_RELOCED) { 385 sort->s_lmpa[(sort->s_lndx)++] = lmp; 386 FLAGS(lmp) |= FLG_RT_INITCLCT; 387 } 388 } 389 390 /* 391 * Forward LD_BREATH search (used to fire .fini's the old fashioned way). 392 */ 393 static void 394 fb_visit(Rt_map * lmp, Sort * sort, int flag) 395 { 396 while (lmp) { 397 /* 398 * If we're called from dlclose() then we only collect those 399 * objects marked for deletion. 400 */ 401 if (!(flag & RT_SORT_DELETE) || (FLAGS(lmp) & FLG_RT_DELETE)) { 402 /* 403 * Only collect objects that have had their .init 404 * collected, and haven't already been .fini collected. 405 */ 406 if ((FLAGS(lmp) & 407 (FLG_RT_INITCLCT | FLG_RT_FINICLCT)) == 408 (FLG_RT_INITCLCT)) { 409 sort->s_lmpa[(sort->s_lndx)++] = lmp; 410 FLAGS(lmp) |= FLG_RT_FINICLCT; 411 } 412 } 413 lmp = (Rt_map *)NEXT(lmp); 414 } 415 } 416 #endif 417 418 /* 419 * Find corresponding strongly connected component structure. 420 */ 421 static Alist * 422 trace_find_scc(Sort * sort, Rt_map * lmp) 423 { 424 Alist **alpp; 425 Aliste off1; 426 427 for (ALIST_TRAVERSE(sort->s_scc, off1, alpp)) { 428 Rt_map **lmpp; 429 Aliste off2; 430 431 for (ALIST_TRAVERSE(*alpp, off2, lmpp)) { 432 if (lmp == *lmpp) 433 return (*alpp); 434 } 435 } 436 return (NULL); 437 } 438 439 /* 440 * Print out the .init dependency information (ldd). 441 */ 442 static void 443 trace_sort(Sort * sort) 444 { 445 int ndx = 0; 446 Alist *alp; 447 Rt_map *lmp1; 448 449 (void) printf(MSG_ORIG(MSG_STR_NL)); 450 451 while ((lmp1 = sort->s_lmpa[ndx++]) != NULL) { 452 static const char *ffmt, *cfmt = 0, *sfmt = 0; 453 Bnd_desc ** bdpp; 454 Aliste off1; 455 456 if (INIT(lmp1) == 0) 457 continue; 458 459 if (sfmt == 0) 460 sfmt = MSG_INTL(MSG_LDD_INIT_FMT_02); 461 462 #ifndef LD_BREADTH_DISABLED 463 if (rtld_flags & RT_FL_BREADTH) { 464 (void) printf(sfmt, NAME(lmp1)); 465 continue; 466 } 467 #endif 468 /* 469 * If the only component on the strongly connected list is 470 * this link-map, then there are no dependencies. 471 */ 472 if ((alp = trace_find_scc(sort, lmp1)) == NULL) { 473 (void) printf(sfmt, NAME(lmp1)); 474 continue; 475 } 476 477 /* 478 * Establish message formats for cyclic dependencies. 479 */ 480 if (cfmt == 0) { 481 cfmt = MSG_INTL(MSG_LDD_INIT_FMT_03); 482 ffmt = MSG_ORIG(MSG_LDD_INIT_FMT_FILE); 483 } 484 485 (void) printf(cfmt, NAME(lmp1), CYCGROUP(lmp1)); 486 487 for (ALIST_TRAVERSE(CALLERS(lmp1), off1, bdpp)) { 488 Rt_map **lmpp3, *lmp2 = (*bdpp)->b_caller; 489 Aliste off2; 490 491 for (ALIST_TRAVERSE(alp, off2, lmpp3)) { 492 if (lmp2 != *lmpp3) 493 continue; 494 495 (void) printf(ffmt, NAME(*lmpp3)); 496 } 497 } 498 } 499 } 500 501 /* 502 * A reverse ordered list (for .init's) contains INITFIRST elements. Move each 503 * of these elements to the front of the list. 504 */ 505 static void 506 r_initfirst(Sort * sort, int end) 507 { 508 Rt_map * tlmp; 509 int bgn, ifst, lifst = 0; 510 511 for (bgn = 0; bgn < sort->s_initfirst; bgn++) { 512 for (ifst = lifst; ifst <= end; ifst++) { 513 tlmp = sort->s_lmpa[ifst]; 514 515 if (!(FLAGS(tlmp) & FLG_RT_INITFRST)) 516 continue; 517 518 /* 519 * If the INITFIRST element is already at the front of 520 * the list leave it there. 521 */ 522 if (ifst == bgn) { 523 lifst = ifst + 1; 524 break; 525 } 526 527 /* 528 * Move the elements from the front of the list up to 529 * the INITFIRST element, back one position. 530 */ 531 (void) memmove(&sort->s_lmpa[bgn + 1], 532 &sort->s_lmpa[bgn], 533 ((ifst - bgn) * sizeof (Rt_map *))); 534 535 /* 536 * Insert INITFIRST element at the front of the list. 537 */ 538 sort->s_lmpa[bgn] = tlmp; 539 lifst = ifst + 1; 540 break; 541 } 542 } 543 } 544 545 /* 546 * A forward ordered list (for .fini's) contains INITFIRST elements. Move each 547 * of these elements to the front of the list. 548 */ 549 static void 550 f_initfirst(Sort * sort, int end) 551 { 552 Rt_map * tlmp; 553 int bgn, ifst, lifst = 0; 554 555 for (bgn = 0; bgn < sort->s_initfirst; bgn++) { 556 for (ifst = lifst; ifst <= end; ifst++) { 557 tlmp = sort->s_lmpa[ifst]; 558 559 if (!(FLAGS(tlmp) & FLG_RT_INITFRST)) 560 continue; 561 562 /* 563 * If the INITFIRST element is already at the end of 564 * the list leave it there. 565 */ 566 if (ifst == end) 567 break; 568 569 /* 570 * Move the elements from after the INITFIRST element 571 * up to the back of the list, up one position. 572 */ 573 (void) memmove(&sort->s_lmpa[ifst], 574 &sort->s_lmpa[ifst + 1], 575 ((end - ifst) * sizeof (Rt_map *))); 576 577 /* 578 * Insert INITFIRST element at the back of the list. 579 */ 580 sort->s_lmpa[end--] = tlmp; 581 lifst = ifst; 582 break; 583 } 584 } 585 } 586 587 /* 588 * Sort the dependency 589 */ 590 Rt_map ** 591 tsort(Rt_map * lmp, int num, int flag) 592 { 593 Rt_map * _lmp; 594 Lm_list * lml = LIST(lmp); 595 uint_t id = 0; 596 Word init = lml->lm_flags & LML_FLG_TRC_INIT; 597 Sort sort = { 0 }; 598 599 if (num == 0) 600 return (0); 601 602 /* 603 * Prior to tsorting any .init sections, insure that the `environ' 604 * symbol is initialized for this link-map list. 605 */ 606 if ((flag & RT_SORT_REV) && ((lml->lm_flags & 607 (LML_FLG_TRC_ENABLE | LML_FLG_ENVIRON)) == 0)) 608 set_environ(lml); 609 610 /* 611 * Allocate memory for link-map list array. Calloc the array to insure 612 * all elements are zero, we might find that no objects need processing. 613 */ 614 sort.s_lmp = lmp; 615 sort.s_num = num + 1; 616 if ((sort.s_lmpa = calloc(sort.s_num, sizeof (Rt_map *))) == NULL) 617 return ((Rt_map **)S_ERROR); 618 619 #ifndef LD_BREADTH_DISABLED 620 /* 621 * A breadth first search is easy, simply add each object to the 622 * link-map array. 623 */ 624 if (rtld_flags & RT_FL_BREADTH) { 625 if (flag & RT_SORT_REV) 626 rb_visit(lmp, &sort); 627 else 628 fb_visit(lmp, &sort, flag); 629 630 /* 631 * If tracing init sections (only meaningful for RT_SORT_REV) 632 * print out the sorted dependencies. 633 */ 634 if (init) 635 trace_sort(&sort); 636 637 return (sort.s_lmpa); 638 } 639 #endif 640 /* 641 * We need to topologically sort the dependencies. 642 */ 643 if ((sort.s_stack = malloc(sort.s_num * sizeof (Rt_map *))) == NULL) 644 return ((Rt_map **)S_ERROR); 645 646 /* 647 * Determine where to start searching for tsort() candidates. Any call 648 * to tsort() for .init processing is passed the link-map from which to 649 * start searching. However, previously loaded, uninitialized objects 650 * may be dependencies of newly loaded objects, and in this case start 651 * at the head of the link-map list, not the new link-map itself. 652 * These previously loaded objects will have been tagged for inclusion 653 * in this tsort() pass. They still remain on an existing tsort() list, 654 * which must have been prempted for control to have arrived here. 655 * However, they will be ignored when encountered on any previous 656 * tsort() list if their init has already been called. 657 */ 658 if (LIST(lmp)->lm_flags & LML_FLG_BNDUNINIT) { 659 LIST(lmp)->lm_flags &= ~LML_FLG_BNDUNINIT; 660 _lmp = LIST(lmp)->lm_head; 661 } else 662 _lmp = lmp; 663 664 for (; _lmp; _lmp = (Rt_map *)NEXT(_lmp)) { 665 if (flag & RT_SORT_REV) { 666 /* 667 * For .init processing, only collect objects that have 668 * been relocated and haven't already been collected. 669 */ 670 if ((FLAGS(_lmp) & (FLG_RT_RELOCED | 671 FLG_RT_INITCLCT)) != FLG_RT_RELOCED) 672 continue; 673 674 if (dep_visit(_lmp, lml, &sort, &id, flag) == 0) 675 return ((Rt_map **)S_ERROR); 676 677 } else if (!(flag & RT_SORT_DELETE) || 678 (FLAGS(_lmp) & FLG_RT_DELETE)) { 679 /* 680 * Only collect objects that have had their .init 681 * collected, and haven't already been .fini collected. 682 */ 683 if (!((FLAGS(_lmp) & 684 (FLG_RT_INITCLCT | FLG_RT_FINICLCT)) == 685 (FLG_RT_INITCLCT))) 686 continue; 687 688 if (dep_visit(_lmp, lml, &sort, &id, flag) == 0) 689 return ((Rt_map **)S_ERROR); 690 } 691 } 692 693 /* 694 * The dependencies have been collected such that they are appropriate 695 * for an .init order, for .fini order reverse them. 696 */ 697 if (flag & RT_SORT_FWD) { 698 int bgn = 0, end = sort.s_lndx - 1; 699 700 while (bgn < end) { 701 Rt_map * tlmp = sort.s_lmpa[end]; 702 703 sort.s_lmpa[end] = sort.s_lmpa[bgn]; 704 sort.s_lmpa[bgn] = tlmp; 705 706 bgn++, end--; 707 } 708 } 709 710 /* 711 * If INITFIRST objects have been collected then move them to the front 712 * or end of the list as appropriate. 713 */ 714 if (sort.s_initfirst) { 715 if (flag & RT_SORT_REV) 716 r_initfirst(&sort, sort.s_lndx - 1); 717 else 718 f_initfirst(&sort, sort.s_lndx - 1); 719 } 720 721 /* 722 * If tracing init sections (only meaningful for RT_SORT_REV), print 723 * out the sorted dependencies. 724 */ 725 if (init) 726 trace_sort(&sort); 727 728 /* 729 * Clean any temporary structures prior to return. 730 */ 731 if (sort.s_stack) 732 free(sort.s_stack); 733 734 if (sort.s_queue) { 735 Aliste off; 736 Rt_map **lmpp; 737 738 /* 739 * Traverse the link-maps collected on the sort queue and 740 * delete the depth index. These link-maps may be traversed 741 * again to sort other components either for .inits, and almost 742 * certainly for .finis. 743 */ 744 for (ALIST_TRAVERSE(sort.s_queue, off, lmpp)) 745 IDX(*lmpp) = 0; 746 747 free(sort.s_queue); 748 } 749 750 if (sort.s_scc) { 751 Aliste off; 752 Alist **alpp; 753 754 for (ALIST_TRAVERSE(sort.s_scc, off, alpp)) 755 free(*alpp); 756 free(sort.s_scc); 757 } 758 759 /* 760 * The caller is responsible for freeing the sorted link-map list once 761 * the associated .init/.fini's have been fired. 762 */ 763 return (sort.s_lmpa); 764 } 765