On Thu, 07 May 2026 08:02:15 +0200,
Theo Buehler <[email protected]> wrote:
> 
> On Wed, May 06, 2026 at 05:29:17PM +0100, Stuart Henderson wrote:
> > On 2026/05/06 18:18, Kirill A. Korinsky wrote:
> > > On Wed, 06 May 2026 18:02:20 +0200,
> > > Stuart Henderson <[email protected]> wrote:
> > > > 
> > > > $ diffsyms
> > > 
> > > Now I wonder that it is :)
> > 
> > local script, can't remember who it came from (at least some of it is
> > not mine, I am too lazy to remember about print -u2 and would have used
> > echo >&2 instead ;)
> 
> You shared an adapted-to-ports version of a rather old guenther
> check_sym with me a long time ago (still in ~tb/bin/check_sym).
> 
> This got out of sync with base, missing features like the data object size
> changes and also some fixes I wanted to have, such as cleaning the mess
> it left behind in /tmp, so I extracted your changes into a wrapper script
> of src/lib/check_sym and added the fake check and shared that back with
> you. Then you polished that a bit more by the looks of it.
> 
> If you want to slap an ISC license on it, my contributions seem to be
> from 2022.
> 
> I still hope someone will eventually come up with a bsd.port.mk target
> of this :)
>

Like this?

I've used /usr/src/lib/check_sym as base, get some ideas from
~tb/bin/check_sym with some polish as I thnk right and it ends with this
diff.

Index: /usr/src/share/man/man5/bsd.port.mk.5
===================================================================
RCS file: /home/cvs/src/share/man/man5/bsd.port.mk.5,v
diff -u -p -r1.654 bsd.port.mk.5
--- /usr/src/share/man/man5/bsd.port.mk.5       4 Nov 2025 12:52:28 -0000       
1.654
+++ /usr/src/share/man/man5/bsd.port.mk.5       7 May 2026 23:34:55 -0000
@@ -158,6 +158,17 @@ Essentially invoke
 env -i ${MAKE_ENV} ${MAKE_PROGRAM} ${MAKE_FLAGS} \e
        -f ${MAKE_FILE} ${ALL_TARGET}
 .Ed
+.It Cm check-diffsyms
+Compare dynamic symbols for shared libraries listed in the
+.Ev PLIST
+against the corresponding libraries from the installed package.
+The target requires an installed package for every checked subpackage
+and a completed
+.Cm fake
+stage; unchanged libraries produce no output, while changed libraries
+show added or removed libraries, or added, removed, weakened,
+strengthened, external reference, and PLT differences for matching
+libraries.
 .It Cm check-register
 Introspection target.
 Verify from the ports tree, without building anything, that the current
Index: /usr/ports/infrastructure/mk/bsd.port.mk
===================================================================
RCS file: /home/cvs/ports/infrastructure/mk/bsd.port.mk,v
diff -u -p -r1.1649 bsd.port.mk
--- /usr/ports/infrastructure/mk/bsd.port.mk    1 Apr 2026 15:14:57 -0000       
1.1649
+++ /usr/ports/infrastructure/mk/bsd.port.mk    7 May 2026 23:39:53 -0000
@@ -2026,6 +2026,7 @@ CHECK_LIB_DEPENDS_ARGS += -F pthread
 
 _CHECK_LIB_DEPENDS = PORTSDIR=${PORTSDIR} ${_PERLSCRIPT}/check-lib-depends
 _CHECK_LIB_DEPENDS += -d ${_PKG_REPO} -B ${WRKINST} ${CHECK_LIB_DEPENDS_ARGS}
+_PORT_CHECK_SYM = ${_SHSCRIPT}/port-check-sym -B ${WRKINST} -P ${PREFIX}
 
 .for _s in ${MULTI_PACKAGES}
 .  if ${STATIC_PLIST${_s}:L} == "no"
@@ -2536,7 +2537,7 @@ _internal-all _internal-build _internal-
        _internal-subpackage _internal-subupdate _internal-uninstall \
        _internal-update _internal-update-or-install _internal-generate-readmes 
\
        _internal-update-or-install-all _internal-update-plist \
-       lib-depends-check port-lib-depends-check update-patches:
+       lib-depends-check port-lib-depends-check check-diffsyms update-patches:
 .  if !defined(IGNORE_SILENT)
        @${ECHO_MSG} "===>  ${FULLPKGNAME${SUBPACKAGE}}${_MASTER} 
${IGNORE${SUBPACKAGE}} ${_EXTRA_IGNORE}."
 .  endif
@@ -2560,6 +2561,22 @@ port-lib-depends-check: ${WRKINST}/.save
                        ${_CHECK_LIB_DEPENDS} -i -s ${WRKINST}/.saved_libs; \
        done
 
