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