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 }