github.com/hbdrawn/golang@v0.0.0-20141214014649-6b835209aba2/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 "fmt" 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(self bool) bool { 25 // For now, only works on amd64 platforms. 26 if runtime.GOARCH != "amd64" { 27 return false 28 } 29 // Self test reads test binary; only works on Linux. 30 if self && runtime.GOOS != "linux" { 31 return false 32 } 33 // Command below expects "sh", so Unix. 34 if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { 35 return false 36 } 37 if pclinetestBinary != "" { 38 return true 39 } 40 var err error 41 pclineTempDir, err = ioutil.TempDir("", "pclinetest") 42 if err != nil { 43 panic(err) 44 } 45 if strings.Contains(pclineTempDir, " ") { 46 panic("unexpected space in tempdir") 47 } 48 // This command builds pclinetest from pclinetest.asm; 49 // the resulting binary looks like it was built from pclinetest.s, 50 // but we have renamed it to keep it away from the go tool. 51 pclinetestBinary = filepath.Join(pclineTempDir, "pclinetest") 52 command := fmt.Sprintf("go tool 6a -o %s.6 pclinetest.asm && go tool 6l -H linux -E main -o %s %s.6", 53 pclinetestBinary, pclinetestBinary, pclinetestBinary) 54 cmd := exec.Command("sh", "-c", command) 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 symdat, err := f.Section(".gosymtab").Data() 88 if err != nil { 89 f.Close() 90 t.Fatalf("reading %s gosymtab: %v", file, err) 91 } 92 pclndat, err := f.Section(".gopclntab").Data() 93 if err != nil { 94 f.Close() 95 t.Fatalf("reading %s gopclntab: %v", file, err) 96 } 97 98 pcln := NewLineTable(pclndat, f.Section(".text").Addr) 99 tab, err := NewTable(symdat, pcln) 100 if err != nil { 101 f.Close() 102 t.Fatalf("parsing %s gosymtab: %v", file, err) 103 } 104 105 return f, tab 106 } 107 108 var goarch = os.Getenv("O") 109 110 func TestLineFromAline(t *testing.T) { 111 if !dotest(true) { 112 return 113 } 114 defer endtest() 115 116 tab := getTable(t) 117 if tab.go12line != nil { 118 // aline's don't exist in the Go 1.2 table. 119 t.Skip("not relevant to Go 1.2 symbol table") 120 } 121 122 // Find the sym package 123 pkg := tab.LookupFunc("debug/gosym.TestLineFromAline").Obj 124 if pkg == nil { 125 t.Fatalf("nil pkg") 126 } 127 128 // Walk every absolute line and ensure that we hit every 129 // source line monotonically 130 lastline := make(map[string]int) 131 final := -1 132 for i := 0; i < 10000; i++ { 133 path, line := pkg.lineFromAline(i) 134 // Check for end of object 135 if path == "" { 136 if final == -1 { 137 final = i - 1 138 } 139 continue 140 } else if final != -1 { 141 t.Fatalf("reached end of package at absolute line %d, but absolute line %d mapped to %s:%d", final, i, path, line) 142 } 143 // It's okay to see files multiple times (e.g., sys.a) 144 if line == 1 { 145 lastline[path] = 1 146 continue 147 } 148 // Check that the is the next line in path 149 ll, ok := lastline[path] 150 if !ok { 151 t.Errorf("file %s starts on line %d", path, line) 152 } else if line != ll+1 { 153 t.Fatalf("expected next line of file %s to be %d, got %d", path, ll+1, line) 154 } 155 lastline[path] = line 156 } 157 if final == -1 { 158 t.Errorf("never reached end of object") 159 } 160 } 161 162 func TestLineAline(t *testing.T) { 163 if !dotest(true) { 164 return 165 } 166 defer endtest() 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 if !dotest(false) { 210 return 211 } 212 defer endtest() 213 214 f, tab := crack(pclinetestBinary, t) 215 text := f.Section(".text") 216 textdat, err := text.Data() 217 if err != nil { 218 t.Fatalf("reading .text: %v", err) 219 } 220 221 // Test PCToLine 222 sym := tab.LookupFunc("linefrompc") 223 wantLine := 0 224 for pc := sym.Entry; pc < sym.End; pc++ { 225 off := pc - text.Addr // TODO(rsc): should not need off; bug in 8g 226 if textdat[off] == 255 { 227 break 228 } 229 wantLine += int(textdat[off]) 230 t.Logf("off is %d %#x (max %d)", off, textdat[off], sym.End-pc) 231 file, line, fn := tab.PCToLine(pc) 232 if fn == nil { 233 t.Errorf("failed to get line of PC %#x", pc) 234 } else if !strings.HasSuffix(file, "pclinetest.asm") || line != wantLine || fn != sym { 235 t.Errorf("PCToLine(%#x) = %s:%d (%s), want %s:%d (%s)", pc, file, line, fn.Name, "pclinetest.asm", wantLine, sym.Name) 236 } 237 } 238 239 // Test LineToPC 240 sym = tab.LookupFunc("pcfromline") 241 lookupline := -1 242 wantLine = 0 243 off := uint64(0) // TODO(rsc): should not need off; bug in 8g 244 for pc := sym.Value; pc < sym.End; pc += 2 + uint64(textdat[off]) { 245 file, line, fn := tab.PCToLine(pc) 246 off = pc - text.Addr 247 if textdat[off] == 255 { 248 break 249 } 250 wantLine += int(textdat[off]) 251 if line != wantLine { 252 t.Errorf("expected line %d at PC %#x in pcfromline, got %d", wantLine, pc, line) 253 off = pc + 1 - text.Addr 254 continue 255 } 256 if lookupline == -1 { 257 lookupline = line 258 } 259 for ; lookupline <= line; lookupline++ { 260 pc2, fn2, err := tab.LineToPC(file, lookupline) 261 if lookupline != line { 262 // Should be nothing on this line 263 if err == nil { 264 t.Errorf("expected no PC at line %d, got %#x (%s)", lookupline, pc2, fn2.Name) 265 } 266 } else if err != nil { 267 t.Errorf("failed to get PC of line %d: %s", lookupline, err) 268 } else if pc != pc2 { 269 t.Errorf("expected PC %#x (%s) at line %d, got PC %#x (%s)", pc, fn.Name, line, pc2, fn2.Name) 270 } 271 } 272 off = pc + 1 - text.Addr 273 } 274 }