github.com/geraldss/go/src@v0.0.0-20210511222824-ac7d0ebfc235/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 "compress/gzip" 10 "debug/elf" 11 "internal/testenv" 12 "io" 13 "os" 14 "os/exec" 15 "path/filepath" 16 "runtime" 17 "strings" 18 "testing" 19 ) 20 21 var ( 22 pclineTempDir string 23 pclinetestBinary string 24 ) 25 26 func dotest(t *testing.T) { 27 testenv.MustHaveGoBuild(t) 28 // For now, only works on amd64 platforms. 29 if runtime.GOARCH != "amd64" { 30 t.Skipf("skipping on non-AMD64 system %s", runtime.GOARCH) 31 } 32 var err error 33 pclineTempDir, err = os.MkdirTemp("", "pclinetest") 34 if err != nil { 35 t.Fatal(err) 36 } 37 pclinetestBinary = filepath.Join(pclineTempDir, "pclinetest") 38 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", pclinetestBinary) 39 cmd.Dir = "testdata" 40 cmd.Env = append(os.Environ(), "GOOS=linux") 41 cmd.Stdout = os.Stdout 42 cmd.Stderr = os.Stderr 43 if err := cmd.Run(); err != nil { 44 t.Fatal(err) 45 } 46 } 47 48 func endtest() { 49 if pclineTempDir != "" { 50 os.RemoveAll(pclineTempDir) 51 pclineTempDir = "" 52 pclinetestBinary = "" 53 } 54 } 55 56 // skipIfNotELF skips the test if we are not running on an ELF system. 57 // These tests open and examine the test binary, and use elf.Open to do so. 58 func skipIfNotELF(t *testing.T) { 59 switch runtime.GOOS { 60 case "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris", "illumos": 61 // OK. 62 default: 63 t.Skipf("skipping on non-ELF system %s", runtime.GOOS) 64 } 65 } 66 67 func getTable(t *testing.T) *Table { 68 f, tab := crack(os.Args[0], t) 69 f.Close() 70 return tab 71 } 72 73 func crack(file string, t *testing.T) (*elf.File, *Table) { 74 // Open self 75 f, err := elf.Open(file) 76 if err != nil { 77 t.Fatal(err) 78 } 79 return parse(file, f, t) 80 } 81 82 func parse(file string, f *elf.File, t *testing.T) (*elf.File, *Table) { 83 s := f.Section(".gosymtab") 84 if s == nil { 85 t.Skip("no .gosymtab section") 86 } 87 symdat, err := s.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 func TestLineFromAline(t *testing.T) { 109 skipIfNotELF(t) 110 111 tab := getTable(t) 112 if tab.go12line != nil { 113 // aline's don't exist in the Go 1.2 table. 114 t.Skip("not relevant to Go 1.2 symbol table") 115 } 116 117 // Find the sym package 118 pkg := tab.LookupFunc("debug/gosym.TestLineFromAline").Obj 119 if pkg == nil { 120 t.Fatalf("nil pkg") 121 } 122 123 // Walk every absolute line and ensure that we hit every 124 // source line monotonically 125 lastline := make(map[string]int) 126 final := -1 127 for i := 0; i < 10000; i++ { 128 path, line := pkg.lineFromAline(i) 129 // Check for end of object 130 if path == "" { 131 if final == -1 { 132 final = i - 1 133 } 134 continue 135 } else if final != -1 { 136 t.Fatalf("reached end of package at absolute line %d, but absolute line %d mapped to %s:%d", final, i, path, line) 137 } 138 // It's okay to see files multiple times (e.g., sys.a) 139 if line == 1 { 140 lastline[path] = 1 141 continue 142 } 143 // Check that the is the next line in path 144 ll, ok := lastline[path] 145 if !ok { 146 t.Errorf("file %s starts on line %d", path, line) 147 } else if line != ll+1 { 148 t.Fatalf("expected next line of file %s to be %d, got %d", path, ll+1, line) 149 } 150 lastline[path] = line 151 } 152 if final == -1 { 153 t.Errorf("never reached end of object") 154 } 155 } 156 157 func TestLineAline(t *testing.T) { 158 skipIfNotELF(t) 159 160 tab := getTable(t) 161 if tab.go12line != nil { 162 // aline's don't exist in the Go 1.2 table. 163 t.Skip("not relevant to Go 1.2 symbol table") 164 } 165 166 for _, o := range tab.Files { 167 // A source file can appear multiple times in a 168 // object. alineFromLine will always return alines in 169 // the first file, so track which lines we've seen. 170 found := make(map[string]int) 171 for i := 0; i < 1000; i++ { 172 path, line := o.lineFromAline(i) 173 if path == "" { 174 break 175 } 176 177 // cgo files are full of 'Z' symbols, which we don't handle 178 if len(path) > 4 && path[len(path)-4:] == ".cgo" { 179 continue 180 } 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 190 a, err := o.alineFromLine(path, line) 191 if err != nil { 192 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) 193 } else if a != i { 194 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) 195 } 196 } 197 } 198 } 199 200 func TestPCLine(t *testing.T) { 201 if testing.Short() { 202 t.Skip("skipping in -short mode") 203 } 204 dotest(t) 205 defer endtest() 206 207 f, tab := crack(pclinetestBinary, t) 208 defer f.Close() 209 text := f.Section(".text") 210 textdat, err := text.Data() 211 if err != nil { 212 t.Fatalf("reading .text: %v", err) 213 } 214 215 // Test PCToLine 216 sym := tab.LookupFunc("main.linefrompc") 217 wantLine := 0 218 for pc := sym.Entry; pc < sym.End; pc++ { 219 off := pc - text.Addr // TODO(rsc): should not need off; bug in 8g 220 if textdat[off] == 255 { 221 break 222 } 223 wantLine += int(textdat[off]) 224 t.Logf("off is %d %#x (max %d)", off, textdat[off], sym.End-pc) 225 file, line, fn := tab.PCToLine(pc) 226 if fn == nil { 227 t.Errorf("failed to get line of PC %#x", pc) 228 } else if !strings.HasSuffix(file, "pclinetest.s") || line != wantLine || fn != sym { 229 t.Errorf("PCToLine(%#x) = %s:%d (%s), want %s:%d (%s)", pc, file, line, fn.Name, "pclinetest.s", wantLine, sym.Name) 230 } 231 } 232 233 // Test LineToPC 234 sym = tab.LookupFunc("main.pcfromline") 235 lookupline := -1 236 wantLine = 0 237 off := uint64(0) // TODO(rsc): should not need off; bug in 8g 238 for pc := sym.Value; pc < sym.End; pc += 2 + uint64(textdat[off]) { 239 file, line, fn := tab.PCToLine(pc) 240 off = pc - text.Addr 241 if textdat[off] == 255 { 242 break 243 } 244 wantLine += int(textdat[off]) 245 if line != wantLine { 246 t.Errorf("expected line %d at PC %#x in pcfromline, got %d", wantLine, pc, line) 247 off = pc + 1 - text.Addr 248 continue 249 } 250 if lookupline == -1 { 251 lookupline = line 252 } 253 for ; lookupline <= line; lookupline++ { 254 pc2, fn2, err := tab.LineToPC(file, lookupline) 255 if lookupline != line { 256 // Should be nothing on this line 257 if err == nil { 258 t.Errorf("expected no PC at line %d, got %#x (%s)", lookupline, pc2, fn2.Name) 259 } 260 } else if err != nil { 261 t.Errorf("failed to get PC of line %d: %s", lookupline, err) 262 } else if pc != pc2 { 263 t.Errorf("expected PC %#x (%s) at line %d, got PC %#x (%s)", pc, fn.Name, line, pc2, fn2.Name) 264 } 265 } 266 off = pc + 1 - text.Addr 267 } 268 } 269 270 // Test that we can parse a pclntab from 1.15. 271 // The file was compiled in /tmp/hello.go: 272 // [BEGIN] 273 // package main 274 // 275 // func main() { 276 // println("hello") 277 // } 278 // [END] 279 func Test115PclnParsing(t *testing.T) { 280 zippedDat, err := os.ReadFile("testdata/pcln115.gz") 281 if err != nil { 282 t.Fatal(err) 283 } 284 var gzReader *gzip.Reader 285 gzReader, err = gzip.NewReader(bytes.NewBuffer(zippedDat)) 286 if err != nil { 287 t.Fatal(err) 288 } 289 var dat []byte 290 dat, err = io.ReadAll(gzReader) 291 if err != nil { 292 t.Fatal(err) 293 } 294 const textStart = 0x1001000 295 pcln := NewLineTable(dat, textStart) 296 var tab *Table 297 tab, err = NewTable(nil, pcln) 298 if err != nil { 299 t.Fatal(err) 300 } 301 var f *Func 302 var pc uint64 303 pc, f, err = tab.LineToPC("/tmp/hello.go", 3) 304 if err != nil { 305 t.Fatal(err) 306 } 307 if pcln.version != ver12 { 308 t.Fatal("Expected pcln to parse as an older version") 309 } 310 if pc != 0x105c280 { 311 t.Fatalf("expect pc = 0x105c280, got 0x%x", pc) 312 } 313 if f.Name != "main.main" { 314 t.Fatalf("expected to parse name as main.main, got %v", f.Name) 315 } 316 }