github.com/jlowellwofford/u-root@v1.0.0/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 r, err := os.Open(n) 114 if err != nil { 115 return nil, err 116 } 117 defer r.Close() 118 f, err := elf.NewFile(r) 119 if err != nil { 120 continue 121 } 122 s := f.Section(".interp") 123 var interp string 124 if s != nil { 125 // If there is an interpreter section, it should be 126 // an error if we can't read it. 127 i, err := s.Data() 128 if err != nil { 129 return nil, err 130 } 131 // Ignore #! interpreters 132 if len(i) > 1 && i[0] == '#' && i[1] == '!' { 133 continue 134 } 135 // annoyingly, s.Data() seems to return the null at the end and, 136 // weirdly, that seems to confuse the kernel. Truncate it. 137 interp = string(i[:len(i)-1]) 138 } 139 if interp == "" { 140 if f.Type != elf.ET_DYN { 141 continue 142 } 143 // This is a shared library. Turns out you can run an interpreter with 144 // --list and this shared library as an argument. What interpreter 145 // do we use? Well, there's no way to know. You have to guess. 146 // I'm not sure why they could not just put an interp section in 147 // .so's but maybe that would cause trouble somewhere else. 148 interp, err = LdSo() 149 if err != nil { 150 return nil, err 151 } 152 } 153 // We could just append the interp but people 154 // expect to see that first. 155 if interps[interp] == nil { 156 err := follow(interp, interps) 157 if err != nil { 158 return nil, err 159 } 160 } 161 // oh boy. Now to run the interp and get more names. 162 n, err := runinterp(interp, n) 163 if err != nil { 164 return nil, err 165 } 166 for i := range n { 167 if err := follow(n[i], list); err != nil { 168 log.Fatalf("ldd: %v", err) 169 } 170 } 171 } 172 173 for i := range interps { 174 libs = append(libs, interps[i]) 175 } 176 177 for i := range list { 178 libs = append(libs, list[i]) 179 } 180 181 return libs, nil 182 } 183 184 func List(names []string) ([]string, error) { 185 var list []string 186 l, err := Ldd(names) 187 if err != nil { 188 return nil, err 189 } 190 for i := range l { 191 list = append(list, l[i].FullName) 192 } 193 return list, nil 194 }