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