xref: /freebsd/usr.bin/truss/tests/truss_test.sh (revision af25f24762307bf606690c0aca6a3205a0515914)
1*af25f247SDevin Teske#
2*af25f247SDevin Teske# SPDX-License-Identifier: BSD-2-Clause
3*af25f247SDevin Teske#
4*af25f247SDevin Teske# Copyright (c) 2026 Devin Teske <dteske@FreeBSD.org>
5*af25f247SDevin Teske#
6*af25f247SDevin Teske
7*af25f247SDevin Teske# The system calls a program makes vary with the machine and with the
8*af25f247SDevin Teske# run-time linker, so these tests assert which system calls -t may and
9*af25f247SDevin Teske# may not report rather than the exact sequence of them.
10*af25f247SDevin Teske
11*af25f247SDevin Teskerequire_truss()
12*af25f247SDevin Teske{
13*af25f247SDevin Teske	truss -o /dev/null /usr/bin/true >/dev/null 2>&1 ||
14*af25f247SDevin Teske	    atf_skip "unable to trace a child process here"
15*af25f247SDevin Teske}
16*af25f247SDevin Teske
17*af25f247SDevin Teske# Write the sorted, unique names of the system calls reported in a truss
18*af25f247SDevin Teske# output file to another file.
19*af25f247SDevin Teskesyscall_names()
20*af25f247SDevin Teske{
21*af25f247SDevin Teske	sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_.]*\)(.*$/\1/p' "$1" | sort -u > "$2"
22*af25f247SDevin Teske}
23*af25f247SDevin Teske
24*af25f247SDevin Teske# Fail unless every name in a file matches an extended regular expression.
25*af25f247SDevin Teskeonly_names()
26*af25f247SDevin Teske{
27*af25f247SDevin Teske	if grep -Ev "$1" "$2" > unexpected; then
28*af25f247SDevin Teske		atf_fail "reported system calls not selected by the filter:" \
29*af25f247SDevin Teske		    "$(tr '\n' ' ' < unexpected)"
30*af25f247SDevin Teske	fi
31*af25f247SDevin Teske}
32*af25f247SDevin Teske
33*af25f247SDevin Teske# Fail if any name in a file matches an extended regular expression.
34*af25f247SDevin Teskeno_name()
35*af25f247SDevin Teske{
36*af25f247SDevin Teske	if grep -E "$1" "$2" > unexpected; then
37*af25f247SDevin Teske		atf_fail "system calls excluded by the filter were reported:" \
38*af25f247SDevin Teske		    "$(tr '\n' ' ' < unexpected)"
39*af25f247SDevin Teske	fi
40*af25f247SDevin Teske}
41*af25f247SDevin Teske
42*af25f247SDevin Teske# Fail unless at least one name in a file matches.
43*af25f247SDevin Teskesome_name()
44*af25f247SDevin Teske{
45*af25f247SDevin Teske	grep -Eq "$1" "$2" ||
46*af25f247SDevin Teske	    atf_fail "no system call matching $1 was reported"
47*af25f247SDevin Teske}
48*af25f247SDevin Teske
49*af25f247SDevin Teskeatf_test_case list
50*af25f247SDevin Teskelist_head()
51*af25f247SDevin Teske{
52*af25f247SDevin Teske	atf_set descr "-t with no expression prints the available groups"
53*af25f247SDevin Teske}
54*af25f247SDevin Teskelist_body()
55*af25f247SDevin Teske{
56*af25f247SDevin Teske	atf_check -s exit:2 -o match:'@all' -o match:'@none' \
57*af25f247SDevin Teske	    -o match:'@read' -o match:'@write' -o match:'@file' \
58*af25f247SDevin Teske	    -o match:'@net' \
59*af25f247SDevin Teske	    truss -t
60*af25f247SDevin Teske}
61*af25f247SDevin Teske
62*af25f247SDevin Teskeatf_test_case unknown_group
63*af25f247SDevin Teskeunknown_group_head()
64*af25f247SDevin Teske{
65*af25f247SDevin Teske	atf_set descr "an unknown @group is rejected"
66*af25f247SDevin Teske}
67*af25f247SDevin Teskeunknown_group_body()
68*af25f247SDevin Teske{
69*af25f247SDevin Teske	atf_check -s exit:1 -e match:'unknown system call group @nosuch' \
70*af25f247SDevin Teske	    truss -t @nosuch /usr/bin/true
71*af25f247SDevin Teske}
72*af25f247SDevin Teske
73*af25f247SDevin Teskeatf_test_case empty_term
74*af25f247SDevin Teskeempty_term_head()
75*af25f247SDevin Teske{
76*af25f247SDevin Teske	atf_set descr "an empty term adds nothing to the expression"
77*af25f247SDevin Teske}
78*af25f247SDevin Teskeempty_term_body()
79*af25f247SDevin Teske{
80*af25f247SDevin Teske	require_truss
81*af25f247SDevin Teske	printf 'hello\n' > input
82*af25f247SDevin Teske
83*af25f247SDevin Teske	# An empty expression filters nothing, as if -t were absent.
84*af25f247SDevin Teske	atf_check -s exit:0 -o inline:"hello\n" truss -o out -t '' cat input
85*af25f247SDevin Teske	syscall_names out names
86*af25f247SDevin Teske	some_name '^read$' names
87*af25f247SDevin Teske	some_name '^openat$' names
88*af25f247SDevin Teske
89*af25f247SDevin Teske	# A stray comma is ignored rather than being an error.
90*af25f247SDevin Teske	atf_check -s exit:0 -o inline:"hello\n" \
91*af25f247SDevin Teske	    truss -o out2 -t ',read,,write,' cat input
92*af25f247SDevin Teske	syscall_names out2 names2
93*af25f247SDevin Teske	only_names '^(read|write)$' names2
94*af25f247SDevin Teske	some_name '^read$' names2
95*af25f247SDevin Teske
96*af25f247SDevin Teske	# A negation with nothing to negate is still a mistake.
97*af25f247SDevin Teske	atf_check -s exit:1 -e match:"missing pattern after" \
98*af25f247SDevin Teske	    truss -t '!' /usr/bin/true
99*af25f247SDevin Teske}
100*af25f247SDevin Teske
101*af25f247SDevin Teskeatf_test_case none
102*af25f247SDevin Teskenone_head()
103*af25f247SDevin Teske{
104*af25f247SDevin Teske	atf_set descr "@none selects no system call"
105*af25f247SDevin Teske}
106*af25f247SDevin Teskenone_body()
107*af25f247SDevin Teske{
108*af25f247SDevin Teske	require_truss
109*af25f247SDevin Teske	printf 'hello\n' > input
110*af25f247SDevin Teske
111*af25f247SDevin Teske	# @none's member list is the single negated member "!*", so this
112*af25f247SDevin Teske	# also covers a group whose members exclude rather than include.
113*af25f247SDevin Teske	atf_check -s exit:0 -o inline:"hello\n" truss -o out -t @none cat input
114*af25f247SDevin Teske	syscall_names out names
115*af25f247SDevin Teske	atf_check -o empty cat names
116*af25f247SDevin Teske
117*af25f247SDevin Teske	atf_check -s exit:0 -o inline:"hello\n" \
118*af25f247SDevin Teske	    truss -o out2 -t '!@none' cat input
119*af25f247SDevin Teske	syscall_names out2 names2
120*af25f247SDevin Teske	some_name '^read$' names2
121*af25f247SDevin Teske	some_name '^openat$' names2
122*af25f247SDevin Teske
123*af25f247SDevin Teske	# @none is the empty set rather than a switch: it selects nothing
124*af25f247SDevin Teske	# and leaves the terms before it alone.
125*af25f247SDevin Teske	atf_check -s exit:0 -o inline:"hello\n" \
126*af25f247SDevin Teske	    truss -o out3 -t '@file,@none' cat input
127*af25f247SDevin Teske	syscall_names out3 names3
128*af25f247SDevin Teske	some_name '^openat$' names3
129*af25f247SDevin Teske}
130*af25f247SDevin Teske
131*af25f247SDevin Teskeatf_test_case by_number
132*af25f247SDevin Teskeby_number_head()
133*af25f247SDevin Teske{
134*af25f247SDevin Teske	atf_set descr "a term may name a system call by number"
135*af25f247SDevin Teske}
136*af25f247SDevin Teskeby_number_body()
137*af25f247SDevin Teske{
138*af25f247SDevin Teske	require_truss
139*af25f247SDevin Teske	printf 'hello\n' > input
140*af25f247SDevin Teske
141*af25f247SDevin Teske	# 3 and 4 have been read(2) and write(2) since 4.2BSD.
142*af25f247SDevin Teske	atf_check -s exit:0 -o inline:"hello\n" -e empty \
143*af25f247SDevin Teske	    truss -o out -t 3 cat input
144*af25f247SDevin Teske	syscall_names out names
145*af25f247SDevin Teske	only_names '^read$' names
146*af25f247SDevin Teske	some_name '^read$' names
147*af25f247SDevin Teske
148*af25f247SDevin Teske	atf_check -s exit:0 -o inline:"hello\n" -e empty \
149*af25f247SDevin Teske	    truss -o out2 -t 3,4 cat input
150*af25f247SDevin Teske	syscall_names out2 names2
151*af25f247SDevin Teske	only_names '^(read|write)$' names2
152*af25f247SDevin Teske
153*af25f247SDevin Teske	# Numeric terms negate like any other.
154*af25f247SDevin Teske	atf_check -s exit:0 -o inline:"hello\n" -e empty \
155*af25f247SDevin Teske	    truss -o out3 -t '!3' cat input
156*af25f247SDevin Teske	syscall_names out3 names3
157*af25f247SDevin Teske	no_name '^read$' names3
158*af25f247SDevin Teske	some_name '.' names3
159*af25f247SDevin Teske
160*af25f247SDevin Teske	# Leading zeroes are still just a number.
161*af25f247SDevin Teske	atf_check -s exit:0 -o inline:"hello\n" -e empty \
162*af25f247SDevin Teske	    truss -o out4 -t 003 cat input
163*af25f247SDevin Teske	syscall_names out4 names4
164*af25f247SDevin Teske	only_names '^read$' names4
165*af25f247SDevin Teske}
166*af25f247SDevin Teske
167*af25f247SDevin Teskeatf_test_case bad_number
168*af25f247SDevin Teskebad_number_head()
169*af25f247SDevin Teske{
170*af25f247SDevin Teske	atf_set descr "only an unrepresentable system call number is rejected"
171*af25f247SDevin Teske}
172*af25f247SDevin Teskebad_number_body()
173*af25f247SDevin Teske{
174*af25f247SDevin Teske	require_truss
175*af25f247SDevin Teske	printf 'hello\n' > input
176*af25f247SDevin Teske
177*af25f247SDevin Teske	# A process may issue any number the kernel can hold, so a number
178*af25f247SDevin Teske	# beyond the tables truss knows is not second-guessed.  It simply
179*af25f247SDevin Teske	# does not match anything this program happens to call.
180*af25f247SDevin Teske	atf_check -s exit:0 -o inline:"hello\n" -e empty \
181*af25f247SDevin Teske	    truss -o out -t 99999 cat input
182*af25f247SDevin Teske	syscall_names out names
183*af25f247SDevin Teske	atf_check -o empty cat names
184*af25f247SDevin Teske
185*af25f247SDevin Teske	# Too large to be a system call number at all: an error.
186*af25f247SDevin Teske	atf_check -s exit:1 -e match:'system call number is too large' \
187*af25f247SDevin Teske	    truss -t 4294967296 /usr/bin/true
188*af25f247SDevin Teske}
189*af25f247SDevin Teske
190*af25f247SDevin Teskeatf_test_case unknown_syscall
191*af25f247SDevin Teskeunknown_syscall_head()
192*af25f247SDevin Teske{
193*af25f247SDevin Teske	atf_set descr "a term matching no system call warns but still runs"
194*af25f247SDevin Teske}
195*af25f247SDevin Teskeunknown_syscall_body()
196*af25f247SDevin Teske{
197*af25f247SDevin Teske	require_truss
198*af25f247SDevin Teske	printf 'hello\n' > input
199*af25f247SDevin Teske
200*af25f247SDevin Teske	# A typo is a warning, not an error: the command still runs.
201*af25f247SDevin Teske	atf_check -s exit:0 -o inline:"hello\n" \
202*af25f247SDevin Teske	    -e match:'opne: matches no known system call' \
203*af25f247SDevin Teske	    truss -o out -t opne cat input
204*af25f247SDevin Teske	syscall_names out names
205*af25f247SDevin Teske	atf_check -o empty cat names
206*af25f247SDevin Teske
207*af25f247SDevin Teske	# So is a pattern that can never match.
208*af25f247SDevin Teske	atf_check -s exit:0 -o inline:"hello\n" \
209*af25f247SDevin Teske	    -e match:'matches no known system call' \
210*af25f247SDevin Teske	    truss -o out2 -t 'raed*' cat input
211*af25f247SDevin Teske
212*af25f247SDevin Teske	# Names of every ABI truss knows are accepted without complaint,
213*af25f247SDevin Teske	# whether or not that ABI is the one being traced here.
214*af25f247SDevin Teske	for name in read openat linux_write linux_newstat compat11.stat; do
215*af25f247SDevin Teske		atf_check -s exit:0 -o inline:"hello\n" -e empty \
216*af25f247SDevin Teske		    truss -o out3 -t "$name" cat input
217*af25f247SDevin Teske	done
218*af25f247SDevin Teske
219*af25f247SDevin Teske	# A number is the way to name a system call by number; '#' is not
220*af25f247SDevin Teske	# a prefix truss accepts, so it is diagnosed like any other typo.
221*af25f247SDevin Teske	atf_check -s exit:0 -o inline:"hello\n" \
222*af25f247SDevin Teske	    -e match:'matches no known system call' \
223*af25f247SDevin Teske	    truss -o out4 -t '#237' cat input
224*af25f247SDevin Teske}
225*af25f247SDevin Teske
226*af25f247SDevin Teskeatf_test_case by_name
227*af25f247SDevin Teskeby_name_head()
228*af25f247SDevin Teske{
229*af25f247SDevin Teske	atf_set descr "a term naming one system call selects only that one"
230*af25f247SDevin Teske}
231*af25f247SDevin Teskeby_name_body()
232*af25f247SDevin Teske{
233*af25f247SDevin Teske	require_truss
234*af25f247SDevin Teske	printf 'hello\n' > input
235*af25f247SDevin Teske
236*af25f247SDevin Teske	atf_check -s exit:0 -o inline:"hello\n" truss -o out -t read cat input
237*af25f247SDevin Teske	syscall_names out names
238*af25f247SDevin Teske	only_names '^read$' names
239*af25f247SDevin Teske	some_name '^read$' names
240*af25f247SDevin Teske}
241*af25f247SDevin Teske
242*af25f247SDevin Teskeatf_test_case by_pattern
243*af25f247SDevin Teskeby_pattern_head()
244*af25f247SDevin Teske{
245*af25f247SDevin Teske	atf_set descr "a term may be an fnmatch(3) pattern"
246*af25f247SDevin Teske}
247*af25f247SDevin Teskeby_pattern_body()
248*af25f247SDevin Teske{
249*af25f247SDevin Teske	require_truss
250*af25f247SDevin Teske	atf_check ln -s target link
251*af25f247SDevin Teske
252*af25f247SDevin Teske	atf_check -s exit:0 -o ignore truss -o out -t 'readlink*' readlink link
253*af25f247SDevin Teske	syscall_names out names
254*af25f247SDevin Teske	only_names '^readlink' names
255*af25f247SDevin Teske	some_name '^readlink' names
256*af25f247SDevin Teske}
257*af25f247SDevin Teske
258*af25f247SDevin Teskeatf_test_case group
259*af25f247SDevin Teskegroup_head()
260*af25f247SDevin Teske{
261*af25f247SDevin Teske	atf_set descr "an @group selects the system calls it names"
262*af25f247SDevin Teske}
263*af25f247SDevin Teskegroup_body()
264*af25f247SDevin Teske{
265*af25f247SDevin Teske	require_truss
266*af25f247SDevin Teske	printf 'hello\n' > input
267*af25f247SDevin Teske
268*af25f247SDevin Teske	atf_check -s exit:0 -o inline:"hello\n" truss -o out -t @read cat input
269*af25f247SDevin Teske	syscall_names out names
270*af25f247SDevin Teske	some_name '^read$' names
271*af25f247SDevin Teske	no_name '^(openat|close|mmap|munmap|mprotect)$' names
272*af25f247SDevin Teske}
273*af25f247SDevin Teske
274*af25f247SDevin Teskeatf_test_case group_read_excludes_readlink
275*af25f247SDevin Teskegroup_read_excludes_readlink_head()
276*af25f247SDevin Teske{
277*af25f247SDevin Teske	atf_set descr "@read selects I/O reads but not readlink(2)"
278*af25f247SDevin Teske}
279*af25f247SDevin Teskegroup_read_excludes_readlink_body()
280*af25f247SDevin Teske{
281*af25f247SDevin Teske	require_truss
282*af25f247SDevin Teske	atf_check ln -s target link
283*af25f247SDevin Teske
284*af25f247SDevin Teske	# readlink(1) calls readlink(2), which @read must not select even
285*af25f247SDevin Teske	# though "read*" does.
286*af25f247SDevin Teske	atf_check -s exit:0 -o ignore truss -o out -t @read readlink link
287*af25f247SDevin Teske	syscall_names out names
288*af25f247SDevin Teske	no_name '^readlink' names
289*af25f247SDevin Teske	some_name '^read$' names
290*af25f247SDevin Teske
291*af25f247SDevin Teske	atf_check -s exit:0 -o ignore truss -o out2 -t 'read*' readlink link
292*af25f247SDevin Teske	syscall_names out2 names2
293*af25f247SDevin Teske	some_name '^readlink' names2
294*af25f247SDevin Teske}
295*af25f247SDevin Teske
296*af25f247SDevin Teskeatf_test_case group_reference
297*af25f247SDevin Teskegroup_reference_head()
298*af25f247SDevin Teske{
299*af25f247SDevin Teske	atf_set descr "@desc includes the members of @read and @write"
300*af25f247SDevin Teske}
301*af25f247SDevin Teskegroup_reference_body()
302*af25f247SDevin Teske{
303*af25f247SDevin Teske	require_truss
304*af25f247SDevin Teske	printf 'hello\n' > input
305*af25f247SDevin Teske
306*af25f247SDevin Teske	atf_check -s exit:0 -o inline:"hello\n" truss -o out -t @desc cat input
307*af25f247SDevin Teske	syscall_names out names
308*af25f247SDevin Teske	some_name '^read$' names
309*af25f247SDevin Teske	some_name '^close$' names
310*af25f247SDevin Teske}
311*af25f247SDevin Teske
312*af25f247SDevin Teskeatf_test_case negation
313*af25f247SDevin Teskenegation_head()
314*af25f247SDevin Teske{
315*af25f247SDevin Teske	atf_set descr "a term prefixed with ! excludes what it matches"
316*af25f247SDevin Teske}
317*af25f247SDevin Teskenegation_body()
318*af25f247SDevin Teske{
319*af25f247SDevin Teske	require_truss
320*af25f247SDevin Teske
321*af25f247SDevin Teske	atf_check -s exit:0 -o ignore truss -o out -t '!@all' /usr/bin/true
322*af25f247SDevin Teske	syscall_names out names
323*af25f247SDevin Teske	atf_check -o empty cat names
324*af25f247SDevin Teske
325*af25f247SDevin Teske	atf_check -s exit:0 -o ignore truss -o out2 -t '!@memory' /usr/bin/true
326*af25f247SDevin Teske	syscall_names out2 names2
327*af25f247SDevin Teske	no_name '^(mmap|munmap|mprotect)$' names2
328*af25f247SDevin Teske	some_name '.' names2
329*af25f247SDevin Teske}
330*af25f247SDevin Teske
331*af25f247SDevin Teskeatf_test_case order
332*af25f247SDevin Teskeorder_head()
333*af25f247SDevin Teske{
334*af25f247SDevin Teske	atf_set descr "the last term to match a system call wins"
335*af25f247SDevin Teske}
336*af25f247SDevin Teskeorder_body()
337*af25f247SDevin Teske{
338*af25f247SDevin Teske	require_truss
339*af25f247SDevin Teske	printf 'hello\n' > input
340*af25f247SDevin Teske
341*af25f247SDevin Teske	atf_check -s exit:0 -o ignore truss -o out -t 'read,!read' cat input
342*af25f247SDevin Teske	syscall_names out names
343*af25f247SDevin Teske	atf_check -o empty cat names
344*af25f247SDevin Teske
345*af25f247SDevin Teske	atf_check -s exit:0 -o ignore truss -o out2 -t '!read,read' cat input
346*af25f247SDevin Teske	syscall_names out2 names2
347*af25f247SDevin Teske	only_names '^read$' names2
348*af25f247SDevin Teske	some_name '^read$' names2
349*af25f247SDevin Teske}
350*af25f247SDevin Teske
351*af25f247SDevin Teskeatf_test_case accumulate
352*af25f247SDevin Teskeaccumulate_head()
353*af25f247SDevin Teske{
354*af25f247SDevin Teske	atf_set descr "repeating -t appends to the expression"
355*af25f247SDevin Teske}
356*af25f247SDevin Teskeaccumulate_body()
357*af25f247SDevin Teske{
358*af25f247SDevin Teske	require_truss
359*af25f247SDevin Teske	atf_check ln -s target link
360*af25f247SDevin Teske
361*af25f247SDevin Teske	atf_check -s exit:0 -o ignore \
362*af25f247SDevin Teske	    truss -o out -t read -t readlink readlink link
363*af25f247SDevin Teske	syscall_names out names
364*af25f247SDevin Teske	only_names '^(read|readlink)$' names
365*af25f247SDevin Teske	some_name '^read$' names
366*af25f247SDevin Teske	some_name '^readlink$' names
367*af25f247SDevin Teske}
368*af25f247SDevin Teske
369*af25f247SDevin Teskeatf_test_case count
370*af25f247SDevin Teskecount_head()
371*af25f247SDevin Teske{
372*af25f247SDevin Teske	atf_set descr "-c counts only the selected system calls"
373*af25f247SDevin Teske}
374*af25f247SDevin Teskecount_body()
375*af25f247SDevin Teske{
376*af25f247SDevin Teske	require_truss
377*af25f247SDevin Teske	printf 'hello\n' > input
378*af25f247SDevin Teske
379*af25f247SDevin Teske	atf_check -s exit:0 -o inline:"hello\n" \
380*af25f247SDevin Teske	    truss -c -o out -t read cat input
381*af25f247SDevin Teske	atf_check -o match:'^read ' cat out
382*af25f247SDevin Teske	atf_check -s exit:1 -o empty grep -q '^openat' out
383*af25f247SDevin Teske}
384*af25f247SDevin Teske
385*af25f247SDevin Teskeatf_init_test_cases()
386*af25f247SDevin Teske{
387*af25f247SDevin Teske	atf_add_test_case list
388*af25f247SDevin Teske	atf_add_test_case unknown_group
389*af25f247SDevin Teske	atf_add_test_case empty_term
390*af25f247SDevin Teske	atf_add_test_case none
391*af25f247SDevin Teske	atf_add_test_case by_number
392*af25f247SDevin Teske	atf_add_test_case bad_number
393*af25f247SDevin Teske	atf_add_test_case unknown_syscall
394*af25f247SDevin Teske	atf_add_test_case by_name
395*af25f247SDevin Teske	atf_add_test_case by_pattern
396*af25f247SDevin Teske	atf_add_test_case group
397*af25f247SDevin Teske	atf_add_test_case group_read_excludes_readlink
398*af25f247SDevin Teske	atf_add_test_case group_reference
399*af25f247SDevin Teske	atf_add_test_case negation
400*af25f247SDevin Teske	atf_add_test_case order
401*af25f247SDevin Teske	atf_add_test_case accumulate
402*af25f247SDevin Teske	atf_add_test_case count
403*af25f247SDevin Teske}
404