1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22 /*
23 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
24 */
25
26 #include <mdb/mdb_modapi.h>
27 #include <fcntl.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <sys/avl.h>
31 #include <sys/lwp.h>
32 #include <thr_uberdata.h>
33 #include <stddef.h>
34 #include "findstack.h"
35
36 #if defined(__i386) || defined(__amd64)
37 struct rwindow {
38 uintptr_t rw_fp;
39 uintptr_t rw_rtn;
40 };
41 #endif
42
43 #ifndef STACK_BIAS
44 #define STACK_BIAS 0
45 #endif
46
47 #ifdef __amd64
48 #define STACKS_REGS_FP "rbp"
49 #define STACKS_REGS_RC "rip"
50 #else
51 #ifdef __i386
52 #define STACKS_REGS_FP "ebp"
53 #define STACKS_REGS_RC "eip"
54 #else
55 #define STACKS_REGS_FP "fp"
56 #define STACKS_REGS_RC "pc"
57 #endif
58 #endif
59
60 #define STACKS_SOBJ_MX (uintptr_t)"MX"
61 #define STACKS_SOBJ_CV (uintptr_t)"CV"
62
63 int
thread_text_to_state(const char * state,uint_t * out)64 thread_text_to_state(const char *state, uint_t *out)
65 {
66 if (strcmp(state, "PARKED") == 0) {
67 *out = B_TRUE;
68 } else if (strcmp(state, "UNPARKED") == 0) {
69 *out = B_FALSE;
70 } else if (strcmp(state, "FREE") == 0) {
71 /*
72 * When run with "-i", ::stacks filters out "FREE" threads.
73 * We therefore need to recognize "FREE", and set it to a
74 * value that will never match fsi_tstate.
75 */
76 *out = UINT_MAX;
77 } else {
78 return (-1);
79 }
80
81 return (0);
82 }
83
84 void
thread_state_to_text(uint_t state,char * out,size_t out_sz)85 thread_state_to_text(uint_t state, char *out, size_t out_sz)
86 {
87 (void) snprintf(out, out_sz, state ? "PARKED" : "UNPARKED");
88 }
89
90 int
sobj_text_to_ops(const char * name,uintptr_t * sobj_ops_out)91 sobj_text_to_ops(const char *name, uintptr_t *sobj_ops_out)
92 {
93 if (strcmp(name, "MX") == 0) {
94 *sobj_ops_out = STACKS_SOBJ_MX;
95 } else if (strcmp(name, "CV") == 0) {
96 *sobj_ops_out = STACKS_SOBJ_CV;
97 } else {
98 mdb_warn("sobj \"%s\" not recognized\n", name);
99 return (-1);
100 }
101
102 return (0);
103 }
104
105 void
sobj_ops_to_text(uintptr_t addr,char * out,size_t sz)106 sobj_ops_to_text(uintptr_t addr, char *out, size_t sz)
107 {
108 (void) snprintf(out, sz, "%s", addr == 0 ? "<none>" : (char *)addr);
109 }
110
111 static int
stacks_module_callback(mdb_object_t * obj,void * arg)112 stacks_module_callback(mdb_object_t *obj, void *arg)
113 {
114 stacks_module_t *smp = arg;
115 boolean_t match = (strcmp(obj->obj_name, smp->sm_name) == 0);
116 char *suffix = ".so";
117 const char *s, *next;
118 size_t len;
119
120 if (smp->sm_size != 0)
121 return (0);
122
123 /*
124 * It doesn't match the name, but -- for convenience -- we want to
125 * allow matches before ".so.[suffix]". An aside: why doesn't
126 * strrstr() exist? (Don't google that. I'm serious, don't do it.
127 * If you do, and you read the thread of "why doesn't strrstr() exist?"
128 * circa 2005 you will see things that you will NEVER be able to unsee!)
129 */
130 if (!match && (s = strstr(obj->obj_name, suffix)) != NULL) {
131 while ((next = strstr(s + 1, suffix)) != NULL) {
132 s = next;
133 continue;
134 }
135
136 len = s - obj->obj_name;
137
138 match = (strncmp(smp->sm_name, obj->obj_name, len) == 0 &&
139 smp->sm_name[len] == '\0');
140 }
141
142 /*
143 * If we have a library that has the libc directory in the path, we
144 * want to match against anything that would match libc.so.1. (This
145 * is necessary to be able to easily deal with libc implementations
146 * that have alternate hardware capabilities.)
147 */
148 if (!match && strstr(obj->obj_fullname, "/libc/") != NULL) {
149 mdb_object_t libc = *obj;
150
151 libc.obj_name = "libc.so.1";
152 libc.obj_fullname = "";
153
154 return (stacks_module_callback(&libc, arg));
155 }
156
157 if (match) {
158 smp->sm_text = obj->obj_base;
159 smp->sm_size = obj->obj_size;
160 }
161
162 return (0);
163 }
164
165 int
stacks_module(stacks_module_t * smp)166 stacks_module(stacks_module_t *smp)
167 {
168 if (mdb_object_iter(stacks_module_callback, smp) != 0)
169 return (-1);
170
171 return (0);
172 }
173
174 typedef struct stacks_ulwp {
175 avl_node_t sulwp_node;
176 lwpid_t sulwp_id;
177 uintptr_t sulwp_addr;
178 } stacks_ulwp_t;
179
180 boolean_t stacks_ulwp_initialized;
181 avl_tree_t stacks_ulwp_byid;
182
183 /*ARGSUSED*/
184 int
stacks_ulwp_walk(uintptr_t addr,ulwp_t * ulwp,void * ignored)185 stacks_ulwp_walk(uintptr_t addr, ulwp_t *ulwp, void *ignored)
186 {
187 stacks_ulwp_t *sulwp = mdb_alloc(sizeof (stacks_ulwp_t), UM_SLEEP);
188
189 sulwp->sulwp_id = ulwp->ul_lwpid;
190 sulwp->sulwp_addr = addr;
191
192 if (avl_find(&stacks_ulwp_byid, sulwp, NULL) != NULL) {
193 mdb_warn("found multiple LWPs with ID %d!", ulwp->ul_lwpid);
194 return (WALK_ERR);
195 }
196
197 avl_add(&stacks_ulwp_byid, sulwp);
198
199 return (WALK_NEXT);
200 }
201
202 static int
stacks_ulwp_compare(const void * l,const void * r)203 stacks_ulwp_compare(const void *l, const void *r)
204 {
205 const stacks_ulwp_t *lhs = l;
206 const stacks_ulwp_t *rhs = r;
207
208 if (lhs->sulwp_id > rhs->sulwp_id)
209 return (1);
210
211 if (lhs->sulwp_id < rhs->sulwp_id)
212 return (-1);
213
214 return (0);
215 }
216
217 /*ARGSUSED*/
218 int
stacks_findstack(uintptr_t addr,findstack_info_t * fsip,uint_t print_warnings)219 stacks_findstack(uintptr_t addr, findstack_info_t *fsip, uint_t print_warnings)
220 {
221 mdb_reg_t reg;
222 uintptr_t fp;
223 struct rwindow frame;
224 avl_tree_t *tree = &stacks_ulwp_byid;
225 stacks_ulwp_t *sulwp, cmp;
226 ulwp_t ulwp;
227
228 fsip->fsi_failed = 0;
229 fsip->fsi_pc = 0;
230 fsip->fsi_sp = 0;
231 fsip->fsi_depth = 0;
232 fsip->fsi_overflow = 0;
233
234 if (!stacks_ulwp_initialized) {
235 avl_create(tree, stacks_ulwp_compare, sizeof (stacks_ulwp_t),
236 offsetof(stacks_ulwp_t, sulwp_node));
237
238 if (mdb_walk("ulwp",
239 (mdb_walk_cb_t)stacks_ulwp_walk, NULL) != 0) {
240 mdb_warn("couldn't walk 'ulwp'");
241 return (-1);
242 }
243
244 stacks_ulwp_initialized = B_TRUE;
245 }
246
247 bzero(&cmp, sizeof (cmp));
248 cmp.sulwp_id = (lwpid_t)addr;
249
250 if ((sulwp = avl_find(tree, &cmp, NULL)) == NULL) {
251 mdb_warn("couldn't find ulwp_t for tid %d\n", cmp.sulwp_id);
252 return (-1);
253 }
254
255 if (mdb_vread(&ulwp, sizeof (ulwp), sulwp->sulwp_addr) == -1) {
256 mdb_warn("couldn't read ulwp_t for tid %d at %p",
257 cmp.sulwp_id, sulwp->sulwp_addr);
258 return (-1);
259 }
260
261 fsip->fsi_tstate = ulwp.ul_sleepq != NULL;
262 fsip->fsi_sobj_ops = (uintptr_t)(ulwp.ul_sleepq == NULL ? 0 :
263 (ulwp.ul_qtype == MX ? STACKS_SOBJ_MX : STACKS_SOBJ_CV));
264
265 if (mdb_getareg(addr, STACKS_REGS_FP, ®) != 0) {
266 mdb_warn("couldn't read frame pointer for thread 0x%p", addr);
267 return (-1);
268 }
269
270 fsip->fsi_sp = fp = (uintptr_t)reg;
271
272 #if !defined(__i386)
273 if (mdb_getareg(addr, STACKS_REGS_RC, ®) != 0) {
274 mdb_warn("couldn't read program counter for thread 0x%p", addr);
275 return (-1);
276 }
277
278 fsip->fsi_pc = (uintptr_t)reg;
279 #endif
280
281 while (fp != 0) {
282 if (mdb_vread(&frame, sizeof (frame), fp) == -1) {
283 mdb_warn("couldn't read frame for thread 0x%p at %p",
284 addr, fp);
285 return (-1);
286 }
287
288 if (frame.rw_rtn == 0)
289 break;
290
291 if (fsip->fsi_depth < fsip->fsi_max_depth) {
292 fsip->fsi_stack[fsip->fsi_depth++] = frame.rw_rtn;
293 } else {
294 fsip->fsi_overflow = 1;
295 break;
296 }
297
298 fp = frame.rw_fp + STACK_BIAS;
299 }
300
301 return (0);
302 }
303
304 void
stacks_findstack_cleanup()305 stacks_findstack_cleanup()
306 {
307 avl_tree_t *tree = &stacks_ulwp_byid;
308 void *cookie = NULL;
309 stacks_ulwp_t *sulwp;
310
311 if (!stacks_ulwp_initialized)
312 return;
313
314 while ((sulwp = avl_destroy_nodes(tree, &cookie)) != NULL)
315 mdb_free(sulwp, sizeof (stacks_ulwp_t));
316
317 bzero(tree, sizeof (*tree));
318 stacks_ulwp_initialized = B_FALSE;
319 }
320
321 void
stacks_help(void)322 stacks_help(void)
323 {
324 mdb_printf(
325 "::stacks processes all of the thread stacks in the process, grouping\n"
326 "together threads which have the same:\n"
327 "\n"
328 " * Thread state,\n"
329 " * Sync object type, and\n"
330 " * PCs in their stack trace.\n"
331 "\n"
332 "The default output (no address or options) is just a dump of the thread\n"
333 "groups in the process. For a view of active threads, use \"::stacks -i\",\n"
334 "which filters out threads sleeping on a CV. More general filtering options\n"
335 "are described below, in the \"FILTERS\" section.\n"
336 "\n"
337 "::stacks can be used in a pipeline. The input to ::stacks is one or more\n"
338 "thread IDs. When output into a pipe, ::stacks prints all of the threads \n"
339 "input, filtered by the given filtering options. This means that multiple\n"
340 "::stacks invocations can be piped together to achieve more complicated\n"
341 "filters. For example, to get threads which have both '__door_return' and\n"
342 "'mutex_lock' in their stack trace, you could do:\n"
343 "\n"
344 " ::stacks -c __door_return | ::stacks -c mutex_lock\n"
345 "\n"
346 "To get the full list of threads in each group, use the '-a' flag:\n"
347 "\n"
348 " ::stacks -a\n"
349 "\n");
350 mdb_dec_indent(2);
351 mdb_printf("%<b>OPTIONS%</b>\n");
352 mdb_inc_indent(2);
353 mdb_printf("%s",
354 " -a Print all of the grouped threads, instead of just a count.\n"
355 " -f Force a re-run of the thread stack gathering.\n"
356 " -v Be verbose about thread stack gathering.\n"
357 "\n");
358 mdb_dec_indent(2);
359 mdb_printf("%<b>FILTERS%</b>\n");
360 mdb_inc_indent(2);
361 mdb_printf("%s",
362 " -i Show active threads; equivalent to '-S CV'.\n"
363 " -c func[+offset]\n"
364 " Only print threads whose stacks contain func/func+offset.\n"
365 " -C func[+offset]\n"
366 " Only print threads whose stacks do not contain func/func+offset.\n"
367 " -m module\n"
368 " Only print threads whose stacks contain functions from module.\n"
369 " -M module\n"
370 " Only print threads whose stacks do not contain functions from\n"
371 " module.\n"
372 " -s {type | ALL}\n"
373 " Only print threads which are on a 'type' synchronization object\n"
374 " (SOBJ).\n"
375 " -S {type | ALL}\n"
376 " Only print threads which are not on a 'type' SOBJ.\n"
377 " -t tstate\n"
378 " Only print threads which are in thread state 'tstate'.\n"
379 " -T tstate\n"
380 " Only print threads which are not in thread state 'tstate'.\n"
381 "\n");
382 }
383