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