bee2765cc1
Highlights of changes between r3490 and r3520: - Improve C++ demangling - Improve compatibility with Binutils tools wrt. error messages - Handle additional types/sections/etc. in readelf and elfdump - addr2line, cxxfilt: use setvbuf to set line buffering for filter use PR: 218395 MFC after: 2 weeks Relnotes: Yes Sponsored by: The FreeBSD Foundation
2121 lines
49 KiB
C
2121 lines
49 KiB
C
/*-
|
|
* Copyright (c) 2007 Hyogeol Lee <hyogeollee@gmail.com>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer
|
|
* in this position and unchanged.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/queue.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <ar.h>
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <dwarf.h>
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <gelf.h>
|
|
#include <getopt.h>
|
|
#include <inttypes.h>
|
|
#include <libdwarf.h>
|
|
#include <libelftc.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <unistd.h>
|
|
|
|
#include "_elftc.h"
|
|
|
|
ELFTC_VCSID("$Id: nm.c 3504 2016-12-17 15:33:16Z kaiwang27 $");
|
|
|
|
/* symbol information list */
|
|
STAILQ_HEAD(sym_head, sym_entry);
|
|
|
|
struct sym_entry {
|
|
char *name;
|
|
GElf_Sym *sym;
|
|
STAILQ_ENTRY(sym_entry) sym_entries;
|
|
};
|
|
|
|
typedef int (*fn_sort)(const void *, const void *);
|
|
typedef void (*fn_elem_print)(char, const char *, const GElf_Sym *, const char *);
|
|
typedef void (*fn_sym_print)(const GElf_Sym *);
|
|
typedef int (*fn_filter)(char, const GElf_Sym *, const char *);
|
|
|
|
/* output filter list */
|
|
static SLIST_HEAD(filter_head, filter_entry) nm_out_filter =
|
|
SLIST_HEAD_INITIALIZER(nm_out_filter);
|
|
|
|
struct filter_entry {
|
|
fn_filter fn;
|
|
SLIST_ENTRY(filter_entry) filter_entries;
|
|
};
|
|
|
|
struct sym_print_data {
|
|
struct sym_head *headp;
|
|
size_t sh_num, list_num;
|
|
const char *t_table, **s_table, *filename, *objname;
|
|
};
|
|
|
|
struct nm_prog_info {
|
|
const char *name;
|
|
const char *def_filename;
|
|
};
|
|
|
|
/* List for line number information. */
|
|
struct line_info_entry {
|
|
uint64_t addr; /* address */
|
|
uint64_t line; /* line number */
|
|
char *file; /* file name with path */
|
|
SLIST_ENTRY(line_info_entry) entries;
|
|
};
|
|
SLIST_HEAD(line_info_head, line_info_entry);
|
|
|
|
/* List for function line number information. */
|
|
struct func_info_entry {
|
|
char *name; /* function name */
|
|
char *file; /* file name with path */
|
|
uint64_t lowpc; /* low address */
|
|
uint64_t highpc; /* high address */
|
|
uint64_t line; /* line number */
|
|
SLIST_ENTRY(func_info_entry) entries;
|
|
};
|
|
SLIST_HEAD(func_info_head, func_info_entry);
|
|
|
|
/* List for variable line number information. */
|
|
struct var_info_entry {
|
|
char *name; /* variable name */
|
|
char *file; /* file name with path */
|
|
uint64_t addr; /* address */
|
|
uint64_t line; /* line number */
|
|
SLIST_ENTRY(var_info_entry) entries;
|
|
};
|
|
SLIST_HEAD(var_info_head, var_info_entry);
|
|
|
|
/* output numric type */
|
|
enum radix {
|
|
RADIX_OCT,
|
|
RADIX_HEX,
|
|
RADIX_DEC
|
|
};
|
|
|
|
/* output symbol type, PRINT_SYM_DYN for dynamic symbol only */
|
|
enum print_symbol {
|
|
PRINT_SYM_SYM,
|
|
PRINT_SYM_DYN
|
|
};
|
|
|
|
/* output name type */
|
|
enum print_name {
|
|
PRINT_NAME_NONE,
|
|
PRINT_NAME_FULL,
|
|
PRINT_NAME_MULTI
|
|
};
|
|
|
|
struct nm_prog_options {
|
|
enum print_symbol print_symbol;
|
|
enum print_name print_name;
|
|
enum radix t;
|
|
int demangle_type;
|
|
bool print_debug;
|
|
bool print_armap;
|
|
int print_size;
|
|
bool debug_line;
|
|
int def_only;
|
|
bool undef_only;
|
|
int sort_size;
|
|
bool sort_reverse;
|
|
int no_demangle;
|
|
|
|
/*
|
|
* function pointer to sort symbol list.
|
|
* possible function - cmp_name, cmp_none, cmp_size, cmp_value
|
|
*/
|
|
fn_sort sort_fn;
|
|
|
|
/*
|
|
* function pointer to print symbol elem.
|
|
* possible function - sym_elem_print_all
|
|
* sym_elem_print_all_portable
|
|
* sym_elem_print_all_sysv
|
|
*/
|
|
fn_elem_print elem_print_fn;
|
|
|
|
fn_sym_print value_print_fn;
|
|
fn_sym_print size_print_fn;
|
|
};
|
|
|
|
#define CHECK_SYM_PRINT_DATA(p) (p->headp == NULL || p->sh_num == 0 || \
|
|
p->t_table == NULL || p->s_table == NULL || p->filename == NULL)
|
|
#define IS_SYM_TYPE(t) ((t) == '?' || isalpha((t)) != 0)
|
|
#define IS_UNDEF_SYM_TYPE(t) ((t) == 'U' || (t) == 'v' || (t) == 'w')
|
|
#define UNUSED(p) ((void)p)
|
|
|
|
static int cmp_name(const void *, const void *);
|
|
static int cmp_none(const void *, const void *);
|
|
static int cmp_size(const void *, const void *);
|
|
static int cmp_value(const void *, const void *);
|
|
static void filter_dest(void);
|
|
static int filter_insert(fn_filter);
|
|
static void get_opt(int, char **);
|
|
static int get_sym(Elf *, struct sym_head *, int, size_t, size_t,
|
|
const char *, const char **, int);
|
|
static const char * get_sym_name(Elf *, const GElf_Sym *, size_t,
|
|
const char **, int);
|
|
static char get_sym_type(const GElf_Sym *, const char *);
|
|
static void global_dest(void);
|
|
static void global_init(void);
|
|
static bool is_sec_data(GElf_Shdr *);
|
|
static bool is_sec_debug(const char *);
|
|
static bool is_sec_nobits(GElf_Shdr *);
|
|
static bool is_sec_readonly(GElf_Shdr *);
|
|
static bool is_sec_text(GElf_Shdr *);
|
|
static void print_ar_index(int, Elf *);
|
|
static void print_header(const char *, const char *);
|
|
static void print_version(void);
|
|
static int read_elf(Elf *, const char *, Elf_Kind);
|
|
static int read_object(const char *);
|
|
static int read_files(int, char **);
|
|
static void set_opt_value_print_fn(enum radix);
|
|
static int sym_elem_def(char, const GElf_Sym *, const char *);
|
|
static int sym_elem_global(char, const GElf_Sym *, const char *);
|
|
static int sym_elem_global_static(char, const GElf_Sym *,
|
|
const char *);
|
|
static int sym_elem_nondebug(char, const GElf_Sym *, const char *);
|
|
static int sym_elem_nonzero_size(char, const GElf_Sym *,
|
|
const char *);
|
|
static void sym_elem_print_all(char, const char *,
|
|
const GElf_Sym *, const char *);
|
|
static void sym_elem_print_all_portable(char, const char *,
|
|
const GElf_Sym *, const char *);
|
|
static void sym_elem_print_all_sysv(char, const char *,
|
|
const GElf_Sym *, const char *);
|
|
static int sym_elem_undef(char, const GElf_Sym *, const char *);
|
|
static void sym_list_dest(struct sym_head *);
|
|
static int sym_list_insert(struct sym_head *, const char *,
|
|
const GElf_Sym *);
|
|
static void sym_list_print(struct sym_print_data *,
|
|
struct func_info_head *, struct var_info_head *,
|
|
struct line_info_head *);
|
|
static void sym_list_print_each(struct sym_entry *,
|
|
struct sym_print_data *, struct func_info_head *,
|
|
struct var_info_head *, struct line_info_head *);
|
|
static struct sym_entry *sym_list_sort(struct sym_print_data *);
|
|
static void sym_size_oct_print(const GElf_Sym *);
|
|
static void sym_size_hex_print(const GElf_Sym *);
|
|
static void sym_size_dec_print(const GElf_Sym *);
|
|
static void sym_value_oct_print(const GElf_Sym *);
|
|
static void sym_value_hex_print(const GElf_Sym *);
|
|
static void sym_value_dec_print(const GElf_Sym *);
|
|
static void usage(int);
|
|
|
|
static struct nm_prog_info nm_info;
|
|
static struct nm_prog_options nm_opts;
|
|
static int nm_elfclass;
|
|
|
|
/*
|
|
* Point to current sym_print_data to use portable qsort function.
|
|
* (e.g. There is no qsort_r function in NetBSD.)
|
|
*
|
|
* Using in sym_list_sort.
|
|
*/
|
|
static struct sym_print_data *nm_print_data;
|
|
|
|
static const struct option nm_longopts[] = {
|
|
{ "debug-syms", no_argument, NULL, 'a' },
|
|
{ "defined-only", no_argument, &nm_opts.def_only, 1},
|
|
{ "demangle", optional_argument, NULL, 'C' },
|
|
{ "dynamic", no_argument, NULL, 'D' },
|
|
{ "extern-only", no_argument, NULL, 'g' },
|
|
{ "format", required_argument, NULL, 'F' },
|
|
{ "help", no_argument, NULL, 'h' },
|
|
{ "line-numbers", no_argument, NULL, 'l' },
|
|
{ "no-demangle", no_argument, &nm_opts.no_demangle,
|
|
1},
|
|
{ "no-sort", no_argument, NULL, 'p' },
|
|
{ "numeric-sort", no_argument, NULL, 'v' },
|
|
{ "print-armap", no_argument, NULL, 's' },
|
|
{ "print-file-name", no_argument, NULL, 'A' },
|
|
{ "print-size", no_argument, NULL, 'S' },
|
|
{ "radix", required_argument, NULL, 't' },
|
|
{ "reverse-sort", no_argument, NULL, 'r' },
|
|
{ "size-sort", no_argument, &nm_opts.sort_size, 1},
|
|
{ "undefined-only", no_argument, NULL, 'u' },
|
|
{ "version", no_argument, NULL, 'V' },
|
|
{ NULL, 0, NULL, 0 }
|
|
};
|
|
|
|
#if defined(ELFTC_NEED_BYTEORDER_EXTENSIONS)
|
|
static __inline uint32_t
|
|
be32dec(const void *pp)
|
|
{
|
|
unsigned char const *p = (unsigned char const *)pp;
|
|
|
|
return ((p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]);
|
|
}
|
|
|
|
static __inline uint32_t
|
|
le32dec(const void *pp)
|
|
{
|
|
unsigned char const *p = (unsigned char const *)pp;
|
|
|
|
return ((p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0]);
|
|
}
|
|
|
|
static __inline uint64_t
|
|
be64dec(const void *pp)
|
|
{
|
|
unsigned char const *p = (unsigned char const *)pp;
|
|
|
|
return (((uint64_t)be32dec(p) << 32) | be32dec(p + 4));
|
|
}
|
|
|
|
static __inline uint64_t
|
|
le64dec(const void *pp)
|
|
{
|
|
unsigned char const *p = (unsigned char const *)pp;
|
|
|
|
return (((uint64_t)le32dec(p + 4) << 32) | le32dec(p));
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
cmp_name(const void *l, const void *r)
|
|
{
|
|
|
|
assert(l != NULL);
|
|
assert(r != NULL);
|
|
assert(((const struct sym_entry *)l)->name != NULL);
|
|
assert(((const struct sym_entry *)r)->name != NULL);
|
|
|
|
return (strcmp(((const struct sym_entry *)l)->name,
|
|
((const struct sym_entry *)r)->name));
|
|
}
|
|
|
|
static int
|
|
cmp_none(const void *l, const void *r)
|
|
{
|
|
|
|
UNUSED(l);
|
|
UNUSED(r);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/* Size comparison. If l and r have same size, compare their name. */
|
|
static int
|
|
cmp_size(const void *lp, const void *rp)
|
|
{
|
|
const struct sym_entry *l, *r;
|
|
|
|
l = lp;
|
|
r = rp;
|
|
|
|
assert(l != NULL);
|
|
assert(l->name != NULL);
|
|
assert(l->sym != NULL);
|
|
assert(r != NULL);
|
|
assert(r->name != NULL);
|
|
assert(r->sym != NULL);
|
|
|
|
if (l->sym->st_size == r->sym->st_size)
|
|
return (strcmp(l->name, r->name));
|
|
|
|
return (l->sym->st_size - r->sym->st_size);
|
|
}
|
|
|
|
/* Value comparison. Undefined symbols come first. */
|
|
static int
|
|
cmp_value(const void *lp, const void *rp)
|
|
{
|
|
const struct sym_entry *l, *r;
|
|
const char *ttable;
|
|
int l_is_undef, r_is_undef;
|
|
|
|
l = lp;
|
|
r = rp;
|
|
|
|
assert(nm_print_data != NULL);
|
|
ttable = nm_print_data->t_table;
|
|
|
|
assert(l != NULL);
|
|
assert(l->name != NULL);
|
|
assert(l->sym != NULL);
|
|
assert(r != NULL);
|
|
assert(r->name != NULL);
|
|
assert(r->sym != NULL);
|
|
assert(ttable != NULL);
|
|
|
|
l_is_undef = IS_UNDEF_SYM_TYPE(get_sym_type(l->sym, ttable)) ? 1 : 0;
|
|
r_is_undef = IS_UNDEF_SYM_TYPE(get_sym_type(r->sym, ttable)) ? 1 : 0;
|
|
|
|
assert(l_is_undef + r_is_undef >= 0);
|
|
assert(l_is_undef + r_is_undef <= 2);
|
|
|
|
switch (l_is_undef + r_is_undef) {
|
|
case 0:
|
|
/* Both defined */
|
|
if (l->sym->st_value == r->sym->st_value)
|
|
return (strcmp(l->name, r->name));
|
|
return (l->sym->st_value > r->sym->st_value ? 1 : -1);
|
|
case 1:
|
|
/* One undefined */
|
|
return (l_is_undef == 0 ? 1 : -1);
|
|
case 2:
|
|
/* Both undefined */
|
|
return (strcmp(l->name, r->name));
|
|
}
|
|
/* NOTREACHED */
|
|
|
|
return (l->sym->st_value - r->sym->st_value);
|
|
}
|
|
|
|
static void
|
|
filter_dest(void)
|
|
{
|
|
struct filter_entry *e;
|
|
|
|
while (!SLIST_EMPTY(&nm_out_filter)) {
|
|
e = SLIST_FIRST(&nm_out_filter);
|
|
SLIST_REMOVE_HEAD(&nm_out_filter, filter_entries);
|
|
free(e);
|
|
}
|
|
}
|
|
|
|
static int
|
|
filter_insert(fn_filter filter_fn)
|
|
{
|
|
struct filter_entry *e;
|
|
|
|
assert(filter_fn != NULL);
|
|
|
|
if ((e = malloc(sizeof(struct filter_entry))) == NULL) {
|
|
warn("malloc");
|
|
return (0);
|
|
}
|
|
e->fn = filter_fn;
|
|
SLIST_INSERT_HEAD(&nm_out_filter, e, filter_entries);
|
|
|
|
return (1);
|
|
}
|
|
|
|
static int
|
|
parse_demangle_option(const char *opt)
|
|
{
|
|
|
|
if (opt == NULL)
|
|
return (ELFTC_DEM_UNKNOWN);
|
|
else if (!strncasecmp(opt, "gnu-v2", 6))
|
|
return (ELFTC_DEM_GNU2);
|
|
else if (!strncasecmp(opt, "gnu-v3", 6))
|
|
return (ELFTC_DEM_GNU3);
|
|
else if (!strncasecmp(opt, "arm", 3))
|
|
return (ELFTC_DEM_ARM);
|
|
else
|
|
errx(EXIT_FAILURE, "unknown demangling style '%s'", opt);
|
|
|
|
/* NOTREACHED */
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
get_opt(int argc, char **argv)
|
|
{
|
|
int ch;
|
|
bool is_posix, oflag;
|
|
|
|
if (argc <= 0 || argv == NULL)
|
|
return;
|
|
|
|
oflag = is_posix = false;
|
|
nm_opts.t = RADIX_HEX;
|
|
while ((ch = getopt_long(argc, argv, "ABCDF:PSVaefghlnoprst:uvx",
|
|
nm_longopts, NULL)) != -1) {
|
|
switch (ch) {
|
|
case 'A':
|
|
nm_opts.print_name = PRINT_NAME_FULL;
|
|
break;
|
|
case 'B':
|
|
nm_opts.elem_print_fn = &sym_elem_print_all;
|
|
break;
|
|
case 'C':
|
|
nm_opts.demangle_type = parse_demangle_option(optarg);
|
|
break;
|
|
case 'D':
|
|
nm_opts.print_symbol = PRINT_SYM_DYN;
|
|
break;
|
|
case 'F':
|
|
/* sysv, bsd, posix */
|
|
switch (optarg[0]) {
|
|
case 'B':
|
|
case 'b':
|
|
nm_opts.elem_print_fn = &sym_elem_print_all;
|
|
break;
|
|
case 'P':
|
|
case 'p':
|
|
is_posix = true;
|
|
nm_opts.elem_print_fn =
|
|
&sym_elem_print_all_portable;
|
|
break;
|
|
case 'S':
|
|
case 's':
|
|
nm_opts.elem_print_fn =
|
|
&sym_elem_print_all_sysv;
|
|
break;
|
|
default:
|
|
warnx("%s: Invalid format", optarg);
|
|
usage(1);
|
|
}
|
|
|
|
break;
|
|
case 'P':
|
|
is_posix = true;
|
|
nm_opts.elem_print_fn = &sym_elem_print_all_portable;
|
|
break;
|
|
case 'S':
|
|
nm_opts.print_size = 1;
|
|
break;
|
|
case 'V':
|
|
print_version();
|
|
/* NOTREACHED */
|
|
case 'a':
|
|
nm_opts.print_debug = true;
|
|
break;
|
|
case 'e':
|
|
filter_insert(sym_elem_global_static);
|
|
break;
|
|
case 'f':
|
|
break;
|
|
case 'g':
|
|
filter_insert(sym_elem_global);
|
|
break;
|
|
case 'h':
|
|
usage(0);
|
|
break;
|
|
case 'l':
|
|
nm_opts.debug_line = true;
|
|
break;
|
|
case 'n':
|
|
case 'v':
|
|
nm_opts.sort_fn = &cmp_value;
|
|
break;
|
|
case 'o':
|
|
oflag = true;
|
|
break;
|
|
case 'p':
|
|
nm_opts.sort_fn = &cmp_none;
|
|
break;
|
|
case 'r':
|
|
nm_opts.sort_reverse = true;
|
|
break;
|
|
case 's':
|
|
nm_opts.print_armap = true;
|
|
break;
|
|
case 't':
|
|
/* t require always argument to getopt_long */
|
|
switch (optarg[0]) {
|
|
case 'd':
|
|
nm_opts.t = RADIX_DEC;
|
|
break;
|
|
case 'o':
|
|
nm_opts.t = RADIX_OCT;
|
|
break;
|
|
case 'x':
|
|
nm_opts.t = RADIX_HEX;
|
|
break;
|
|
default:
|
|
warnx("%s: Invalid radix", optarg);
|
|
usage(1);
|
|
}
|
|
break;
|
|
case 'u':
|
|
filter_insert(sym_elem_undef);
|
|
nm_opts.undef_only = true;
|
|
break;
|
|
/* case 'v': see case 'n' above. */
|
|
case 'x':
|
|
nm_opts.t = RADIX_HEX;
|
|
break;
|
|
case 0:
|
|
if (nm_opts.sort_size != 0) {
|
|
nm_opts.sort_fn = &cmp_size;
|
|
filter_insert(sym_elem_def);
|
|
filter_insert(sym_elem_nonzero_size);
|
|
}
|
|
if (nm_opts.def_only != 0)
|
|
filter_insert(sym_elem_def);
|
|
if (nm_opts.no_demangle != 0)
|
|
nm_opts.demangle_type = -1;
|
|
break;
|
|
default :
|
|
usage(1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* In POSIX mode, the '-o' option controls the output radix.
|
|
* In non-POSIX mode, the option is a synonym for the '-A' and
|
|
* '--print-file-name' options.
|
|
*/
|
|
if (oflag) {
|
|
if (is_posix)
|
|
nm_opts.t = RADIX_OCT;
|
|
else
|
|
nm_opts.print_name = PRINT_NAME_FULL;
|
|
}
|
|
|
|
assert(nm_opts.sort_fn != NULL && "nm_opts.sort_fn is null");
|
|
assert(nm_opts.elem_print_fn != NULL &&
|
|
"nm_opts.elem_print_fn is null");
|
|
assert(nm_opts.value_print_fn != NULL &&
|
|
"nm_opts.value_print_fn is null");
|
|
|
|
set_opt_value_print_fn(nm_opts.t);
|
|
|
|
if (nm_opts.undef_only == true) {
|
|
if (nm_opts.sort_fn == &cmp_size)
|
|
errx(EXIT_FAILURE,
|
|
"--size-sort with -u is meaningless");
|
|
if (nm_opts.def_only != 0)
|
|
errx(EXIT_FAILURE,
|
|
"-u with --defined-only is meaningless");
|
|
}
|
|
if (nm_opts.print_debug == false)
|
|
filter_insert(sym_elem_nondebug);
|
|
if (nm_opts.sort_reverse == true && nm_opts.sort_fn == cmp_none)
|
|
nm_opts.sort_reverse = false;
|
|
}
|
|
|
|
/*
|
|
* Get symbol information from elf.
|
|
*/
|
|
static int
|
|
get_sym(Elf *elf, struct sym_head *headp, int shnum, size_t dynndx,
|
|
size_t strndx, const char *type_table, const char **sec_table,
|
|
int sec_table_size)
|
|
{
|
|
Elf_Scn *scn;
|
|
Elf_Data *data;
|
|
GElf_Shdr shdr;
|
|
GElf_Sym sym;
|
|
struct filter_entry *fep;
|
|
size_t ndx;
|
|
int rtn;
|
|
const char *sym_name;
|
|
char type;
|
|
bool filter;
|
|
int i, j;
|
|
|
|
assert(elf != NULL);
|
|
assert(headp != NULL);
|
|
|
|
rtn = 0;
|
|
for (i = 1; i < shnum; i++) {
|
|
if ((scn = elf_getscn(elf, i)) == NULL) {
|
|
warnx("elf_getscn failed: %s", elf_errmsg(-1));
|
|
continue;
|
|
}
|
|
if (gelf_getshdr(scn, &shdr) != &shdr) {
|
|
warnx("gelf_getshdr failed: %s", elf_errmsg(-1));
|
|
continue;
|
|
}
|
|
if (shdr.sh_type == SHT_SYMTAB) {
|
|
if (nm_opts.print_symbol != PRINT_SYM_SYM)
|
|
continue;
|
|
} else if (shdr.sh_type == SHT_DYNSYM) {
|
|
if (nm_opts.print_symbol != PRINT_SYM_DYN)
|
|
continue;
|
|
} else
|
|
continue;
|
|
|
|
ndx = shdr.sh_type == SHT_DYNSYM ? dynndx : strndx;
|
|
|
|
data = NULL;
|
|
while ((data = elf_getdata(scn, data)) != NULL) {
|
|
j = 1;
|
|
while (gelf_getsym(data, j++, &sym) != NULL) {
|
|
sym_name = get_sym_name(elf, &sym, ndx,
|
|
sec_table, sec_table_size);
|
|
filter = false;
|
|
type = get_sym_type(&sym, type_table);
|
|
SLIST_FOREACH(fep, &nm_out_filter,
|
|
filter_entries) {
|
|
if (!fep->fn(type, &sym, sym_name)) {
|
|
filter = true;
|
|
break;
|
|
}
|
|
}
|
|
if (filter == false) {
|
|
if (sym_list_insert(headp, sym_name,
|
|
&sym) == 0)
|
|
return (0);
|
|
rtn++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return (rtn);
|
|
}
|
|
|
|
static const char *
|
|
get_sym_name(Elf *elf, const GElf_Sym *sym, size_t ndx, const char **sec_table,
|
|
int sec_table_size)
|
|
{
|
|
const char *sym_name;
|
|
|
|
sym_name = NULL;
|
|
|
|
/* Show section name as symbol name for STT_SECTION symbols. */
|
|
if (GELF_ST_TYPE(sym->st_info) == STT_SECTION) {
|
|
if (sec_table != NULL && sym->st_shndx < sec_table_size)
|
|
sym_name = sec_table[sym->st_shndx];
|
|
} else
|
|
sym_name = elf_strptr(elf, ndx, sym->st_name);
|
|
|
|
if (sym_name == NULL)
|
|
sym_name = "(null)";
|
|
|
|
return (sym_name);
|
|
}
|
|
|
|
static char
|
|
get_sym_type(const GElf_Sym *sym, const char *type_table)
|
|
{
|
|
bool is_local;
|
|
|
|
if (sym == NULL || type_table == NULL)
|
|
return ('?');
|
|
|
|
is_local = sym->st_info >> 4 == STB_LOCAL;
|
|
|
|
if (sym->st_shndx == SHN_ABS) /* absolute */
|
|
return (is_local ? 'a' : 'A');
|
|
|
|
if (sym->st_shndx == SHN_COMMON) /* common */
|
|
return ('C');
|
|
|
|
if ((sym->st_info) >> 4 == STB_WEAK) { /* weak */
|
|
if ((sym->st_info & 0xf) == STT_OBJECT)
|
|
return (sym->st_shndx == SHN_UNDEF ? 'v' : 'V');
|
|
|
|
return (sym->st_shndx == SHN_UNDEF ? 'w' : 'W');
|
|
}
|
|
|
|
if (sym->st_shndx == SHN_UNDEF) /* undefined */
|
|
return ('U');
|
|
|
|
return (is_local == true && type_table[sym->st_shndx] != 'N' ?
|
|
tolower((unsigned char) type_table[sym->st_shndx]) :
|
|
type_table[sym->st_shndx]);
|
|
}
|
|
|
|
static void
|
|
global_dest(void)
|
|
{
|
|
|
|
filter_dest();
|
|
}
|
|
|
|
static void
|
|
global_init(void)
|
|
{
|
|
|
|
if (elf_version(EV_CURRENT) == EV_NONE)
|
|
errx(EXIT_FAILURE, "elf_version error");
|
|
|
|
nm_info.name = ELFTC_GETPROGNAME();
|
|
nm_info.def_filename = "a.out";
|
|
nm_opts.print_symbol = PRINT_SYM_SYM;
|
|
nm_opts.print_name = PRINT_NAME_NONE;
|
|
nm_opts.demangle_type = -1;
|
|
nm_opts.print_debug = false;
|
|
nm_opts.print_armap = false;
|
|
nm_opts.print_size = 0;
|
|
nm_opts.debug_line = false;
|
|
nm_opts.def_only = 0;
|
|
nm_opts.undef_only = false;
|
|
nm_opts.sort_size = 0;
|
|
nm_opts.sort_reverse = false;
|
|
nm_opts.no_demangle = 0;
|
|
nm_opts.sort_fn = &cmp_name;
|
|
nm_opts.elem_print_fn = &sym_elem_print_all;
|
|
nm_opts.value_print_fn = &sym_value_dec_print;
|
|
nm_opts.size_print_fn = &sym_size_dec_print;
|
|
SLIST_INIT(&nm_out_filter);
|
|
}
|
|
|
|
static bool
|
|
is_sec_data(GElf_Shdr *s)
|
|
{
|
|
|
|
assert(s != NULL && "shdr is NULL");
|
|
|
|
return (((s->sh_flags & SHF_ALLOC) != 0) && s->sh_type != SHT_NOBITS);
|
|
}
|
|
|
|
static bool
|
|
is_sec_debug(const char *shname)
|
|
{
|
|
const char *dbg_sec[] = {
|
|
".debug",
|
|
".gnu.linkonce.wi.",
|
|
".line",
|
|
".rel.debug",
|
|
".rela.debug",
|
|
".stab",
|
|
NULL
|
|
};
|
|
const char **p;
|
|
|
|
if (shname == NULL)
|
|
return (false);
|
|
|
|
for (p = dbg_sec; *p; p++) {
|
|
if (!strncmp(shname, *p, strlen(*p)))
|
|
return (true);
|
|
}
|
|
|
|
return (false);
|
|
}
|
|
|
|
static bool
|
|
is_sec_nobits(GElf_Shdr *s)
|
|
{
|
|
|
|
assert(s != NULL && "shdr is NULL");
|
|
|
|
return (s->sh_type == SHT_NOBITS);
|
|
}
|
|
|
|
static bool
|
|
is_sec_readonly(GElf_Shdr *s)
|
|
{
|
|
|
|
assert(s != NULL && "shdr is NULL");
|
|
|
|
return ((s->sh_flags & SHF_WRITE) == 0);
|
|
}
|
|
|
|
static bool
|
|
is_sec_text(GElf_Shdr *s)
|
|
{
|
|
|
|
assert(s != NULL && "shdr is NULL");
|
|
|
|
return ((s->sh_flags & SHF_EXECINSTR) != 0);
|
|
}
|
|
|
|
static void
|
|
print_ar_index(int fd, Elf *arf)
|
|
{
|
|
Elf *elf;
|
|
Elf_Arhdr *arhdr;
|
|
Elf_Arsym *arsym;
|
|
Elf_Cmd cmd;
|
|
off_t start;
|
|
size_t arsym_size;
|
|
|
|
if (arf == NULL)
|
|
return;
|
|
|
|
if ((arsym = elf_getarsym(arf, &arsym_size)) == NULL)
|
|
return;
|
|
|
|
printf("\nArchive index:\n");
|
|
|
|
start = arsym->as_off;
|
|
cmd = ELF_C_READ;
|
|
while (arsym_size > 1) {
|
|
if (elf_rand(arf, arsym->as_off) == arsym->as_off &&
|
|
(elf = elf_begin(fd, cmd, arf)) != NULL) {
|
|
if ((arhdr = elf_getarhdr(elf)) != NULL)
|
|
printf("%s in %s\n", arsym->as_name,
|
|
arhdr->ar_name != NULL ?
|
|
arhdr->ar_name : arhdr->ar_rawname);
|
|
elf_end(elf);
|
|
}
|
|
++arsym;
|
|
--arsym_size;
|
|
}
|
|
|
|
elf_rand(arf, start);
|
|
}
|
|
|
|
#define DEMANGLED_BUFFER_SIZE (8 * 1024)
|
|
#define PRINT_DEMANGLED_NAME(FORMAT, NAME) do { \
|
|
char _demangled[DEMANGLED_BUFFER_SIZE]; \
|
|
if (nm_opts.demangle_type < 0 || \
|
|
elftc_demangle((NAME), _demangled, sizeof(_demangled), \
|
|
nm_opts.demangle_type) < 0) \
|
|
printf((FORMAT), (NAME)); \
|
|
else \
|
|
printf((FORMAT), _demangled); \
|
|
} while (0)
|
|
|
|
static void
|
|
print_header(const char *file, const char *obj)
|
|
{
|
|
|
|
if (file == NULL)
|
|
return;
|
|
|
|
if (nm_opts.elem_print_fn == &sym_elem_print_all_sysv) {
|
|
printf("\n\n%s from %s",
|
|
nm_opts.undef_only == false ? "Symbols" :
|
|
"Undefined symbols", file);
|
|
if (obj != NULL)
|
|
printf("[%s]", obj);
|
|
printf(":\n\n");
|
|
|
|
printf("\
|
|
Name Value Class Type Size Line Section\n\n");
|
|
} else {
|
|
/* archive file without -A option and POSIX */
|
|
if (nm_opts.print_name != PRINT_NAME_FULL && obj != NULL) {
|
|
if (nm_opts.elem_print_fn ==
|
|
sym_elem_print_all_portable)
|
|
printf("%s[%s]:\n", file, obj);
|
|
else if (nm_opts.elem_print_fn == sym_elem_print_all)
|
|
printf("\n%s:\n", obj);
|
|
/* multiple files(not archive) without -A option */
|
|
} else if (nm_opts.print_name == PRINT_NAME_MULTI) {
|
|
if (nm_opts.elem_print_fn == sym_elem_print_all)
|
|
printf("\n");
|
|
printf("%s:\n", file);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
print_version(void)
|
|
{
|
|
|
|
(void) printf("%s (%s)\n", nm_info.name, elftc_version());
|
|
exit(0);
|
|
}
|
|
|
|
static uint64_t
|
|
get_block_value(Dwarf_Debug dbg, Dwarf_Block *block)
|
|
{
|
|
Elf *elf;
|
|
GElf_Ehdr eh;
|
|
Dwarf_Error de;
|
|
|
|
if (dwarf_get_elf(dbg, &elf, &de) != DW_DLV_OK) {
|
|
warnx("dwarf_get_elf failed: %s", dwarf_errmsg(de));
|
|
return (0);
|
|
}
|
|
|
|
if (gelf_getehdr(elf, &eh) != &eh) {
|
|
warnx("gelf_getehdr failed: %s", elf_errmsg(-1));
|
|
return (0);
|
|
}
|
|
|
|
if (block->bl_len == 5) {
|
|
if (eh.e_ident[EI_DATA] == ELFDATA2LSB)
|
|
return (le32dec((uint8_t *) block->bl_data + 1));
|
|
else
|
|
return (be32dec((uint8_t *) block->bl_data + 1));
|
|
} else if (block->bl_len == 9) {
|
|
if (eh.e_ident[EI_DATA] == ELFDATA2LSB)
|
|
return (le64dec((uint8_t *) block->bl_data + 1));
|
|
else
|
|
return (be64dec((uint8_t *) block->bl_data + 1));
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static char *
|
|
find_object_name(Dwarf_Debug dbg, Dwarf_Die die)
|
|
{
|
|
Dwarf_Die ret_die;
|
|
Dwarf_Attribute at;
|
|
Dwarf_Off off;
|
|
Dwarf_Error de;
|
|
const char *str;
|
|
char *name;
|
|
|
|
if (dwarf_attrval_string(die, DW_AT_name, &str, &de) == DW_DLV_OK) {
|
|
if ((name = strdup(str)) == NULL) {
|
|
warn("strdup");
|
|
return (NULL);
|
|
}
|
|
return (name);
|
|
}
|
|
|
|
if (dwarf_attr(die, DW_AT_specification, &at, &de) != DW_DLV_OK)
|
|
return (NULL);
|
|
|
|
if (dwarf_global_formref(at, &off, &de) != DW_DLV_OK)
|
|
return (NULL);
|
|
|
|
if (dwarf_offdie(dbg, off, &ret_die, &de) != DW_DLV_OK)
|
|
return (NULL);
|
|
|
|
return (find_object_name(dbg, ret_die));
|
|
}
|
|
|
|
static void
|
|
search_line_attr(Dwarf_Debug dbg, struct func_info_head *func_info,
|
|
struct var_info_head *var_info, Dwarf_Die die, char **src_files,
|
|
Dwarf_Signed filecount)
|
|
{
|
|
Dwarf_Attribute at;
|
|
Dwarf_Unsigned udata;
|
|
Dwarf_Half tag;
|
|
Dwarf_Block *block;
|
|
Dwarf_Bool flag;
|
|
Dwarf_Die ret_die;
|
|
Dwarf_Error de;
|
|
struct func_info_entry *func;
|
|
struct var_info_entry *var;
|
|
int ret;
|
|
|
|
if (dwarf_tag(die, &tag, &de) != DW_DLV_OK) {
|
|
warnx("dwarf_tag failed: %s", dwarf_errmsg(de));
|
|
goto cont_search;
|
|
}
|
|
|
|
/* We're interested in DIEs which define functions or variables. */
|
|
if (tag != DW_TAG_subprogram && tag != DW_TAG_entry_point &&
|
|
tag != DW_TAG_inlined_subroutine && tag != DW_TAG_variable)
|
|
goto cont_search;
|
|
|
|
if (tag == DW_TAG_variable) {
|
|
|
|
/* Ignore "artificial" variable. */
|
|
if (dwarf_attrval_flag(die, DW_AT_artificial, &flag, &de) ==
|
|
DW_DLV_OK && flag)
|
|
goto cont_search;
|
|
|
|
/* Ignore pure declaration. */
|
|
if (dwarf_attrval_flag(die, DW_AT_declaration, &flag, &de) ==
|
|
DW_DLV_OK && flag)
|
|
goto cont_search;
|
|
|
|
/* Ignore stack varaibles. */
|
|
if (dwarf_attrval_flag(die, DW_AT_external, &flag, &de) !=
|
|
DW_DLV_OK || !flag)
|
|
goto cont_search;
|
|
|
|
if ((var = calloc(1, sizeof(*var))) == NULL) {
|
|
warn("calloc failed");
|
|
goto cont_search;
|
|
}
|
|
|
|
if (dwarf_attrval_unsigned(die, DW_AT_decl_file, &udata,
|
|
&de) == DW_DLV_OK && udata > 0 &&
|
|
(Dwarf_Signed) (udata - 1) < filecount) {
|
|
var->file = strdup(src_files[udata - 1]);
|
|
if (var->file == NULL) {
|
|
warn("strdup");
|
|
free(var);
|
|
goto cont_search;
|
|
}
|
|
}
|
|
|
|
if (dwarf_attrval_unsigned(die, DW_AT_decl_line, &udata, &de) ==
|
|
DW_DLV_OK)
|
|
var->line = udata;
|
|
|
|
var->name = find_object_name(dbg, die);
|
|
if (var->name == NULL) {
|
|
if (var->file)
|
|
free(var->file);
|
|
free(var);
|
|
goto cont_search;
|
|
}
|
|
|
|
if (dwarf_attr(die, DW_AT_location, &at, &de) == DW_DLV_OK &&
|
|
dwarf_formblock(at, &block, &de) == DW_DLV_OK) {
|
|
/*
|
|
* Since we ignored stack variables, the rest are the
|
|
* external varaibles which should always use DW_OP_addr
|
|
* operator for DW_AT_location value.
|
|
*/
|
|
if (*((uint8_t *)block->bl_data) == DW_OP_addr)
|
|
var->addr = get_block_value(dbg, block);
|
|
}
|
|
|
|
SLIST_INSERT_HEAD(var_info, var, entries);
|
|
|
|
} else {
|
|
|
|
if ((func = calloc(1, sizeof(*func))) == NULL) {
|
|
warn("calloc failed");
|
|
goto cont_search;
|
|
}
|
|
|
|
/*
|
|
* Note that dwarf_attrval_unsigned() handles DW_AT_abstract_origin
|
|
* internally, so it can retrieve DW_AT_decl_file/DW_AT_decl_line
|
|
* attributes for inlined functions as well.
|
|
*/
|
|
if (dwarf_attrval_unsigned(die, DW_AT_decl_file, &udata,
|
|
&de) == DW_DLV_OK && udata > 0 &&
|
|
(Dwarf_Signed) (udata - 1) < filecount) {
|
|
func->file = strdup(src_files[udata - 1]);
|
|
if (func->file == NULL) {
|
|
warn("strdup");
|
|
free(func);
|
|
goto cont_search;
|
|
}
|
|
}
|
|
|
|
if (dwarf_attrval_unsigned(die, DW_AT_decl_line, &udata, &de) ==
|
|
DW_DLV_OK)
|
|
func->line = udata;
|
|
|
|
func->name = find_object_name(dbg, die);
|
|
if (func->name == NULL) {
|
|
if (func->file)
|
|
free(func->file);
|
|
free(func);
|
|
goto cont_search;
|
|
}
|
|
|
|
if (dwarf_attrval_unsigned(die, DW_AT_low_pc, &udata, &de) ==
|
|
DW_DLV_OK)
|
|
func->lowpc = udata;
|
|
if (dwarf_attrval_unsigned(die, DW_AT_high_pc, &udata, &de) ==
|
|
DW_DLV_OK)
|
|
func->highpc = udata;
|
|
|
|
SLIST_INSERT_HEAD(func_info, func, entries);
|
|
}
|
|
|
|
cont_search:
|
|
|
|
/* Search children. */
|
|
ret = dwarf_child(die, &ret_die, &de);
|
|
if (ret == DW_DLV_ERROR)
|
|
warnx("dwarf_child: %s", dwarf_errmsg(de));
|
|
else if (ret == DW_DLV_OK)
|
|
search_line_attr(dbg, func_info, var_info, ret_die, src_files,
|
|
filecount);
|
|
|
|
/* Search sibling. */
|
|
ret = dwarf_siblingof(dbg, die, &ret_die, &de);
|
|
if (ret == DW_DLV_ERROR)
|
|
warnx("dwarf_siblingof: %s", dwarf_errmsg(de));
|
|
else if (ret == DW_DLV_OK)
|
|
search_line_attr(dbg, func_info, var_info, ret_die, src_files,
|
|
filecount);
|
|
|
|
dwarf_dealloc(dbg, die, DW_DLA_DIE);
|
|
}
|
|
|
|
/*
|
|
* Read elf file and collect symbol information, sort them, print.
|
|
* Return 1 at failed, 0 at success.
|
|
*/
|
|
static int
|
|
read_elf(Elf *elf, const char *filename, Elf_Kind kind)
|
|
{
|
|
Dwarf_Debug dbg;
|
|
Dwarf_Die die;
|
|
Dwarf_Error de;
|
|
Dwarf_Half tag;
|
|
Elf_Arhdr *arhdr;
|
|
Elf_Scn *scn;
|
|
GElf_Shdr shdr;
|
|
GElf_Half i;
|
|
Dwarf_Line *lbuf;
|
|
Dwarf_Unsigned lineno;
|
|
Dwarf_Signed lcount, filecount;
|
|
Dwarf_Addr lineaddr;
|
|
struct sym_print_data p_data;
|
|
struct sym_head list_head;
|
|
struct line_info_head *line_info;
|
|
struct func_info_head *func_info;
|
|
struct var_info_head *var_info;
|
|
struct line_info_entry *lie;
|
|
struct func_info_entry *func;
|
|
struct var_info_entry *var;
|
|
const char *shname, *objname;
|
|
char *type_table, **sec_table, *sfile, **src_files;
|
|
size_t shstrndx, shnum, dynndx, strndx;
|
|
int ret, rtn, e_err;
|
|
|
|
#define OBJNAME (objname == NULL ? filename : objname)
|
|
|
|
assert(filename != NULL && "filename is null");
|
|
|
|
STAILQ_INIT(&list_head);
|
|
type_table = NULL;
|
|
sec_table = NULL;
|
|
line_info = NULL;
|
|
func_info = NULL;
|
|
var_info = NULL;
|
|
objname = NULL;
|
|
dynndx = SHN_UNDEF;
|
|
strndx = SHN_UNDEF;
|
|
rtn = 0;
|
|
|
|
nm_elfclass = gelf_getclass(elf);
|
|
|
|
if (kind == ELF_K_AR) {
|
|
if ((arhdr = elf_getarhdr(elf)) == NULL)
|
|
goto next_cmd;
|
|
objname = arhdr->ar_name != NULL ? arhdr->ar_name :
|
|
arhdr->ar_rawname;
|
|
}
|
|
if (!elf_getshnum(elf, &shnum)) {
|
|
if ((e_err = elf_errno()) != 0)
|
|
warnx("%s: %s", OBJNAME, "File format not recognized");
|
|
else
|
|
warnx("%s: cannot get section number", OBJNAME);
|
|
rtn = 1;
|
|
goto next_cmd;
|
|
}
|
|
if (shnum == 0) {
|
|
warnx("%s: has no section", OBJNAME);
|
|
rtn = 1;
|
|
goto next_cmd;
|
|
}
|
|
if (!elf_getshstrndx(elf, &shstrndx)) {
|
|
warnx("%s: cannot get str index", OBJNAME);
|
|
rtn = 1;
|
|
goto next_cmd;
|
|
}
|
|
/* type_table for type determine */
|
|
if ((type_table = malloc(sizeof(char) * shnum)) == NULL) {
|
|
warn("%s: malloc", OBJNAME);
|
|
rtn = 1;
|
|
goto next_cmd;
|
|
}
|
|
/* sec_table for section name to display in sysv format */
|
|
if ((sec_table = calloc(shnum, sizeof(char *))) == NULL) {
|
|
warn("%s: calloc", OBJNAME);
|
|
rtn = 1;
|
|
goto next_cmd;
|
|
}
|
|
|
|
type_table[0] = 'U';
|
|
if ((sec_table[0] = strdup("*UND*")) == NULL) {
|
|
warn("strdup");
|
|
goto next_cmd;
|
|
}
|
|
|
|
for (i = 1; i < shnum; ++i) {
|
|
type_table[i] = 'U';
|
|
if ((scn = elf_getscn(elf, i)) == NULL) {
|
|
if ((e_err = elf_errno()) != 0)
|
|
warnx("%s: %s", OBJNAME, elf_errmsg(e_err));
|
|
else
|
|
warnx("%s: cannot get section", OBJNAME);
|
|
rtn = 1;
|
|
goto next_cmd;
|
|
}
|
|
if (gelf_getshdr(scn, &shdr) == NULL)
|
|
goto next_cmd;
|
|
|
|
/*
|
|
* Cannot test by type and attribute for dynstr, strtab
|
|
*/
|
|
shname = elf_strptr(elf, shstrndx, (size_t) shdr.sh_name);
|
|
if (shname != NULL) {
|
|
if ((sec_table[i] = strdup(shname)) == NULL) {
|
|
warn("strdup");
|
|
goto next_cmd;
|
|
}
|
|
if (!strncmp(shname, ".dynstr", 7)) {
|
|
dynndx = elf_ndxscn(scn);
|
|
if (dynndx == SHN_UNDEF) {
|
|
warnx("%s: elf_ndxscn failed: %s",
|
|
OBJNAME, elf_errmsg(-1));
|
|
goto next_cmd;
|
|
}
|
|
}
|
|
if (!strncmp(shname, ".strtab", 7)) {
|
|
strndx = elf_ndxscn(scn);
|
|
if (strndx == SHN_UNDEF) {
|
|
warnx("%s: elf_ndxscn failed: %s",
|
|
OBJNAME, elf_errmsg(-1));
|
|
goto next_cmd;
|
|
}
|
|
}
|
|
} else {
|
|
sec_table[i] = strdup("*UND*");
|
|
if (sec_table[i] == NULL) {
|
|
warn("strdup");
|
|
goto next_cmd;
|
|
}
|
|
}
|
|
|
|
|
|
if (is_sec_text(&shdr))
|
|
type_table[i] = 'T';
|
|
else if (is_sec_data(&shdr)) {
|
|
if (is_sec_readonly(&shdr))
|
|
type_table[i] = 'R';
|
|
else
|
|
type_table[i] = 'D';
|
|
} else if (is_sec_nobits(&shdr))
|
|
type_table[i] = 'B';
|
|
else if (is_sec_debug(shname))
|
|
type_table[i] = 'N';
|
|
else if (is_sec_readonly(&shdr) && !is_sec_nobits(&shdr))
|
|
type_table[i] = 'n';
|
|
}
|
|
|
|
print_header(filename, objname);
|
|
|
|
if ((dynndx == SHN_UNDEF && nm_opts.print_symbol == PRINT_SYM_DYN) ||
|
|
(strndx == SHN_UNDEF && nm_opts.print_symbol == PRINT_SYM_SYM)) {
|
|
warnx("%s: no symbols", OBJNAME);
|
|
/* This is not an error case */
|
|
goto next_cmd;
|
|
}
|
|
|
|
STAILQ_INIT(&list_head);
|
|
|
|
if (!nm_opts.debug_line)
|
|
goto process_sym;
|
|
|
|
/*
|
|
* Collect dwarf line number information.
|
|
*/
|
|
|
|
if (dwarf_elf_init(elf, DW_DLC_READ, NULL, NULL, &dbg, &de) !=
|
|
DW_DLV_OK) {
|
|
warnx("dwarf_elf_init failed: %s", dwarf_errmsg(de));
|
|
goto process_sym;
|
|
}
|
|
|
|
line_info = malloc(sizeof(struct line_info_head));
|
|
func_info = malloc(sizeof(struct func_info_head));
|
|
var_info = malloc(sizeof(struct var_info_head));
|
|
if (line_info == NULL || func_info == NULL || var_info == NULL) {
|
|
warn("malloc");
|
|
(void) dwarf_finish(dbg, &de);
|
|
goto process_sym;
|
|
}
|
|
SLIST_INIT(line_info);
|
|
SLIST_INIT(func_info);
|
|
SLIST_INIT(var_info);
|
|
|
|
while ((ret = dwarf_next_cu_header(dbg, NULL, NULL, NULL, NULL, NULL,
|
|
&de)) == DW_DLV_OK) {
|
|
die = NULL;
|
|
while (dwarf_siblingof(dbg, die, &die, &de) == DW_DLV_OK) {
|
|
if (dwarf_tag(die, &tag, &de) != DW_DLV_OK) {
|
|
warnx("dwarf_tag failed: %s",
|
|
dwarf_errmsg(de));
|
|
continue;
|
|
}
|
|
/* XXX: What about DW_TAG_partial_unit? */
|
|
if (tag == DW_TAG_compile_unit)
|
|
break;
|
|
}
|
|
if (die == NULL) {
|
|
warnx("could not find DW_TAG_compile_unit die");
|
|
continue;
|
|
}
|
|
|
|
/* Retrieve source file list. */
|
|
ret = dwarf_srcfiles(die, &src_files, &filecount, &de);
|
|
if (ret == DW_DLV_ERROR)
|
|
warnx("dwarf_srclines: %s", dwarf_errmsg(de));
|
|
if (ret != DW_DLV_OK)
|
|
continue;
|
|
|
|
/*
|
|
* Retrieve line number information from .debug_line section.
|
|
*/
|
|
|
|
ret = dwarf_srclines(die, &lbuf, &lcount, &de);
|
|
if (ret == DW_DLV_ERROR)
|
|
warnx("dwarf_srclines: %s", dwarf_errmsg(de));
|
|
if (ret != DW_DLV_OK)
|
|
goto line_attr;
|
|
for (i = 0; (Dwarf_Signed) i < lcount; i++) {
|
|
if (dwarf_lineaddr(lbuf[i], &lineaddr, &de)) {
|
|
warnx("dwarf_lineaddr: %s", dwarf_errmsg(de));
|
|
continue;
|
|
}
|
|
if (dwarf_lineno(lbuf[i], &lineno, &de)) {
|
|
warnx("dwarf_lineno: %s", dwarf_errmsg(de));
|
|
continue;
|
|
}
|
|
if (dwarf_linesrc(lbuf[i], &sfile, &de)) {
|
|
warnx("dwarf_linesrc: %s", dwarf_errmsg(de));
|
|
continue;
|
|
}
|
|
if ((lie = malloc(sizeof(*lie))) == NULL) {
|
|
warn("malloc");
|
|
continue;
|
|
}
|
|
lie->addr = lineaddr;
|
|
lie->line = lineno;
|
|
lie->file = strdup(sfile);
|
|
if (lie->file == NULL) {
|
|
warn("strdup");
|
|
free(lie);
|
|
continue;
|
|
}
|
|
SLIST_INSERT_HEAD(line_info, lie, entries);
|
|
}
|
|
|
|
line_attr:
|
|
/* Retrieve line number information from DIEs. */
|
|
search_line_attr(dbg, func_info, var_info, die, src_files, filecount);
|
|
}
|
|
|
|
(void) dwarf_finish(dbg, &de);
|
|
|
|
process_sym:
|
|
|
|
p_data.list_num = get_sym(elf, &list_head, shnum, dynndx, strndx,
|
|
type_table, (void *) sec_table, shnum);
|
|
|
|
if (p_data.list_num == 0)
|
|
goto next_cmd;
|
|
|
|
p_data.headp = &list_head;
|
|
p_data.sh_num = shnum;
|
|
p_data.t_table = type_table;
|
|
p_data.s_table = (void *) sec_table;
|
|
p_data.filename = filename;
|
|
p_data.objname = objname;
|
|
|
|
sym_list_print(&p_data, func_info, var_info, line_info);
|
|
|
|
next_cmd:
|
|
if (nm_opts.debug_line) {
|
|
if (func_info != NULL) {
|
|
while (!SLIST_EMPTY(func_info)) {
|
|
func = SLIST_FIRST(func_info);
|
|
SLIST_REMOVE_HEAD(func_info, entries);
|
|
free(func->file);
|
|
free(func->name);
|
|
free(func);
|
|
}
|
|
free(func_info);
|
|
func_info = NULL;
|
|
}
|
|
if (var_info != NULL) {
|
|
while (!SLIST_EMPTY(var_info)) {
|
|
var = SLIST_FIRST(var_info);
|
|
SLIST_REMOVE_HEAD(var_info, entries);
|
|
free(var->file);
|
|
free(var->name);
|
|
free(var);
|
|
}
|
|
free(var_info);
|
|
var_info = NULL;
|
|
}
|
|
if (line_info != NULL) {
|
|
while (!SLIST_EMPTY(line_info)) {
|
|
lie = SLIST_FIRST(line_info);
|
|
SLIST_REMOVE_HEAD(line_info, entries);
|
|
free(lie->file);
|
|
free(lie);
|
|
}
|
|
free(line_info);
|
|
line_info = NULL;
|
|
}
|
|
}
|
|
|
|
if (sec_table != NULL)
|
|
for (i = 0; i < shnum; ++i)
|
|
free(sec_table[i]);
|
|
free(sec_table);
|
|
free(type_table);
|
|
|
|
sym_list_dest(&list_head);
|
|
|
|
return (rtn);
|
|
|
|
#undef OBJNAME
|
|
}
|
|
|
|
static int
|
|
read_object(const char *filename)
|
|
{
|
|
Elf *elf, *arf;
|
|
Elf_Cmd elf_cmd;
|
|
Elf_Kind kind;
|
|
int fd, rtn, e_err;
|
|
|
|
assert(filename != NULL && "filename is null");
|
|
|
|
if ((fd = open(filename, O_RDONLY)) == -1) {
|
|
warn("'%s'", filename);
|
|
return (1);
|
|
}
|
|
|
|
elf_cmd = ELF_C_READ;
|
|
if ((arf = elf_begin(fd, elf_cmd, (Elf *) NULL)) == NULL) {
|
|
if ((e_err = elf_errno()) != 0)
|
|
warnx("elf_begin error: %s", elf_errmsg(e_err));
|
|
else
|
|
warnx("elf_begin error");
|
|
close(fd);
|
|
return (1);
|
|
}
|
|
|
|
assert(arf != NULL && "arf is null.");
|
|
|
|
rtn = 0;
|
|
if ((kind = elf_kind(arf)) == ELF_K_NONE) {
|
|
warnx("%s: File format not recognized", filename);
|
|
elf_end(arf);
|
|
close(fd);
|
|
return (1);
|
|
}
|
|
if (kind == ELF_K_AR) {
|
|
if (nm_opts.print_name == PRINT_NAME_MULTI &&
|
|
nm_opts.elem_print_fn == sym_elem_print_all)
|
|
printf("\n%s:\n", filename);
|
|
if (nm_opts.print_armap == true)
|
|
print_ar_index(fd, arf);
|
|
}
|
|
|
|
while ((elf = elf_begin(fd, elf_cmd, arf)) != NULL) {
|
|
rtn |= read_elf(elf, filename, kind);
|
|
|
|
/*
|
|
* If file is not archive, elf_next return ELF_C_NULL and
|
|
* stop the loop.
|
|
*/
|
|
elf_cmd = elf_next(elf);
|
|
elf_end(elf);
|
|
}
|
|
|
|
elf_end(arf);
|
|
close(fd);
|
|
|
|
return (rtn);
|
|
}
|
|
|
|
static int
|
|
read_files(int argc, char **argv)
|
|
{
|
|
int rtn = 0;
|
|
|
|
if (argc < 0 || argv == NULL)
|
|
return (1);
|
|
|
|
if (argc == 0)
|
|
rtn |= read_object(nm_info.def_filename);
|
|
else {
|
|
if (nm_opts.print_name == PRINT_NAME_NONE && argc > 1)
|
|
nm_opts.print_name = PRINT_NAME_MULTI;
|
|
while (argc > 0) {
|
|
rtn |= read_object(*argv);
|
|
--argc;
|
|
++argv;
|
|
}
|
|
}
|
|
|
|
return (rtn);
|
|
}
|
|
|
|
static void
|
|
print_lineno(struct sym_entry *ep, struct func_info_head *func_info,
|
|
struct var_info_head *var_info, struct line_info_head *line_info)
|
|
{
|
|
struct func_info_entry *func;
|
|
struct var_info_entry *var;
|
|
struct line_info_entry *lie;
|
|
|
|
/* For function symbol, search the function line information list. */
|
|
if ((ep->sym->st_info & 0xf) == STT_FUNC && func_info != NULL) {
|
|
SLIST_FOREACH(func, func_info, entries) {
|
|
if (func->name != NULL &&
|
|
!strcmp(ep->name, func->name) &&
|
|
ep->sym->st_value >= func->lowpc &&
|
|
ep->sym->st_value < func->highpc) {
|
|
printf("\t%s:%" PRIu64, func->file, func->line);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* For variable symbol, search the variable line information list. */
|
|
if ((ep->sym->st_info & 0xf) == STT_OBJECT && var_info != NULL) {
|
|
SLIST_FOREACH(var, var_info, entries) {
|
|
if (!strcmp(ep->name, var->name) &&
|
|
ep->sym->st_value == var->addr) {
|
|
printf("\t%s:%" PRIu64, var->file, var->line);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Otherwise search line number information the .debug_line section. */
|
|
if (line_info != NULL) {
|
|
SLIST_FOREACH(lie, line_info, entries) {
|
|
if (ep->sym->st_value == lie->addr) {
|
|
printf("\t%s:%" PRIu64, lie->file, lie->line);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
set_opt_value_print_fn(enum radix t)
|
|
{
|
|
|
|
switch (t) {
|
|
case RADIX_OCT:
|
|
nm_opts.value_print_fn = &sym_value_oct_print;
|
|
nm_opts.size_print_fn = &sym_size_oct_print;
|
|
|
|
break;
|
|
case RADIX_DEC:
|
|
nm_opts.value_print_fn = &sym_value_dec_print;
|
|
nm_opts.size_print_fn = &sym_size_dec_print;
|
|
|
|
break;
|
|
case RADIX_HEX:
|
|
default :
|
|
nm_opts.value_print_fn = &sym_value_hex_print;
|
|
nm_opts.size_print_fn = &sym_size_hex_print;
|
|
}
|
|
|
|
assert(nm_opts.value_print_fn != NULL &&
|
|
"nm_opts.value_print_fn is null");
|
|
}
|
|
|
|
static void
|
|
sym_elem_print_all(char type, const char *sec, const GElf_Sym *sym,
|
|
const char *name)
|
|
{
|
|
|
|
if (sec == NULL || sym == NULL || name == NULL ||
|
|
nm_opts.value_print_fn == NULL)
|
|
return;
|
|
|
|
if (IS_UNDEF_SYM_TYPE(type)) {
|
|
if (nm_opts.t == RADIX_HEX && nm_elfclass == ELFCLASS32)
|
|
printf("%-8s", "");
|
|
else
|
|
printf("%-16s", "");
|
|
} else {
|
|
switch ((nm_opts.sort_fn == & cmp_size ? 2 : 0) +
|
|
nm_opts.print_size) {
|
|
case 3:
|
|
if (sym->st_size != 0) {
|
|
nm_opts.value_print_fn(sym);
|
|
printf(" ");
|
|
nm_opts.size_print_fn(sym);
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
if (sym->st_size != 0)
|
|
nm_opts.size_print_fn(sym);
|
|
break;
|
|
|
|
case 1:
|
|
nm_opts.value_print_fn(sym);
|
|
if (sym->st_size != 0) {
|
|
printf(" ");
|
|
nm_opts.size_print_fn(sym);
|
|
}
|
|
break;
|
|
|
|
case 0:
|
|
default:
|
|
nm_opts.value_print_fn(sym);
|
|
}
|
|
}
|
|
|
|
printf(" %c ", type);
|
|
PRINT_DEMANGLED_NAME("%s", name);
|
|
}
|
|
|
|
static void
|
|
sym_elem_print_all_portable(char type, const char *sec, const GElf_Sym *sym,
|
|
const char *name)
|
|
{
|
|
|
|
if (sec == NULL || sym == NULL || name == NULL ||
|
|
nm_opts.value_print_fn == NULL)
|
|
return;
|
|
|
|
PRINT_DEMANGLED_NAME("%s", name);
|
|
printf(" %c ", type);
|
|
if (!IS_UNDEF_SYM_TYPE(type)) {
|
|
nm_opts.value_print_fn(sym);
|
|
printf(" ");
|
|
if (sym->st_size != 0)
|
|
nm_opts.size_print_fn(sym);
|
|
} else
|
|
printf(" ");
|
|
}
|
|
|
|
static void
|
|
sym_elem_print_all_sysv(char type, const char *sec, const GElf_Sym *sym,
|
|
const char *name)
|
|
{
|
|
|
|
if (sec == NULL || sym == NULL || name == NULL ||
|
|
nm_opts.value_print_fn == NULL)
|
|
return;
|
|
|
|
PRINT_DEMANGLED_NAME("%-20s|", name);
|
|
if (IS_UNDEF_SYM_TYPE(type))
|
|
printf(" ");
|
|
else
|
|
nm_opts.value_print_fn(sym);
|
|
|
|
printf("| %c |", type);
|
|
|
|
switch (sym->st_info & 0xf) {
|
|
case STT_OBJECT:
|
|
printf("%18s|", "OBJECT");
|
|
break;
|
|
|
|
case STT_FUNC:
|
|
printf("%18s|", "FUNC");
|
|
break;
|
|
|
|
case STT_SECTION:
|
|
printf("%18s|", "SECTION");
|
|
break;
|
|
|
|
case STT_FILE:
|
|
printf("%18s|", "FILE");
|
|
break;
|
|
|
|
case STT_LOPROC:
|
|
printf("%18s|", "LOPROC");
|
|
break;
|
|
|
|
case STT_HIPROC:
|
|
printf("%18s|", "HIPROC");
|
|
break;
|
|
|
|
case STT_NOTYPE:
|
|
default:
|
|
printf("%18s|", "NOTYPE");
|
|
}
|
|
|
|
if (sym->st_size != 0)
|
|
nm_opts.size_print_fn(sym);
|
|
else
|
|
printf(" ");
|
|
|
|
printf("| |%s", sec);
|
|
}
|
|
|
|
static int
|
|
sym_elem_def(char type, const GElf_Sym *sym, const char *name)
|
|
{
|
|
|
|
assert(IS_SYM_TYPE((unsigned char) type));
|
|
|
|
UNUSED(sym);
|
|
UNUSED(name);
|
|
|
|
return (!IS_UNDEF_SYM_TYPE((unsigned char) type));
|
|
}
|
|
|
|
static int
|
|
sym_elem_global(char type, const GElf_Sym *sym, const char *name)
|
|
{
|
|
|
|
assert(IS_SYM_TYPE((unsigned char) type));
|
|
|
|
UNUSED(sym);
|
|
UNUSED(name);
|
|
|
|
/* weak symbols resemble global. */
|
|
return (isupper((unsigned char) type) || type == 'w');
|
|
}
|
|
|
|
static int
|
|
sym_elem_global_static(char type, const GElf_Sym *sym, const char *name)
|
|
{
|
|
unsigned char info;
|
|
|
|
assert(sym != NULL);
|
|
|
|
UNUSED(type);
|
|
UNUSED(name);
|
|
|
|
info = sym->st_info >> 4;
|
|
|
|
return (info == STB_LOCAL ||
|
|
info == STB_GLOBAL ||
|
|
info == STB_WEAK);
|
|
}
|
|
|
|
static int
|
|
sym_elem_nondebug(char type, const GElf_Sym *sym, const char *name)
|
|
{
|
|
|
|
assert(sym != NULL);
|
|
|
|
UNUSED(type);
|
|
UNUSED(name);
|
|
|
|
if (sym->st_value == 0 && (sym->st_info & 0xf) == STT_FILE)
|
|
return (0);
|
|
if (sym->st_name == 0)
|
|
return (0);
|
|
|
|
return (1);
|
|
}
|
|
|
|
static int
|
|
sym_elem_nonzero_size(char type, const GElf_Sym *sym, const char *name)
|
|
{
|
|
|
|
assert(sym != NULL);
|
|
|
|
UNUSED(type);
|
|
UNUSED(name);
|
|
|
|
return (sym->st_size > 0);
|
|
}
|
|
|
|
static int
|
|
sym_elem_undef(char type, const GElf_Sym *sym, const char *name)
|
|
{
|
|
|
|
assert(IS_SYM_TYPE((unsigned char) type));
|
|
|
|
UNUSED(sym);
|
|
UNUSED(name);
|
|
|
|
return (IS_UNDEF_SYM_TYPE((unsigned char) type));
|
|
}
|
|
|
|
static void
|
|
sym_list_dest(struct sym_head *headp)
|
|
{
|
|
struct sym_entry *ep, *ep_n;
|
|
|
|
if (headp == NULL)
|
|
return;
|
|
|
|
ep = STAILQ_FIRST(headp);
|
|
while (ep != NULL) {
|
|
ep_n = STAILQ_NEXT(ep, sym_entries);
|
|
free(ep->sym);
|
|
free(ep->name);
|
|
free(ep);
|
|
ep = ep_n;
|
|
}
|
|
}
|
|
|
|
static int
|
|
sym_list_insert(struct sym_head *headp, const char *name, const GElf_Sym *sym)
|
|
{
|
|
struct sym_entry *e;
|
|
|
|
if (headp == NULL || name == NULL || sym == NULL)
|
|
return (0);
|
|
if ((e = malloc(sizeof(struct sym_entry))) == NULL) {
|
|
warn("malloc");
|
|
return (0);
|
|
}
|
|
if ((e->name = strdup(name)) == NULL) {
|
|
warn("strdup");
|
|
free(e);
|
|
return (0);
|
|
}
|
|
if ((e->sym = malloc(sizeof(GElf_Sym))) == NULL) {
|
|
warn("malloc");
|
|
free(e->name);
|
|
free(e);
|
|
return (0);
|
|
}
|
|
|
|
memcpy(e->sym, sym, sizeof(GElf_Sym));
|
|
|
|
/* Display size instead of value for common symbol. */
|
|
if (sym->st_shndx == SHN_COMMON)
|
|
e->sym->st_value = sym->st_size;
|
|
|
|
STAILQ_INSERT_TAIL(headp, e, sym_entries);
|
|
|
|
return (1);
|
|
}
|
|
|
|
/* If file has not .debug_info, line_info will be NULL */
|
|
static void
|
|
sym_list_print(struct sym_print_data *p, struct func_info_head *func_info,
|
|
struct var_info_head *var_info, struct line_info_head *line_info)
|
|
{
|
|
struct sym_entry *e_v;
|
|
size_t si;
|
|
int i;
|
|
|
|
if (p == NULL || CHECK_SYM_PRINT_DATA(p))
|
|
return;
|
|
if ((e_v = sym_list_sort(p)) == NULL)
|
|
return;
|
|
if (nm_opts.sort_reverse == false)
|
|
for (si = 0; si != p->list_num; ++si)
|
|
sym_list_print_each(&e_v[si], p, func_info, var_info,
|
|
line_info);
|
|
else
|
|
for (i = p->list_num - 1; i != -1; --i)
|
|
sym_list_print_each(&e_v[i], p, func_info, var_info,
|
|
line_info);
|
|
|
|
free(e_v);
|
|
}
|
|
|
|
/* If file has not .debug_info, line_info will be NULL */
|
|
static void
|
|
sym_list_print_each(struct sym_entry *ep, struct sym_print_data *p,
|
|
struct func_info_head *func_info, struct var_info_head *var_info,
|
|
struct line_info_head *line_info)
|
|
{
|
|
const char *sec;
|
|
char type;
|
|
|
|
if (ep == NULL || CHECK_SYM_PRINT_DATA(p))
|
|
return;
|
|
|
|
assert(ep->name != NULL);
|
|
assert(ep->sym != NULL);
|
|
|
|
type = get_sym_type(ep->sym, p->t_table);
|
|
|
|
if (nm_opts.print_name == PRINT_NAME_FULL) {
|
|
printf("%s", p->filename);
|
|
if (nm_opts.elem_print_fn == &sym_elem_print_all_portable) {
|
|
if (p->objname != NULL)
|
|
printf("[%s]", p->objname);
|
|
printf(": ");
|
|
} else {
|
|
if (p->objname != NULL)
|
|
printf(":%s", p->objname);
|
|
printf(":");
|
|
}
|
|
}
|
|
|
|
switch (ep->sym->st_shndx) {
|
|
case SHN_LOPROC:
|
|
/* LOPROC or LORESERVE */
|
|
sec = "*LOPROC*";
|
|
break;
|
|
case SHN_HIPROC:
|
|
sec = "*HIPROC*";
|
|
break;
|
|
case SHN_LOOS:
|
|
sec = "*LOOS*";
|
|
break;
|
|
case SHN_HIOS:
|
|
sec = "*HIOS*";
|
|
break;
|
|
case SHN_ABS:
|
|
sec = "*ABS*";
|
|
break;
|
|
case SHN_COMMON:
|
|
sec = "*COM*";
|
|
break;
|
|
case SHN_HIRESERVE:
|
|
/* HIRESERVE or XINDEX */
|
|
sec = "*HIRESERVE*";
|
|
break;
|
|
default:
|
|
if (ep->sym->st_shndx > p->sh_num)
|
|
return;
|
|
sec = p->s_table[ep->sym->st_shndx];
|
|
break;
|
|
}
|
|
|
|
nm_opts.elem_print_fn(type, sec, ep->sym, ep->name);
|
|
|
|
if (nm_opts.debug_line == true && !IS_UNDEF_SYM_TYPE(type))
|
|
print_lineno(ep, func_info, var_info, line_info);
|
|
|
|
printf("\n");
|
|
}
|
|
|
|
static struct sym_entry *
|
|
sym_list_sort(struct sym_print_data *p)
|
|
{
|
|
struct sym_entry *ep, *e_v;
|
|
int idx;
|
|
|
|
if (p == NULL || CHECK_SYM_PRINT_DATA(p))
|
|
return (NULL);
|
|
|
|
if ((e_v = malloc(sizeof(struct sym_entry) * p->list_num)) == NULL) {
|
|
warn("malloc");
|
|
return (NULL);
|
|
}
|
|
|
|
idx = 0;
|
|
STAILQ_FOREACH(ep, p->headp, sym_entries) {
|
|
if (ep->name != NULL && ep->sym != NULL) {
|
|
e_v[idx].name = ep->name;
|
|
e_v[idx].sym = ep->sym;
|
|
++idx;
|
|
}
|
|
}
|
|
|
|
assert((size_t)idx == p->list_num);
|
|
|
|
if (nm_opts.sort_fn != &cmp_none) {
|
|
nm_print_data = p;
|
|
assert(nm_print_data != NULL);
|
|
qsort(e_v, p->list_num, sizeof(struct sym_entry),
|
|
nm_opts.sort_fn);
|
|
}
|
|
|
|
return (e_v);
|
|
}
|
|
|
|
static void
|
|
sym_size_oct_print(const GElf_Sym *sym)
|
|
{
|
|
|
|
assert(sym != NULL && "sym is null");
|
|
printf("%016" PRIo64, sym->st_size);
|
|
}
|
|
|
|
static void
|
|
sym_size_hex_print(const GElf_Sym *sym)
|
|
{
|
|
|
|
assert(sym != NULL && "sym is null");
|
|
if (nm_elfclass == ELFCLASS32)
|
|
printf("%08" PRIx64, sym->st_size);
|
|
else
|
|
printf("%016" PRIx64, sym->st_size);
|
|
}
|
|
|
|
static void
|
|
sym_size_dec_print(const GElf_Sym *sym)
|
|
{
|
|
|
|
assert(sym != NULL && "sym is null");
|
|
printf("%016" PRId64, sym->st_size);
|
|
}
|
|
|
|
static void
|
|
sym_value_oct_print(const GElf_Sym *sym)
|
|
{
|
|
|
|
assert(sym != NULL && "sym is null");
|
|
printf("%016" PRIo64, sym->st_value);
|
|
}
|
|
|
|
static void
|
|
sym_value_hex_print(const GElf_Sym *sym)
|
|
{
|
|
|
|
assert(sym != NULL && "sym is null");
|
|
if (nm_elfclass == ELFCLASS32)
|
|
printf("%08" PRIx64, sym->st_value);
|
|
else
|
|
printf("%016" PRIx64, sym->st_value);
|
|
}
|
|
|
|
static void
|
|
sym_value_dec_print(const GElf_Sym *sym)
|
|
{
|
|
|
|
assert(sym != NULL && "sym is null");
|
|
printf("%016" PRId64, sym->st_value);
|
|
}
|
|
|
|
static void
|
|
usage(int exitcode)
|
|
{
|
|
|
|
printf("Usage: %s [options] file ...\
|
|
\n Display symbolic information in file.\n\
|
|
\n Options: \
|
|
\n -A, --print-file-name Write the full pathname or library name of an\
|
|
\n object on each line.\
|
|
\n -a, --debug-syms Display all symbols include debugger-only\
|
|
\n symbols.", nm_info.name);
|
|
printf("\
|
|
\n -B Equivalent to specifying \"--format=bsd\".\
|
|
\n -C, --demangle[=style] Decode low-level symbol names.\
|
|
\n --no-demangle Do not demangle low-level symbol names.\
|
|
\n -D, --dynamic Display only dynamic symbols.\
|
|
\n -e Display only global and static symbols.");
|
|
printf("\
|
|
\n -f Produce full output (default).\
|
|
\n --format=format Display output in specific format. Allowed\
|
|
\n formats are: \"bsd\", \"posix\" and \"sysv\".\
|
|
\n -g, --extern-only Display only global symbol information.\
|
|
\n -h, --help Show this help message.\
|
|
\n -l, --line-numbers Display filename and linenumber using\
|
|
\n debugging information.\
|
|
\n -n, --numeric-sort Sort symbols numerically by value.");
|
|
printf("\
|
|
\n -o Write numeric values in octal. Equivalent to\
|
|
\n specifying \"-t o\".\
|
|
\n -p, --no-sort Do not sort symbols.\
|
|
\n -P Write information in a portable output format.\
|
|
\n Equivalent to specifying \"--format=posix\".\
|
|
\n -r, --reverse-sort Reverse the order of the sort.\
|
|
\n -S, --print-size Print symbol sizes instead values.\
|
|
\n -s, --print-armap Include an index of archive members.\
|
|
\n --size-sort Sort symbols by size.");
|
|
printf("\
|
|
\n -t, --radix=format Write each numeric value in the specified\
|
|
\n format:\
|
|
\n d In decimal,\
|
|
\n o In octal,\
|
|
\n x In hexadecimal.");
|
|
printf("\
|
|
\n -u, --undefined-only Display only undefined symbols.\
|
|
\n --defined-only Display only defined symbols.\
|
|
\n -V, --version Show the version identifier for %s.\
|
|
\n -v Sort output by value.\
|
|
\n -x Write numeric values in hexadecimal.\
|
|
\n Equivalent to specifying \"-t x\".",
|
|
nm_info.name);
|
|
printf("\n\
|
|
\n The default options are: output in bsd format, use a hexadecimal radix,\
|
|
\n sort by symbol name, do not demangle names.\n");
|
|
|
|
exit(exitcode);
|
|
}
|
|
|
|
/*
|
|
* Display symbolic information in file.
|
|
* Return 0 at success, >0 at failed.
|
|
*/
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
int rtn;
|
|
|
|
global_init();
|
|
get_opt(argc, argv);
|
|
rtn = read_files(argc - optind, argv + optind);
|
|
global_dest();
|
|
|
|
exit(rtn);
|
|
}
|