#!/bin/ksh
-# $OpenBSD: check_sym,v 1.11 2022/01/03 03:40:48 guenther Exp $
+# $OpenBSD: check_sym,v 1.12 2024/08/15 01:25:13 guenther Exp $
#
# Copyright (c) 2016,2019,2022 Philip Guenther <guenther@openbsd.org>
#
#
#
# check_sym -- compare the symbols and external function references in two
-# versions of a shared library
+# versions of a library
#
# SYNOPSIS
-# check_sym [-chkv] [old [new]]
+# check_sym [-chkSv] [old [new]]
#
# DESCRIPTION
# Library developers need to be aware when they have changed the
# In each case, additions and removals are reported; for exported
# symbols it also reports when a symbol is weakened or strengthened.
#
+# With the -S option, a similar analysis is done but for the static lib.
+#
# The shared libraries to compare can be specified on the
# command-line. Otherwise, check_sym expects to be run from the
# source directory of a library with a shlib_version file specifying
# files left behind by the -k option can be cleaned up by invoking
# check_syms with the -c option.
#
-# The -v option enables verbose output.
+# The -v option enables verbose output, showing relocation counts.
#
# The *basic* rules of thumb for library versions are: if you
# * stop exporting a symbol, or
get_lib_name()
{
- sed -n 's/^[ ]*LIB[ ]*=[ ]*\([^ ]*\).*/\1/p' "$@"
+ sed -n '/^[ ]*LIB[ ]*=/{ s/^[^=]*=[ ]*\([^ ]*\).*/\1/p; q;}' "$@"
}
pick_highest()
[[ $old != "" ]]
}
+fail() { echo "$*" >&2; exit 1; }
+
usage()
{
- usage="usage: check_sym [-chkv] [old [new]]"
- if [[ $# -gt 0 ]]
- then
- echo "check_sym: $@
-$usage" >&2
- exit 1
- fi
+ usage="usage: check_sym [-chkSv] [old [new]]"
+ [[ $# -eq 0 ]] || fail "check_sym: $*
+$usage"
echo "$usage"
exit 0
}
+
+#
+# Output helpers
+#
+data_sym_changes()
+{
+ join "$@" | awk '$2 != $3 { print $1 " " $2 " --> " $3 }'
+}
+
+output_if_not_empty()
+{
+ leader=$1
+ shift
+ if "$@" | grep -q .
+ then
+ echo "$leader"
+ "$@" | sed 's:^: :'
+ echo
+ fi
+}
+
+
+#
+# Dynamic library routines
+#
+
+dynamic_collect()
+{
+ readelf -sW $old | filt_symtab > $odir/Ds1
+ readelf -sW $new | filt_symtab > $odir/Ds2
+
+ readelf -rW $old > $odir/r1
+ readelf -rW $new > $odir/r2
+
+ case $(readelf -h $new | grep '^ *Machine:') in
+ *MIPS*) cpu=mips64
+ gotsym1=$(readelf -d $old | awk '$2 ~ /MIPS_GOTSYM/{print $3}')
+ gotsym2=$(readelf -d $new | awk '$2 ~ /MIPS_GOTSYM/{print $3}')
+ ;;
+ *HPPA*) cpu=hppa;;
+ *) cpu=dontcare;;
+ esac
+}
+
+jump_slots()
+{
+ case $cpu in
+ hppa) awk '/IPLT/ && $5 != ""{print $5}' r$1
+ ;;
+ mips64) # the $((gotsym$1)) converts hex to decimal
+ awk -v g=$((gotsym$1)) \
+ '/^Symbol table ..symtab/{exit}
+ $6 == "PROTECTED" { next }
+ $1+0 >= g && $4 == "FUNC" {print $8}' Ds$1
+ ;;
+ *) awk '/JU*MP_SL/ && $5 != ""{print $5}' r$1
+ ;;
+ esac | sort -o j$1
+}
+
+dynamic_sym()
+{
+ awk -v s=$1 '/^Symbol table ..symtab/{exit}
+ ! /^ *[1-9]/ {next}
+ $5 == "LOCAL" {next}
+ $7 == "UND" {print $8 | ("sort -o DU" s); next }
+ $5 == "GLOBAL" {print $8 | ("sort -o DS" s) }
+ $5 == "WEAK" {print $8 | ("sort -o DW" s) }
+ $4 == "OBJECT" {print $8, $3 | ("sort -o DO" s) }
+ {print $8 | ("sort -o D" s)
+ print $4, $5, $6, $8}' Ds$1 | sort -o d$1
+}
+
+static_sym()
+{
+ awk '/^Symbol table ..symtab/{s=1}
+ /LOCAL/{next}
+ s&&/^ *[1-9]/{print $4, $5, $6, $8}' Ds$1 | sort -o s$1
+}
+
+dynamic_analysis()
+{
+ jump_slots $1
+ dynamic_sym $1
+ #static_sym $1
+ comm -23 j$1 DU$1 >J$1
+ return 0
+}
+
+dynamic_output()
+{
+ if cmp -s d[12] && cmp -s DO[12]
+ then
+ printf "No dynamic export changes\n"
+ else
+ printf "Dynamic export changes:\n"
+ output_if_not_empty "added:" comm -13 D[12]
+ output_if_not_empty "removed:" comm -23 D[12]
+ output_if_not_empty "weakened:" comm -12 DS1 DW2
+ output_if_not_empty "strengthened:" comm -12 DW1 DS2
+ output_if_not_empty "data object sizes changes:" \
+ data_sym_changes DO[12]
+ fi
+ if ! cmp -s DU[12]
+ then
+ printf "External reference changes:\n"
+ output_if_not_empty "added:" comm -13 DU[12]
+ output_if_not_empty "removed:" comm -23 DU[12]
+ fi
+
+ if $verbose; then
+ printf "\nReloc counts:\nbefore:\n"
+ grep ^R r1
+ printf "\nafter:\n"
+ grep ^R r2
+ fi
+
+ output_if_not_empty "PLT added:" comm -13 J[12]
+ output_if_not_empty "PLT removed:" comm -23 J[12]
+}
+
+
+#
+# Static library routines
+#
+static_collect()
+{
+ readelf -sW $old | filt_ret | filt_symtab > $odir/Ss1
+ readelf -sW $new | filt_ret | filt_symtab > $odir/Ss2
+}
+
+static_analysis()
+{
+ awk -v s=$1 '!/^ *[1-9]/{next}
+ $5 == "LOCAL" {next}
+ $7 == "UND" {print $8 | ("sort -uo SU" s); next }
+ $6 == "HIDDEN" {print $8 | ("sort -uo SH" s) }
+ $5 == "GLOBAL" {print $8 | ("sort -o SS" s) }
+ $5 == "WEAK" {print $8 | ("sort -o SW" s) }
+ $4 == "OBJECT" {print $8, $3 | ("sort -o SO" s) }
+ {print $8 | ("sort -o S" s)
+ print $4, $5, $6, $8}' Ss$1 | sort -o s$1
+ grep -v '^_' SH$1 >Sh$1 || :
+}
+
+static_output()
+{
+ output_if_not_empty "hidden but not reserved:" comm -13 Sh[12]
+ if cmp -s s[12] && cmp -s SO[12]
+ then
+ printf "No static export changes\n"
+ else
+ printf "Static export changes:\n"
+ output_if_not_empty "added:" comm -13 S[12]
+ output_if_not_empty "removed:" comm -23 S[12]
+ output_if_not_empty "weakened:" comm -12 SS1 SW2
+ output_if_not_empty "strengthened:" comm -12 SW1 SS2
+ output_if_not_empty "data object sizes changes:" \
+ data_sym_changes SO[12]
+ fi
+ if ! cmp -s SU[12]
+ then
+ printf "External reference changes:\n"
+ output_if_not_empty "added:" comm -13 SU[12]
+ output_if_not_empty "removed:" comm -23 SU[12]
+ fi
+}
+
+
unset odir
-file_list={D{,S,W,O},J,S,U,d,j,r,s}{1,2}
+file_list={D{,O,S,s,W,U},J,d,j,r}{1,2}
+static_file_list={S{,H,h,O,S,U,W},U,s}{1,2}
keep_temp=false
+dynamic=true
+static=false
verbose=false
-while getopts :chkv opt "$@"
+
+do_static() { static=true dynamic=false file_list=$static_file_list; }
+
+while getopts :chkSv opt "$@"
do
case $opt in
c) rm -f /tmp/$file_list
exit 0;;
h) usage;;
k) keep_temp=true;;
+ S) do_static;;
v) verbose=true;;
\?) usage "unknown option -- $OPTARG";;
esac
[[ $# -gt 2 ]] && usage "too many arguments"
# Old library?
-if [[ $1 = ?(*/)lib*.so* ]]
+if ! $static && [[ $1 = ?(*/)lib*.so* ]]
then
- if [[ ! -f $1 ]]
- then
- echo "$1 doesn't exist" >&2
- exit 1
- fi
+ [[ -f $1 ]] || fail "$1 doesn't exist"
old=$1
lib=${old##*/}
lib=${lib%%.so.*}
shift
+elif [[ $1 = ?(*/)lib*.a ]]
+then
+ # woo hoo, static library mode
+ do_static
+ if [[ -f $1 ]]
+ then
+ old=$1
+ lib=${old##*/}
+ elif [[ $1 = lib*.a && -f /usr/lib/$1 ]]
+ then
+ old=/usr/lib/$1
+ lib=$1
+ else
+ fail "$1 doesn't exist"
+ fi
+ lib=${lib%%.a}
+ shift
else
# try determining it from the current directory
if [[ -f Makefile ]] && lib=$(get_lib_name Makefile) &&
# Is there a copy of that lib in the current directory?
# If so, use the highest numbered one
- if ! pick_highest $lib.so.* && ! pick_highest /usr/lib/$lib.so.*
+ if ! $static &&
+ ! pick_highest $lib.so.* &&
+ ! pick_highest /usr/lib/$lib.so.*
+ then
+ fail "unable to find $lib.so.*"
+ elif $static
then
- echo "unable to find $lib.so.*" >&2
- exit 1
+ old=/usr/lib/${lib}.a
+ [[ -f $old ]] || fail "$old doesn't exist"
fi
fi
# New library?
-if [[ $1 = ?(*/)lib*.so* ]]
+if [[ $1 = ?(*/)lib*.so* ]] ||
+ { $static && [[ $1 = ?(*/)lib*.a ]]; }
then
new=$1
shift
+elif $static
+then
+ new=obj/${lib}.a
else
# Dig info out of the just built library
. ./shlib_version
new=obj/${lib}.so.${major}.${minor}
fi
-if [[ ! -f $new ]]
-then
- echo "$new doesn't exist" >&2
- exit 1
-fi
+[[ -f $new ]] || fail "$new doesn't exist"
# Filter the output of readelf -s to be easier to parse by removing a
# field that only appears on some symbols: [<other>: 88]
# Not really arch-specific, but I've only seen it on alpha
-filt_symtab() {
- sed 's/\[<other>: [0-9a-f]*\]//'
-}
+filt_symtab() { sed 's/\[<other>: [0-9a-f]*\]//'; }
+filt_ret() { egrep -v ' (__retguard_[0-9]+|__llvm_retpoline_[a-z]+[0-9]*)$'; }
if $keep_temp
then
done
set +C
-readelf -rW $old > $odir/r1
-readelf -rW $new > $odir/r2
-
-readelf -sW $old | filt_symtab > $odir/s1
-readelf -sW $new | filt_symtab > $odir/s2
-
-case $(readelf -h $new | grep '^ *Machine:') in
-*MIPS*) cpu=mips64;;
-*HPPA*) cpu=hppa;;
-*) cpu=dontcare;;
-esac
-
-if [[ $cpu = mips64 ]]
-then
- gotsym1=$(readelf -d $old | awk '$2 ~ /MIPS_GOTSYM/{print $3}')
- gotsym2=$(readelf -d $new | awk '$2 ~ /MIPS_GOTSYM/{print $3}')
-fi
+#
+# Collect data
+#
+$dynamic && dynamic_collect
+$static && static_collect
# Now that we're done accessing $old and $new (which could be
# relative paths), chdir into our work directory, whatever it is
cd $odir
-jump_slots() {
- case $cpu in
- hppa) awk '/IPLT/ && $5 != ""{print $5}' r$1
- ;;
- mips64) # the $((gotsym$1)) converts hex to decimal
- awk -v g=$((gotsym$1)) \
- '/^Symbol table ..symtab/{exit}
- $6 == "PROTECTED" { next }
- $1+0 >= g && $4 == "FUNC" {print $8}' s$1
- ;;
- *) awk '/JU*MP_SL/ && $5 != ""{print $5}' r$1
- ;;
- esac | sort -o j$1
-}
-
-dynamic_sym() {
- awk -v s=$1 '/^Symbol table ..symtab/{exit}
- ! /^ *[1-9]/ {next}
- $7 == "UND" {print $8 | ("sort -o U" s); next }
- $5 == "GLOBAL" {print $8 | ("sort -o DS" s) }
- $5 == "WEAK" {print $8 | ("sort -o DW" s) }
- $5 != "LOCAL" {print $8 | ("sort -o D" s) }
- $5 != "LOCAL" && $4 == "OBJECT" {
- print $8, $3 | ("sort -o DO" s) }
- {print $4, $5, $6, $8}' s$1 | sort -o d$1
-}
-
-static_sym() {
- awk '/^Symbol table ..symtab/{s=1}
- /LOCAL/{next}
- s&&/^ *[1-9]/{print $4, $5, $6, $8}' s$1 | sort -o S$1
-}
-
-data_sym_changes() {
- join "$@" | awk '$2 != $3 { print $1 " " $2 " --> " $3 }'
-}
-
-output_if_not_empty() {
- leader=$1
- shift
- if "$@" | grep -q .
- then
- echo "$leader"
- "$@" | sed 's:^: :'
- echo
- fi
-}
-
-
+#
+# Do The Job
+#
for i in 1 2
do
- jump_slots $i
- dynamic_sym $i
- static_sym $i
- comm -23 j$i U$i >J$i
+ $dynamic && dynamic_analysis $i
+ $static && static_analysis $i
done
-echo "$old --> $new"
-if cmp -s d[12] && cmp -s DO[12]
-then
- printf "No dynamic export changes\n"
-else
- printf "Dynamic export changes:\n"
- output_if_not_empty "added:" comm -13 D[12]
- output_if_not_empty "removed:" comm -23 D[12]
- output_if_not_empty "weakened:" comm -12 DS1 DW2
- output_if_not_empty "strengthened:" comm -12 DW1 DS2
- output_if_not_empty "data object sizes changes:" \
- data_sym_changes DO[12]
-fi
-if ! cmp -s U[12]
-then
- printf "External reference changes:\n"
- output_if_not_empty "added:" comm -13 U[12]
- output_if_not_empty "removed:" comm -23 U[12]
-fi
-
-if $verbose; then
- printf "\nReloc counts:\nbefore:\n"
- grep ^R r1
- printf "\nafter:\n"
- grep ^R r2
-fi
+{
+ echo "$old --> $new"
+ $dynamic && dynamic_output
+ $static && static_output
+}
-output_if_not_empty "PLT added:" comm -13 J1 J2
-output_if_not_empty "PLT removed:" comm -23 J1 J2