github.com/gidoBOSSftw5731/go/src@v0.0.0-20210226122457-d24b0edbf019/debug/dwarf/line_test.go (about)

     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 TestLineSeek(t *testing.T) {
   121  	d := elfData(t, "testdata/line-gcc.elf")
   122  
   123  	// Get the line table for the first CU.
   124  	cu, err := d.Reader().Next()
   125  	if err != nil {
   126  		t.Fatal("d.Reader().Next:", err)
   127  	}
   128  	lr, err := d.LineReader(cu)
   129  	if err != nil {
   130  		t.Fatal("d.LineReader:", err)
   131  	}
   132  
   133  	// Read entries forward.
   134  	var line LineEntry
   135  	var posTable []LineReaderPos
   136  	var table []LineEntry
   137  	for {
   138  		posTable = append(posTable, lr.Tell())
   139  
   140  		err := lr.Next(&line)
   141  		if err != nil {
   142  			if err == io.EOF {
   143  				break
   144  			}
   145  			t.Fatal("lr.Next:", err)
   146  		}
   147  		table = append(table, line)
   148  	}
   149  
   150  	// Test that Reset returns to the first line.
   151  	lr.Reset()
   152  	if err := lr.Next(&line); err != nil {
   153  		t.Fatal("lr.Next after Reset failed:", err)
   154  	} else if line != table[0] {
   155  		t.Fatal("lr.Next after Reset returned", line, "instead of", table[0])
   156  	}
   157  
   158  	// Check that entries match when seeking backward.
   159  	for i := len(posTable) - 1; i >= 0; i-- {
   160  		lr.Seek(posTable[i])
   161  		err := lr.Next(&line)
   162  		if i == len(posTable)-1 {
   163  			if err != io.EOF {
   164  				t.Fatal("expected io.EOF after seek to end, got", err)
   165  			}
   166  		} else if err != nil {
   167  			t.Fatal("lr.Next after seek to", posTable[i], "failed:", err)
   168  		} else if line != table[i] {
   169  			t.Fatal("lr.Next after seek to", posTable[i], "returned", line, "instead of", table[i])
   170  		}
   171  	}
   172  
   173  	// Check that seeking to a PC returns the right line.
   174  	if err := lr.SeekPC(table[0].Address-1, &line); err != ErrUnknownPC {
   175  		t.Fatalf("lr.SeekPC to %#x returned %v instead of ErrUnknownPC", table[0].Address-1, err)
   176  	}
   177  	for i, testLine := range table {
   178  		if testLine.EndSequence {
   179  			if err := lr.SeekPC(testLine.Address, &line); err != ErrUnknownPC {
   180  				t.Fatalf("lr.SeekPC to %#x returned %v instead of ErrUnknownPC", testLine.Address, err)
   181  			}
   182  			continue
   183  		}
   184  
   185  		nextPC := table[i+1].Address
   186  		for pc := testLine.Address; pc < nextPC; pc++ {
   187  			if err := lr.SeekPC(pc, &line); err != nil {
   188  				t.Fatalf("lr.SeekPC to %#x failed: %v", pc, err)
   189  			} else if line != testLine {
   190  				t.Fatalf("lr.SeekPC to %#x returned %v instead of %v", pc, line, testLine)
   191  			}
   192  		}
   193  	}
   194  }
   195  
   196  func testLineTable(t *testing.T, want []LineEntry, files [][]*LineFile, d *Data) {
   197  	// Get line table from d.
   198  	var got []LineEntry
   199  	dr := d.Reader()
   200  	for {
   201  		ent, err := dr.Next()
   202  		if err != nil {
   203  			t.Fatal("dr.Next:", err)
   204  		} else if ent == nil {
   205  			break
   206  		}
   207  
   208  		if ent.Tag != TagCompileUnit {
   209  			dr.SkipChildren()
   210  			continue
   211  		}
   212  
   213  		// Ignore system compilation units (this happens in
   214  		// the Windows binary). We'll still decode the line
   215  		// table, but won't check it.
   216  		name := ent.Val(AttrName).(string)
   217  		ignore := strings.HasPrefix(name, "C:/crossdev/") || strings.HasPrefix(name, "../../")
   218  
   219  		// Decode CU's line table.
   220  		lr, err := d.LineReader(ent)
   221  		if err != nil {
   222  			t.Fatal("d.LineReader:", err)
   223  		} else if lr == nil {
   224  			continue
   225  		}
   226  
   227  		for {
   228  			var line LineEntry
   229  			err := lr.Next(&line)
   230  			if err != nil {
   231  				if err == io.EOF {
   232  					break
   233  				}
   234  				t.Fatal("lr.Next:", err)
   235  			}
   236  			// Ignore sources from the Windows build environment.
   237  			if ignore {
   238  				continue
   239  			}
   240  			got = append(got, line)
   241  		}
   242  
   243  		// Check file table.
   244  		if !ignore {
   245  			if !compareFiles(files[0], lr.Files()) {
   246  				t.Log("File tables do not match. Got:")
   247  				dumpFiles(t, lr.Files())
   248  				t.Log("Want:")
   249  				dumpFiles(t, files[0])
   250  				t.Fail()
   251  			}
   252  			files = files[1:]
   253  		}
   254  	}
   255  
   256  	// Compare line tables.
   257  	if !compareLines(got, want) {
   258  		t.Log("Line tables do not match. Got:")
   259  		dumpLines(t, got)
   260  		t.Log("Want:")
   261  		dumpLines(t, want)
   262  		t.FailNow()
   263  	}
   264  }
   265  
   266  func compareFiles(a, b []*LineFile) bool {
   267  	if len(a) != len(b) {
   268  		return false
   269  	}
   270  	for i := range a {
   271  		if a[i] == nil && b[i] == nil {
   272  			continue
   273  		}
   274  		if a[i] != nil && b[i] != nil && a[i].Name == b[i].Name {
   275  			continue
   276  		}
   277  		return false
   278  	}
   279  	return true
   280  }
   281  
   282  func dumpFiles(t *testing.T, files []*LineFile) {
   283  	for i, f := range files {
   284  		name := "<nil>"
   285  		if f != nil {
   286  			name = f.Name
   287  		}
   288  		t.Logf("  %d %s", i, name)
   289  	}
   290  }
   291  
   292  func compareLines(a, b []LineEntry) bool {
   293  	if len(a) != len(b) {
   294  		return false
   295  	}
   296  
   297  	for i := range a {
   298  		al, bl := a[i], b[i]
   299  		// If both are EndSequence, then the only other valid
   300  		// field is Address. Otherwise, test equality of all
   301  		// fields.
   302  		if al.EndSequence && bl.EndSequence && al.Address == bl.Address {
   303  			continue
   304  		}
   305  		if al.File.Name != bl.File.Name {
   306  			return false
   307  		}
   308  		al.File = nil
   309  		bl.File = nil
   310  		if al != bl {
   311  			return false
   312  		}
   313  	}
   314  	return true
   315  }
   316  
   317  func dumpLines(t *testing.T, lines []LineEntry) {
   318  	for _, l := range lines {
   319  		t.Logf("  %+v File:%+v", l, l.File)
   320  	}
   321  }
   322  
   323  type joinTest struct {
   324  	dirname, filename string
   325  	path              string
   326  }
   327  
   328  var joinTests = []joinTest{
   329  	{"a", "b", "a/b"},
   330  	{"a", "", "a"},
   331  	{"", "b", "b"},
   332  	{"/a", "b", "/a/b"},
   333  	{"/a/", "b", "/a/b"},
   334  
   335  	{`C:\Windows\`, `System32`, `C:\Windows\System32`},
   336  	{`C:\Windows\`, ``, `C:\Windows\`},
   337  	{`C:\`, `Windows`, `C:\Windows`},
   338  	{`C:\Windows\`, `C:System32`, `C:\Windows\System32`},
   339  	{`C:\Windows`, `a/b`, `C:\Windows\a/b`},
   340  	{`\\host\share\`, `foo`, `\\host\share\foo`},
   341  	{`\\host\share\`, `foo\bar`, `\\host\share\foo\bar`},
   342  	{`//host/share/`, `foo/bar`, `//host/share/foo/bar`},
   343  
   344  	// Note: the Go compiler currently emits DWARF line table paths
   345  	// with '/' instead of '\' (see issues #19784, #36495). These
   346  	// tests are to cover cases that might come up for Windows Go
   347  	// binaries.
   348  	{`c:/workdir/go/src/x`, `y.go`, `c:/workdir/go/src/x/y.go`},
   349  	{`d:/some/thing/`, `b.go`, `d:/some/thing/b.go`},
   350  	{`e:\blah\`, `foo.c`, `e:\blah\foo.c`},
   351  
   352  	// The following are "best effort". We shouldn't see relative
   353  	// base directories in DWARF, but these test that pathJoin
   354  	// doesn't fail miserably if it sees one.
   355  	{`C:`, `a`, `C:a`},
   356  	{`C:`, `a\b`, `C:a\b`},
   357  	{`C:.`, `a`, `C:.\a`},
   358  	{`C:a`, `b`, `C:a\b`},
   359  }
   360  
   361  func TestPathJoin(t *testing.T) {
   362  	for _, test := range joinTests {
   363  		got := PathJoin(test.dirname, test.filename)
   364  		if test.path != got {
   365  			t.Errorf("pathJoin(%q, %q) = %q, want %q", test.dirname, test.filename, got, test.path)
   366  		}
   367  	}
   368  }