Home | History | Annotate | Download | only in gosym
      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