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