Source file src/debug/dwarf/line_test.go

     1  // Copyright 2015 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  package dwarf_test
     6  
     7  import (
     8  	. "debug/dwarf"
     9  	"io"
    10  	"strings"
    11  	"testing"
    12  )
    13  
    14  var (
    15  	file1C = &LineFile{Name: "/home/austin/go.dev/src/debug/dwarf/testdata/line1.c"}
    16  	file1H = &LineFile{Name: "/home/austin/go.dev/src/debug/dwarf/testdata/line1.h"}
    17  	file2C = &LineFile{Name: "/home/austin/go.dev/src/debug/dwarf/testdata/line2.c"}
    18  )
    19  
    20  func TestLineELFGCC(t *testing.T) {
    21  	// Generated by:
    22  	//   # gcc --version | head -n1
    23  	//   gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2
    24  	//   # gcc -g -o line-gcc.elf line*.c
    25  
    26  	// Line table based on readelf --debug-dump=rawline,decodedline
    27  	want := []LineEntry{
    28  		{Address: 0x40059d, File: file1H, Line: 2, IsStmt: true},
    29  		{Address: 0x4005a5, File: file1H, Line: 2, IsStmt: true},
    30  		{Address: 0x4005b4, File: file1H, Line: 5, IsStmt: true},
    31  		{Address: 0x4005bd, File: file1H, Line: 6, IsStmt: true, Discriminator: 2},
    32  		{Address: 0x4005c7, File: file1H, Line: 5, IsStmt: true, Discriminator: 2},
    33  		{Address: 0x4005cb, File: file1H, Line: 5, IsStmt: false, Discriminator: 1},
    34  		{Address: 0x4005d1, File: file1H, Line: 7, IsStmt: true},
    35  		{Address: 0x4005e7, File: file1C, Line: 6, IsStmt: true},
    36  		{Address: 0x4005eb, File: file1C, Line: 7, IsStmt: true},
    37  		{Address: 0x4005f5, File: file1C, Line: 8, IsStmt: true},
    38  		{Address: 0x4005ff, File: file1C, Line: 9, IsStmt: true},
    39  		{Address: 0x400601, EndSequence: true},
    40  
    41  		{Address: 0x400601, File: file2C, Line: 4, IsStmt: true},
    42  		{Address: 0x400605, File: file2C, Line: 5, IsStmt: true},
    43  		{Address: 0x40060f, File: file2C, Line: 6, IsStmt: true},
    44  		{Address: 0x400611, EndSequence: true},
    45  	}
    46  	files := [][]*LineFile{{nil, file1H, file1C}, {nil, file2C}}
    47  
    48  	testLineTable(t, want, files, elfData(t, "testdata/line-gcc.elf"))
    49  }
    50  
    51  func TestLineELFGCCZstd(t *testing.T) {
    52  	// Generated by:
    53  	//   # gcc --version | head -n1
    54  	//   gcc (Debian 12.2.0-10) 12.2.0
    55  	//   # gcc -g -no-pie -Wl,--compress-debug-sections=zstd line*.c
    56  
    57  	zfile1H := &LineFile{Name: "/home/iant/go/src/debug/dwarf/testdata/line1.h"}
    58  	zfile1C := &LineFile{Name: "/home/iant/go/src/debug/dwarf/testdata/line1.c"}
    59  	zfile2C := &LineFile{Name: "/home/iant/go/src/debug/dwarf/testdata/line2.c"}
    60  
    61  	// Line table based on readelf --debug-dump=rawline,decodedline
    62  	want := []LineEntry{
    63  		{Address: 0x401126, File: zfile1H, Line: 2, Column: 1, IsStmt: true},
    64  		{Address: 0x40112a, File: zfile1H, Line: 5, Column: 8, IsStmt: true},
    65  		{Address: 0x401131, File: zfile1H, Line: 5, Column: 2, IsStmt: true},
    66  		{Address: 0x401133, File: zfile1H, Line: 6, Column: 10, IsStmt: true, Discriminator: 3},
    67  		{Address: 0x40113d, File: zfile1H, Line: 5, Column: 22, IsStmt: true, Discriminator: 3},
    68  		{Address: 0x401141, File: zfile1H, Line: 5, Column: 15, IsStmt: true, Discriminator: 1},
    69  		{Address: 0x401147, File: zfile1H, Line: 7, Column: 1, IsStmt: true},
    70  		{Address: 0x40114b, File: zfile1C, Line: 6, Column: 1, IsStmt: true},
    71  		{Address: 0x40114f, File: zfile1C, Line: 7, Column: 2, IsStmt: true},
    72  		{Address: 0x401159, File: zfile1C, Line: 8, Column: 2, IsStmt: true},
    73  		{Address: 0x401168, File: zfile1C, Line: 9, Column: 1, IsStmt: true},
    74  		{Address: 0x40116a, EndSequence: true},
    75  
    76  		{Address: 0x40116a, File: zfile2C, Line: 4, Column: 1, IsStmt: true},
    77  		{Address: 0x40116e, File: zfile2C, Line: 5, Column: 2, IsStmt: true},
    78  		{Address: 0x40117d, File: zfile2C, Line: 6, Column: 1, IsStmt: true},
    79  		{Address: 0x401180, EndSequence: true},
    80  	}
    81  	files := [][]*LineFile{
    82  		{zfile1C, zfile1H, zfile1C},
    83  		{zfile2C, zfile2C},
    84  	}
    85  
    86  	testLineTable(t, want, files, elfData(t, "testdata/line-gcc-zstd.elf"))
    87  }
    88  
    89  func TestLineGCCWindows(t *testing.T) {
    90  	// Generated by:
    91  	//   > gcc --version
    92  	//   gcc (tdm64-1) 4.9.2
    93  	//   > gcc -g -o line-gcc-win.bin line1.c C:\workdir\go\src\debug\dwarf\testdata\line2.c
    94  
    95  	toWindows := func(lf *LineFile) *LineFile {
    96  		lf2 := *lf
    97  		lf2.Name = strings.Replace(lf2.Name, "/home/austin/go.dev/", "C:\\workdir\\go\\", -1)
    98  		lf2.Name = strings.Replace(lf2.Name, "/", "\\", -1)
    99  		return &lf2
   100  	}
   101  	file1C := toWindows(file1C)
   102  	file1H := toWindows(file1H)
   103  	file2C := toWindows(file2C)
   104  
   105  	// Line table based on objdump --dwarf=rawline,decodedline
   106  	want := []LineEntry{
   107  		{Address: 0x401530, File: file1H, Line: 2, IsStmt: true},
   108  		{Address: 0x401538, File: file1H, Line: 5, IsStmt: true},
   109  		{Address: 0x401541, File: file1H, Line: 6, IsStmt: true, Discriminator: 3},
   110  		{Address: 0x40154b, File: file1H, Line: 5, IsStmt: true, Discriminator: 3},
   111  		{Address: 0x40154f, File: file1H, Line: 5, IsStmt: false, Discriminator: 1},
   112  		{Address: 0x401555, File: file1H, Line: 7, IsStmt: true},
   113  		{Address: 0x40155b, File: file1C, Line: 6, IsStmt: true},
   114  		{Address: 0x401563, File: file1C, Line: 6, IsStmt: true},
   115  		{Address: 0x401568, File: file1C, Line: 7, IsStmt: true},
   116  		{Address: 0x40156d, File: file1C, Line: 8, IsStmt: true},
   117  		{Address: 0x401572, File: file1C, Line: 9, IsStmt: true},
   118  		{Address: 0x401578, EndSequence: true},
   119  
   120  		{Address: 0x401580, File: file2C, Line: 4, IsStmt: true},
   121  		{Address: 0x401588, File: file2C, Line: 5, IsStmt: true},
   122  		{Address: 0x401595, File: file2C, Line: 6, IsStmt: true},
   123  		{Address: 0x40159b, EndSequence: true},
   124  	}
   125  	files := [][]*LineFile{{nil, file1H, file1C}, {nil, file2C}}
   126  
   127  	testLineTable(t, want, files, peData(t, "testdata/line-gcc-win.bin"))
   128  }
   129  
   130  func TestLineELFClang(t *testing.T) {
   131  	// Generated by:
   132  	//   # clang --version | head -n1
   133  	//   Ubuntu clang version 3.4-1ubuntu3 (tags/RELEASE_34/final) (based on LLVM 3.4)
   134  	//   # clang -g -o line-clang.elf line*.
   135  
   136  	want := []LineEntry{
   137  		{Address: 0x400530, File: file1C, Line: 6, IsStmt: true},
   138  		{Address: 0x400534, File: file1C, Line: 7, IsStmt: true, PrologueEnd: true},
   139  		{Address: 0x400539, File: file1C, Line: 8, IsStmt: true},
   140  		{Address: 0x400545, File: file1C, Line: 9, IsStmt: true},
   141  		{Address: 0x400550, File: file1H, Line: 2, IsStmt: true},
   142  		{Address: 0x400554, File: file1H, Line: 5, IsStmt: true, PrologueEnd: true},
   143  		{Address: 0x400568, File: file1H, Line: 6, IsStmt: true},
   144  		{Address: 0x400571, File: file1H, Line: 5, IsStmt: true},
   145  		{Address: 0x400581, File: file1H, Line: 7, IsStmt: true},
   146  		{Address: 0x400583, EndSequence: true},
   147  
   148  		{Address: 0x400590, File: file2C, Line: 4, IsStmt: true},
   149  		{Address: 0x4005a0, File: file2C, Line: 5, IsStmt: true, PrologueEnd: true},
   150  		{Address: 0x4005a7, File: file2C, Line: 6, IsStmt: true},
   151  		{Address: 0x4005b0, EndSequence: true},
   152  	}
   153  	files := [][]*LineFile{{nil, file1C, file1H}, {nil, file2C}}
   154  
   155  	testLineTable(t, want, files, elfData(t, "testdata/line-clang.elf"))
   156  }
   157  
   158  func TestLineRnglists(t *testing.T) {
   159  	// Test a newer file, generated by clang.
   160  	file := &LineFile{Name: "/usr/local/google/home/iant/foo.c"}
   161  	want := []LineEntry{
   162  		{Address: 0x401020, File: file, Line: 12, IsStmt: true},
   163  		{Address: 0x401020, File: file, Line: 13, Column: 12, IsStmt: true, PrologueEnd: true},
   164  		{Address: 0x401022, File: file, Line: 13, Column: 7},
   165  		{Address: 0x401024, File: file, Line: 17, Column: 1, IsStmt: true},
   166  		{Address: 0x401027, File: file, Line: 16, Column: 10, IsStmt: true},
   167  		{Address: 0x40102c, EndSequence: true},
   168  		{Address: 0x401000, File: file, Line: 2, IsStmt: true},
   169  		{Address: 0x401000, File: file, Line: 6, Column: 17, IsStmt: true, PrologueEnd: true},
   170  		{Address: 0x401002, File: file, Line: 6, Column: 3},
   171  		{Address: 0x401019, File: file, Line: 9, Column: 3, IsStmt: true},
   172  		{Address: 0x40101a, File: file, Line: 0, Column: 3},
   173  		{Address: 0x40101c, File: file, Line: 9, Column: 3},
   174  		{Address: 0x40101d, EndSequence: true},
   175  	}
   176  	files := [][]*LineFile{{file}}
   177  
   178  	testLineTable(t, want, files, elfData(t, "testdata/rnglistx.elf"))
   179  }
   180  
   181  func TestLineSeek(t *testing.T) {
   182  	d := elfData(t, "testdata/line-gcc.elf")
   183  
   184  	// Get the line table for the first CU.
   185  	cu, err := d.Reader().Next()
   186  	if err != nil {
   187  		t.Fatal("d.Reader().Next:", err)
   188  	}
   189  	lr, err := d.LineReader(cu)
   190  	if err != nil {
   191  		t.Fatal("d.LineReader:", err)
   192  	}
   193  
   194  	// Read entries forward.
   195  	var line LineEntry
   196  	var posTable []LineReaderPos
   197  	var table []LineEntry
   198  	for {
   199  		posTable = append(posTable, lr.Tell())
   200  
   201  		err := lr.Next(&line)
   202  		if err != nil {
   203  			if err == io.EOF {
   204  				break
   205  			}
   206  			t.Fatal("lr.Next:", err)
   207  		}
   208  		table = append(table, line)
   209  	}
   210  
   211  	// Test that Reset returns to the first line.
   212  	lr.Reset()
   213  	if err := lr.Next(&line); err != nil {
   214  		t.Fatal("lr.Next after Reset failed:", err)
   215  	} else if line != table[0] {
   216  		t.Fatal("lr.Next after Reset returned", line, "instead of", table[0])
   217  	}
   218  
   219  	// Check that entries match when seeking backward.
   220  	for i := len(posTable) - 1; i >= 0; i-- {
   221  		lr.Seek(posTable[i])
   222  		err := lr.Next(&line)
   223  		if i == len(posTable)-1 {
   224  			if err != io.EOF {
   225  				t.Fatal("expected io.EOF after seek to end, got", err)
   226  			}
   227  		} else if err != nil {
   228  			t.Fatal("lr.Next after seek to", posTable[i], "failed:", err)
   229  		} else if line != table[i] {
   230  			t.Fatal("lr.Next after seek to", posTable[i], "returned", line, "instead of", table[i])
   231  		}
   232  	}
   233  
   234  	// Check that seeking to a PC returns the right line.
   235  	if err := lr.SeekPC(table[0].Address-1, &line); err != ErrUnknownPC {
   236  		t.Fatalf("lr.SeekPC to %#x returned %v instead of ErrUnknownPC", table[0].Address-1, err)
   237  	}
   238  	for i, testLine := range table {
   239  		if testLine.EndSequence {
   240  			if err := lr.SeekPC(testLine.Address, &line); err != ErrUnknownPC {
   241  				t.Fatalf("lr.SeekPC to %#x returned %v instead of ErrUnknownPC", testLine.Address, err)
   242  			}
   243  			continue
   244  		}
   245  
   246  		nextPC := table[i+1].Address
   247  		for pc := testLine.Address; pc < nextPC; pc++ {
   248  			if err := lr.SeekPC(pc, &line); err != nil {
   249  				t.Fatalf("lr.SeekPC to %#x failed: %v", pc, err)
   250  			} else if line != testLine {
   251  				t.Fatalf("lr.SeekPC to %#x returned %v instead of %v", pc, line, testLine)
   252  			}
   253  		}
   254  	}
   255  }
   256  
   257  func testLineTable(t *testing.T, want []LineEntry, files [][]*LineFile, d *Data) {
   258  	// Get line table from d.
   259  	var got []LineEntry
   260  	dr := d.Reader()
   261  	for {
   262  		ent, err := dr.Next()
   263  		if err != nil {
   264  			t.Fatal("dr.Next:", err)
   265  		} else if ent == nil {
   266  			break
   267  		}
   268  
   269  		if ent.Tag != TagCompileUnit {
   270  			dr.SkipChildren()
   271  			continue
   272  		}
   273  
   274  		// Ignore system compilation units (this happens in
   275  		// the Windows binary). We'll still decode the line
   276  		// table, but won't check it.
   277  		name := ent.Val(AttrName).(string)
   278  		ignore := strings.HasPrefix(name, "C:/crossdev/") || strings.HasPrefix(name, "../../")
   279  
   280  		// Decode CU's line table.
   281  		lr, err := d.LineReader(ent)
   282  		if err != nil {
   283  			t.Fatal("d.LineReader:", err)
   284  		} else if lr == nil {
   285  			continue
   286  		}
   287  
   288  		for {
   289  			var line LineEntry
   290  			err := lr.Next(&line)
   291  			if err != nil {
   292  				if err == io.EOF {
   293  					break
   294  				}
   295  				t.Fatal("lr.Next:", err)
   296  			}
   297  			// Ignore sources from the Windows build environment.
   298  			if ignore {
   299  				continue
   300  			}
   301  			got = append(got, line)
   302  		}
   303  
   304  		// Check file table.
   305  		if !ignore {
   306  			if !compareFiles(files[0], lr.Files()) {
   307  				t.Log("File tables do not match. Got:")
   308  				dumpFiles(t, lr.Files())
   309  				t.Log("Want:")
   310  				dumpFiles(t, files[0])
   311  				t.Fail()
   312  			}
   313  			files = files[1:]
   314  		}
   315  	}
   316  
   317  	// Compare line tables.
   318  	if !compareLines(t, got, want) {
   319  		t.Log("Line tables do not match. Got:")
   320  		dumpLines(t, got)
   321  		t.Log("Want:")
   322  		dumpLines(t, want)
   323  		t.FailNow()
   324  	}
   325  }
   326  
   327  func compareFiles(a, b []*LineFile) bool {
   328  	if len(a) != len(b) {
   329  		return false
   330  	}
   331  	for i := range a {
   332  		if a[i] == nil && b[i] == nil {
   333  			continue
   334  		}
   335  		if a[i] != nil && b[i] != nil && a[i].Name == b[i].Name {
   336  			continue
   337  		}
   338  		return false
   339  	}
   340  	return true
   341  }
   342  
   343  func dumpFiles(t *testing.T, files []*LineFile) {
   344  	for i, f := range files {
   345  		name := "<nil>"
   346  		if f != nil {
   347  			name = f.Name
   348  		}
   349  		t.Logf("  %d %s", i, name)
   350  	}
   351  }
   352  
   353  func compareLines(t *testing.T, a, b []LineEntry) bool {
   354  	t.Helper()
   355  	if len(a) != len(b) {
   356  		t.Errorf("len(a) == %d, len(b) == %d", len(a), len(b))
   357  		return false
   358  	}
   359  
   360  	for i := range a {
   361  		al, bl := a[i], b[i]
   362  		// If both are EndSequence, then the only other valid
   363  		// field is Address. Otherwise, test equality of all
   364  		// fields.
   365  		if al.EndSequence && bl.EndSequence && al.Address == bl.Address {
   366  			continue
   367  		}
   368  		if al.File.Name != bl.File.Name {
   369  			t.Errorf("%d: name %v != name %v", i, al.File.Name, bl.File.Name)
   370  			return false
   371  		}
   372  		al.File = nil
   373  		bl.File = nil
   374  		if al != bl {
   375  			t.Errorf("%d: %#v != %#v", i, al, bl)
   376  			return false
   377  		}
   378  	}
   379  	return true
   380  }
   381  
   382  func dumpLines(t *testing.T, lines []LineEntry) {
   383  	for _, l := range lines {
   384  		t.Logf("  %+v File:%+v", l, l.File)
   385  	}
   386  }
   387  
   388  type joinTest struct {
   389  	dirname, filename string
   390  	path              string
   391  }
   392  
   393  var joinTests = []joinTest{
   394  	{"a", "b", "a/b"},
   395  	{"a", "", "a"},
   396  	{"", "b", "b"},
   397  	{"/a", "b", "/a/b"},
   398  	{"/a/", "b", "/a/b"},
   399  
   400  	{`C:\Windows\`, `System32`, `C:\Windows\System32`},
   401  	{`C:\Windows\`, ``, `C:\Windows\`},
   402  	{`C:\`, `Windows`, `C:\Windows`},
   403  	{`C:\Windows\`, `C:System32`, `C:\Windows\System32`},
   404  	{`C:\Windows`, `a/b`, `C:\Windows\a/b`},
   405  	{`\\host\share\`, `foo`, `\\host\share\foo`},
   406  	{`\\host\share\`, `foo\bar`, `\\host\share\foo\bar`},
   407  	{`//host/share/`, `foo/bar`, `//host/share/foo/bar`},
   408  
   409  	// Note: the Go compiler currently emits DWARF line table paths
   410  	// with '/' instead of '\' (see issues #19784, #36495). These
   411  	// tests are to cover cases that might come up for Windows Go
   412  	// binaries.
   413  	{`c:/workdir/go/src/x`, `y.go`, `c:/workdir/go/src/x/y.go`},
   414  	{`d:/some/thing/`, `b.go`, `d:/some/thing/b.go`},
   415  	{`e:\blah\`, `foo.c`, `e:\blah\foo.c`},
   416  
   417  	// The following are "best effort". We shouldn't see relative
   418  	// base directories in DWARF, but these test that pathJoin
   419  	// doesn't fail miserably if it sees one.
   420  	{`C:`, `a`, `C:a`},
   421  	{`C:`, `a\b`, `C:a\b`},
   422  	{`C:.`, `a`, `C:.\a`},
   423  	{`C:a`, `b`, `C:a\b`},
   424  }
   425  
   426  func TestPathJoin(t *testing.T) {
   427  	for _, test := range joinTests {
   428  		got := PathJoin(test.dirname, test.filename)
   429  		if test.path != got {
   430  			t.Errorf("pathJoin(%q, %q) = %q, want %q", test.dirname, test.filename, got, test.path)
   431  		}
   432  	}
   433  }
   434  
   435  func TestPathLineReaderMalformed(t *testing.T) {
   436  	// This test case drawn from issue #52354. What's happening
   437  	// here is that the stmtList attribute in the compilation
   438  	// unit is malformed (negative).
   439  	var aranges, frame, pubnames, ranges, str []byte
   440  	abbrev := []byte{0x10, 0x20, 0x20, 0x20, 0x21, 0x20, 0x10, 0x21, 0x61,
   441  		0x0, 0x0, 0xff, 0x20, 0xff, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
   442  		0x20, 0x20, 0x20, 0x20, 0x20, 0x20}
   443  	info := []byte{0x0, 0x0, 0x0, 0x9, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0,
   444  		0x20, 0x10, 0x10}
   445  	line := []byte{0x20}
   446  	Data0, err := New(abbrev, aranges, frame, info, line, pubnames, ranges, str)
   447  	if err != nil {
   448  		t.Fatalf("error unexpected: %v", err)
   449  	}
   450  	Reader0 := Data0.Reader()
   451  	Entry0, err := Reader0.Next()
   452  	if err != nil {
   453  		t.Fatalf("error unexpected: %v", err)
   454  	}
   455  	LineReader0, err := Data0.LineReader(Entry0)
   456  	if err == nil {
   457  		t.Fatalf("expected error")
   458  	}
   459  	if LineReader0 != nil {
   460  		t.Fatalf("expected nil line reader")
   461  	}
   462  }
   463  

View as plain text