1 | // Copyright 2013 The Go 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 | // ssadump: a tool for displaying and interpreting the SSA form of Go programs. |
6 | package main // import "golang.org/x/tools/cmd/ssadump" |
7 | |
8 | import ( |
9 | "flag" |
10 | "fmt" |
11 | "go/build" |
12 | "go/types" |
13 | "os" |
14 | "runtime" |
15 | "runtime/pprof" |
16 | |
17 | "golang.org/x/tools/go/buildutil" |
18 | "golang.org/x/tools/go/packages" |
19 | "golang.org/x/tools/go/ssa" |
20 | "golang.org/x/tools/go/ssa/interp" |
21 | "golang.org/x/tools/go/ssa/ssautil" |
22 | ) |
23 | |
24 | // flags |
25 | var ( |
26 | mode = ssa.BuilderMode(0) |
27 | |
28 | testFlag = flag.Bool("test", false, "include implicit test packages and executables") |
29 | |
30 | runFlag = flag.Bool("run", false, "interpret the SSA program") |
31 | |
32 | interpFlag = flag.String("interp", "", `Options controlling the SSA test interpreter. |
33 | The value is a sequence of zero or more more of these letters: |
34 | R disable [R]ecover() from panic; show interpreter crash instead. |
35 | T [T]race execution of the program. Best for single-threaded programs! |
36 | `) |
37 | |
38 | cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") |
39 | |
40 | args stringListValue |
41 | ) |
42 | |
43 | func init() { |
44 | flag.Var(&mode, "build", ssa.BuilderModeDoc) |
45 | flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc) |
46 | flag.Var(&args, "arg", "add argument to interpreted program") |
47 | } |
48 | |
49 | const usage = `SSA builder and interpreter. |
50 | Usage: ssadump [-build=[DBCSNFLG]] [-test] [-run] [-interp=[TR]] [-arg=...] package... |
51 | Use -help flag to display options. |
52 | |
53 | Examples: |
54 | % ssadump -build=F hello.go # dump SSA form of a single package |
55 | % ssadump -build=F -test fmt # dump SSA form of a package and its tests |
56 | % ssadump -run -interp=T hello.go # interpret a program, with tracing |
57 | |
58 | The -run flag causes ssadump to build the code in a runnable form and run the first |
59 | package named main. |
60 | |
61 | Interpretation of the standard "testing" package is no longer supported. |
62 | ` |
63 | |
64 | func main() { |
65 | if err := doMain(); err != nil { |
66 | fmt.Fprintf(os.Stderr, "ssadump: %s\n", err) |
67 | os.Exit(1) |
68 | } |
69 | } |
70 | |
71 | func doMain() error { |
72 | flag.Parse() |
73 | if len(flag.Args()) == 0 { |
74 | fmt.Fprint(os.Stderr, usage) |
75 | os.Exit(1) |
76 | } |
77 | |
78 | cfg := &packages.Config{ |
79 | Mode: packages.LoadSyntax, |
80 | Tests: *testFlag, |
81 | } |
82 | |
83 | // Choose types.Sizes from conf.Build. |
84 | // TODO(adonovan): remove this when go/packages provides a better way. |
85 | var wordSize int64 = 8 |
86 | switch build.Default.GOARCH { |
87 | case "386", "arm": |
88 | wordSize = 4 |
89 | } |
90 | sizes := &types.StdSizes{ |
91 | MaxAlign: 8, |
92 | WordSize: wordSize, |
93 | } |
94 | |
95 | var interpMode interp.Mode |
96 | for _, c := range *interpFlag { |
97 | switch c { |
98 | case 'T': |
99 | interpMode |= interp.EnableTracing |
100 | case 'R': |
101 | interpMode |= interp.DisableRecover |
102 | default: |
103 | return fmt.Errorf("unknown -interp option: '%c'", c) |
104 | } |
105 | } |
106 | |
107 | // Profiling support. |
108 | if *cpuprofile != "" { |
109 | f, err := os.Create(*cpuprofile) |
110 | if err != nil { |
111 | fmt.Fprintln(os.Stderr, err) |
112 | os.Exit(1) |
113 | } |
114 | pprof.StartCPUProfile(f) |
115 | defer pprof.StopCPUProfile() |
116 | } |
117 | |
118 | // Load, parse and type-check the initial packages, |
119 | // and, if -run, their dependencies. |
120 | if *runFlag { |
121 | cfg.Mode = packages.LoadAllSyntax |
122 | } |
123 | initial, err := packages.Load(cfg, flag.Args()...) |
124 | if err != nil { |
125 | return err |
126 | } |
127 | if len(initial) == 0 { |
128 | return fmt.Errorf("no packages") |
129 | } |
130 | if packages.PrintErrors(initial) > 0 { |
131 | return fmt.Errorf("packages contain errors") |
132 | } |
133 | |
134 | // Turn on instantiating generics during build if the program will be run. |
135 | if *runFlag { |
136 | mode |= ssa.InstantiateGenerics |
137 | } |
138 | |
139 | // Create SSA-form program representation. |
140 | prog, pkgs := ssautil.AllPackages(initial, mode) |
141 | |
142 | for i, p := range pkgs { |
143 | if p == nil { |
144 | return fmt.Errorf("cannot build SSA for package %s", initial[i]) |
145 | } |
146 | } |
147 | |
148 | if !*runFlag { |
149 | // Build and display only the initial packages |
150 | // (and synthetic wrappers). |
151 | for _, p := range pkgs { |
152 | p.Build() |
153 | } |
154 | |
155 | } else { |
156 | // Run the interpreter. |
157 | // Build SSA for all packages. |
158 | prog.Build() |
159 | |
160 | // Earlier versions of the interpreter needed the runtime |
161 | // package; however, interp cannot handle unsafe constructs |
162 | // used during runtime's package initialization at the moment. |
163 | // The key construct blocking support is: |
164 | // *((*T)(unsafe.Pointer(p))) |
165 | // Unfortunately, this means only trivial programs can be |
166 | // interpreted by ssadump. |
167 | if prog.ImportedPackage("runtime") != nil { |
168 | return fmt.Errorf("-run: program depends on runtime package (interpreter can run only trivial programs)") |
169 | } |
170 | |
171 | if runtime.GOARCH != build.Default.GOARCH { |
172 | return fmt.Errorf("cross-interpretation is not supported (target has GOARCH %s, interpreter has %s)", |
173 | build.Default.GOARCH, runtime.GOARCH) |
174 | } |
175 | |
176 | // Run first main package. |
177 | for _, main := range ssautil.MainPackages(pkgs) { |
178 | fmt.Fprintf(os.Stderr, "Running: %s\n", main.Pkg.Path()) |
179 | os.Exit(interp.Interpret(main, interpMode, sizes, main.Pkg.Path(), args)) |
180 | } |
181 | return fmt.Errorf("no main package") |
182 | } |
183 | return nil |
184 | } |
185 | |
186 | // stringListValue is a flag.Value that accumulates strings. |
187 | // e.g. --flag=one --flag=two would produce []string{"one", "two"}. |
188 | type stringListValue []string |
189 | |
190 | func newStringListValue(val []string, p *[]string) *stringListValue { |
191 | *p = val |
192 | return (*stringListValue)(p) |
193 | } |
194 | |
195 | func (ss *stringListValue) Get() interface{} { return []string(*ss) } |
196 | |
197 | func (ss *stringListValue) String() string { return fmt.Sprintf("%q", *ss) } |
198 | |
199 | func (ss *stringListValue) Set(s string) error { *ss = append(*ss, s); return nil } |
200 |
Members