Support stripping shared libraries and binaries

Strip all shared libraries and binaries by default.  Use a shell script
to wrap the long sequences of commands needed by some strip variants.

Change-Id: I465bf7cc48330913e60e24762fd55fa2a7731c26
diff --git a/cc/builder.go b/cc/builder.go
index ca8bc75..10c8508 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -101,6 +101,18 @@
 		},
 		"objcopyCmd", "prefix")
 
+	stripPath = pctx.SourcePathVariable("stripPath", "build/soong/scripts/strip.sh")
+
+	strip = pctx.StaticRule("strip",
+		blueprint.RuleParams{
+			Depfile:     "${out}.d",
+			Deps:        blueprint.DepsGCC,
+			Command:     "CROSS_COMPILE=$crossCompile $stripPath ${args} -i ${in} -o ${out} -d ${out}.d",
+			CommandDeps: []string{"$stripPath"},
+			Description: "strip $out",
+		},
+		"args", "crossCompile")
+
 	copyGccLibPath = pctx.SourcePathVariable("copyGccLibPath", "build/soong/scripts/copygcclib.sh")
 
 	copyGccLib = pctx.StaticRule("copyGccLib",
@@ -138,6 +150,10 @@
 	nocrt       bool
 	toolchain   Toolchain
 	clang       bool
+
+	stripKeepSymbols       bool
+	stripKeepMiniDebugInfo bool
+	stripAddGnuDebuglink   bool
 }
 
 // Generate rules for compiling multiple .c, .cpp, or .S files to individual .o files
@@ -397,6 +413,32 @@
 	})
 }
 
+func TransformStrip(ctx common.AndroidModuleContext, inputFile common.Path,
+	outputFile common.WritablePath, flags builderFlags) {
+
+	crossCompile := gccCmd(flags.toolchain, "")
+	args := ""
+	if flags.stripAddGnuDebuglink {
+		args += " --add-gnu-debuglink"
+	}
+	if flags.stripKeepMiniDebugInfo {
+		args += " --keep-mini-debug-info"
+	}
+	if flags.stripKeepSymbols {
+		args += " --keep-symbols"
+	}
+
+	ctx.ModuleBuild(pctx, common.ModuleBuildParams{
+		Rule:   strip,
+		Output: outputFile,
+		Input:  inputFile,
+		Args: map[string]string{
+			"crossCompile": crossCompile,
+			"args":         args,
+		},
+	})
+}
+
 func CopyGccLib(ctx common.AndroidModuleContext, libName string,
 	flags builderFlags, outputFile common.WritablePath) {
 
diff --git a/cc/cc.go b/cc/cc.go
index 6297a4e..052df2b 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -434,10 +434,16 @@
 	Relative_install_path string
 }
 
+type StripProperties struct {
+	Strip struct {
+		None         bool
+		Keep_symbols bool
+	}
+}
+
 type UnusedProperties struct {
 	Native_coverage *bool
 	Required        []string
-	Strip           string
 	Tags            []string
 }
 
@@ -1441,6 +1447,7 @@
 type libraryLinker struct {
 	baseLinker
 	flagExporter
+	stripper
 
 	Properties LibraryLinkerProperties
 
@@ -1465,7 +1472,8 @@
 	return append(props,
 		&library.Properties,
 		&library.dynamicProperties,
-		&library.flagExporter.Properties)
+		&library.flagExporter.Properties,
+		&library.stripper.StripProperties)
 }
 
 func (library *libraryLinker) flags(ctx ModuleContext, flags Flags) Flags {
@@ -1554,9 +1562,6 @@
 func (library *libraryLinker) linkShared(ctx ModuleContext,
 	flags Flags, deps PathDeps, objFiles common.Paths) common.Path {
 
-	outputFile := common.PathForModuleOut(ctx,
-		ctx.ModuleName()+library.Properties.VariantName+flags.Toolchain.ShlibSuffix())
-
 	var linkerDeps common.Paths
 
 	versionScript := common.OptionalPathForModuleSrc(ctx, library.Properties.Version_script)
@@ -1595,14 +1600,26 @@
 		}
 	}
 
+	fileName := ctx.ModuleName() + library.Properties.VariantName + flags.Toolchain.ShlibSuffix()
+	outputFile := common.PathForModuleOut(ctx, fileName)
+	ret := outputFile
+
+	builderFlags := flagsToBuilderFlags(flags)
+
+	if library.stripper.needsStrip(ctx) {
+		strippedOutputFile := outputFile
+		outputFile = common.PathForModuleOut(ctx, "unstripped", fileName)
+		library.stripper.strip(ctx, outputFile, strippedOutputFile, builderFlags)
+	}
+
 	sharedLibs := deps.SharedLibs
 	sharedLibs = append(sharedLibs, deps.LateSharedLibs...)
 
 	TransformObjToDynamicBinary(ctx, objFiles, sharedLibs,
 		deps.StaticLibs, deps.LateStaticLibs, deps.WholeStaticLibs,
-		linkerDeps, deps.CrtBegin, deps.CrtEnd, false, flagsToBuilderFlags(flags), outputFile)
+		linkerDeps, deps.CrtBegin, deps.CrtEnd, false, builderFlags, outputFile)
 
