Source file
src/cmd/link/dwarf_test.go
1
2
3
4
5 package main
6
7 import (
8 "bytes"
9 cmddwarf "cmd/internal/dwarf"
10 "cmd/internal/objfile"
11 "cmd/internal/quoted"
12 "debug/dwarf"
13 "internal/platform"
14 "internal/testenv"
15 "os"
16 "os/exec"
17 "path"
18 "path/filepath"
19 "runtime"
20 "strings"
21 "testing"
22 )
23
24 func testDWARF(t *testing.T, buildmode string, expectDWARF bool, env ...string) {
25 testenv.MustHaveCGO(t)
26 testenv.MustHaveGoBuild(t)
27
28 if !platform.ExecutableHasDWARF(runtime.GOOS, runtime.GOARCH) {
29 t.Skipf("skipping on %s/%s: no DWARF symbol table in executables", runtime.GOOS, runtime.GOARCH)
30 }
31
32 t.Parallel()
33
34 for _, prog := range []string{"testprog", "testprogcgo"} {
35 prog := prog
36 expectDWARF := expectDWARF
37 if runtime.GOOS == "aix" && prog == "testprogcgo" {
38 extld := os.Getenv("CC")
39 if extld == "" {
40 extld = "gcc"
41 }
42 extldArgs, err := quoted.Split(extld)
43 if err != nil {
44 t.Fatal(err)
45 }
46 expectDWARF, err = cmddwarf.IsDWARFEnabledOnAIXLd(extldArgs)
47 if err != nil {
48 t.Fatal(err)
49 }
50 }
51
52 t.Run(prog, func(t *testing.T) {
53 t.Parallel()
54
55 tmpDir := t.TempDir()
56
57 exe := filepath.Join(tmpDir, prog+".exe")
58 dir := "../../runtime/testdata/" + prog
59 cmd := goCmd(t, "build", "-o", exe)
60 if buildmode != "" {
61 cmd.Args = append(cmd.Args, "-buildmode", buildmode)
62 }
63 cmd.Args = append(cmd.Args, dir)
64 cmd.Env = append(cmd.Env, env...)
65 cmd.Env = append(cmd.Env, "CGO_CFLAGS=")
66 out, err := cmd.CombinedOutput()
67 if err != nil {
68 t.Fatalf("go build -o %v %v: %v\n%s", exe, dir, err, out)
69 }
70
71 if buildmode == "c-archive" {
72
73 ar := os.Getenv("AR")
74 if ar == "" {
75 ar = "ar"
76 }
77 cmd := testenv.Command(t, ar, "-x", exe)
78 cmd.Dir = tmpDir
79 if out, err := cmd.CombinedOutput(); err != nil {
80 t.Fatalf("%s -x %s: %v\n%s", ar, exe, err, out)
81 }
82 exe = filepath.Join(tmpDir, "go.o")
83 }
84
85 darwinSymbolTestIsTooFlaky := true
86 if runtime.GOOS == "darwin" && !darwinSymbolTestIsTooFlaky {
87 if _, err = exec.LookPath("symbols"); err == nil {
88
89 out, err = testenv.Command(t, "symbols", exe).CombinedOutput()
90 if err != nil {
91 t.Fatalf("symbols %v: %v: %s", filepath.Base(exe), err, out)
92 } else {
93 if bytes.HasPrefix(out, []byte("Unable to find file")) {
94
95 t.Fatalf("symbols %v: failed to parse file", filepath.Base(exe))
96 } else if bytes.Contains(out, []byte(", Empty]")) {
97 t.Fatalf("symbols %v: parsed as empty", filepath.Base(exe))
98 }
99 }
100 }
101 }
102
103 f, err := objfile.Open(exe)
104 if err != nil {
105 t.Fatal(err)
106 }
107 defer f.Close()
108
109 syms, err := f.Symbols()
110 if err != nil {
111 t.Fatal(err)
112 }
113
114 var addr uint64
115 for _, sym := range syms {
116 if sym.Name == "main.main" {
117 addr = sym.Addr
118 break
119 }
120 }
121 if addr == 0 {
122 t.Fatal("cannot find main.main in symbols")
123 }
124
125 d, err := f.DWARF()
126 if err != nil {
127 if expectDWARF {
128 t.Fatal(err)
129 }
130 return
131 } else {
132 if !expectDWARF {
133 t.Fatal("unexpected DWARF section")
134 }
135 }
136
137
138
139 wantFile := path.Join(prog, "main.go")
140 wantLine := 24
141 r := d.Reader()
142 entry, err := r.SeekPC(addr)
143 if err != nil {
144 t.Fatal(err)
145 }
146 lr, err := d.LineReader(entry)
147 if err != nil {
148 t.Fatal(err)
149 }
150 var line dwarf.LineEntry
151 if err := lr.SeekPC(addr, &line); err == dwarf.ErrUnknownPC {
152 t.Fatalf("did not find file:line for %#x (main.main)", addr)
153 } else if err != nil {
154 t.Fatal(err)
155 }
156 if !strings.HasSuffix(line.File.Name, wantFile) || line.Line != wantLine {
157 t.Errorf("%#x is %s:%d, want %s:%d", addr, line.File.Name, line.Line, filepath.Join("...", wantFile), wantLine)
158 }
159
160 if buildmode != "c-archive" {
161 testModuledata(t, d)
162 }
163 })
164 }
165 }
166
167
168
169 func testModuledata(t *testing.T, d *dwarf.Data) {
170 const symName = "runtime.firstmoduledata"
171
172 r := d.Reader()
173 for {
174 e, err := r.Next()
175 if err != nil {
176 t.Error(err)
177 return
178 }
179 if e == nil {
180 t.Errorf("did not find DWARF entry for %s", symName)
181 return
182 }
183
184 switch e.Tag {
185 case dwarf.TagVariable:
186
187 case dwarf.TagCompileUnit, dwarf.TagSubprogram:
188 continue
189 default:
190 r.SkipChildren()
191 continue
192 }
193
194 nameIdx, typeIdx := -1, -1
195 for i := range e.Field {
196 f := &e.Field[i]
197 switch f.Attr {
198 case dwarf.AttrName:
199 nameIdx = i
200 case dwarf.AttrType:
201 typeIdx = i
202 }
203 }
204 if nameIdx == -1 {
205
206 r.SkipChildren()
207 continue
208 }
209 nameStr, ok := e.Field[nameIdx].Val.(string)
210 if !ok {
211
212 r.SkipChildren()
213 continue
214 }
215 if nameStr != symName {
216 r.SkipChildren()
217 continue
218 }
219
220 if typeIdx == -1 {
221 t.Errorf("%s has no DWARF type", symName)
222 return
223 }
224 off, ok := e.Field[typeIdx].Val.(dwarf.Offset)
225 if !ok {
226 t.Errorf("unexpected Go type %T for DWARF type for %s; expected %T", e.Field[typeIdx].Val, symName, dwarf.Offset(0))
227 return
228 }
229
230 typeInfo, err := d.Type(off)
231 if err != nil {
232 t.Error(err)
233 return
234 }
235
236 typeName := typeInfo.Common().Name
237 if want := "runtime.moduledata"; typeName != want {
238 t.Errorf("type of %s is %s, expected %s", symName, typeName, want)
239 }
240 for {
241 typedef, ok := typeInfo.(*dwarf.TypedefType)
242 if !ok {
243 break
244 }
245 typeInfo = typedef.Type
246 }
247 if _, ok := typeInfo.(*dwarf.StructType); !ok {
248 t.Errorf("type of %s is %T, expected %T", symName, typeInfo, dwarf.StructType{})
249 }
250
251 return
252 }
253 }
254
255 func TestDWARF(t *testing.T) {
256 testDWARF(t, "", true)
257 if !testing.Short() {
258 if runtime.GOOS == "windows" {
259 t.Skip("skipping Windows/c-archive; see Issue 35512 for more.")
260 }
261 if !platform.BuildModeSupported(runtime.Compiler, "c-archive", runtime.GOOS, runtime.GOARCH) {
262 t.Skipf("skipping c-archive test on unsupported platform %s-%s", runtime.GOOS, runtime.GOARCH)
263 }
264 t.Run("c-archive", func(t *testing.T) {
265 testDWARF(t, "c-archive", true)
266 })
267 }
268 }
269
270 func TestDWARFiOS(t *testing.T) {
271
272
273
274 if testing.Short() {
275 t.Skip("skipping in short mode")
276 }
277 if runtime.GOARCH != "amd64" || runtime.GOOS != "darwin" {
278 t.Skip("skipping on non-darwin/amd64 platform")
279 }
280 if err := testenv.Command(t, "xcrun", "--help").Run(); err != nil {
281 t.Skipf("error running xcrun, required for iOS cross build: %v", err)
282 }
283
284
285 if output, err := testenv.Command(t, "xcodebuild", "-showsdks").CombinedOutput(); err != nil {
286 t.Skipf("error running xcodebuild, required for iOS cross build: %v", err)
287 } else if !strings.Contains(string(output), "iOS SDK") {
288 t.Skipf("iOS SDK not detected.")
289 }
290 cc := "CC=" + runtime.GOROOT() + "/misc/ios/clangwrap.sh"
291
292 t.Run("exe", func(t *testing.T) {
293 testDWARF(t, "", false, cc, "CGO_ENABLED=1", "GOOS=ios", "GOARCH=arm64")
294 })
295
296 t.Run("c-archive", func(t *testing.T) {
297 testDWARF(t, "c-archive", true, cc, "CGO_ENABLED=1", "GOOS=ios", "GOARCH=arm64")
298 })
299 }
300
301
302
303
304
305
306
307
308
309 func TestDWARFLocationList(t *testing.T) {
310 if runtime.GOOS != "linux" {
311 t.Skip("skipping test on non-linux OS")
312 }
313 testenv.MustHaveCGO(t)
314 testenv.MustHaveGoBuild(t)
315
316 if !platform.ExecutableHasDWARF(runtime.GOOS, runtime.GOARCH) {
317 t.Skipf("skipping on %s/%s: no DWARF symbol table in executables", runtime.GOOS, runtime.GOARCH)
318 }
319
320 t.Parallel()
321
322 tmpDir := t.TempDir()
323 exe := filepath.Join(tmpDir, "issue65405.exe")
324 dir := "./testdata/dwarf/issue65405"
325
326 cmd := goCmd(t, "build", "-gcflags=all=-N -l", "-o", exe, dir)
327 cmd.Env = append(cmd.Env, "CGO_CFLAGS=")
328 out, err := cmd.CombinedOutput()
329 if err != nil {
330 t.Fatalf("go build -o %v %v: %v\n%s", exe, dir, err, out)
331 }
332
333 f, err := objfile.Open(exe)
334 if err != nil {
335 t.Fatal(err)
336 }
337 defer f.Close()
338
339 d, err := f.DWARF()
340 if err != nil {
341 t.Fatal(err)
342 }
343
344
345 reader := d.Reader()
346
347 for {
348 entry, err := reader.Next()
349 if err != nil {
350 t.Fatal(err)
351 }
352 if entry == nil {
353 break
354 }
355
356
357 if entry.Tag == dwarf.TagSubprogram {
358 fnName, ok := entry.Val(dwarf.AttrName).(string)
359 if !ok || fnName != "net.sendFile" {
360 reader.SkipChildren()
361 continue
362 }
363
364 for {
365 paramEntry, err := reader.Next()
366 if err != nil {
367 t.Fatal(err)
368 }
369 if paramEntry == nil || paramEntry.Tag == 0 {
370 break
371 }
372
373 if paramEntry.Tag == dwarf.TagFormalParameter {
374 paramName, _ := paramEntry.Val(dwarf.AttrName).(string)
375
376
377 if loc := paramEntry.Val(dwarf.AttrLocation); loc != nil {
378 switch locData := loc.(type) {
379 case []byte:
380 if len(locData) == 0 {
381 t.Errorf("%s return parameter %q has empty location list", fnName, paramName)
382 return
383 }
384 case int64:
385
386 if locData == 0 {
387 t.Errorf("%s return parameter %q has zero location list offset", fnName, paramName)
388 return
389 }
390 default:
391 t.Errorf("%s return parameter %q has unexpected location type %T: %v", fnName, paramName, locData, locData)
392 }
393 } else {
394 t.Errorf("%s return parameter %q has no location attribute", fnName, paramName)
395 }
396 }
397 }
398 }
399 }
400 }
401
402 func TestFlagW(t *testing.T) {
403 testenv.MustHaveGoBuild(t)
404 if runtime.GOOS == "aix" {
405 t.Skip("internal/xcoff cannot parse file without symbol table")
406 }
407 if !platform.ExecutableHasDWARF(runtime.GOOS, runtime.GOARCH) {
408 t.Skipf("skipping on %s/%s: no DWARF symbol table in executables", runtime.GOOS, runtime.GOARCH)
409 }
410
411 t.Parallel()
412
413 tmpdir := t.TempDir()
414 src := filepath.Join(tmpdir, "a.go")
415 err := os.WriteFile(src, []byte(helloSrc), 0666)
416 if err != nil {
417 t.Fatal(err)
418 }
419
420 type testCase struct {
421 flag string
422 wantDWARF bool
423 }
424 tests := []testCase{
425 {"-w", false},
426 {"-s", false},
427 {"-s -w=0", true},
428 }
429 if testenv.HasCGO() && runtime.GOOS != "solaris" {
430 tests = append(tests,
431 testCase{"-w -linkmode=external", false},
432 testCase{"-s -linkmode=external", false},
433
434
435
436
437 )
438 }
439
440 for _, test := range tests {
441 name := strings.ReplaceAll(test.flag, " ", "_")
442 t.Run(name, func(t *testing.T) {
443 ldflags := "-ldflags=" + test.flag
444 exe := filepath.Join(t.TempDir(), "a.exe")
445 cmd := goCmd(t, "build", ldflags, "-o", exe, src)
446 out, err := cmd.CombinedOutput()
447 if err != nil {
448 t.Fatalf("build failed: %v\n%s", err, out)
449 }
450
451 f, err := objfile.Open(exe)
452 if err != nil {
453 t.Fatal(err)
454 }
455 defer f.Close()
456
457 d, err := f.DWARF()
458 if test.wantDWARF {
459 if err != nil {
460 t.Errorf("want binary with DWARF, got error %v", err)
461 }
462 } else {
463 if d != nil {
464 t.Errorf("want binary with no DWARF, got DWARF")
465 }
466 }
467 })
468 }
469 }
470
View as plain text