summaryrefslogtreecommitdiff
path: root/utils/mcpu-nm.c
diff options
context:
space:
mode:
Diffstat (limited to 'utils/mcpu-nm.c')
-rw-r--r--utils/mcpu-nm.c716
1 files changed, 716 insertions, 0 deletions
diff --git a/utils/mcpu-nm.c b/utils/mcpu-nm.c
new file mode 100644
index 0000000..63c3bfd
--- /dev/null
+++ b/utils/mcpu-nm.c
@@ -0,0 +1,716 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <libmcpu.h>
+#include <mcpu-elf.h>
+
+#include "mcpu-archive.h"
+#include "mcpu-melf.h"
+#include "mcpu-version.h"
+
+
+typedef struct mcpu_nm_shdr
+{
+ __mpu_uint32_t type;
+ __mpu_uint64_t flags;
+ __mpu_uint64_t offset;
+ __mpu_uint64_t size;
+ __mpu_uint32_t link;
+ __mpu_uint64_t entsize;
+} mcpu_nm_shdr;
+
+
+typedef struct mcpu_nm_file
+{
+ const unsigned char *data;
+ size_t size;
+ int encoding;
+ __mpu_uint16_t shnum;
+ mcpu_nm_shdr *shdr;
+} mcpu_nm_file;
+
+
+typedef struct mcpu_nm_symbol
+{
+ char *name;
+ __mpu_uint64_t value;
+ __mpu_uint64_t size;
+ __mpu_uint16_t shndx;
+ unsigned int bind;
+ unsigned int type;
+ char code;
+ size_t order;
+} mcpu_nm_symbol;
+
+
+typedef struct mcpu_nm_symbols
+{
+ mcpu_nm_symbol *symbol;
+ size_t count;
+} mcpu_nm_symbols;
+
+
+typedef struct mcpu_nm_options
+{
+ int all_symbols;
+ int extern_only;
+ int undefined_only;
+ int numeric_sort;
+ int reverse_sort;
+ int no_sort;
+ int dynamic;
+ int print_file_name;
+} mcpu_nm_options;
+
+
+static const mcpu_nm_options *sort_options;
+
+
+static void
+usage( FILE *fp, const char *name )
+{
+ fprintf( fp,
+ "usage: %s [options] MELF-file(s)\n\n"
+ "List symbols from LibMCPU MELF objects, executables, shared objects and archives.\n\n"
+ "Options:\n"
+ " -a, --debug-syms - display all named symbols, including FILE/SECTION\n"
+ " -g, --extern-only - display only external GLOBAL/WEAK symbols\n"
+ " -u, --undefined-only - display only undefined symbols\n"
+ " -n, --numeric-sort - sort symbols numerically by value\n"
+ " -r, --reverse-sort - reverse the selected sort order\n"
+ " -p, --no-sort - preserve symbol-table order\n"
+ " -D, --dynamic - display .dynsym instead of .symtab\n"
+ " -A, --print-file-name - prefix each symbol with its input file name\n"
+ " --help - show this help\n"
+ " --version - show program version\n"
+ " --dump-version - show version number only\n",
+ name );
+
+} /* End of usage() */
+
+
+static void
+nm_file_free( mcpu_nm_file *file )
+{
+ if( !file )
+ return;
+ free( file->shdr );
+ memset( file, 0, sizeof( *file ) );
+
+} /* End of nm_file_free() */
+
+
+static void
+nm_symbols_free( mcpu_nm_symbols *symbols )
+{
+ size_t i;
+
+ if( !symbols )
+ return;
+ for( i = 0; i < symbols->count; ++i )
+ free( symbols->symbol[i].name );
+ free( symbols->symbol );
+ memset( symbols, 0, sizeof( *symbols ) );
+
+} /* End of nm_symbols_free() */
+
+
+static void
+get_shdr( const unsigned char *p, int data, mcpu_nm_shdr *sh )
+{
+ memset( sh, 0, sizeof( *sh ) );
+ sh->type = cpu_melf_get32( p + MELF64_SHDR_SH_TYPE_OFF, data );
+ sh->flags = cpu_melf_get64( p + MELF64_SHDR_SH_FLAGS_OFF, data );
+ sh->offset = cpu_melf_get64( p + MELF64_SHDR_SH_OFFSET_OFF, data );
+ sh->size = cpu_melf_get64( p + MELF64_SHDR_SH_SIZE_OFF, data );
+ sh->link = cpu_melf_get32( p + MELF64_SHDR_SH_LINK_OFF, data );
+ sh->entsize = cpu_melf_get64( p + MELF64_SHDR_SH_ENTSIZE_OFF, data );
+
+} /* End of get_shdr() */
+
+
+static int
+nm_file_open_data( const unsigned char *data, size_t size, mcpu_nm_file *file,
+ char *err, size_t errsz )
+{
+ __mpu_uint64_t shoff;
+ __mpu_uint16_t shentsize;
+ size_t i;
+
+ memset( file, 0, sizeof( *file ) );
+ file->data = data;
+ file->size = size;
+
+ if( size < MELF64_EHDR_SIZE )
+ {
+ snprintf( err, errsz, "file is too small for a MELF64 header" );
+ return( -1 );
+ }
+ if( data[MELF_EI_MAG0] != MELFMAG0 || data[MELF_EI_MAG1] != MELFMAG1 ||
+ data[MELF_EI_MAG2] != MELFMAG2 || data[MELF_EI_MAG3] != MELFMAG3 )
+ {
+ snprintf( err, errsz, "not a MELF file" );
+ return( -1 );
+ }
+ if( data[MELF_EI_CLASS] != MELF_CLASS_64 )
+ {
+ snprintf( err, errsz, "unsupported MELF class %u",
+ (unsigned int)data[MELF_EI_CLASS] );
+ return( -1 );
+ }
+
+ file->encoding = data[MELF_EI_DATA];
+ if( file->encoding != MELF_DATA_LSB && file->encoding != MELF_DATA_MSB )
+ {
+ snprintf( err, errsz, "invalid MELF data encoding %d", file->encoding );
+ return( -1 );
+ }
+ if( cpu_melf_get16( data + MELF64_EHDR_E_EHSIZE_OFF, file->encoding ) !=
+ MELF64_EHDR_SIZE )
+ {
+ snprintf( err, errsz, "unsupported MELF64 header size" );
+ return( -1 );
+ }
+
+ shoff = cpu_melf_get64( data + MELF64_EHDR_E_SHOFF_OFF, file->encoding );
+ shentsize = cpu_melf_get16( data + MELF64_EHDR_E_SHENTSIZE_OFF,
+ file->encoding );
+ file->shnum = cpu_melf_get16( data + MELF64_EHDR_E_SHNUM_OFF,
+ file->encoding );
+
+ if( !file->shnum )
+ return( 0 );
+ if( shentsize != MELF64_SHDR_SIZE ||
+ !cpu_melf_span( size, shoff,
+ (__mpu_uint64_t)file->shnum * MELF64_SHDR_SIZE ) )
+ {
+ snprintf( err, errsz, "malformed section-header table" );
+ return( -1 );
+ }
+
+ file->shdr = (mcpu_nm_shdr *)calloc( file->shnum, sizeof( *file->shdr ) );
+ if( !file->shdr )
+ {
+ snprintf( err, errsz, "out of memory" );
+ return( -1 );
+ }
+
+ for( i = 0; i < file->shnum; ++i )
+ get_shdr( data + (size_t)shoff + i * MELF64_SHDR_SIZE,
+ file->encoding, &file->shdr[i] );
+
+ return( 0 );
+
+} /* End of nm_file_open_data() */
+
+
+static const char *
+string_at( const unsigned char *table, size_t size, __mpu_uint64_t offset )
+{
+ const unsigned char *p;
+
+ if( !table || offset >= size )
+ return( NULL );
+ p = table + (size_t)offset;
+ if( !memchr( p, '\0', size - (size_t)offset ) )
+ return( NULL );
+ return( (const char *)p );
+
+} /* End of string_at() */
+
+
+static int
+symbol_code( const mcpu_nm_file *file, unsigned int bind,
+ __mpu_uint16_t shndx, char *code )
+{
+ char c;
+
+ if( bind == MELF_STB_WEAK )
+ {
+ *code = shndx == MELF_SHN_UNDEF ? 'w' : 'W';
+ return( 0 );
+ }
+ if( shndx == MELF_SHN_UNDEF )
+ {
+ *code = 'U';
+ return( 0 );
+ }
+ if( shndx == MELF_SHN_ABS )
+ c = 'A';
+ else if( shndx == MELF_SHN_COMMON )
+ c = 'C';
+ else if( shndx >= file->shnum )
+ c = '?';
+ else if( file->shdr[shndx].flags & MELF_SHF_EXECINSTR )
+ c = 'T';
+ else if( file->shdr[shndx].type == MELF_SHT_NOBITS &&
+ (file->shdr[shndx].flags & MELF_SHF_WRITE) )
+ c = 'B';
+ else if( file->shdr[shndx].flags & MELF_SHF_WRITE )
+ c = 'D';
+ else if( file->shdr[shndx].flags & MELF_SHF_ALLOC )
+ c = 'R';
+ else
+ c = 'N';
+
+ if( bind == MELF_STB_LOCAL && c >= 'A' && c <= 'Z' )
+ c = (char)(c - 'A' + 'a');
+ *code = c;
+ return( c == '?' ? -1 : 0 );
+
+} /* End of symbol_code() */
+
+
+static int
+symbols_append( mcpu_nm_symbols *symbols, const char *name,
+ __mpu_uint64_t value, __mpu_uint64_t size,
+ __mpu_uint16_t shndx, unsigned int bind, unsigned int type,
+ char code, size_t order, char *err, size_t errsz )
+{
+ mcpu_nm_symbol *q;
+ char *copy;
+
+ q = (mcpu_nm_symbol *)realloc( symbols->symbol,
+ (symbols->count + 1) * sizeof( *q ) );
+ if( !q )
+ {
+ snprintf( err, errsz, "out of memory" );
+ return( -1 );
+ }
+ copy = strdup( name );
+ if( !copy )
+ {
+ snprintf( err, errsz, "out of memory" );
+ return( -1 );
+ }
+
+ symbols->symbol = q;
+ q = &symbols->symbol[symbols->count++];
+ memset( q, 0, sizeof( *q ) );
+ q->name = copy;
+ q->value = value;
+ q->size = size;
+ q->shndx = shndx;
+ q->bind = bind;
+ q->type = type;
+ q->code = code;
+ q->order = order;
+ return( 0 );
+
+} /* End of symbols_append() */
+
+
+static int
+collect_table( const mcpu_nm_file *file, size_t table_index,
+ const mcpu_nm_options *options, mcpu_nm_symbols *symbols,
+ size_t *order, char *err, size_t errsz )
+{
+ const mcpu_nm_shdr *sh = &file->shdr[table_index];
+ const mcpu_nm_shdr *str;
+ const unsigned char *strtab;
+ size_t strsize;
+ size_t count;
+ size_t i;
+
+ if( sh->entsize != MELF64_SYM_SIZE || sh->size % MELF64_SYM_SIZE ||
+ sh->size > SIZE_MAX || !cpu_melf_span( file->size, sh->offset, sh->size ) )
+ {
+ snprintf( err, errsz, "malformed symbol table" );
+ return( -1 );
+ }
+ if( sh->link >= file->shnum )
+ {
+ snprintf( err, errsz, "symbol table has invalid string-table link" );
+ return( -1 );
+ }
+ str = &file->shdr[sh->link];
+ if( str->type != MELF_SHT_STRTAB || str->size > SIZE_MAX ||
+ !cpu_melf_span( file->size, str->offset, str->size ) )
+ {
+ snprintf( err, errsz, "malformed symbol string table" );
+ return( -1 );
+ }
+
+ strtab = file->data + (size_t)str->offset;
+ strsize = (size_t)str->size;
+ count = (size_t)(sh->size / MELF64_SYM_SIZE);
+
+ for( i = 0; i < count; ++i )
+ {
+ const unsigned char *p;
+ __mpu_uint32_t name_off;
+ unsigned int bind;
+ unsigned int type;
+ __mpu_uint16_t shndx;
+ __mpu_uint64_t value;
+ __mpu_uint64_t ssize;
+ const char *name;
+ char code;
+
+ p = file->data + (size_t)sh->offset + i * MELF64_SYM_SIZE;
+ name_off = cpu_melf_get32( p + MELF64_SYM_ST_NAME_OFF, file->encoding );
+ bind = MELF64_ST_BIND( p[MELF64_SYM_ST_INFO_OFF] );
+ type = MELF64_ST_TYPE( p[MELF64_SYM_ST_INFO_OFF] );
+ shndx = cpu_melf_get16( p + MELF64_SYM_ST_SHNDX_OFF, file->encoding );
+ value = cpu_melf_get64( p + MELF64_SYM_ST_VALUE_OFF, file->encoding );
+ ssize = cpu_melf_get64( p + MELF64_SYM_ST_SIZE_OFF, file->encoding );
+ name = string_at( strtab, strsize, name_off );
+
+ if( !name )
+ {
+ snprintf( err, errsz, "symbol has invalid name offset" );
+ return( -1 );
+ }
+ if( !*name )
+ continue;
+ if( !options->all_symbols &&
+ (type == MELF_STT_FILE || type == MELF_STT_SECTION) )
+ continue;
+ if( options->extern_only && bind == MELF_STB_LOCAL )
+ continue;
+ if( options->undefined_only && shndx != MELF_SHN_UNDEF )
+ continue;
+ if( symbol_code( file, bind, shndx, &code ) )
+ {
+ snprintf( err, errsz, "symbol '%s' has invalid section index", name );
+ return( -1 );
+ }
+
+ if( symbols_append( symbols, name, value, ssize, shndx, bind, type,
+ code, (*order)++, err, errsz ) )
+ return( -1 );
+ }
+ return( 0 );
+
+} /* End of collect_table() */
+
+
+static int
+collect_symbols( const unsigned char *data, size_t size,
+ const mcpu_nm_options *options, mcpu_nm_symbols *symbols,
+ char *err, size_t errsz )
+{
+ mcpu_nm_file file;
+ size_t i;
+ size_t order = 0;
+ int want;
+
+ memset( symbols, 0, sizeof( *symbols ) );
+ if( nm_file_open_data( data, size, &file, err, errsz ) )
+ return( -1 );
+
+ want = options->dynamic ? MELF_SHT_DYNSYM : MELF_SHT_SYMTAB;
+ for( i = 0; i < file.shnum; ++i )
+ {
+ if( file.shdr[i].type != (unsigned int)want )
+ continue;
+ if( collect_table( &file, i, options, symbols, &order, err, errsz ) )
+ {
+ nm_file_free( &file );
+ nm_symbols_free( symbols );
+ return( -1 );
+ }
+ }
+
+ nm_file_free( &file );
+ return( 0 );
+
+} /* End of collect_symbols() */
+
+
+static int
+compare_symbols( const void *ap, const void *bp )
+{
+ const mcpu_nm_symbol *a = (const mcpu_nm_symbol *)ap;
+ const mcpu_nm_symbol *b = (const mcpu_nm_symbol *)bp;
+ int c;
+
+ if( sort_options->numeric_sort )
+ {
+ if( a->value < b->value )
+ c = -1;
+ else if( a->value > b->value )
+ c = 1;
+ else
+ {
+ c = strcmp( a->name, b->name );
+ if( !c )
+ c = a->order < b->order ? -1 : a->order > b->order ? 1 : 0;
+ }
+ }
+ else
+ {
+ c = strcmp( a->name, b->name );
+ if( !c )
+ c = a->order < b->order ? -1 : a->order > b->order ? 1 : 0;
+ }
+
+ return( sort_options->reverse_sort ? -c : c );
+
+} /* End of compare_symbols() */
+
+
+static void
+print_one_symbol( const mcpu_nm_symbol *symbol, const char *prefix,
+ const mcpu_nm_options *options )
+{
+ if( options->print_file_name && prefix )
+ printf( "%s: ", prefix );
+
+ if( symbol->shndx == MELF_SHN_UNDEF )
+ printf( "%16s %c %s\n", "", symbol->code, symbol->name );
+ else
+ printf( "%016" PRIx64 " %c %s\n",
+ (uint64_t)symbol->value, symbol->code, symbol->name );
+
+} /* End of print_one_symbol() */
+
+
+static int
+print_symbols_data( const unsigned char *data, size_t size,
+ const char *header, const char *prefix,
+ const mcpu_nm_options *options, int show_header,
+ char *err, size_t errsz )
+{
+ mcpu_nm_symbols symbols;
+ size_t i;
+
+ if( collect_symbols( data, size, options, &symbols, err, errsz ) )
+ return( -1 );
+
+ if( !options->no_sort && symbols.count > 1 )
+ {
+ sort_options = options;
+ qsort( symbols.symbol, symbols.count, sizeof( *symbols.symbol ),
+ compare_symbols );
+ }
+
+ if( show_header && !options->print_file_name )
+ printf( "%s:\n", header );
+ for( i = 0; i < symbols.count; ++i )
+ print_one_symbol( &symbols.symbol[i], prefix, options );
+
+ nm_symbols_free( &symbols );
+ return( 0 );
+
+} /* End of print_symbols_data() */
+
+
+static int
+is_archive_file( const unsigned char *data, size_t size )
+{
+ static const unsigned char magic[] = "!<arch>\n";
+
+ return( size >= sizeof( magic ) - 1 &&
+ !memcmp( data, magic, sizeof( magic ) - 1 ) );
+
+} /* End of is_archive_file() */
+
+
+static int
+process_archive( const char *path, const mcpu_nm_options *options,
+ char *err, size_t errsz )
+{
+ mcpu_archive archive;
+ size_t i;
+
+ if( mcpu_archive_read( path, &archive, err, errsz ) )
+ return( -1 );
+
+ for( i = 0; i < archive.member_count; ++i )
+ {
+ const mcpu_archive_member *member = &archive.members[i];
+ char label[1024];
+
+ if( snprintf( label, sizeof( label ), "%s:%s", path, member->name ) >=
+ (int)sizeof( label ) )
+ {
+ snprintf( err, errsz, "archive/member name is too long" );
+ mcpu_archive_free( &archive );
+ return( -1 );
+ }
+
+ if( print_symbols_data( member->data, member->size, label, label,
+ options, 1, err, errsz ) )
+ {
+ mcpu_archive_free( &archive );
+ return( -1 );
+ }
+ if( !options->print_file_name && i + 1 < archive.member_count )
+ printf( "\n" );
+ }
+
+ mcpu_archive_free( &archive );
+ return( 0 );
+
+} /* End of process_archive() */
+
+
+static int
+process_path( const char *path, const mcpu_nm_options *options,
+ int show_header, char *err, size_t errsz )
+{
+ unsigned char *data = NULL;
+ size_t size = 0;
+ int rc;
+
+ if( cpu_melf_readall( path, &data, &size, err, errsz ) )
+ return( -1 );
+
+ if( is_archive_file( data, size ) )
+ {
+ free( data );
+ return( process_archive( path, options, err, errsz ) );
+ }
+
+ rc = print_symbols_data( data, size, path, path, options,
+ show_header, err, errsz );
+ free( data );
+ return( rc );
+
+} /* End of process_path() */
+
+
+static int
+parse_short_options( const char *arg, mcpu_nm_options *options )
+{
+ const char *p = arg + 1;
+
+ while( *p )
+ {
+ switch( *p )
+ {
+ case 'a': options->all_symbols = 1; break;
+ case 'g': options->extern_only = 1; break;
+ case 'u': options->undefined_only = 1; break;
+ case 'n': options->numeric_sort = 1; options->no_sort = 0; break;
+ case 'r': options->reverse_sort = 1; break;
+ case 'p': options->no_sort = 1; options->numeric_sort = 0; break;
+ case 'D': options->dynamic = 1; break;
+ case 'A': options->print_file_name = 1; break;
+ default: return( -1 );
+ }
+ ++p;
+ }
+ return( 0 );
+
+} /* End of parse_short_options() */
+
+
+int
+main( int argc, char **argv )
+{
+ mcpu_nm_options options;
+ char **files = NULL;
+ size_t file_count = 0;
+ size_t file_capacity = 0;
+ int i;
+ int rc = 0;
+
+ memset( &options, 0, sizeof( options ) );
+
+ for( i = 1; i < argc; ++i )
+ {
+ const char *arg = argv[i];
+
+ if( !strcmp( arg, "--help" ) )
+ {
+ usage( stdout, argv[0] );
+ free( files );
+ return( 0 );
+ }
+ else if( !strcmp( arg, "--version" ) ||
+ !strcmp( arg, "--dump-version" ) )
+ {
+ mcpu_version_action version_action;
+ int rc;
+
+ mcpu_version_parse_option( arg, &version_action );
+ rc = mcpu_version_print( argv[0], version_action );
+ free( files );
+ return( rc == 0 ? 0 : 1 );
+ }
+ else if( !strcmp( arg, "--debug-syms" ) )
+ options.all_symbols = 1;
+ else if( !strcmp( arg, "--extern-only" ) )
+ options.extern_only = 1;
+ else if( !strcmp( arg, "--undefined-only" ) )
+ options.undefined_only = 1;
+ else if( !strcmp( arg, "--numeric-sort" ) )
+ {
+ options.numeric_sort = 1;
+ options.no_sort = 0;
+ }
+ else if( !strcmp( arg, "--reverse-sort" ) )
+ options.reverse_sort = 1;
+ else if( !strcmp( arg, "--no-sort" ) )
+ {
+ options.no_sort = 1;
+ options.numeric_sort = 0;
+ }
+ else if( !strcmp( arg, "--dynamic" ) )
+ options.dynamic = 1;
+ else if( !strcmp( arg, "--print-file-name" ) )
+ options.print_file_name = 1;
+ else if( arg[0] == '-' && arg[1] != '\0' )
+ {
+ if( parse_short_options( arg, &options ) )
+ {
+ fprintf( stderr, "mcpu-nm: unknown option '%s'\n", arg );
+ usage( stderr, argv[0] );
+ free( files );
+ return( 1 );
+ }
+ }
+ else
+ {
+ if( file_count == file_capacity )
+ {
+ size_t next = file_capacity ? file_capacity * 2U : 4U;
+ char **q = (char **)realloc( files, next * sizeof( *q ) );
+
+ if( !q )
+ {
+ fprintf( stderr, "mcpu-nm: out of memory\n" );
+ free( files );
+ return( 1 );
+ }
+ files = q;
+ file_capacity = next;
+ }
+ files[file_count++] = argv[i];
+ }
+ }
+
+ if( !file_count )
+ {
+ usage( stderr, argv[0] );
+ free( files );
+ return( 1 );
+ }
+
+ for( i = 0; i < (int)file_count; ++i )
+ {
+ char err[512];
+
+ if( i && !options.print_file_name )
+ printf( "\n" );
+ if( process_path( files[i], &options,
+ file_count > 1, err, sizeof( err ) ) )
+ {
+ fprintf( stderr, "mcpu-nm: %s: %s\n", files[i], err );
+ rc = 1;
+ }
+ }
+
+ free( files );
+ return( rc );
+
+} /* End of main() */