1#! /bin/bash -eu 2 3# Compare object files the linker used to build given binary for 4# two different configurations. 5# As an example, suppose we comnt to compare `adbd` binary that is 6# included in `com.android.adbd` APEX. We first build this APEX 7# with Soong and rename the build tree to `out.ref`: 8# $ m com.android.adbd && mv out out.ref 9# Then we build it again with mixed build and rename the build tree 10# to `out.mix` 11# $ m --bazel-mode-staging com.android.adbd && mv out out.mix 12# Now we can run this script to compare `adbd` binaries between 13# two builds as follows: 14# $ compare_elf.sh adbd out.ref out.mix 15# Note that we refer to the first of the two build directories as 16# 'reference' and to the second one as 'our'. 17# 18# There are two ways to specify the binaries to compare: 19# * compare_elf.sh REFDIR REFELF OURDIR OURELF 20# Compare REFDIR/**/REFELF (i.e., the file in REFDIR whose path ends 21# with REFELF in REFDIR) to OURDIR/**/OUROELF 22# * compare_elf.sh ELF REFDIR OURDIR 23# This is a shortcut: 24# if ELF ends with .so, the same as 25# compare_elf.sh REFDIR ELF OURDIR ELF 26# otherwise the same as 27# compare_elf.sh REFDIR ELF OURDIR ELF.unstripped 28# 29# Overall, the process is as follows: 30# * For each build, extract the list of the input objects and 31# map each such object's unique configuration-independent key 32# * Compare the maps. For each common key, use `elfdiff` to compare 33# the ELF files 34function die() { format=$1; shift; printf "$format\n" $@; exit 1; } 35 36case $# in 37 3) declare -r refelf=$1 refdir=$2 ourdir=$3 38 [[ ${ourelf:=$refelf} =~ .so$ ]] || ourelf=$ourelf.unstripped ;; 39 4) declare -r refdir=$1 refelf=$2 ourdir=$3 ourelf=$4 ;; 40 *) die "usage:\n ${0##*/} ELF REFDIR OURDIR\nor\n ${0##*/} REFDIR REFELF OURDIR OURELF" ;; 41esac 42[[ -d $refdir ]] || die "$refdir is not a build directory" 43[[ -d $ourdir ]] || die "$outdir is not a build directory" 44 45declare -r elf_input_files="${0%/*}"/elf_input_files.sh 46 47# Outputs the script that initialize an associative array with 48# given name that maps object keys to their paths inside the tree. 49# Ignore prebuilts and .so files. 50# Normalize library names as in Bazel they sometimes start with 51# `liblib` instead of `lib` and may end with `_bp2build_library_static` 52# It's a rather ugly sed script. 53# Anyways, the output script looks like this: 54# declare -A <name>=( 55# ["libfoo.a(abc.o)"]="<path>/libfoo(abc.o)" 56# .... 57# ) 58function objects_map() { 59 local -r name=$1 out_dir=$2 prefix="${3:-}" 60 grep -v -e '^prebuilts/' -e '\.so$' | sed -nr \ 61 -e "1ideclare -A $name=(" \ 62 -e "s|^|$prefix|" \ 63 -e "s|^out/|$out_dir/|" \ 64 -e '/_bp2build_cc_library_static\.a/s|(.*)/(lib)?(lib[^/]*)(_bp2build_cc_library_static\.a)\((.+)\)$|["\3.a(\5)"]="\1/\2\3\4(\5)"|p' \ 65 -e '/_bp2build_cc_library_static\.a/!s|(.*)/(lib)?(lib[^/]*)\((.+)\)$|["\3(\4)"]="\1/\2\3(\4)"|p' \ 66 -e 's|(.*)/([^/]*\.s?o)$|["\2"]="\1/\2"|p' \ 67 -e '$i)' 68} 69 70declare -r reffiles=$(mktemp --suffix=.ref) ourfiles=$(mktemp --suffix=.our) 71declare -r comparator=$(mktemp /tmp/elfdiff.XXXXXX) 72trap 'rm -f $ourfiles $reffiles $comparator' EXIT 73 74# Initialize `ref_objects` to be objects map for ref build 75"$elf_input_files" $refelf $refdir >$reffiles || exit 1 76. <(objects_map ref_objects $refdir <$reffiles ) 77 78# Initialize `our_objects` to be objects map for our build 79"$elf_input_files" $ourelf $ourdir >$ourfiles || exit 1 80declare -r bazel_prefix=out/bazel/output/execroot/__main__/ 81. <(objects_map our_objects $ourdir $bazel_prefix <$ourfiles ) 82 83# Minor re-keying fo `our_objects` (e.g., Soong's `main.o` is 84# Bazel's libadbd__internal_root.lo(main.o) 85declare -Ar equivalences=( 86 ["libadbd__internal_root.lo(main.o)"]="main.o" 87 ["libadbd__internal_root.lo(libbuildversion.o)"]="libbuildversion.a(libbuildversion.o)" 88 ["crtend.o"]="crtend_android.o") 89for k in "${!equivalences[@]}"; do 90 if [[ -v "our_objects[$k]" ]]; then 91 our_objects["${equivalences[$k]}"]="${our_objects[$k]}" 92 unset "our_objects[$k]" 93 fi 94done 95 96declare -a missing extra common 97# Compare the keys from `ref_objects` and `our_objects` and output the script 98# to initialize `missing`, `extra` and `common` arrays to resp. only in 99# `ref_objects`, only in `sour_objects`, and common 100function classify() { 101 comm <(printf "%s\n" "${!ref_objects[@]}" | sort) <(printf "%s\n" "${!our_objects[@]}" | sort) \ 102 | sed -nr '/^\t\t/{s|^\t\t(.*)|common+=("\1")|p;d};/^\t/{s|^\t(.*)|extra+=("\1")|p;d};s|(.*)|missing+=("\1")|p' 103} 104 105. <(classify) 106if [[ -v missing ]]; then 107 printf "The following input object files are missing:\n" 108 for o in "${missing[@]}"; do 109 printf " %s\n" "${ref_objects[$o]}" 110 done 111fi 112 113if [[ -v extra ]]; then 114 printf "The following input object files are extra:\n" 115 for o in "${extra[@]}"; do 116 printf " %s\n" "${our_objects[$o]}" 117 done 118fi 119 120# Build the ELF files comparator, it is Go binary. 121declare -r elfdiff=android/bazel/mkcompare/elfdiff/... 122GOWORK=$PWD/build/bazel/mkcompare/go.work go build -o $comparator $elfdiff || exit 1 123 124# Output ELF file pairs to compare and feed them the parallel executor. 125for o in "${common[@]}"; do echo "${ref_objects[$o]} ${our_objects[$o]}"; done |\ 126 parallel --colsep ' ' $comparator {1} {2} 127