xref: /freebsd/cddl/usr.sbin/dwatch/libexec/coredump (revision 434283fda99e89af20c3fe95fde427624ae96d04)
1*434283fdSDevin Teske# -*- tab-width: 4 -*- ;; Emacs
2*434283fdSDevin Teske# vi: set filetype=sh tabstop=8 shiftwidth=8 noexpandtab :: Vi/ViM
3*434283fdSDevin Teske############################################################ IDENT(1)
4*434283fdSDevin Teske#
5*434283fdSDevin Teske# $Title: dwatch(8) module for coredump-worthy signal delivery $
6*434283fdSDevin Teske# $Copyright: 2026 Devin Teske. All rights reserved. $
7*434283fdSDevin Teske#
8*434283fdSDevin Teske############################################################ DESCRIPTION
9*434283fdSDevin Teske#
10*434283fdSDevin Teske# Print signals whose default action produces a coredump, as they are
11*434283fdSDevin Teske# sent, together with a verdict of whether a core will actually be
12*434283fdSDevin Teske# written, rendered the same way and in the same order the kernel will
13*434283fdSDevin Teske# decide it: the target may be ignoring or catching the signal, dumps
14*434283fdSDevin Teske# may be disabled (kern.coredump), the target may be flagged sugid
15*434283fdSDevin Teske# (kern.sugid_coredump) or may have disabled tracing via procctl(2)
16*434283fdSDevin Teske# PROC_TRACE_CTL, or RLIMIT_CORE may be 0.
17*434283fdSDevin Teske# Coredump-worthy is defined by the SIGPROP_CORE entries of the
18*434283fdSDevin Teske# sigproptbl in kern_sig.c: SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGEMT,
19*434283fdSDevin Teske# SIGFPE, SIGBUS, SIGSEGV, and SIGSYS.
20*434283fdSDevin Teske# The coredump-top profile maintains a running catalog, updated
21*434283fdSDevin Teske# every 3 seconds, of coredump-worthy signals by process and signal.
22*434283fdSDevin Teske# Combine with `-O cmd' to capture state as each event occurs.
23*434283fdSDevin Teske#
24*434283fdSDevin Teske############################################################ PRAGMAS
25*434283fdSDevin Teske
26*434283fdSDevin Teskecase "$PROFILE" in
27*434283fdSDevin Teskecoredump-top)
28*434283fdSDevin Teske	DTRACE_PRAGMA="
29*434283fdSDevin Teske		option quiet
30*434283fdSDevin Teske		option aggsortrev
31*434283fdSDevin Teske	" # END-QUOTE
32*434283fdSDevin Teske	;;
33*434283fdSDevin Teskeesac
34*434283fdSDevin Teske
35*434283fdSDevin Teske############################################################ PROBE
36*434283fdSDevin Teske
37*434283fdSDevin Teskecase "$PROFILE" in
38*434283fdSDevin Teskecoredump-top)
39*434283fdSDevin Teske	: ${PROBE:=profile:::tick-3s} ;;
40*434283fdSDevin Teske*)
41*434283fdSDevin Teske	: ${PROBE:=proc:::signal-send}
42*434283fdSDevin Teskeesac
43*434283fdSDevin Teske
44*434283fdSDevin Teske############################################################ GLOBALS
45*434283fdSDevin Teske
46*434283fdSDevin Teskeexec 9<<EOF
47*434283fdSDevin Teske/*
48*434283fdSDevin Teske * Signals whose default action includes a coredump, per the
49*434283fdSDevin Teske * SIGPROP_CORE entries of the sigproptbl in kern_sig.c of FreeBSD
50*434283fdSDevin Teske */
51*434283fdSDevin Teskeinline int coredump_sig[int sig] =
52*434283fdSDevin Teske	sig == SIGQUIT ?	1 :
53*434283fdSDevin Teske	sig == SIGILL ?		1 :
54*434283fdSDevin Teske	sig == SIGTRAP ?	1 :
55*434283fdSDevin Teske	sig == SIGABRT ?	1 :	/* SIGIOT */
56*434283fdSDevin Teske	sig == SIGEMT ?		1 :
57*434283fdSDevin Teske	sig == SIGFPE ?		1 :
58*434283fdSDevin Teske	sig == SIGBUS ?		1 :
59*434283fdSDevin Teske	sig == SIGSEGV ?	1 :
60*434283fdSDevin Teske	sig == SIGSYS ?		1 :
61*434283fdSDevin Teske	0;
62*434283fdSDevin TeskeEOF
63*434283fdSDevin TeskeGLOBALS=$( cat <&9 )
64*434283fdSDevin Teske
65*434283fdSDevin Teske############################################################ EVENT ACTION
66*434283fdSDevin Teske
67*434283fdSDevin Teske[ "$CUSTOM_TEST" ] || case "$PROFILE" in
68*434283fdSDevin Teskecoredump-top)	;;
69*434283fdSDevin Teske*)		EVENT_TEST="coredump_sig[this->sig]"
70*434283fdSDevin Teskeesac
71*434283fdSDevin Teske
72*434283fdSDevin Teske############################################################ ACTIONS
73*434283fdSDevin Teske
74*434283fdSDevin Teskeif [ "$PROFILE" = "coredump-top" ]; then
75*434283fdSDevin Teskeexec 9<<EOF
76*434283fdSDevin Teske$GLOBALS
77*434283fdSDevin Teske
78*434283fdSDevin TeskeBEGIN { printf("Cataloging coredump-worthy signals ...") } /* probe ID $ID */
79*434283fdSDevin Teske
80*434283fdSDevin Teskeproc:::signal-send /coredump_sig[(int)arg2]/ /* probe ID $(( $ID + 1 )) */
81*434283fdSDevin Teske{
82*434283fdSDevin Teske	@cores[stringof(((struct proc *)args[1])->p_comm),
83*434283fdSDevin Teske		signal_string[(int)arg2]] = count();
84*434283fdSDevin Teske}
85*434283fdSDevin TeskeEOF
86*434283fdSDevin TeskeACTIONS=$( cat <&9 )
87*434283fdSDevin TeskeID=$(( $ID + 2 ))
88*434283fdSDevin Teskeelse
89*434283fdSDevin Teskeexec 9<<EOF
90*434283fdSDevin Teske$GLOBALS
91*434283fdSDevin Teske
92*434283fdSDevin Teskethis int		sig;
93*434283fdSDevin Teskethis pid_t		pid;
94*434283fdSDevin Teskethis string		verdict;
95*434283fdSDevin Teskethis struct proc *	target;
96*434283fdSDevin Teskethis u_int		core_catch;
97*434283fdSDevin Teskethis u_int		core_ign;
98*434283fdSDevin Teskethis u_int		core_notrace;
99*434283fdSDevin Teskethis u_int		core_sugid;
100*434283fdSDevin Teskethis uint64_t		core_limit;
101*434283fdSDevin Teske
102*434283fdSDevin Teske$PROBE /* probe ID $ID */
103*434283fdSDevin Teske{${TRACE:+
104*434283fdSDevin Teske	printf("<$ID>");
105*434283fdSDevin Teske}
106*434283fdSDevin Teske	this->target = (struct proc *)args[1];
107*434283fdSDevin Teske	this->pid = (pid_t)this->target->p_pid;
108*434283fdSDevin Teske	this->sig = (int)arg2;
109*434283fdSDevin Teske}
110*434283fdSDevin Teske
111*434283fdSDevin Teske$PROBE /* probe ID $(( $ID + 1 )) */
112*434283fdSDevin Teske{${TRACE:+
113*434283fdSDevin Teske	printf("<$(( $ID + 1 ))>");
114*434283fdSDevin Teske}
115*434283fdSDevin Teske	/*
116*434283fdSDevin Teske	 * Render a verdict the way the kernel will: disposition first
117*434283fdSDevin Teske	 * (struct sigacts), then the coredump() gauntlet in its order:
118*434283fdSDevin Teske	 * kern.coredump, kern.sugid_coredump vs P_SUGID, procctl(2)
119*434283fdSDevin Teske	 * PROC_TRACE_CTL (P2_NOTRACE), and finally RLIMIT_CORE
120*434283fdSDevin Teske	 */
121*434283fdSDevin Teske	this->core_ign =
122*434283fdSDevin Teske		this->target->p_sigacts->ps_sigignore.__bits[
123*434283fdSDevin Teske			(this->sig - 1) >> 5] &
124*434283fdSDevin Teske		(1 << ((this->sig - 1) & 31));
125*434283fdSDevin Teske	this->core_catch =
126*434283fdSDevin Teske		this->target->p_sigacts->ps_sigcatch.__bits[
127*434283fdSDevin Teske			(this->sig - 1) >> 5] &
128*434283fdSDevin Teske		(1 << ((this->sig - 1) & 31));
129*434283fdSDevin Teske	this->core_sugid =
130*434283fdSDevin Teske		this->target->p_flag & 0x00000100; /* P_SUGID */
131*434283fdSDevin Teske	this->core_notrace =
132*434283fdSDevin Teske		this->target->p_flag2 & 0x00000002; /* P2_NOTRACE */
133*434283fdSDevin Teske	this->core_limit = this->target->p_limit->
134*434283fdSDevin Teske		pl_rlimit[4].rlim_cur; /* RLIMIT_CORE */
135*434283fdSDevin Teske
136*434283fdSDevin Teske	this->verdict =
137*434283fdSDevin Teske		this->core_ign ?	"ignored" :
138*434283fdSDevin Teske		this->core_catch ?	"caught" :
139*434283fdSDevin Teske		\`do_coredump == 0 ?	"denied by kern.coredump" :
140*434283fdSDevin Teske		this->core_sugid && \`sugid_coredump == 0 ?
141*434283fdSDevin Teske			"denied by kern.sugid_coredump" :
142*434283fdSDevin Teske		this->core_notrace ?	"denied by procctl trace ctl" :
143*434283fdSDevin Teske		this->core_limit == 0 ?	"denied by RLIMIT_CORE" :
144*434283fdSDevin Teske		"will dump core";
145*434283fdSDevin Teske
146*434283fdSDevin Teske	$( pproc -P _core "(struct proc *)args[1]" )
147*434283fdSDevin Teske}
148*434283fdSDevin TeskeEOF
149*434283fdSDevin TeskeACTIONS=$( cat <&9 )
150*434283fdSDevin TeskeID=$(( $ID + 2 ))
151*434283fdSDevin Teskefi
152*434283fdSDevin Teske
153*434283fdSDevin Teske############################################################ EVENT TAG
154*434283fdSDevin Teske
155*434283fdSDevin Teske# For the running catalog, override the default `UID.GID CMD[PID]: ' tag
156*434283fdSDevin Teske# with ANSI cursor-homing and screen-clearing codes plus column headers.
157*434283fdSDevin Teske
158*434283fdSDevin Teskeif [ "$PROFILE" = "coredump-top" ]; then
159*434283fdSDevin Teskesize=$( stty size 2> /dev/null )
160*434283fdSDevin Teskerows="${size%% *}"
161*434283fdSDevin Teskecols="${size#* }"
162*434283fdSDevin Teske
163*434283fdSDevin Teskeexec 9<<EOF
164*434283fdSDevin Teske	printf("\033[H"); /* Position the cursor at top-left */
165*434283fdSDevin Teske	printf("\033[J"); /* Clear display from cursor to end */
166*434283fdSDevin Teske
167*434283fdSDevin Teske	/* Header line containing probe (left) and date (right) */
168*434283fdSDevin Teske	printf("%-*s%s%Y%s\n",
169*434283fdSDevin Teske		$(( ${cols:-80} - 20 )), "$PROBE",
170*434283fdSDevin Teske		console ? "\033[32m" : "",
171*434283fdSDevin Teske		walltimestamp,
172*434283fdSDevin Teske		console ? "\033[39m" : "");
173*434283fdSDevin Teske
174*434283fdSDevin Teske	/* Column headers */
175*434283fdSDevin Teske	printf("%s%8s %-20s %s%s\n",
176*434283fdSDevin Teske		console ? "\033[1m" : "",
177*434283fdSDevin Teske		"COUNT",
178*434283fdSDevin Teske		"EXECNAME",
179*434283fdSDevin Teske		"SIGNAL",
180*434283fdSDevin Teske		console ? "\033[22m" : "");
181*434283fdSDevin TeskeEOF
182*434283fdSDevin TeskeEVENT_TAG=$( cat <&9 )
183*434283fdSDevin Teskefi
184*434283fdSDevin Teske
185*434283fdSDevin Teske############################################################ EVENT DETAILS
186*434283fdSDevin Teske
187*434283fdSDevin Teskeif [ "$PROFILE" = "coredump-top" ]; then
188*434283fdSDevin Teskeexec 9<<EOF
189*434283fdSDevin Teske	/* NB: Cumulative; not truncated between updates */
190*434283fdSDevin Teske	printa("%@8u %-20s %s\n", @cores);
191*434283fdSDevin TeskeEOF
192*434283fdSDevin TeskeEVENT_DETAILS=$( cat <&9 )
193*434283fdSDevin Teskeelif [ ! "$CUSTOM_DETAILS" ]; then
194*434283fdSDevin Teskeexec 9<<EOF
195*434283fdSDevin Teske	/*
196*434283fdSDevin Teske	 * Print coredump-worthy signal details
197*434283fdSDevin Teske	 */
198*434283fdSDevin Teske	printf("%s[%d] pid %d (%s) -- %s",
199*434283fdSDevin Teske		signal_string[this->sig],
200*434283fdSDevin Teske		this->sig,
201*434283fdSDevin Teske		this->pid,
202*434283fdSDevin Teske		this->verdict,
203*434283fdSDevin Teske		this->args_core);
204*434283fdSDevin TeskeEOF
205*434283fdSDevin TeskeEVENT_DETAILS=$( cat <&9 )
206*434283fdSDevin Teskefi
207*434283fdSDevin Teske
208*434283fdSDevin Teske################################################################################
209*434283fdSDevin Teske# END
210*434283fdSDevin Teske################################################################################
211