[BACK]Return to check_sym CVS log [TXT][DIR] Up to [local] / src / lib

Annotation of src/lib/check_sym, Revision 1.6

1.1       guenther    1: #!/bin/ksh
1.6     ! guenther    2: #  $OpenBSD: check_sym,v 1.5 2017/08/11 17:58:21 guenther Exp $
1.3       guenther    3: #
                      4: # Copyright (c) 2016 Philip Guenther <guenther@openbsd.org>
                      5: #
                      6: # Permission to use, copy, modify, and distribute this software for any
                      7: # purpose with or without fee is hereby granted, provided that the above
                      8: # copyright notice and this permission notice appear in all copies.
                      9: #
                     10: # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
                     11: # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
                     12: # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
                     13: # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
                     14: # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
                     15: # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
                     16: # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
                     17: #
1.1       guenther   18: #
                     19: #  check_sym -- compare the symbols and external function references in two
                     20: #      versions of a shared library
                     21: #
                     22: #  SYNOPSIS
                     23: #      check_sym [-ch] [old [new]]
                     24: #
                     25: #  DESCRIPTION
                     26: #      Library developers need to be aware when they have changed the
                     27: #      ABI of a library.  To assist them, check_sym examines two versions
                     28: #      of a shared library and reports changes to the following:
                     29: #       * the set of exported symbols and their strengths
                     30: #       * the set of undefined symbols referenced
                     31: #       * the set of lazily-resolved functions (PLT)
                     32: #
                     33: #      In each case, additions and removals are reported; for exported
                     34: #      symbols it also reports when a symbol is weakened or strengthened.
                     35: #
                     36: #      The shared libraries to compare can be specified on the
                     37: #      command-line.  Otherwise, check_sym expects to be run from the
1.6     ! guenther   38: #      source directory of a library with a shlib_version file specifying
1.1       guenther   39: #      the version being built and the new library in the obj subdirectory.
1.6     ! guenther   40: #      If the old library to compare against wasn't specified either then
1.1       guenther   41: #      check_sym will take the highest version of that library in the
                     42: #      *current* directory, or the highest version of that library in
                     43: #      /usr/lib if it wasn't present in the current directory.
                     44: #
                     45: #      check_sym uses fixed names in /tmp for its intermediate files,
                     46: #      as they contain useful details for those trying to understand
                     47: #      what changed.  If any of them cannot be created by the user,
                     48: #      the command will fail.  The files can be cleaned up using
                     49: #      the -c option.
                     50: #
                     51: #
                     52: #      The *basic* rules of thumb for library versions are: if you
                     53: #       * stop exporting a symbol, or
                     54: #       * change the size of a data symbol (not reported by check_sym)
                     55: #       * start exporting a symbol that an inter-dependent library needs
                     56: #      then you need to bump the MAJOR version of the library.
                     57: #
                     58: #      Otherwise, if you:
                     59: #       * start exporting a symbol
                     60: #      then you need to bump the MINOR version of the library.
                     61: #
                     62: #  SEE ALSO
                     63: #      readelf(1), elf(5)
                     64: #
                     65: #  AUTHORS
                     66: #      Philip Guenther <guenther@openbsd.org>
                     67: #
                     68: #  CAVEATS
                     69: #      The elf format is infinitely extendable, but check_sym only
                     70: #      handles a few weirdnesses.  Running it on or against new archs
                     71: #      may result in meaningless results.
                     72: #
                     73: #  BUGS
                     74: #      Should report changes in the size of exported data objects.
                     75: #
1.5       guenther   76: #      While the author stills find the intermediate files useful,
                     77: #      most people won't.  By default they should be placed in a
                     78: #      temp directory and removed.
                     79: #
