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