line_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  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. package dwarf_test
  5. import (
  6. . "debug/dwarf"
  7. "io"
  8. "strings"
  9. "testing"
  10. )
  11. var (
  12. file1C = &LineFile{Name: "/home/austin/go.dev/src/debug/dwarf/testdata/line1.c"}
  13. file1H = &LineFile{Name: "/home/austin/go.dev/src/debug/dwarf/testdata/line1.h"}
  14. file2C = &LineFile{Name: "/home/austin/go.dev/src/debug/dwarf/testdata/line2.c"}
  15. )
  16. func TestLineELFGCC(t *testing.T) {
  17. // Generated by:
  18. // # gcc --version | head -n1
  19. // gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2
  20. // # gcc -g -o line-gcc.elf line*.c
  21. // Line table based on readelf --debug-dump=rawline,decodedline
  22. want := []LineEntry{
  23. {Address: 0x40059d, File: file1H, Line: 2, IsStmt: true},
  24. {Address: 0x4005a5, File: file1H, Line: 2, IsStmt: true},
  25. {Address: 0x4005b4, File: file1H, Line: 5, IsStmt: true},
  26. {Address: 0x4005bd, File: file1H, Line: 6, IsStmt: true, Discriminator: 2},
  27. {Address: 0x4005c7, File: file1H, Line: 5, IsStmt: true, Discriminator: 2},
  28. {Address: 0x4005cb, File: file1H, Line: 5, IsStmt: false, Discriminator: 1},
  29. {Address: 0x4005d1, File: file1H, Line: 7, IsStmt: true},
  30. {Address: 0x4005e7, File: file1C, Line: 6, IsStmt: true},
  31. {Address: 0x4005eb, File: file1C, Line: 7, IsStmt: true},
  32. {Address: 0x4005f5, File: file1C, Line: 8, IsStmt: true},
  33. {Address: 0x4005ff, File: file1C, Line: 9, IsStmt: true},
  34. {Address: 0x400601, EndSequence: true},
  35. {Address: 0x400601, File: file2C, Line: 4, IsStmt: true},
  36. {Address: 0x400605, File: file2C, Line: 5, IsStmt: true},
  37. {Address: 0x40060f, File: file2C, Line: 6, IsStmt: true},
  38. {Address: 0x400611, EndSequence: true},
  39. }
  40. files := [][]*LineFile{{nil, file1H, file1C}, {nil, file2C}}
  41. testLineTable(t, want, files, elfData(t, "testdata/line-gcc.elf"))
  42. }
  43. func TestLineGCCWindows(t *testing.T) {
  44. // Generated by:
  45. // > gcc --version
  46. // gcc (tdm64-1) 4.9.2
  47. // > gcc -g -o line-gcc-win.bin line1.c C:\workdir\go\src\debug\dwarf\testdata\line2.c
  48. toWindows := func(lf *LineFile) *LineFile {
  49. lf2 := *lf
  50. lf2.Name = strings.Replace(lf2.Name, "/home/austin/go.dev/", "C:\\workdir\\go\\", -1)
  51. lf2.Name = strings.Replace(lf2.Name, "/", "\\", -1)
  52. return &lf2
  53. }
  54. file1C := toWindows(file1C)
  55. file1H := toWindows(file1H)
  56. file2C := toWindows(file2C)
  57. // Line table based on objdump --dwarf=rawline,decodedline
  58. want := []LineEntry{
  59. {Address: 0x401530, File: file1H, Line: 2, IsStmt: true},
  60. {Address: 0x401538, File: file1H, Line: 5, IsStmt: true},
  61. {Address: 0x401541, File: file1H, Line: 6, IsStmt: true, Discriminator: 3},
  62. {Address: 0x40154b, File: file1H, Line: 5, IsStmt: true, Discriminator: 3},
  63. {Address: 0x40154f, File: file1H, Line: 5, IsStmt: false, Discriminator: 1},
  64. {Address: 0x401555, File: file1H, Line: 7, IsStmt: true},
  65. {Address: 0x40155b, File: file1C, Line: 6, IsStmt: true},
  66. {Address: 0x401563, File: file1C, Line: 6, IsStmt: true},
  67. {Address: 0x401568, File: file1C, Line: 7, IsStmt: true},
  68. {Address: 0x40156d, File: file1C, Line: 8, IsStmt: true},
  69. {Address: 0x401572, File: file1C, Line: 9, IsStmt: true},
  70. {Address: 0x401578, EndSequence: true},
  71. {Address: 0x401580, File: file2C, Line: 4, IsStmt: true},
  72. {Address: 0x401588, File: file2C, Line: 5, IsStmt: true},
  73. {Address: 0x401595, File: file2C, Line: 6, IsStmt: true},
  74. {Address: 0x40159b, EndSequence: true},
  75. }
  76. files := [][]*LineFile{{nil, file1H, file1C}, {nil, file2C}}
  77. testLineTable(t, want, files, peData(t, "testdata/line-gcc-win.bin"))
  78. }
  79. func TestLineELFClang(t *testing.T) {
  80. // Generated by:
  81. // # clang --version | head -n1
  82. // Ubuntu clang version 3.4-1ubuntu3 (tags/RELEASE_34/final) (based on LLVM 3.4)
  83. // # clang -g -o line-clang.elf line*.
  84. want := []LineEntry{
  85. {Address: 0x400530, File: file1C, Line: 6, IsStmt: true},
  86. {Address: 0x400534, File: file1C, Line: 7, IsStmt: true, PrologueEnd: true},
  87. {Address: 0x400539, File: file1C, Line: 8, IsStmt: true},
  88. {Address: 0x400545, File: file1C, Line: 9, IsStmt: true},
  89. {Address: 0x400550, File: file1H, Line: 2, IsStmt: true},
  90. {Address: 0x400554, File: file1H, Line: 5, IsStmt: true, PrologueEnd: true},
  91. {Address: 0x400568, File: file1H, Line: 6, IsStmt: true},
  92. {Address: 0x400571, File: file1H, Line: 5, IsStmt: true},
  93. {Address: 0x400581, File: file1H, Line: 7, IsStmt: true},
  94. {Address: 0x400583, EndSequence: true},
  95. {Address: 0x400590, File: file2C, Line: 4, IsStmt: true},
  96. {Address: 0x4005a0, File: file2C, Line: 5, IsStmt: true, PrologueEnd: true},
  97. {Address: 0x4005a7, File: file2C, Line: 6, IsStmt: true},
  98. {Address: 0x4005b0, EndSequence: true},
  99. }
  100. files := [][]*LineFile{{nil, file1C, file1H}, {nil, file2C}}
  101. testLineTable(t, want, files, elfData(t, "testdata/line-clang.elf"))
  102. }
  103. func TestLineRnglists(t *testing.T) {
  104. // Test a newer file, generated by clang.
  105. file := &LineFile{Name: "/usr/local/google/home/iant/foo.c"}
  106. want := []LineEntry{
  107. {Address: 0x401020, File: file, Line: 12, IsStmt: true},
  108. {Address: 0x401020, File: file, Line: 13, Column: 12, IsStmt: true, PrologueEnd: true},
  109. {Address: 0x401022, File: file, Line: 13, Column: 7},
  110. {Address: 0x401024, File: file, Line: 17, Column: 1, IsStmt: true},
  111. {Address: 0x401027, File: file, Line: 16, Column: 10, IsStmt: true},
  112. {Address: 0x40102c, EndSequence: true},
  113. {Address: 0x401000, File: file, Line: 2, IsStmt: true},
  114. {Address: 0x401000, File: file, Line: 6, Column: 17, IsStmt: true, PrologueEnd: true},
  115. {Address: 0x401002, File: file, Line: 6, Column: 3},
  116. {Address: 0x401019, File: file, Line: 9, Column: 3, IsStmt: true},
  117. {Address: 0x40101a, File: file, Line: 0, Column: 3},
  118. {Address: 0x40101c, File: file, Line: 9, Column: 3},
  119. {Address: 0x40101d, EndSequence: true},
  120. }
  121. files := [][]*LineFile{{file}}
  122. testLineTable(t, want, files, elfData(t, "testdata/rnglistx.elf"))
  123. }
  124. func TestLineSeek(t *testing.T) {
  125. d := elfData(t, "testdata/line-gcc.elf")
  126. // Get the line table for the first CU.
  127. cu, err := d.Reader().Next()
  128. if err != nil {
  129. t.Fatal("d.Reader().Next:", err)
  130. }
  131. lr, err := d.LineReader(cu)
  132. if err != nil {
  133. t.Fatal("d.LineReader:", err)
  134. }
  135. // Read entries forward.
  136. var line LineEntry
  137. var posTable []LineReaderPos
  138. var table []LineEntry
  139. for {
  140. posTable = append(posTable, lr.Tell())
  141. err := lr.Next(&line)
  142. if err != nil {
  143. if err == io.EOF {
  144. break
  145. }
  146. t.Fatal("lr.Next:", err)
  147. }
  148. table = append(table, line)
  149. }
  150. // Test that Reset returns to the first line.
  151. lr.Reset()
  152. if err := lr.Next(&line); err != nil {
  153. t.Fatal("lr.Next after Reset failed:", err)
  154. } else if line != table[0] {
  155. t.Fatal("lr.Next after Reset returned", line, "instead of", table[0])
  156. }
  157. // Check that entries match when seeking backward.
  158. for i := len(posTable) - 1; i >= 0; i-- {
  159. lr.Seek(posTable[i])
  160. err := lr.Next(&line)
  161. if i == len(posTable)-1 {
  162. if err != io.EOF {
  163. t.Fatal("expected io.EOF after seek to end, got", err)
  164. }
  165. } else if err != nil {
  166. t.Fatal("lr.Next after seek to", posTable[i], "failed:", err)
  167. } else if line != table[i] {
  168. t.Fatal("lr.Next after seek to", posTable[i], "returned", line, "instead of", table[i])
  169. }
  170. }
  171. // Check that seeking to a PC returns the right line.
  172. if err := lr.SeekPC(table[0].Address-1, &line); err != ErrUnknownPC {
  173. t.Fatalf("lr.SeekPC to %#x returned %v instead of ErrUnknownPC", table[0].Address-1, err)
  174. }
  175. for i, testLine := range table {
  176. if testLine.EndSequence {
  177. if err := lr.SeekPC(testLine.Address, &line); err != ErrUnknownPC {
  178. t.Fatalf("lr.SeekPC to %#x returned %v instead of ErrUnknownPC", testLine.Address, err)
  179. }
  180. continue
  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. func testLineTable(t *testing.T, want []LineEntry, files [][]*LineFile, d *Data) {
  193. // Get line table from d.
  194. var got []LineEntry
  195. dr := d.Reader()
  196. for {
  197. ent, err := dr.Next()
  198. if err != nil {
  199. t.Fatal("dr.Next:", err)
  200. } else if ent == nil {
  201. break
  202. }
  203. if ent.Tag != TagCompileUnit {
  204. dr.SkipChildren()
  205. continue
  206. }
  207. // Ignore system compilation units (this happens in
  208. // the Windows binary). We'll still decode the line
  209. // table, but won't check it.
  210. name := ent.Val(AttrName).(string)
  211. ignore := strings.HasPrefix(name, "C:/crossdev/") || strings.HasPrefix(name, "../../")
  212. // Decode CU's line table.
  213. lr, err := d.LineReader(ent)
  214. if err != nil {
  215. t.Fatal("d.LineReader:", err)
  216. } else if lr == nil {
  217. continue
  218. }
  219. for {
  220. var line LineEntry
  221. err := lr.Next(&line)
  222. if err != nil {
  223. if err == io.EOF {
  224. break
  225. }
  226. t.Fatal("lr.Next:", err)
  227. }
  228. // Ignore sources from the Windows build environment.
  229. if ignore {
  230. continue
  231. }
  232. got = append(got, line)
  233. }
  234. // Check file table.
  235. if !ignore {
  236. if !compareFiles(files[0], lr.Files()) {
  237. t.Log("File tables do not match. Got:")
  238. dumpFiles(t, lr.Files())
  239. t.Log("Want:")
  240. dumpFiles(t, files[0])
  241. t.Fail()
  242. }
  243. files = files[1:]
  244. }
  245. }
  246. // Compare line tables.
  247. if !compareLines(got, want) {
  248. t.Log("Line tables do not match. Got:")
  249. dumpLines(t, got)
  250. t.Log("Want:")
  251. dumpLines(t, want)
  252. t.FailNow()
  253. }
  254. }
  255. func compareFiles(a, b []*LineFile) bool {
  256. if len(a) != len(b) {
  257. return false
  258. }
  259. for i := range a {
  260. if a[i] == nil && b[i] == nil {
  261. continue
  262. }
  263. if a[i] != nil && b[i] != nil && a[i].Name == b[i].Name {
  264. continue
  265. }
  266. return false
  267. }
  268. return true
  269. }
  270. func dumpFiles(t *testing.T, files []*LineFile) {
  271. for i, f := range files {
  272. name := "<nil>"
  273. if f != nil {
  274. name = f.Name
  275. }
  276. t.Logf(" %d %s", i, name)
  277. }
  278. }
  279. func compareLines(a, b []LineEntry) bool {
  280. if len(a) != len(b) {
  281. return false
  282. }
  283. for i := range a {
  284. al, bl := a[i], b[i]
  285. // If both are EndSequence, then the only other valid
  286. // field is Address. Otherwise, test equality of all
  287. // fields.
  288. if al.EndSequence && bl.EndSequence && al.Address == bl.Address {
  289. continue
  290. }
  291. if al.File.Name != bl.File.Name {
  292. return false
  293. }
  294. al.File = nil
  295. bl.File = nil
  296. if al != bl {
  297. return false
  298. }
  299. }
  300. return true
  301. }
  302. func dumpLines(t *testing.T, lines []LineEntry) {
  303. for _, l := range lines {
  304. t.Logf(" %+v File:%+v", l, l.File)
  305. }
  306. }
  307. type joinTest struct {
  308. dirname, filename string
  309. path string
  310. }
  311. var joinTests = []joinTest{
  312. {"a", "b", "a/b"},
  313. {"a", "", "a"},
  314. {"", "b", "b"},
  315. {"/a", "b", "/a/b"},
  316. {"/a/", "b", "/a/b"},
  317. {`C:\Windows\`, `System32`, `C:\Windows\System32`},
  318. {`C:\Windows\`, ``, `C:\Windows\`},
  319. {`C:\`, `Windows`, `C:\Windows`},
  320. {`C:\Windows\`, `C:System32`, `C:\Windows\System32`},
  321. {`C:\Windows`, `a/b`, `C:\Windows\a/b`},
  322. {`\\host\share\`, `foo`, `\\host\share\foo`},
  323. {`\\host\share\`, `foo\bar`, `\\host\share\foo\bar`},
  324. {`//host/share/`, `foo/bar`, `//host/share/foo/bar`},
  325. // Note: the Go compiler currently emits DWARF line table paths
  326. // with '/' instead of '\' (see issues #19784, #36495). These
  327. // tests are to cover cases that might come up for Windows Go
  328. // binaries.
  329. {`c:/workdir/go/src/x`, `y.go`, `c:/workdir/go/src/x/y.go`},
  330. {`d:/some/thing/`, `b.go`, `d:/some/thing/b.go`},
  331. {`e:\blah\`, `foo.c`, `e:\blah\foo.c`},
  332. // The following are "best effort". We shouldn't see relative
  333. // base directories in DWARF, but these test that pathJoin
  334. // doesn't fail miserably if it sees one.
  335. {`C:`, `a`, `C:a`},
  336. {`C:`, `a\b`, `C:a\b`},
  337. {`C:.`, `a`, `C:.\a`},
  338. {`C:a`, `b`, `C:a\b`},
  339. }
  340. func TestPathJoin(t *testing.T) {
  341. for _, test := range joinTests {
  342. got := PathJoin(test.dirname, test.filename)
  343. if test.path != got {
  344. t.Errorf("pathJoin(%q, %q) = %q, want %q", test.dirname, test.filename, got, test.path)
  345. }
  346. }
  347. }