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

Annotation of src/lib/check_sym, Revision 1.10

1.1       guenther    1: #!/bin/ksh
1.10    ! guenther    2: #  $OpenBSD: check_sym,v 1.9 2019/10/05 00:59:24 guenther Exp $
1.3       guenther    3: #
1.7       guenther    4: # Copyright (c) 2016,2019 Philip Guenther <guenther@openbsd.org>
1.3       guenther    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
1.7       guenther   54: #       * change the size of a data symbol
1.1       guenther   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
1.5       guenther   74: #      While the author stills find the intermediate files useful,
                     75: #      most people won't.  By default they should be placed in a
                     76: #      temp directory and removed.
                     77: #
1.1       guenther   78:
                     79: get_lib_name()
                     80: {
                     81:        sed -n 's/^[    ]*LIB[  ]*=[    ]*\([^  ]*\).*/\1/p' "$@"
                     82: }
                     83:
                     84: pick_highest()
                     85: {
                     86:        old=
                     87:        omaj=-1
                     88:        omin=0
                     89:        for i
                     90:        do
                     91:                [[ -f $i ]] || continue
                     92:                maj=${i%.*}; maj=${maj##*.}
                     93:                min=${i##*.}
                     94:                if [[ $maj -gt $omaj || ( $maj -eq $omaj && $min -gt $omin ) ]]
                     95:                then
                     96:                        old=$i
                     97:                        omaj=$maj
                     98:                        omin=$min
                     99:                fi
                    100:        done
                    101:        [[ $old != "" ]]
                    102: }
                    103:
1.5       guenther  104: usage()
                    105: {
                    106:        usage="usage: check_sym [-chv] [old [new]]"
                    107:        if [[ $# -gt 0 ]]
                    108:        then
                    109:                echo "check_sym: $@
                    110: $usage" >&2
                    111:                exit 1
                    112:        fi
                    113:        echo "$usage"
                    114:        exit 0
                    115: }
                    116:
1.7       guenther  117: file_list=/tmp/{D{,S,W,O},J,S,U,d,j,r,s}{1,2}
1.1       guenther  118:
1.5       guenther  119: verbose=false
                    120: while getopts :chv opt "$@"
                    121: do
                    122:        case $opt in
                    123:        h)      usage;;
                    124:        c)      rm -f $file_list
                    125:                exit 0;;
                    126:        v)      verbose=true;;
                    127:        \?)     usage "unknown option -- $OPTARG";;
                    128:        esac
                    129: done
                    130: shift $((OPTIND - 1))
                    131: [[ $# -gt 2 ]] && usage "too many arguments"
1.1       guenther  132:
                    133: # Old library?
                    134: if [[ $1 = ?(*/)lib*.so* ]]
                    135: then
                    136:        if [[ ! -f $1 ]]
                    137:        then
                    138:                echo "$1 doesn't exist" >&2
                    139:                exit 1
                    140:        fi
                    141:        old=$1
                    142:        lib=${old##*/}
                    143:        lib=${lib%%.so.*}
                    144:        shift
                    145: else
                    146:        # try determining it from the current directory
                    147:        if [[ -f Makefile ]] && lib=$(get_lib_name Makefile) &&
                    148:           [[ $lib != "" ]]
                    149:        then
                    150:                lib=lib$lib
                    151:        else
                    152:                lib=libc
                    153:        fi
                    154:
                    155:        # Is there a copy of that lib in the current directory?
                    156:        # If so, use the highest numbered one
                    157:        if ! pick_highest $lib.so.* && ! pick_highest /usr/lib/$lib.so.*
                    158:        then
                    159:                echo "unable to find $lib.so.*" >&2
                    160:                exit 1
                    161:        fi
                    162: fi
                    163:
                    164: # New library?
                    165: if [[ $1 = ?(*/)lib*.so* ]]
                    166: then
                    167:        new=$1
                    168:        shift
                    169: else
                    170:        # Dig info out of the just built library
                    171:        . ./shlib_version
                    172:        new=obj/${lib}.so.${major}.${minor}
                    173: fi
1.5       guenther  174: if [[ ! -f $new ]]
                    175: then
                    176:        echo "$new doesn't exist" >&2
                    177:        exit 1
                    178: fi
1.1       guenther  179:
                    180: # Filter the output of readelf -s to be easier to parse by removing a
                    181: # field that only appears on some symbols: [<other>: 88]
                    182: # Not really arch-specific, but I've only seen it on alpha
                    183: filt_symtab() {
                    184:        sed 's/\[<other>: [0-9a-f]*\]//'
                    185: }
                    186:
                    187: # precreate all the files we'll use, but with noclobber set to avoid
                    188: # symlink attacks
                    189: set -C
                    190: files=
                    191: trap 'rm -f $files' 1 2 15 ERR
                    192: for i in $file_list
                    193: do
                    194:        rm -f $i
                    195:        3>$i
                    196:        files="$files $i"
                    197: done
                    198: set +C
                    199:
                    200: readelf -rW $old > /tmp/r1
                    201: readelf -rW $new > /tmp/r2
                    202:
                    203: readelf -sW $old | filt_symtab > /tmp/s1
                    204: readelf -sW $new | filt_symtab > /tmp/s2
                    205:
                    206:
1.9       guenther  207: case $(readelf -h $new | grep '^ *Machine:') in
                    208: *MIPS*)        cpu=mips64;;
                    209: *HPPA*)        cpu=hppa;;
                    210: *)     cpu=dontcare;;
                    211: esac
                    212:
                    213: if [[ $cpu = mips64 ]]
