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