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

Annotation of src/lib/check_sym, Revision 1.4

1.1       guenther    1: #!/bin/ksh
1.4     ! guenther    2: #  $OpenBSD: check_sym,v 1.3 2016/09/22 21:35:25 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
                     38: #      source directory of a library, with a shlib_version file specifying
                     39: #      the version being built and the new library in the obj subdirectory.
                     40: #      If the old library to compare against, wasn't specified either then
                     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: #
                     76:
                     77: get_lib_name()
                     78: {
                     79:        sed -n 's/^[    ]*LIB[  ]*=[    ]*\([^  ]*\).*/\1/p' "$@"
                     80: }
                     81:
                     82: pick_highest()
                     83: {
                     84:        old=
                     85:        omaj=-1
                     86:        omin=0
                     87:        for i
                     88:        do
                     89:                [[ -f $i ]] || continue
                     90:                maj=${i%.*}; maj=${maj##*.}
                     91:                min=${i##*.}
                     92:                if [[ $maj -gt $omaj || ( $maj -eq $omaj && $min -gt $omin ) ]]
                     93:                then
                     94:                        old=$i
                     95:                        omaj=$maj
                     96:                        omin=$min
                     97:                fi
                     98:        done
                     99:        [[ $old != "" ]]
                    100: }
                    101:
1.4     ! guenther  102: file_list=/tmp/{D{,S,W},J,S,U,d,j,r,s}{1,2}
1.1       guenther  103:
                    104: if [[ $1 = "-h" ]]
                    105: then
                    106:        echo "usage: $0 [-ch] [old [new]]"
                    107:        exit 0
                    108: elif [[ $1 = "-c" ]]
                    109: then
                    110:        rm -f $file_list
                    111:        exit 0
                    112: fi
                    113:
                    114: # Old library?
                    115: if [[ $1 = ?(*/)lib*.so* ]]
                    116: then
                    117:        if [[ ! -f $1 ]]
                    118:        then
                    119:                echo "$1 doesn't exist" >&2
                    120:                exit 1
                    121:        fi
                    122:        old=$1
                    123:        lib=${old##*/}
                    124:        lib=${lib%%.so.*}
                    125:        shift
                    126: else
                    127:        # try determining it from the current directory
                    128:        if [[ -f Makefile ]] && lib=$(get_lib_name Makefile) &&
                    129:           [[ $lib != "" ]]
                    130:        then
                    131:                lib=lib$lib
                    132:        else
                    133:                lib=libc
                    134:        fi
                    135:
                    136:        # Is there a copy of that lib in the current directory?
                    137:        # If so, use the highest numbered one
                    138:        if ! pick_highest $lib.so.* && ! pick_highest /usr/lib/$lib.so.*
                    139:        then
                    140:                echo "unable to find $lib.so.*" >&2
                    141:                exit 1
                    142:        fi
                    143: fi
                    144:
                    145: # New library?
                    146: if [[ $1 = ?(*/)lib*.so* ]]
                    147: then
                    148:        if [[ ! -f $1 ]]
                    149:        then
                    150:                echo "$1 doesn't exist" >&2
                    151:                exit 1
                    152:        fi
                    153:        new=$1
                    154:        shift
                    155: else
                    156:        # Dig info out of the just built library
                    157:        . ./shlib_version
                    158:        new=obj/${lib}.so.${major}.${minor}
                    159: fi
                    160:
                    161: # Filter the output of readelf -s to be easier to parse by removing a
                    162: # field that only appears on some symbols: [<other>: 88]
                    163: # Not really arch-specific, but I've only seen it on alpha
                    164: filt_symtab() {
                    165:        sed 's/\[<other>: [0-9a-f]*\]//'
                    166: }
                    167:
                    168: # precreate all the files we'll use, but with noclobber set to avoid
                    169: # symlink attacks
                    170: set -C
                    171: files=
                    172: trap 'rm -f $files' 1 2 15 ERR
                    173: for i in $file_list
                    174: do
                    175:        rm -f $i
                    176:        3>$i
                    177:        files="$files $i"
                    178: done
                    179: set +C
                    180:
                    181: readelf -rW $old > /tmp/r1
                    182: readelf -rW $new > /tmp/r2
                    183:
                    184: readelf -sW $old | filt_symtab > /tmp/s1
                    185: readelf -sW $new | filt_symtab > /tmp/s2
                    186:
                    187:
1.4     ! guenther  188: cpu=$(uname -p)
1.1       guenther  189: if [[ $cpu = mips64* ]]
                    190: then
1.4     ! guenther  191:        gotsym1=$(readelf -d $old | awk '$2 ~ /MIPS_GOTSYM/{print $3}')
        !           192:        gotsym2=$(readelf -d $new | awk '$2 ~ /MIPS_GOTSYM/{print $3}')
1.1       guenther  193: fi
                    194:
                    195: jump_slots() {
                    196:        case $cpu in
                    197:        hppa*)  awk '/IPLT/ && $5 != ""{print $5}' /tmp/r$1
                    198:                ;;
1.4     ! guenther  199:        mips*)  # the $((gotsym$1)) converts hex to decimal
        !           200:                awk -v g=$((gotsym$1)) \
1.1       guenther  201:                        '/^Symbol table ..symtab/{exit}
                    202:                        $1+0 >= g && $4 == "FUNC" {print $8}' /tmp/s$1
                    203:                ;;
                    204:        *)      awk '/JU*MP_SL/ && $5 != ""{print $5}' /tmp/r$1
                    205:                ;;
                    206:        esac | sort -o /tmp/j$1
                    207: }
                    208:
                    209: dynamic_sym() {
                    210:        awk -v s=$1 '/^Symbol table ..symtab/{exit}
                    211:                ! /^ *[1-9]/   {next}
                    212:                $7 == "UND"    {print $8 | ("sort -o /tmp/U" s); next }
                    213:                $5 == "GLOBAL" {print $8 | ("sort -o /tmp/DS" s) }
                    214:                $5 == "WEAK"   {print $8 | ("sort -o /tmp/DW" s) }
                    215:                $5 != "LOCAL"  {print $8 | ("sort -o /tmp/D" s) }
                    216:                {print $4, $5, $6, $8}' /tmp/s$1 | sort -o /tmp/d$1
                    217: #      awk -v s=$1 '$2 == "GLOBAL" {print $4 | ("sort -o /tmp/DS" s) }
                    218: #                   $2 == "WEAK"   {print $4 | ("sort -o /tmp/DW" s) }
                    219: #                   $1 != "SECTION"{print $4}' /tmp/d$1 | sort -o /tmp/D$1
                    220: }
                    221:
                    222: static_sym() {
                    223:        awk '/^Symbol table ..symtab/{s=1}
                    224:             /LOCAL/{next}
                    225:             s&&/^ *[1-9]/{print $4, $5, $6, $8}' /tmp/s$1 | sort -o /tmp/S$1
                    226: }
                    227:
                    228: output_if_not_empty() {
                    229:        leader=$1
                    230:        shift
                    231:        if "$@" | grep -q .
                    232:        then
                    233:                echo "$leader"
                    234:                "$@" | sed 's:^:        :'
                    235:                echo
                    236:        fi
                    237: }
                    238:
                    239:
                    240: for i in 1 2
                    241: do
                    242:        jump_slots $i
                    243:        dynamic_sym $i
                    244:        static_sym $i
                    245:        comm -23 /tmp/j$i /tmp/U$i >/tmp/J$i
                    246: done
                    247:
                    248: echo "$old --> $new"
                    249: if cmp -s /tmp/d[12]
                    250: then
                    251:        printf "No dynamic export changes\n"
                    252: else
                    253:        printf "Dynamic export changes:\n"
                    254:        output_if_not_empty "added:" comm -13 /tmp/D[12]
                    255:        output_if_not_empty "removed:" comm -23 /tmp/D[12]
                    256:        output_if_not_empty "weakened:" comm -12 /tmp/DS1 /tmp/DW2
                    257:        output_if_not_empty "strengthened:" comm -12 /tmp/DW1 /tmp/DS2
                    258: fi
                    259: if ! cmp -s /tmp/U[12]
                    260: then
                    261:        printf "External reference changes:\n"
                    262:        output_if_not_empty "added:" comm -13 /tmp/U[12]
                    263:        output_if_not_empty "removed:" comm -23 /tmp/U[12]
                    264: fi
                    265:
                    266: if false; then
                    267:        printf "\nReloc counts:\nbefore:\n"
                    268:        grep ^R /tmp/r1
                    269:        printf "\nafter:\n"
                    270:        grep ^R /tmp/r2
                    271: fi
                    272:
                    273: output_if_not_empty "PLT added:" comm -13 /tmp/J1 /tmp/J2
                    274: output_if_not_empty "PLT removed:" comm -23 /tmp/J1 /tmp/J2