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