1#!/bin/ksh 2# $OpenBSD: check_sym,v 1.2 2016/09/20 04:27:27 guenther Exp $ 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 62get_lib_name() 63{ 64 sed -n 's/^[ ]*LIB[ ]*=[ ]*\([^ ]*\).*/\1/p' "$@" 65} 66 67pick_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 87cpu=$(uname -p) 88if [[ $cpu = mips64* ]] 89then 90 file_list=/tmp/{D{,S,W,Y},J,S,U,d,j,r,s}{1,2} 91else 92 file_list=/tmp/{D{,S,W},J,S,U,d,j,r,s}{1,2} 93fi 94 95if [[ $1 = "-h" ]] 96then 97 echo "usage: $0 [-ch] [old [new]]" 98 exit 0 99elif [[ $1 = "-c" ]] 100then 101 rm -f $file_list 102 exit 0 103fi 104 105# Old library? 106if [[ $1 = ?(*/)lib*.so* ]] 107then 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 117else 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 134fi 135 136# New library? 137if [[ $1 = ?(*/)lib*.so* ]] 138then 139 if [[ ! -f $1 ]] 140 then 141 echo "$1 doesn't exist" >&2 142 exit 1 143 fi 144 new=$1 145 shift 146else 147 # Dig info out of the just built library 148 . ./shlib_version 149 new=obj/${lib}.so.${major}.${minor} 150fi 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 155filt_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 161set -C 162files= 163trap 'rm -f $files' 1 2 15 ERR 164for i in $file_list 165do 166 rm -f $i 167 3>$i 168 files="$files $i" 169done 170set +C 171 172readelf -rW $old > /tmp/r1 173readelf -rW $new > /tmp/r2 174 175readelf -sW $old | filt_symtab > /tmp/s1 176readelf -sW $new | filt_symtab > /tmp/s2 177 178 179if [[ $cpu = mips64* ]] 180then 181 readelf -d $old >/tmp/DY1 182 readelf -d $new >/tmp/DY2 183else 184 rm -f /tmp/DY[12] 185fi 186 187jump_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 202dynamic_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 217static_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 223output_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 235for i in 1 2 236do 237 jump_slots $i 238 dynamic_sym $i 239 static_sym $i 240 comm -23 /tmp/j$i /tmp/U$i >/tmp/J$i 241done 242 243echo "$old --> $new" 244if cmp -s /tmp/d[12] 245then 246 printf "No dynamic export changes\n" 247else 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 253fi 254if ! cmp -s /tmp/U[12] 255then 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] 259fi 260 261if false; then 262 printf "\nReloc counts:\nbefore:\n" 263 grep ^R /tmp/r1 264 printf "\nafter:\n" 265 grep ^R /tmp/r2 266fi 267 268output_if_not_empty "PLT added:" comm -13 /tmp/J1 /tmp/J2 269output_if_not_empty "PLT removed:" comm -23 /tmp/J1 /tmp/J2 270