github.com/flyinox/gosm@v0.0.0-20171117061539-16768cb62077/src/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  
    47  	testLineTable(t, want, elfData(t, "testdata/line-gcc.elf"))
    48  }
    49  
    50  func TestLineGCCWindows(t *testing.T) {
    51  	// Generated by:
    52  	//   > gcc --version
    53  	//   gcc (tdm64-1) 4.9.2
    54  	//   > gcc -g -o line-gcc-win.bin line1.c C:\workdir\go\src\debug\dwarf\testdata\line2.c
    55  
    56  	toWindows := func(lf *LineFile) *LineFile {
    57  		lf2 := *lf
    58  		lf2.Name = strings.Replace(lf2.Name, "/home/austin/go.dev/", "C:\\workdir\\go\\", -1)
    59  		lf2.Name = strings.Replace(lf2.Name, "/", "\\", -1)
    60  		return &lf2
    61  	}
    62  	file1C := toWindows(file1C)
    63  	file1H := toWindows(file1H)
    64  	file2C := toWindows(file2C)
    65  
    66  	// Line table based on objdump --dwarf=rawline,decodedline
    67  	want := []LineEntry{
    68  		{Address: 0x401530, File: file1H, Line: 2, IsStmt: true},
    69  		{Address: 0x401538, File: file1H, Line: 5, IsStmt: true},
    70  		{Address: 0x401541, File: file1H, Line: 6, IsStmt: true, Discriminator: 3},
    71  		{Address: 0x40154b, File: file1H, Line: 5, IsStmt: true, Discriminator: 3},
    72  		{Address: 0x40154f, File: file1H, Line: 5, IsStmt: false, Discriminator: 1},
    73  		{Address: 0x401555, File: file1H, Line: 7, IsStmt: true},
    74  		{Address: 0x40155b, File: file1C, Line: 6, IsStmt: true},
    75  		{Address: 0x401563, File: file1C, Line: 6, IsStmt: true},
    76  		{Address: 0x401568, File: file1C, Line: 7, IsStmt: true},
    77  		{Address: 0x40156d, File: file1C, Line: 8, IsStmt: true},
    78  		{Address: 0x401572, File: file1C, Line: 9, IsStmt: true},
    79  		{Address: 0x401578, EndSequence: true},
    80  
    81  		{Address: 0x401580, File: file2C, Line: 4, IsStmt: true},
    82  		{Address: 0x401588, File: file2C, Line: 5, IsStmt: true},
    83  		{Address: 0x401595, File: file2C, Line: 6, IsStmt: true},
    84  		{Address: 0x40159b, EndSequence: true},
    85  	}
    86  
    87  	testLineTable(t, want, peData(t, "testdata/line-gcc-win.bin"))
    88  }
    89  
    90  func TestLineELFClang(t *testing.T) {
    91  	// Generated by:
    92  	//   # clang --version | head -n1
    93  	//   Ubuntu clang version 3.4-1ubuntu3 (tags/RELEASE_34/final) (based on LLVM 3.4)
    94  	//   # clang -g -o line-clang.elf line*.
    95  
    96  	want := []LineEntry{
    97  		{Address: 0x400530, File: file1C, Line: 6, IsStmt: true},
    98  		{Address: 0x400534, File: file1C, Line: 7, IsStmt: true, PrologueEnd: true},
    99  		{Address: 0x400539, File: file1C, Line: 8, IsStmt: true},
   100  		{Address: 0x400545, File: file1C, Line: 9, IsStmt: true},
   101  		{Address: 0x400550, File: file1H, Line: 2, IsStmt: true},
   102  		{Address: 0x400554, File: file1H, Line: 5, IsStmt: true, PrologueEnd: true},
   103  		{Address: 0x400568, File: file1H, Line: 6, IsStmt: true},
   104  		{Address: 0x400571, File: file1H, Line: 5, IsStmt: true},
   105  		{Address: 0x400581, File: file1H, Line: 7, IsStmt: true},
   106  		{Address: 0x400583, EndSequence: true},
   107  
   108  		{Address: 0x400590, File: file2C, Line: 4, IsStmt: true},
   109  		{Address: 0x4005a0, File: file2C, Line: 5, IsStmt: true, PrologueEnd: true},
   110  		{Address: 0x4005a7, File: file2C, Line: 6, IsStmt: true},
   111  		{Address: 0x4005b0, EndSequence: true},
   112  	}
   113  
   114  	testLineTable(t, want, elfData(t, "testdata/line-clang.elf"))
   115  }
   116  
   117  func TestLineSeek(t *testing.T) {
   118  	d := elfData(t, "testdata/line-gcc.elf")
   119  
   120  	// Get the line table for the first CU.
   121  	cu, err := d.Reader().Next()
   122  	if err != nil {
   123  		t.Fatal("d.Reader().Next:", err)
   124  	}
   125  	lr, err := d.LineReader(cu)
   126  	if err != nil {
   127  		t.Fatal("d.LineReader:", err)
   128  	}
   129  
   130  	// Read entries forward.
   131  	var line LineEntry
   132  	var posTable []LineReaderPos
   133  	var table []LineEntry
   134  	for {
   135  		posTable = append(posTable, lr.Tell())
   136  
   137  		err := lr.Next(&line)
   138  		if err != nil {
   139  			if err == io.EOF {
   140  				break
   141  			}
   142  			t.Fatal("lr.Next:", err)
   143  		}
   144  		table = append(table, line)
   145  	}
   146  
   147  	// Test that Reset returns to the first line.
   148  	lr.Reset()
   149  	if err := lr.Next(&line); err != nil {
   150  		t.Fatal("lr.Next after Reset failed:", err)
   151  	} else if line != table[0] {
   152  		t.Fatal("lr.Next after Reset returned", line, "instead of", table[0])
   153  	}
   154  
   155  	// Check that entries match when seeking backward.
   156  	for i := len(posTable) - 1; i >= 0; i-- {
   157  		lr.Seek(posTable[i])
   158  		err := lr.Next(&line)
   159  		if i == len(posTable)-1 {
   160  			if err != io.EOF {
   161  				t.Fatal("expected io.EOF after seek to end, got", err)
   162  			}
   163  		} else if err != nil {
   164  			t.Fatal("lr.Next after seek to", posTable[i], "failed:", err)
   165  		} else if line != table[i] {
   166  			t.Fatal("lr.Next after seek to", posTable[i], "returned", line, "instead of", table[i])
   167  		}
   168  	}
   169  
   170  	// Check that seeking to a PC returns the right line.
   171  	if err := lr.SeekPC(table[0].Address-1, &line); err != ErrUnknownPC {
   172  		t.Fatalf("lr.SeekPC to %#x returned %v instead of ErrUnknownPC", table[0].Address-1, err)
   173  	}
   174  	for i, testLine := range table {
   175  		if testLine.EndSequence {
   176  			if err := lr.SeekPC(testLine.Address, &line); err != ErrUnknownPC {
   177  				t.Fatalf("lr.SeekPC to %#x returned %v instead of ErrUnknownPC", testLine.Address, err)
   178  			}
   179  			continue
   180  		}
   181  
   182  		nextPC := table[i+1].Address
   183  		for pc := testLine.Address; pc < nextPC; pc++ {
   184  			if err := lr.SeekPC(pc, &line); err != nil {
   185  				t.Fatalf("lr.SeekPC to %#x failed: %v", pc, err)
   186  			} else if line != testLine {
   187  				t.Fatalf("lr.SeekPC to %#x returned %v instead of %v", pc, line, testLine)
   188  			}
   189  		}
   190  	}
   191  }
   192  
   193  func testLineTable(t *testing.T, want []LineEntry, d *Data) {
   194  	// Get line table from d.
   195  	var got []LineEntry
   196  	dr := d.Reader()
   197  	for {
   198  		ent, err := dr.Next()
   199  		if err != nil {
   200  			t.Fatal("dr.Next:", err)
   201  		} else if ent == nil {
   202  			break
   203  		}
   204  
   205  		if ent.Tag != TagCompileUnit {
   206  			dr.SkipChildren()
   207  			continue
   208  		}
   209  
   210  		// Decode CU's line table.
   211  		lr, err := d.LineReader(ent)
   212  		if err != nil {
   213  			t.Fatal("d.LineReader:", err)
   214  		} else if lr == nil {
   215  			continue
   216  		}
   217  
   218  		for {
   219  			var line LineEntry
   220  			err := lr.Next(&line)
   221  			if err != nil {
   222  				if err == io.EOF {
   223  					break
   224  				}
   225  				t.Fatal("lr.Next:", err)
   226  			}
   227  			// Ignore sources from the Windows build environment.
   228  			if strings.HasPrefix(line.File.Name, "C:\\crossdev\\") ||
   229  				strings.HasPrefix(line.File.Name, "C:/crossdev/") {
   230  				continue
   231  			}
   232  			got = append(got, line)
   233  		}
   234  	}
   235  
   236  	// Compare line tables.
   237  	if !compareLines(got, want) {
   238  		t.Log("Line tables do not match. Got:")
   239  		dumpLines(t, got)
   240  		t.Log("Want:")
   241  		dumpLines(t, want)
   242  		t.FailNow()
   243  	}
   244  }
   245  
   246  func compareLines(a, b []LineEntry) bool {
   247  	if len(a) != len(b) {
   248  		return false
   249  	}
   250  
   251  	for i := range a {
   252  		al, bl := a[i], b[i]
   253  		// If both are EndSequence, then the only other valid
   254  		// field is Address. Otherwise, test equality of all
   255  		// fields.
   256  		if al.EndSequence && bl.EndSequence && al.Address == bl.Address {
   257  			continue
   258  		}
   259  		if al.File.Name != bl.File.Name {
   260  			return false
   261  		}
   262  		al.File = nil
   263  		bl.File = nil
   264  		if al != bl {
   265  			return false
   266  		}
   267  	}
   268  	return true
   269  }
   270  
   271  func dumpLines(t *testing.T, lines []LineEntry) {
   272  	for _, l := range lines {
   273  		t.Logf("  %+v File:%+v", l, l.File)
   274  	}
   275  }
   276  
   277  type joinTest struct {
   278  	dirname, filename string
   279  	path              string
   280  }
   281  
   282  var joinTests = []joinTest{
   283  	{"a", "b", "a/b"},
   284  	{"a", "", "a"},
   285  	{"", "b", "b"},
   286  	{"/a", "b", "/a/b"},
   287  	{"/a/", "b", "/a/b"},
   288  
   289  	{`C:\Windows\`, `System32`, `C:\Windows\System32`},
   290  	{`C:\Windows\`, ``, `C:\Windows\`},
   291  	{`C:\`, `Windows`, `C:\Windows`},
   292  	{`C:\Windows\`, `C:System32`, `C:\Windows\System32`},
   293  	{`C:\Windows`, `a/b`, `C:\Windows\a/b`},
   294  	{`\\host\share\`, `foo`, `\\host\share\foo`},
   295  	{`\\host\share\`, `foo\bar`, `\\host\share\foo\bar`},
   296  	{`//host/share/`, `foo/bar`, `//host/share/foo/bar`},
   297  
   298  	// The following are "best effort". We shouldn't see relative
   299  	// base directories in DWARF, but these test that pathJoin
   300  	// doesn't fail miserably if it sees one.
   301  	{`C:`, `a`, `C:a`},
   302  	{`C:`, `a\b`, `C:a\b`},
   303  	{`C:.`, `a`, `C:.\a`},
   304  	{`C:a`, `b`, `C:a\b`},
   305  }
   306  
   307  func TestPathJoin(t *testing.T) {
   308  	for _, test := range joinTests {
   309  		got := PathJoin(test.dirname, test.filename)
   310  		if test.path != got {
   311  			t.Errorf("pathJoin(%q, %q) = %q, want %q", test.dirname, test.filename, got, test.path)
   312  		}
   313  	}
   314  }