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