blob: 8b05344441907e7648287b4d4929b815a2c19dc8 [file] [log] [blame]
#! /bin/bash -eu
# Compare object files the linker used to build given binary for
# two different configurations.
# As an example, suppose we comnt to compare `adbd` binary that is
# included in `com.android.adbd` APEX. We first build this APEX
# with Soong and rename the build tree to `out.ref`:
# $ m com.android.adbd && mv out out.ref
# Then we build it again with mixed build and rename the build tree
# to `out.mix`
# $ m --bazel-mode-staging com.android.adbd && mv out out.mix
# Now we can run this script to compare `adbd` binaries between
# two builds as follows:
# $ compare_elf.sh adbd out.ref out.mix
# Note that we refer to the first of the two build directories as
# 'reference' and to the second one as 'our'.
#
# There are two ways to specify the binaries to compare:
# * compare_elf.sh REFDIR REFELF OURDIR OURELF
# Compare REFDIR/**/REFELF (i.e., the file in REFDIR whose path ends
# with REFELF in REFDIR) to OURDIR/**/OUROELF
# * compare_elf.sh ELF REFDIR OURDIR
# This is a shortcut:
# if ELF ends with .so, the same as
# compare_elf.sh REFDIR ELF OURDIR ELF
# otherwise the same as
# compare_elf.sh REFDIR ELF OURDIR ELF.unstripped
#
# Overall, the process is as follows:
# * For each build, extract the list of the input objects and
# map each such object's unique configuration-independent key
# * Compare the maps. For each common key, use `elfdiff` to compare
# the ELF files
function die() { format=$1; shift; printf "$format\n" $@; exit 1; }
case $# in
3) declare -r refelf=$1 refdir=$2 ourdir=$3
[[ ${ourelf:=$refelf} =~ .so$ ]] || ourelf=$ourelf.unstripped ;;
4) declare -r refdir=$1 refelf=$2 ourdir=$3 ourelf=$4 ;;
*) die "usage:\n ${0##*/} ELF REFDIR OURDIR\nor\n ${0##*/} REFDIR REFELF OURDIR OURELF" ;;
esac
[[ -d $refdir ]] || die "$refdir is not a build directory"
[[ -d $ourdir ]] || die "$outdir is not a build directory"
declare -r elf_input_files="${0%/*}"/elf_input_files.sh
# Outputs the script that initialize an associative array with
# given name that maps object keys to their paths inside the tree.
# Ignore prebuilts and .so files.
# Normalize library names as in Bazel they sometimes start with
# `liblib` instead of `lib` and may end with `_bp2build_library_static`
# It's a rather ugly sed script.
# Anyways, the output script looks like this:
# declare -A <name>=(
# ["libfoo.a(abc.o)"]="<path>/libfoo(abc.o)"
# ....
# )
function objects_map() {
local -r name=$1 out_dir=$2 prefix="${3:-}"
grep -v -e '^prebuilts/' -e '\.so$' | sed -nr \
-e "1ideclare -A $name=(" \
-e "s|^|$prefix|" \
-e "s|^out/|$out_dir/|" \
-e '/_bp2build_cc_library_static\.a/s|(.*)/(lib)?(lib[^/]*)(_bp2build_cc_library_static\.a)\((.+)\)$|["\3.a(\5)"]="\1/\2\3\4(\5)"|p' \
-e '/_bp2build_cc_library_static\.a/!s|(.*)/(lib)?(lib[^/]*)\((.+)\)$|["\3(\4)"]="\1/\2\3(\4)"|p' \
-e 's|(.*)/([^/]*\.s?o)$|["\2"]="\1/\2"|p' \
-e '$i)'
}
declare -r reffiles=$(mktemp --suffix=.ref) ourfiles=$(mktemp --suffix=.our)
declare -r comparator=$(mktemp /tmp/elfdiff.XXXXXX)
trap 'rm -f $ourfiles $reffiles $comparator' EXIT
# Initialize `ref_objects` to be objects map for ref build
"$elf_input_files" $refelf $refdir >$reffiles || exit 1
. <(objects_map ref_objects $refdir <$reffiles )
# Initialize `our_objects` to be objects map for our build
"$elf_input_files" $ourelf $ourdir >$ourfiles || exit 1
declare -r bazel_prefix=out/bazel/output/execroot/__main__/
. <(objects_map our_objects $ourdir $bazel_prefix <$ourfiles )
# Minor re-keying fo `our_objects` (e.g., Soong's `main.o` is
# Bazel's libadbd__internal_root.lo(main.o)
declare -Ar equivalences=(
["libadbd__internal_root.lo(main.o)"]="main.o"
["libadbd__internal_root.lo(libbuildversion.o)"]="libbuildversion.a(libbuildversion.o)"
["crtend.o"]="crtend_android.o")
for k in "${!equivalences[@]}"; do
if [[ -v "our_objects[$k]" ]]; then
our_objects["${equivalences[$k]}"]="${our_objects[$k]}"
unset "our_objects[$k]"
fi
done
declare -a missing extra common
# Compare the keys from `ref_objects` and `our_objects` and output the script
# to initialize `missing`, `extra` and `common` arrays to resp. only in
# `ref_objects`, only in `sour_objects`, and common
function classify() {
comm <(printf "%s\n" "${!ref_objects[@]}" | sort) <(printf "%s\n" "${!our_objects[@]}" | sort) \
| sed -nr '/^\t\t/{s|^\t\t(.*)|common+=("\1")|p;d};/^\t/{s|^\t(.*)|extra+=("\1")|p;d};s|(.*)|missing+=("\1")|p'
}
. <(classify)
if [[ -v missing ]]; then
printf "The following input object files are missing:\n"
for o in "${missing[@]}"; do
printf " %s\n" "${ref_objects[$o]}"
done
fi
if [[ -v extra ]]; then
printf "The following input object files are extra:\n"
for o in "${extra[@]}"; do
printf " %s\n" "${our_objects[$o]}"
done
fi
# Build the ELF files comparator, it is Go binary.
declare -r elfdiff=android/bazel/mkcompare/elfdiff/...
GOWORK=$PWD/build/bazel/mkcompare/go.work go build -o $comparator $elfdiff || exit 1
# Output ELF file pairs to compare and feed them the parallel executor.
for o in "${common[@]}"; do echo "${ref_objects[$o]} ${our_objects[$o]}"; done |\
parallel --colsep ' ' $comparator {1} {2}