xref: /aosp_15_r20/build/bazel/scripts/elf_compare.sh (revision 7594170e27e0732bc44b93d1440d87a54b6ffe7c)
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