github.com/hugelgupf/u-root@v0.0.0-20191023214958-4807c632154c/pkg/ldd/ldd_unix.go (about)

     1  // Copyright 2009-2018 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // ldd returns all the library dependencies of an executable.
     6  //
     7  // The way this is done on GNU-based systems is interesting. For each ELF, one
     8  // finds the .interp section. If there is no interpreter
     9  // there's not much to do.
    10  //
    11  // If there is an interpreter, we run it with the --list option and the file as
    12  // an argument. We need to parse the output.
    13  //
    14  // For all lines with =>  as the 2nd field, we take the 3rd field as a
    15  // dependency. The field may be a symlink.  Rather than stat the link and do
    16  // other such fooling around, we can do a readlink on it; if it fails, we just
    17  // need to add that file name; if it succeeds, we need to add that file name
    18  // and repeat with the next link in the chain. We can let the kernel do the
    19  // work of figuring what to do if and when we hit EMLINK.
    20  package ldd
    21  
    22  import (
    23  	"debug/elf"
    24  	"fmt"
    25  	"log"
    26  	"os"
    27  	"os/exec"
    28  	"path/filepath"
    29  	"strings"
    30  )
    31  
    32  // Follow starts at a pathname and adds it
    33  // to a map if it is not there.
    34  // If the pathname is a symlink, indicated by the Readlink
    35  // succeeding, links repeats and continues
    36  // for as long as the name is not found in the map.
    37  func follow(l string, names map[string]*FileInfo) error {
    38  	for {
    39  		if names[l] != nil {
    40  			return nil
    41  		}
    42  		i, err := os.Lstat(l)
    43  		if err != nil {
    44  			return fmt.Errorf("%v", err)
    45  		}
    46  
    47  		names[l] = &FileInfo{FullName: l, FileInfo: i}
    48  		if i.Mode().IsRegular() {
    49  			return nil
    50  		}
    51  		// If it's a symlink, the read works; if not, it fails.
    52  		// we can skip testing the type, since we still have to
    53  		// handle any error if it's a link.
    54  		next, err := os.Readlink(l)
    55  		if err != nil {
    56  			return err
    57  		}
    58  		// It may be a relative link, so we need to
    59  		// make it abs.
    60  		if filepath.IsAbs(next) {
    61  			l = next
    62  			continue
    63  		}
    64  		l = filepath.Join(filepath.Dir(l), next)
    65  	}
    66  }
    67  
    68  // runinterp runs the interpreter with the --list switch
    69  // and the file as an argument. For each returned line
    70  // it looks for => as the second field, indicating a
    71  // real .so (as opposed to the .vdso or a string like
    72  // 'not a dynamic executable'.
    73  func runinterp(interp, file string) ([]string, error) {
    74  	var names []string
    75  	o, err := exec.Command(interp, "--list", file).Output()
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  	for _, p := range strings.Split(string(o), "\n") {
    80  		f := strings.Split(p, " ")
    81  		if len(f) < 3 {
    82  			continue
    83  		}
    84  		if f[1] != "=>" || len(f[2]) == 0 {
    85  			continue
    86  		}
    87  		names = append(names, f[2])
    88  	}
    89  	return names, nil
    90  }
    91  
    92  type FileInfo struct {
    93  	FullName string
    94  	os.FileInfo
    95  }
    96  
    97  // Ldd returns a list of all library dependencies for a set of files.
    98  //
    99  // If a file has no dependencies, that is not an error. The only possible error
   100  // is if a file does not exist, or it says it has an interpreter but we can't
   101  // read it, or we are not able to run its interpreter.
   102  //
   103  // It's not an error for a file to not be an ELF.
   104  func Ldd(names []string) ([]*FileInfo, error) {
   105  	var (
   106  		list    = make(map[string]*FileInfo)
   107  		interps = make(map[string]*FileInfo)
   108  		libs    []*FileInfo
   109  	)
   110  	for _, n := range names {
   111  		if err := follow(n, list); err != nil {
   112  			return nil, err
   113  		}
   114  	}
   115  	for _, n := range names {
   116  		r, err := os.Open(n)
   117  		if err != nil {
   118  			return nil, err
   119  		}
   120  		defer r.Close()
   121  		f, err := elf.NewFile(r)
   122  		if err != nil {
   123  			continue
   124  		}
   125  		s := f.Section(".interp")
   126  		var interp string
   127  		if s != nil {
   128  			// If there is an interpreter section, it should be
   129  			// an error if we can't read it.
   130  			i, err := s.Data()
   131  			if err != nil {
   132  				return nil, err
   133  			}
   134  			// Ignore #! interpreters
   135  			if len(i) > 1 && i[0] == '#' && i[1] == '!' {
   136  				continue
   137  			}
   138  			// annoyingly, s.Data() seems to return the null at the end and,
   139  			// weirdly, that seems to confuse the kernel. Truncate it.
   140  			interp = string(i[:len(i)-1])
   141  		}
   142  		if interp == "" {
   143  			if f.Type != elf.ET_DYN || f.Class == elf.ELFCLASSNONE {
   144  				continue
   145  			}
   146  			bit64 := true
   147  			if f.Class != elf.ELFCLASS64 {
   148  				bit64 = false
   149  			}
   150  
   151  			// This is a shared library. Turns out you can run an interpreter with
   152  			// --list and this shared library as an argument. What interpreter
   153  			// do we use? Well, there's no way to know. You have to guess.
   154  			// I'm not sure why they could not just put an interp section in
   155  			// .so's but maybe that would cause trouble somewhere else.
   156  			interp, err = LdSo(bit64)
   157  			if err != nil {
   158  				return nil, err
   159  			}
   160  		}
   161  		// We could just append the interp but people
   162  		// expect to see that first.
   163  		if interps[interp] == nil {
   164  			err := follow(interp, interps)
   165  			if err != nil {
   166  				return nil, err
   167  			}
   168  		}
   169  		// oh boy. Now to run the interp and get more names.
   170  		n, err := runinterp(interp, n)
   171  		if err != nil {
   172  			return nil, err
   173  		}
   174  		for i := range n {
   175  			if err := follow(n[i], list); err != nil {
   176  				log.Fatalf("ldd: %v", err)
   177  			}
   178  		}
   179  	}
   180  
   181  	for i := range interps {
   182  		libs = append(libs, interps[i])
   183  	}
   184  
   185  	for i := range list {
   186  		libs = append(libs, list[i])
   187  	}
   188  
   189  	return libs, nil
   190  }
   191  
   192  // List returns the dependency file paths of files in names.
   193  func List(names []string) ([]string, error) {
   194  	var list []string
   195  	l, err := Ldd(names)
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  	for i := range l {
   200  		list = append(list, l[i].FullName)
   201  	}
   202  	return list, nil
   203  }