...

Source file src/debug/dwarf/line_test.go

Documentation: debug/dwarf

		 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  

View as plain text