From 45cee0b3d608f3551b74be368cddafe7d41e7e38 Mon Sep 17 00:00:00 2001 From: guenther Date: Thu, 15 Aug 2024 01:25:13 +0000 Subject: [PATCH] Support describing ABI changes for static libraries too. Try the -S option --- lib/check_sym | 370 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 240 insertions(+), 130 deletions(-) diff --git a/lib/check_sym b/lib/check_sym index 704fb1ebdc4..8333b96422c 100755 --- a/lib/check_sym +++ b/lib/check_sym @@ -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 # @@ -17,10 +17,10 @@ # # # 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: [: 88] # Not really arch-specific, but I've only seen it on alpha -filt_symtab() { - sed 's/\[: [0-9a-f]*\]//' -} +filt_symtab() { sed 's/\[: [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 -- 2.20.1