You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

288 lines
7.4 KiB

  1. // Copyright 2018 Google Inc. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package gosym
  15. import (
  16. "debug/elf"
  17. "fmt"
  18. "io/ioutil"
  19. "os"
  20. "os/exec"
  21. "path/filepath"
  22. "runtime"
  23. "strings"
  24. "testing"
  25. )
  26. var (
  27. pclineTempDir string
  28. pclinetestBinary string
  29. )
  30. func dotest(self bool) bool {
  31. // For now, only works on amd64 platforms.
  32. if runtime.GOARCH != "amd64" {
  33. return false
  34. }
  35. // Self test reads test binary; only works on Linux.
  36. if self && runtime.GOOS != "linux" {
  37. return false
  38. }
  39. // Command below expects "sh", so Unix.
  40. if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
  41. return false
  42. }
  43. if pclinetestBinary != "" {
  44. return true
  45. }
  46. var err error
  47. pclineTempDir, err = ioutil.TempDir("", "pclinetest")
  48. if err != nil {
  49. panic(err)
  50. }
  51. if strings.Contains(pclineTempDir, " ") {
  52. panic("unexpected space in tempdir")
  53. }
  54. // This command builds pclinetest from pclinetest.asm;
  55. // the resulting binary looks like it was built from pclinetest.s,
  56. // but we have renamed it to keep it away from the go tool.
  57. pclinetestBinary = filepath.Join(pclineTempDir, "pclinetest")
  58. command := fmt.Sprintf("go tool 6a -o %s.6 pclinetest.asm && go tool 6l -H linux -E main -o %s %s.6",
  59. pclinetestBinary, pclinetestBinary, pclinetestBinary)
  60. cmd := exec.Command("sh", "-c", command)
  61. cmd.Stdout = os.Stdout
  62. cmd.Stderr = os.Stderr
  63. if err := cmd.Run(); err != nil {
  64. panic(err)
  65. }
  66. return true
  67. }
  68. func endtest() {
  69. if pclineTempDir != "" {
  70. os.RemoveAll(pclineTempDir)
  71. pclineTempDir = ""
  72. pclinetestBinary = ""
  73. }
  74. }
  75. func getTable(t *testing.T) *Table {
  76. f, tab := crack(os.Args[0], t)
  77. f.Close()
  78. return tab
  79. }
  80. func crack(file string, t *testing.T) (*elf.File, *Table) {
  81. // Open self
  82. f, err := elf.Open(file)
  83. if err != nil {
  84. t.Fatal(err)
  85. }
  86. return parse(file, f, t)
  87. }
  88. func parse(file string, f *elf.File, t *testing.T) (*elf.File, *Table) {
  89. symdat, err := f.Section(".gosymtab").Data()
  90. if err != nil {
  91. f.Close()
  92. t.Fatalf("reading %s gosymtab: %v", file, err)
  93. }
  94. pclndat, err := f.Section(".gopclntab").Data()
  95. if err != nil {
  96. f.Close()
  97. t.Fatalf("reading %s gopclntab: %v", file, err)
  98. }
  99. pcln := NewLineTable(pclndat, f.Section(".text").Addr)
  100. tab, err := NewTable(symdat, pcln)
  101. if err != nil {
  102. f.Close()
  103. t.Fatalf("parsing %s gosymtab: %v", file, err)
  104. }
  105. return f, tab
  106. }
  107. var goarch = os.Getenv("O")
  108. func TestLineFromAline(t *testing.T) {
  109. t.Skip("wants to use go tool 6a which hasn't existed for who knows how long")
  110. if !dotest(true) {
  111. return
  112. }
  113. defer endtest()
  114. tab := getTable(t)
  115. if tab.go12line != nil {
  116. // aline's don't exist in the Go 1.2 table.
  117. t.Skip("not relevant to Go 1.2 symbol table")
  118. }
  119. // Find the sym package
  120. pkg := tab.LookupFunc("debug/gosym.TestLineFromAline").Obj
  121. if pkg == nil {
  122. t.Fatalf("nil pkg")
  123. }
  124. // Walk every absolute line and ensure that we hit every
  125. // source line monotonically
  126. lastline := make(map[string]int)
  127. final := -1
  128. for i := 0; i < 10000; i++ {
  129. path, line := pkg.lineFromAline(i)
  130. // Check for end of object
  131. if path == "" {
  132. if final == -1 {
  133. final = i - 1
  134. }
  135. continue
  136. } else if final != -1 {
  137. t.Fatalf("reached end of package at absolute line %d, but absolute line %d mapped to %s:%d", final, i, path, line)
  138. }
  139. // It's okay to see files multiple times (e.g., sys.a)
  140. if line == 1 {
  141. lastline[path] = 1
  142. continue
  143. }
  144. // Check that the is the next line in path
  145. ll, ok := lastline[path]
  146. if !ok {
  147. t.Errorf("file %s starts on line %d", path, line)
  148. } else if line != ll+1 {
  149. t.Fatalf("expected next line of file %s to be %d, got %d", path, ll+1, line)
  150. }
  151. lastline[path] = line
  152. }
  153. if final == -1 {
  154. t.Errorf("never reached end of object")
  155. }
  156. }
  157. func TestLineAline(t *testing.T) {
  158. t.Skip("wants to use go tool 6a which hasn't existed for who knows how long")
  159. if !dotest(true) {
  160. return
  161. }
  162. defer endtest()
  163. tab := getTable(t)
  164. if tab.go12line != nil {
  165. // aline's don't exist in the Go 1.2 table.
  166. t.Skip("not relevant to Go 1.2 symbol table")
  167. }
  168. for _, o := range tab.Files {
  169. // A source file can appear multiple times in a
  170. // object. alineFromLine will always return alines in
  171. // the first file, so track which lines we've seen.
  172. found := make(map[string]int)
  173. for i := 0; i < 1000; i++ {
  174. path, line := o.lineFromAline(i)
  175. if path == "" {
  176. break
  177. }
  178. // cgo files are full of 'Z' symbols, which we don't handle
  179. if len(path) > 4 && path[len(path)-4:] == ".cgo" {
  180. continue
  181. }
  182. if minline, ok := found[path]; path != "" && ok {
  183. if minline >= line {
  184. // We've already covered this file
  185. continue
  186. }
  187. }
  188. found[path] = line
  189. a, err := o.alineFromLine(path, line)
  190. if err != nil {
  191. t.Errorf("absolute line %d in object %s maps to %s:%d, but mapping that back gives error %s", i, o.Paths[0].Name, path, line, err)
  192. } else if a != i {
  193. t.Errorf("absolute line %d in object %s maps to %s:%d, which maps back to absolute line %d\n", i, o.Paths[0].Name, path, line, a)
  194. }
  195. }
  196. }
  197. }
  198. func TestPCLine(t *testing.T) {
  199. t.Skip("wants to use go tool 6a which hasn't existed for who knows how long")
  200. if !dotest(false) {
  201. return
  202. }
  203. defer endtest()
  204. f, tab := crack(pclinetestBinary, t)
  205. text := f.Section(".text")
  206. textdat, err := text.Data()
  207. if err != nil {
  208. t.Fatalf("reading .text: %v", err)
  209. }
  210. // Test PCToLine
  211. sym := tab.LookupFunc("linefrompc")
  212. wantLine := 0
  213. for pc := sym.Entry; pc < sym.End; pc++ {
  214. off := pc - text.Addr // TODO(rsc): should not need off; bug in 8g
  215. if textdat[off] == 255 {
  216. break
  217. }
  218. wantLine += int(textdat[off])
  219. t.Logf("off is %d %#x (max %d)", off, textdat[off], sym.End-pc)
  220. file, line, fn := tab.PCToLine(pc)
  221. if fn == nil {
  222. t.Errorf("failed to get line of PC %#x", pc)
  223. } else if !strings.HasSuffix(file, "pclinetest.asm") || line != wantLine || fn != sym {
  224. t.Errorf("PCToLine(%#x) = %s:%d (%s), want %s:%d (%s)", pc, file, line, fn.Name, "pclinetest.asm", wantLine, sym.Name)
  225. }
  226. }
  227. // Test LineToPC
  228. sym = tab.LookupFunc("pcfromline")
  229. lookupline := -1
  230. wantLine = 0
  231. off := uint64(0) // TODO(rsc): should not need off; bug in 8g
  232. for pc := sym.Value; pc < sym.End; pc += 2 + uint64(textdat[off]) {
  233. file, line, fn := tab.PCToLine(pc)
  234. off = pc - text.Addr
  235. if textdat[off] == 255 {
  236. break
  237. }
  238. wantLine += int(textdat[off])
  239. if line != wantLine {
  240. t.Errorf("expected line %d at PC %#x in pcfromline, got %d", wantLine, pc, line)
  241. off = pc + 1 - text.Addr
  242. continue
  243. }
  244. if lookupline == -1 {
  245. lookupline = line
  246. }
  247. for ; lookupline <= line; lookupline++ {
  248. pc2, fn2, err := tab.LineToPC(file, lookupline)
  249. if lookupline != line {
  250. // Should be nothing on this line
  251. if err == nil {
  252. t.Errorf("expected no PC at line %d, got %#x (%s)", lookupline, pc2, fn2.Name)
  253. }
  254. } else if err != nil {
  255. t.Errorf("failed to get PC of line %d: %s", lookupline, err)
  256. } else if pc != pc2 {
  257. t.Errorf("expected PC %#x (%s) at line %d, got PC %#x (%s)", pc, fn.Name, line, pc2, fn2.Name)
  258. }
  259. }
  260. off = pc + 1 - text.Addr
  261. }
  262. }