1// Copyright 2019 The Bazel Authors. All rights reserved. 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 15package strip_test 16 17import ( 18 "bytes" 19 "errors" 20 "fmt" 21 "os/exec" 22 "strings" 23 "testing" 24 25 "github.com/bazelbuild/rules_go/go/tools/bazel_testing" 26) 27 28func TestMain(m *testing.M) { 29 bazel_testing.TestMain(m, bazel_testing.Args{ 30 Main: ` 31-- BUILD.bazel -- 32load("@io_bazel_rules_go//go:def.bzl", "go_binary") 33 34go_binary( 35 name = "strip", 36 srcs = ["strip.go"], 37) 38-- strip.go -- 39package main 40 41import ( 42 "debug/elf" 43 "debug/macho" 44 "debug/pe" 45 "errors" 46 "flag" 47 "fmt" 48 "io" 49 "os" 50 "regexp" 51 "runtime" 52 "runtime/debug" 53 "strings" 54) 55 56var wantStrip = flag.Bool("wantstrip", false, "") 57 58func main() { 59 flag.Parse() 60 stackTrace, err := panicAndRecover() 61 if err != nil { 62 panic(err) 63 } 64 gotStackTrace := strings.Split(stackTrace, "\n") 65 if len(gotStackTrace) != len(wantStackTrace) { 66 panic(fmt.Sprintf("got %d lines of stack trace, want %d", len(gotStackTrace), len(wantStackTrace))) 67 } 68 for i := range gotStackTrace { 69 expectedLine := regexp.MustCompile(wantStackTrace[i]) 70 if !expectedLine.MatchString(gotStackTrace[i]) { 71 panic(fmt.Sprintf("got unexpected stack trace line %q at index %d", gotStackTrace[i], i)) 72 } 73 } 74 stripped, err := isStripped() 75 if err != nil { 76 panic(err) 77 } 78 if stripped != *wantStrip { 79 panic(fmt.Sprintf("got stripped=%t, want stripped=%t", stripped, *wantStrip)) 80 } 81} 82 83func panicAndRecover() (stackTrace string, err error) { 84 defer func() { 85 if r := recover(); r != nil { 86 stackTrace = string(debug.Stack()) 87 } 88 }() 89 panic("test") 90 return "", errors.New("should not reach here") 91} 92 93func isStripped() (bool, error) { 94 ownLocation, err := os.Executable() 95 if err != nil { 96 return false, err 97 } 98 ownBinary, err := os.Open(ownLocation) 99 if err != nil { 100 return false, err 101 } 102 defer ownBinary.Close() 103 switch runtime.GOOS { 104 case "darwin": 105 return isStrippedMachO(ownBinary) 106 case "linux": 107 return isStrippedElf(ownBinary) 108 case "windows": 109 return isStrippedPE(ownBinary) 110 default: 111 return false, fmt.Errorf("unsupported OS: %s", runtime.GOOS) 112 } 113} 114 115func isStrippedMachO(f io.ReaderAt) (bool, error) { 116 macho, err := macho.NewFile(f) 117 if err != nil { 118 return false, err 119 } 120 gotDwarf := macho.Segment("__DWARF") != nil 121 gotDebugInfo := macho.Section("__zdebug_info") != nil 122 if gotDwarf != gotDebugInfo { 123 return false, fmt.Errorf("inconsistent stripping: gotDwarf=%v, gotDebugInfo=%v", gotDwarf, gotDebugInfo) 124 } 125 return !gotDwarf, nil 126} 127 128func isStrippedElf(f io.ReaderAt) (bool, error) { 129 elf, err := elf.NewFile(f) 130 if err != nil { 131 return false, err 132 } 133 var gotSymtab bool 134 for _, section := range elf.Sections { 135 if section.Name == ".symtab" { 136 gotSymtab = true 137 break 138 } 139 } 140 return !gotSymtab, nil 141} 142 143 144func isStrippedPE(f io.ReaderAt) (bool, error) { 145 pe, err := pe.NewFile(f) 146 if err != nil { 147 return false, err 148 } 149 symtab := pe.Section(".symtab") 150 if symtab == nil { 151 return false, fmt.Errorf("no .symtab section") 152 } 153 emptySymtab := (symtab.VirtualSize <= 4) && (symtab.Size <= 512) 154 return emptySymtab, nil 155} 156 157 158` + embedWantedStackTraces(), 159 }) 160} 161 162func Test(t *testing.T) { 163 for _, test := range []struct { 164 desc, stripFlag, compilationMode string 165 wantStrip bool 166 }{ 167 { 168 desc: "run_auto", 169 wantStrip: true, 170 }, 171 { 172 desc: "run_fastbuild", 173 compilationMode: "fastbuild", 174 wantStrip: true, 175 }, 176 { 177 desc: "run_dbg", 178 compilationMode: "dbg", 179 }, 180 { 181 desc: "run_opt", 182 compilationMode: "opt", 183 }, 184 { 185 desc: "run_always", 186 stripFlag: "always", 187 wantStrip: true, 188 }, 189 { 190 desc: "run_always_opt", 191 stripFlag: "always", 192 compilationMode: "opt", 193 wantStrip: true, 194 }, 195 { 196 desc: "run_never", 197 stripFlag: "never", 198 }, 199 { 200 desc: "run_sometimes_fastbuild", 201 stripFlag: "sometimes", 202 compilationMode: "fastbuild", 203 wantStrip: true, 204 }, 205 { 206 desc: "run_sometimes_dbg", 207 stripFlag: "sometimes", 208 compilationMode: "dbg", 209 }, 210 { 211 desc: "run_sometimes_opt", 212 stripFlag: "sometimes", 213 compilationMode: "opt", 214 }, 215 } { 216 t.Run(test.desc, func(t *testing.T) { 217 args := []string{"run"} 218 if len(test.stripFlag) > 0 { 219 args = append(args, "--strip", test.stripFlag) 220 } 221 if len(test.compilationMode) > 0 { 222 args = append(args, "--compilation_mode", test.compilationMode) 223 } 224 args = append(args, "//:strip", "--", fmt.Sprintf("-wantstrip=%v", test.wantStrip)) 225 cmd := bazel_testing.BazelCmd(args...) 226 stderr := &bytes.Buffer{} 227 cmd.Stderr = stderr 228 t.Logf("running: bazel %s", strings.Join(args, " ")) 229 if err := cmd.Run(); err != nil { 230 var xerr *exec.ExitError 231 if !errors.As(err, &xerr) { 232 t.Fatalf("unexpected error: %v", err) 233 } 234 if xerr.ExitCode() == bazel_testing.BUILD_FAILURE { 235 t.Fatalf("unexpected build failure: %v\nstderr:\n%s", err, stderr.Bytes()) 236 return 237 } else if xerr.ExitCode() == bazel_testing.TESTS_FAILED { 238 t.Fatalf("error running %s:\n%s", strings.Join(cmd.Args, " "), stderr.Bytes()) 239 } else { 240 t.Fatalf("unexpected error: %v\nstderr:\n%s", err, stderr.Bytes()) 241 } 242 } 243 }) 244 } 245} 246 247var wantStackTrace = []string{ 248 `^goroutine \d+ \[running\]:$`, 249 `^runtime/debug\.Stack\(\)$`, 250 `^ GOROOT/src/runtime/debug/stack\.go:\d+ \+0x[0-9a-f]+$`, 251 `^main\.panicAndRecover\.func1\(\)$`, 252 `^ strip\.go:\d+ \+0x[0-9a-f]+$`, 253 `^panic\({0x[0-9a-f]+, 0x[0-9a-f]+}\)$`, 254 `^ GOROOT/src/runtime/panic\.go:\d+ \+0x[0-9a-f]+$`, 255 `^main\.panicAndRecover\(\)$`, 256 `^ strip\.go:\d+ \+0x[0-9a-f]+$`, 257 `^main\.main\(\)$`, 258 `^ strip\.go:\d+ \+0x[0-9a-f]+$`, 259 `^$`, 260} 261 262func embedWantedStackTraces() string { 263 buf := &bytes.Buffer{} 264 fmt.Fprintln(buf, "var wantStackTrace = []string{") 265 for _, s := range wantStackTrace { 266 fmt.Fprintf(buf, "`%s`,\n", s) 267 } 268 fmt.Fprintln(buf, "}") 269 return buf.String() 270} 271