/*
* 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
* 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 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Analyze the versioning information within a file.
*
* -C demangle C++ symbol names.
*
* -d dump version definitions.
*
* -l print reduced (local) symbols. Implies -s.
*
* -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 -r option,
* WEAK and INFO attributes are displayed. With the -s option
* the version symbol is displayed.
*
* -I index only print the specifed version index, or index range.
*
* -N name only print the specifed `name'.
*/
#include <fcntl.h>
#include <stdio.h>
#include <libelf.h>
#include <link.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <locale.h>
#include <errno.h>
#include <sgs.h>
#include <conv.h>
#include <gelf.h>
#include <debug.h>
#include <ctype.h>
#include <alist.h>
#include "msg.h"
/*
* Define Alist initialization sizes.
*/
typedef struct cache {
char *c_name;
} Cache;
typedef struct gver_desc {
const char *vd_name;
unsigned long vd_hash;
} GVer_desc;
/* Versym related data used by gvers_syms() */
typedef struct {
/*
* Type used to manage -I and -N options:
*
* The -I option specifies a VERSYM index, or index range. The
* result is to select the VERDEF or VERNEED records with
* indexes that match those given.
*
* -N options come in two forms:
*
* 1) name
* 2) needobj (version)
*
* The meaning of the first case depends on the type of
* version record being matched:
*
* VERDEF - name is the name of a version defined
* by the object being processed (i.e. SUNW_1.1).
*
* VERNEED - name is the name of the object file
* on which the dependency exists (i.e. libc.so.1).
*
* -N options of the second form only apply to VERNEED records.
* They are used to specify a version from a needed object.
*/
/* match_opt_t is used to note which match option was used */
typedef enum {
} match_opt_t;
typedef struct {
union {
struct {
} name;
struct {
} ndx;
} value;
} match_rec_t;
static const char *cname;
/* Used to track whether an option defaulted to on, or was explicitly set */
/*
* Determine whether a symbol name should be demangled.
*/
static const char *
{
if (Cflag)
return (Elf_demangle_name(name));
else
return (name);
}
/*
* Append an item to the specified list, and return a pointer to the list
* node created.
*
* exit:
* On success, a new list node is created and the item is
* added to the list. On failure, a fatal error is issued
* and the process exits.
*/
static void
{
exit(1);
}
}
/*
* Add an entry to match_list for use by match(). This routine is for
* use during getopt() processing.
*
* entry:
* opt - One of 'N' or 'I', indicating the option
* str - Value string corresponding to opt
*
* exit:
* The new match record has been added. On error, a fatal
* error is issued and and the process exits.
*/
static void
{
/*
* Macros for removing leading and trailing whitespace:
* WS_SKIP - Advance _str without passing the NULL termination,
* until the first character is not whitespace.
* WS_SKIP_LIMIT - Advance _str without passing _limit,
* until the first character is not whitespace.
* WS_RSKIP_LIMIT - Move _tail back without passing _str,
* until the character before it is not whitespace.
* Write a NULL termination at that point.
*/
(_str)++
(_tail)--; \
*(_tail) = '\0'
exit(1);
}
if (opt == 'N') {
exit(1);
}
/* Assume this is a plain string */
/*
* If s2 points at a closing paren, then this might
* be a MATCH_OPT_NEED_VER case. Otherwise we're done.
*/
return;
/* We have a closing paren. Locate the opening one. */
;
if (*s1 != '(')
return;
s2--; /* Points at closing paren */
/* Terminate needobj, skipping trailing whitespace */
return;
}
/* If we get here, we are looking at a -I index option */
/* Value must use some of the input, and be positive */
goto syntax_error;
if (*str != ':') {
} else {
str++; /* Skip the ':' */
if (*str == '\0') {
} else {
goto syntax_error;
}
}
/* If we are successful, there is nothing left to parse */
if (*str == '\0')
return;
/*
* If we get here, there is leftover input. Fall through
* to issue a syntax error.
*/
exit(1);
}
/*
* Returns True (1) if the version with the given name or index should
* be displayed, and False (0) if it should not be.
*
* entry:
* needobj - NULL for VERDEF records, the name of the
* needed object for VERNEED.
* version - NULL, or needed version
* ndx - Versym index of version under consideration, or a value less
* than 1 to indicate that no valid index is given.
*
* exit:
* by one of the -I or -N command line options, or if no such option
* was used in the command invocation.
*/
int
{
const char *str;
/* If there is no match list, then we approve everything */
if (alist_nitems(match_list) == 0)
return (1);
/* Run through the match records and check for a hit */
case MATCH_OPT_NAME:
if (needobj)
else if (version)
else
break;
return (1);
break;
case MATCH_OPT_NEED_VER:
return (1);
break;
case MATCH_OPT_NDX:
return (1);
break;
case MATCH_OPT_RANGE:
/*
* A range end value less than 0 means that any value
* above the start is acceptible.
*/
if ((ndx > 0) &&
return (1);
break;
}
}
/* Nothing matched */
return (0);
}
/*
* List the symbols that belong to a specified version
*
* entry:
* vsdata - VERSYM related data from the object
* vd_ndx - The VERSYM index for symbols to display
* vd_name - Version name
* needobj - NULL for symbols corresponding to a VERDEF
* record. Name of the needed object in the case
* of a VERNEED record.
* file - Object file
*/
static void
{
int _symn;
const char *name;
continue;
/*
* Symbols that reference a VERDEF record
* have some extra details to handle.
*/
/*
* For data symbols defined by this object,
* determine the size.
*/
/*
* Only output the version symbol when the verbose
* flag is used.
*/
continue;
}
if (oflag) {
else
if (size)
else
} else {
if (size)
else
}
}
}
/*
* 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
{
char *strs;
/* LINTED */
/*
* Verify symtab[1] is the output file symbol.
*/
file);
return;
}
/*
* Scan the remaining symbols until the next file symbol is found.
*/
const char *name;
continue;
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.
*/
break;
if (oflag) {
} else {
if (found == 0) {
found = 1;
}
}
}
}
/*
* Print data from the files VERNEED section.
*
* If we have been asked to display symbols, then the
* output format follows that used for verdef sections,
* with each version displayed separately. For instance:
*
* libc.so.1 (SUNW_1.7):
* sym1;
* sym2;
* libc.so.1 (SUNW_1.9):
* sym3;
*
* If we are not displaying symbols, then a terse format
* is used, which combines all the needed versions from
* a given object into a single line. In this case, the
* versions are shown whether or not they contribute symbols.
*
* libc.so.1 (SUNW_1.7, SUNW_1.9);
*/
static int
const char *file)
{
char *strs;
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.
*/
/*
* Get the data buffer for the associated string table.
*/
/* Obtain the needed object file name */
error = 1;
/* Process the versions needed from this object */
continue;
if (show) {
/*
* If one-line ouput is called for
* display the filename being processed.
*/
(void) printf(
file);
(void) printf(
needobj);
started = 1;
}
/*
* If not showing symbols, only show INFO
* versions in verbose mode. They don't
* actually contribute to the version
* interface as seen by rtld, so listing them
* without qualification can be misleading.
*/
(alist_nitems(match_list) != 0) ||
listcnt++;
/* Show non-zero flags */
(void) printf(
&ver_flags_buf));
}
}
/*
* If we are showing symbols, and vna_other is
* non-zero, list them here.
*
* A value of 0 means that this object uses
* traditional Solaris versioning rules, under
* which VERSYM does not contain indexes to VERNEED
* records. In this case, there is nothing to show.
*/
}
}
return (error);
}
/*
* Return a GVer_desc descriptor for the given version if one
* exists.
*
* entry:
* name - Version name
* hash - ELF hash of name
* lst - APlist of existing descriptors.
* file - Object file containing the version
*
* exit:
* Return the corresponding GVer_desc struct if it
* exists, and NULL otherwise.
*/
static GVer_desc *
{
return (vdp);
return (NULL);
}
/*
* Return a GVer_desc descriptor for the given version.
*
* entry:
* name - Version name
* hash - ELF hash of name
* lst - List of existing descriptors.
* file - Object file containing the version
*
* exit:
* Return the corresponding GVer_desc struct. If the
* descriptor does not already exist, it is created.
* On error, a fatal error is issued and the process exits.
*/
static GVer_desc *
{
exit(1);
}
}
return (vdp);
}
/*
* Insert a version dependency for the given GVer_desc descriptor.
*
* entry:
* name - Dependency version name
* hash - ELF hash of name
* lst - List of existing descriptors.
* vdp - Existing version descriptor to which the dependency
* is to be added.
* file - Object file containing the version
*
* exit:
* A descriptor for the dependency version is looked up
* (created if necessary), and then added to the dependency
* list for vdp. Returns the dependency descriptor. On error,
* a fatal error is issued and the process exits.
*/
static GVer_desc *
const char *file)
{
return (vdp);
}
static void
{
/*
* 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.
*/
}
static void
{
if (!oflag)
}
}
/*
* Print the files version definition sections.
*/
static int
const char *file)
{
char *strs;
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.
*/
}
/*
* Get the data buffer for the associated string table.
*/
/*
* Process the version definitions placing each on a version dependency
* list.
*/
const char *_name;
/*
* Determine the version name and any dependencies.
*/
return (0);
}
/*
* Remember the base version for possible later use.
*/
if (ndx == VER_NDX_GLOBAL)
}
/*
* Normalize the dependency list if required.
*/
if (nflag) {
}
/*
* Always dereference the base version.
*/
if (bvdp)
}
/*
* Traverse the dependency list and print out the appropriate
* information.
*/
int count;
continue;
if ((alist_nitems(match_list) == 0) &&
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)
}
count = 1;
if (count++ == 1) {
if (oflag)
(void) printf(
_name);
(void) printf(
_name);
else
(void) printf(
_name);
} else
(void) printf(
}
if (count != 1)
else
} else {
else if (!vsdata) {
if (oflag)
file);
}
}
/* If we are not printing symbols, we're done */
continue;
/*
* If a specific version to match 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.
*/
if (alist_nitems(match_list) != 0) {
/*
* If the verbose flag is set, and this is not
* the base version, then add the base version as a
* dependency.
*/
if (!oflag)
}
}
}
return (error);
}
int
{
char *names;
int error = 0;
/*
* Check for a binary that better fits this architecture.
*/
/*
* Establish locale.
*/
opterr = 0;
switch (var) {
case 'C':
Cflag = USR_DEFINED;
break;
case 'd':
dflag = USR_DEFINED;
break;
case 'l':
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 'I':
case 'N':
break;
case '?':
cname);
exit(1);
default:
break;
}
}
/*
* No files specified on the command line?
*/
exit(1);
}
/*
* By default print both version definitions and needed dependencies.
*/
/*
* Open the input file and initialize the elf interface.
*/
error = 1;
continue;
}
(void) elf_version(EV_CURRENT);
error = 1;
continue;
}
file);
error = 1;
continue;
}
error = 1;
continue;
}
/*
* Obtain the .shstrtab data buffer to provide the required
* section name strings.
*/
error = 1;
continue;
}
error = 1;
continue;
}
/*
* 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.
*/
exit(1);
}
exit(1);
}
_cache++;
elf_errmsg(elf_errno()));
error = 1;
continue;
}
NULL) {
elf_errmsg(elf_errno()));
error = 1;
continue;
}
/*
* Remember the version sections and symbol table.
*/
case SHT_SUNW_verdef:
if (dflag)
_cache_def = _cache;
break;
case SHT_SUNW_verneed:
if (rflag)
break;
case SHT_SUNW_versym:
if (sflag)
_cache_sym = _cache;
break;
case SHT_SYMTAB:
if (lflag)
_cache_loc = _cache;
break;
}
}
/*
* Before printing anything out determine if any warnings are
* necessary.
*/
}
/*
* If there is more than one input file, and we're not printing
* one-line output, display the filename being processed.
*/
/*
* If we're printing symbols, then collect the data
* necessary to do that.
*/
if (_cache_sym != NULL) {
}
/*
* Print the files version needed sections.
*/
if (_cache_need)
/*
* Print the files version definition sections.
*/
if (_cache_def)
/*
* Print any local symbol reductions.
*/
if (_cache_loc)
/*
* 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.
*/
error = 1;
}
return (error);
}
const char *
{
}