pclntab_test.go 5.31 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// Copyright 2009 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gosym

import (
	"debug/elf";
	"os";
	"testing";
	"syscall";
)

func dotest() bool {
	// For now, only works on ELF platforms.
Russ Cox's avatar
Russ Cox committed
16
	return syscall.OS == "linux" && os.Getenv("GOARCH") == "amd64";
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
}

func getTable(t *testing.T) *Table {
	f, tab := crack(os.Args[0], t);
	f.Close();
	return tab;
}

func crack(file string, t *testing.T) (*elf.File, *Table) {
	// Open self
	f, err := elf.Open(file);
	if err != nil {
		t.Fatal(err);
	}
	return parse(file, f, t);
}

func parse(file string, f *elf.File, t *testing.T) (*elf.File, *Table) {
	symdat, err := f.Section(".gosymtab").Data();
	if err != nil {
		f.Close();
		t.Fatalf("reading %s gosymtab: %v", file, err);
	}
	pclndat, err := f.Section(".gopclntab").Data();
	if err != nil {
		f.Close();
		t.Fatalf("reading %s gopclntab: %v", file, err);
	}

	pcln := NewLineTable(pclndat, f.Section(".text").Addr);
	tab, err := NewTable(symdat, pcln);
	if err != nil {
		f.Close();
		t.Fatalf("parsing %s gosymtab: %v", file, err);
	}

	return f, tab;
}

var goarch = os.Getenv("O")

func TestLineFromAline(t *testing.T) {
	if !dotest() {
		return;
	}

	tab := getTable(t);

	// Find the sym package
	pkg := tab.LookupFunc("gosym.TestLineFromAline").Obj;
	if pkg == nil {
		t.Fatalf("nil pkg");
	}

	// Walk every absolute line and ensure that we hit every
	// source line monotonically
Russ Cox's avatar
Russ Cox committed
73
	lastline := make(map[string]int);
74 75 76 77 78 79
	final := -1;
	for i := 0; i < 10000; i++ {
		path, line := pkg.lineFromAline(i);
		// Check for end of object
		if path == "" {
			if final == -1 {
Russ Cox's avatar
Russ Cox committed
80
				final = i-1;
81 82 83 84 85 86 87 88 89 90 91 92 93 94
			}
			continue;
		} else if final != -1 {
			t.Fatalf("reached end of package at absolute line %d, but absolute line %d mapped to %s:%d", final, i, path, line);
		}
		// It's okay to see files multiple times (e.g., sys.a)
		if line == 1 {
			lastline[path] = 1;
			continue;
		}
		// Check that the is the next line in path
		ll, ok := lastline[path];
		if !ok {
			t.Errorf("file %s starts on line %d", path, line);
Russ Cox's avatar
Russ Cox committed
95 96
		} else if line != ll+1 {
			t.Errorf("expected next line of file %s to be %d, got %d", path, ll+1, line);
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
		}
		lastline[path] = line;
	}
	if final == -1 {
		t.Errorf("never reached end of object");
	}
}

func TestLineAline(t *testing.T) {
	if !dotest() {
		return;
	}

	tab := getTable(t);

	for _, o := range tab.Files {
		// A source file can appear multiple times in a
		// object.  alineFromLine will always return alines in
		// the first file, so track which lines we've seen.
Russ Cox's avatar
Russ Cox committed
116
		found := make(map[string]int);
117 118 119 120 121 122 123
		for i := 0; i < 1000; i++ {
			path, line := o.lineFromAline(i);
			if path == "" {
				break;
			}

			// cgo files are full of 'Z' symbols, which we don't handle
Russ Cox's avatar
Russ Cox committed
124
			if len(path) > 4 && path[len(path)-4 : len(path)] == ".cgo" {
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
				continue;
			}

			if minline, ok := found[path]; path != "" && ok {
				if minline >= line {
					// We've already covered this file
					continue;
				}
			}
			found[path] = line;

			a, err := o.alineFromLine(path, line);
			if err != nil {
				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);
			} else if a != i {
				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);
			}
		}
	}
}

// gotest: if [ "$(uname)-$(uname -m)" = Linux-x86_64 ]; then
// gotest:    mkdir -p _test && $AS pclinetest.s && $LD -E main -l -o _test/pclinetest pclinetest.$O
// gotest: fi
func TestPCLine(t *testing.T) {
	if !dotest() {
		return;
	}

	f, tab := crack("_test/pclinetest", t);
	text := f.Section(".text");
	textdat, err := text.Data();
	if err != nil {
		t.Fatalf("reading .text: %v", err);
	}

	// Test PCToLine
	sym := tab.LookupFunc("linefrompc");
	wantLine := 0;
	for pc := sym.Entry; pc < sym.End; pc++ {
		file, line, fn := tab.PCToLine(pc);
		off := pc - text.Addr;	// TODO(rsc): should not need off; bug in 8g
		wantLine += int(textdat[off]);
		if fn == nil {
			t.Errorf("failed to get line of PC %#x", pc);
Russ Cox's avatar
Russ Cox committed
170
		} else if len(file) < 12 || file[len(file)-12 : len(file)] != "pclinetest.s" || line != wantLine || fn != sym {
171 172 173 174 175 176 177 178 179
			t.Errorf("expected %s:%d (%s) at PC %#x, got %s:%d (%s)", "pclinetest.s", wantLine, sym.Name, pc, file, line, fn.Name);
		}
	}

	// Test LineToPC
	sym = tab.LookupFunc("pcfromline");
	lookupline := -1;
	wantLine = 0;
	off := uint64(0);	// TODO(rsc): should not need off; bug in 8g
Russ Cox's avatar
Russ Cox committed
180
	for pc := sym.Value; pc < sym.End; pc += 2+uint64(textdat[off]) {
181
		file, line, fn := tab.PCToLine(pc);
Russ Cox's avatar
Russ Cox committed
182
		off = pc - text.Addr;
183 184 185
		wantLine += int(textdat[off]);
		if line != wantLine {
			t.Errorf("expected line %d at PC %#x in pcfromline, got %d", wantLine, pc, line);
Russ Cox's avatar
Russ Cox committed
186
			off = pc + 1 - text.Addr;
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
			continue;
		}
		if lookupline == -1 {
			lookupline = line;
		}
		for ; lookupline <= line; lookupline++ {
			pc2, fn2, err := tab.LineToPC(file, lookupline);
			if lookupline != line {
				// Should be nothing on this line
				if err == nil {
					t.Errorf("expected no PC at line %d, got %#x (%s)", lookupline, pc2, fn2.Name);
				}
			} else if err != nil {
				t.Errorf("failed to get PC of line %d: %s", lookupline, err);
			} else if pc != pc2 {
				t.Errorf("expected PC %#x (%s) at line %d, got PC %#x (%s)", pc, fn.Name, line, pc2, fn2.Name);
			}
		}
Russ Cox's avatar
Russ Cox committed
205
		off = pc + 1 - text.Addr;
206 207
	}
}