Add shlib-compat under tools.

shlib-compat is ABI compatibility checker for shared libraries with
symbol versioning.
This commit is contained in:
Gleb Kurtsou 2013-03-25 00:31:14 +00:00
parent 54ccc8b588
commit a1897c0cf6
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=248693
31 changed files with 1833 additions and 0 deletions

View File

@ -0,0 +1,9 @@
# $FreeBSD$
SRCS+= sysfake.c
CLEANFILES+= sysfake.c
sysfake.c: ${.CURDIR}/../../sys/kern/syscalls.master
sh ${.CURDIR}/../../tools/tools/shlib-compat/makesyscalls-fake.sh \
${.CURDIR}/../../sys/kern/syscalls.master > ${.TARGET}

View File

@ -0,0 +1,22 @@
ABI compatibility checker for shared libraries with symbol versioning.
shlib-compat uses dwarf debugging symbols to recreate definitions of
exported symbols, including function arguments and structural types.
The shlib-compat.py script requires devel/dwarfdump port to be
installed.
Syscalls in libc are implemented as assembly stubs and thus have no
debugging symbols attached. To enable sysfake stubs rebuild libc
adding the following to /etc/make.conf:
.if ${.CURDIR:M/usr/src/lib/libc}
.include "../../tools/tools/shlib-compat/Makefile.sysfake"
.endif
To compare libc.so versions compiled with sysfake stubs:
./shlib-compat.py -v --alias-prefix __sysfake_ \
--alias-prefix __sysfake_freebsd8_ \
--exclude-ver FBSDprivate \
--out-orig out-orig.c --out-new out-new.c libc.so.7.orig libc.so.7.new

View File

@ -0,0 +1,130 @@
#! /bin/sh -
#
# $FreeBSD$
set -e
case $# in
0) echo "usage: $0 input-file <config-file>" 1>&2
exit 1
;;
esac
if [ -n "$2" -a -f "$2" ]; then
. $2
fi
sed -e '
s/\$//g
:join
/\\$/{a\
N
s/\\\n//
b join
}
2,${
/^#/!s/\([{}()*,]\)/ \1 /g
}
' < $1 | awk '
BEGIN {
printf "#include <sys/param.h>\n"
printf "#include <machine/atomic.h>\n"
printf "\n"
printf "#include <sys/_semaphore.h>\n"
printf "#include <sys/aio.h>\n"
printf "#include <sys/cpuset.h>\n"
printf "#include <sys/jail.h>\n"
printf "#include <sys/linker.h>\n"
printf "#include <sys/mac.h>\n"
printf "#include <sys/module.h>\n"
printf "#include <sys/mount.h>\n"
printf "#include <sys/mqueue.h>\n"
printf "#include <sys/msg.h>\n"
printf "#include <sys/poll.h>\n"
printf "#include <sys/proc.h>\n"
printf "#include <sys/resource.h>\n"
printf "#include <sys/sem.h>\n"
printf "#include <sys/shm.h>\n"
printf "#include <sys/signal.h>\n"
printf "#include <sys/signalvar.h>\n"
printf "#include <sys/socket.h>\n"
printf "#include <sys/stat.h>\n"
printf "#include <sys/thr.h>\n"
printf "#include <sys/time.h>\n"
printf "#include <sys/timex.h>\n"
printf "#include <sys/timeffc.h>\n"
printf "#include <sys/ucontext.h>\n"
printf "#include <sys/utsname.h>\n"
printf "#include <sys/uuid.h>\n"
printf "#include <sys/wait.h>\n"
printf "\n"
printf "#ifndef _ACL_PRIVATE\n"
printf "#define _ACL_PRIVATE\n"
printf "#endif\n"
printf "#include <sys/acl.h>\n"
printf "\n"
printf "#ifndef EBUSY\n"
printf "#define errno 0\n"
printf "#define EBUSY 0\n"
printf "#endif\n"
printf "#include <sys/umtx.h>\n"
printf "\n"
# existing compat shims
printf "struct ostat;\n"
printf "struct nstat;\n"
printf "struct ostatfs;\n"
printf "struct osigaction;\n"
printf "struct osigcontext;\n"
printf "struct oaiocb;\n"
printf "union semun_old;\n"
printf "typedef unsigned int osigset_t;\n"
printf "struct msqid_ds_old;\n"
printf "struct shmid_ds_old;\n"
# TODO
printf "struct ucontext4;\n"
printf "struct sctp_sndrcvinfo;\n"
printf "\n"
}
NF < 4 || $1 !~ /^[0-9]+$/ {
next
}
$3 ~ "UNIMPL" || $3 ~ "OBSOL" || $3 ~ "NODEF" || $3 ~ "NOPROTO" ||
$3 ~ "NOSTD"{
next
}
$4 == "{" {
if ($3 ~ /COMPAT[0-9]*/) {
n = split($3, flags, /\|/)
for (i = 1; i <= n; i++) {
if (flags[i] == "COMPAT") {
$6 = "o" $6
} else if (flags[i] ~ /COMPAT[0-9]+/) {
sub(/COMPAT/, "freebsd", flags[i])
$6 = flags[i] "_" $6
}
}
}
$6 = "__sysfake_" $6
r = ""
if ($5 != "void")
r = "0"
def = ""
impl = ""
for ( i = 5; i <= NF; i++) {
if ($i == ";")
break;
if ($i == "," || $i == ")")
impl = impl " __unused"
impl = impl " " $i
def = def " " $i
}
printf "%s;\n", def
printf "%s\n{ return %s; }\n", impl, r
next
}
{
printf "invalid line: "
print
}
'

