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