1 #!/usr/bin/perl 2 # 3 # CDDL HEADER START 4 # 5 # The contents of this file are subject to the terms of the 6 # Common Development and Distribution License, Version 1.0 only 7 # (the "License"). You may not use this file except in compliance 8 # with the License. 9 # 10 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 11 # or http://www.opensolaris.org/os/licensing. 12 # See the License for the specific language governing permissions 13 # and limitations under the License. 14 # 15 # When distributing Covered Code, include this CDDL HEADER in each 16 # file and include the License file at usr/src/OPENSOLARIS.LICENSE. 17 # If applicable, add the following below this CDDL HEADER, with the 18 # fields enclosed by brackets "[]" replaced with your own identifying 19 # information: Portions Copyright [yyyy] [name of copyright owner] 20 # 21 # CDDL HEADER END 22 # 23 # 24 # Copyright (c) 2000 by Sun Microsystems, Inc. 25 # All rights reserved. 26 # 27 28 # ident "%Z%%M% %I% %E% SMI" 29 30 # 31 # check for perl5 -- we use things unavailable in perl4 32 # 33 34 die "Sorry, this program requires perl version 5.000 or up. You have $]. Stopping" if $] < 5.000; 35 36 $DBM_DIR_CHARACTERIZATION = "directory for the dbm databases"; 37 38 $Usage = 39 "Usage: get_depend_info 40 -dbdir <$DBM_DIR_CHARACTERIZATION> [ -s ] [ -cons ] [ -root directory ] 41 [ -f ] [ -p ] [ -pkg SUNWxxx ] [ filename ] 42 [-h for help]\n"; 43 44 $Help = 45 "This program statically analyzes executable files and their 46 symbolic links using /usr/bin/ldd and /usr/bin/strings. It 47 can accept filename(s) or packages as the list of files to be 48 analyzed. By default, the program will report the file 49 dependencies and which packages those dependencies came from. 50 There is one required argument: 51 52 -dbdir <dir> the $DBM_DIR_CHARACTERIZATION 53 54 The optional argument -h produces this message instead of any processing. 55 The optional argument -cons tells the tool to be conservative and not to 56 run /usr/bin/strings. 57 The optional argument -root allows you to specify a new root (useful for 58 doing analysis on build trees). 59 The optional argument -pkg allows you to specify a package name. 60 The optional argument -f only outputs the filename of the dependencies 61 The optional argument -p only outputs the packanames of the dependencies 62 63 The optional argument -s ONLY outputs symbolic links for files or packages. 64 No ldd or strings analysis is done. 65 66 Some Examples: 67 get_depend_info -dbdir ./DBM /usr/bin/ls 68 get_depend_info -dbdir ./DBM /usr/bin/* 69 get_depend_info -dbdir ./DBM -pkg SUNWnisu 70 get_depend_info -f -dbdir ./DBM -pkg SUNWnisu 71 get_depend_info -s -dbdir ./DBM /usr/bin/* 72 get_depend_info -s -dbdir ./DBM -pkg SUNWnisu 73 74 75 NOTE: Run make_pkg_db to create the database directory for get_depend_info 76 "; 77 78 # 79 # process arguments 80 # 81 82 @PkgList = ""; 83 $PackageOnly = false; 84 $FileOnly = false; 85 $Verbose = true; 86 $Silent = false; 87 $SymLink = false; 88 $NoStrings = false; 89 $Root = ""; 90 91 while (@ARGV) { 92 $arg = shift (@ARGV); 93 if ($arg eq "-h") { 94 print "$Help\n$Usage"; 95 exit 0; 96 } elsif ($arg eq "-dbdir") { 97 $DBDir = shift(@ARGV) unless ($ARGV[0] =~ /^-/); 98 } elsif ($arg eq "-s") { 99 $SymLink = true; 100 $Silent = true; 101 } elsif ($arg eq "-p") { 102 $PackageOnly = true; 103 $Verbose = false; 104 } elsif ($arg eq "-f") { 105 $FileOnly = true; 106 $Verbose = false; 107 } elsif ($arg eq "-cons") { 108 $NoStrings = true; 109 } elsif ($arg eq "-pkg") { 110 $PKGName = shift(@ARGV) unless ($ARGV[0] =~ /^-/); 111 } elsif ($arg eq "-root") { 112 $Root = shift(@ARGV) unless ($ARGV[0] =~ /^-/); 113 }else { 114 push(@filelist, $arg); 115 } 116 } 117 118 if (!$DBDir) { 119 print STDERR "Required argument -dbdir missing. \n$Usage"; 120 exit 1; 121 } 122 123 if ($PKGName) { 124 # For a given pkg definition directory, this subroutine will 125 # go through the proto files and look for executable files. 126 # It will then put all the executable files into @filelist 127 &HandlePackageName($PKGName); 128 129 if ($PackageOnly eq true) { 130 $Silent = true; 131 } 132 } 133 134 &OpenDBs; 135 136 $Silent = true if (($Verbose eq false) && ($PackageOnly eq false) 137 && ($FileOnly eq false)); 138 139 foreach $entry (@filelist) { 140 141 print("\n\nAnalyzing $Root$entry:\n") unless ($Silent eq true); 142 143 # make sure file exists 144 if (!(-r $entry)) { 145 print STDERR "Could not open file $entry\n"; 146 next; 147 } 148 149 150 $file = $FTYPE{$entry}; 151 $pkgs = $PKGS{$entry}; 152 $abslink = $ABSLINK{$entry}; 153 154 if ($file eq "d") { 155 print("Input file is a directory\n") unless ($Silent eq true); 156 next; 157 } 158 159 # destfile represents the actual file we are interested in! 160 if ($abslink =~ /\w/) { 161 $destfile = $abslink; 162 163 if (($FTYPE{$entry} eq "s") && ($SymLink eq true)) { 164 print("$entry is linked to $destfile:$PKGS{$destfile}\n"); 165 } 166 } 167 else { 168 $destfile = $entry; 169 } 170 171 # if the -s flag is set, tell 'em about sym links and go to the next file 172 next if ($SymLink eq true); 173 174 $mode = $MODE{$destfile}; 175 176 # Handle the case where the user resets $ROOT 177 $destfile = "$Root$destfile" if ($Root =~ /\w/); 178 $filecmd = `/usr/bin/file $destfile 2>&1`; 179 180 # make sure we are dealing with an executable 181 if (($mode !~ /(.)(.*)7/) && ($mode !~ /(.)(.*)5/) && ($mode !~ /(.)(.*)3/) && ($mode !~ /(.)(.*)1/)){ 182 print("Input file is not an executable\n"); 183 next; 184 } 185 186 # Kernel modules are handled separately 187 if ($destfile =~ /\/kernel\//) { 188 &HandleKernelMod($destfile, $FTYPE{$entry}); 189 &OutputPackageList if (($PackageOnly eq true) && !($PKGName)); 190 next; 191 } 192 193 # take care of scripts 194 if (($filecmd =~ /script/) || ($filecmd =~ /text/)) { 195 &HandleScripts($destfile); 196 &OutputPackageList if (($PackageOnly eq true) && !($PKGName)); 197 next; 198 } 199 200 # Its not a script, not a kernel module, so its get to be a binary 201 &HandleBinaries($destfile); 202 &OutputPackageList if (($PackageOnly eq true) && !($PKGName)); 203 } 204 205 if (($PKGName) && ($SymLink eq false)) { 206 print ("\n\nPackage dependencies for $PKGName:\n"); 207 &OutputPackageList; 208 } 209 210 &CloseDBs; 211 212 #===========================END OF MAIN==================================== 213 214 sub GetLddInfo { # return multi-line string of ldd info for File 215 local ($FileID, $FileType) = @_; 216 217 $outstring = "* Not a File\n"; 218 return ($outstring) if $FileType =~ /[Mlsdc]/; # ldd results not useful here 219 220 # 221 # use map file to see if this is a file that gives a known bad ldd return 222 # 223 224 # if ($Unsup{$FileID} == 1) { 225 # $outstring = "* unsupported or unknown file type, per map file"; 226 # return ($outstring); 227 # } 228 # $err = ""; 229 # $string = `/usr/bin/ldd $FileID 2>&1`; 230 # if ($?) { # if some error (don't just get wait status here) 231 # $errnum = 0 + $!; 232 # $err = "==$?==$errnum=="; 233 # if (($err eq "==256==29==") || ($err eq "==256==0==")) { 234 # $err = "*"; # these are normal ldd returns 235 # } else { 236 # die "Unexpected ldd return $? $!"; 237 # } 238 # $string =~ s/\/usr\/bin\/ldd:[^\0]*://g; # trim up error line 239 # } elsif ($string =~ s/warning:.*://) { # other normal ldd returns 240 # $err = "*"; 241 # } 242 243 $outstring = ""; 244 $string = `/usr/bin/ldd $FileID 2>&1`; 245 # on a non-zero ldd, return nothing 246 return ($outstring) if ($?); 247 248 249 $outstring = ""; 250 @infolines = split(/\s*\n\s*/, $string); 251 foreach $line (@infolines) { 252 $line =~ s/^\s+//; # trim leading ws 253 next unless $line; # skip if blank 254 @words = split(/\s/, $line); 255 $filename = $words[0]; 256 $outstring .= "$filename\n"; 257 } 258 return ($outstring); 259 } 260 261 sub CloseDBs { 262 # close the dbs 263 dbmclose(FTYPE); 264 dbmclose(MODE); 265 dbmclose(PKGS); 266 dbmclose(ABSLINK); 267 dbmclose(PKGNAMES); 268 } 269 270 sub OpenDBs { 271 # open the databases for read-only 272 dbmopen(%FTYPE, "$DBDir/FTYPE", 0664) || 273 die"Cannot open dbm db $DBDir/FTYPE\n"; 274 275 dbmopen(%MODE, "$DBDir/MODE", 0664) || 276 die"Cannot open dbm db $DBDir/MODE\n"; 277 278 dbmopen(%PKGS, "$DBDir/PKGS", 0664) || 279 die"Cannot open dbm db $DBDir/PKGS\n"; 280 281 dbmopen(%ABSLINK, "$DBDir/ABSLINK", 0664) || 282 die"Cannot open dbm db $DBDir/ABSLINK \n"; 283 284 dbmopen(%PKGNAMES, "$DBDir/PKGNAMES", 0644) || 285 die"Cannot open dbm db $DBDir/PKGNAMES\n"; 286 } 287 288 sub HandleKernelMod { 289 local ($entry, $ftype) = @_; 290 291 # search for the magic right, starting from the right (ie. end of path) 292 $index = rindex($entry, "kernel"); 293 # rindex() returns where the "kernel" began, add 6 to get 294 # "{some path}/kernel" 295 $index += 6; 296 # OK, now pull out the absolute path 297 $KernelPath = substr($entry, 0, $index); 298 299 # There are two ways to figure out the dependencies. 300 # First, we check to see if /usr/bin/ldd will tell us. 301 # If ldd fails, then we need to look at the output of /usr/bin/strings 302 303 $LddInfo = ""; 304 $LddInfo = &GetLddInfo($entry, $ftype); 305 306 if ($LddInfo =~ /\w/) { 307 @list = ""; 308 @list = split(/\n/, $LddInfo); 309 foreach $file (@list) { 310 $found = 0; 311 312 # first, check to see if there is a module relative to 313 # this file 314 if ($FTYPE{"$KernelPath/$file"} =~ /\w/){ 315 &Output("$KernelPath/$file"); 316 $found++; 317 } 318 319 # Haven't found it yet, check /kernel 320 if (($FTYPE{"/kernel/$file"} =~ /\w/) && ($found == 0)){ 321 &Output("/kernel/$file"); 322 $found++; 323 } 324 325 # Haven't found it yet, check /usr/kernel 326 if (($FTYPE{"/usr/kernel/$file"} =~ /\w/) && ($found == 0)){ 327 &Output("/usr/kernel/$file"); 328 $found++; 329 } 330 331 if ($found == 0) { 332 print("Could not resolve $file\n"); 333 } 334 } 335 return; 336 } 337 338 # the ldd failed, so now let's look at the string output 339 $string = ""; 340 @infolines = ""; 341 @outlines = ""; 342 343 $string = `/usr/bin/strings $entry 2>&1`; 344 @infolines = split(/\s*\n\s*/, $string); 345 346 foreach $line (@infolines) { 347 if ($line =~ /\//){ 348 push (@outlines,$line); 349 } 350 } 351 352 foreach $line (@outlines) { 353 @words = split(/\s/, $line); 354 foreach $word (@words) { 355 $found = 0; 356 357 # first, check to see if there is a module relative to 358 # this file 359 if ($FTYPE{"$KernelPath/$word"} =~ /\w/){ 360 &Output("$KernelPath/$word"); 361 $found++; 362 } 363 364 # Haven't found it yet, check /kernel 365 if (($FTYPE{"/kernel/$word"} =~ /\w/) && ($found == 0)){ 366 &Output("/kernel/$word"); 367 $found++; 368 } 369 370 # Haven't found it yet, check /usr/kernel 371 if (($FTYPE{"/usr/kernel/$word"} =~ /\w/) && ($found == 0)){ 372 &Output("/usr/kernel/$word"); 373 $found++; 374 } 375 } 376 } 377 } 378 379 sub GetStringsInfo { # return multi-line string of ldd info for File 380 local ($FileID, $FileType) = @_; 381 382 $outstring = "* Not a File\n"; 383 return ($outstring) if $FileType =~ /[Mlsdc]/; # ldd results not useful here 384 return ($outstring) if ($NoStrings eq true); # we are running in conservative mode 385 386 # use map file to see if this is a file that gives a known bad ldd return 387 if ($Unsup{$FileID} == 1) { 388 $outstring = "* unsupported or unknown file type, per map file"; 389 return ($outstring); 390 } 391 $err = ""; 392 $string = ""; 393 $string = `/usr/bin/strings $FileID 2>&1`; 394 395 $outstring = ""; 396 @infolines = ""; 397 @outlines = ""; 398 @infolines = split(/\s*\n\s*/, $string); 399 400 foreach $line (@infolines) { 401 if (($line =~ /\//) && !($line =~ /%/) && !($line =~ m%/$%)){ 402 push (@outlines,$line); 403 } 404 } 405 @outlines = sort(@outlines); 406 407 foreach $word (@outlines) { 408 if ($lastword ne $word) { 409 $outstring .= $word; $outstring .= "\n"; 410 } 411 $lastword = $word; 412 } 413 return ($outstring); 414 } 415 416 sub HandleScripts { 417 local ($filename) = @_; 418 open(SCRIPT, $filename); 419 420 undef @output; 421 while (<SCRIPT>) { 422 s/^\s+//; # trim leading ws 423 s/=/ /g; # get rid of all = 424 s/\`/ /g; # get rid of all ` 425 next if ($_ =~ /^#/); # strip out obvious comments 426 next unless $_; # skip if blank 427 428 $line = $_; 429 @words = split(/\s/, $line); 430 foreach $word (@words) { 431 if (($PKGS{$word} =~ /\w/) && ($FTYPE{$word} ne "d")) { 432 push(@output, $word); 433 } 434 } 435 } 436 437 @output = sort(@output); 438 $count = 0; 439 440 # make sure we don't output dupes 441 foreach $file (@output) { 442 if ($count == 0) { 443 &Output($file); 444 } 445 446 if (($count > 0) && ($output[$count] ne $output[$count-1])) { 447 &Output($file); 448 } 449 $count++; 450 } 451 452 # remember to play nice 453 close(SCRIPT); 454 } 455 456 sub HandleBinaries { 457 local ($filename) = @_; 458 $LddInfo = &GetLddInfo($destfile, $FTYPE{$filename}); 459 $StringInfo = &GetStringsInfo($destfile, $FTYPE{$filename}); 460 461 # Parse the ldd output. 462 # Libs can be found in /kernel or /usr/lib 463 @list = split(/\n/, $LddInfo); 464 foreach $file (@list) { 465 $found = 0; 466 if ($FTYPE{"/kernel/$file"} =~ /\w/){ 467 &Output("/kernel/$file"); 468 $found++; 469 } 470 471 if ($FTYPE{"/usr/lib/$file"} =~ /\w/){ 472 &Output("/usr/lib/$file"); 473 $found++; 474 } 475 476 if ($found == 0) { 477 print("Could not resolve $file\n"); 478 } 479 } 480 481 # For the strings output, we parse it to see if we can match it to 482 # any files distributed in a package. 483 @list = split(/\n/, $StringInfo); 484 foreach $file (@list) { 485 if (($FTYPE{$file} =~ /\w/) && ($FTYPE{$file} ne "d")) { 486 &Output($file); 487 } 488 } 489 } 490 491 sub Output { 492 local($filename) = @_; 493 494 # If they want a package listing, a unique sorted list 495 # will be outputted later. Here we simply push elements onto 496 # this list. 497 if ($PKGName) { 498 push(@PkgList, "$PKGS{$filename}\n"); 499 } 500 501 if ($Verbose eq true) { 502 print("$filename:$PKGS{$filename}\n"); 503 return; 504 } 505 506 # If they want a package listing, a unique sorted list 507 # will be outputted later. Here we simply push elements onto 508 # this list. 509 if ($PackageOnly eq true) { 510 push(@PkgList, "$PKGS{$filename}\n"); 511 return; 512 } 513 514 if ($FileOnly eq true) { 515 print("$filename\n"); 516 return; 517 } 518 } 519 520 sub HandlePackageName { 521 local($pkg) = @_; 522 $pkgchk = `/usr/sbin/pkgchk -l $pkg | grep Pathname | sed 's/Pathname: //'`; 523 524 @files = split(/\n/, $pkgchk); 525 foreach $file (@files) { 526 push(@filelist, $file); 527 } 528 } 529 530 sub OutputPackageList { 531 local($filename) = @_; 532 # If the user specified a package list, here we sort 533 # the list and make sure we don't output dupes. 534 $lastpkg = ""; 535 @outPkgs = sort(@PkgList); 536 537 foreach $pkg (@outPkgs) { 538 $pkg =~ s/\s*$//; # trim extra space off the end 539 540 # make sure this entry isn't a dupe before 541 # printing it 542 if ($lastpkg ne $pkg) { 543 print("P $pkg\t$PKGNAMES{$pkg}\n"); 544 } 545 546 $lastpkg = $pkg; 547 } 548 549 # reset the list for the next entry 550 @PkgList = ""; 551 } 552