xref: /spdk/test/make/check_so_deps.sh (revision 7192849ed24874f3e9cc31e8a33a9b32c49b9506)
1#!/usr/bin/env bash
2
3if [ "$(uname -s)" = "FreeBSD" ]; then
4	echo "Not testing for shared object dependencies on FreeBSD."
5	exit 0
6fi
7
8rootdir=$(readlink -f $(dirname $0)/../..)
9
10if [[ ! -f $1 ]]; then
11	echo "ERROR: SPDK test configuration not specified"
12	exit 1
13fi
14
15source $1
16source "$rootdir/test/common/autotest_common.sh"
17
18libdir="$rootdir/build/lib"
19libdeps_file="$rootdir/mk/spdk.lib_deps.mk"
20source_abi_dir="$HOME/spdk_20_04/build/lib"
21suppression_file="$HOME/abigail_suppressions.ini"
22
23function confirm_abi_deps() {
24	local processed_so=0
25
26	if ! hash abidiff; then
27		echo "Unable to check ABI compatibility. Please install abidiff."
28		return 1
29	fi
30
31	if [ ! -d $source_abi_dir ]; then
32		echo "No source ABI available, failing this test."
33		return 1
34	fi
35
36	cat << EOF > ${suppression_file}
37[suppress_variable]
38	name = SPDK_LOG_IDXD
39[suppress_variable]
40	name = SPDK_LOG_IOAT
41[suppress_variable]
42	name = SPDK_LOG_JSON_UTIL
43[suppress_variable]
44	name = SPDK_LOG_RPC
45[suppress_variable]
46	name = SPDK_LOG_RPC_CLIENT
47[suppress_function]
48	name = spdk_jsonrpc_server_handle_request
49[suppress_function]
50	name = spdk_jsonrpc_server_handle_error
51[suppress_function]
52	name = spdk_jsonrpc_server_send_response
53[suppress_function]
54	name = spdk_jsonrpc_parse_request
55[suppress_function]
56	name = spdk_jsonrpc_free_request
57[suppress_function]
58	name = spdk_jsonrpc_parse_response
59[suppress_variable]
60	name = SPDK_LOG_LOG_RPC
61[suppress_variable]
62	name = SPDK_LOG_LOG
63[suppress_variable]
64	name = SPDK_LOG_LVOL
65[suppress_variable]
66	name = SPDK_LOG_NBD
67[suppress_function]
68	name = spdk_nbd_disk_find_by_nbd_path
69[suppress_function]
70	name = spdk_nbd_disk_first
71[suppress_function]
72	name = spdk_nbd_disk_next
73[suppress_function]
74	name = spdk_nbd_disk_get_nbd_path
75[suppress_function]
76	name = spdk_nbd_disk_get_bdev_name
77[suppress_variable]
78	name = SPDK_LOG_NET
79[suppress_function]
80	name = spdk_interface_net_interface_add_ip_address
81[suppress_function]
82	name = spdk_interface_net_interface_delete_ip_address
83[suppress_function]
84	name = spdk_interface_get_list
85[suppress_function]
86	name = spdk_get_uevent
87[suppress_function]
88	name = spdk_uevent_connect
89[suppress_function]
90	name = spdk_nvme_ctrlr_get_current_process
91[suppress_function]
92	name = spdk_nvme_ctrlr_get_process
93[suppress_function]
94	name = spdk_nvme_get_ctrlr_by_trid_unsafe
95[suppress_function]
96	name = spdk_nvme_io_msg_process
97[suppress_function]
98	name = spdk_nvme_wait_for_completion
99[suppress_function]
100	name = spdk_nvme_wait_for_completion_robust_lock
101[suppress_function]
102	name = spdk_nvme_wait_for_completion_timeout
103[suppress_variable]
104	name = SPDK_LOG_NVME
105[suppress_variable]
106	name = SPDK_LOG_OPAL
107[suppress_variable]
108	name = spdk_opal_method
109[suppress_variable]
110	name = spdk_opal_uid
111[suppress_variable]
112	name = SPDK_LOG_REDUCE
113[suppress_variable]
114	name = SPDK_LOG_THREAD
115[suppress_variable]
116	name = SPDK_LOG_TRACE
117[suppress_function]
118	name = spdk_crc32_table_init
119[suppress_function]
120	name = spdk_crc32_update
121[suppress_variable]
122	name = SPDK_LOG_VIRTIO_DEV
123[suppress_variable]
124	name = SPDK_LOG_VIRTIO_PCI
125[suppress_variable]
126	name = SPDK_LOG_VIRTIO_USER
127[suppress_variable]
128	name = SPDK_LOG_VMD
129[suppress_variable]
130	name = SPDK_LOG_ACCEL_IDXD
131[suppress_variable]
132	name = SPDK_LOG_ACCEL_IOAT
133[suppress_variable]
134	name = SPDK_LOG_AIO
135[suppress_variable]
136	name = SPDK_LOG_VBDEV_COMPRESS
137[suppress_variable]
138	name = SPDK_LOG_CRYPTO
139[suppress_variable]
140	name = SPDK_LOG_VBDEV_DELAY
141[suppress_function]
142	name = spdk_vbdev_error_create
143[suppress_function]
144	name = spdk_vbdev_error_delete
145[suppress_function]
146	name = spdk_vbdev_error_inject_error
147[suppress_variable]
148	name = SPDK_LOG_BDEV_FTL
149[suppress_variable]
150	name = SPDK_LOG_GPT_PARSE
151[suppress_variable]
152	name = SPDK_LOG_VBDEV_GPT
153[suppress_function]
154	name = spdk_gpt_parse_mbr
155[suppress_function]
156	name = spdk_gpt_parse_partition_table
157[suppress_variable]
158	name = SPDK_LOG_ISCSI_INIT
159[suppress_variable]
160	name = SPDK_LOG_LVOL_RPC
161[suppress_variable]
162	name = SPDK_LOG_VBDEV_LVOL
163[suppress_variable]
164	name = SPDK_LOG_BDEV_MALLOC
165[suppress_variable]
166	name = SPDK_LOG_BDEV_NULL
167[suppress_variable]
168	name = SPDK_LOG_BDEV_NVME
169[suppress_function]
170	name = spdk_bdev_nvme_create
171[suppress_function]
172	name = spdk_bdev_nvme_delete
173[suppress_function]
174	name = spdk_bdev_nvme_get_ctrlr
175[suppress_function]
176	name = spdk_bdev_nvme_get_io_qpair
177[suppress_function]
178	name = spdk_bdev_nvme_get_opts
179[suppress_function]
180	name = spdk_bdev_nvme_set_hotplug
181[suppress_function]
182	name = spdk_bdev_nvme_set_opts
183[suppress_function]
184	name = spdk_vbdev_opal_create
185[suppress_function]
186	name = spdk_vbdev_opal_destruct
187[suppress_function]
188	name = spdk_vbdev_opal_enable_new_user
189[suppress_function]
190	name = spdk_vbdev_opal_get_info_from_bdev
191[suppress_function]
192	name = spdk_vbdev_opal_set_lock_state
193[suppress_variable]
194	name = SPDK_LOG_BDEV_OCSSD
195[suppress_variable]
196	name = SPDK_LOG_VBDEV_OPAL
197[suppress_variable]
198	name = SPDK_LOG_OCFCTX
199[suppress_variable]
200	name = SPDK_LOG_VBDEV_PASSTHRU
201[suppress_variable]
202	name = SPDK_LOG_BDEV_PMEM
203[suppress_function]
204	name = spdk_create_pmem_disk
205[suppress_function]
206	name = spdk_delete_pmem_disk
207[suppress_variable]
208	name = SPDK_LOG_BDEV_RAID
209[suppress_variable]
210	name = SPDK_LOG_BDEV_RAID0
211[suppress_variable]
212	name = SPDK_LOG_BDEV_RAID5
213[suppress_variable]
214	name = SPDK_LOG_RAID_RPC
215[suppress_variable]
216	name = SPDK_LOG_BDEV_RBD
217[suppress_function]
218	name = spdk_bdev_rbd_create
219[suppress_function]
220	name = spdk_bdev_rbd_delete
221[suppress_function]
222	name = spdk_bdev_rbd_dup_config
223[suppress_function]
224	name = spdk_bdev_rbd_free_config
225[suppress_function]
226	name = spdk_bdev_rbd_resize
227[suppress_variable]
228	name = SPDK_LOG_VBDEV_SPLIT
229[suppress_function]
230	name = spdk_vbdev_split_destruct
231[suppress_function]
232	name = spdk_vbdev_split_get_part_base
233[suppress_variable]
234	name = SPDK_LOG_URING
235[suppress_variable]
236	name = SPDK_LOG_VIRTIO
237[suppress_variable]
238	name = SPDK_LOG_VIRTIO_BLK
239[suppress_variable]
240	name = SPDK_LOG_VBDEV_ZONE_BLOCK
241[suppress_function]
242	name = spdk_vbdev_zone_block_create
243[suppress_function]
244	name = spdk_vbdev_zone_block_delete
245[suppress_variable]
246	name = SPDK_LOG_BLOBFS_BDEV
247[suppress_variable]
248	name = SPDK_LOG_BLOBFS_BDEV_RPC
249[suppress_function]
250	name = spdk_blobfs_fuse_send_request
251[suppress_function]
252	name = spdk_blobfs_fuse_start
253[suppress_function]
254	name = spdk_blobfs_fuse_stop
255[suppress_variable]
256	name = SPDK_LOG_APP_RPC
257[suppress_function]
258	name = spdk_nvmf_parse_conf
259[suppress_variable]
260	name = SPDK_LOG_VHOST
261[suppress_variable]
262	name = SPDK_LOG_VHOST_BLK
263[suppress_variable]
264	name = SPDK_LOG_VHOST_BLK_DATA
265[suppress_variable]
266	name = SPDK_LOG_VHOST_RING
267[suppress_variable]
268	name = SPDK_LOG_VHOST_RPC
269[suppress_variable]
270	name = SPDK_LOG_VHOST_SCSI
271[suppress_variable]
272	name = SPDK_LOG_VHOST_SCSI_DATA
273[suppress_variable]
274	name = SPDK_LOG_VHOST_SCSI_QUEUE
275[suppress_variable]
276	name = spdk_vhost_scsi_device_backend
277[suppress_type]
278	name = spdk_net_impl
279[suppress_type]
280	name = spdk_lvol
281EOF
282
283	for object in "$libdir"/libspdk_*.so; do
284		so_file=$(basename $object)
285		if [ ! -f "$source_abi_dir/$so_file" ]; then
286			echo "No corresponding object for $so_file in canonical directory. Skipping."
287			continue
288		fi
289
290		if ! output=$(abidiff "$source_abi_dir/$so_file" "$libdir/$so_file" --headers-dir1 "$source_abi_dir/../../include/" --headers-dir2 "$rootdir/include" --leaf-changes-only --suppressions $suppression_file --stat); then
291			# remove any filtered out variables.
292			output=$(sed "s/ [()][^)]*[)]//g" <<< "$output")
293
294			IFS="." read -r _ _ new_so_maj new_so_min < <(readlink "$libdir/$so_file")
295			IFS="." read -r _ _ old_so_maj old_so_min < <(readlink "$source_abi_dir/$so_file")
296
297			found_abi_change=false
298			so_name_changed=no
299
300			if [[ $output == *"ELF SONAME changed"* ]]; then
301				so_name_changed=yes
302			fi
303
304			changed_leaf_types=0
305			if [[ $output =~ "leaf types summary: "([0-9]+) ]]; then
306				changed_leaf_types=${BASH_REMATCH[1]}
307			fi
308
309			removed_functions=0 changed_functions=0 added_functions=0
310			if [[ $output =~ "functions summary: "([0-9]+)" Removed, "([0-9]+)" Changed, "([0-9]+)" Added" ]]; then
311				removed_functions=${BASH_REMATCH[1]} changed_functions=${BASH_REMATCH[2]} added_functions=${BASH_REMATCH[3]}
312			fi
313
314			removed_vars=0 changed_vars=0 added_vars=0
315			if [[ $output =~ "variables summary: "([0-9]+)" Removed, "([0-9]+)" Changed, "([0-9]+)" Added" ]]; then
316				removed_vars=${BASH_REMATCH[1]} changed_vars=${BASH_REMATCH[2]} added_vars=${BASH_REMATCH[3]}
317			fi
318
319			if ((changed_leaf_types != 0)); then
320				if ((new_so_maj == old_so_maj)); then
321					touch $fail_file
322					echo "Please update the major SO version for $so_file. A header accesible type has been modified since last release."
323				fi
324				found_abi_change=true
325			fi
326
327			if ((removed_functions != 0)) || ((removed_vars != 0)); then
328				if ((new_so_maj == old_so_maj)); then
329					touch $fail_file
330					echo "Please update the major SO version for $so_file. API functions or variables have been removed since last release."
331				fi
332				found_abi_change=true
333			fi
334
335			if ((changed_functions != 0)) || ((changed_vars != 0)); then
336				if ((new_so_maj == old_so_maj)); then
337					touch $fail_file
338					echo "Please update the major SO version for $so_file. API functions or variables have been changed since last release."
339				fi
340				found_abi_change=true
341			fi
342
343			if ((added_functions != 0)) || ((added_vars != 0)); then
344				if ((new_so_min == old_so_min && new_so_maj == old_so_maj)) && ! $found_abi_change; then
345					touch $fail_file
346					echo "Please update the minor SO version for $so_file. API functions or variables have been added since last release."
347				fi
348				found_abi_change=true
349			fi
350
351			if [[ $so_name_changed == yes ]]; then
352				if ! $found_abi_change; then
353					# Unfortunately, libspdk_idxd made it into 20.04 without an SO suffix. TODO:: remove after 20.07
354					if [ "$so_file" != "libspdk_idxd.so" ] && [ "$so_file" != "libspdk_accel_idxd.so" ]; then
355						echo "SO name for $so_file changed without a change to abi. please revert that change."
356						touch $fail_file
357					fi
358				fi
359
360				if ((new_so_maj != old_so_maj && new_so_min != 0)); then
361					echo "SO major version for $so_file was bumped. Please reset the minor version to 0."
362					touch $fail_file
363				fi
364
365				expected_new_so_min=$((old_so_min + 1))
366				if ((new_so_min > old_so_min && expected_new_so_min != new_so_min)); then
367					echo "SO minor version for $so_file was incremented more than once. Please revert minor version to $expected_new_so_min."
368					touch $fail_file
369				fi
370			fi
371
372			continue
373		fi
374		processed_so=$((processed_so + 1))
375	done
376	rm -f $suppression_file
377	echo "Processed $processed_so objects."
378}
379
380# This function is needed to properly evaluate the Make variables into actual dependencies.
381function replace_defined_variables() {
382	local arr=("$@")
383	local bad_values=()
384	local good_values=()
385	local new_values
386	for dep in "${arr[@]}"; do
387		if [[ $dep == *'$'* ]]; then
388			raw_dep=${dep/$\(/}
389			raw_dep=${raw_dep/\)/ }
390			bad_values+=("$raw_dep")
391		else
392			good_values+=("$dep")
393		fi
394	done
395	for dep in "${bad_values[@]}"; do
396		dep_def_arr=($(grep -v "#" $libdeps_file | grep "${dep}" | cut -d "=" -f 2 | xargs))
397		new_values=($(replace_defined_variables "${dep_def_arr[@]}"))
398		good_values=("${good_values[@]}" "${new_values[@]}")
399	done
400	echo ${good_values[*]}
401}
402
403function confirm_deps() {
404	lib=$1
405	missing_syms=()
406	dep_names=()
407	found_symbol_lib=""
408
409	#keep the space here to differentiate bdev and bdev_*
410	lib_shortname=$(basename $lib | sed 's,libspdk_,,g' | sed 's,\.so, ,g')
411	lib_make_deps=($(grep "DEPDIRS-${lib_shortname}" $libdeps_file | cut -d "=" -f 2 | xargs))
412	lib_make_deps=($(replace_defined_variables "${lib_make_deps[@]}"))
413
414	for ign_dep in "${IGNORED_LIBS[@]}"; do
415		for i in "${!lib_make_deps[@]}"; do
416			if [[ ${lib_make_deps[i]} == "$ign_dep" ]]; then
417				unset 'lib_make_deps[i]'
418			fi
419		done
420	done
421
422	symbols=$(readelf -s $lib | grep -E "NOTYPE.*GLOBAL.*UND" | awk '{print $8}' | sort | uniq)
423	for symbol in $symbols; do
424		for deplib in $DEP_LIBS; do
425			if [ "$deplib" == "$lib" ]; then
426				continue
427			fi
428			found_symbol=$(readelf -s $deplib | grep -E "DEFAULT\s+[0-9]+\s$symbol$") || true
429			if [ "$found_symbol" != "" ]; then
430				found_symbol_lib=$(basename $deplib | sed 's,libspdk_,,g' | sed 's,\.so,,g')
431				break
432			fi
433		done
434		if [ "$found_symbol" == "" ]; then
435			missing_syms+=("$symbol")
436		else
437			dep_names+=("$found_symbol_lib")
438		fi
439	done
440	IFS=$'\n'
441	# Ignore any event_* dependencies. Those are based on the subsystem configuration and not readelf.
442	lib_make_deps=($(printf "%s\n" "${lib_make_deps[@]}" | sort | grep -v "event_"))
443	# Ignore the env_dpdk readelf dependency. We don't want people explicitly linking against it.
444	dep_names=($(printf "%s\n" "${dep_names[@]}" | sort | uniq | grep -v "env_dpdk"))
445	unset IFS
446	diff=$(echo "${dep_names[@]}" "${lib_make_deps[@]}" | tr ' ' '\n' | sort | uniq -u)
447	if [ "$diff" != "" ]; then
448		touch $fail_file
449		echo "there was a dependency mismatch in the library $lib_shortname"
450		echo "The makefile lists: '${lib_make_deps[*]}'"
451		echo "readelf outputs   : '${dep_names[*]}'"
452		echo "---------------------------------------------------------------------"
453	fi
454}
455
456# By removing the spdk.lib_deps.mk file from spdk.lib.mk, we ensure that we won't
457# create any link dependencies. Then we can be sure we get a valid accounting of the
458# symbol dependencies we have.
459sed -i -e 's,include $(SPDK_ROOT_DIR)/mk/spdk.lib_deps.mk,,g' "$rootdir/mk/spdk.lib.mk"
460
461source ~/autorun-spdk.conf
462config_params=$(get_config_params)
463if [ "$SPDK_TEST_OCF" -eq 1 ]; then
464	config_params="$config_params --with-ocf=$rootdir/build/ocf.a"
465fi
466
467$MAKE $MAKEFLAGS clean
468./configure $config_params --with-shared
469$MAKE $MAKEFLAGS
470
471xtrace_disable
472
473fail_file=$output_dir/check_so_deps_fail
474
475rm -f $fail_file
476
477run_test "confirm_abi_deps" confirm_abi_deps
478
479echo "---------------------------------------------------------------------"
480# Exclude libspdk_env_dpdk.so from the library list. We don't link against this one so that
481# users can define their own environment abstraction. However we do want to still check it
482# for dependencies to avoid printing out a bunch of confusing symbols under the missing
483# symbols section.
484SPDK_LIBS=$(ls -1 $libdir/libspdk_*.so | grep -v libspdk_env_dpdk.so)
485DEP_LIBS=$(ls -1 $libdir/libspdk_*.so)
486
487IGNORED_LIBS=()
488if grep -q 'CONFIG_VHOST_INTERNAL_LIB?=n' $rootdir/mk/config.mk; then
489	IGNORED_LIBS+=("rte_vhost")
490fi
491
492(
493	for lib in $SPDK_LIBS; do confirm_deps $lib & done
494	wait
495)
496
497$MAKE $MAKEFLAGS clean
498git checkout "$rootdir/mk/spdk.lib.mk"
499
500if [ -f $fail_file ]; then
501	rm -f $fail_file
502	echo "shared object test failed"
503	exit 1
504fi
505
506xtrace_restore
507