+check-diffsyms: ${_FAKE_COOKIE}
+.for _S in ${BUILD_PACKAGES}
+       @b=$$(cd ${.CURDIR} && SUBPACKAGE=${_S} ${MAKE} print-plist|sed -ne 
'/^@pkgpath /s,,-e ,p'); \
+       a=$$(${PKG_INFO} -e ${FULLPKGPATH${_S}} $$b 2>/dev/null |sort -u); \
+       case $$a in \
+               '') echo 1>&2 "Fatal: no installed package for 
${FULLPKGPATH${_S}}"; exit 1;; \
+               *) d=$$(${_PBUILD} mktemp -d 
${WRKDIR}/check-diffsyms.XXXXXXXXXX || exit 1); \
+                  trap "${_PBUILD} rm -rf $$d" 0 1 2 3 15; \
+                  ${PKG_INFO} -qf $$a | sed -n '/^@lib /{ s///; p; }' | \
+                       ${_PBUILD} tee $$d/oldlibs >/dev/null || exit 1; \
+                  cd ${.CURDIR} && SUBPACKAGE=${_S} ${MAKE} print-plist-libs | 
\
+                       ${_PBUILD} ${_PORT_CHECK_SYM} -T $$d -o $$d/oldlibs; \
+                  r=$$?; ${_PBUILD} rm -rf $$d; trap - 0 1 2 3 15; exit $$r;; \
+       esac
+.endfor
+
 # Most standard port targets create a cookie to avoid being re-run.
 #
 # fetch is an exception, as it uses the files it fetches as `cookies',
