From a1897c0cf68737040c4c31f9bbe987cd35ff82cc Mon Sep 17 00:00:00 2001 From: Gleb Kurtsou Date: Mon, 25 Mar 2013 00:31:14 +0000 Subject: [PATCH] Add shlib-compat under tools. shlib-compat is ABI compatibility checker for shared libraries with symbol versioning. --- tools/tools/shlib-compat/Makefile.sysfake | 9 + tools/tools/shlib-compat/README | 22 + tools/tools/shlib-compat/makesyscalls-fake.sh | 130 ++ tools/tools/shlib-compat/shlib-compat-dirs.sh | 48 + tools/tools/shlib-compat/shlib-compat.py | 1097 +++++++++++++++++ tools/tools/shlib-compat/test/Makefile | 37 + tools/tools/shlib-compat/test/Makefile.inc | 13 + tools/tools/shlib-compat/test/Versions.def | 11 + .../tools/shlib-compat/test/libtest1/Makefile | 6 + .../shlib-compat/test/libtest1/Symbol.map | 12 + tools/tools/shlib-compat/test/libtest1/test.c | 66 + .../tools/shlib-compat/test/libtest2/Makefile | 6 + .../shlib-compat/test/libtest2/Symbol.map | 15 + tools/tools/shlib-compat/test/libtest2/test.c | 74 ++ .../tools/shlib-compat/test/libtest3/Makefile | 6 + .../shlib-compat/test/libtest3/Symbol.map | 12 + tools/tools/shlib-compat/test/libtest3/test.c | 87 ++ .../shlib-compat/test/libtestsys/Makefile | 11 + .../shlib-compat/test/libtestsys/Symbol.map | 9 + tools/tools/shlib-compat/test/regress.1-1.out | 8 + tools/tools/shlib-compat/test/regress.1-2.out | 10 + tools/tools/shlib-compat/test/regress.1-3.out | 8 + tools/tools/shlib-compat/test/regress.2-1.out | 10 + tools/tools/shlib-compat/test/regress.2-2.out | 9 + tools/tools/shlib-compat/test/regress.2-3.out | 10 + tools/tools/shlib-compat/test/regress.3-1.out | 8 + tools/tools/shlib-compat/test/regress.3-2.out | 10 + tools/tools/shlib-compat/test/regress.3-3.out | 8 + tools/tools/shlib-compat/test/regress.m4 | 59 + tools/tools/shlib-compat/test/regress.sh | 16 + tools/tools/shlib-compat/test/regress.t | 6 + 31 files changed, 1833 insertions(+) create mode 100644 tools/tools/shlib-compat/Makefile.sysfake create mode 100644 tools/tools/shlib-compat/README create mode 100755 tools/tools/shlib-compat/makesyscalls-fake.sh create mode 100755 tools/tools/shlib-compat/shlib-compat-dirs.sh create mode 100755 tools/tools/shlib-compat/shlib-compat.py create mode 100644 tools/tools/shlib-compat/test/Makefile create mode 100644 tools/tools/shlib-compat/test/Makefile.inc create mode 100644 tools/tools/shlib-compat/test/Versions.def create mode 100644 tools/tools/shlib-compat/test/libtest1/Makefile create mode 100644 tools/tools/shlib-compat/test/libtest1/Symbol.map create mode 100644 tools/tools/shlib-compat/test/libtest1/test.c create mode 100644 tools/tools/shlib-compat/test/libtest2/Makefile create mode 100644 tools/tools/shlib-compat/test/libtest2/Symbol.map create mode 100644 tools/tools/shlib-compat/test/libtest2/test.c create mode 100644 tools/tools/shlib-compat/test/libtest3/Makefile create mode 100644 tools/tools/shlib-compat/test/libtest3/Symbol.map create mode 100644 tools/tools/shlib-compat/test/libtest3/test.c create mode 100644 tools/tools/shlib-compat/test/libtestsys/Makefile create mode 100644 tools/tools/shlib-compat/test/libtestsys/Symbol.map create mode 100644 tools/tools/shlib-compat/test/regress.1-1.out create mode 100644 tools/tools/shlib-compat/test/regress.1-2.out create mode 100644 tools/tools/shlib-compat/test/regress.1-3.out create mode 100644 tools/tools/shlib-compat/test/regress.2-1.out create mode 100644 tools/tools/shlib-compat/test/regress.2-2.out create mode 100644 tools/tools/shlib-compat/test/regress.2-3.out create mode 100644 tools/tools/shlib-compat/test/regress.3-1.out create mode 100644 tools/tools/shlib-compat/test/regress.3-2.out create mode 100644 tools/tools/shlib-compat/test/regress.3-3.out create mode 100644 tools/tools/shlib-compat/test/regress.m4 create mode 100755 tools/tools/shlib-compat/test/regress.sh create mode 100644 tools/tools/shlib-compat/test/regress.t diff --git a/tools/tools/shlib-compat/Makefile.sysfake b/tools/tools/shlib-compat/Makefile.sysfake new file mode 100644 index 000000000000..bbd869715fa4 --- /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 000000000000..28bb64ff5331 --- /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 000000000000..1d073dabb1b7 --- /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 " 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 \n" + printf "#include \n" + printf "\n" + printf "#include \n" + printf "#include \n" + printf "#include \n" + printf "#include \n" + printf "#include \n" + printf "#include \n" + printf "#include \n" + printf "#include \n" + printf "#include \n" + printf "#include \n" + printf "#include \n" + printf "#include \n" + printf "#include \n" + printf "#include \n" + printf "#include \n" + printf "#include \n" + printf "#include \n" + printf "#include \n" + printf "#include \n" + printf "#include \n" + printf "#include \n" + printf "#include \n" + printf "#include \n" + printf "#include \n" + printf "#include \n" + printf "#include \n" + printf "#include \n" + printf "\n" + printf "#ifndef _ACL_PRIVATE\n" + printf "#define _ACL_PRIVATE\n" + printf "#endif\n" + printf "#include \n" + printf "\n" + printf "#ifndef EBUSY\n" + printf "#define errno 0\n" + printf "#define EBUSY 0\n" + printf "#endif\n" + printf "#include \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 000000000000..21ff309874a5 --- /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 000000000000..726c53ffb967 --- /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\w*)\s*SYMBOL TABLE:') + + re_local_symbol = re.compile('(?P[0-9a-fA-F]+)\s+(?P\w+)\s+(?P\w+)\s+(?P
[^\s]+)\s+(?P[0-9a-fA-F]+)\s*(?P[^\s]*)') + re_lame_symbol = re.compile('(?P[0-9a-fA-F]+)\s+(?P\w+)\s+\*[A-Z]+\*') + + re_dynamic_symbol = re.compile('(?P[0-9a-fA-F]+)\s+(?P\w+)\s+(?P\w+)\s+(?P
[^\s]+)\s+(?P[0-9a-fA-F]+)\s*(?P[^\s]*)\s*(?P[^\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\d+)><(?P\d+\+*\d*)><(?P\w+)>') + re_argname = re.compile('(?P\w+)<') + re_argunknown = re.compile('<[^<>]+>') + + 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 000000000000..f4a354826b10 --- /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 diff --git a/tools/tools/shlib-compat/test/Makefile.inc b/tools/tools/shlib-compat/test/Makefile.inc new file mode 100644 index 000000000000..14aaef6ea956 --- /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 000000000000..0f46445cc124 --- /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 000000000000..32d77d4f31e1 --- /dev/null +++ b/tools/tools/shlib-compat/test/libtest1/Makefile @@ -0,0 +1,6 @@ +# $FreeBSD$ + +TESTNUM= 1 + +.include <../Makefile.inc> +.include 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 000000000000..67649ae01868 --- /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 000000000000..dc3a561d6782 --- /dev/null +++ b/tools/tools/shlib-compat/test/libtest1/test.c @@ -0,0 +1,66 @@ +/* + * $FreeBSD$ + */ + +#include +#include + +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 000000000000..f1c277d96c80 --- /dev/null +++ b/tools/tools/shlib-compat/test/libtest2/Makefile @@ -0,0 +1,6 @@ +# $FreeBSD$ + +TESTNUM= 2 + +.include <../Makefile.inc> +.include 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 000000000000..d4b7826913d0 --- /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 000000000000..c26935cec33e --- /dev/null +++ b/tools/tools/shlib-compat/test/libtest2/test.c @@ -0,0 +1,74 @@ +/* + * $FreeBSD$ + */ + +#include +#include + +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 000000000000..9f8970fd6aee --- /dev/null +++ b/tools/tools/shlib-compat/test/libtest3/Makefile @@ -0,0 +1,6 @@ +# $FreeBSD$ + +TESTNUM= 3 + +.include <../Makefile.inc> +.include 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 000000000000..67649ae01868 --- /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 000000000000..95a169c68236 --- /dev/null +++ b/tools/tools/shlib-compat/test/libtest3/test.c @@ -0,0 +1,87 @@ +/* + * $FreeBSD$ + */ + +#include +#include + +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 000000000000..837cfc3b3520 --- /dev/null +++ b/tools/tools/shlib-compat/test/libtestsys/Makefile @@ -0,0 +1,11 @@ +# $FreeBSD$ + +TESTNUM= sys + +CLEANFILES+= test.c + +.include <../Makefile.inc> +.include + +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 000000000000..d596923bf077 --- /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 000000000000..50199804766c --- /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 000000000000..981fa54ba44a --- /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 000000000000..9844886ad297 --- /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 000000000000..98147782442d --- /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 000000000000..6d25425be559 --- /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 000000000000..f278ca52cc8e --- /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 000000000000..9844886ad297 --- /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 000000000000..f4ce3231de06 --- /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 000000000000..50199804766c --- /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 000000000000..35e2ab958f00 --- /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 000000000000..e113cce87b63 --- /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 000000000000..35feb209a0e5 --- /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