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

Annotation of src/lib/check_sym, Revision 1.2

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