/* * 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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * Analyze the versioning information within a file. * * -C demangle C++ symbol names. * * -d dump version definitions. * * -l print reduced (local) symbols. * * -n normalize any version definitions. * * -o dump output in one-line fashion (more suitable for grep'ing * and diff'ing). * * -r dump the version requirements on library dependencies * * -s display the symbols associated with each version definition. * * -v verbose output. With the -r and -d options any WEAK attribute * is displayed. With the -d option, any version inheritance, * and the base version are displayed. With the -s option the * version symbol is displayed. * * -N name only print the specifed `name'. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "msg.h" #define FLG_VER_AVAIL 0x10 typedef struct cache { Elf_Scn *c_scn; Elf_Data *c_data; char *c_name; } Cache; typedef struct gver_desc { const char *vd_name; unsigned long vd_hash; GElf_Half vd_ndx; GElf_Half vd_flags; List vd_deps; } GVer_desc; static const char *cname; static int Cflag, dflag, lflag, nflag, oflag, rflag, sflag, vflag; static const char * Format_ofil = "%s -", * Format_tnco = "\t%s:\n", * Format_tnse = "\t%s;\n", * Format_bgnl = "\t%s (%s", * Format_next = ", %s", * Format_weak = " [WEAK]", * Format_endl = ");\n"; #define DEF_DEFINED 1 #define USR_DEFINED 2 /* * Determine whether a symbol name should be demangled. */ static const char * demangle(const char *name) { if (Cflag) return (Elf_demangle_name(name)); else return (name); } /* * Print any reduced symbols. The convention is that reduced symbols exist as * LOCL entries in the .symtab, between the FILE symbol for the output file and * the first FILE symbol for any input file used to build the output file. */ static void sym_local(Cache *cache, Cache *csym, const char *file) { int symn, _symn, found = 0; GElf_Shdr shdr; GElf_Sym sym; char *strs, *local = "_LOCAL_"; (void) gelf_getshdr(csym->c_scn, &shdr); strs = (char *)cache[shdr.sh_link].c_data->d_buf; /* LINTED */ symn = shdr.sh_info; /* * Verify symtab[1] is the output file symbol. */ (void) gelf_getsym(csym->c_data, 1, &sym); if (GELF_ST_TYPE(sym.st_info) != STT_FILE) { (void) fprintf(stderr, MSG_INTL(MSG_VER_UNREDSYMS), cname, file); (void) fprintf(stderr, MSG_INTL(MSG_VER_NOTSTTFILE), csym->c_name); return; } /* * Scan the remaining symbols until the next file symbol is found. */ for (_symn = 2; _symn < symn; _symn++) { const char *name; (void) gelf_getsym(csym->c_data, _symn, &sym); if (GELF_ST_TYPE(sym.st_info) == STT_SECTION) continue; if (GELF_ST_TYPE(sym.st_info) == STT_FILE) break; /* * Its possible that section symbols are followed immediately * by globals. This is the case if an object (filter) is * generated exclusively from mapfile symbol definitions. */ if (GELF_ST_BIND(sym.st_info) != STB_LOCAL) break; name = demangle(strs + sym.st_name); if (oflag) { (void) printf(Format_ofil, file); (void) printf("\t%s: %s\n", local, name); } else { if (found == 0) { found = 1; (void) printf(Format_tnco, local); } (void) printf("\t\t%s;\n", name); } } } /* * Print the files version needed sections. */ static int gvers_need(Cache *cache, Cache *need, const char *file, const char *name) { unsigned int num, _num; char *strs; GElf_Verneed *vnd = need->c_data->d_buf; GElf_Shdr shdr; int error = 0; (void) gelf_getshdr(need->c_scn, &shdr); /* * Verify the version revision. We only check the first version * structure as it is assumed all other version structures in this * data section will be of the same revision. */ if (vnd->vn_version > VER_DEF_CURRENT) (void) fprintf(stderr, MSG_INTL(MSG_VER_HIGHREV), cname, file, vnd->vn_version, VER_DEF_CURRENT); /* * Get the data buffer for the associated string table. */ strs = (char *)cache[shdr.sh_link].c_data->d_buf; num = shdr.sh_info; for (_num = 1; _num <= num; _num++, vnd = (GElf_Verneed *)((uintptr_t)vnd + vnd->vn_next)) { GElf_Vernaux *vnap = (GElf_Vernaux *) ((uintptr_t)vnd + vnd->vn_aux); GElf_Half cnt = vnd->vn_cnt; const char *_name, * dep; /* * Obtain the version name and determine if we need to process * it further. */ _name = (char *)(strs + vnd->vn_file); if (name && (strcmp(name, _name) == 0)) continue; error = 1; /* * If one-line ouput is called for display the filename being * processed. */ if (oflag) (void) printf(Format_ofil, file); /* * Determine the version name required from this file. */ if (cnt--) dep = (char *)(strs + vnap->vna_name); else dep = MSG_ORIG(MSG_STR_EMPTY); (void) printf(Format_bgnl, _name, dep); if (vflag && (vnap->vna_flags == VER_FLG_WEAK)) (void) printf(Format_weak); /* * Extract any other version dependencies for this file */ /* CSTYLED */ for (vnap = (GElf_Vernaux *)((uintptr_t)vnap + vnap->vna_next); cnt; cnt--, vnap = (GElf_Vernaux *)((uintptr_t)vnap + vnap->vna_next)) { dep = (char *)(strs + vnap->vna_name); (void) printf(Format_next, dep); if (vflag && (vnap->vna_flags == VER_FLG_WEAK)) (void) printf(Format_weak); } (void) printf(Format_endl); } return (error); } /* * Append an item to the specified list, and return a pointer to the list * node created. */ static Listnode * list_append(List *lst, const void *item, const char *file) { Listnode *_lnp; if ((_lnp = malloc(sizeof (Listnode))) == 0) { int err = errno; (void) fprintf(stderr, MSG_INTL(MSG_SYS_MALLOC), cname, file, strerror(err)); exit(1); } _lnp->data = (void *)item; _lnp->next = NULL; if (lst->head == NULL) lst->tail = lst->head = _lnp; else { lst->tail->next = _lnp; lst->tail = lst->tail->next; } return (_lnp); } static GVer_desc * gvers_find(const char *name, unsigned long hash, List *lst) { Listnode *lnp; GVer_desc *vdp; for (LIST_TRAVERSE(lst, lnp, vdp)) { if (vdp->vd_hash != hash) continue; if (strcmp(vdp->vd_name, name) == 0) return (vdp); } return (0); } static GVer_desc * gvers_desc(const char *name, unsigned long hash, List *lst, const char *file) { GVer_desc *vdp; if ((vdp = gvers_find(name, hash, lst)) == 0) { if ((vdp = calloc(sizeof (GVer_desc), 1)) == 0) { int err = errno; (void) fprintf(stderr, MSG_INTL(MSG_SYS_MALLOC), cname, file, strerror(err)); exit(1); } vdp->vd_name = name; vdp->vd_hash = hash; if (list_append(lst, vdp, file) == 0) return (0); } return (vdp); } static GVer_desc * gvers_depend(const char *name, unsigned long hash, GVer_desc *vdp, List *lst, const char *file) { GVer_desc *_vdp; if ((_vdp = gvers_desc(name, hash, lst, file)) == 0) return (0); if (list_append(&vdp->vd_deps, _vdp, file) == 0) return (0); return (vdp); } static void gvers_syms(GElf_Versym *vsp, Elf_Data *sym_data, int symn, char *strs, GVer_desc *vdp, const char *file) { GElf_Sym sym; int _symn; for (_symn = 0; _symn < symn; _symn++) { size_t size = 0; const char *name; if (vsp[_symn] != vdp->vd_ndx) continue; /* * For data symbols determine the size. */ (void) gelf_getsym(sym_data, _symn, &sym); if ((GELF_ST_TYPE(sym.st_info) == STT_OBJECT) || (GELF_ST_TYPE(sym.st_info) == STT_COMMON) || (GELF_ST_TYPE(sym.st_info) == STT_TLS)) size = (size_t)sym.st_size; name = demangle(strs + sym.st_name); /* * Only output the version symbol when the verbose flag is used. */ if (!vflag && (sym.st_shndx == SHN_ABS)) { if (strcmp(name, vdp->vd_name) == 0) continue; } if (oflag) { (void) printf(Format_ofil, file); (void) printf("\t%s: ", vdp->vd_name); if (size) (void) printf("%s (%ld);\n", name, (ulong_t)size); else (void) printf("%s;\n", name); } else { if (size) (void) printf("\t\t%s (%ld);\n", name, (ulong_t)size); else (void) printf("\t\t%s;\n", name); } } } static void gvers_derefer(GVer_desc * vdp, int weak) { Listnode * _lnp; GVer_desc * _vdp; /* * If the head of the list was a weak then we only clear out * weak dependencies, but if the head of the list was 'strong' * we clear the REFER bit on all dependencies. */ if ((weak && (vdp->vd_flags & VER_FLG_WEAK)) || (!weak)) vdp->vd_flags &= ~FLG_VER_AVAIL; for (LIST_TRAVERSE(&vdp->vd_deps, _lnp, _vdp)) gvers_derefer(_vdp, weak); } static void recurse_syms(GElf_Versym *vsp, Elf_Data *sym_data, int symn, char *strs, GVer_desc *vdp, const char *file) { Listnode *_lnp; GVer_desc *_vdp; for (LIST_TRAVERSE(&vdp->vd_deps, _lnp, _vdp)) { if (!oflag) (void) printf(Format_tnco, _vdp->vd_name); gvers_syms(vsp, sym_data, symn, strs, _vdp, file); if (_vdp->vd_deps.head) recurse_syms(vsp, sym_data, symn, strs, _vdp, file); } } /* * Print the files version definition sections. */ static int gvers_def(Cache *cache, Cache *def, Cache *csym, const char *file, const char *name) { unsigned int num, _num; char *strs; GElf_Versym *vsp; GElf_Verdef *vdf = def->c_data->d_buf; GElf_Shdr shdr; Elf_Data *sym_data; int symn; GVer_desc *vdp, *bvdp = 0; Listnode *lnp; List verdefs = {0, 0}; int error = 0; /* * Verify the version revision. We only check the first version * structure as it is assumed all other version structures in this * data section will be of the same revision. */ if (vdf->vd_version > VER_DEF_CURRENT) { (void) fprintf(stderr, MSG_INTL(MSG_VER_HIGHREV), cname, file, vdf->vd_version, VER_DEF_CURRENT); } /* * Get the data buffer for the associated string table. */ (void) gelf_getshdr(def->c_scn, &shdr); strs = (char *)cache[shdr.sh_link].c_data->d_buf; num = shdr.sh_info; /* * Process the version definitions placing each on a version dependency * list. */ for (_num = 1; _num <= num; _num++, vdf = (GElf_Verdef *)((uintptr_t)vdf + vdf->vd_next)) { GElf_Half cnt = vdf->vd_cnt; GElf_Half ndx = vdf->vd_ndx; GElf_Verdaux *vdap = (GElf_Verdaux *)((uintptr_t)vdf + vdf->vd_aux); const char *_name; /* * Determine the version name and any dependencies. */ _name = (char *)(strs + vdap->vda_name); if ((vdp = gvers_desc(_name, elf_hash(_name), &verdefs, file)) == 0) return (0); vdp->vd_ndx = ndx; vdp->vd_flags = vdf->vd_flags | FLG_VER_AVAIL; vdap = (GElf_Verdaux *)((uintptr_t)vdap + vdap->vda_next); for (cnt--; cnt; cnt--, vdap = (GElf_Verdaux *)((uintptr_t)vdap + vdap->vda_next)) { _name = (char *)(strs + vdap->vda_name); if (gvers_depend(_name, elf_hash(_name), vdp, &verdefs, file) == 0) return (0); } /* * Remember the base version for possible later use. */ if (ndx == VER_NDX_GLOBAL) bvdp = vdp; } /* * Normalize the dependency list if required. */ if (nflag) { for (LIST_TRAVERSE(&verdefs, lnp, vdp)) { Listnode * _lnp; GVer_desc * _vdp; int type = vdp->vd_flags & VER_FLG_WEAK; for (LIST_TRAVERSE(&vdp->vd_deps, _lnp, _vdp)) gvers_derefer(_vdp, type); } /* * Always dereference the base version. */ if (bvdp) bvdp->vd_flags &= ~FLG_VER_AVAIL; } /* * Traverse the dependency list and print out the appropriate * information. */ for (LIST_TRAVERSE(&verdefs, lnp, vdp)) { Listnode * _lnp; GVer_desc * _vdp; int count; if (name && (strcmp(name, vdp->vd_name) != 0)) continue; if (!name && !(vdp->vd_flags & FLG_VER_AVAIL)) continue; error = 1; if (vflag) { /* * If the verbose flag is set determine if this version * has a `weak' attribute, and print any version * dependencies this version inherits. */ if (oflag) (void) printf(Format_ofil, file); (void) printf("\t%s", vdp->vd_name); if (vdp->vd_flags & VER_FLG_WEAK) (void) printf(Format_weak); count = 1; for (LIST_TRAVERSE(&vdp->vd_deps, _lnp, _vdp)) { const char *_name = _vdp->vd_name; if (count++ == 1) { if (oflag) (void) printf(": {%s", _name); else if (vdp->vd_flags & VER_FLG_WEAK) (void) printf(":\t{%s", _name); else (void) printf(": \t{%s", _name); } else (void) printf(Format_next, _name); } if (count != 1) (void) printf("}"); if (csym && !oflag) (void) printf(":\n"); else (void) printf(";\n"); } else { if (csym && !oflag) (void) printf(Format_tnco, vdp->vd_name); else if (!csym) { if (oflag) (void) printf(Format_ofil, file); (void) printf(Format_tnse, vdp->vd_name); } } /* * If we need to print symbols get the associated symbol table. */ if (csym) { (void) gelf_getshdr(csym->c_scn, &shdr); vsp = (GElf_Versym *)csym->c_data->d_buf; sym_data = cache[shdr.sh_link].c_data; (void) gelf_getshdr(cache[shdr.sh_link].c_scn, &shdr); /* LINTED */ symn = (int)(shdr.sh_size / shdr.sh_entsize); } else continue; /* * If a specific version name has been specified then display * any of its own symbols plus any inherited from other * versions. Otherwise simply print out the symbols for this * version. */ gvers_syms(vsp, sym_data, symn, strs, vdp, file); if (name) { recurse_syms(vsp, sym_data, symn, strs, vdp, file); /* * If the verbose flag is set add the base version as a * dependency (unless it's the list we were asked to * print in the first place). */ if (vflag && bvdp && strcmp(name, bvdp->vd_name)) { if (!oflag) (void) printf(Format_tnco, bvdp->vd_name); gvers_syms(vsp, sym_data, symn, strs, bvdp, file); } } } return (error); } int main(int argc, char **argv, char **envp) { GElf_Shdr shdr; Elf *elf; Elf_Scn *scn; Elf_Data *data; GElf_Ehdr ehdr; int nfile, var; const char *name; char *names; Cache *cache, *_cache; Cache *_cache_def, *_cache_need, *_cache_sym, *_cache_loc; int error = 0; /* * Check for a binary that better fits this architecture. */ conv_check_native(argv, envp); /* * Establish locale. */ (void) setlocale(LC_MESSAGES, MSG_ORIG(MSG_STR_EMPTY)); (void) textdomain(MSG_ORIG(MSG_SUNW_OST_SGS)); cname = argv[0]; name = NULL; Cflag = dflag = lflag = nflag = oflag = rflag = sflag = vflag = 0; opterr = 0; while ((var = getopt(argc, argv, "CdlnorsvN:")) != EOF) { switch (var) { case 'C': Cflag = USR_DEFINED; break; case 'd': dflag = USR_DEFINED; break; case 'l': lflag = USR_DEFINED; break; case 'n': nflag = USR_DEFINED; break; case 'o': oflag = USR_DEFINED; break; case 'r': rflag = USR_DEFINED; break; case 's': sflag = USR_DEFINED; break; case 'v': vflag = USR_DEFINED; break; case 'N': name = optarg; break; case '?': (void) fprintf(stderr, MSG_INTL(MSG_USAGE_BRIEF), cname); (void) fprintf(stderr, MSG_INTL(MSG_USAGE_DETAIL)); exit(1); default: break; } } /* * No files specified on the command line? */ if ((nfile = argc - optind) == 0) { (void) fprintf(stderr, MSG_INTL(MSG_USAGE_BRIEF), cname); exit(1); } /* * By default print both version definitions and needed dependencies. */ if ((dflag == 0) && (rflag == 0)) dflag = rflag = DEF_DEFINED; /* * Open the input file and initialize the elf interface. */ for (; optind < argc; optind++) { int derror = 0, nerror = 0, err; const char *file = argv[optind]; if ((var = open(file, O_RDONLY)) == -1) { err = errno; (void) fprintf(stderr, MSG_INTL(MSG_SYS_OPEN), cname, file, strerror(err)); error = 1; continue; } (void) elf_version(EV_CURRENT); if ((elf = elf_begin(var, ELF_C_READ, NULL)) == NULL) { (void) fprintf(stderr, MSG_ORIG(MSG_ELF_BEGIN), cname, file, elf_errmsg(elf_errno())); error = 1; (void) close(var); continue; } if (elf_kind(elf) != ELF_K_ELF) { (void) fprintf(stderr, MSG_INTL(MSG_ELF_NOTELF), cname, file); error = 1; (void) close(var); (void) elf_end(elf); continue; } if (gelf_getehdr(elf, &ehdr) == NULL) { (void) fprintf(stderr, MSG_ORIG(MSG_ELF_GETEHDR), cname, file, elf_errmsg(elf_errno())); error = 1; (void) close(var); (void) elf_end(elf); continue; } /* * Obtain the .shstrtab data buffer to provide the required * section name strings. */ if ((scn = elf_getscn(elf, ehdr.e_shstrndx)) == NULL) { (void) fprintf(stderr, MSG_ORIG(MSG_ELF_GETSCN), cname, file, elf_errmsg(elf_errno())); error = 1; (void) close(var); (void) elf_end(elf); continue; } if ((data = elf_getdata(scn, NULL)) == NULL) { (void) fprintf(stderr, MSG_ORIG(MSG_ELF_GETDATA), cname, file, elf_errmsg(elf_errno())); error = 1; (void) close(var); (void) elf_end(elf); continue; } names = data->d_buf; /* * Fill in the cache descriptor with information for each * section we might need. We probably only need to save * read-only allocable sections as this is where the version * structures and their associated symbols and strings live. * However, God knows what someone can do with a mapfile, and * as elf_begin has already gone through all the overhead we * might as well set up the cache for every section. */ if ((cache = calloc(ehdr.e_shnum, sizeof (Cache))) == 0) { int err = errno; (void) fprintf(stderr, MSG_INTL(MSG_SYS_MALLOC), cname, file, strerror(err)); exit(1); } _cache_def = _cache_need = _cache_sym = _cache_loc = 0; _cache = cache; _cache++; for (scn = NULL; scn = elf_nextscn(elf, scn); _cache++) { if (gelf_getshdr(scn, &shdr) == NULL) { (void) fprintf(stderr, MSG_ORIG(MSG_ELF_GETSHDR), cname, file, elf_errmsg(elf_errno())); error = 1; continue; } if ((_cache->c_data = elf_getdata(scn, NULL)) == NULL) { (void) fprintf(stderr, MSG_ORIG(MSG_ELF_GETDATA), cname, file, elf_errmsg(elf_errno())); error = 1; continue; } _cache->c_scn = scn; _cache->c_name = names + shdr.sh_name; /* * Remember the version sections and symbol table. */ if ((shdr.sh_type == SHT_SUNW_verdef) && dflag) _cache_def = _cache; else if ((shdr.sh_type == SHT_SUNW_verneed) && rflag) _cache_need = _cache; else if ((shdr.sh_type == SHT_SUNW_versym) && sflag) _cache_sym = _cache; else if ((shdr.sh_type == SHT_SYMTAB) && lflag) _cache_loc = _cache; } /* * Before printing anything out determine if any warnings are * necessary. */ if (lflag && (_cache_loc == 0)) { (void) fprintf(stderr, MSG_INTL(MSG_VER_UNREDSYMS), cname, file); (void) fprintf(stderr, MSG_INTL(MSG_VER_NOSYMTAB)); } /* * If there is more than one input file, and we're not printing * one-line output, display the filename being processed. */ if ((nfile > 1) && !oflag) (void) printf("%s:\n", file); /* * Print the files version needed sections. */ if (_cache_need) nerror = gvers_need(cache, _cache_need, file, name); /* * Print the files version definition sections. */ if (_cache_def) derror = gvers_def(cache, _cache_def, _cache_sym, file, name); /* * Print any local symbol reductions. */ if (_cache_loc) sym_local(cache, _cache_loc, file); /* * Determine the error return. There are three conditions that * may produce an error (a non-zero return): * * o if the user specified -d and no version definitions * were found. * * o if the user specified -r and no version requirements * were found. * * o if the user specified neither -d or -r, (thus both are * enabled by default), and no version definitions or * version dependencies were found. */ if (((dflag == USR_DEFINED) && (derror == 0)) || ((rflag == USR_DEFINED) && (nerror == 0)) || (rflag && dflag && (derror == 0) && (nerror == 0))) error = 1; (void) close(var); (void) elf_end(elf); free(cache); } return (error); } const char * _pvs_msg(Msg mid) { return (gettext(MSG_ORIG(mid))); }