1.1       guenther   80:
                     81: get_lib_name()
                     82: {
                     83:        sed -n 's/^[    ]*LIB[  ]*=[    ]*\([^  ]*\).*/\1/p' "$@"
                     84: }
                     85:
                     86: pick_highest()
                     87: {
                     88:        old=
                     89:        omaj=-1
                     90:        omin=0
                     91:        for i
                     92:        do
                     93:                [[ -f $i ]] || continue
                     94:                maj=${i%.*}; maj=${maj##*.}
                     95:                min=${i##*.}
                     96:                if [[ $maj -gt $omaj || ( $maj -eq $omaj && $min -gt $omin ) ]]
                     97:                then
                     98:                        old=$i
                     99:                        omaj=$maj
                    100:                        omin=$min
                    101:                fi
                    102:        done
                    103:        [[ $old != "" ]]
                    104: }
                    105:
1.5       guenther  106: usage()
                    107: {
                    108:        usage="usage: check_sym [-chv] [old [new]]"
                    109:        if [[ $# -gt 0 ]]
                    110:        then
                    111:                echo "check_sym: $@
                    112: $usage" >&2
                    113:                exit 1
                    114:        fi
                    115:        echo "$usage"
                    116:        exit 0
                    117: }
                    118:
1.4       guenther  119: file_list=/tmp/{D{,S,W},J,S,U,d,j,r,s}{1,2}
1.1       guenther  120:
1.5       guenther  121: verbose=false
                    122: while getopts :chv opt "$@"
                    123: do
                    124:        case $opt in
                    125:        h)      usage;;
                    126:        c)      rm -f $file_list
                    127:                exit 0;;
                    128:        v)      verbose=true;;
                    129:        \?)     usage "unknown option -- $OPTARG";;
                    130:        esac
                    131: done
                    132: shift $((OPTIND - 1))
                    133: [[ $# -gt 2 ]] && usage "too many arguments"
1.1       guenther  134:
                    135: # Old library?
                    136: if [[ $1 = ?(*/)lib*.so* ]]
                    137: then
                    138:        if [[ ! -f $1 ]]
                    139:        then
                    140:                echo "$1 doesn't exist" >&2
                    141:                exit 1
                    142:        fi
                    143:        old=$1
                    144:        lib=${old##*/}
                    145:        lib=${lib%%.so.*}
                    146:        shift
                    147: else
                    148:        # try determining it from the current directory
                    149:        if [[ -f Makefile ]] && lib=$(get_lib_name Makefile) &&
                    150:           [[ $lib != "" ]]
                    151:        then
                    152:                lib=lib$lib
                    153:        else
                    154:                lib=libc
                    155:        fi
                    156:
                    157:        # Is there a copy of that lib in the current directory?
                    158:        # If so, use the highest numbered one
                    159:        if ! pick_highest $lib.so.* && ! pick_highest /usr/lib/$lib.so.*
                    160:        then
                    161:                echo "unable to find $lib.so.*" >&2
                    162:                exit 1
                    163:        fi
                    164: fi
                    165:
                    166: # New library?
                    167: if [[ $1 = ?(*/)lib*.so* ]]
                    168: then
                    169:        new=$1
                    170:        shift
                    171: else
                    172:        # Dig info out of the just built library
                    173:        . ./shlib_version
                    174:        new=obj/${lib}.so.${major}.${minor}
                    175: fi
1.5       guenther  176: if [[ ! -f $new ]]
                    177: then
                    178:        echo "$new doesn't exist" >&2
                    179:        exit 1
                    180: fi
1.1       guenther  181:
                    182: # Filter the output of readelf -s to be easier to parse by removing a
                    183: # field that only appears on some symbols: [<other>: 88]
                    184: # Not really arch-specific, but I've only seen it on alpha
                    185: filt_symtab() {
                    186:        sed 's/\[<other>: [0-9a-f]*\]//'
                    187: }
                    188:
                    189: # precreate all the files we'll use, but with noclobber set to avoid
                    190: # symlink attacks
                    191: set -C
                    192: files=
                    193: trap 'rm -f $files' 1 2 15 ERR
                    194: for i in $file_list
                    195: do
                    196:        rm -f $i
                    197:        3>$i
                    198:        files="$files $i"
                    199: done
                    200: set +C
                    201:
                    202: readelf -rW $old > /tmp/r1
                    203: readelf -rW $new > /tmp/r2
                    204:
                    205: readelf -sW $old | filt_symtab > /tmp/s1
                    206: readelf -sW $new | filt_symtab > /tmp/s2
                    207:
                    208:
1.4       guenther  209: cpu=$(uname -p)
1.1       guenther  210: if [[ $cpu = mips64* ]]
                    211: then
1.4       guenther  212:        gotsym1=$(readelf -d $old | awk '$2 ~ /MIPS_GOTSYM/{print $3}')
                    213:        gotsym2=$(readelf -d $new | awk '$2 ~ /MIPS_GOTSYM/{print $3}')
1.1       guenther  214: fi
                    215:
                    216: jump_slots() {
                    217:        case $cpu in
                    218:        hppa*)  awk '/IPLT/ && $5 != ""{print $5}' /tmp/r$1
                    219:                ;;
1.4       guenther  220:        mips*)  # the $((gotsym$1)) converts hex to decimal
                    221:                awk -v g=$((gotsym$1)) \
1.1       guenther  222:                        '/^Symbol table ..symtab/{exit}
                    223:                        $1+0 >= g && $4 == "FUNC" {print $8}' /tmp/s$1
                    224:                ;;
                    225:        *)      awk '/JU*MP_SL/ && $5 != ""{print $5}' /tmp/r$1
                    226:                ;;
                    227:        esac | sort -o /tmp/j$1
                    228: }
                    229:
                    230: dynamic_sym() {
                    231:        awk -v s=$1 '/^Symbol table ..symtab/{exit}
                    232:                ! /^ *[1-9]/   {next}
                    233:                $7 == "UND"    {print $8 | ("sort -o /tmp/U" s); next }
                    234:                $5 == "GLOBAL" {print $8 | ("sort -o /tmp/DS" s) }
                    235:                $5 == "WEAK"   {print $8 | ("sort -o /tmp/DW" s) }
                    236:                $5 != "LOCAL"  {print $8 | ("sort -o /tmp/D" s) }
                    237:                {print $4, $5, $6, $8}' /tmp/s$1 | sort -o /tmp/d$1
                    238: #      awk -v s=$1 '$2 == "GLOBAL" {print $4 | ("sort -o /tmp/DS" s) }
                    239: #                   $2 == "WEAK"   {print $4 | ("sort -o /tmp/DW" s) }
                    240: #                   $1 != "SECTION"{print $4}' /tmp/d$1 | sort -o /tmp/D$1
                    241: }
                    242:
                    243: static_sym() {
                    244:        awk '/^Symbol table ..symtab/{s=1}
                    245:             /LOCAL/{next}
                    246:             s&&/^ *[1-9]/{print $4, $5, $6, $8}' /tmp/s$1 | sort -o /tmp/S$1
                    247: }
                    248:
                    249: output_if_not_empty() {
                    250:        leader=$1
                    251:        shift
                    252:        if "$@" | grep -q .
                    253:        then
                    254:                echo "$leader"
                    255:                "$@" | sed 's:^:        :'
                    256:                echo
                    257:        fi
                    258: }
                    259:
                    260:
                    261: for i in 1 2
                    262: do
                    263:        jump_slots $i
                    264:        dynamic_sym $i
                    265:        static_sym $i
                    266:        comm -23 /tmp/j$i /tmp/U$i >/tmp/J$i
                    267: done
                    268:
                    269: echo "$old --> $new"
                    270: if cmp -s /tmp/d[12]
                    271: then
                    272:        printf "No dynamic export changes\n"
                    273: else
                    274:        printf "Dynamic export changes:\n"
                    275:        output_if_not_empty "added:" comm -13 /tmp/D[12]
                    276:        output_if_not_empty "removed:" comm -23 /tmp/D[12]
                    277:        output_if_not_empty "weakened:" comm -12 /tmp/DS1 /tmp/DW2
                    278:        output_if_not_empty "strengthened:" comm -12 /tmp/DW1 /tmp/DS2
                    279: fi
                    280: if ! cmp -s /tmp/U[12]
                    281: then
                    282:        printf "External reference changes:\n"
                    283:        output_if_not_empty "added:" comm -13 /tmp/U[12]
                    284:        output_if_not_empty "removed:" comm -23 /tmp/U[12]
                    285: fi
                    286:
1.5       guenther  287: if $verbose; then
1.1       guenther  288:        printf "\nReloc counts:\nbefore:\n"
                    289:        grep ^R /tmp/r1
                    290:        printf "\nafter:\n"
                    291:        grep ^R /tmp/r2
                    292: fi
                    293:
                    294: output_if_not_empty "PLT added:" comm -13 /tmp/J1 /tmp/J2
                    295: output_if_not_empty "PLT removed:" comm -23 /tmp/J1 /tmp/J2