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