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

View as plain text