-	return outputFile
+	return ret
 }
 
 func (library *libraryLinker) link(ctx ModuleContext,
@@ -1746,6 +1763,7 @@
 
 type binaryLinker struct {
 	baseLinker
+	stripper
 
 	Properties BinaryLinkerProperties
 
@@ -1755,7 +1773,10 @@
 var _ linker = (*binaryLinker)(nil)
 
 func (binary *binaryLinker) props() []interface{} {
-	return append(binary.baseLinker.props(), &binary.Properties)
+	return append(binary.baseLinker.props(),
+		&binary.Properties,
+		&binary.stripper.StripProperties)
+
 }
 
 func (binary *binaryLinker) buildStatic() bool {
@@ -1906,18 +1927,12 @@
 func (binary *binaryLinker) link(ctx ModuleContext,
 	flags Flags, deps PathDeps, objFiles common.Paths) common.Path {
 
-	outputFile := common.PathForModuleOut(ctx, binary.getStem(ctx)+flags.Toolchain.ExecutableSuffix())
+	fileName := binary.getStem(ctx) + flags.Toolchain.ExecutableSuffix()
+	outputFile := common.PathForModuleOut(ctx, fileName)
+	ret := outputFile
 	if ctx.HostOrDevice().Host() {
 		binary.hostToolPath = common.OptionalPathForPath(outputFile)
 	}
-	ret := outputFile
-
-	if binary.Properties.Prefix_symbols != "" {
-		afterPrefixSymbols := outputFile
-		outputFile = common.PathForModuleOut(ctx, binary.getStem(ctx)+".intermediate")
-		TransformBinaryPrefixSymbols(ctx, binary.Properties.Prefix_symbols, outputFile,
-			flagsToBuilderFlags(flags), afterPrefixSymbols)
-	}
 
 	var linkerDeps common.Paths
 
@@ -1928,9 +1943,24 @@
 		flags.LdFlags = append(flags.LdFlags, " -Wl,-dynamic-linker,"+flags.DynamicLinker)
 	}
 
+	builderFlags := flagsToBuilderFlags(flags)
+
+	if binary.stripper.needsStrip(ctx) {
+		strippedOutputFile := outputFile
+		outputFile = common.PathForModuleOut(ctx, "unstripped", fileName)
+		binary.stripper.strip(ctx, outputFile, strippedOutputFile, builderFlags)
+	}
+
+	if binary.Properties.Prefix_symbols != "" {
+		afterPrefixSymbols := outputFile
+		outputFile = common.PathForModuleOut(ctx, "unprefixed", fileName)
+		TransformBinaryPrefixSymbols(ctx, binary.Properties.Prefix_symbols, outputFile,
+			flagsToBuilderFlags(flags), afterPrefixSymbols)
+	}
+
 	TransformObjToDynamicBinary(ctx, objFiles, sharedLibs, deps.StaticLibs,
 		deps.LateStaticLibs, deps.WholeStaticLibs, linkerDeps, deps.CrtBegin, deps.CrtEnd, true,
-		flagsToBuilderFlags(flags), outputFile)
+		builderFlags, outputFile)
 
 	return ret
 }
@@ -1939,6 +1969,22 @@
 	return binary.hostToolPath
 }
 
