| #!/usr/bin/perl |
| |
| # |
| #//===----------------------------------------------------------------------===// |
| #// |
| #// The LLVM Compiler Infrastructure |
| #// |
| #// This file is dual licensed under the MIT and the University of Illinois Open |
| #// Source Licenses. See LICENSE.txt for details. |
| #// |
| #//===----------------------------------------------------------------------===// |
| # |
| |
| use strict; |
| use warnings; |
| |
| use File::Glob ":glob"; |
| use Data::Dumper; |
| |
| use FindBin; |
| use lib "$FindBin::Bin/lib"; |
| |
| use tools; |
| use Platform ":vars"; |
| |
| our $VERSION = "0.004"; |
| |
| # -------------------------------------------------------------------------------------------------- |
| # Set of objects: # Ref to hash, keys are names of objects. |
| # object0: # Ref to hash of two elements with keys "defined" and "undefined". |
| # defined: # Ref to array of symbols defined in object0. |
| # - symbol0 # Symbol name. |
| # - ... |
| # undefined: # Ref to array of symbols referenced in object0. |
| # - symbol0 |
| # - ... |
| # object1: |
| # ... |
| # ... |
| # -------------------------------------------------------------------------------------------------- |
| |
| # -------------------------------------------------------------------------------------------------- |
| # Set of symbols: # Ref to hash, keys are names of symbols. |
| # symbol0: # Ref to array of object names where the symbol0 is defined. |
| # - object0 # Object file name. |
| # - ... |
| # symbol1: |
| # ... |
| # ... |
| # -------------------------------------------------------------------------------------------------- |
| |
| sub dump_objects($$$) { |
| |
| my ( $title, $objects, $dump ) = @_; |
| |
| if ( $dump > 0 ) { |
| STDERR->print( $title, "\n" ); |
| foreach my $object ( sort( keys( %$objects ) ) ) { |
| STDERR->print( " $object\n" ); |
| if ( $dump > 1 ) { |
| STDERR->print( " Defined symbols:\n" ); |
| foreach my $symbol ( sort( @{ $objects->{ $object }->{ defined } } ) ) { |
| STDERR->print( " $symbol\n" ); |
| }; # foreach $symbol |
| STDERR->print( " Undefined symbols:\n" ); |
| foreach my $symbol ( sort( @{ $objects->{ $object }->{ undefined } } ) ) { |
| STDERR->print( " $symbol\n" ); |
| }; # foreach $symbol |
| }; # if |
| }; # foreach $object |
| }; # if |
| |
| }; # sub dump_objects |
| |
| sub dump_symbols($$$) { |
| |
| my ( $title, $symbols, $dump ) = @_; |
| |
| if ( $dump > 0 ) { |
| STDERR->print( $title, "\n" ); |
| foreach my $symbol ( sort( keys( %$symbols ) ) ) { |
| STDERR->print( " $symbol\n" ); |
| if ( $dump > 1 ) { |
| foreach my $object ( sort( @{ $symbols->{ $symbol } } ) ) { |
| STDERR->print( " $object\n" ); |
| }; # foreach |
| }; # if |
| }; # foreach $object |
| }; # if |
| |
| }; # sub dump_symbols |
| |
| # -------------------------------------------------------------------------------------------------- |
| # Name: |
| # load_symbols -- Fulfill objects data structure with symbol names. |
| # Synopsis: |
| # load_symbols( $objects ); |
| # Arguments: |
| # $objects (in/out) -- Set of objects. On enter, it is expected that top-level hash has filled |
| # with object names only. On exit, it is completely fulfilled with lists of symbols |
| # defined or referenced in each object file. |
| # Returns: |
| # Nothing. |
| # Example: |
| # my $objects = { foo.o => {} }; |
| # load_symbols( $objects ); |
| # # Now $objects is { goo.o => { defined => [ ... ], undefined => [ ... ] } }. |
| # |
| # -------------------------------------------------------------------------------------------------- |
| # This version of load_symbols parses output of nm command and works on Linux* OS and OS X*. |
| # |
| sub _load_symbols_nm($) { |
| |
| my $objects = shift( @_ ); |
| # It is a ref to hash. Keys are object names, values are empty hashes (for now). |
| my @bulk; |
| |
| if ( %$objects ) { |
| # Do not run nm if a set of objects is empty -- nm will try to open a.out in this case. |
| execute( |
| [ |
| "nm", |
| "-g", # Display only external (global) symbols. |
| "-o", # Precede each symbol by the name of the input file. |
| keys( %$objects ) |
| # Running nm once (rather than once per object) improves performance |
| # drastically. |
| ], |
| -stdout => \@bulk |
| ); |
| }; # if |
| |
| foreach my $line ( @bulk ) { |
| if ( $line !~ m{^(.*):(?: ?[0-9a-f]*| *) ([A-Za-z]) (.*)$} ) { |
| die "Cannot parse nm output, line:\n $line\n"; |
| }; # if |
| my ( $file, $tag, $symbol ) = ( $1, $2, $3 ); |
| if ( not exists( $objects->{ $file } ) ) { |
| die "nm reported unknown object file:\n $line\n"; |
| }; # if |
| # AC: exclude some libc symbols from renaming, otherwise we have problems |
| # in tests for gfortran + static libiomp on Lin_32. |
| # These symbols came from libtbbmalloc.a |
| if ( $target_os eq "lin" ) { |
| if ( $symbol =~ m{__i686} ) { |
| next; |
| } |
| } |
| # AC: added "w" to tags of undefined symbols, e.g. malloc is weak in libirc v12.1. |
| if ( $tag eq "U" or $tag eq "w" ) { # Symbol not defined. |
| push( @{ $objects->{ $file }->{ undefined } }, $symbol ); |
| } else { # Symbol defined. |
| push( @{ $objects->{ $file }->{ defined } }, $symbol ); |
| }; # if |
| }; # foreach |
| |
| return undef; |
| |
| }; # sub _load_symbols_nm |
| |
| # -------------------------------------------------------------------------------------------------- |
| # This version of load_symbols parses output of link command and works on Windows* OS. |
| # |
| sub _load_symbols_link($) { |
| |
| my $objects = shift( @_ ); |
| # It is a ref to hash. Keys are object names, values are empty hashes (for now). |
| my @bulk; |
| |
| if ( %$objects ) { |
| # Do not run nm if a set of objects is empty -- nm will try to open a.out in this case. |
| execute( |
| [ |
| "link", |
| "/dump", |
| "/symbols", |
| keys( %$objects ) |
| # Running nm once (rather than once per object) improves performance |
| # drastically. |
| ], |
| -stdout => \@bulk |
| ); |
| }; # if |
| |
| my $num_re = qr{[0-9A-F]{3,4}}; |
| my $addr_re = qr{[0-9A-F]{8}}; |
| my $tag_re = qr{DEBUG|ABS|UNDEF|SECT[0-9A-F]+}; |
| my $class_re = qr{Static|External|Filename|Label|BeginFunction|EndFunction|WeakExternal|\.bf or\.ef}; |
| |
| my $file; |
| foreach my $line ( @bulk ) { |
| if ( $line =~ m{\ADump of file (.*?)\n\z} ) { |
| $file = $1; |
| if ( not exists( $objects->{ $file } ) ) { |
| die "link reported unknown object file:\n $line\n"; |
| }; # if |
| } elsif ( $line =~ m{\A$num_re } ) { |
| if ( not defined( $file ) ) { |
| die "link reported symbol of unknown object file:\n $line\n"; |
| }; # if |
| if ( $line !~ m{\A$num_re $addr_re ($tag_re)\s+notype(?: \(\))?\s+($class_re)\s+\| (.*?)\n\z} ) { |
| die "Cannot parse link output, line:\n $line\n"; |
| }; # if |
| my ( $tag, $class, $symbol ) = ( $1, $2, $3 ); |
| # link.exe /dump sometimes prints comments for symbols, e. g.: |
| # ".?0_memcopyA ([Entry] ?0_memcopyA)", or "??_C@_01A@r?$AA@ (`string')". |
| # Strip these comments. |
| $symbol =~ s{ \(.*\)\z}{}; |
| if ( $class eq "External" ) { |
| if ( $tag eq "UNDEF" ) { # Symbol not defined. |
| push( @{ $objects->{ $file }->{ undefined } }, $symbol ); |
| } else { # Symbol defined. |
| push( @{ $objects->{ $file }->{ defined } }, $symbol ); |
| }; # if |
| }; # if |
| } else { |
| # Ignore all other lines. |
| }; # if |
| }; # foreach |
| |
| return undef; |
| |
| }; # sub _load_symbols_link |
| |
| # -------------------------------------------------------------------------------------------------- |
| # Name: |
| # symbols -- Construct set of symbols with specified tag in the specified set of objects. |
| # Synopsis: |
| # my $symbols = defined_symbols( $objects, $tag ); |
| # Arguments: |
| # $objects (in) -- Set of objects. |
| # $tag (in) -- A tag, "defined" or "undefined". |
| # Returns: |
| # Set of symbols with the specified tag. |
| # |
| sub symbols($$) { |
| |
| my $objects = shift( @_ ); |
| my $tag = shift( @_ ); |
| |
| my $symbols = {}; |
| |
| foreach my $object ( keys( %$objects ) ) { |
| foreach my $symbol ( @{ $objects->{ $object }->{ $tag } } ) { |
| push( @{ $symbols->{ $symbol } }, $object ); |
| }; # foreach $symbol |
| }; # foreach $object |
| |
| return $symbols; |
| |
| }; # sub symbols |
| |
| sub defined_symbols($) { |
| |
| my $objects = shift( @_ ); |
| my $defined = symbols( $objects, "defined" ); |
| return $defined; |
| |
| }; # sub defined_symbols |
| |
| sub undefined_symbols($) { |
| |
| my $objects = shift( @_ ); |
| my $defined = symbols( $objects, "defined" ); |
| my $undefined = symbols( $objects, "undefined" ); |
| foreach my $symbol ( keys( %$defined ) ) { |
| delete( $undefined->{ $symbol } ); |
| }; # foreach symbol |
| return $undefined; |
| |
| }; # sub undefined_symbols |
| |
| # -------------------------------------------------------------------------------------------------- |
| # Name: |
| # _required_extra_objects -- Select a subset of extra objects required to resolve undefined |
| # symbols in a set of objects. It is a helper sub for required_extra_objects(). |
| # Synopsis: |
| # my $required = _required_extra_objects( $objects, $extra, $symbols ); |
| # Arguments: |
| # $objects (in) -- A set of objects to be searched for undefined symbols. |
| # $extra (in) -- A set of extra objects to be searched for defined symbols to resolve undefined |
| # symbols in objects. |
| # $symbols (in/out) -- Set of symbols defined in the set of external objects. At the first call |
| # it should consist of all the symbols defined in all the extra objects. Symbols defined in |
| # the selected subset of extra objects are removed from set of defined symbols, because |
| # they are out of interest for subsequent calls. |
| # Returns: |
| # A subset of extra objects required by the specified set of objects. |
| # |
| sub _required_extra_objects($$$$) { |
| |
| my $objects = shift( @_ ); |
| my $extra = shift( @_ ); |
| my $symbols = shift( @_ ); |
| my $dump = shift( @_ ); |
| |
| my $required = {}; |
| |
| if ( $dump > 0 ) { |
| STDERR->print( "Required extra objects:\n" ); |
| }; # if |
| foreach my $object ( keys( %$objects ) ) { |
| foreach my $symbol ( @{ $objects->{ $object }->{ undefined } } ) { |
| if ( exists( $symbols->{ $symbol } ) ) { |
| # Add all objects where the symbol is defined to the required objects. |
| foreach my $req_obj ( @{ $symbols->{ $symbol } } ) { |
| if ( $dump > 0 ) { |
| STDERR->print( " $req_obj\n" ); |
| if ( $dump > 1 ) { |
| STDERR->print( " by $object\n" ); |
| STDERR->print( " due to $symbol\n" ); |
| }; # if |
| }; # if |
| $required->{ $req_obj } = $extra->{ $req_obj }; |
| }; # foreach $req_obj |
| # Delete the symbol from list of defined symbols. |
| delete( $symbols->{ $symbol } ); |
| }; # if |
| }; # foreach $symbol |
| }; # foreach $object |
| |
| return $required; |
| |
| }; # sub _required_extra_objects |
| |
| |
| # -------------------------------------------------------------------------------------------------- |
| # Name: |
| # required_extra_objects -- Select a subset of extra objects required to resolve undefined |
| # symbols in a set of base objects and selected extra objects. |
| # Synopsis: |
| # my $required = required_extra_objects( $base, $extra ); |
| # Arguments: |
| # $base (in/out) -- A set of base objects to be searched for undefined symbols. On enter, it is |
| # expected that top-level hash has filled with object names only. On exit, it is completely |
| # fulfilled with lists of symbols defined and/or referenced in each object file. |
| # $extra (in/out) -- A set of extra objects to be searched for defined symbols required to |
| # resolve undefined symbols in a set of base objects. Usage is similar to base objects. |
| # Returns: |
| # A subset of extra object files. |
| # |
| sub required_extra_objects($$$) { |
| |
| my $base = shift( @_ ); |
| my $extra = shift( @_ ); |
| my $dump = shift( @_ ); |
| |
| # Load symbols for each object. |
| load_symbols( $base ); |
| load_symbols( $extra ); |
| if ( $dump ) { |
| dump_objects( "Base objects:", $base, $dump ); |
| dump_objects( "Extra objects:", $extra, $dump ); |
| }; # if |
| |
| # Collect symbols defined in extra objects. |
| my $symbols = defined_symbols( $extra ); |
| |
| my $required = {}; |
| # Select extra objects required by base objects. |
| my $delta = _required_extra_objects( $base, $extra, $symbols, $dump ); |
| while ( %$delta ) { |
| %$required = ( %$required, %$delta ); |
| # Probably, just selected objects require some more objects. |
| $delta = _required_extra_objects( $delta, $extra, $symbols, $dump ); |
| }; # while |
| |
| if ( $dump ) { |
| my $base_undefined = undefined_symbols( $base ); |
| my $req_undefined = undefined_symbols( $required ); |
| dump_symbols( "Symbols undefined in base objects:", $base_undefined, $dump ); |
| dump_symbols( "Symbols undefined in required objects:", $req_undefined, $dump ); |
| }; # if |
| |
| return $required; |
| |
| }; # sub required_extra_objects |
| |
| |
| # -------------------------------------------------------------------------------------------------- |
| # Name: |
| # copy_objects -- Copy (and optionally edit) object files to specified directory. |
| # Synopsis: |
| # copy_objects( $objects, $target, $prefix, @symbols ); |
| # Arguments: |
| # $objects (in) -- A set of object files. |
| # $target (in) -- A name of target directory. Directory must exist. |
| # $prefix (in) -- A prefix to add to all the symbols listed in @symbols. If prefix is undefined, |
| # object files are just copied. |
| # @symbols (in) -- List of symbol names to be renamed. |
| # Returns: |
| # None. |
| # |
| sub copy_objects($$;$\@) { |
| |
| my $objects = shift( @_ ); |
| my $target = shift( @_ ); |
| my $prefix = shift( @_ ); |
| my $symbols = shift( @_ ); |
| my @redefine; |
| my @redefine_; |
| my $syms_file = "__kmp_sym_pairs.log"; |
| |
| if ( not -e $target ) { |
| die "\"$target\" directory does not exist\n"; |
| }; # if |
| if ( not -d $target ) { |
| die "\"$target\" is not a directory\n"; |
| }; # if |
| |
| if ( defined( $prefix ) and @$symbols ) { |
| my %a = map ( ( "$_ $prefix$_" => 1 ), @$symbols ); |
| @redefine_ = keys( %a ); |
| }; # if |
| foreach my $line ( @redefine_ ) { |
| $line =~ s{$prefix(\W+)}{$1$prefix}; |
| push( @redefine, $line ); |
| } |
| write_file( $syms_file, \@redefine ); |
| foreach my $src ( sort( keys( %$objects ) ) ) { |
| my $dst = cat_file( $target, get_file( $src ) ); |
| if ( @redefine ) { |
| execute( [ "objcopy", "--redefine-syms", $syms_file, $src, $dst ] ); |
| } else { |
| copy_file( $src, $dst, -overwrite => 1 ); |
| }; # if |
| }; # foreach $object |
| |
| }; # sub copy_objects |
| |
| |
| # -------------------------------------------------------------------------------------------------- |
| # Main. |
| # -------------------------------------------------------------------------------------------------- |
| |
| my $base = {}; |
| my $extra = {}; |
| my $switcher = $base; |
| my $dump = 0; |
| my $print_base; |
| my $print_extra; |
| my $copy_base; |
| my $copy_extra; |
| my $prefix; |
| |
| # Parse command line. |
| |
| Getopt::Long::Configure( "permute" ); |
| get_options( |
| Platform::target_options(), |
| "base" => sub { $switcher = $base; }, |
| "extra" => sub { $switcher = $extra; }, |
| "print-base" => \$print_base, |
| "print-extra" => \$print_extra, |
| "print-all" => sub { $print_base = 1; $print_extra = 1; }, |
| "copy-base=s" => \$copy_base, |
| "copy-extra=s" => \$copy_extra, |
| "copy-all=s" => sub { $copy_base = $_[ 1 ]; $copy_extra = $_[ 1 ]; }, |
| "dump" => sub { ++ $dump; }, |
| "prefix=s" => \$prefix, |
| "<>" => |
| sub { |
| my $arg = $_[ 0 ]; |
| my @args; |
| if ( $^O eq "MSWin32" ) { |
| # Windows* OS does not expand wildcards. Do it... |
| @args = bsd_glob( $arg ); |
| } else { |
| @args = ( $arg ); |
| }; # if |
| foreach my $object ( @args ) { |
| if ( exists( $base->{ $object } ) or exists( $extra->{ $object } ) ) { |
| die "Object \"$object\" has already been specified.\n"; |
| }; # if |
| $switcher->{ $object } = { defined => [], undefined => [] }; |
| }; # foreach |
| }, |
| ); |
| if ( not %$base ) { |
| cmdline_error( "No base objects specified" ); |
| }; # if |
| |
| if ( $target_os eq "win" ) { |
| *load_symbols = \&_load_symbols_link; |
| } elsif ( $target_os eq "lin" or $target_os eq "lrb" ) { |
| *load_symbols = \&_load_symbols_nm; |
| } elsif ( $target_os eq "mac" ) { |
| *load_symbols = \&_load_symbols_nm; |
| } else { |
| runtime_error( "OS \"$target_os\" not supported" ); |
| }; # if |
| |
| # Do the work. |
| |
| my $required = required_extra_objects( $base, $extra, $dump ); |
| if ( $print_base ) { |
| print( map( "$_\n", sort( keys( %$base ) ) ) ); |
| }; # if |
| if ( $print_extra ) { |
| print( map( "$_\n", sort( keys( %$required ) ) ) ); |
| }; # if |
| my @symbols; |
| if ( defined( $prefix ) ) { |
| foreach my $object ( sort( keys( %$required ) ) ) { |
| push( @symbols, @{ $required->{ $object }->{ defined } } ); |
| }; # foreach $objects |
| }; # if |
| if ( $copy_base ) { |
| copy_objects( $base, $copy_base, $prefix, @symbols ); |
| }; # if |
| if ( $copy_extra ) { |
| copy_objects( $required, $copy_extra, $prefix, @symbols ); |
| }; # if |
| |
| exit( 0 ); |
| |
| __END__ |
| |
| =pod |
| |
| =head1 NAME |
| |
| B<required-objects.pl> -- Select a required extra object files. |
| |
| =head1 SYNOPSIS |
| |
| B<required-objects.pl> I<option>... [--base] I<file>... --extra I<file>... |
| |
| =head1 DESCRIPTION |
| |
| B<required-objects.pl> works with two sets of object files -- a set of I<base> objects |
| and a set of I<extra> objects, and selects those extra objects which are required for resolving |
| undefined symbols in base objects I<and> selected extra objects. |
| |
| Selected object files may be copied to specified location or their names may be printed to stdout, |
| a name per line. Additionally, symbols defined in selected extra objects may be renamed. |
| |
| Depending on OS, different external tools may be used. For example, B<required-objects.pl> uses |
| F<link.exe> on "win" and F<nm> on "lin" and "mac" OSes. Normally OS is autodetected, but |
| detection can be overrided with B<--os> option. It may be helpful in cross-build environments. |
| |
| =head1 OPTIONS |
| |
| =over |
| |
| =item B<--base> |
| |
| The list of base objects follows this option. |
| |
| =item B<--extra> |
| |
| List of extra objects follows this option. |
| |
| =item B<--print-all> |
| |
| Print list of base objects and list of required extra objects. |
| |
| =item B<--print-base> |
| |
| Print list of base objects. |
| |
| =item B<--print-extra> |
| |
| Print list of selected extra objects. |
| |
| =item B<--copy-all=>I<dir> |
| |
| Copy all base and selected extra objects to specified directory. The directory must exist. Existing |
| files are overwritten. |
| |
| =item B<--copy-base=>I<dir> |
| |
| Copy all base objects to specified directory. |
| |
| =item B<--copy-extra=>I<dir> |
| |
| Copy selected extra objects to specified directory. |
| |
| =item B<--prefix=>I<str> |
| |
| If prefix is specified, copied object files are edited -- symbols defined in selected extra |
| object files are renamed (in all the copied object files) by adding this prefix. |
| |
| F<objcopy> program should be available for performing this operation. |
| |
| =item B<--os=>I<str> |
| |
| Specify OS name. By default OS is autodetected. |
| |
| Depending on OS, B<required-objects.pl> uses different external tools. |
| |
| =item B<--help> |
| |
| Print short help message and exit. |
| |
| =item B<--doc> |
| |
| =item B<--manual> |
| |
| Print full documentation and exit. |
| |
| =item B<--version> |
| |
| Print version and exit. |
| |
| =back |
| |
| =head1 ARGUMENTS |
| |
| =over |
| |
| =item I<file> |
| |
| A name of object file. |
| |
| =back |
| |
| =head1 EXAMPLES |
| |
| $ required-objects.pl --base obj/*.o --extra ../lib/obj/*.o --print-extra > required.lst |
| $ ar cr libx.a obj/*.o $(cat required.lst) |
| |
| $ required-objects.pl --base internal/*.o --extra external/*.o --prefix=__xyz_ --copy-all=obj |
| $ ar cr xyz.a obj/*.o |
| |
| =cut |
| |
| # end of file # |
| |