|
| 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 | + |
| 15 | +package strip_test |
| 16 | + |
| 17 | +import ( |
| 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 | + |
| 28 | +func TestMain(m *testing.M) { |
| 29 | + bazel_testing.TestMain(m, bazel_testing.Args{ |
| 30 | + Main: ` |
| 31 | +-- BUILD.bazel -- |
| 32 | +load("@io_bazel_rules_go//go:def.bzl", "go_binary") |
| 33 | +
|
| 34 | +go_binary( |
| 35 | + name = "strip", |
| 36 | + srcs = ["strip.go"], |
| 37 | +) |
| 38 | +-- strip.go -- |
| 39 | +package main |
| 40 | +
|
| 41 | +import ( |
| 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 | +
|
| 56 | +var wantStrip = flag.Bool("wantstrip", false, "") |
| 57 | +
|
| 58 | +func 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 | +
|
| 83 | +func 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 | +
|
| 93 | +func 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 | +
|
| 115 | +func 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 | +
|
| 128 | +func 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 | +
|
| 144 | +func 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 | + |
| 162 | +func 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 | + |
| 247 | +var 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 | + |
| 262 | +func 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 | +} |
0 commit comments