summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--tools/tools/shlib-compat/Makefile.sysfake9
-rw-r--r--tools/tools/shlib-compat/README22
-rwxr-xr-xtools/tools/shlib-compat/makesyscalls-fake.sh130
-rwxr-xr-xtools/tools/shlib-compat/shlib-compat-dirs.sh48
-rwxr-xr-xtools/tools/shlib-compat/shlib-compat.py1097
-rw-r--r--tools/tools/shlib-compat/test/Makefile37
-rw-r--r--tools/tools/shlib-compat/test/Makefile.inc13
-rw-r--r--tools/tools/shlib-compat/test/Versions.def11
-rw-r--r--tools/tools/shlib-compat/test/libtest1/Makefile6
-rw-r--r--tools/tools/shlib-compat/test/libtest1/Symbol.map12
-rw-r--r--tools/tools/shlib-compat/test/libtest1/test.c66
-rw-r--r--tools/tools/shlib-compat/test/libtest2/Makefile6
-rw-r--r--tools/tools/shlib-compat/test/libtest2/Symbol.map15
-rw-r--r--tools/tools/shlib-compat/test/libtest2/test.c74
-rw-r--r--tools/tools/shlib-compat/test/libtest3/Makefile6
-rw-r--r--tools/tools/shlib-compat/test/libtest3/Symbol.map12
-rw-r--r--tools/tools/shlib-compat/test/libtest3/test.c87
-rw-r--r--tools/tools/shlib-compat/test/libtestsys/Makefile11
-rw-r--r--tools/tools/shlib-compat/test/libtestsys/Symbol.map9
-rw-r--r--tools/tools/shlib-compat/test/regress.1-1.out8
-rw-r--r--tools/tools/shlib-compat/test/regress.1-2.out10
-rw-r--r--tools/tools/shlib-compat/test/regress.1-3.out8
-rw-r--r--tools/tools/shlib-compat/test/regress.2-1.out10
-rw-r--r--tools/tools/shlib-compat/test/regress.2-2.out9
-rw-r--r--tools/tools/shlib-compat/test/regress.2-3.out10
-rw-r--r--tools/tools/shlib-compat/test/regress.3-1.out8
-rw-r--r--tools/tools/shlib-compat/test/regress.3-2.out10
-rw-r--r--tools/tools/shlib-compat/test/regress.3-3.out8
-rw-r--r--tools/tools/shlib-compat/test/regress.m459
-rwxr-xr-xtools/tools/shlib-compat/test/regress.sh16
-rw-r--r--tools/tools/shlib-compat/test/regress.t6
31 files changed, 1833 insertions, 0 deletions
diff --git a/tools/tools/shlib-compat/Makefile.sysfake b/tools/tools/shlib-compat/Makefile.sysfake
new file mode 100644
index 0000000..bbd8697
--- /dev/null
+++ b/tools/tools/shlib-compat/Makefile.sysfake
@@ -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}
+
diff --git a/tools/tools/shlib-compat/README b/tools/tools/shlib-compat/README
new file mode 100644
index 0000000..28bb64f
--- /dev/null
+++ b/tools/tools/shlib-compat/README
@@ -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
diff --git a/tools/tools/shlib-compat/makesyscalls-fake.sh b/tools/tools/shlib-compat/makesyscalls-fake.sh
new file mode 100755
index 0000000..1d073da
--- /dev/null
+++ b/tools/tools/shlib-compat/makesyscalls-fake.sh
@@ -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
+ }
+'
diff --git a/tools/tools/shlib-compat/shlib-compat-dirs.sh b/tools/tools/shlib-compat/shlib-compat-dirs.sh
new file mode 100755
index 0000000..21ff309
--- /dev/null
+++ b/tools/tools/shlib-compat/shlib-compat-dirs.sh
@@ -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
diff --git a/tools/tools/shlib-compat/shlib-compat.py b/tools/tools/shlib-compat/shlib-compat.py
new file mode 100755
index 0000000..726c53f
--- /dev/null
+++ b/tools/tools/shlib-compat/shlib-compat.py
@@ -0,0 +1,1097 @@
+#!/usr/bin/env python
+#-
+# Copyright (c) 2010 Gleb Kurtsou
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# $FreeBSD$
+
+import os
+import sys
+import re
+import optparse
+
+class Config(object):
+ version = '0.1'
+ # controlled by user
+ verbose = 0
+ dump = False
+ no_dump = False
+ version_filter = None
+ symbol_filter = None
+ alias_prefixes = []
+ # misc opts
+ objdump = 'objdump'
+ dwarfdump = 'dwarfdump'
+ # debug
+ cmpcache_enabled = True
+ dwarfcache_enabled = True
+ w_alias = True
+ w_cached = False
+ w_symbol = True
+
+ class FileConfig(object):
+ filename = None
+ out = sys.stdout
+ def init(self, outname):
+ if outname and outname != '-':
+ self.out = open(outname, "w")
+
+ origfile = FileConfig()
+ newfile = FileConfig()
+
+ @classmethod
+ def init(cls):
+ cls.version_filter = StrFilter()
+ cls.symbol_filter = StrFilter()
+
+class App(object):
+ result_code = 0
+
+def warn(cond, msg):
+ if cond:
+ print >> sys.stderr, "WARN: " + msg
+
+# {{{ misc
+
+class StrFilter(object):
+ def __init__(self):
+ self.exclude = []
+ self.include = []
+
+ def compile(self):
+ self.re_exclude = [ re.compile(x) for x in self.exclude ]
+ self.re_include = [ re.compile(x) for x in self.include ]
+
+ def match(self, s):
+ if len(self.re_include):
+ matched = False
+ for r in self.re_include:
+ if r.match(s):
+ matched = True
+ break
+ if not matched:
+ return False
+ for r in self.re_exclude:
+ if r.match(s):
+ return False
+ return True
+
+class Cache(object):
+
+ class CacheStats(object):
+ def __init__(self):
+ self.hit = 0
+ self.miss = 0
+
+ def show(self, name):
+ total = self.hit + self.miss
+ if total == 0:
+ ratio = '(undef)'
+ else:
+ ratio = '%f' % (self.hit/float(total))
+ return '%s cache stats: hit: %d; miss: %d; ratio: %s' % \
+ (name, self.hit, self.miss, ratio)
+
+ def __init__(self, enabled=True, stats=None):
+ self.enabled = enabled
+ self.items = {}
+ if stats == None:
+ self.stats = Cache.CacheStats()
+ else:
+ self.stats = stats
+
+ def get(self, id):
+ if self.enabled and self.items.has_key(id):
+ self.stats.hit += 1
+ return self.items[id]
+ else:
+ self.stats.miss += 1
+ return None
+
+ def put(self, id, obj):
+ if self.enabled:
+ if self.items.has_key(id) and obj is not self.items[id]:
+ #raise ValueError("Item is already cached: %d (%s, %s)" %
+ # (id, self.items[id], obj))
+ warn(Config.w_cached, "Item is already cached: %d (%s, %s)" % \
+ (id, self.items[id], obj))
+ self.items[id] = obj
+
+ def replace(self, id, obj):
+ if self.enabled:
+ assert self.items.has_key(id)
+ self.items[id] = obj
+
+class ListDiff(object):
+ def __init__(self, orig, new):
+ self.orig = set(orig)
+ self.new = set(new)
+ self.common = self.orig & self.new
+ self.added = self.new - self.common
+ self.removed = self.orig - self.common
+
+class PrettyPrinter(object):
+ def __init__(self):
+ self.stack = []
+
+ def run_nested(self, obj):
+ ex = obj._pp_ex(self)
+ self.stack.append(ex)
+
+ def run(self, obj):
+ self._result = obj._pp(self)
+ return self._result
+
+ def nested(self):
+ return sorted(set(self.stack))
+
+ def result(self):
+ return self._result;
+
+# }}}
+
+#{{{ symbols and version maps
+
+class Symbol(object):
+ def __init__(self, name, offset, version, lib):
+ self.name = name
+ self.offset = offset
+ self.version = version
+ self.lib = lib
+ self.definition = None
+
+ @property
+ def name_ver(self):
+ return self.name + '@' + self.version
+
+ def __repr__(self):
+ return "Symbol(%s, 0x%x, %s)" % (self.name, self.offset, self.version)
+
+class CommonSymbol(object):
+ def __init__(self, origsym, newsym):
+ if origsym.name != newsym.name or origsym.version != newsym.version:
+ raise RuntimeError("Symbols have different names: %s",
+ [origsym, newsym])
+ self.origsym = origsym
+ self.newsym = newsym
+ self.name = newsym.name
+ self.version = newsym.version
+
+ def __repr__(self):
+ return "CommonSymbol(%s, %s)" % (self.name, self.version)
+
+class SymbolAlias(object):
+ def __init__(self, alias, prefix, offset):
+ assert alias.startswith(prefix)
+ self.alias = alias
+ self.name = alias[len(prefix):]
+ self.offset = offset
+
+ def __repr__(self):
+ return "SymbolAlias(%s, 0x%x)" % (self.alias, self.offset)
+
+
+class VersionMap(object):
+ def __init__(self, name):
+ self.name = name
+ self.symbols = {}
+
+ def append(self, symbol):
+ if (self.symbols.has_key(symbol.name)):
+ raise ValueError("Symbol is already defined %s@%s" %
+ (symbol.name, self.name))
+ self.symbols[symbol.name] = symbol
+
+ def names(self):
+ return self.symbols.keys()
+
+ def __repr__(self):
+ return repr(self.symbols.values())
+
+# }}}
+
+# {{{ types and definitions
+
+class Def(object):
+ _is_alias = False
+
+ def __init__(self, id, name, **kwargs):
+ self.id = id
+ self.name = name
+ self.attrs = kwargs
+
+ def __getattr__(self, attr):
+ if not self.attrs.has_key(attr):
+ raise AttributeError('%s in %s' % (attr, str(self)))
+ return self.attrs[attr]
+
+ def _name_opt(self, default=''):
+ if not self.name:
+ return default
+ return self.name
+
+ def _alias(self):
+ if self._is_alias:
+ return self.type._alias()
+ return self
+
+ def __cmp__(self, other):
+ # TODO assert 'self' and 'other' belong to different libraries
+ #print 'cmp defs: %s, %s' % (self, other)
+ a = self._alias()
+ try:
+ b = other._alias()
+ except AttributeError:
+ return 1
+ r = cmp(a.__class__, b.__class__)
+ if r == 0:
+ if a.id != 0 and b.id != 0:
+ ind = (long(a.id) << 32) + b.id
+ r = Dwarf.cmpcache.get(ind)
+ if r != None:
+ return r
+ else:
+ ind = 0
+ r = cmp(a.attrs, b.attrs)
+ if ind != 0:
+ Dwarf.cmpcache.put(ind, r)
+ else:
+ r = 0
+ #raise RuntimeError('Comparing different classes: %s, %s' %
+ # (a.__class__.__name__, b.__class__.__name__))
+ return r
+
+ def __repr__(self):
+ p = []
+ if hasattr(self, 'name'):
+ p.append("name=%s" % self.name)
+ for (k, v) in self.attrs.items():
+ if isinstance(v, Def):
+ v = v.__class__.__name__ + '(...)'
+ p.append("%s=%s" % (k, v))
+ return self.__class__.__name__ + '(' + ', '.join(p) + ')'
+
+ def _mapval(self, param, vals):
+ if param not in vals.keys():
+ raise NotImplementedError("Invalid value '%s': %s" %
+ (param, str(self)))
+ return vals[param]
+
+ def _pp_ex(self, pp):
+ raise NotImplementedError('Extended pretty print not implemeted: %s' %
+ str(self))
+
+ def _pp(self, pp):
+ raise NotImplementedError('Pretty print not implemeted: %s' % str(self))
+
+class AnonymousDef(Def):
+ def __init__(self, id, **kwargs):
+ Def.__init__(self, id, None, **kwargs)
+
+class Void(AnonymousDef):
+ _instance = None
+
+ def __new__(cls, *args, **kwargs):
+ if not cls._instance:
+ cls._instance = super(Void, cls).__new__(
+ cls, *args, **kwargs)
+ return cls._instance
+
+ def __init__(self):
+ AnonymousDef.__init__(self, 0)
+
+ def _pp(self, pp):
+ return "void"
+
+class VarArgs(AnonymousDef):
+ def _pp(self, pp):
+ return "..."
+
+class PointerDef(AnonymousDef):
+ def _pp(self, pp):
+ t = pp.run(self.type)
+ return "%s*" % (t,)
+
+class BaseTypeDef(Def):
+ inttypes = ['DW_ATE_signed', 'DW_ATE_unsigned', 'DW_ATE_unsigned_char']
+ def _pp(self, pp):
+ if self.encoding in self.inttypes:
+ sign = '' if self.encoding == 'DW_ATE_signed' else 'u'
+ bits = int(self.byte_size) * 8
+ return '%sint%s_t' % (sign, bits)
+ elif self.encoding == 'DW_ATE_signed_char' and int(self.byte_size) == 1:
+ return 'char';
+ elif self.encoding == 'DW_ATE_float':
+ return self._mapval(self.byte_size, {
+ '16': 'long double',
+ '8': 'double',
+ '4': 'float',
+ })
+ raise NotImplementedError('Invalid encoding: %s' % self)
+
+class TypeAliasDef(Def):
+ _is_alias = True
+ def _pp(self, pp):
+ alias = self._alias()
+ # push typedef name
+ if self.name and not alias.name:
+ alias.name = 'T(%s)' % self.name
+ # return type with modifiers
+ return self.type._pp(pp)
+
+class EnumerationTypeDef(Def):
+ def _pp(self, pp):
+ return 'enum ' + self._name_opt('UNKNOWN')
+
+class ConstTypeDef(AnonymousDef):
+ _is_alias = True
+ def _pp(self, pp):
+ return 'const ' + self.type._pp(pp)
+
+class VolatileTypeDef(AnonymousDef):
+ _is_alias = True
+ def _pp(self, pp):
+ return 'volatile ' + self.type._pp(pp)
+
+class ArrayDef(AnonymousDef):
+ def _pp(self, pp):
+ t = pp.run(self.type)
+ assert len(self.subranges) == 1
+ try:
+ sz = int(self.subranges[0].upper_bound) + 1
+ except ValueError:
+ s = re.sub(r'\(.+\)', '', self.subranges[0].upper_bound)
+ sz = int(s) + 1
+ return '%s[%s]' % (t, sz)
+
+class ArraySubrangeDef(AnonymousDef):
+ pass
+
+class FunctionDef(Def):
+ def _pp(self, pp):
+ result = pp.run(self.result)
+ if not self.params:
+ params = "void"
+ else:
+ params = ', '.join([ pp.run(x) for x in self.params ])
+ return "%s %s(%s);" % (result, self.name, params)
+
+class FunctionTypeDef(Def):
+ def _pp(self, pp):
+ result = pp.run(self.result)
+ if not self.params:
+ params = "void"
+ else:
+ params = ', '.join([ pp.run(x) for x in self.params ])
+ return "F(%s, %s, (%s))" % (self._name_opt(), result, params)
+
+class ParameterDef(Def):
+ def _pp(self, pp):
+ t = pp.run(self.type)
+ return "%s %s" % (t, self._name_opt())
+
+# TODO
+class StructForwardDef(Def):
+ pass
+
+class IncompleteDef(Def):
+ def update(self, complete, cache=None):
+ self.complete = complete
+ complete.incomplete = self
+ if cache != None:
+ cached = cache.get(self.id)
+ if cached != None and isinstance(cached, IncompleteDef):
+ cache.replace(self.id, complete)
+
+class StructIncompleteDef(IncompleteDef):
+ def _pp(self, pp):
+ return "struct %s" % (self.name,)
+
+class UnionIncompleteDef(IncompleteDef):
+ def _pp(self, pp):
+ return "union %s" % (self.name,)
+
+class StructDef(Def):
+ def _pp_ex(self, pp, suffix=';'):
+ members = [ pp.run(x) for x in self.members ]
+ return "struct %s { %s }%s" % \
+ (self._name_opt(), ' '.join(members), suffix)
+ def _pp(self, pp):
+ if self.name:
+ pp.run_nested(self)
+ return "struct %s" % (self.name,)
+ else:
+ return self._pp_ex(pp, suffix='')
+
+class UnionDef(Def):
+ def _pp_ex(self, pp, suffix=';'):
+ members = [ pp.run(x) for x in self.members ]
+ return "union %s { %s }%s" % \
+ (self._name_opt(), ' '.join(members), suffix)
+ def _pp(self, pp):
+ if self.name:
+ pp.run_nested(self)
+ return "union %s" % (self.name,)
+ else:
+ return self._pp_ex(pp, suffix='')
+
+class MemberDef(Def):
+ def _pp(self, pp):
+ t = pp.run(self.type)
+ if self.bit_size:
+ bits = ":%s" % self.bit_size
+ else:
+ bits = ""
+ return "%s %s%s;" % (t, self._name_opt(), bits)
+
+class Dwarf(object):
+
+ cmpcache = Cache(enabled=Config.cmpcache_enabled)
+
+ def __init__(self, dump):
+ self.dump = dump
+
+ def _build_optarg_type(self, praw):
+ type = praw.optarg('type', Void())
+ if type != Void():
+ type = self.buildref(praw.unit, type)
+ return type
+
+ def build_subprogram(self, raw):
+ if raw.optname == None:
+ raw.setname('SUBPROGRAM_NONAME_' + raw.arg('low_pc'));
+ params = [ self.build(x) for x in raw.nested ]
+ result = self._build_optarg_type(raw)
+ return FunctionDef(raw.id, raw.name, params=params, result=result)
+
+ def build_subroutine_type(self, raw):
+ params = [ self.build(x) for x in raw.nested ]
+ result = self._build_optarg_type(raw)
+ return FunctionTypeDef(raw.id, raw.optname, params=params, result=result)
+
+ def build_formal_parameter(self, raw):
+ type = self._build_optarg_type(raw)
+ return ParameterDef(raw.id, raw.optname, type=type)
+
+ def build_pointer_type(self, raw):
+ type = self._build_optarg_type(raw)
+ return PointerDef(raw.id, type=type)
+
+ def build_member(self, raw):
+ type = self.buildref(raw.unit, raw.arg('type'))
+ return MemberDef(raw.id, raw.name, type=type,
+ bit_size=raw.optarg('bit_size', None))
+
+ def build_structure_type(self, raw):
+ incomplete = raw.unit.incomplete.get(raw.id)
+ if incomplete == None:
+ incomplete = StructIncompleteDef(raw.id, raw.optname)
+ raw.unit.incomplete.put(raw.id, incomplete)
+ else:
+ return incomplete
+ members = [ self.build(x) for x in raw.nested ]
+ byte_size = raw.optarg('byte_size', None)
+ if byte_size == None:
+ obj = StructForwardDef(raw.id, raw.name, members=members,
+ forcename=raw.name)
+ obj = StructDef(raw.id, raw.optname, members=members,
+ byte_size=byte_size)
+ incomplete.update(obj, cache=raw.unit.cache)
+ return obj
+
+ def build_union_type(self, raw):
+ incomplete = raw.unit.incomplete.get(raw.id)
+ if incomplete == None:
+ incomplete = UnionIncompleteDef(raw.id, raw.optname)
+ raw.unit.incomplete.put(raw.id, incomplete)
+ else:
+ return incomplete
+ members = [ self.build(x) for x in raw.nested ]
+ byte_size = raw.optarg('byte_size', None)
+ obj = UnionDef(raw.id, raw.optname, members=members,
+ byte_size=byte_size)
+ obj.incomplete = incomplete
+ incomplete.complete = obj
+ return obj
+
+ def build_typedef(self, raw):
+ type = self._build_optarg_type(raw)
+ return TypeAliasDef(raw.id, raw.name, type=type)
+
+ def build_const_type(self, raw):
+ type = self._build_optarg_type(raw)
+ return ConstTypeDef(raw.id, type=type)
+
+ def build_volatile_type(self, raw):
+ type = self._build_optarg_type(raw)
+ return VolatileTypeDef(raw.id, type=type)
+
+ def build_enumeration_type(self, raw):
+ # TODO handle DW_TAG_enumerator ???
+ return EnumerationTypeDef(raw.id, name=raw.optname,
+ byte_size=raw.arg('byte_size'))
+
+ def build_base_type(self, raw):
+ return BaseTypeDef(raw.id, raw.optname,
+ byte_size=raw.arg('byte_size'), encoding=raw.arg('encoding'))
+
+ def build_array_type(self, raw):
+ type = self.buildref(raw.unit, raw.arg('type'))
+ subranges = [ self.build(x) for x in raw.nested ]
+ return ArrayDef(raw.id, type=type, subranges=subranges)
+
+ def build_subrange_type(self, raw):
+ type = self.buildref(raw.unit, raw.arg('type'))
+ return ArraySubrangeDef(raw.id, type=type,
+ upper_bound=raw.optarg('upper_bound', 0))
+
+ def build_unspecified_parameters(self, raw):
+ return VarArgs(raw.id)
+
+ def _get_id(self, id):
+ try:
+ return int(id)
+ except ValueError:
+ if (id.startswith('<') and id.endswith('>')):
+ return int(id[1:-1])
+ else:
+ raise ValueError("Invalid dwarf id: %s" % id)
+
+ def build(self, raw):
+ obj = raw.unit.cache.get(raw.id)
+ if obj != None:
+ return obj
+ builder_name = raw.tag.replace('DW_TAG_', 'build_')
+ try:
+ builder = getattr(self, builder_name)
+ except AttributeError:
+ raise AttributeError("Unknown dwarf tag: %s" % raw)
+ obj = builder(raw)
+ raw.unit.cache.put(obj.id, obj)
+ return obj
+
+ def buildref(self, unit, id):
+ id = self._get_id(id)
+ raw = unit.tags[id]
+ obj = self.build(raw)
+ return obj
+
+# }}}
+
+class Shlib(object):
+ def __init__(self, libfile):
+ self.libfile = libfile
+ self.versions = {}
+ self.alias_syms = {}
+
+ def parse_objdump(self):
+ objdump = ObjdumpParser(self.libfile)
+ objdump.run()
+ for p in objdump.dynamic_symbols:
+ vername = p['ver']
+ if vername.startswith('(') and vername.endswith(')'):
+ vername = vername[1:-1]
+ if not Config.version_filter.match(vername):
+ continue
+ if not Config.symbol_filter.match(p['symbol']):
+ continue
+ sym = Symbol(p['symbol'], p['offset'], vername, self)
+ if not self.versions.has_key(vername):
+ self.versions[vername] = VersionMap(vername)
+ self.versions[vername].append(sym)
+ if Config.alias_prefixes:
+ self.local_offsetmap = objdump.local_offsetmap
+ for p in objdump.local_symbols:
+ for prefix in Config.alias_prefixes:
+ if not p['symbol'].startswith(prefix):
+ continue
+ alias = SymbolAlias(p['symbol'], prefix, p['offset'])
+ if self.alias_syms.has_key(alias.name):
+ prevalias = self.alias_syms[alias.name]
+ if alias.name != prevalias.name or \
+ alias.offset != prevalias.offset:
+ warn(Config.w_alias, "Symbol alias is " \
+ "already defined: %s: %s at %08x -- %s at %08x" % \
+ (alias.alias, alias.name, alias.offset,
+ prevalias.name, prevalias.offset))
+ self.alias_syms[alias.name] = alias
+
+ def parse_dwarfdump(self):
+ dwarfdump = DwarfdumpParser(self.libfile)
+ def lookup(sym):
+ raw = None
+ try:
+ raw = dwarfdump.offsetmap[sym.offset]
+ except:
+ try:
+ localnames = self.local_offsetmap[sym.offset]
+ localnames.sort(key=lambda x: -len(x))
+ for localname in localnames:
+ if not self.alias_syms.has_key(localname):
+ continue
+ alias = self.alias_syms[localname]
+ raw = dwarfdump.offsetmap[alias.offset]
+ break
+ except:
+ pass
+ return raw
+ dwarfdump.run()
+ dwarf = Dwarf(dwarfdump)
+ for ver in self.versions.values():
+ for sym in ver.symbols.values():
+ raw = lookup(sym);
+ if not raw:
+ warn(Config.w_symbol, "Symbol %s (%s) not found at offset 0x%x" % \
+ (sym.name_ver, self.libfile, sym.offset))
+ continue
+ if Config.verbose >= 3:
+ print "Parsing symbol %s (%s)" % (sym.name_ver, self.libfile)
+ sym.definition = dwarf.build(raw)
+
+ def parse(self):
+ if not os.path.isfile(self.libfile):
+ print >> sys.stderr, ("No such file: %s" % self.libfile)
+ sys.exit(1)
+ self.parse_objdump()
+ self.parse_dwarfdump()
+
+# {{{ parsers
+
+class Parser(object):
+ def __init__(self, proc):
+ self.proc = proc
+ self.parser = self.parse_begin
+
+ def run(self):
+ fd = os.popen(self.proc, 'r')
+ while True:
+ line = fd.readline()
+ if (not line):
+ break
+ line = line.strip()
+ if (line):
+ self.parser(line)
+ err = fd.close()
+ if err:
+ print >> sys.stderr, ("Execution failed: %s" % self.proc)
+ sys.exit(2)
+
+ def parse_begin(self, line):
+ print(line)
+
+class ObjdumpParser(Parser):
+
+ re_header = re.compile('(?P<table>\w*)\s*SYMBOL TABLE:')
+
+ re_local_symbol = re.compile('(?P<offset>[0-9a-fA-F]+)\s+(?P<bind>\w+)\s+(?P<type>\w+)\s+(?P<section>[^\s]+)\s+(?P<foffset>[0-9a-fA-F]+)\s*(?P<symbol>[^\s]*)')
+ re_lame_symbol = re.compile('(?P<offset>[0-9a-fA-F]+)\s+(?P<bind>\w+)\s+\*[A-Z]+\*')
+
+ re_dynamic_symbol = re.compile('(?P<offset>[0-9a-fA-F]+)\s+(?P<bind>\w+)\s+(?P<type>\w+)\s+(?P<section>[^\s]+)\s+(?P<foffset>[0-9a-fA-F]+)\s*(?P<ver>[^\s]*)\s*(?P<symbol>[^\s]*)')
+
+ def __init__(self, libfile):
+ Parser.__init__(self, "%s -wtT %s" % (Config.objdump, libfile))
+ self.dynamic_symbols = []
+ self.local_symbols = []
+ self.local_offsetmap = {}
+
+ def parse_begin(self, line):
+ self.parse_header(line)
+
+ def add_symbol(self, table, symbol, offsetmap = None):
+ offset = int(symbol['offset'], 16);
+ symbol['offset'] = offset
+ if (offset == 0):
+ return
+ table.append(symbol)
+ if offsetmap != None:
+ if not offsetmap.has_key(offset):
+ offsetmap[offset] = [symbol['symbol']]
+ else:
+ offsetmap[offset].append(symbol['symbol'])
+
+ def parse_header(self, line):
+ m = self.re_header.match(line)
+ if (m):
+ table = m.group('table')
+ if (table == "DYNAMIC"):
+ self.parser = self.parse_dynamic
+ elif table == '':
+ self.parser = self.parse_local
+ else:
+ raise ValueError("Invalid symbol table: %s" % table)
+ return True
+ return False
+
+ def parse_local(self, line):
+ if (self.parse_header(line)):
+ return
+ if (self.re_lame_symbol.match(line)):
+ return
+ m = self.re_local_symbol.match(line)
+ if (not m):
+ return
+ #raise ValueError("Invalid symbol definition: %s" % line)
+ p = m.groupdict()
+ if (p['symbol'] and p['symbol'].find('@') == -1):
+ self.add_symbol(self.local_symbols, p, self.local_offsetmap);
+
+ def parse_dynamic(self, line):
+ if (self.parse_header(line)):
+ return
+ if (self.re_lame_symbol.match(line)):
+ return
+ m = self.re_dynamic_symbol.match(line)
+ if (not m):
+ raise ValueError("Invalid symbol definition: %s" % line)
+ p = m.groupdict()
+ if (p['symbol'] and p['ver']):
+ self.add_symbol(self.dynamic_symbols, p);
+
+class DwarfdumpParser(Parser):
+
+ tagcache_stats = Cache.CacheStats()
+
+ class Unit(object):
+ def __init__(self):
+ self.cache = Cache(enabled=Config.dwarfcache_enabled,
+ stats=DwarfdumpParser.tagcache_stats)
+ self.incomplete = Cache()
+ self.tags = {}
+
+ class Tag(object):
+ def __init__(self, unit, data):
+ self.unit = unit
+ self.id = int(data['id'])
+ self.level = int(data['level'])
+ self.tag = data['tag']
+ self.args = {}
+ self.nested = []
+
+ @property
+ def name(self):
+ return self.arg('name')
+
+ @property
+ def optname(self):
+ return self.optarg('name', None)
+
+ def setname(self, name):
+ self.args['DW_AT_name'] = name
+
+ def arg(self, a):
+ name = 'DW_AT_' + a
+ try:
+ return self.args[name]
+ except KeyError:
+ raise KeyError("Argument '%s' not found in %s: %s" %
+ (name, self, self.args))
+
+ def optarg(self, a, default):
+ try:
+ return self.arg(a)
+ except KeyError:
+ return default
+
+ def __repr__(self):
+ return "Tag(%d, %d, %s)" % (self.level, self.id, self.tag)
+
+ re_header = re.compile('<(?P<level>\d+)><(?P<id>\d+\+*\d*)><(?P<tag>\w+)>')
+ re_argname = re.compile('(?P<arg>\w+)<')
+ re_argunknown = re.compile('<Unknown AT value \w+><[^<>]+>')
+
+ skip_tags = set([
+ 'DW_TAG_lexical_block',
+ 'DW_TAG_inlined_subroutine',
+ 'DW_TAG_label',
+ 'DW_TAG_variable',
+ ])
+
+ def __init__(self, libfile):
+ Parser.__init__(self, "%s -di %s" % (Config.dwarfdump, libfile))
+ self.current_unit = None
+ self.offsetmap = {}
+ self.stack = []
+
+ def parse_begin(self, line):
+ if line == '.debug_info':
+ self.parser = self.parse_debuginfo
+ else:
+ raise ValueError("Invalid dwarfdump header: %s" % line)
+
+ def parse_argvalue(self, args):
+ assert args.startswith('<')
+ i = 1
+ cnt = 1
+ while i < len(args) and args[i]:
+ if args[i] == '<':
+ cnt += 1
+ elif args[i] == '>':
+ cnt -= 1
+ if cnt == 0:
+ break
+ i = i + 1
+ value = args[1:i]
+ args = args[i+1:]
+ return (args, value)
+
+ def parse_arg(self, tag, args):
+ m = self.re_argname.match(args)
+ if not m:
+ m = self.re_argunknown.match(args)
+ if not m:
+ raise ValueError("Invalid dwarfdump: couldn't parse arguments: %s" %
+ args)
+ args = args[len(m.group(0)):].lstrip()
+ return args
+ argname = m.group('arg')
+ args = args[len(argname):]
+ value = []
+ while len(args) > 0 and args.startswith('<'):
+ (args, v) = self.parse_argvalue(args)
+ value.append(v)
+ args = args.lstrip()
+ if len(value) == 1:
+ value = value[0]
+ tag.args[argname] = value
+ return args
+
+ def parse_debuginfo(self, line):
+ m = self.re_header.match(line)
+ if not m:
+ raise ValueError("Invalid dwarfdump: %s" % line)
+ if m.group('level') == '0':
+ self.current_unit = DwarfdumpParser.Unit()
+ return
+ tag = DwarfdumpParser.Tag(self.current_unit, m.groupdict())
+ args = line[len(m.group(0)):].lstrip()
+ while args:
+ args = self.parse_arg(tag, args)
+ tag.unit.tags[tag.id] = tag
+ if tag.args.has_key('DW_AT_low_pc') and \
+ tag.tag not in DwarfdumpParser.skip_tags:
+ offset = int(tag.args['DW_AT_low_pc'], 16)
+ if self.offsetmap.has_key(offset):
+ raise ValueError("Dwarf dump parse error: " +
+ "symbol is aleady defined at offset 0x%x" % offset)
+ self.offsetmap[offset] = tag
+ if len(self.stack) > 0:
+ prev = self.stack.pop()
+ while prev.level >= tag.level and len(self.stack) > 0:
+ prev = self.stack.pop()
+ if prev.level < tag.level:
+ assert prev.level == tag.level - 1
+ # TODO check DW_AT_sibling ???
+ if tag.tag not in DwarfdumpParser.skip_tags:
+ prev.nested.append(tag)
+ self.stack.append(prev)
+ self.stack.append(tag)
+ assert len(self.stack) == tag.level
+
+# }}}
+
+def list_str(l):
+ l = [ str(x) for x in l ]
+ l.sort()
+ return ', '.join(l)
+
+def names_ver_str(vername, names):
+ return list_str([ x + "@" + vername for x in names ])
+
+def common_symbols(origlib, newlib):
+ result = []
+ verdiff = ListDiff(origlib.versions.keys(), newlib.versions.keys())
+ if Config.verbose >= 1:
+ print 'Original versions: ', list_str(verdiff.orig)
+ print 'New versions: ', list_str(verdiff.new)
+ for vername in verdiff.added:
+ print 'Added version: ', vername
+ print ' Added symbols: ', \
+ names_ver_str(vername, newlib.versions[vername].names())
+ for vername in verdiff.removed:
+ print 'Removed version: ', vername
+ print ' Removed symbols: ', \
+ names_ver_str(vername, origlib.versions[vername].names())
+ added = []
+ removed = []
+ for vername in verdiff.common:
+ origver = origlib.versions[vername]
+ newver = newlib.versions[vername]
+ namediff = ListDiff(origver.names(), newver.names())
+ if namediff.added:
+ added.append(names_ver_str(vername, namediff.added))
+ if namediff.removed:
+ removed.append(names_ver_str(vername, namediff.removed))
+ commonver = VersionMap(vername)
+ result.append(commonver)
+ for n in namediff.common:
+ sym = CommonSymbol(origver.symbols[n], newver.symbols[n])
+ commonver.append(sym)
+ if added:
+ print 'Added symbols:'
+ for i in added:
+ print ' ', i
+ if removed:
+ print 'Removed symbols:'
+ for i in removed:
+ print ' ', i
+ return result
+
+def cmp_symbols(commonver):
+ for ver in commonver:
+ names = ver.names();
+ names.sort()
+ for symname in names:
+ sym = ver.symbols[symname]
+ match = sym.origsym.definition == sym.newsym.definition
+ if not match:
+ App.result_code = 1
+ if Config.verbose >= 1 or not match:
+ print '%s: definitions %smatch' % \
+ (sym.origsym.name_ver, "" if match else "mis")
+ if Config.dump or (not match and not Config.no_dump):
+ for x in [(sym.origsym, Config.origfile),
+ (sym.newsym, Config.newfile)]:
+ xsym = x[0]
+ xout = x[1].out
+ if not xsym.definition:
+ print >> xout, '\n// Definition not found: %s %s' % \
+ (xsym.name_ver, xsym.lib.libfile)
+ continue
+ print >> xout, '\n// Definitions mismatch: %s %s' % \
+ (xsym.name_ver, xsym.lib.libfile)
+ pp = PrettyPrinter()
+ pp.run(xsym.definition)
+ for i in pp.nested():
+ print >> xout, i
+ print >> xout, pp.result()
+
+def dump_symbols(commonver):
+ class SymbolDump(object):
+ def __init__(self, io_conf):
+ self.io_conf = io_conf
+ self.pp = PrettyPrinter()
+ self.res = []
+ def run(self, sym):
+ r = self.pp.run(sym.definition)
+ self.res.append('/* %s@%s */ %s' % (sym.name, sym.version, r))
+ def finish(self):
+ print >> self.io_conf.out, '\n// Symbol dump: version %s, library %s' % \
+ (ver.name, self.io_conf.filename)
+ for i in self.pp.nested():
+ print >> self.io_conf.out, i
+ print >> self.io_conf.out, ''
+ for i in self.res:
+ print >> self.io_conf.out, i
+ for ver in commonver:
+ names = sorted(ver.names());
+ d_orig = SymbolDump(Config.origfile)
+ d_new = SymbolDump(Config.newfile)
+ for symname in names:
+ sym = ver.symbols[symname]
+ if not sym.origsym.definition or not sym.newsym.definition:
+ # XXX
+ warn(Config.w_symbol, 'Missing symbol definition: %s@%s' % \
+ (symname, ver.name))
+ continue
+ d_orig.run(sym.origsym)
+ d_new.run(sym.newsym)
+ d_orig.finish()
+ d_new.finish()
+
+if __name__ == '__main__':
+ Config.init()
+ parser = optparse.OptionParser(usage="usage: %prog origlib newlib",
+ version="%prog " + Config.version)
+ parser.add_option('-v', '--verbose', action='count',
+ help="verbose mode, may be specified several times")
+ parser.add_option('--alias-prefix', action='append',
+ help="name prefix to try for symbol alias lookup", metavar="STR")
+ parser.add_option('--dump', action='store_true',
+ help="dump symbol definitions")
+ parser.add_option('--no-dump', action='store_true',
+ help="disable dump for mismatched symbols")
+ parser.add_option('--out-orig', action='store',
+ help="result output file for original library", metavar="ORIGFILE")
+ parser.add_option('--out-new', action='store',
+ help="result output file for new library", metavar="NEWFILE")
+ parser.add_option('--exclude-ver', action='append', metavar="RE")
+ parser.add_option('--include-ver', action='append', metavar="RE")
+ parser.add_option('--exclude-sym', action='append', metavar="RE")
+ parser.add_option('--include-sym', action='append', metavar="RE")
+ for opt in ['alias', 'cached', 'symbol']:
+ parser.add_option("--w-" + opt,
+ action="store_true", dest="w_" + opt)
+ parser.add_option("--w-no-" + opt,
+ action="store_false", dest="w_" + opt)
+ (opts, args) = parser.parse_args()
+
+ if len(args) != 2:
+ parser.print_help()
+ sys.exit(-1)
+ if opts.out_orig:
+ Config.origfile.init(opts.out_orig)
+ if opts.out_new:
+ Config.newfile.init(opts.out_new)
+ if opts.no_dump:
+ Config.dump = False
+ Config.no_dump = True
+ if opts.dump:
+ Config.dump = True
+ Config.no_dump = False
+ Config.verbose = 1
+ if opts.verbose:
+ Config.verbose = opts.verbose
+ if opts.alias_prefix:
+ Config.alias_prefixes = opts.alias_prefix
+ Config.alias_prefixes.sort(key=lambda x: -len(x))
+ for (k, v) in ({ '_sym': Config.symbol_filter,
+ '_ver': Config.version_filter }).items():
+ for a in [ 'exclude', 'include' ]:
+ opt = getattr(opts, a + k)
+ if opt:
+ getattr(v, a).extend(opt)
+ Config.version_filter.compile()
+ Config.symbol_filter.compile()
+ for w in ['w_alias', 'w_cached', 'w_symbol']:
+ if hasattr(opts, w):
+ v = getattr(opts, w)
+ if v != None:
+ setattr(Config, w, v)
+
+ (Config.origfile.filename, Config.newfile.filename) = (args[0], args[1])
+
+ origlib = Shlib(Config.origfile.filename)
+ origlib.parse()
+ newlib = Shlib(Config.newfile.filename)
+ newlib.parse()
+
+ commonver = common_symbols(origlib, newlib)
+ if Config.dump:
+ dump_symbols(commonver)
+ cmp_symbols(commonver)
+ if Config.verbose >= 4:
+ print Dwarf.cmpcache.stats.show('Cmp')
+ print DwarfdumpParser.tagcache_stats.show('Dwarf tag')
+
+ sys.exit(App.result_code)
diff --git a/tools/tools/shlib-compat/test/Makefile b/tools/tools/shlib-compat/test/Makefile
new file mode 100644
index 0000000..f4a3548
--- /dev/null
+++ b/tools/tools/shlib-compat/test/Makefile
@@ -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>
diff --git a/tools/tools/shlib-compat/test/Makefile.inc b/tools/tools/shlib-compat/test/Makefile.inc
new file mode 100644
index 0000000..14aaef6
--- /dev/null
+++ b/tools/tools/shlib-compat/test/Makefile.inc
@@ -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
diff --git a/tools/tools/shlib-compat/test/Versions.def b/tools/tools/shlib-compat/test/Versions.def
new file mode 100644
index 0000000..0f46445
--- /dev/null
+++ b/tools/tools/shlib-compat/test/Versions.def
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+TEST_1.0 {
+};
+
+TEST_1.1 {
+} TEST_1.0;
+
+TESTprivate_1.0 {
+} TEST_1.1;
+
diff --git a/tools/tools/shlib-compat/test/libtest1/Makefile b/tools/tools/shlib-compat/test/libtest1/Makefile
new file mode 100644
index 0000000..32d77d4
--- /dev/null
+++ b/tools/tools/shlib-compat/test/libtest1/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+TESTNUM= 1
+
+.include <../Makefile.inc>
+.include <bsd.lib.mk>
diff --git a/tools/tools/shlib-compat/test/libtest1/Symbol.map b/tools/tools/shlib-compat/test/libtest1/Symbol.map
new file mode 100644
index 0000000..67649ae
--- /dev/null
+++ b/tools/tools/shlib-compat/test/libtest1/Symbol.map
@@ -0,0 +1,12 @@
+/*
+ * $FreeBSD$
+ */
+
+TEST_1.0 {
+ func1;
+ func2;
+ func3;
+ func4;
+ func5;
+ func6;
+};
diff --git a/tools/tools/shlib-compat/test/libtest1/test.c b/tools/tools/shlib-compat/test/libtest1/test.c
new file mode 100644
index 0000000..dc3a561
--- /dev/null
+++ b/tools/tools/shlib-compat/test/libtest1/test.c
@@ -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);
+}
diff --git a/tools/tools/shlib-compat/test/libtest2/Makefile b/tools/tools/shlib-compat/test/libtest2/Makefile
new file mode 100644
index 0000000..f1c277d
--- /dev/null
+++ b/tools/tools/shlib-compat/test/libtest2/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+TESTNUM= 2
+
+.include <../Makefile.inc>
+.include <bsd.lib.mk>
diff --git a/tools/tools/shlib-compat/test/libtest2/Symbol.map b/tools/tools/shlib-compat/test/libtest2/Symbol.map
new file mode 100644
index 0000000..d4b7826
--- /dev/null
+++ b/tools/tools/shlib-compat/test/libtest2/Symbol.map
@@ -0,0 +1,15 @@
+/*
+ * $FreeBSD$
+ */
+
+TEST_1.0 {
+ func2;
+ func3;
+ func4;
+ func5;
+ func6;
+};
+
+TEST_1.1 {
+ func1;
+};
diff --git a/tools/tools/shlib-compat/test/libtest2/test.c b/tools/tools/shlib-compat/test/libtest2/test.c
new file mode 100644
index 0000000..c26935c
--- /dev/null
+++ b/tools/tools/shlib-compat/test/libtest2/test.c
@@ -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);
+}
diff --git a/tools/tools/shlib-compat/test/libtest3/Makefile b/tools/tools/shlib-compat/test/libtest3/Makefile
new file mode 100644
index 0000000..9f8970f
--- /dev/null
+++ b/tools/tools/shlib-compat/test/libtest3/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+TESTNUM= 3
+
+.include <../Makefile.inc>
+.include <bsd.lib.mk>
diff --git a/tools/tools/shlib-compat/test/libtest3/Symbol.map b/tools/tools/shlib-compat/test/libtest3/Symbol.map
new file mode 100644
index 0000000..67649ae
--- /dev/null
+++ b/tools/tools/shlib-compat/test/libtest3/Symbol.map
@@ -0,0 +1,12 @@
+/*
+ * $FreeBSD$
+ */
+
+TEST_1.0 {
+ func1;
+ func2;
+ func3;
+ func4;
+ func5;
+ func6;
+};
diff --git a/tools/tools/shlib-compat/test/libtest3/test.c b/tools/tools/shlib-compat/test/libtest3/test.c
new file mode 100644
index 0000000..95a169c6
--- /dev/null
+++ b/tools/tools/shlib-compat/test/libtest3/test.c
@@ -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);
diff --git a/tools/tools/shlib-compat/test/libtestsys/Makefile b/tools/tools/shlib-compat/test/libtestsys/Makefile
new file mode 100644
index 0000000..837cfc3
--- /dev/null
+++ b/tools/tools/shlib-compat/test/libtestsys/Makefile
@@ -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}
diff --git a/tools/tools/shlib-compat/test/libtestsys/Symbol.map b/tools/tools/shlib-compat/test/libtestsys/Symbol.map
new file mode 100644
index 0000000..d596923
--- /dev/null
+++ b/tools/tools/shlib-compat/test/libtestsys/Symbol.map
@@ -0,0 +1,9 @@
+/*
+ * $FreeBSD$
+ */
+
+TEST_1.0 {
+ mknod;
+ chmod;
+ stat;
+};
diff --git a/tools/tools/shlib-compat/test/regress.1-1.out b/tools/tools/shlib-compat/test/regress.1-1.out
new file mode 100644
index 0000000..5019980
--- /dev/null
+++ b/tools/tools/shlib-compat/test/regress.1-1.out
@@ -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
diff --git a/tools/tools/shlib-compat/test/regress.1-2.out b/tools/tools/shlib-compat/test/regress.1-2.out
new file mode 100644
index 0000000..981fa54
--- /dev/null
+++ b/tools/tools/shlib-compat/test/regress.1-2.out
@@ -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
diff --git a/tools/tools/shlib-compat/test/regress.1-3.out b/tools/tools/shlib-compat/test/regress.1-3.out
new file mode 100644
index 0000000..9844886
--- /dev/null
+++ b/tools/tools/shlib-compat/test/regress.1-3.out
@@ -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
diff --git a/tools/tools/shlib-compat/test/regress.2-1.out b/tools/tools/shlib-compat/test/regress.2-1.out
new file mode 100644
index 0000000..9814778
--- /dev/null
+++ b/tools/tools/shlib-compat/test/regress.2-1.out
@@ -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
diff --git a/tools/tools/shlib-compat/test/regress.2-2.out b/tools/tools/shlib-compat/test/regress.2-2.out
new file mode 100644
index 0000000..6d25425
--- /dev/null
+++ b/tools/tools/shlib-compat/test/regress.2-2.out
@@ -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
diff --git a/tools/tools/shlib-compat/test/regress.2-3.out b/tools/tools/shlib-compat/test/regress.2-3.out
new file mode 100644
index 0000000..f278ca5
--- /dev/null
+++ b/tools/tools/shlib-compat/test/regress.2-3.out
@@ -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
diff --git a/tools/tools/shlib-compat/test/regress.3-1.out b/tools/tools/shlib-compat/test/regress.3-1.out
new file mode 100644
index 0000000..9844886
--- /dev/null
+++ b/tools/tools/shlib-compat/test/regress.3-1.out
@@ -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
diff --git a/tools/tools/shlib-compat/test/regress.3-2.out b/tools/tools/shlib-compat/test/regress.3-2.out
new file mode 100644
index 0000000..f4ce323
--- /dev/null
+++ b/tools/tools/shlib-compat/test/regress.3-2.out
@@ -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
diff --git a/tools/tools/shlib-compat/test/regress.3-3.out b/tools/tools/shlib-compat/test/regress.3-3.out
new file mode 100644
index 0000000..5019980
--- /dev/null
+++ b/tools/tools/shlib-compat/test/regress.3-3.out
@@ -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
diff --git a/tools/tools/shlib-compat/test/regress.m4 b/tools/tools/shlib-compat/test/regress.m4
new file mode 100644
index 0000000..35e2ab9
--- /dev/null
+++ b/tools/tools/shlib-compat/test/regress.m4
@@ -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)
diff --git a/tools/tools/shlib-compat/test/regress.sh b/tools/tools/shlib-compat/test/regress.sh
new file mode 100755
index 0000000..e113cce
--- /dev/null
+++ b/tools/tools/shlib-compat/test/regress.sh
@@ -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()
diff --git a/tools/tools/shlib-compat/test/regress.t b/tools/tools/shlib-compat/test/regress.t
new file mode 100644
index 0000000..35feb20
--- /dev/null
+++ b/tools/tools/shlib-compat/test/regress.t
@@ -0,0 +1,6 @@
+#!/bin/sh
+# $FreeBSD$
+
+cd `dirname $0`
+
+m4 regress.m4 regress.sh | sh
OpenPOWER on IntegriCloud