/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Utilities to handle shared object dependency graph. * * The algorithms used in this file are taken from the following book: * Algorithms in C * Robert Sedgewick * Addison-Wesley Publishing company * ISBN 0-201-51425-7 * From the following chapters: * Chapter 29 Elementary Graph Algorithms * Chapter 32 Directed Graph */ #include #include #include #include #include #include #include #include #include #include "_rtld.h" #include "msg.h" /* * Structure for maintaining sorting state. */ typedef struct { Rt_map **s_lmpa; /* link-map[] (returned to caller) */ Rt_map *s_lmp; /* originating link-map */ Rt_map **s_stack; /* strongly connected component stack */ APlist *s_scc; /* cyclic list */ APlist *s_queue; /* depth queue for cyclic components */ int s_sndx; /* present stack index */ int s_lndx; /* present link-map index */ int s_num; /* number of objects to sort */ int s_initfirst; /* no. of INITFIRST entries */ } Sort; #define AL_CNT_SCC 10 /* * qsort(3c) comparison function. */ static int compare(const void *lmpp1, const void *lmpp2) { Rt_map *lmp1 = *((Rt_map **)lmpp1); Rt_map *lmp2 = *((Rt_map **)lmpp2); if (IDX(lmp1) > IDX(lmp2)) return (-1); if (IDX(lmp1) < IDX(lmp2)) return (1); return (0); } /* * This routine is called when a cyclic dependency is detected between strongly * connected components. The nodes within the cycle are reverse breadth-first * sorted. */ static int sort_scc(Sort *sort, int fndx, int flag) { static const char *tfmt = 0, *ffmt; static int cnt = 1; int ndx; Rt_map *lmp; Lm_list *lml = LIST(sort->s_lmp); Word lmflags = lml->lm_flags; Word init, unref; /* * If this is the first cyclic dependency traverse the new objects that * have been added to the link-map list and for each object establish * a unique depth index. We build this dynamically as we have no idea * of the number of objects that will be inspected (logic matches that * used by dlsym() to traverse lazy dependencies). */ if (sort->s_queue == NULL) { Aliste idx1; Rt_map *lmp, *lmp2; lmp = sort->s_lmp; ndx = 1; if (aplist_append(&sort->s_queue, lmp, sort->s_num) == NULL) return (0); IDX(lmp) = ndx++; for (APLIST_TRAVERSE(sort->s_queue, idx1, lmp2)) { Bnd_desc *bdp; Aliste idx2; for (APLIST_TRAVERSE(DEPENDS(lmp2), idx2, bdp)) { Rt_map *lmp3 = bdp->b_depend; if (IDX(lmp3) || (LIST(lmp3) != lml)) continue; /* * If we're .init processing and this depend- * encies .init has been called, skip it. */ if ((flag & RT_SORT_REV) && (FLAGS(lmp3) & FLG_RT_INITCALL)) continue; if (aplist_append(&sort->s_queue, lmp3, sort->s_num) == NULL) return (0); IDX(lmp3) = ndx++; } } } /* * Sort the cyclics. */ qsort(&(sort->s_lmpa[fndx]), sort->s_lndx - fndx, sizeof (Rt_map *), compare); /* * Under ldd -i, or debugging, print this cycle. Under ldd -i/-U assign * each object a group identifier so that cyclic dependent callers can * be better traced (see trace_sort()), or analyzed for non-use. */ init = lmflags & LML_FLG_TRC_INIT; unref = lmflags & LML_FLG_TRC_UNREF; if ((init == 0) && (unref == 0) && (DBG_ENABLED == 0)) return (1); if (init) { if (tfmt == 0) { tfmt = MSG_INTL(MSG_LDD_INIT_FMT_01); ffmt = MSG_ORIG(MSG_LDD_INIT_FMT_FILE); } (void) printf(tfmt, cnt); } DBG_CALL(Dbg_util_scc_title(lml, (flag & RT_SORT_REV))); /* * Identify this cyclic group, and under ldd -i print the cycle in the * order its components will be run. */ if (flag & RT_SORT_REV) { for (ndx = fndx; ndx < sort->s_lndx; ndx++) { lmp = sort->s_lmpa[ndx]; CYCGROUP(lmp) = cnt; if (init) (void) printf(ffmt, NAME(lmp)); DBG_CALL(Dbg_util_scc_entry(lmp, ndx)); } cnt++; } else if (DBG_ENABLED) { for (ndx = sort->s_lndx - 1; ndx >= fndx; ndx--) { lmp = sort->s_lmpa[ndx]; DBG_CALL(Dbg_util_scc_entry(lmp, ndx)); } } /* * If we're looking for unused dependencies determine if any of these * cyclic components are referenced from outside of the cycle. */ if (unref || DBG_ENABLED) { for (ndx = fndx; ndx < sort->s_lndx; ndx++) { Bnd_desc *bdp; Aliste idx; lmp = sort->s_lmpa[ndx]; /* * If this object has a handle then it can be in use by * anyone. */ if (HANDLES(lmp)) return (1); /* * Traverse this objects callers looking for outside * references. */ for (APLIST_TRAVERSE(CALLERS(lmp), idx, bdp)) { Rt_map *clmp = bdp->b_caller; if ((bdp->b_flags & BND_REFER) == 0) continue; if (CYCGROUP(lmp) != CYCGROUP(clmp)) return (1); } } /* * If we're here then none of the cyclic dependents have been * referenced from outside of the cycle, mark them as unused. */ for (ndx = fndx; ndx < sort->s_lndx; ndx++) { lmp = sort->s_lmpa[ndx]; FLAGS1(lmp) &= ~FL1_RT_USED; } } return (1); } /* * Take elements off of the stack and move them to the link-map array. Typically * this routine just pops one strongly connected component (individual link-map) * at a time. When a cyclic dependency has been detected the stack will contain * more than just the present object to process, and will trigger the later call * to sort_scc() to sort these elements. */ static int visit(Lm_list *lml, Rt_map *lmp, Sort *sort, int flag) { APlist *alp = NULL; int num = sort->s_lndx; Word tracing = lml->lm_flags & LML_FLG_TRC_ENABLE; Rt_map *tlmp; do { tlmp = sort->s_stack[--(sort->s_sndx)]; SORTVAL(tlmp) = sort->s_num; DBG_CALL(Dbg_util_collect(tlmp, sort->s_lndx, flag)); sort->s_lmpa[(sort->s_lndx)++] = tlmp; if (flag & RT_SORT_REV) { /* * Indicate the object has had its .init collected. * Note, that regardless of the object having a .init * the object is added to the tsort list, as it's from * this list that any post-init flags are established. */ FLAGS(tlmp) |= FLG_RT_INITCLCT; lml->lm_init--; } else { /* * Indicate the object has had its .fini collected. * Note, that regardless of the object having a .fini, * the object is added to the tsort list, as it's from * this list that any audit_objclose() activity is * triggered. */ FLAGS(tlmp) |= FLG_RT_FINICLCT; } /* * If tracing, save the strongly connected component. */ if (tracing && (aplist_append(&alp, tlmp, AL_CNT_SCC) == NULL)) return (0); } while (tlmp != lmp); /* * Determine if there are cyclic dependencies to process. If so, sort * the components, and retain them for tracing output. */ if (sort->s_lndx > (num + 1)) { if (sort_scc(sort, num, flag) == 0) return (0); if (tracing && (aplist_append(&sort->s_scc, alp, AL_CNT_SCC) == NULL)) return (0); } else if (alp) free(alp); return (1); } static int dep_visit(Lm_list *, Rt_map *, uint_t, Rt_map *, Sort *, int); static int _dep_visit(Lm_list *lml, int min, Rt_map *clmp, Rt_map *dlmp, uint_t bflags, Sort *sort, int flag) { int _min; /* * Only collect objects that belong to the callers link-map. Catches * cross dependencies (filtering) to ld.so.1. */ if (LIST(dlmp) != lml) return (min); /* * Determine if this object hasn't been inspected. */ if ((_min = SORTVAL(dlmp)) == -1) { if (flag & RT_SORT_REV) { /* * For .init processing, only collect objects that have * been relocated and haven't already been collected. */ if ((FLAGS(dlmp) & (FLG_RT_RELOCED | FLG_RT_INITCLCT)) != FLG_RT_RELOCED) return (min); /* * If this object contains no .init, there's no need to * establish a dependency. */ if ((INIT(dlmp) == 0) && (INITARRAY(dlmp) == 0)) return (min); } else { /* * For .fini processing only collect objects that have * had their .init collected, and haven't already been * .fini collected. */ if ((FLAGS(dlmp) & (FLG_RT_INITCLCT | FLG_RT_FINICLCT)) != FLG_RT_INITCLCT) return (min); /* * If we're deleting a subset of objects, only collect * those marked for deletion. */ if ((flag & RT_SORT_DELETE) && ((FLAGS(dlmp) & FLG_RT_DELETE) == 0)) return (min); /* * If this object contains no .fini, there's no need to * establish a dependency. */ if ((FINI(dlmp) == 0) && (FINIARRAY(dlmp) == 0)) return (min); } /* * Inspect this new dependency. */ if ((_min = dep_visit(lml, clmp, bflags, dlmp, sort, flag)) == -1) return (-1); } /* * Keep track of the smallest SORTVAL that has been encountered. If * this value is smaller than the present object, then the dependency * edge has cycled back to objects that have been processed earlier * along this dependency edge. */ if (_min < min) { DBG_CALL(Dbg_util_edge_out(clmp, sort->s_stack[_min])); return (_min); } else return (min); } /* * Visit the dependencies of each object. */ static int dep_visit(Lm_list *lml, Rt_map *clmp, uint_t cbflags, Rt_map *lmp, Sort *sort, int flag) { int min; Aliste idx1; Bnd_desc *bdp; Dyninfo *dip; min = SORTVAL(lmp) = sort->s_sndx; sort->s_stack[(sort->s_sndx)++] = lmp; if (FLAGS(lmp) & FLG_RT_INITFRST) sort->s_initfirst++; DBG_CALL(Dbg_util_edge_in(lml, clmp, cbflags, lmp, min, flag)); /* * Traverse both explicit and implicit dependencies. */ for (APLIST_TRAVERSE(DEPENDS(lmp), idx1, bdp)) { if ((min = _dep_visit(lml, min, lmp, bdp->b_depend, bdp->b_flags, sort, flag)) == -1) return (-1); } /* * Traverse any filtee dependencies. */ if (((dip = DYNINFO(lmp)) != NULL) && (FLAGS1(lmp) & MSK_RT_FILTER)) { uint_t cnt, max = DYNINFOCNT(lmp); for (cnt = 0; cnt < max; cnt++, dip++) { Alist *falp; Pdesc *pdp; if (((falp = (Alist *)dip->di_info) == NULL) || ((dip->di_flags & MSK_DI_FILTER) == 0)) continue; for (ALIST_TRAVERSE(falp, idx1, pdp)) { Aliste idx2; Grp_hdl *ghp; Grp_desc *gdp; if ((pdp->pd_plen == 0) || ((ghp = (Grp_hdl *)pdp->pd_info) == NULL)) continue; for (ALIST_TRAVERSE(ghp->gh_depends, idx2, gdp)) { if (gdp->gd_depend == lmp) continue; if ((min = _dep_visit(lml, min, lmp, gdp->gd_depend, BND_FILTER, sort, flag)) == -1) return (-1); } } } } /* * Having returned to where the minimum SORTVAL is equivalent to the * object that has just been processed, collect any dependencies that * are available on the sorting stack. */ if (min == SORTVAL(lmp)) { if (visit(lml, lmp, sort, flag) == 0) return (-1); } return (min); } /* * Find corresponding strongly connected component structure. */ static APlist * trace_find_scc(Sort *sort, Rt_map *lmp) { APlist *alp; Aliste idx1; for (APLIST_TRAVERSE(sort->s_scc, idx1, alp)) { Rt_map *lmp2; Aliste idx2; for (APLIST_TRAVERSE(alp, idx2, lmp2)) { if (lmp == lmp2) return (alp); } } return (NULL); } /* * Print out the .init dependency information (ldd). */ static void trace_sort(Sort *sort) { int ndx = 0; APlist *alp; Rt_map *lmp1; (void) printf(MSG_ORIG(MSG_STR_NL)); while ((lmp1 = sort->s_lmpa[ndx++]) != NULL) { static const char *ffmt, *cfmt = 0, *sfmt = 0; Bnd_desc *bdp; Aliste idx1; if ((INIT(lmp1) == 0) || (FLAGS(lmp1) & FLG_RT_INITCALL)) continue; if (sfmt == 0) sfmt = MSG_INTL(MSG_LDD_INIT_FMT_02); /* * If the only component on the strongly connected list is * this link-map, then there are no dependencies. */ if ((alp = trace_find_scc(sort, lmp1)) == NULL) { (void) printf(sfmt, NAME(lmp1)); continue; } /* * Establish message formats for cyclic dependencies. */ if (cfmt == 0) { cfmt = MSG_INTL(MSG_LDD_INIT_FMT_03); ffmt = MSG_ORIG(MSG_LDD_INIT_FMT_FILE); } (void) printf(cfmt, NAME(lmp1), CYCGROUP(lmp1)); for (APLIST_TRAVERSE(CALLERS(lmp1), idx1, bdp)) { Rt_map *lmp3, *lmp2 = bdp->b_caller; Aliste idx2; for (APLIST_TRAVERSE(alp, idx2, lmp3)) { if (lmp2 != lmp3) continue; (void) printf(ffmt, NAME(lmp3)); } } } } /* * A reverse ordered list (for .init's) contains INITFIRST elements. Move each * of these elements to the front of the list. */ static void r_initfirst(Sort * sort, int end) { Rt_map *tlmp; int bgn, ifst, lifst = 0; for (bgn = 0; bgn < sort->s_initfirst; bgn++) { for (ifst = lifst; ifst <= end; ifst++) { tlmp = sort->s_lmpa[ifst]; if (!(FLAGS(tlmp) & FLG_RT_INITFRST)) continue; /* * If the INITFIRST element is already at the front of * the list leave it there. */ if (ifst == bgn) { lifst = ifst + 1; break; } /* * Move the elements from the front of the list up to * the INITFIRST element, back one position. */ (void) memmove(&sort->s_lmpa[bgn + 1], &sort->s_lmpa[bgn], ((ifst - bgn) * sizeof (Rt_map *))); /* * Insert INITFIRST element at the front of the list. */ sort->s_lmpa[bgn] = tlmp; lifst = ifst + 1; break; } } } /* * A forward ordered list (for .fini's) contains INITFIRST elements. Move each * of these elements to the front of the list. */ static void f_initfirst(Sort *sort, int end) { Rt_map *tlmp; int bgn, ifst, lifst = 0; for (bgn = 0; bgn < sort->s_initfirst; bgn++) { for (ifst = lifst; ifst <= end; ifst++) { tlmp = sort->s_lmpa[ifst]; if (!(FLAGS(tlmp) & FLG_RT_INITFRST)) continue; /* * If the INITFIRST element is already at the end of * the list leave it there. */ if (ifst == end) break; /* * Move the elements from after the INITFIRST element * up to the back of the list, up one position. */ (void) memmove(&sort->s_lmpa[ifst], &sort->s_lmpa[ifst + 1], ((end - ifst) * sizeof (Rt_map *))); /* * Insert INITFIRST element at the back of the list. */ sort->s_lmpa[end--] = tlmp; lifst = ifst; break; } } } /* * Determine whether .init or .fini processing is required. */ static int initorfini(Lm_list *lml, Rt_map *lmp, int flag, Sort *sort) { if (flag & RT_SORT_REV) { /* * For .init processing, only collect objects that have been * relocated and haven't already been collected. */ if ((FLAGS(lmp) & (FLG_RT_RELOCED | FLG_RT_INITCLCT)) != FLG_RT_RELOCED) return (0); if (dep_visit(lml, 0, 0, lmp, sort, flag) == -1) return (1); } else if (!(flag & RT_SORT_DELETE) || (FLAGS(lmp) & FLG_RT_DELETE)) { /* * Only collect objects that have had their .init collected, * and haven't already been .fini collected. */ if (!((FLAGS(lmp) & (FLG_RT_INITCLCT | FLG_RT_FINICLCT)) == (FLG_RT_INITCLCT))) return (0); if (dep_visit(lml, 0, 0, lmp, sort, flag) == -1) return (1); } return (0); } /* * Sort the dependency */ Rt_map ** tsort(Rt_map *lmp, int num, int flag) { Rt_map *_lmp; Lm_list *lml = LIST(lmp); Word init = lml->lm_flags & LML_FLG_TRC_INIT; Sort sort = { 0 }; size_t size; if (num == 0) return (0); /* * Prior to tsorting any .init sections, insure that the `environ' * symbol is initialized for this link-map list. */ if ((flag & RT_SORT_REV) && ((lml->lm_flags & (LML_FLG_TRC_ENABLE | LML_FLG_ENVIRON)) == 0)) set_environ(lml); /* * Allocate memory for link-map list array. Calloc the array to insure * all elements are zero, we might find that no objects need processing. * At the same time, allocate a stack for any topological sorting that * might be necessary. */ sort.s_lmp = lmp; sort.s_num = num + 1; size = sort.s_num * sizeof (Rt_map *); if ((sort.s_lmpa = calloc(2, size)) == NULL) return ((Rt_map **)S_ERROR); sort.s_stack = (Rt_map **)((uintptr_t)sort.s_lmpa + size); /* * Determine where to start searching for tsort() candidates. Any call * to tsort() for .init processing is passed the link-map from which to * start searching. However, if new objects have dependencies on * existing objects, or existing objects have been promoted (RTLD_LAZY * to RTLD_NOW), then start searching at the head of the link-map list. * These previously loaded objects will have been tagged for inclusion * in this tsort() pass. They still remain on an existing tsort() list, * which must have been prempted for control to have arrived here. * However, they will be ignored when encountered on any previous * tsort() list if their .init has already been called. */ if (lml->lm_flags & LML_FLG_OBJREEVAL) _lmp = lml->lm_head; else _lmp = lmp; DBG_CALL(Dbg_file_bindings(_lmp, flag)); lml->lm_flags &= ~(LML_FLG_OBJREEVAL | LML_FLG_OBJADDED | LML_FLG_OBJDELETED); /* * If interposers exist, inspect these objects first. * * Interposers can provide implicit dependencies - for example, an * application that has a dependency on libumem will caused any other * dependencies of the application that use the malloc family, to * have an implicit dependency on libumem. However, under the default * condition of lazy binding, these dependency relationships on libumem * are unknown to the tsorting process (ie. a call to one of the malloc * family has not occurred to establish the dependency). This lack of * dependency information makes the interposer look "standalone", * whereas the interposers .init/.fini should be analyzed with respect * to the dependency relationship libumem will eventually create. * * By inspecting interposing objects first, we are able to trigger * their .init sections to be accounted for before any others. * Selecting these .init sections first is important for the malloc * libraries, as these libraries need to prepare for pthread_atfork(). * However, handling interposer libraries in this generic fashion * should help provide for other dependency relationships that may * exist. */ if ((lml->lm_flags & (LML_FLG_INTRPOSE | LML_FLG_INTRPOSETSORT)) == LML_FLG_INTRPOSE) { Rt_map *ilmp = _lmp; /* * Unless the executable is tagged as an interposer, skip to * the next object. */ if ((FLAGS(ilmp) & MSK_RT_INTPOSE) == 0) ilmp = NEXT_RT_MAP(ilmp); for (; ilmp; ilmp = NEXT_RT_MAP(ilmp)) { if ((FLAGS(ilmp) & MSK_RT_INTPOSE) == 0) break; if (initorfini(lml, ilmp, (flag | RT_SORT_INTPOSE), &sort) != 0) return ((Rt_map **)S_ERROR); } /* * Once all interposers are processed, there is no need to * look for interposers again. An interposer can only * be introduced before any relocation takes place, thus * interposer .init's will be grabbed during the first tsort * starting at the head of the link-map list. * * Interposers can't be unloaded. Thus interposer .fini's can * only be called during atexit() processing. The interposer * tsort flag is removed from each link-map list during * atexit_fini() so that the interposers .fini sections are * processed appropriately. */ lml->lm_flags |= LML_FLG_INTRPOSETSORT; } /* * Inspect any standard objects. */ for (; _lmp; _lmp = NEXT_RT_MAP(_lmp)) { if (FLAGS(_lmp) & MSK_RT_INTPOSE) continue; if (initorfini(lml, _lmp, flag, &sort) != 0) return ((Rt_map **)S_ERROR); } /* * The dependencies have been collected such that they are appropriate * for an .init order, for .fini order reverse them. */ if (flag & RT_SORT_FWD) { int bgn = 0, end = sort.s_lndx - 1; while (bgn < end) { Rt_map *tlmp = sort.s_lmpa[end]; sort.s_lmpa[end] = sort.s_lmpa[bgn]; sort.s_lmpa[bgn] = tlmp; bgn++, end--; } } /* * If INITFIRST objects have been collected then move them to the front * or end of the list as appropriate. */ if (sort.s_initfirst) { if (flag & RT_SORT_REV) r_initfirst(&sort, sort.s_lndx - 1); else f_initfirst(&sort, sort.s_lndx - 1); } /* * If tracing .init sections (only meaningful for RT_SORT_REV), print * out the sorted dependencies. */ if (init) trace_sort(&sort); /* * Clean any temporary structures prior to return. */ if (sort.s_queue) { Aliste idx; Rt_map *lmp2; /* * Traverse the link-maps collected on the sort queue and * delete the depth index. These link-maps may be traversed * again to sort other components either for inits, and almost * certainly for .finis. */ for (APLIST_TRAVERSE(sort.s_queue, idx, lmp2)) IDX(lmp2) = 0; free(sort.s_queue); } if (sort.s_scc) { Aliste idx; APlist *alp; for (APLIST_TRAVERSE(sort.s_scc, idx, alp)) free(alp); free(sort.s_scc); } /* * The caller is responsible for freeing the sorted link-map list once * the associated .init/.fini's have been fired. */ DBG_CALL(Dbg_file_bindings_done(lml)); return (sort.s_lmpa); }