View File

@ -0,0 +1,48 @@
#!/bin/sh -e
#
# $FreeBSD$
SHLIB_COMPAT=$(dirname $0)/shlib-compat.py
if [ $# -lt 3 ]; then
echo "Usage: $0 orig-dir new-dir output-dir"
exit 1
fi
orig=$1
new=$2
out=$3
shift 3
remove_empty() {
local i
for i in $*; do
[ -s $i ] || rm -f $i
done
}
test_file() {
local i
for i in $*; do
if [ \! -f $1 ]; then
echo "file not found: $1"
return 1
fi
done
}
rorig=`realpath $orig`
rnew=`realpath $new`
list=`(cd $rorig; ls; cd $rnew; ls) | sort -u`
for i in $list; do
echo $i
test_file $orig/$i $new/$i || continue
$SHLIB_COMPAT --out-orig $out/$i.orig.c --out-new $out/$i.new.c -v "$@" \
$orig/$i $new/$i > $out/$i.cmp 2> $out/$i.err || true
remove_empty $out/$i.orig.c $out/$i.new.c $out/$i.cmp $out/$i.err
if [ -f $out/$i.orig.c -a -f $out/$i.new.c ]; then
astyle --quiet --style=bsd -k3 $out/$i.orig.c $out/$i.new.c
rm -f $out/$i.orig.c.orig $out/$i.new.c.orig
diff -u $out/$i.orig.c $out/$i.new.c > $out/$i.diff || true
fi
done

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,37 @@
# $FreeBSD$
SUBDIR= libtest1 \
libtest2 \
libtest3 \
libtestsys
NUMTEST=3
.PHONY: test regress.sh gentests gendiffs cleandiffs
test: all
sh regress.t
prove: all
prove ${.CURDIR}
regress.sh:
echo 'run() { ../shlib-compat.py --no-dump -vv libtest$$1/libtest$$1.so.0 libtest$$2/libtest$$2.so.0; }' > ${.TARGET}
N=`expr ${NUMTEST} \* ${NUMTEST}`; echo "echo 1..$$N" >> ${.TARGET}
echo 'REGRESSION_START($$1)' >> ${.TARGET}
for a in `jot ${NUMTEST}`; do for b in `jot ${NUMTEST}`; do echo "REGRESSION_TEST(\`$$a-$$b', \`run $$a $$b')"; done; done >> ${.TARGET}
echo "REGRESSION_END()" >> ${.TARGET}
gentests: regress.sh
for a in `jot ${NUMTEST}`; do for b in `jot ${NUMTEST}`; do ../shlib-compat.py -vv libtest$$a/libtest$$a.so.0 libtest$$b/libtest$$b.so.0 > regress.$$a-$$b.out; done; done
gendiffs:
for a in `jot ${NUMTEST}`; do for b in `jot ${NUMTEST}`; do (diff -ur libtest$$a libtest$$b > regress.$$a-$$b.diff || exit 0); done; done
cleandiffs:
rm -f regress.*-*.diff
clean: cleandiffs
.include <bsd.subdir.mk>

View File

@ -0,0 +1,13 @@
# $FreeBSD$
LIB= test${TESTNUM}
SHLIB_MAJOR= 0
SRCS+= test.c
WARNS?= 3
DEBUG_FLAGS?= -g
VERSION_DEF= ${.CURDIR}/../Versions.def
SYMBOL_MAPS= ${.CURDIR}/Symbol.map

View File

@ -0,0 +1,11 @@
# $FreeBSD$
TEST_1.0 {
};
TEST_1.1 {
} TEST_1.0;
TESTprivate_1.0 {
} TEST_1.1;

View File

@ -0,0 +1,6 @@
# $FreeBSD$
TESTNUM= 1
.include <../Makefile.inc>
.include <bsd.lib.mk>

View File

@ -0,0 +1,12 @@
/*
* $FreeBSD$
*/
TEST_1.0 {
func1;
func2;
func3;
func4;
func5;
func6;
};

View File

@ -0,0 +1,66 @@
/*
* $FreeBSD$
*/
#include <sys/types.h>
#include <sys/stdint.h>
struct s1 {
int32_t f1_int;
char *f2_str;
short f3_short;
uint64_t f4_uint64;
intmax_t f5_intmax;
void* f6_ptr;
};
struct s2 {
char f1_buf[30];
struct s1 *f2_s1;
};
struct s3 {
struct s1 f1_s1;
uint32_t f2_int32;
};
int func1(int a, int b);
int func2(int64_t a, uint64_t b);
void func3(struct s1 *s);
void func4(struct s1 s);
int func5(int a, void *b, struct s2 *s);
int func6(char a, struct s3 *s);
int
func1(int a, int b)
{
return (a - b);
}
int
func2(int64_t a, uint64_t b)
{
return (a - b);
}
void
func3(struct s1 *s)
{
}
void
func4(struct s1 s)
{
}
int
func5(int a, void *b, struct s2 *s)
{
return (0);
}
int
func6(char a, struct s3 *s)
{
return (0);
}

View File

@ -0,0 +1,6 @@
# $FreeBSD$
TESTNUM= 2
.include <../Makefile.inc>
.include <bsd.lib.mk>

View File

@ -0,0 +1,15 @@
/*
* $FreeBSD$
*/
TEST_1.0 {
func2;
func3;
func4;
func5;
func6;
};
TEST_1.1 {
func1;
};

View File

@ -0,0 +1,74 @@
/*
* $FreeBSD$
*/
#include <sys/types.h>
#include <sys/stdint.h>
struct s1 {
int32_t f1_int;
char *f2_str;
short f3_short;
uint64_t f4_uint64;
intmax_t f5_intmax;
void* f6_ptr;
};
struct s2 {
char f1_buf[30];
struct s1 *f2_s1;
};
struct s3 {
struct s1 f1_s1;
uint32_t f2_int32;
};
int func1(uint64_t a, uint64_t b);
int compat_func1(int a, int b);
int func2(int64_t a, uint64_t b);
void func3(struct s1 *s);
void func4(struct s1 s);
int func5(int a, void *b, struct s2 *s);
int func6(char a, struct s3 *s);
int
func1(uint64_t a, uint64_t b)
{
return (a - b);
}
int
compat_func1(int a, int b)
{
return func1(a, b);
}
__sym_compat(func1, compat_func1, TEST_1.0);
int
func2(int64_t a, uint64_t b)
{
return (a - b);
}
void
func3(struct s1 *s)
{
}
void
func4(struct s1 s)
{
}
int
func5(int a, void *b, struct s2 *s)
{
return (0);
}
int
func6(char a, struct s3 *s)
{
return (0);
}

View File

@ -0,0 +1,6 @@
# $FreeBSD$
TESTNUM= 3
.include <../Makefile.inc>
.include <bsd.lib.mk>

View File

@ -0,0 +1,12 @@
/*
* $FreeBSD$
*/
TEST_1.0 {
func1;
func2;
func3;
func4;
func5;
func6;
};

View File

@ -0,0 +1,87 @@
/*
* $FreeBSD$
*/
#include <sys/types.h>
#include <sys/stdint.h>
struct s1 {
int f1_int;
char *f2_str;
short f3_short;
uint64_t f4_uint64;
intmax_t f5_intmax;
void* f6_ptr;
};
struct s2 {
char f1_buf[20];
struct s1 *f2_s1;
};
struct s3 {
struct s1 f1_s1;
uint32_t f2_int32;
};
enum f3_t {
f3_val0, f3_val1
};
struct s4 {
struct s1 f1_s1;
uint32_t f2_int32;
enum f3_t f3_enum;
};
typedef int i32;
int func1(int a, int b);
int func2(int64_t a, uint64_t b);
void func3(struct s1 *s);
void func4(struct s1 s);
int32_t func5(i32 a, void *b, struct s2 *s);
int func6__compat(char a, struct s3 *s);
int func6(char a, struct s4 *s);
int
func1(int a, int b)
{
return (a - b);
}
int
func2(int64_t a, uint64_t b)
{
return (a - b);
}
void
func3(struct s1 *s)
{
}
void
func4(struct s1 s)
{
}
int
func5(int a, void *b, struct s2 *s)
{
return (0);
}
int
func6(char a, struct s4 *s)
{
return (0);
}
int
func6__compat(char a, struct s3 *s)
{
return (0);
}
__sym_compat(func6, func6__compat, TEST_1.0);

View File

@ -0,0 +1,11 @@
# $FreeBSD$
TESTNUM= sys
CLEANFILES+= test.c
.include <../Makefile.inc>
.include <bsd.lib.mk>
test.c: ../../makesyscalls-fake.sh
sh ${.CURDIR}/../../makesyscalls-fake.sh /usr/src/sys/kern/syscalls.master > ${.TARGET}

View File

@ -0,0 +1,9 @@
/*
* $FreeBSD$
*/
TEST_1.0 {
mknod;
chmod;
stat;
};

View File

@ -0,0 +1,8 @@
Original versions: TEST_1.0
New versions: TEST_1.0
func1@TEST_1.0: definitions match
func2@TEST_1.0: definitions match
func3@TEST_1.0: definitions match
func4@TEST_1.0: definitions match
func5@TEST_1.0: definitions match
func6@TEST_1.0: definitions match

View File

@ -0,0 +1,10 @@
Original versions: TEST_1.0
New versions: TEST_1.0, TEST_1.1
Added version: TEST_1.1
Added symbols: func1@TEST_1.1
func1@TEST_1.0: definitions match
func2@TEST_1.0: definitions match
func3@TEST_1.0: definitions match
func4@TEST_1.0: definitions match
func5@TEST_1.0: definitions match
func6@TEST_1.0: definitions match

View File

@ -0,0 +1,8 @@
Original versions: TEST_1.0
New versions: TEST_1.0
func1@TEST_1.0: definitions match
func2@TEST_1.0: definitions match
func3@TEST_1.0: definitions match
func4@TEST_1.0: definitions match
func5@TEST_1.0: definitions mismatch
func6@TEST_1.0: definitions match

View File

@ -0,0 +1,10 @@
Original versions: TEST_1.0, TEST_1.1
New versions: TEST_1.0
Removed version: TEST_1.1
Removed symbols: func1@TEST_1.1
func1@TEST_1.0: definitions match
func2@TEST_1.0: definitions match
func3@TEST_1.0: definitions match
func4@TEST_1.0: definitions match
func5@TEST_1.0: definitions match
func6@TEST_1.0: definitions match

View File

@ -0,0 +1,9 @@
Original versions: TEST_1.0, TEST_1.1
New versions: TEST_1.0, TEST_1.1
func1@TEST_1.0: definitions match
func2@TEST_1.0: definitions match
func3@TEST_1.0: definitions match
func4@TEST_1.0: definitions match
func5@TEST_1.0: definitions match
func6@TEST_1.0: definitions match
func1@TEST_1.1: definitions match

View File

@ -0,0 +1,10 @@
Original versions: TEST_1.0, TEST_1.1
New versions: TEST_1.0
Removed version: TEST_1.1
Removed symbols: func1@TEST_1.1
func1@TEST_1.0: definitions match
func2@TEST_1.0: definitions match
func3@TEST_1.0: definitions match
func4@TEST_1.0: definitions match
func5@TEST_1.0: definitions mismatch
func6@TEST_1.0: definitions match

View File

@ -0,0 +1,8 @@
Original versions: TEST_1.0
New versions: TEST_1.0
func1@TEST_1.0: definitions match
func2@TEST_1.0: definitions match
func3@TEST_1.0: definitions match
func4@TEST_1.0: definitions match
func5@TEST_1.0: definitions mismatch
func6@TEST_1.0: definitions match

View File

@ -0,0 +1,10 @@
Original versions: TEST_1.0
New versions: TEST_1.0, TEST_1.1
Added version: TEST_1.1
Added symbols: func1@TEST_1.1
func1@TEST_1.0: definitions match
func2@TEST_1.0: definitions match
func3@TEST_1.0: definitions match
func4@TEST_1.0: definitions match
func5@TEST_1.0: definitions mismatch
func6@TEST_1.0: definitions match

View File

@ -0,0 +1,8 @@
Original versions: TEST_1.0
New versions: TEST_1.0
func1@TEST_1.0: definitions match
func2@TEST_1.0: definitions match
func3@TEST_1.0: definitions match
func4@TEST_1.0: definitions match
func5@TEST_1.0: definitions match
func6@TEST_1.0: definitions match

View File

@ -0,0 +1,59 @@
# $FreeBSD$
dnl A library of routines for doing regression tests for userland utilities.
dnl Start up. We initialise the exit status to 0 (no failure) and change
dnl into the directory specified by our first argument, which is the
dnl directory to run the tests inside.
define(`REGRESSION_START',
TESTDIR=$1
if [ -z "$TESTDIR" ]; then
TESTDIR=.
fi
cd $TESTDIR
STATUS=0)
dnl Check $? to see if we passed or failed. The first parameter is the test
dnl which passed or failed. It may be nil.
define(`REGRESSION_PASSFAIL',
if [ $? -eq 0 ]; then
echo "ok - $1 # Test detected no regression. (in $TESTDIR)"
else
STATUS=$?
echo "not ok - $1 # Test failed: regression detected. See above. (in $TESTDIR)"
fi)
dnl An actual test. The first parameter is the test name. The second is the
dnl command/commands to execute for the actual test. Their exit status is
dnl checked. It is assumed that the test will output to stdout, and that the
dnl output to be used to check for regression will be in regress.TESTNAME.out.
define(`REGRESSION_TEST',
$2 | diff -u regress.$1.out -
REGRESSION_PASSFAIL($1))
dnl A freeform regression test. Only exit status is checked.
define(`REGRESSION_TEST_FREEFORM',
$2
REGRESSION_PASSFAIL($1))
dnl A regression test like REGRESSION_TEST, except only regress.out is used
dnl for checking output differences. The first argument is the command, the
dnl second argument (which may be empty) is the test name.
define(`REGRESSION_TEST_ONE',
$1 | diff -u regress.out -
REGRESSION_PASSFAIL($2))
dnl A fatal error. This will exit with the given status (first argument) and
dnl print the message (second argument) prefixed with the string "FATAL :" to
dnl the error stream.
define(`REGRESSION_FATAL',
echo "Bail out! $2 (in $TESTDIR)" > /dev/stderr
exit $1)
dnl Cleanup. Exit with the status code of the last failure. Should probably
dnl be the number of failed tests, but hey presto, this is what it does. This
dnl could also clean up potential droppings, if some forms of regression tests
dnl end up using mktemp(1) or such.
define(`REGRESSION_END',
exit $STATUS)

View File

@ -0,0 +1,16 @@
#!/bin/sh
# $FreeBSD$
run() { ../shlib-compat.py --no-dump -vv libtest$1/libtest$1.so.0.debug libtest$2/libtest$2.so.0.debug; }
echo 1..9
REGRESSION_START($1)
REGRESSION_TEST(`1-1', `run 1 1')
REGRESSION_TEST(`1-2', `run 1 2')
REGRESSION_TEST(`1-3', `run 1 3')
REGRESSION_TEST(`2-1', `run 2 1')
REGRESSION_TEST(`2-2', `run 2 2')
REGRESSION_TEST(`2-3', `run 2 3')
REGRESSION_TEST(`3-1', `run 3 1')
REGRESSION_TEST(`3-2', `run 3 2')
REGRESSION_TEST(`3-3', `run 3 3')
REGRESSION_END()

View File

@ -0,0 +1,6 @@
#!/bin/sh
# $FreeBSD$
cd `dirname $0`
m4 regress.m4 regress.sh | sh