-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
321 lines (282 loc) · 6.91 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
// Author: Nikolas Sepos <nikolas.sepos@gmail.com>
//
// SPDX-License-Identifier: MIT
//
package main
import (
"bufio"
"debug/elf"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"github.com/nseps/godag"
"github.com/spf13/pflag"
)
// Data is exported so i can use data as variable name
type Data struct {
Name string
Path string
}
var ldPath []string
func main() {
pflag.Usage = func() {
fmt.Fprintf(os.Stderr, "* Usage of %s:\n", os.Args[0])
pflag.PrintDefaults()
}
libPath := pflag.StringP("libPath", "L", "", "A colon separated list of paths to look for libraries. If set this would be the only path")
appLibPath := pflag.StringP("appendlibPath", "a", "", "A colon separated list of paths to append in default path")
preLibPath := pflag.StringP("prependlibPath", "p", "", "A colon separated list of paths to prepend in default path")
ldConf := pflag.String("ldConf", "/etc/ld.so.conf", "Parse ld config file and append to the default path")
trace := pflag.Bool("trace", false, "Run binary with LD_TRACE_LOADED_OBJECTS=1 set. This is equivalent to ldd <target>. No need for ldd binary")
tree := pflag.Bool("tree", false, "Get a nice dependency tree")
export := pflag.String("export", "", "Export directory path. It will copy everything it finds, including target binary, to dir")
pflag.Parse()
// we want 1 argument. the target binary
if len(os.Args) < 2 {
die(fmt.Errorf("path to binary is missing"))
}
target := os.Args[1]
// chack if exists
if _, err := os.Stat(target); err != nil {
die(fmt.Errorf("cannot stat target binary: %v", err))
}
// parse elf
file, err := elf.Open(target)
if err != nil {
die(fmt.Errorf("cannot read elf target: %v", err))
}
defer file.Close()
// simulate ldd <target>
if *trace {
cmd := exec.Command(target)
cmd.Env = []string{"LD_TRACE_LOADED_OBJECTS=1"}
out, err := cmd.CombinedOutput()
if err != nil {
die(fmt.Errorf("could not execute target %s: %v", target, err))
}
// what's our target
fmt.Printf("Target: %s, Class: %s\n", target, file.Class)
// print ldd output
fmt.Print(string(out))
return
}
if *libPath == "" {
// prepend to path
if *preLibPath != "" {
ldPath = strings.Split(*preLibPath, ":")
}
// binary path
binPath := filepath.Dir(target)
ldPath = append(ldPath, binPath)
// prepend /lib64 if is 64bit bin
if file.Class == elf.ELFCLASS64 {
ldPath = append(ldPath, "/lib64")
}
// default lib path
ldPath = append(ldPath, "/lib", "/usr/lib")
// append to path
if *appLibPath != "" {
ldPath = append(ldPath, strings.Split(*appLibPath, ":")...)
}
// parse ld conf
if *ldConf != "" {
pathLdConf, err := parseLdConf(*ldConf)
if err != nil {
// no need to die on this
fmt.Printf("Warning: Parsing ldconfig failed: %v\n", err)
} else {
ldPath = append(ldPath, pathLdConf...)
}
}
} else {
// there shall be only one path!
ldPath = strings.Split(*libPath, ":")
}
fmt.Println("Path lookup order:")
fmt.Println(strings.Join(ldPath, "\n"))
fmt.Println()
d := dag.New()
err = getTree(target, filepath.Base(target), d)
if err != nil {
die(fmt.Errorf("failed to get tree: %v", err))
}
root := d.Roots()[0]
deps := map[string]*Data{}
root.Walk(dag.WalkDepthFirst, func(node *dag.Node, depth int) error {
// get data from graph
data, ok := node.Value.(*Data)
if !ok {
die(fmt.Errorf("Cannot get value from node"))
}
// TODO: Expose the underlaying map from godag
// to not have to walk and get unique keys.
// We know that the map has already all we need.
if _, inMap := deps[data.Name]; !inMap {
deps[data.Name] = data
}
// if tree should be surrounding the Walk function
// after the above is solved.
if *tree {
// print tree
for i := 0; i < depth; i = i + 1 {
fmt.Printf(" |")
}
fmt.Printf("-%s\n", data.Name)
}
return nil
})
if *export != "" {
os.Mkdir(*export, 0775)
for _, dep := range deps {
if dep.Path == "" {
fmt.Printf("Warning: File not found for lib %s\n", dep.Name)
continue
}
fmt.Printf("Copy %s: %s => %s\n", dep.Name, dep.Path, *export)
if err := copyFile(dep.Path, filepath.Join(*export, dep.Name)); err != nil {
die(fmt.Errorf("cannot copy file %s: %v", dep.Path, err))
}
}
return
}
if !*tree {
// what's our target
fmt.Printf("Target: %s, Class: %s\n", target, file.Class)
for _, dep := range deps {
// just print the unsorted map
fmt.Printf(" %s => %s\n", dep.Name, dep.Path)
}
}
}
func die(err error) {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
pflag.Usage()
os.Exit(1)
}
func copyFile(src, dst string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
inStat, err := in.Stat()
if err != nil {
return err
}
out, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, inStat.Mode())
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
if err != nil {
return err
}
return out.Close()
}
func getTree(path, name string, d *dag.Dag) error {
deps, err := getLibDeps(path)
if err != nil {
return err
}
d.AddNode(name, &Data{
Name: name,
Path: path,
})
for _, lib := range deps {
libFile, err := findInPath(lib, ldPath)
if err != nil {
return err
}
// we could not find the lib
if libFile == "" {
// just add the node
d.AddNode(lib, &Data{
Name: lib,
Path: "",
})
} else {
// recurse
err := getTree(libFile, lib, d)
if err != nil {
return err
}
}
d.AddEdge(name, lib)
if err != nil {
fmt.Println(err)
}
}
return nil
}
func getLibDeps(path string) ([]string, error) {
file, err := elf.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
deps, err := file.ImportedLibraries()
if err != nil {
return nil, err
}
return deps, nil
}
func findInPath(lib string, path []string) (string, error) {
for _, p := range path {
file := filepath.Join(p, lib)
_, err := os.Stat(file)
if err == nil {
return file, nil
}
if os.IsNotExist(err) {
continue
}
return "", err
}
return "", nil
}
var rInclude = regexp.MustCompile("^include (.*)$")
func parseLdConf(path string) ([]string, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
ret := []string{}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
// discard empty lines
if strings.TrimLeft(line, " ") == "" {
continue
}
// discard comments
if strings.TrimLeft(line, " ")[0] == '#' {
continue
}
// include other files
if rInclude.MatchString(line) {
// grub the file glob
match := rInclude.FindStringSubmatch(line)
// get included file paths
incPaths, err := filepath.Glob(match[1])
if err != nil {
return nil, err
}
for _, incPath := range incPaths {
inc, err := parseLdConf(incPath)
if err != nil {
return nil, err
}
ret = append(ret, inc...)
}
continue
}
ret = append(ret, filepath.Clean(line))
}
return ret, nil
}