Source file
src/debug/gosym/pclntab_test.go
1
2
3
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
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
57
58 func skipIfNotELF(t *testing.T) {
59 switch runtime.GOOS {
60 case "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris", "illumos":
61
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
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
114 t.Skip("not relevant to Go 1.2 symbol table")
115 }
116
117
118 pkg := tab.LookupFunc("debug/gosym.TestLineFromAline").Obj
119 if pkg == nil {
120 t.Fatalf("nil pkg")
121 }
122
123
124
125 lastline := make(map[string]int)
126 final := -1
127 for i := 0; i < 10000; i++ {
128 path, line := pkg.lineFromAline(i)
129
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
139 if line == 1 {
140 lastline[path] = 1
141 continue
142 }
143
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
163 t.Skip("not relevant to Go 1.2 symbol table")
164 }
165
166 for _, o := range tab.Files {
167
168
169
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
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
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
216 sym := tab.LookupFunc("main.linefrompc")
217 wantLine := 0
218 for pc := sym.Entry; pc < sym.End; pc++ {
219 off := pc - text.Addr
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
234 sym = tab.LookupFunc("main.pcfromline")
235 lookupline := -1
236 wantLine = 0
237 off := uint64(0)
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
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
271
272
273
274
275
276
277
278
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 }
317
View as plain text