Support describing ABI changes for static libraries too.
authorguenther <guenther@openbsd.org>
Thu, 15 Aug 2024 01:25:13 +0000 (01:25 +0000)
committerguenther <guenther@openbsd.org>
Thu, 15 Aug 2024 01:25:13 +0000 (01:25 +0000)
Try the -S option

lib/check_sym

index 704fb1e..8333b96 100755 (executable)
@@ -1,5 +1,5 @@
 #!/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
@@ -33,6 +33,8 @@
 #      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
@@ -50,7 +52,7 @@
 #      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
@@ -81,7 +83,7 @@
 
 get_lib_name()
 {
-       sed -n 's/^[    ]*LIB[  ]*=[    ]*\([^  ]*\).*/\1/p' "$@"
+       sed -n '/^[     ]*LIB[  ]*=/{ s/^[^=]*=[        ]*\([^  ]*\).*/\1/p; q;}' "$@"
 }
 
 pick_highest()
@@ -104,31 +106,205 @@ 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
@@ -137,17 +313,30 @@ shift $((OPTIND - 1))
 [[ $# -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) &&
@@ -160,35 +349,39 @@ else
 
        # 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
@@ -210,112 +403,29 @@ do
 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