github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/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 TestLineELFGCCZstd(t *testing.T) { 52 // Generated by: 53 // # gcc --version | head -n1 54 // gcc (Debian 12.2.0-10) 12.2.0 55 // # gcc -g -no-pie -Wl,--compress-debug-sections=zstd line*.c 56 57 zfile1H := &LineFile{Name: "/home/iant/go/src/debug/dwarf/testdata/line1.h"} 58 zfile1C := &LineFile{Name: "/home/iant/go/src/debug/dwarf/testdata/line1.c"} 59 zfile2C := &LineFile{Name: "/home/iant/go/src/debug/dwarf/testdata/line2.c"} 60 61 // Line table based on readelf --debug-dump=rawline,decodedline 62 want := []LineEntry{ 63 {Address: 0x401126, File: zfile1H, Line: 2, Column: 1, IsStmt: true}, 64 {Address: 0x40112a, File: zfile1H, Line: 5, Column: 8, IsStmt: true}, 65 {Address: 0x401131, File: zfile1H, Line: 5, Column: 2, IsStmt: true}, 66 {Address: 0x401133, File: zfile1H, Line: 6, Column: 10, IsStmt: true, Discriminator: 3}, 67 {Address: 0x40113d, File: zfile1H, Line: 5, Column: 22, IsStmt: true, Discriminator: 3}, 68 {Address: 0x401141, File: zfile1H, Line: 5, Column: 15, IsStmt: true, Discriminator: 1}, 69 {Address: 0x401147, File: zfile1H, Line: 7, Column: 1, IsStmt: true}, 70 {Address: 0x40114b, File: zfile1C, Line: 6, Column: 1, IsStmt: true}, 71 {Address: 0x40114f, File: zfile1C, Line: 7, Column: 2, IsStmt: true}, 72 {Address: 0x401159, File: zfile1C, Line: 8, Column: 2, IsStmt: true}, 73 {Address: 0x401168, File: zfile1C, Line: 9, Column: 1, IsStmt: true}, 74 {Address: 0x40116a, EndSequence: true}, 75 76 {Address: 0x40116a, File: zfile2C, Line: 4, Column: 1, IsStmt: true}, 77 {Address: 0x40116e, File: zfile2C, Line: 5, Column: 2, IsStmt: true}, 78 {Address: 0x40117d, File: zfile2C, Line: 6, Column: 1, IsStmt: true}, 79 {Address: 0x401180, EndSequence: true}, 80 } 81 files := [][]*LineFile{ 82 {zfile1C, zfile1H, zfile1C}, 83 {zfile2C, zfile2C}, 84 } 85 86 testLineTable(t, want, files, elfData(t, "testdata/line-gcc-zstd.elf")) 87 } 88 89 func TestLineGCCWindows(t *testing.T) { 90 // Generated by: 91 // > gcc --version 92 // gcc (tdm64-1) 4.9.2 93 // > gcc -g -o line-gcc-win.bin line1.c C:\workdir\go\src\debug\dwarf\testdata\line2.c 94 95 toWindows := func(lf *LineFile) *LineFile { 96 lf2 := *lf 97 lf2.Name = strings.Replace(lf2.Name, "/home/austin/go.dev/", "C:\\workdir\\go\\", -1) 98 lf2.Name = strings.Replace(lf2.Name, "/", "\\", -1) 99 return &lf2 100 } 101 file1C := toWindows(file1C) 102 file1H := toWindows(file1H) 103 file2C := toWindows(file2C) 104 105 // Line table based on objdump --dwarf=rawline,decodedline 106 want := []LineEntry{ 107 {Address: 0x401530, File: file1H, Line: 2, IsStmt: true}, 108 {Address: 0x401538, File: file1H, Line: 5, IsStmt: true}, 109 {Address: 0x401541, File: file1H, Line: 6, IsStmt: true, Discriminator: 3}, 110 {Address: 0x40154b, File: file1H, Line: 5, IsStmt: true, Discriminator: 3}, 111 {Address: 0x40154f, File: file1H, Line: 5, IsStmt: false, Discriminator: 1}, 112 {Address: 0x401555, File: file1H, Line: 7, IsStmt: true}, 113 {Address: 0x40155b, File: file1C, Line: 6, IsStmt: true}, 114 {Address: 0x401563, File: file1C, Line: 6, IsStmt: true}, 115 {Address: 0x401568, File: file1C, Line: 7, IsStmt: true}, 116 {Address: 0x40156d, File: file1C, Line: 8, IsStmt: true}, 117 {Address: 0x401572, File: file1C, Line: 9, IsStmt: true}, 118 {Address: 0x401578, EndSequence: true}, 119 120 {Address: 0x401580, File: file2C, Line: 4, IsStmt: true}, 121 {Address: 0x401588, File: file2C, Line: 5, IsStmt: true}, 122 {Address: 0x401595, File: file2C, Line: 6, IsStmt: true}, 123 {Address: 0x40159b, EndSequence: true}, 124 } 125 files := [][]*LineFile{{nil, file1H, file1C}, {nil, file2C}} 126 127 testLineTable(t, want, files, peData(t, "testdata/line-gcc-win.bin")) 128 } 129 130 func TestLineELFClang(t *testing.T) { 131 // Generated by: 132 // # clang --version | head -n1 133 // Ubuntu clang version 3.4-1ubuntu3 (tags/RELEASE_34/final) (based on LLVM 3.4) 134 // # clang -g -o line-clang.elf line*. 135 136 want := []LineEntry{ 137 {Address: 0x400530, File: file1C, Line: 6, IsStmt: true}, 138 {Address: 0x400534, File: file1C, Line: 7, IsStmt: true, PrologueEnd: true}, 139 {Address: 0x400539, File: file1C, Line: 8, IsStmt: true}, 140 {Address: 0x400545, File: file1C, Line: 9, IsStmt: true}, 141 {Address: 0x400550, File: file1H, Line: 2, IsStmt: true}, 142 {Address: 0x400554, File: file1H, Line: 5, IsStmt: true, PrologueEnd: true}, 143 {Address: 0x400568, File: file1H, Line: 6, IsStmt: true}, 144 {Address: 0x400571, File: file1H, Line: 5, IsStmt: true}, 145 {Address: 0x400581, File: file1H, Line: 7, IsStmt: true}, 146 {Address: 0x400583, EndSequence: true}, 147 148 {Address: 0x400590, File: file2C, Line: 4, IsStmt: true}, 149 {Address: 0x4005a0, File: file2C, Line: 5, IsStmt: true, PrologueEnd: true}, 150 {Address: 0x4005a7, File: file2C, Line: 6, IsStmt: true}, 151 {Address: 0x4005b0, EndSequence: true}, 152 } 153 files := [][]*LineFile{{nil, file1C, file1H}, {nil, file2C}} 154 155 testLineTable(t, want, files, elfData(t, "testdata/line-clang.elf")) 156 } 157 158 func TestLineRnglists(t *testing.T) { 159 // Test a newer file, generated by clang. 160 file := &LineFile{Name: "/usr/local/google/home/iant/foo.c"} 161 want := []LineEntry{ 162 {Address: 0x401020, File: file, Line: 12, IsStmt: true}, 163 {Address: 0x401020, File: file, Line: 13, Column: 12, IsStmt: true, PrologueEnd: true}, 164 {Address: 0x401022, File: file, Line: 13, Column: 7}, 165 {Address: 0x401024, File: file, Line: 17, Column: 1, IsStmt: true}, 166 {Address: 0x401027, File: file, Line: 16, Column: 10, IsStmt: true}, 167 {Address: 0x40102c, EndSequence: true}, 168 {Address: 0x401000, File: file, Line: 2, IsStmt: true}, 169 {Address: 0x401000, File: file, Line: 6, Column: 17, IsStmt: true, PrologueEnd: true}, 170 {Address: 0x401002, File: file, Line: 6, Column: 3}, 171 {Address: 0x401019, File: file, Line: 9, Column: 3, IsStmt: true}, 172 {Address: 0x40101a, File: file, Line: 0, Column: 3}, 173 {Address: 0x40101c, File: file, Line: 9, Column: 3}, 174 {Address: 0x40101d, EndSequence: true}, 175 } 176 files := [][]*LineFile{{file}} 177 178 testLineTable(t, want, files, elfData(t, "testdata/rnglistx.elf")) 179 } 180 181 func TestLineSeek(t *testing.T) { 182 d := elfData(t, "testdata/line-gcc.elf") 183 184 // Get the line table for the first CU. 185 cu, err := d.Reader().Next() 186 if err != nil { 187 t.Fatal("d.Reader().Next:", err) 188 } 189 lr, err := d.LineReader(cu) 190 if err != nil { 191 t.Fatal("d.LineReader:", err) 192 } 193 194 // Read entries forward. 195 var line LineEntry 196 var posTable []LineReaderPos 197 var table []LineEntry 198 for { 199 posTable = append(posTable, lr.Tell()) 200 201 err := lr.Next(&line) 202 if err != nil { 203 if err == io.EOF { 204 break 205 } 206 t.Fatal("lr.Next:", err) 207 } 208 table = append(table, line) 209 } 210 211 // Test that Reset returns to the first line. 212 lr.Reset() 213 if err := lr.Next(&line); err != nil { 214 t.Fatal("lr.Next after Reset failed:", err) 215 } else if line != table[0] { 216 t.Fatal("lr.Next after Reset returned", line, "instead of", table[0]) 217 } 218 219 // Check that entries match when seeking backward. 220 for i := len(posTable) - 1; i >= 0; i-- { 221 lr.Seek(posTable[i]) 222 err := lr.Next(&line) 223 if i == len(posTable)-1 { 224 if err != io.EOF { 225 t.Fatal("expected io.EOF after seek to end, got", err) 226 } 227 } else if err != nil { 228 t.Fatal("lr.Next after seek to", posTable[i], "failed:", err) 229 } else if line != table[i] { 230 t.Fatal("lr.Next after seek to", posTable[i], "returned", line, "instead of", table[i]) 231 } 232 } 233 234 // Check that seeking to a PC returns the right line. 235 if err := lr.SeekPC(table[0].Address-1, &line); err != ErrUnknownPC { 236 t.Fatalf("lr.SeekPC to %#x returned %v instead of ErrUnknownPC", table[0].Address-1, err) 237 } 238 for i, testLine := range table { 239 if testLine.EndSequence { 240 if err := lr.SeekPC(testLine.Address, &line); err != ErrUnknownPC { 241 t.Fatalf("lr.SeekPC to %#x returned %v instead of ErrUnknownPC", testLine.Address, err) 242 } 243 continue 244 } 245 246 nextPC := table[i+1].Address 247 for pc := testLine.Address; pc < nextPC; pc++ { 248 if err := lr.SeekPC(pc, &line); err != nil { 249 t.Fatalf("lr.SeekPC to %#x failed: %v", pc, err) 250 } else if line != testLine { 251 t.Fatalf("lr.SeekPC to %#x returned %v instead of %v", pc, line, testLine) 252 } 253 } 254 } 255 } 256 257 func testLineTable(t *testing.T, want []LineEntry, files [][]*LineFile, d *Data) { 258 // Get line table from d. 259 var got []LineEntry 260 dr := d.Reader() 261 for { 262 ent, err := dr.Next() 263 if err != nil { 264 t.Fatal("dr.Next:", err) 265 } else if ent == nil { 266 break 267 } 268 269 if ent.Tag != TagCompileUnit { 270 dr.SkipChildren() 271 continue 272 } 273 274 // Ignore system compilation units (this happens in 275 // the Windows binary). We'll still decode the line 276 // table, but won't check it. 277 name := ent.Val(AttrName).(string) 278 ignore := strings.HasPrefix(name, "C:/crossdev/") || strings.HasPrefix(name, "../../") 279 280 // Decode CU's line table. 281 lr, err := d.LineReader(ent) 282 if err != nil { 283 t.Fatal("d.LineReader:", err) 284 } else if lr == nil { 285 continue 286 } 287 288 for { 289 var line LineEntry 290 err := lr.Next(&line) 291 if err != nil { 292 if err == io.EOF { 293 break 294 } 295 t.Fatal("lr.Next:", err) 296 } 297 // Ignore sources from the Windows build environment. 298 if ignore { 299 continue 300 } 301 got = append(got, line) 302 } 303 304 // Check file table. 305 if !ignore { 306 if !compareFiles(files[0], lr.Files()) { 307 t.Log("File tables do not match. Got:") 308 dumpFiles(t, lr.Files()) 309 t.Log("Want:") 310 dumpFiles(t, files[0]) 311 t.Fail() 312 } 313 files = files[1:] 314 } 315 } 316 317 // Compare line tables. 318 if !compareLines(t, got, want) { 319 t.Log("Line tables do not match. Got:") 320 dumpLines(t, got) 321 t.Log("Want:") 322 dumpLines(t, want) 323 t.FailNow() 324 } 325 } 326 327 func compareFiles(a, b []*LineFile) bool { 328 if len(a) != len(b) { 329 return false 330 } 331 for i := range a { 332 if a[i] == nil && b[i] == nil { 333 continue 334 } 335 if a[i] != nil && b[i] != nil && a[i].Name == b[i].Name { 336 continue 337 } 338 return false 339 } 340 return true 341 } 342 343 func dumpFiles(t *testing.T, files []*LineFile) { 344 for i, f := range files { 345 name := "<nil>" 346 if f != nil { 347 name = f.Name 348 } 349 t.Logf(" %d %s", i, name) 350 } 351 } 352 353 func compareLines(t *testing.T, a, b []LineEntry) bool { 354 t.Helper() 355 if len(a) != len(b) { 356 t.Errorf("len(a) == %d, len(b) == %d", len(a), len(b)) 357 return false 358 } 359 360 for i := range a { 361 al, bl := a[i], b[i] 362 // If both are EndSequence, then the only other valid 363 // field is Address. Otherwise, test equality of all 364 // fields. 365 if al.EndSequence && bl.EndSequence && al.Address == bl.Address { 366 continue 367 } 368 if al.File.Name != bl.File.Name { 369 t.Errorf("%d: name %v != name %v", i, al.File.Name, bl.File.Name) 370 return false 371 } 372 al.File = nil 373 bl.File = nil 374 if al != bl { 375 t.Errorf("%d: %#v != %#v", i, al, bl) 376 return false 377 } 378 } 379 return true 380 } 381 382 func dumpLines(t *testing.T, lines []LineEntry) { 383 for _, l := range lines { 384 t.Logf(" %+v File:%+v", l, l.File) 385 } 386 } 387 388 type joinTest struct { 389 dirname, filename string 390 path string 391 } 392 393 var joinTests = []joinTest{ 394 {"a", "b", "a/b"}, 395 {"a", "", "a"}, 396 {"", "b", "b"}, 397 {"/a", "b", "/a/b"}, 398 {"/a/", "b", "/a/b"}, 399 400 {`C:\Windows\`, `System32`, `C:\Windows\System32`}, 401 {`C:\Windows\`, ``, `C:\Windows\`}, 402 {`C:\`, `Windows`, `C:\Windows`}, 403 {`C:\Windows\`, `C:System32`, `C:\Windows\System32`}, 404 {`C:\Windows`, `a/b`, `C:\Windows\a/b`}, 405 {`\\host\share\`, `foo`, `\\host\share\foo`}, 406 {`\\host\share\`, `foo\bar`, `\\host\share\foo\bar`}, 407 {`//host/share/`, `foo/bar`, `//host/share/foo/bar`}, 408 409 // Note: the Go compiler currently emits DWARF line table paths 410 // with '/' instead of '\' (see issues #19784, #36495). These 411 // tests are to cover cases that might come up for Windows Go 412 // binaries. 413 {`c:/workdir/go/src/x`, `y.go`, `c:/workdir/go/src/x/y.go`}, 414 {`d:/some/thing/`, `b.go`, `d:/some/thing/b.go`}, 415 {`e:\blah\`, `foo.c`, `e:\blah\foo.c`}, 416 417 // The following are "best effort". We shouldn't see relative 418 // base directories in DWARF, but these test that pathJoin 419 // doesn't fail miserably if it sees one. 420 {`C:`, `a`, `C:a`}, 421 {`C:`, `a\b`, `C:a\b`}, 422 {`C:.`, `a`, `C:.\a`}, 423 {`C:a`, `b`, `C:a\b`}, 424 } 425 426 func TestPathJoin(t *testing.T) { 427 for _, test := range joinTests { 428 got := PathJoin(test.dirname, test.filename) 429 if test.path != got { 430 t.Errorf("pathJoin(%q, %q) = %q, want %q", test.dirname, test.filename, got, test.path) 431 } 432 } 433 } 434 435 func TestPathLineReaderMalformed(t *testing.T) { 436 // This test case drawn from issue #52354. What's happening 437 // here is that the stmtList attribute in the compilation 438 // unit is malformed (negative). 439 var aranges, frame, pubnames, ranges, str []byte 440 abbrev := []byte{0x10, 0x20, 0x20, 0x20, 0x21, 0x20, 0x10, 0x21, 0x61, 441 0x0, 0x0, 0xff, 0x20, 0xff, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 442 0x20, 0x20, 0x20, 0x20, 0x20, 0x20} 443 info := []byte{0x0, 0x0, 0x0, 0x9, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 444 0x20, 0x10, 0x10} 445 line := []byte{0x20} 446 Data0, err := New(abbrev, aranges, frame, info, line, pubnames, ranges, str) 447 if err != nil { 448 t.Fatalf("error unexpected: %v", err) 449 } 450 Reader0 := Data0.Reader() 451 Entry0, err := Reader0.Next() 452 if err != nil { 453 t.Fatalf("error unexpected: %v", err) 454 } 455 LineReader0, err := Data0.LineReader(Entry0) 456 if err == nil { 457 t.Fatalf("expected error") 458 } 459 if LineReader0 != nil { 460 t.Fatalf("expected nil line reader") 461 } 462 }