Home | History | Annotate | Download | only in 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 	"testing"
     11 )
     12 
     13 var (
     14 	file1C = &LineFile{Name: "/home/austin/go.dev/src/debug/dwarf/testdata/line1.c"}
     15 	file1H = &LineFile{Name: "/home/austin/go.dev/src/debug/dwarf/testdata/line1.h"}
     16 	file2C = &LineFile{Name: "/home/austin/go.dev/src/debug/dwarf/testdata/line2.c"}
     17 )
     18 
     19 func TestLineELFGCC(t *testing.T) {
     20 	// Generated by:
     21 	//   # gcc --version | head -n1
     22 	//   gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2
     23 	//   # gcc -g -o line-gcc.elf line*.c
     24 
     25 	// Line table based on readelf --debug-dump=rawline,decodedline
     26 	want := []LineEntry{
     27 		{Address: 0x40059d, File: file1H, Line: 2, IsStmt: true},
     28 		{Address: 0x4005a5, File: file1H, Line: 2, IsStmt: true},
     29 		{Address: 0x4005b4, File: file1H, Line: 5, IsStmt: true},
     30 		{Address: 0x4005bd, File: file1H, Line: 6, IsStmt: true, Discriminator: 2},
     31 		{Address: 0x4005c7, File: file1H, Line: 5, IsStmt: true, Discriminator: 2},
     32 		{Address: 0x4005cb, File: file1H, Line: 5, IsStmt: false, Discriminator: 1},
     33 		{Address: 0x4005d1, File: file1H, Line: 7, IsStmt: true},
     34 		{Address: 0x4005e7, File: file1C, Line: 6, IsStmt: true},
     35 		{Address: 0x4005eb, File: file1C, Line: 7, IsStmt: true},
     36 		{Address: 0x4005f5, File: file1C, Line: 8, IsStmt: true},
     37 		{Address: 0x4005ff, File: file1C, Line: 9, IsStmt: true},
     38 		{Address: 0x400601, EndSequence: true},
     39 
     40 		{Address: 0x400601, File: file2C, Line: 4, IsStmt: true},
     41 		{Address: 0x400605, File: file2C, Line: 5, IsStmt: true},
     42 		{Address: 0x40060f, File: file2C, Line: 6, IsStmt: true},
     43 		{Address: 0x400611, EndSequence: true},
     44 	}
     45 
     46 	testLineTable(t, want, elfData(t, "testdata/line-gcc.elf"))
     47 }
     48 
     49 func TestLineELFClang(t *testing.T) {
     50 	// Generated by:
     51 	//   # clang --version | head -n1
     52 	//   Ubuntu clang version 3.4-1ubuntu3 (tags/RELEASE_34/final) (based on LLVM 3.4)
     53 	//   # clang -g -o line-clang.elf line*.
     54 
     55 	want := []LineEntry{
     56 		{Address: 0x400530, File: file1C, Line: 6, IsStmt: true},
     57 		{Address: 0x400534, File: file1C, Line: 7, IsStmt: true, PrologueEnd: true},
     58 		{Address: 0x400539, File: file1C, Line: 8, IsStmt: true},
     59 		{Address: 0x400545, File: file1C, Line: 9, IsStmt: true},
     60 		{Address: 0x400550, File: file1H, Line: 2, IsStmt: true},
     61 		{Address: 0x400554, File: file1H, Line: 5, IsStmt: true, PrologueEnd: true},
     62 		{Address: 0x400568, File: file1H, Line: 6, IsStmt: true},
     63 		{Address: 0x400571, File: file1H, Line: 5, IsStmt: true},
     64 		{Address: 0x400581, File: file1H, Line: 7, IsStmt: true},
     65 		{Address: 0x400583, EndSequence: true},
     66 
     67 		{Address: 0x400590, File: file2C, Line: 4, IsStmt: true},
     68 		{Address: 0x4005a0, File: file2C, Line: 5, IsStmt: true, PrologueEnd: true},
     69 		{Address: 0x4005a7, File: file2C, Line: 6, IsStmt: true},
     70 		{Address: 0x4005b0, EndSequence: true},
     71 	}
     72 
     73 	testLineTable(t, want, elfData(t, "testdata/line-clang.elf"))
     74 }
     75 
     76 func TestLineSeek(t *testing.T) {
     77 	d := elfData(t, "testdata/line-gcc.elf")
     78 
     79 	// Get the line table for the first CU.
     80 	cu, err := d.Reader().Next()
     81 	if err != nil {
     82 		t.Fatal("d.Reader().Next:", err)
     83 	}
     84 	lr, err := d.LineReader(cu)
     85 	if err != nil {
     86 		t.Fatal("d.LineReader:", err)
     87 	}
     88 
     89 	// Read entries forward.
     90 	var line LineEntry
     91 	var posTable []LineReaderPos
     92 	var table []LineEntry
     93 	for {
     94 		posTable = append(posTable, lr.Tell())
     95 
     96 		err := lr.Next(&line)
     97 		if err != nil {
     98 			if err == io.EOF {
     99 				break
    100 			}
    101 			t.Fatal("lr.Next:", err)
    102 		}
    103 		table = append(table, line)
    104 	}
    105 
    106 	// Test that Reset returns to the first line.
    107 	lr.Reset()
    108 	if err := lr.Next(&line); err != nil {
    109 		t.Fatal("lr.Next after Reset failed:", err)
    110 	} else if line != table[0] {
    111 		t.Fatal("lr.Next after Reset returned", line, "instead of", table[0])
    112 	}
    113 
    114 	// Check that entries match when seeking backward.
    115 	for i := len(posTable) - 1; i >= 0; i-- {
    116 		lr.Seek(posTable[i])
    117 		err := lr.Next(&line)
    118 		if i == len(posTable)-1 {
    119 			if err != io.EOF {
    120 				t.Fatal("expected io.EOF after seek to end, got", err)
    121 			}
    122 		} else if err != nil {
    123 			t.Fatal("lr.Next after seek to", posTable[i], "failed:", err)
    124 		} else if line != table[i] {
    125 			t.Fatal("lr.Next after seek to", posTable[i], "returned", line, "instead of", table[i])
    126 		}
    127 	}
    128 
    129 	// Check that seeking to a PC returns the right line.
    130 	if err := lr.SeekPC(table[0].Address-1, &line); err != ErrUnknownPC {
    131 		t.Fatalf("lr.SeekPC to %#x returned %v instead of ErrUnknownPC", table[0].Address-1, err)
    132 	}
    133 	for i, testLine := range table {
    134 		if testLine.EndSequence {
    135 			if err := lr.SeekPC(testLine.Address, &line); err != ErrUnknownPC {
    136 				t.Fatalf("lr.SeekPC to %#x returned %v instead of ErrUnknownPC", testLine.Address, err)
    137 			}
    138 			continue
    139 		}
    140 
    141 		nextPC := table[i+1].Address
    142 		for pc := testLine.Address; pc < nextPC; pc++ {
    143 			if err := lr.SeekPC(pc, &line); err != nil {
    144 				t.Fatalf("lr.SeekPC to %#x failed: %v", pc, err)
    145 			} else if line != testLine {
    146 				t.Fatalf("lr.SeekPC to %#x returned %v instead of %v", pc, line, testLine)
    147 			}
    148 		}
    149 	}
    150 }
    151 
    152 func testLineTable(t *testing.T, want []LineEntry, d *Data) {
    153 	// Get line table from d.
    154 	var got []LineEntry
    155 	dr := d.Reader()
    156 	for {
    157 		ent, err := dr.Next()
    158 		if err != nil {
    159 			t.Fatal("dr.Next:", err)
    160 		} else if ent == nil {
    161 			break
    162 		}
    163 
    164 		if ent.Tag != TagCompileUnit {
    165 			dr.SkipChildren()
    166 			continue
    167 		}
    168 
    169 		// Decode CU's line table.
    170 		lr, err := d.LineReader(ent)
    171 		if err != nil {
    172 			t.Fatal("d.LineReader:", err)
    173 		} else if lr == nil {
    174 			continue
    175 		}
    176 
    177 		for {
    178 			var line LineEntry
    179 			err := lr.Next(&line)
    180 			if err != nil {
    181 				if err == io.EOF {
    182 					break
    183 				}
    184 				t.Fatal("lr.Next:", err)
    185 			}
    186 			got = append(got, line)
    187 		}
    188 	}
    189 
    190 	// Compare line tables.
    191 	if !compareLines(got, want) {
    192 		t.Log("Line tables do not match. Got:")
    193 		dumpLines(t, got)
    194 		t.Log("Want:")
    195 		dumpLines(t, want)
    196 		t.FailNow()
    197 	}
    198 }
    199 
    200 func compareLines(a, b []LineEntry) bool {
    201 	if len(a) != len(b) {
    202 		return false
    203 	}
    204 
    205 	for i := range a {
    206 		al, bl := a[i], b[i]
    207 		// If both are EndSequence, then the only other valid
    208 		// field is Address. Otherwise, test equality of all
    209 		// fields.
    210 		if al.EndSequence && bl.EndSequence && al.Address == bl.Address {
    211 			continue
    212 		}
    213 		if al.File.Name != bl.File.Name {
    214 			return false
    215 		}
    216 		al.File = nil
    217 		bl.File = nil
    218 		if al != bl {
    219 			return false
    220 		}
    221 	}
    222 	return true
    223 }
    224 
    225 func dumpLines(t *testing.T, lines []LineEntry) {
    226 	for _, l := range lines {
    227 		t.Logf("  %+v File:%+v", l, l.File)
    228 	}
    229 }
    230