1#!/usr/bin/env perl
2
3#
4#//===----------------------------------------------------------------------===//
5#//
6#// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
7#// See https://llvm.org/LICENSE.txt for license information.
8#// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
9#//
10#//===----------------------------------------------------------------------===//
11#
12
13use strict;
14use warnings;
15
16use FindBin;
17use lib "$FindBin::Bin/lib";
18
19use tools;
20
21our $VERSION = "0.005";
22my $target_os;
23my $target_arch;
24
25# --------------------------------------------------------------------------------------------------
26# Ouput parse error.
27#     $tool -- Name of tool.
28#     @bulk -- Output of the tool.
29#     $n    -- Number of line caused parse error.
30sub parse_error($\@$) {
31    my ( $tool, $bulk, $n ) = @_;
32    my @bulk;
33    for ( my $i = 0; $i < @$bulk; ++ $i ) {
34        push( @bulk, ( $i == $n ? ">>> " : "    " ) . $bulk->[ $i ] );
35    }; # for $i
36    runtime_error( "Fail to parse $tool output:", @bulk, "(eof)" );
37}; # sub parse_error
38
39
40# --------------------------------------------------------------------------------------------------
41# Linux* OS version of get_deps() parses output of ldd:
42#
43# $ ldd libname.so
44#   libc.so.6 => /lib64/libc.so.6 (0x00002b60fedd8000)
45#   libdl.so.2 => /lib64/libdl.so.2 (0x00002b60ff12b000)
46#   libpthread.so.0 => /lib64/libpthread.so.0 (0x00002b60ff32f000)
47#   /lib64/ld-linux-x86-64.so.2 (0x0000003879400000)
48#
49# Note: ldd printd all the dependencies, direct and indirect. (For example, if specified library
50# requires libdl.so, and libdl.so requires /lib/ld-linux.so, ldd prints both libdl.so and
51# /lib/ld-linux.so). If you do not want indirect dependencies, look at readelf tool.
52#
53sub get_deps_ldd($) {
54
55    my $lib = shift ( @_ );
56    my $tool = "ldd";
57    my @bulk;
58    my @deps;
59
60    execute( [ $tool, $lib ], -stdout => \@bulk );
61    debug( @bulk, "(eof)" );
62
63    foreach my $i ( 0 .. @bulk - 1 ) {
64        my $line = $bulk[ $i ];
65        if ( $line !~ m{^\s*(?:([_a-z0-9.+-/]*)\s+=>\s+)?([_a-z0-9.+-/]*)\s+\(0x[0-9a-z]*\)$}i ) {
66            parse_error( $tool, @bulk, $i );
67        }; # if
68        my $dep = ( defined( $1 ) ? $1 : $2 );
69        push( @deps, $dep );
70    }; # foreach $i
71
72    return @deps;
73
74}; # sub get_deps_ldd
75
76
77# --------------------------------------------------------------------------------------------------
78# Another Linux* OS version of get_deps() parses output of readelf:
79#
80# $ readelf -d exports/lin_32e/lib/libomp.so
81#
82# Dynamic segment at offset 0x87008 contains 24 entries:
83#   Tag        Type                         Name/Value
84#  0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
85#  0x0000000000000001 (NEEDED)             Shared library: [libdl.so.2]
86#  0x0000000000000001 (NEEDED)             Shared library: [libpthread.so.0]
87#  0x000000000000000e (SONAME)             Library soname: [libomp.so]
88#  0x000000000000000d (FINI)               0x51caa
89#  0x0000000000000004 (HASH)               0x158
90#  0x0000000000000005 (STRTAB)             0x9350
91#  ...
92#
93# Note: In contrast to ldd, readlef shows only direct dependencies.
94#
95sub get_deps_readelf($) {
96
97    my $file = shift ( @_ );
98    my $tool;
99    my @bulk;
100    my @deps;
101
102    if($target_arch eq "mic") {
103        $tool = "x86_64-k1om-linux-readelf";
104    } else {
105        $tool = "readelf";
106    }
107
108    # Force the readelf call to be in English. For example, when readelf
109    # is called on a french localization, it will find "Librairie partagees"
110    # instead of shared library
111    $ENV{ LANG } = "C";
112
113    execute( [ $tool, "-d", $file ], -stdout => \@bulk );
114    debug( @bulk, "(eof)" );
115
116    my $i = 0;
117    # Parse header.
118    ( $i < @bulk and $bulk[ $i ] =~ m{^\s*$} )
119        or parse_error( $tool, @bulk, $i );
120    ++ $i;
121    if ( $i == @bulk - 1 and $bulk[ $i ] =~ m{^There is no dynamic section in this file\.\s*$} ) {
122        # This is not dynamic executable => no dependencies.
123        return @deps;
124    }; # if
125    ( $i < @bulk and $bulk[ $i ] =~ m{^Dynamic (?:segment|section) at offset 0x[0-9a-f]+ contains \d+ entries:\s*$} )
126        or parse_error( $tool, @bulk, $i );
127    ++ $i;
128    ( $i < @bulk and $bulk[ $i ] =~ m{^\s*Tag\s+Type\s+Name/Value\s*$} )
129        or parse_error( $tool, @bulk, $i );
130    ++ $i;
131    # Parse body.
132    while ( $i < @bulk ) {
133        my $line = $bulk[ $i ];
134        if ( $line !~ m{^\s*0x[0-9a-f]+\s+\(?([_A-Z0-9]+)\)?\s+(.*)\s*$}i ) {
135            parse_error( $tool, @bulk, $i );
136        }; # if
137        my ( $type, $value ) = ( $1, $2 );
138        if ( $type eq "NEEDED" ) {
139            if ( $value !~ m{\AShared library: \[(.*)\]\z} ) {
140                parse_error( $tool, @bulk, $i );
141            }; # if
142            my $dep = $1;
143            push( @deps, $dep );
144        }; # if
145        ++ $i;
146    }; # foreach $i
147
148    return @deps;
149
150}; # sub get_deps_readelf
151
152
153# --------------------------------------------------------------------------------------------------
154# OS X* version of get_deps() parses output of otool:
155#
156# $ otool -L libname.dylib
157# exports/mac_32/lib.thin/libomp.dylib:
158#        libomp.dylib (compatibility version 5.0.0, current version 5.0.0)
159#        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 88.1.3)
160#
161sub get_deps_otool($) {
162
163    my $file = shift ( @_ );
164    my $name = get_file( $file );
165    my $tool = "otool";
166    my @bulk;
167    my @deps;
168
169    if ( $target_arch eq "32e" ) {
170        # On older (Tiger) systems otool does not recognize 64-bit binaries, so try to locate
171        # otool64.
172        my $path = which( "otool64" );
173        if ( defined ( $path ) ) {
174            $tool = "otool64";
175        }; # if
176    }; # if
177
178    execute( [ $tool, "-L", $file ], -stdout => \@bulk );
179    debug( @bulk, "(eof)" );
180
181    my $i = 0;
182    # Parse the first one or two lines separately.
183    ( $i < @bulk and $bulk[ $i ] =~ m{^\Q$file\E:$} )
184        or parse_error( $tool, @bulk, $i );
185    ++ $i;
186    if ( $name =~ m{\.dylib\z} ) {
187        # Added "@rpath/" enables dynamic load of the library designated at link time.
188        $name = '@rpath/' . $name;
189        # In case of dynamic library otool print the library itself as a dependent library.
190        ( $i < @bulk and $bulk[ $i ] =~ m{^\s+\Q$name\E\s+\(compatibility version.*\)$} )
191            or parse_error( $tool, @bulk, $i );
192        ++ $i;
193    }; # if
194
195    # Then parse the rest.
196    while ( $i < @bulk ) {
197        my $line = $bulk[ $i ];
198        if ( $line !~ m/^\s*(.*)\s+\(compatibility version\s.*\)$/ ) {
199            parse_error( $tool, @bulk, $i );
200        }; # if
201        my ( $dep ) = ( $1 );
202        push( @deps, $dep );
203        ++ $i;
204    }; # while
205
206    return @deps;
207
208}; # sub get_deps_otool
209
210
211# --------------------------------------------------------------------------------------------------
212# Windows* OS version of get_deps() parses output of link:
213#
214# > link -dump -dependents libname.dll
215# Microsoft (R) COFF/PE Dumper Version 8.00.40310.39
216# Copyright (C) Microsoft Corporation.  All rights reserved.
217# Dump of file S:\Projects.OMP\users\omalyshe\omp\libomp\exports\win_64\lib\libompmd.dll
218# File Type: DLL
219#   Image has the following dependencies:
220#     KERNEL32.dll
221#   Summary
222#         C000 .data
223#         6000 .pdata
224#        18000 .rdata
225#        ...
226#
227# > link -dump -directives libname.lib
228# Microsoft (R) COFF/PE Dumper Version 8.00.40310.39
229# Copyright (C) Microsoft Corporation.  All rights reserved.
230# Dump of file S:\Projects.OMP\users\omalyshe\omp\libomp\exports\win_32e\lib\libimp5mt.lib
231# File Type: LIBRARY
232#   Linker Directives
233#   -----------------
234#   -defaultlib:"uuid.lib"
235#   -defaultlib:"uuid.lib"
236#   .....
237#   Summary
238#       3250 .bss
239#       3FBC .data
240#         34 .data1
241#       ....
242sub get_deps_link($) {
243
244    my ( $lib ) = @_;
245    my $tool = "link";
246    my @bulk;
247    my @deps;
248
249    my $ext = lc( get_ext( $lib ) );
250    if ( $ext !~ m{\A\.(?:lib|dll|exe)\z}i ) {
251        runtime_error( "Incorrect file is specified: `$lib'; only `lib', `dll' or `exe' file expected" );
252    }; # if
253
254    execute(
255        [ $tool, "/dump", ( $ext eq ".lib" ? "/directives" : "/dependents" ), $lib ],
256        -stdout => \@bulk
257    );
258
259    debug( @bulk, "(eof)" );
260
261    my $i = 0;
262    ( $i < @bulk and $bulk[ $i ] =~ m{^Microsoft \(R\) COFF\/PE Dumper Version.*$} ) or parse_error( $tool, @bulk, $i ); ++ $i;
263    ( $i < @bulk and $bulk[ $i ] =~ m{^Copyright \(C\) Microsoft Corporation\..*$} ) or parse_error( $tool, @bulk, $i ); ++ $i;
264    ( $i < @bulk and $bulk[ $i ] =~ m{^\s*$}                                       ) or parse_error( $tool, @bulk, $i ); ++ $i;
265    ( $i < @bulk and $bulk[ $i ] =~ m{^\s*$}                                       ) or parse_error( $tool, @bulk, $i ); ++ $i;
266    ( $i < @bulk and $bulk[ $i ] =~ m{^Dump of file\s\Q$lib\E$}                    ) or parse_error( $tool, @bulk, $i ); ++ $i;
267    ( $i < @bulk and $bulk[ $i ] =~ m{^\s*$}                                       ) or parse_error( $tool, @bulk, $i ); ++ $i;
268    ( $i < @bulk and $bulk[ $i ] =~ m{^File Type:\s(.*)$}                          ) or parse_error( $tool, @bulk, $i ); ++ $i;
269    ( $i < @bulk and $bulk[ $i ] =~ m{^\s*$}                                       ) or parse_error( $tool, @bulk, $i ); ++ $i;
270
271    if ( $ext eq ".lib" ) {
272
273        my %deps;
274        while ( $i < @bulk ) {
275            my $line = $bulk[ $i ];
276            if ( 0 ) {
277            } elsif ( $line =~ m{^\s*[-/]defaultlib\:(.*)\s*$}i ) {
278                my $dep = $1;
279                # Normalize library name:
280                $dep = lc( $1 );              # Convert to lower case.
281                $dep =~ s{\A"(.*)"\z}{$1};    # Drop surrounding quotes (if any).
282                $dep =~ s{\.lib\z}{};         # Drop .lib suffix (if any).
283                $deps{ $dep } = 1;
284            } elsif ( $line =~ m{^\s*Linker Directives\s*$} ) {
285            } elsif ( $line =~ m{^\s*-+\s*$} ) {
286            } elsif ( $line =~ m{^\s*/alternatename\:.*$} ) {
287            } elsif ( $line =~ m{^\s*$} ) {
288            } elsif ( $line =~ m{^\s*/FAILIFMISMATCH\:.*$} ) {
289                # This directive is produced only by _MSC_VER=1600
290            } elsif ( $line =~ m{^\s*Summary\s*$} ) {
291                last;
292            } else {
293                parse_error( $tool, @bulk, $i );
294            }; # if
295            ++ $i;
296        } # while
297        @deps = keys( %deps );
298
299    } else {
300
301        ( $i < @bulk and $bulk[ $i ] =~ m{\s*Image has the following dependencies\:$} )
302            or parse_error( $tool, @bulk, $i );
303        ++ $i;
304        while ( $i < @bulk ) {
305            my $line = $bulk[ $i ];
306            if ( 0 ) {
307            } elsif ( $line =~ m{^\s*$} ) {
308                # Ignore empty lines.
309            } elsif ( $line =~ m{^\s*(.*\.dll)$}i ) {
310                my $dep = lc( $1 );
311                push( @deps, $dep );
312            } elsif ( $line =~ m{^\s*Summary$} ) {
313                last;
314            } else {
315                parse_error( $tool, @bulk, $i );
316            }; # if
317            ++ $i;
318        }; # while
319
320    }; # if
321
322    return @deps;
323
324}; # sub get_deps_link
325
326
327# --------------------------------------------------------------------------------------------------
328# Main.
329# --------------------------------------------------------------------------------------------------
330
331# Parse command line.
332my $expected;
333my $bare;
334Getopt::Long::Configure( "permute" );
335get_options(
336    "os=s"       => \$target_os,
337    "arch=s"     => \$target_arch,
338    "bare"       => \$bare,
339    "expected=s" => \$expected,
340);
341my @expected;
342if ( defined( $expected ) ) {
343    if ( $expected ne "none" ) {
344        @expected = sort( split( ",", $expected ) );
345        if ( $target_os eq "win" ) {
346            @expected = map( lc( $_ ), @expected );
347        }; # if
348    }; # if
349}; # if
350if ( @ARGV < 1 ) {
351    cmdline_error( "Specify a library name to check for dependencies" );
352}; # if
353if ( @ARGV > 1 ) {
354    cmdline_error( "Too many arguments" );
355}; # if
356my $lib = shift( @ARGV );
357if ( not -e $lib ){
358    runtime_error( "Specified file does not exist: \"$lib\"" );
359}; # if
360
361# Select appropriate get_deps implementation.
362if ( 0 ) {
363} elsif ( $target_os eq "lin" ) {
364    *get_deps = \*get_deps_readelf;
365} elsif ( $target_os eq "mac" ) {
366    *get_deps = \*get_deps_otool;
367} elsif ( $target_os eq "win" ) {
368    *get_deps = \*get_deps_link;
369} else {
370    runtime_error( "OS \"$target_os\" not supported" );
371}; # if
372
373# Do the work.
374my @deps = sort( get_deps( $lib ) );
375if ( $bare ) {
376    print( map( "$_\n", @deps ) );
377} else {
378    info( "Dependencies:", @deps ? map( "    $_", @deps ) : "(none)" );
379}; # if
380if ( defined( $expected ) ) {
381    my %deps = map( ( $_ => 1 ), @deps );
382    foreach my $dep ( @expected ) {
383        delete( $deps{ $dep } );
384    }; # foreach
385    my @unexpected = sort( keys( %deps ) );
386    if ( @unexpected ) {
387        runtime_error( "Unexpected dependencies:", map( "    $_", @unexpected ) );
388    }; # if
389}; # if
390
391exit( 0 );
392
393__END__
394
395=pod
396
397=head1 NAME
398
399B<check-depends.pl> -- Check dependencies for a specified library.
400
401=head1 SYNOPSIS
402
403B<check-depends.pl> I<OPTIONS>... I<library>
404
405=head1 DESCRIPTION
406
407C<check-depends.pl> finds direct dependencies for a specified library. List of actual dependencies
408is sorted alphabetically and printed. If list of expected dependencies is specified, the scripts
409checks the library has only allowed dependencies. In case of not expected dependencies. the script
410issues error message and exits with non-zero code.
411
412Linux* OS and OS X*: The script finds dependencies only for dynamic libraries. Windows* OS: The script
413finds dependencies for either static or dynamic libraries.
414
415The script uses external tools. On Linux* OS, it runs F<readelf>, on OS X* -- F<otool> (or F<otool64>),
416on Windows* OS -- F<link>.
417
418On Windows* OS dependencies are printed in lower case, case of expected dependencies ignored.
419
420=head1 OPTIONS
421
422=over
423
424=item B<--bare>
425
426Do not use fancy formatting; produce plain, bare output: just a list of libraries,
427a library per line.
428
429=item B<--expected=>I<list>
430
431I<list> is comma-separated list of expected dependencies (or C<none>).
432If C<--expected> option specified, C<check-depends.pl> checks the specified library
433has only expected dependencies.
434
435=item B<--os=>I<str>
436
437Specify target OS (tool to use) manually.
438Useful for cross-build, when host OS is not the same as target OS.
439I<str> should be either C<lin>, C<mac>, or C<win>.
440
441=back
442
443=head2 Standard Options
444
445=over
446
447=item B<--help>
448
449Print short help message and exit.
450
451=item B<--doc>
452
453=item B<--manual>
454
455Print full documentation and exit.
456
457=item B<--quiet>
458
459Do not output informational messages.
460
461=item B<--version>
462
463Print version and exit.
464
465=back
466
467=head1 ARGUMENTS
468
469=over
470
471=item I<library>
472
473A name of library to find or check dependencies.
474
475=back
476
477=head1 EXAMPLES
478
479Just print library dependencies (Windows* OS):
480
481    > check-depends.pl exports/win_32/lib/libompmd.dll
482    check-depends.pl: (i) Dependencies:
483    check-depends.pl: (i)     kernel32.dll
484
485Print library dependencies, use bare output (Linux* OS):
486
487    $ check-depends.pl --bare exports/lin_32e/lib/libomp_db.so
488    libc.so.6
489    libdl.so.2
490    libpthread.so.0
491
492Check the library does not have any dependencies (OS X*):
493
494    $ check-depends.pl --expected=none exports/mac_32/lib/libomp.dylib
495    check-depends.pl: (i) Dependencies:
496    check-depends.pl: (i)     /usr/lib/libSystem.B.dylib
497    check-depends.pl: (x) Unexpected dependencies:
498    check-depends.pl: (x)     /usr/lib/libSystem.B.dylib
499    $ echo $?
500    2
501
502=cut
503
504# end of file #
505
506