1.1       guenther  214: then
1.4       guenther  215:        gotsym1=$(readelf -d $old | awk '$2 ~ /MIPS_GOTSYM/{print $3}')
                    216:        gotsym2=$(readelf -d $new | awk '$2 ~ /MIPS_GOTSYM/{print $3}')
1.1       guenther  217: fi
                    218:
                    219: jump_slots() {
                    220:        case $cpu in
1.9       guenther  221:        hppa)   awk '/IPLT/ && $5 != ""{print $5}' /tmp/r$1
1.1       guenther  222:                ;;
1.9       guenther  223:        mips64) # the $((gotsym$1)) converts hex to decimal
1.4       guenther  224:                awk -v g=$((gotsym$1)) \
1.1       guenther  225:                        '/^Symbol table ..symtab/{exit}
1.10    ! guenther  226:                        $6 == "PROTECTED" { next }
1.1       guenther  227:                        $1+0 >= g && $4 == "FUNC" {print $8}' /tmp/s$1
                    228:                ;;
                    229:        *)      awk '/JU*MP_SL/ && $5 != ""{print $5}' /tmp/r$1
                    230:                ;;
                    231:        esac | sort -o /tmp/j$1
                    232: }
                    233:
                    234: dynamic_sym() {
                    235:        awk -v s=$1 '/^Symbol table ..symtab/{exit}
                    236:                ! /^ *[1-9]/   {next}
                    237:                $7 == "UND"    {print $8 | ("sort -o /tmp/U" s); next }
                    238:                $5 == "GLOBAL" {print $8 | ("sort -o /tmp/DS" s) }
                    239:                $5 == "WEAK"   {print $8 | ("sort -o /tmp/DW" s) }
                    240:                $5 != "LOCAL"  {print $8 | ("sort -o /tmp/D" s) }
1.7       guenther  241:                $5 != "LOCAL" && $4 == "OBJECT" {
                    242:                                print $8, $3 | ("sort -o /tmp/DO" s) }
1.1       guenther  243:                {print $4, $5, $6, $8}' /tmp/s$1 | sort -o /tmp/d$1
                    244: }
                    245:
                    246: static_sym() {
                    247:        awk '/^Symbol table ..symtab/{s=1}
                    248:             /LOCAL/{next}
                    249:             s&&/^ *[1-9]/{print $4, $5, $6, $8}' /tmp/s$1 | sort -o /tmp/S$1
                    250: }
                    251:
1.7       guenther  252: data_sym_changes() {
                    253:        join "$@" | awk '$2 != $3 { print $1 " " $2 " --> " $3 }'
                    254: }
                    255:
1.1       guenther  256: output_if_not_empty() {
                    257:        leader=$1
                    258:        shift
                    259:        if "$@" | grep -q .
                    260:        then
                    261:                echo "$leader"
                    262:                "$@" | sed 's:^:        :'
                    263:                echo
                    264:        fi
                    265: }
                    266:
                    267:
                    268: for i in 1 2
                    269: do
                    270:        jump_slots $i
                    271:        dynamic_sym $i
                    272:        static_sym $i
                    273:        comm -23 /tmp/j$i /tmp/U$i >/tmp/J$i
                    274: done
                    275:
                    276: echo "$old --> $new"
1.8       guenther  277: if cmp -s /tmp/d[12] && cmp -s /tmp/DO[12]
1.1       guenther  278: then
                    279:        printf "No dynamic export changes\n"
                    280: else
                    281:        printf "Dynamic export changes:\n"
                    282:        output_if_not_empty "added:" comm -13 /tmp/D[12]
                    283:        output_if_not_empty "removed:" comm -23 /tmp/D[12]
                    284:        output_if_not_empty "weakened:" comm -12 /tmp/DS1 /tmp/DW2
                    285:        output_if_not_empty "strengthened:" comm -12 /tmp/DW1 /tmp/DS2
1.8       guenther  286:        output_if_not_empty "data object sizes changes:" \
                    287:                                        data_sym_changes /tmp/DO[12]
1.1       guenther  288: fi
                    289: if ! cmp -s /tmp/U[12]
                    290: then
                    291:        printf "External reference changes:\n"
                    292:        output_if_not_empty "added:" comm -13 /tmp/U[12]
                    293:        output_if_not_empty "removed:" comm -23 /tmp/U[12]
                    294: fi
                    295:
1.5       guenther  296: if $verbose; then
1.1       guenther  297:        printf "\nReloc counts:\nbefore:\n"
                    298:        grep ^R /tmp/r1
                    299:        printf "\nafter:\n"
                    300:        grep ^R /tmp/r2
                    301: fi
                    302:
                    303: output_if_not_empty "PLT added:" comm -13 /tmp/J1 /tmp/J2
                    304: output_if_not_empty "PLT removed:" comm -23 /tmp/J1 /tmp/J2