+type stripper struct {
+	StripProperties StripProperties
+}
+
+func (stripper *stripper) needsStrip(ctx ModuleContext) bool {
+	return !ctx.AConfig().EmbeddedInMake() && !stripper.StripProperties.Strip.None
+}
+
+func (stripper *stripper) strip(ctx ModuleContext, in, out common.ModuleOutPath,
+	flags builderFlags) {
+	flags.stripKeepSymbols = stripper.StripProperties.Strip.Keep_symbols
+	// TODO(ccross): don't add gnu debuglink for user builds
+	flags.stripAddGnuDebuglink = true
+	TransformStrip(ctx, in, out, flags)
+}
+
 func testPerSrcMutator(mctx common.AndroidBottomUpMutatorContext) {
 	if m, ok := mctx.Module().(*Module); ok {
 		if test, ok := m.linker.(*testLinker); ok {
@@ -2155,6 +2201,7 @@
 		&UnusedProperties{},
 		&StlProperties{},
 		&SanitizeProperties{},
+		&StripProperties{},
 	}
 
 	_, propertyStructs = common.InitAndroidArchModule(module, common.HostAndDeviceDefault,
diff --git a/scripts/strip.sh b/scripts/strip.sh
new file mode 100755
index 0000000..5c43028
--- /dev/null
+++ b/scripts/strip.sh
@@ -0,0 +1,122 @@
+#!/bin/bash -e
+
+# Script to handle the various ways soong may need to strip binaries
+# Inputs:
+#  Environment:
+#   CROSS_COMPILE: prefix added to readelf, objcopy tools
+#  Arguments:
+#   -o ${file}: output file (required)
+#   -d ${file}: deps file (required)
+#   --keep-symbols
+#   --keep-mini-debug-info
+#   --add-gnu-debuglink
+
+OPTSTRING=d:i:o:-:
+
+usage() {
+    cat <<EOF
+Usage: strip.sh [options] -i in-file -o out-file -d deps-file
+Options:
+        --keep-symbols          Keep symbols in out-file
+        --keep-mini-debug-info  Keep compressed debug info in out-file
+        --add-gnu-debuglink     Add a gnu-debuglink section to out-file
+EOF
+    exit 1
+}
+
+do_strip() {
+    "${CROSS_COMPILE}strip" --strip-all "${infile}" -o "${outfile}.tmp"
+}
+
+do_strip_keep_symbols() {
+    "${CROSS_COMPILE}objcopy" "${infile}" "${outfile}.tmp" \
+	`"${CROSS_COMPILE}readelf" -S "${infile}" | awk '/.debug_/ {print "-R " $2}' | xargs`
+}
+
+do_strip_keep_mini_debug_info() {
+    "${CROSS_COMPILE}nm" -D "${infile}" --format=posix --defined-only | awk '{ print $$1 }' | sort >"${outfile}.dynsyms"
+    "${CROSS_COMPILE}nm" "${infile}" --format=posix --defined-only | awk '{ if ($$2 == "T" || $$2 == "t" || $$2 == "D") print $$1 }' | sort > "${outfile}.funcsyms"
+    comm -13 "${outfile}.dynsyms" "${outfile}.funcsyms" > "${outfile}.keep_symbols"
+    "${CROSS_COMPILE}objcopy" --only-keep-debug "${infile}" "${outfile}.debug"
+    "${CROSS_COMPILE}objcopy" --rename-section .debug_frame=saved_debug_frame "${outfile}.debug" "${outfile}.mini_debuginfo"
+    "${CROSS_COMPILE}objcopy" -S --remove-section .gdb_index --remove-section .comment --keep-symbols="${outfile}.keep_symbols" "${outfile}.mini_debuginfo"
+    "${CROSS_COMPILE}objcopy" --rename-section saved_debug_frame=.debug_frame "${outfile}.mini_debuginfo"
+    "${CROSS_COMPILE}strip" --strip-all -R .comment "${infile}" -o "${outfile}.tmp"
+    rm -f "${outfile}.mini_debuginfo.xz"
+    xz "${outfile}.mini_debuginfo"
+    "${CROSS_COMPILE}objcopy" --add-section .gnu_debugdata="${outfile}.mini_debuginfo.xz" "${outfile}.tmp"
+    rm -f "${outfile}.dynsyms" "${outfile}.funcsyms" "${outfile}.keep_symbols" "${outfile}.debug" "${outfile}.mini_debuginfo.xz"
+}
+
+do_add_gnu_debuglink() {
+    "${CROSS_COMPILE}objcopy" --add-gnu-debuglink="${infile}" "${outfile}.tmp"
+}
+
+while getopts $OPTSTRING opt; do
+    case "$opt" in
+	d) depsfile="${OPTARG}" ;;
+	i) infile="${OPTARG}" ;;
+	o) outfile="${OPTARG}" ;;
+	-)
+	    case "${OPTARG}" in
+		keep-symbols) keep_symbols=true ;;
+		keep-mini-debug-info) keep_mini_debug_info=true ;;
+		add-gnu-debuglink) add_gnu_debuglink=true ;;
+		*) echo "Unknown option --${OPTARG}"; usage ;;
+	    esac;;
+	?) usage ;;
+	*) echo "'${opt}' '${OPTARG}'"
+    esac
+done
+
+if [ -z "${infile}" ]; then
+    echo "-i argument is required"
+    usage
+fi
+
+if [ -z "${outfile}" ]; then
+    echo "-o argument is required"
+    usage
+fi
+
+if [ -z "${depsfile}" ]; then
+    echo "-d argument is required"
+    usage
+fi
+
+if [ ! -z "${keep_symbols}" -a ! -z "${keep_mini_debug_info}" ]; then
+    echo "--keep-symbols and --keep-mini-debug-info cannot be used together"
+    usage
+fi
+
+if [ ! -z "${add_gnu_debuglink}" -a ! -z "${keep_mini_debug_info}" ]; then
+    echo "--add-gnu-debuglink cannot be used with --keep-mini-debug-info"
+    usage
+fi
+
+rm -f "${outfile}.tmp"
+
+if [ ! -z "${keep_symbols}" ]; then
+    do_strip_keep_symbols
+elif [ ! -z "${keep_mini_debug_info}" ]; then
+    do_strip_keep_mini_debug_info
+else
+    do_strip
+fi
+
+if [ ! -z "${add_gnu_debuglink}" ]; then
+    do_add_gnu_debuglink
+fi
+
+rm -f "${outfile}"
+mv "${outfile}.tmp" "${outfile}"
+
+cat <<EOF > "${depsfile}"
+${outfile}: \
+  ${infile} \
+  ${CROSS_COMPILE}nm \
+  ${CROSS_COMPILE}objcopy \
+  ${CROSS_COMPILE}readelf \
+  ${CROSS_COMPILE}strip
+
+EOF