[cmd] Delete dump_fuchsia_symbols, which has moved to //tools
New location/name:
https://fuchsia.googlesource.com/tools/+/master/cmd/dump_breakpad_symbols
INTK-388
Change-Id: I1c545796f5c79e47390a884d813ce91faef763f6
diff --git a/cmd/dump_fuchsia_symbols/main.go b/cmd/dump_fuchsia_symbols/main.go
deleted file mode 100755
index 668a6ca..0000000
--- a/cmd/dump_fuchsia_symbols/main.go
+++ /dev/null
@@ -1,320 +0,0 @@
-///bin/true ; exec /usr/bin/env go run "$0" "$@"
-// Copyright 2018 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package main
-
-import (
- // TODO(kjharland): Use a safer hash algorithm. sha256 or sha2, etc.
- "crypto/sha1"
- "encoding/hex"
- "encoding/json"
- "errors"
- "flag"
- "fmt"
- "io/ioutil"
- "log"
- "os"
- "os/exec"
- "path"
- "strings"
-)
-
-const usage = `usage: dump_fuchsia_symbols [options] file1 file2 ... fileN
-
-Dumps symbol data from a collection of IDs files. IDs files are generated as
-part of the build and contain a number of newline-separate records which have
-the syntax:
-
- <hash-value> <absolute-path>
-
-This command does not care about <hash-value>. <absolute-path> is the path to a
-binary generated as part of the Fuchsia build. This command collects every
-<absolute-path> from each of file1, file2 ... fileN and dumps symbol data for
-the binaries at each of those paths. Duplicate paths are skipped.
-
-The output is a collection of symbol files, one for each binary, using an
-arbitrary naming scheme to ensure that every output file name is unique.
-
-Example invocation:
-
-$ dump_fuchsia_symbols \
- -out-dir=/path/to/output/ \
- -dump-syms-path=/path/to/breakpad/dump_syms \
- -summary-file=/path/to/summary \
- /path/to/ids1.txt
-`
-
-// Options represents the command line options.
-type Options struct {
- outdir string
- dryRun bool
- dumpSymsPath string
- summaryFile string
-}
-
-func main() {
- RunMain(os.Args)
-}
-
-// RunMain implements the main() function. Visible for testing.
-func RunMain(args []string) {
- f, options, err := ParseFlags(args)
- if err != nil {
- log.Fatal(err)
- }
-
- if processIdsFiles(f.Args(), options) {
- log.Println("finished with errors")
- os.Exit(1)
- }
-}
-
-// ParseFlags parses command line parameters. Visible for testing.
-func ParseFlags(args []string) (*flag.FlagSet, *Options, error) {
- f := flag.NewFlagSet(args[0], flag.ContinueOnError)
- f.Usage = func() {
- fmt.Println(usage)
- flag.PrintDefaults()
- os.Exit(0)
- }
-
- var options Options
- // First set the flags ...
- f.StringVar(&options.summaryFile, "summary-file", "",
- "Path to a JSON file to write that maps each binary to its symbol file. "+
- "The output looks like {'/path/to/binary': '$out-dir/path/to/file'}. "+
- "Prints to stdout by default.",
- )
- f.StringVar(&options.outdir, "out-dir", "",
- "The directory where symbol output should be written")
- f.StringVar(&options.dumpSymsPath, "dump-syms-path", "",
- "Path to the breakpad tools `dump_syms` executable")
- f.BoolVar(&options.dryRun, "dry-run", false,
- "Print the dump_syms commands to run, without running them, then exit. "+
- "summary-file is always written to stdout during a dry-run.",
- )
- f.Parse(args[1:])
-
- // Ensure at least one file was given.
- if f.NArg() < 1 {
- return nil, nil, errors.New("at least one ids.txt file is required")
- }
- // Ensure path to dump_syms is specified
- if options.dumpSymsPath == "" {
- return nil, nil, errors.New("-dump-syms-path is required")
- }
- // Ensure output directory was given.
- if options.outdir == "" {
- return nil, nil, errors.New("-out-dir is required")
- }
-
- return f, &options, nil
-}
-
-// processidsFiles dumps symbol data for each executable in a set of ids files.
-//
-// Returns true iff any errors occurred.
-func processIdsFiles(idsFiles []string, options *Options) (gotErrors bool) {
- // Indicates whether we've seen a binary path already. Duplicate paths are
- // skipped.
- visited := make(map[string]bool)
- binaryToSymbolFile := make(map[string]string)
-
- // Confirm that the user is performing a dry-run.
- if options.dryRun {
- log.Println("Performing dry-run")
- }
-
- // Iterate through the given set of filepaths.
- for _, idsFile := range idsFiles {
- // Extract the paths to each binary from the IDs file.
- binaryPaths, err := extractBinaryPaths(idsFile)
- if err != nil {
- logError("failed to extract paths from %s: %v", idsFile, err)
- gotErrors = true
- continue
- }
-
- // Generate symbol data for each binary.
- for _, binaryPath := range binaryPaths {
- // Check whether we've seen this path already. Skip if so.
- if _, ok := visited[binaryPath]; ok {
- continue
- }
- // Record that we've seen this binary path.
- visited[binaryPath] = true
-
- // Generate the symbol file path.
- symbolFile := path.Join(options.outdir, hashText(binaryPath)+".sym")
-
- // Record the mapping in the summary.
- binaryToSymbolFile[binaryPath] = symbolFile
-
- // Log what we're about to do. If this a dry run, say so and
- // continue without dumping symbols.
- info := fmt.Sprintf("dumping symbols for %s into %s", binaryPath, symbolFile)
- if options.dryRun {
- log.Println("DRY_RUN: " + info)
- continue
- }
- log.Println(info)
-
- // Dump the symbol data to disk. Record an error
- if err := dumpSymbolData(binaryPath, symbolFile, options.dumpSymsPath); err != nil {
- logError("%v", err)
- gotErrors = true
- continue
- }
- }
- }
-
- var summaryFile *os.File
-
- // If no summary file path was given, write the summmary to stdout.
- if options.summaryFile == "" || options.dryRun {
- summaryFile = os.Stdout
- } else {
- var err error
- summaryFile, err = os.Create(options.summaryFile)
- if err != nil {
- logError("failed to open summary file %s: %v", options.summaryFile, err)
- gotErrors = true
- return
- }
- }
-
- if err := writeSummary(binaryToSymbolFile, summaryFile); err != nil {
- logError("failed to output summary %s: %v", options.summaryFile, err)
- gotErrors = true
- return
- }
-
- return
-}
-
-// Returns a sha1 hash of the input text.
-func hashText(text string) string {
- hash := sha1.New()
- n, err := hash.Write([]byte(text))
- if err != nil {
- panic(err)
- }
- if n == 0 {
- // Empty text should never be passed to this function and likely signifies
- // an error in the input file. Panic here as well.
- panic("0 bytes written for hash of input text '" + text + "'")
- }
-
- return hex.EncodeToString(hash.Sum(nil))
-}
-
-// Logs an error message.
-//
-// format and args work the same as with fmt.Printf.
-func logError(format string, args ...interface{}) {
- log.Printf("ERROR: "+format, args...)
-}
-
-// Writes the summary.
-func writeSummary(summary map[string]string, file *os.File) error {
- // TODO(kjharland): Sort the keys before priting to ensure predictable
- // output or use a different data structure with a consistent ordering.
-
- // Serialize the summary.
- summaryBytes, err := json.MarshalIndent(summary, "", " ")
- if err != nil {
- return fmt.Errorf("json marhsal failed: %v", err)
- }
-
- // Write the summary.
- if _, err := file.Write(summaryBytes); err != nil {
- return fmt.Errorf("write failed: %v", err)
- }
-
- return nil
-}
-
-// Extracts a list of absolute paths to binaries from an idsFile.
-//
-// See the helptext for this command for info about the idsFile. This function
-// handles malformed input gracefully, logging errors rather than returning
-// early.
-//
-// Returns the list of binary paths.
-//
-// TODO(kjharland): Use https://fuchsia.googlesource.com/tools/+/master/symbolize/repo.go
-// and delete this.
-func extractBinaryPaths(idsFile string) ([]string, error) {
- var binaryPaths []string
-
- // Read file contents.
- idsFileBytes, err := ioutil.ReadFile(idsFile)
- if err != nil {
- return nil, err
- }
-
- idsFileLines := strings.Split(string(idsFileBytes), "\n")
-
- // Extract the path to the binary from each line.
- for _, line := range idsFileLines {
- line = strings.TrimSpace(line)
- if len(line) == 0 {
- // Skip empty lines gracefully.
- continue
- }
-
- fields := strings.Split(line, " ")
- if len(fields) != 2 {
- // Lines should only have two columns. Abort if input is malformed.
- return nil, fmt.Errorf("malformed line in %s: %s", idsFile, line)
- }
-
- binaryPaths = append(binaryPaths, fields[1])
- }
-
- return binaryPaths, nil
-}
-
-// Runs the breakpad tool `dump_syms` on the binary at the given absolute path,
-// Then writes the symbol data to the given path.
-func dumpSymbolData(binaryPath, symbolFile, dumpSymsPath string) error {
- // Run the dump_syms command.
- symbolData, err := exec.Command(dumpSymsPath, binaryPath).Output()
- if err != nil {
- return fmt.Errorf("failed to execute %s: %s", dumpSymsPath, err)
- }
-
- // Many Fuchsia binaries are built as "something.elf", but then packaged as
- // just "something". In the ids.txt file, the name still includes the ".elf"
- // extension, which dump_syms emits into the .sym file, and the crash server
- // uses as part of the lookup. The binary name and this value written to
- // the .sym file must match, so if the first header line ends in ".elf"
- // strip it off. This line usually looks something like:
- // MODULE Linux x86_64 094B63014248508BA0636AD3AC3E81D10 sysconf.elf
- lines := strings.SplitN(string(symbolData), "\n", 2)
- if len(lines) != 2 {
- return fmt.Errorf("got <2 lines in symbol data for %s", binaryPath)
- }
-
- // Make sure the first line is not empty.
- lines[0] = strings.TrimSpace(lines[0])
- if lines[0] == "" {
- return fmt.Errorf("unexpected blank first line in symbol data for %s", binaryPath)
- }
-
- // Strip .elf from header if it exists.
- if strings.HasSuffix(lines[0], ".elf") {
- lines[0] = strings.TrimSuffix(lines[0], ".elf")
- // Join the new lines of the symbol data.
- symbolData = []byte(strings.Join(lines, "\n"))
- }
-
- // Write the symbol file.
- if err := ioutil.WriteFile(symbolFile, []byte(symbolData), 0644); err != nil {
- return fmt.Errorf("could not write output file %s: %v", symbolFile, err)
- }
-
- return nil
-}
diff --git a/cmd/dump_fuchsia_symbols/main_test.go b/cmd/dump_fuchsia_symbols/main_test.go
deleted file mode 100644
index 7b09549..0000000
--- a/cmd/dump_fuchsia_symbols/main_test.go
+++ /dev/null
@@ -1,206 +0,0 @@
-package main_test
-
-import (
- "io/ioutil"
- "path/filepath"
- "testing"
-
- cmd "fuchsia.googlesource.com/infra/infra/cmd/dump_fuchsia_symbols"
-)
-
-func TestParseFlags(t *testing.T) {
- // Parses flags from the given command line args.
- parseFlags := func(args []string) error {
- // Prepend the command name to args to avoid having to repeat it
- // in every test case
- args = append([]string{"dump_fuchsia_symbols"}, args...)
- _, _, err := cmd.ParseFlags(args)
- return err
- }
-
- // Expects command line parsing to succeed. Fails otherwise.
- expectSuccess := func(t *testing.T, args []string) {
- if err := parseFlags(args); err != nil {
- t.Errorf("Failed to parse command line: %+v, %v", args, err)
- }
- }
-
- // Expects command line parsing to fail. Fails otherwise.
- expectFailure := func(t *testing.T, args []string) {
- if parseFlags(args) == nil {
- t.Errorf("Expected error when parsing %+v. Got nil", args)
- }
- }
-
- t.Run("should succeed if valid arguments are given", func(t *testing.T) {
- t.Run("when there are multiple input files", func(t *testing.T) {
- expectSuccess(t, []string{
- "-out-dir=output_directory",
- "-dump-syms-path=/path/to/dump_syms",
- "ids.txt",
- "ids2.txt",
- })
- })
-
- t.Run("when -dry-run=true", func(t *testing.T) {
- // explicit "-dry-run=true"
- expectSuccess(t, []string{
- "-dry-run=true",
- "-out-dir=output_directory",
- "-dump-syms-path=/path/to/dump_syms",
- "ids.txt",
- })
- // Just "-dry-run"
- expectSuccess(t, []string{
- "-dry-run",
- "-out-dir=output_directory",
- "-dump-syms-path=/path/to/dump_syms",
- "ids.txt",
- })
- })
-
- })
-
- t.Run("should fail if -out-dir is missing", func(t *testing.T) {
- expectFailure(t, []string{
- "-dry-run",
- "-dump-syms-path=/path/to/dump_syms",
- "ids.txt",
- })
- })
-
- t.Run("should fail if -dump-syms-path is missing", func(t *testing.T) {
- expectFailure(t, []string{
- "-out-dir=output_directory",
- "-dry-run",
- "ids.txt",
- })
- })
- t.Run("should fail if input file is missing", func(t *testing.T) {
- expectFailure(t, []string{
- "-out-dir=output_directory",
- "-dry-run",
- "-dump-syms-path=/path/to/dump_syms",
- })
- })
-}
-
-// Examples-as-tests below this line. "Output:" at the end of each function
-// verifies that what is printed on stdout matches the rest of the comment.
-// If there is a difference, `go test ./...` will fail.
-
-func ExampleWithOneInputFile() {
- // Create a testing input file with fake hash values and binary paths.
- inputFile := createTempFile("ids.txt", `
-01634b09 /path/to/binaryA.elf
-02298167 /path/to/binaryB
-025abbbc /path/to/binaryC.so
-`)
-
- // Run the command in dry-run mode so that we can verify the list of
- // commands that would actually run, given the test input file.
- cmd.RunMain([]string{
- "dump_fuchsia_symbols",
- "-dry-run",
- "-out-dir=/out",
- "-dump-syms-path=/path/to/dump_syms",
- inputFile,
- })
-
- // Output:
- //{
- // "/path/to/binaryA.elf": "/out/fe9881defb9ed1ddb9a89c38be973515f6ad7f0f.sym",
- // "/path/to/binaryB": "/out/f03de72df78157dd14ae1cc031ddba9873947179.sym",
- // "/path/to/binaryC.so": "/out/edbe4e45241c98dcde3538160073a0d6b097b780.sym"
- //}
-}
-
-func ExampleWithMultipleInputFiles() {
- // Create a testing input file with bogus hash values and binary paths.
- inputFileA := createTempFile("idsA.txt", `
-01634b09 /path/to/binaryA.elf
-02298167 /path/to/binaryB
-025abbbc /path/to/binaryC.so
-`)
-
- // Create a testing input file with bogus hash values and binary paths.
- inputFileB := createTempFile("idsB.txt", `
-01634b09 /path/to/binaryD
-02298167 /path/to/binaryE
-025abbbc /path/to/binaryF
-`)
-
- // Run the command in dry-run mode so that we can verify the list of
- // commands that would actually run, given the test input file.
- cmd.RunMain([]string{
- "dump_fuchsia_symbols",
- "-dry-run",
- "-out-dir=/out",
- "-dump-syms-path=/path/to/dump_syms",
- inputFileA,
- inputFileB,
- })
-
- // Output:
- //{
- // "/path/to/binaryA.elf": "/out/fe9881defb9ed1ddb9a89c38be973515f6ad7f0f.sym",
- // "/path/to/binaryB": "/out/f03de72df78157dd14ae1cc031ddba9873947179.sym",
- // "/path/to/binaryC.so": "/out/edbe4e45241c98dcde3538160073a0d6b097b780.sym",
- // "/path/to/binaryD": "/out/8541277ee6941ac4c3c9ab2fc68edfb4c420861e.sym",
- // "/path/to/binaryE": "/out/906bc6368e6462a6cf7b78328a675ce57ef82209.sym",
- // "/path/to/binaryF": "/out/302cb9c3745652180c25e5da2ca3e420b8dd4e25.sym"
- //}
-}
-
-func ExampleSkipDuplicatePaths() {
- // Create a testing input file with bogus hash values and binary paths.
- inputFile := createTempFile("ids.txt", `
-01634b09 /path/to/binaryA
-02298167 /path/to/binaryA
-`)
-
- // Run the command in dry-run mode so that we can verify the list of
- // commands that would actually run, given the test input file.
- cmd.RunMain([]string{
- "dump_fuchsia_symbols",
- "-dry-run",
- "-out-dir=/out",
- "-dump-syms-path=/path/to/dump_syms",
- inputFile,
- })
-
- // Output:
- // {
- // "/path/to/binaryA": "/out/43e5a3c9eb9829f2eb11007223de1fb0b721a909.sym"
- // }
-}
-
-// Creates a temp file with the given name and contents for testing.
-//
-// Returns the absolute path to the file.
-//
-// TODO(kjharland): It would be nice to create an entire temp directory, and
-// create all files in that directory for testing. Then each test can
-// `defer dir.delete()` to clean up.
-func createTempFile(name, contents string) (absPath string) {
- // Create the file.
- file, err := ioutil.TempFile("", name)
- if err != nil {
- panic(err)
- }
-
- // Write the contents.
- file.Write([]byte(contents))
-
- // Grab the absolute path
- absPath, err = filepath.Abs(file.Name())
- if err != nil {
- panic(err)
- }
- return
-}
-
-// No tests to verify the output of dump_syms. Assume it does the right thing.
-
-// TODO(kjharland): Consider creating test binaries to pass as -dump-syms-path,
-// which would let us write integration tests for this file.