#!/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 -- Select a required extra object files. =head1 SYNOPSIS B I