@@ -3857,7 +3874,7 @@ _all_phony = ${_recursive_depends_target
        delete-package distpatch do-build do-configure do-distpatch \
        do-gen do-extract do-install do-test fetch-all \
        install-all lib-depends lib-depends-list \
-       peek-ftp port-lib-depends-check post-build post-configure \
+       peek-ftp port-lib-depends-check check-diffsyms post-build 
post-configure \
        post-distpatch post-extract post-install \
        post-patch post-test pre-build pre-configure pre-extract pre-fake \
        pre-install pre-patch pre-test prepare \
Index: /usr/ports/infrastructure/bin/port-check-sym
===================================================================
RCS file: /usr/ports/infrastructure/bin/port-check-sym
diff -N /usr/ports/infrastructure/bin/port-check-sym
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ /usr/ports/infrastructure/bin/port-check-sym        7 May 2026 23:46:34 
-0000
@@ -0,0 +1,393 @@
+#!/bin/ksh
+# $OpenBSD$
+#
+# Copyright (c) 2026 Kirill A. Korinsky <[email protected]>
+# Copyright (c) 2022 Theo Buehler <[email protected]>
+# Copyright (c) 2016,2019,2022 Philip Guenther <[email protected]>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+#
+# port-check-sym derives from /usr/src/lib/check_sym, but is
+# specialised for ports: it compares installed package libraries with
+# their WRKINST replacements, accepts PLIST library streams, and stays
+# silent when no ABI differences are found.
+
+pick_highest()
+{
+       old=
+       omaj=-1
+       omin=0
+       for i
+       do
+               [[ -f $i ]] || continue
+               maj=${i%.*}; maj=${maj##*.}
+               min=${i##*.}
+               if [[ $maj -gt $omaj || ( $maj -eq $omaj && $min -gt $omin ) ]]
+               then
+                       old=$i
+                       omaj=$maj
+                       omin=$min
+               fi
+       done
+       [[ $old != "" ]]
+}
+
+fail() { echo "$*" >&2; exit 1; }
+
+usage()
+{
+       usage="usage: port-check-sym [-hv] -B wrkinst [-P prefix] [-T tmpdir] 
[-o oldlibs] [lib ...]"
+       [[ $# -eq 0 ]] || fail "port-check-sym: $*
+$usage"
+       echo "$usage"
+       exit 0
+}
+
+
+#
+#  Output helpers
+#
+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) }
+               {print $8 | ("sort -o D" s)
+                print $4, $5, $6, $8}' Ds$1 | sort -o d$1
+}
+
+dynamic_analysis()
+{
+       jump_slots $1
+       dynamic_sym $1
+       comm -23 j$1 DU$1 >J$1
+       return 0
+}
+
+dynamic_output()
+{
+       if ! cmp -s d[12]
+       then
+               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
+       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]
+}
+
+dynamic_changed()
+{
+       ! cmp -s d[12] || ! cmp -s DU[12] || ! cmp -s J[12]
+}
+
+make_odir()
+{
+       if [[ -n $tmpdir ]]
+       then
+               mktemp -d "$tmpdir/port-check-sym.XXXXXXXXXX"
+       else
+               mktemp -dt port-check-sym.XXXXXXXXXX
+       fi
+}
+
+compare_libs()
+(
+       unset odir
+       trap 'ret=$?; rm -rf "$odir"; exit $ret' 0 1 2 15 ERR
+       odir=$(make_odir)
+
+       set -C
+       for i in $odir/$file_list
+       do
+               rm -f $i
+               3>$i
+               files="$files $i"
+       done
+       set +C
+
+
+       #
+       #  Collect data
+       #
+       dynamic_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
+
+       #
+       #  Do The Job
+       #
+       for i in 1 2
+       do
+               dynamic_analysis $i
+       done
+
+       if dynamic_changed || $verbose
+       then
+               echo "$old --> $new"
+               dynamic_output
+       fi
+)
+
+port_check_lib()
+{
+       case $1 in
+       "")     return 0;;
+       esac
+
+       case ${1##*/} in
+       lib*.so.*)      ;;
+       *)              return 0;;
+       esac
+
+       case $1 in
+       /*)     oldpath=$1
+               new=$wrkinst$1
+               ;;
+       *)      oldpath=$prefix/$1
+               new=$wrkinst$prefix/$1
+               ;;
+       esac
+
+       lib=${oldpath##*/}
+       lib=${lib%%.so.*}
+       if [[ -f $oldpath ]]
+       then
+               old=$oldpath
+       else
+               if ! pick_highest ${oldpath%/*}/$lib.so.*
+               then
+                       echo "$oldpath doesn't exist" >&2
+                       return 1
+               fi
+       fi
+       [[ -f $new ]] || {
+               echo "$new doesn't exist" >&2
+               return 1
+       }
+
+       compare_libs
+}
+
+port_check_lib_pair()
+{
+       case $1 in
+       /*)     old=$1;;
+       *)      old=$prefix/$1;;
+       esac
+       case $2 in
+       /*)     new=$wrkinst$2;;
+       *)      new=$wrkinst$prefix/$2;;
+       esac
+
+       [[ -f $old ]] || {
+               echo "$old doesn't exist" >&2
+               return 1
+       }
+       [[ -f $new ]] || {
+               echo "$new doesn't exist" >&2
+               return 1
+       }
+
+       compare_libs
+}
+
+port_lib_map()
+{
+       awk -v prefix="$prefix" '/(^|\/)lib[^\/]*\.so\./ {
+               k = $0
+               if (prefix != "" && index(k, prefix "/") == 1)
+                       k = substr(k, length(prefix) + 2)
+               sub(/\.so\..*$/, "", k)
+               print k, $0
+       }'
+}
+
+port_check_libs()
+(
+       unset odir
+       trap 'ret=$?; rm -rf "$odir"; exit $ret' 0 1 2 15 ERR
+       odir=$(make_odir)
+
+       if [[ $# -eq 0 ]]
+       then
+               cat > $odir/newlibs
+       else
+               for _lib
+               do
+                       echo "$_lib"
+               done > $odir/newlibs
+       fi
+
+       port_lib_map < $old_libs | sort -u > $odir/oldmap
+       port_lib_map < $odir/newlibs | sort -u > $odir/newmap
+
+       join -v 2 $odir/oldmap $odir/newmap |
+           sed 's/^[^ ]* //' > $odir/added
+       join -v 1 $odir/oldmap $odir/newmap |
+           sed 's/^[^ ]* //' > $odir/removed
+       if [[ -s $odir/added || -s $odir/removed ]]
+       then
+               printf "Library changes:\n"
+               output_if_not_empty "added:" cat $odir/added
+               output_if_not_empty "removed:" cat $odir/removed
+       fi
+
+       join $odir/oldmap $odir/newmap > $odir/common
+       failed=0
+       while read _key _old _new
+       do
+               if ! port_check_lib_pair "$_old" "$_new"
+               then
+                       failed=1
+               fi
+       done < $odir/common
+
+       exit $failed
+)
+
+
+unset odir
+file_list={D{,S,s,W,U},J,d,j,r}{1,2}
+
+prefix=${PREFIX:-/usr/local}
+wrkinst=${WRKINST:-}
+verbose=false
+failed=0
+old_libs=
+tmpdir=
+
+while getopts :B:ho:P:T:v opt "$@"
+do
+       case $opt in
+       B)      wrkinst=$OPTARG;;
+       h)      usage;;
+       o)      old_libs=$OPTARG;;
+       P)      prefix=$OPTARG;;
+       T)      tmpdir=$OPTARG;;
+       v)      verbose=true;;
+       \?)     usage "unknown option -- $OPTARG";;
+       esac
+done
+shift $((OPTIND - 1))
+
+[[ -n $wrkinst ]] || fail "WRKINST not set"
+[[ -z $tmpdir || -d $tmpdir ]] || fail "$tmpdir doesn't exist"
+prefix=${prefix%/}
+wrkinst=${wrkinst%/}
+
+# 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]*\]//'; }
+
+if [[ -n $old_libs ]]
+then
+       [[ -f $old_libs ]] || fail "$old_libs doesn't exist"
+       port_check_libs "$@"
+       exit $?
+fi
+
+if [[ $# -eq 0 ]]
+then
+       while IFS= read _lib
+       do
+               if ! port_check_lib "$_lib"
+               then
+                       failed=1
+               fi
+       done
+else
+       for _lib
+       do
+               if ! port_check_lib "$_lib"
+               then
+                       failed=1
+               fi
+       done
+fi
+
+exit $failed


-- 
wbr, Kirill

Reply via email to