1#!/bin/ksh 2# $OpenBSD: check_sym,v 1.4 2016/09/22 21:37:02 guenther Exp $ 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# 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 77get_lib_name() 78{ 79 sed -n 's/^[ ]*LIB[ ]*=[ ]*\([^ ]*\).*/\1/p' "$@" 80} 81 82pick_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 102file_list=/tmp/{D{,S,W},J,S,U,d,j,r,s}{1,2} 103 104if [[ $1 = "-h" ]] 105then 106 echo "usage: $0 [-ch] [old [new]]" 107 exit 0 108elif [[ $1 = "-c" ]] 109then 110 rm -f $file_list 111 exit 0 112fi 113 114# Old library? 115if [[ $1 = ?(*/)lib*.so* ]] 116then 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 126else 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 143fi 144 145# New library? 146if [[ $1 = ?(*/)lib*.so* ]] 147then 148 if [[ ! -f $1 ]] 149 then 150 echo "$1 doesn't exist" >&2 151 exit 1 152 fi 153 new=$1 154 shift 155else 156 # Dig info out of the just built library 157 . ./shlib_version 158 new=obj/${lib}.so.${major}.${minor} 159fi 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 164filt_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 170set -C 171files= 172trap 'rm -f $files' 1 2 15 ERR 173for i in $file_list 174do 175 rm -f $i 176 3>$i 177 files="$files $i" 178done 179set +C 180 181readelf -rW $old > /tmp/r1 182readelf -rW $new > /tmp/r2 183 184readelf -sW $old | filt_symtab > /tmp/s1 185readelf -sW $new | filt_symtab > /tmp/s2 186 187 188cpu=$(uname -p) 189if [[ $cpu = mips64* ]] 190then 191 gotsym1=$(readelf -d $old | awk '$2 ~ /MIPS_GOTSYM/{print $3}') 192 gotsym2=$(readelf -d $new | awk '$2 ~ /MIPS_GOTSYM/{print $3}') 193fi 194 195jump_slots() { 196 case $cpu in 197 hppa*) awk '/IPLT/ && $5 != ""{print $5}' /tmp/r$1 198 ;; 199 mips*) # the $((gotsym$1)) converts hex to decimal 200 awk -v g=$((gotsym$1)) \ 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 209dynamic_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 222static_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 228output_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 240for i in 1 2 241do 242 jump_slots $i 243 dynamic_sym $i 244 static_sym $i 245 comm -23 /tmp/j$i /tmp/U$i >/tmp/J$i 246done 247 248echo "$old --> $new" 249if cmp -s /tmp/d[12] 250then 251 printf "No dynamic export changes\n" 252else 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 258fi 259if ! cmp -s /tmp/U[12] 260then 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] 264fi 265 266if false; then 267 printf "\nReloc counts:\nbefore:\n" 268 grep ^R /tmp/r1 269 printf "\nafter:\n" 270 grep ^R /tmp/r2 271fi 272 273output_if_not_empty "PLT added:" comm -13 /tmp/J1 /tmp/J2 274output_if_not_empty "PLT removed:" comm -23 /tmp/J1 /tmp/J2 275