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