1// Copyright 2019 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// +build linux
16
17package dwarf_test
18
19import (
20	"fmt"
21	"io/ioutil"
22	"os"
23	"os/exec"
24	"path/filepath"
25	"runtime"
26	"strings"
27	"testing"
28
29	"cloud.google.com/go/cmd/go-cloud-debug-agent/internal/debug/dwarf"
30	"cloud.google.com/go/cmd/go-cloud-debug-agent/internal/debug/elf"
31)
32
33var (
34	pcspTempDir    string
35	pcsptestBinary string
36)
37
38func doPCToSPTest(self bool) bool {
39	// For now, only works on amd64 platforms.
40	if runtime.GOARCH != "amd64" {
41		return false
42	}
43	// Self test reads test binary; only works on Linux or Mac.
44	if self {
45		if runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
46			return false
47		}
48	}
49	// Command below expects "sh", so Unix.
50	if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
51		return false
52	}
53	if pcsptestBinary != "" {
54		return true
55	}
56	var err error
57	pcspTempDir, err = ioutil.TempDir("", "pcsptest")
58	if err != nil {
59		panic(err)
60	}
61	if strings.Contains(pcspTempDir, " ") {
62		panic("unexpected space in tempdir")
63	}
64	// This command builds pcsptest from testdata/pcsptest.go.
65	pcsptestBinary = filepath.Join(pcspTempDir, "pcsptest")
66	command := fmt.Sprintf("go tool compile -o %s.6 testdata/pcsptest.go && go tool link -H %s -o %s %s.6",
67		pcsptestBinary, runtime.GOOS, pcsptestBinary, pcsptestBinary)
68	cmd := exec.Command("sh", "-c", command)
69	cmd.Stdout = os.Stdout
70	cmd.Stderr = os.Stderr
71	if err := cmd.Run(); err != nil {
72		panic(err)
73	}
74	return true
75}
76
77func endPCToSPTest() {
78	if pcspTempDir != "" {
79		os.RemoveAll(pcspTempDir)
80		pcspTempDir = ""
81		pcsptestBinary = ""
82	}
83}
84
85func TestPCToSPOffset(t *testing.T) {
86	t.Skip("gets a stack layout it doesn't expect")
87
88	if !doPCToSPTest(false) {
89		return
90	}
91	defer endPCToSPTest()
92
93	data, err := getData(pcsptestBinary)
94	if err != nil {
95		t.Fatal(err)
96	}
97	entry, err := data.LookupFunction("main.test")
98	if err != nil {
99		t.Fatal("lookup startPC:", err)
100	}
101	startPC, ok := entry.Val(dwarf.AttrLowpc).(uint64)
102	if !ok {
103		t.Fatal(`DWARF data for function "main.test" has no low PC`)
104	}
105	endPC, ok := entry.Val(dwarf.AttrHighpc).(uint64)
106	if !ok {
107		t.Fatal(`DWARF data for function "main.test" has no high PC`)
108	}
109
110	const addrSize = 8 // TODO: Assumes amd64.
111	const argSize = 8  // Defined by int64 arguments in test binary.
112
113	// On 64-bit machines, the first offset must be one address size,
114	// for the return PC.
115	offset, err := data.PCToSPOffset(startPC)
116	if err != nil {
117		t.Fatal("startPC:", err)
118	}
119	if offset != addrSize {
120		t.Fatalf("expected %d at start of function; got %d", addrSize, offset)
121	}
122	// On 64-bit machines, expect some 8s and some 32s. (See the
123	// comments in testdata/pcsptest.go.
124	// TODO: The test could be stronger, but not much unless we
125	// disassemble the binary.
126	count := make(map[int64]int)
127	for pc := startPC; pc < endPC; pc++ {
128		offset, err := data.PCToSPOffset(pc)
129		if err != nil {
130			t.Fatal("scanning function:", err)
131		}
132		count[offset]++
133	}
134	if len(count) != 2 {
135		t.Errorf("expected 2 offset values, got %d; counts are: %v", len(count), count)
136	}
137	if count[addrSize] == 0 {
138		t.Errorf("expected some values at offset %d; got %v", addrSize, count)
139	}
140	if count[addrSize+3*argSize] == 0 {
141		t.Errorf("expected some values at offset %d; got %v", addrSize+3*argSize, count)
142	}
143}
144
145func getData(file string) (*dwarf.Data, error) {
146	switch runtime.GOOS {
147	case "linux":
148		f, err := elf.Open(file)
149		if err != nil {
150			return nil, err
151		}
152		dwarf, err := f.DWARF()
153		if err != nil {
154			return nil, err
155		}
156		f.Close()
157		return dwarf, nil
158	}
159	panic("unimplemented DWARF for GOOS=" + runtime.GOOS)
160}
161