mirror of
https://github.com/jimeh/emacs-builds.git
synced 2026-02-19 08:26:40 +00:00
feat(tools): add dylib-tree tool to list/filter linked dynamic libraries
This commit is contained in:
248
cmd/dylib-tree/main.go
Normal file
248
cmd/dylib-tree/main.go
Normal file
@@ -0,0 +1,248 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"debug/macho"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/xlab/treeprint"
|
||||
)
|
||||
|
||||
var app = &cli.App{
|
||||
Name: "link-tree",
|
||||
Usage: "recursive list shared-libraries as a tree",
|
||||
UsageText: "link-tree [options] <binary-file> [<binary-file>]",
|
||||
Flags: []cli.Flag{
|
||||
&cli.IntFlag{
|
||||
Name: "depth",
|
||||
Usage: "max depth of tree (default: 0 = unlimited)",
|
||||
Value: 0,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "ignore",
|
||||
Usage: "path patterns to ignore",
|
||||
},
|
||||
|
||||
&cli.BoolFlag{
|
||||
Name: "ignore-system",
|
||||
Usage: "ignore system libraries",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "real-path",
|
||||
Usage: "show resolved full paths instead of @executable_path " +
|
||||
"and @rpath",
|
||||
},
|
||||
},
|
||||
Action: actionHandler(LinkTreeCmd),
|
||||
}
|
||||
|
||||
type Context struct {
|
||||
Root string
|
||||
ExecutablePath string
|
||||
Depth int
|
||||
MaxDepth int
|
||||
RealPath bool
|
||||
Ignore []string
|
||||
RPaths []string
|
||||
}
|
||||
|
||||
func (s *Context) WithFile(filename string) *Context {
|
||||
ctx := *s
|
||||
ctx.Root = filename
|
||||
ctx.ExecutablePath = filepath.Dir(filename)
|
||||
return &ctx
|
||||
}
|
||||
|
||||
func (s *Context) WithDepth(depth int) *Context {
|
||||
ctx := *s
|
||||
ctx.Depth = depth
|
||||
return &ctx
|
||||
}
|
||||
|
||||
func (s *Context) WithIgnore(ignore []string) *Context {
|
||||
ctx := *s
|
||||
ctx.Ignore = append(ctx.Ignore, ignore...)
|
||||
return &ctx
|
||||
}
|
||||
|
||||
func (s *Context) WithRpaths(rpaths []string) *Context {
|
||||
ctx := *s
|
||||
ctx.RPaths = append(ctx.RPaths, rpaths...)
|
||||
return &ctx
|
||||
}
|
||||
|
||||
func actionHandler(
|
||||
f func(*cli.Context, *Context) error,
|
||||
) func(*cli.Context) error {
|
||||
return func(c *cli.Context) error {
|
||||
ignore := c.StringSlice("ignore")
|
||||
if c.Bool("ignore-system") {
|
||||
ignore = append(
|
||||
ignore,
|
||||
"/System/Library/*",
|
||||
"*/libSystem.*.dylib",
|
||||
"*/libobjc.*.dylib",
|
||||
)
|
||||
}
|
||||
|
||||
ctx := &Context{
|
||||
Ignore: ignore,
|
||||
MaxDepth: c.Int("depth"),
|
||||
RealPath: c.Bool("real-path"),
|
||||
}
|
||||
|
||||
return f(c, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func LinkTreeCmd(c *cli.Context, ctx *Context) error {
|
||||
for _, filename := range c.Args().Slice() {
|
||||
ctx := ctx.WithFile(filename)
|
||||
|
||||
treeRoot := treeprint.New()
|
||||
tree := treeRoot.AddBranch(filename)
|
||||
|
||||
err := processBinary(&tree, ctx, filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(treeRoot.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func processBinary(
|
||||
parent *treeprint.Tree,
|
||||
ctx *Context,
|
||||
filename string,
|
||||
) error {
|
||||
f, err := macho.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx = ctx.WithDepth(ctx.Depth + 1).WithRpaths(getRpaths(f))
|
||||
|
||||
if ctx.MaxDepth > 0 && ctx.Depth > ctx.MaxDepth {
|
||||
return nil
|
||||
}
|
||||
|
||||
libs, err := f.ImportedLibraries()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, lib := range libs {
|
||||
skip, err := ignoreLib(ctx, lib)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skip {
|
||||
continue
|
||||
}
|
||||
|
||||
filename, err := resolveLibFilename(ctx, lib)
|
||||
if err != nil {
|
||||
(*parent).AddBranch(lib)
|
||||
continue
|
||||
}
|
||||
|
||||
skip, err = ignoreLib(ctx, filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skip {
|
||||
continue
|
||||
}
|
||||
|
||||
displayName := lib
|
||||
if ctx.RealPath {
|
||||
displayName = filename
|
||||
}
|
||||
|
||||
tree := (*parent).AddBranch(displayName)
|
||||
|
||||
err = processBinary(&tree, ctx, filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ignoreLib(ctx *Context, lib string) (bool, error) {
|
||||
for _, pattern := range ctx.Ignore {
|
||||
m, err := ignoreRegexp(pattern)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if m.MatchString(lib) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func ignoreRegexp(p string) (*regexp.Regexp, error) {
|
||||
rp := "^" + regexp.QuoteMeta(p) + "$"
|
||||
rp = strings.ReplaceAll(rp, `\*`, ".*")
|
||||
rp = strings.ReplaceAll(rp, `\?`, ".")
|
||||
|
||||
return regexp.Compile(rp)
|
||||
}
|
||||
|
||||
func resolveLibFilename(ctx *Context, lib string) (string, error) {
|
||||
filename := lib
|
||||
|
||||
if strings.HasPrefix(lib, "@executable_path") {
|
||||
filename = filepath.Join(ctx.ExecutablePath, lib[16:])
|
||||
} else if strings.HasPrefix(lib, "@rpath") {
|
||||
for _, r := range ctx.RPaths {
|
||||
if strings.HasPrefix(r, "@executable_path") {
|
||||
r = filepath.Join(ctx.ExecutablePath, r[16:])
|
||||
}
|
||||
|
||||
rfile := filepath.Join(r, lib[6:])
|
||||
_, err := os.Stat(rfile)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
return rfile, nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("could not find %s", lib)
|
||||
}
|
||||
|
||||
_, err := os.Stat(filename)
|
||||
|
||||
return filename, err
|
||||
}
|
||||
|
||||
func getRpaths(f *macho.File) []string {
|
||||
paths := []string{}
|
||||
|
||||
for _, i := range f.Loads {
|
||||
if r, ok := i.(*macho.Rpath); ok {
|
||||
paths = append(paths, r.Path)
|
||||
}
|
||||
}
|
||||
|
||||
return paths
|
||||
}
|
||||
|
||||
func main() {
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,6 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type ReleaseType string
|
||||
|
||||
type Release struct {
|
||||
Name string `yaml:"name"`
|
||||
Title string `yaml:"title,omitempty"`
|
||||
|
||||
1
go.mod
1
go.mod
@@ -5,6 +5,7 @@ go 1.16
|
||||
require (
|
||||
github.com/google/go-github/v35 v35.1.0
|
||||
github.com/urfave/cli/v2 v2.3.0
|
||||
github.com/xlab/treeprint v1.1.0
|
||||
golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
)
|
||||
|
||||
6
go.sum
6
go.sum
@@ -41,6 +41,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
@@ -121,8 +122,12 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5I
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk=
|
||||
github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@@ -368,6 +373,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
Reference in New Issue
Block a user