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