xref: /freebsd/usr.bin/procstat/procstat_kstack.c (revision 63a938566d524836885917d95bd491aa4400b181)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2007 Robert N. M. Watson
5  * Copyright (c) 2015 Allan Jude <allanjude@freebsd.org>
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  *
29  * $FreeBSD$
30  */
31 
32 #include <sys/param.h>
33 #include <sys/sysctl.h>
34 #include <sys/user.h>
35 
36 #include <err.h>
37 #include <errno.h>
38 #include <libprocstat.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 
43 #include "procstat.h"
44 
45 /*
46  * Walk the stack trace provided by the kernel and reduce it to what we
47  * actually want to print.  This involves stripping true instruction pointers,
48  * frame numbers, and carriage returns as generated by stack(9).  If -kk is
49  * specified, print the function and offset, otherwise just the function.
50  */
51 enum trace_state { TS_FRAMENUM, TS_PC, TS_AT, TS_FUNC, TS_OFF };
52 
53 static enum trace_state
54 kstack_nextstate(enum trace_state ts)
55 {
56 
57 	switch (ts) {
58 	case TS_FRAMENUM:
59 		return (TS_PC);
60 
61 	case TS_PC:
62 		return (TS_AT);
63 
64 	case TS_AT:
65 		return (TS_FUNC);
66 
67 	case TS_FUNC:
68 		return (TS_OFF);
69 
70 	case TS_OFF:
71 		return TS_FRAMENUM;
72 
73 	default:
74 		errx(-1, "kstack_nextstate");
75 	}
76 }
77 
78 static void
79 kstack_cleanup(const char *old, char *new, int kflag)
80 {
81 	enum trace_state old_ts, ts;
82 	const char *cp_old;
83 	char *cp_new;
84 
85 	ts = TS_FRAMENUM;
86 	for (cp_old = old, cp_new = new; *cp_old != '\0'; cp_old++) {
87 		switch (*cp_old) {
88 		case ' ':
89 		case '\n':
90 		case '+':
91 			old_ts = ts;
92 			ts = kstack_nextstate(old_ts);
93 			if (old_ts == TS_OFF) {
94 				*cp_new = ' ';
95 				cp_new++;
96 			}
97 			if (kflag > 1 && old_ts == TS_FUNC) {
98 				*cp_new = '+';
99 				cp_new++;
100 			}
101 			continue;
102 		}
103 		if (ts == TS_FUNC || (kflag > 1 && ts == TS_OFF)) {
104 			*cp_new = *cp_old;
105 			cp_new++;
106 		}
107 	}
108 	*cp_new = '\0';
109 }
110 
111 static void
112 kstack_cleanup_encoded(const char *old, char *new, int kflag)
113 {
114 	enum trace_state old_ts, ts;
115 	const char *cp_old;
116 	char *cp_new, *cp_loop, *cp_tofree, *cp_line;
117 
118 	ts = TS_FRAMENUM;
119 	if (kflag == 1) {
120 		for (cp_old = old, cp_new = new; *cp_old != '\0'; cp_old++) {
121 			switch (*cp_old) {
122 			case '\n':
123 				*cp_new = *cp_old;
124 				cp_new++;
125 			case ' ':
126 			case '+':
127 				old_ts = ts;
128 				ts = kstack_nextstate(old_ts);
129 				continue;
130 			}
131 			if (ts == TS_FUNC) {
132 				*cp_new = *cp_old;
133 				cp_new++;
134 			}
135 		}
136 		*cp_new = '\0';
137 		cp_tofree = cp_loop = strdup(new);
138 	} else
139 		cp_tofree = cp_loop = strdup(old);
140         while ((cp_line = strsep(&cp_loop, "\n")) != NULL) {
141 		if (strlen(cp_line) != 0 && *cp_line != 127)
142 			xo_emit("{le:token/%s}", cp_line);
143 	}
144 	free(cp_tofree);
145 }
146 
147 /*
148  * Sort threads by tid.
149  */
150 static int
151 kinfo_kstack_compare(const void *a, const void *b)
152 {
153 
154         return ((const struct kinfo_kstack *)a)->kkst_tid -
155             ((const struct kinfo_kstack *)b)->kkst_tid;
156 }
157 
158 static void
159 kinfo_kstack_sort(struct kinfo_kstack *kkstp, int count)
160 {
161 
162         qsort(kkstp, count, sizeof(*kkstp), kinfo_kstack_compare);
163 }
164 
165 
166 void
167 procstat_kstack(struct procstat *procstat, struct kinfo_proc *kipp)
168 {
169 	struct kinfo_kstack *kkstp, *kkstp_free;
170 	struct kinfo_proc *kip, *kip_free;
171 	char trace[KKST_MAXLEN], encoded_trace[KKST_MAXLEN];
172 	unsigned int i, j;
173 	unsigned int kip_count, kstk_count;
174 
175 	if ((procstat_opts & PS_OPT_NOHEADER) == 0)
176 		xo_emit("{T:/%5s %6s %-19s %-19s %-29s}\n", "PID", "TID", "COMM",
177 		    "TDNAME", "KSTACK");
178 
179 	kkstp = kkstp_free = procstat_getkstack(procstat, kipp, &kstk_count);
180 	if (kkstp == NULL)
181 		return;
182 
183 	/*
184 	 * We need to re-query for thread information, so don't use *kipp.
185 	 */
186 	kip = kip_free = procstat_getprocs(procstat,
187 	    KERN_PROC_PID | KERN_PROC_INC_THREAD, kipp->ki_pid, &kip_count);
188 
189 	if (kip == NULL) {
190 		procstat_freekstack(procstat, kkstp_free);
191 		return;
192 	}
193 
194 	kinfo_kstack_sort(kkstp, kstk_count);
195 	for (i = 0; i < kstk_count; i++) {
196 		kkstp = &kkstp_free[i];
197 
198 		/*
199 		 * Look up the specific thread using its tid so we can
200 		 * display the per-thread command line.
201 		 */
202 		kipp = NULL;
203 		for (j = 0; j < kip_count; j++) {
204 			kipp = &kip_free[j];
205 			if (kkstp->kkst_tid == kipp->ki_tid)
206 				break;
207 		}
208 		if (kipp == NULL)
209 			continue;
210 
211 		xo_emit("{k:process_id/%5d/%d} ", kipp->ki_pid);
212 		xo_emit("{:thread_id/%6d/%d} ", kkstp->kkst_tid);
213 		xo_emit("{:command/%-19s/%s} ", kipp->ki_comm);
214 		xo_emit("{:thread_name/%-19s/%s} ",
215                     kinfo_proc_thread_name(kipp));
216 
217 		switch (kkstp->kkst_state) {
218 		case KKST_STATE_RUNNING:
219 			xo_emit("{:state/%-29s/%s}\n", "<running>");
220 			continue;
221 
222 		case KKST_STATE_SWAPPED:
223 			xo_emit("{:state/%-29s/%s}\n", "<swapped>");
224 			continue;
225 
226 		case KKST_STATE_STACKOK:
227 			break;
228 
229 		default:
230 			xo_emit("{:state/%-29s/%s}\n", "<unknown>");
231 			continue;
232 		}
233 
234 		/*
235 		 * The kernel generates a trace with carriage returns between
236 		 * entries, but for a more compact view, we convert carriage
237 		 * returns to spaces.
238 		 */
239 		kstack_cleanup(kkstp->kkst_trace, trace,
240 		    (procstat_opts & PS_OPT_VERBOSE) != 0 ? 2 : 1);
241 		xo_open_list("trace");
242 		kstack_cleanup_encoded(kkstp->kkst_trace, encoded_trace,
243 		    (procstat_opts & PS_OPT_VERBOSE) != 0 ? 2 : 1);
244 		xo_close_list("trace");
245 		xo_emit("{d:trace/%-29s}\n", trace);
246 	}
247 	procstat_freekstack(procstat, kkstp_free);
248 	procstat_freeprocs(procstat, kip_free);
249 }
250