Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e483f07

Browse files
authored
Add userland stacks and threads commands (#321)
1 parent f5e1943 commit e483f07

File tree

2 files changed

+322
-0
lines changed

2 files changed

+322
-0
lines changed

sdb/commands/ustacks.py

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
#
2+
# Copyright 2019, 2023 Delphix
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
# pylint: disable=missing-docstring
18+
19+
import argparse
20+
from typing import Dict, Iterable, List, Optional, Tuple
21+
from collections import defaultdict
22+
23+
import drgn
24+
from drgn import Thread
25+
26+
import sdb
27+
28+
29+
def gettid(thread: Thread) -> drgn.Object:
30+
return drgn.Object(sdb.get_prog(), 'int', thread.tid)
31+
32+
33+
class UserStacks(sdb.Locator, sdb.PrettyPrinter):
34+
"""
35+
Print the stack traces for active threads
36+
37+
DESCRIPTION
38+
By default, the command will aggregate similar call stacks
39+
printing them in descending order of frequency. The output
40+
includes the thread ID and aggregation count.
41+
42+
Optionally, the command can filter stacks, displaying only
43+
those that contain a given function.
44+
45+
The command returns all thread IDs that matched the
46+
filter.
47+
48+
The command will not produce output on running userland processes,
49+
because the process is not stopped while being analyzed.
50+
51+
EXAMPLES
52+
Print the call stacks for all threads
53+
54+
sdb> stacks
55+
TID COUNT
56+
==========================================================
57+
...
58+
125023 1
59+
0x7f2fd24c1170+0x0
60+
spa_open_common+0x1ac
61+
dsl_sync_task_common+0x65
62+
dsl_sync_task_sig+0x13
63+
zcp_eval+0x70f
64+
dsl_destroy_snapshots_nvl+0x12b
65+
dsl_destroy_snapshot+0x3f
66+
ztest_objset_destroy_cb+0xb7
67+
dmu_objset_find_impl+0x481
68+
dmu_objset_find+0x56
69+
ztest_dmu_objset_create_destroy+0xad
70+
ztest_execute+0x6c
71+
ztest_thread+0xd9
72+
zk_thread_wrapper+0x1c
73+
0x7f2fd24b6609+0x0
74+
...
75+
125037 1
76+
0x7f2fd22ff00b+0x0
77+
0x557766d8aeed+0x0
78+
ztest_dsl_dataset_promote_busy+0x9e
79+
ztest_execute+0x6c
80+
ztest_thread+0xd9
81+
zk_thread_wrapper+0x1c
82+
0x7f2fd24b6609+0x0
83+
...
84+
85+
Print stacks containing the l2arc_feed_thread function
86+
87+
sdb> stacks -c zcp_eval
88+
TID COUNT
89+
==========================================================
90+
125023 1
91+
0x7f2fd24c1170+0x0
92+
spa_open_common+0x1ac
93+
dsl_sync_task_common+0x65
94+
dsl_sync_task_sig+0x13
95+
zcp_eval+0x70f
96+
dsl_destroy_snapshots_nvl+0x12b
97+
dsl_destroy_snapshot+0x3f
98+
ztest_objset_destroy_cb+0xb7
99+
dmu_objset_find_impl+0x481
100+
dmu_objset_find+0x56
101+
ztest_dmu_objset_create_destroy+0xad
102+
ztest_execute+0x6c
103+
ztest_thread+0xd9
104+
zk_thread_wrapper+0x1c
105+
0x7f2fd24b6609+0x0
106+
107+
108+
"""
109+
110+
names = ["stacks", "stack"]
111+
input_type = "int"
112+
output_type = "int"
113+
load_on = [sdb.Userland()]
114+
115+
def __init__(self,
116+
args: Optional[List[str]] = None,
117+
name: str = "_") -> None:
118+
super().__init__(args, name)
119+
self.func_start, self.func_end = 0, 0
120+
121+
@classmethod
122+
def _init_parser(cls, name: str) -> argparse.ArgumentParser:
123+
parser = super()._init_parser(name)
124+
parser.add_argument(
125+
"-a",
126+
"--all",
127+
action="store_true",
128+
help="list all threads for each unique stack trace" +
129+
" instead of printing a single representative thread")
130+
parser.add_argument(
131+
"-c",
132+
"--function",
133+
help="only print threads whose stacks contains FUNCTION")
134+
return parser
135+
136+
@staticmethod
137+
def get_frame_pcs(thread: Thread) -> List[int]:
138+
frame_pcs = []
139+
try:
140+
for frame in thread.stack_trace():
141+
frame_pcs.append(frame.pc)
142+
except ValueError:
143+
#
144+
# Unwinding the stack of a running/runnable task will
145+
# result in an exception. Since we expect some tasks to
146+
# be running, we silently ignore this case, and move on.
147+
#
148+
# Unfortunately, the exception thrown in this case is a
149+
# generic "ValueError" exception, so we may wind up
150+
# masking other "ValueError" exceptions that are not due
151+
# to unwinding the stack of a running task.
152+
#
153+
# This also means that debugging a running userland process will
154+
# be largely ineffective, since we don't use ptrace to stop the
155+
# process the way other debuggers like gdb do.
156+
#
157+
pass
158+
return frame_pcs
159+
160+
def validate_args(self) -> None:
161+
if self.args.function:
162+
try:
163+
#
164+
# It would be simpler to resolve the symbol from the function
165+
# name directly but we use the address due to osandov/drgn#47.
166+
#
167+
func = sdb.get_object(self.args.function)
168+
sym = sdb.get_symbol(func.address_of_())
169+
except KeyError as err:
170+
raise sdb.CommandError(
171+
self.name,
172+
f"symbol '{self.args.function}' does not exist") from err
173+
if func.type_.kind != drgn.TypeKind.FUNCTION:
174+
raise sdb.CommandError(
175+
self.name, f"'{self.args.function}' is not a function")
176+
self.func_start = sym.address
177+
self.func_end = self.func_start + sym.size
178+
179+
def match_stack(self, thread: Thread) -> bool:
180+
if not self.args.function:
181+
return True
182+
183+
for frame_pc in UserStacks.get_frame_pcs(thread):
184+
if self.func_start <= frame_pc < self.func_end:
185+
return True
186+
return False
187+
188+
def print_header(self) -> None:
189+
header = f"{'TID':<10}"
190+
if not self.args.all:
191+
header += f" {'COUNT':>6s}"
192+
print(header)
193+
print("=" * 58)
194+
195+
#
196+
# De-duplicate the objs (threads) using a dictionary indexed by
197+
# task state and program counters. Return a collection sorted by number
198+
# of tasks per stack.
199+
#
200+
@staticmethod
201+
def aggregate_stacks(
202+
objs: Iterable[Thread]
203+
) -> List[Tuple[Tuple[int, ...], List[Thread]]]:
204+
stack_aggr: Dict[Tuple[int, ...], List[Thread]] = defaultdict(list)
205+
for thread in objs:
206+
stack_key = tuple(UserStacks.get_frame_pcs(thread))
207+
stack_aggr[stack_key].append(thread)
208+
return sorted(stack_aggr.items(), key=lambda x: len(x[1]), reverse=True)
209+
210+
def print_stacks(self, objs: Iterable[Thread]) -> None:
211+
self.print_header()
212+
for frame_pcs, threads in UserStacks.aggregate_stacks(objs):
213+
stacktrace_info = ""
214+
215+
if self.args.all:
216+
for thread in threads:
217+
stacktrace_info += f"{thread.tid:<10d}\n"
218+
else:
219+
tid = threads[0].tid
220+
stacktrace_info += f"{tid:<10d} {len(threads):6d}\n"
221+
222+
for frame_pc in frame_pcs:
223+
try:
224+
sym = sdb.get_symbol(frame_pc)
225+
func = sym.name
226+
offset = frame_pc - sym.address
227+
except LookupError:
228+
func = hex(frame_pc)
229+
offset = 0x0
230+
stacktrace_info += f"{'':18s}{func}+{hex(offset)}\n"
231+
print(stacktrace_info)
232+
233+
def pretty_print(self, objs: Iterable[drgn.Object]) -> None:
234+
self.validate_args()
235+
self.print_stacks(
236+
filter(self.match_stack, map(sdb.get_prog().thread, objs)))
237+
238+
def no_input(self) -> Iterable[drgn.Object]:
239+
self.validate_args()
240+
yield from map(gettid, filter(self.match_stack,
241+
sdb.get_prog().threads()))
242+
243+
244+
class UserCrashedThread(sdb.Locator, sdb.PrettyPrinter):
245+
"""
246+
Print the crashed thread. Only works for crash dumps and core dumps.
247+
248+
EXAMPLES
249+
sdb> crashed_thread
250+
TID COUNT
251+
==========================================================
252+
125037 1
253+
0x7f2fd22ff00b+0x0
254+
0x557766d8aeed+0x0
255+
ztest_dsl_dataset_promote_busy+0x9e
256+
ztest_execute+0x6c
257+
ztest_thread+0xd9
258+
zk_thread_wrapper+0x1c
259+
0x7f2fd24b6609+0x0
260+
"""
261+
262+
names = ["crashed_thread", "panic_stack", "panic_thread"]
263+
input_type = "int"
264+
output_type = "int"
265+
load_on = [sdb.Userland()]
266+
267+
def validate_args(self) -> None:
268+
if sdb.get_target_flags() & drgn.ProgramFlags.IS_LIVE:
269+
raise sdb.CommandError(self.name,
270+
"command only works for core/crash dumps")
271+
272+
def pretty_print(self, objs: Iterable[drgn.Object]) -> None:
273+
self.validate_args()
274+
thread = sdb.get_prog().crashed_thread()
275+
stacks_obj = UserStacks()
276+
for obj in objs:
277+
if obj.value_() != thread.tid:
278+
raise sdb.CommandError(
279+
self.name, "can only pretty print the crashed thread")
280+
stacks_obj.print_stacks([thread])
281+
282+
def no_input(self) -> Iterable[drgn.Object]:
283+
self.validate_args()
284+
yield from [gettid(sdb.get_prog().crashed_thread())]

sdb/commands/uthreads.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#
2+
# Copyright 2020 Delphix
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
# pylint: disable=missing-docstring
18+
19+
from typing import Iterable
20+
21+
import drgn
22+
23+
import sdb
24+
from sdb.commands.ustacks import gettid
25+
26+
27+
class UserThreads(sdb.Locator):
28+
"""
29+
Locate the list of threads in the process
30+
"""
31+
32+
names = ["threads", "thread"]
33+
input_type = "int"
34+
output_type = "int"
35+
load_on = [sdb.Userland()]
36+
37+
def no_input(self) -> Iterable[drgn.Object]:
38+
yield from map(gettid, sdb.get_prog().threads())

0 commit comments

Comments
 (0)