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# Output 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