Skip to content

Commit 7b3d6ef

Browse files
author
Delphix Engineering
committed
Merge branch 'refs/heads/upstream-HEAD' into repo-HEAD
2 parents dd48031 + 83e4aa6 commit 7b3d6ef

File tree

12 files changed

+584
-43
lines changed

12 files changed

+584
-43
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ repos:
3333
- id: debug-statements
3434
- id: check-merge-conflict
3535
- repo: https://github.com/netromdk/vermin
36-
rev: v1.6.0
36+
rev: v1.7.0
3737
hooks:
3838
- id: vermin
3939
args: ['-t=3.8-', '--violations', '--eval-annotations']

drgn/commands/__init__.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646

4747
import _drgn_util.argparseformatter
4848
from _drgn_util.typingutils import copy_method_params
49-
from drgn import Program
49+
from drgn import Program, ProgramFlags
5050

5151
_WORD_CHARACTER = r"""[^\s"#&'();<>\\`|]"""
5252
_WORD_CHARACTER_OR_HASH = _WORD_CHARACTER.replace("#", "")
@@ -1102,7 +1102,8 @@ class DrgnCodeBuilder:
11021102
are deduplicated, sorted, and prepended to the final output.
11031103
"""
11041104

1105-
def __init__(self) -> None:
1105+
def __init__(self, prog: Program) -> None:
1106+
self._prog = prog
11061107
self._code: List[str] = []
11071108
self._imports: Dict[str, Set[str]] = collections.defaultdict(set)
11081109

@@ -1128,6 +1129,41 @@ def add_from_import(self, module: str, *names: str) -> None:
11281129
"""
11291130
self._imports[module].update(names)
11301131

1132+
def append_retry_loop_if_live(self, body: str, num_attempts: int) -> None:
1133+
"""
1134+
Append a code fragment wrapped in a loop that retries on transient
1135+
:class:`~drgn.FaultError` or :class:`~drgn.helpers.ValidationError`
1136+
errors if the program is live.
1137+
1138+
If the program is not live, the code fragment is appended verbatim.
1139+
1140+
:param body: Code to attempt in loop body.
1141+
:param num_attempts: Maximum number of attempts.
1142+
"""
1143+
self.append(self.wrap_retry_loop_if_live(body, num_attempts))
1144+
1145+
def wrap_retry_loop_if_live(self, body: str, num_attempts: int) -> str:
1146+
"""
1147+
Like :meth:`append_retry_loop_if_live()`, but return the code fragment
1148+
instead of appending it.
1149+
"""
1150+
if not (self._prog.flags & ProgramFlags.IS_LIVE):
1151+
return body
1152+
self.add_from_import("drgn", "FaultError")
1153+
self.add_from_import("drgn.helpers", "ValidationError")
1154+
# Copy the indentation level.
1155+
indent = re.match(r"[ \t]*", body).group() # type: ignore[union-attr] # This regex always matches.
1156+
return f"""\
1157+
{indent}# This is racy. Retry a limited number of times.
1158+
{indent}for attempts_remaining in range({num_attempts}, 0, -1):
1159+
{indent} try:
1160+
{textwrap.indent(body, " ")}\
1161+
{indent} break
1162+
{indent} except (FaultError, ValidationError):
1163+
{indent} if attempts_remaining == 1:
1164+
{indent} raise
1165+
"""
1166+
11311167
def get(self) -> str:
11321168
"""Get the output as a string."""
11331169
parts: List[str] = []

drgn/commands/_builtin/crash/_kmem.py

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import functools
88
import re
99
import sys
10-
import textwrap
1110
from typing import AbstractSet, Any, Callable, Iterable, List, Optional, Sequence, Tuple
1211

1312
from drgn import (
@@ -158,28 +157,7 @@ def _kmem_free(prog: Program, drgn_arg: bool, show_pages: bool = False) -> None:
158157
loop_body = """\
159158
num_blocks = validate_list_count_nodes(free_list.address_of_())
160159
"""
161-
if prog.flags & ProgramFlags.IS_LIVE:
162-
code.add_from_import("drgn", "FaultError")
163-
code.add_from_import("drgn.helpers", "ValidationError")
164-
code.append(
165-
"""\
166-
# This is racy. Retry a few times if needed.
167-
num_attempts = 10
168-
for attempt in range(num_attempts):
169-
try:
170-
"""
171-
)
172-
loop_body = textwrap.indent(loop_body, " ")
173-
code.append(loop_body)
174-
if prog.flags & ProgramFlags.IS_LIVE:
175-
code.append(
176-
"""\
177-
break
178-
except (FaultError, ValidationError):
179-
if attempt == num_attempts - 1:
180-
raise
181-
"""
182-
)
160+
code.append_retry_loop_if_live(loop_body, 100)
183161
code.append(
184162
"""\
185163
num_pages = num_blocks << order
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# SPDX-License-Identifier: LGPL-2.1-or-later
3+
4+
"""Crash timer command."""
5+
6+
import argparse
7+
from typing import Any, List, Sequence
8+
9+
from drgn import FaultError, Object, Program, ProgramFlags
10+
from drgn.commands import argument, drgn_argument
11+
from drgn.commands.crash import CrashDrgnCodeBuilder, crash_command, parse_cpuspec
12+
from drgn.helpers.common.format import CellFormat, RowOptions, print_table
13+
from drgn.helpers.linux.percpu import per_cpu
14+
from drgn.helpers.linux.timekeeping import ktime_get_coarse_ns, ktime_to_ns
15+
from drgn.helpers.linux.timer import (
16+
hrtimer_clock_base_for_each,
17+
timer_base_for_each,
18+
timer_base_names,
19+
)
20+
21+
22+
def _function_cell(function: Object) -> str:
23+
address = function.value_()
24+
try:
25+
function_symbol = function.prog_.symbol(address)
26+
except LookupError:
27+
return f"{address:x}"
28+
else:
29+
return f"{address:x} <{function_symbol.name}>"
30+
31+
32+
@crash_command(
33+
description="list kernel timers",
34+
arguments=(
35+
argument(
36+
"-r",
37+
dest="hrtimer",
38+
action="store_true",
39+
help="display high-resolution timers (hrtimers)",
40+
),
41+
argument(
42+
"-C",
43+
dest="cpu",
44+
default="all",
45+
help="restrict the output to one or more CPUs, "
46+
"which may be a comma-separated string of CPU numbers or ranges "
47+
"(e.g., '0,3-4')",
48+
),
49+
drgn_argument,
50+
),
51+
)
52+
def _crash_cmd_timer(
53+
prog: Program,
54+
name: str,
55+
args: argparse.Namespace,
56+
**kwargs: Any,
57+
) -> None:
58+
cpuspec = parse_cpuspec(args.cpu)
59+
60+
if args.hrtimer:
61+
if args.drgn:
62+
code = CrashDrgnCodeBuilder(prog)
63+
code.add_from_import("drgn.helpers.linux.percpu", "per_cpu")
64+
code.add_from_import(
65+
"drgn.helpers.linux.timer", "hrtimer_clock_base_for_each"
66+
)
67+
code.add_from_import(
68+
"drgn.helpers.linux.timekeeping", "ktime_get_coarse_ns", "ktime_to_ns"
69+
)
70+
code.append("now = ktime_get_coarse_ns()\n\n")
71+
code.append_cpuspec(
72+
cpuspec,
73+
"""\
74+
cpu_base = per_cpu(prog["hrtimer_bases"], cpu)
75+
76+
for clock_base in cpu_base.clock_base:
77+
clock = clock_base.index
78+
current = now + ktime_to_ns(clock_base.offset)
79+
80+
"""
81+
+ code.wrap_retry_loop_if_live(
82+
"""\
83+
for hrtimer in hrtimer_clock_base_for_each(clock_base.address_of_()):
84+
softexpires = ktime_to_ns(hrtimer._softexpires)
85+
expires = ktime_to_ns(hrtimer.node.expires)
86+
tte = expires - current
87+
function = hrtimer.function
88+
""",
89+
1000,
90+
),
91+
)
92+
return code.print()
93+
94+
hrtimer_bases = prog["hrtimer_bases"]
95+
now = ktime_get_coarse_ns(prog)
96+
first_cpu = True
97+
for cpu in cpuspec.cpus(prog):
98+
if first_cpu:
99+
first_cpu = False
100+
else:
101+
print()
102+
cpu_base = per_cpu(hrtimer_bases, cpu)
103+
print(f"CPU: {cpu} HRTIMER_CPU_BASE: {cpu_base.address_:x}")
104+
105+
first_clock_base = True
106+
for clock_base in cpu_base.clock_base:
107+
if first_clock_base:
108+
first_clock_base = False
109+
else:
110+
print()
111+
112+
index = clock_base.index.read_()
113+
# Crash shows the name of the hrtimer_clock_base::get_time
114+
# function, but that was removed in Linux kernel commit
115+
# 009eb5da29a9 ("hrtimer: Remove hrtimer_clock_base::
116+
# Get_time") (in v6.18), so we omit it.
117+
print(
118+
f" CLOCK: {index.value_()} HRTIMER_CLOCK_BASE: {clock_base.address_:x}"
119+
)
120+
121+
current = (now + ktime_to_ns(clock_base.offset)).value_()
122+
rows: List[Sequence[Any]] = [
123+
(
124+
"",
125+
CellFormat("CURRENT", "^"),
126+
),
127+
("", current),
128+
(
129+
"",
130+
CellFormat("SOFTEXPIRES", "^"),
131+
CellFormat("EXPIRES", "^"),
132+
CellFormat("TTE", "^"),
133+
CellFormat("HRTIMER", "^"),
134+
"FUNCTION",
135+
),
136+
]
137+
# Walking the hrtimer queue is racy. Retry a limited number of
138+
# times on live kernels.
139+
for _ in range(1000 if (prog.flags & ProgramFlags.IS_LIVE) else 1):
140+
try:
141+
for hrtimer in hrtimer_clock_base_for_each(clock_base):
142+
expires = ktime_to_ns(hrtimer.node.expires).value_()
143+
rows.append(
144+
(
145+
"",
146+
ktime_to_ns(hrtimer._softexpires).value_(),
147+
expires,
148+
expires - current,
149+
CellFormat(hrtimer.value_(), "^x"),
150+
_function_cell(hrtimer.function),
151+
),
152+
)
153+
break
154+
except FaultError:
155+
del rows[3:]
156+
else:
157+
print(" (corrupted)")
158+
continue
159+
if len(rows) > 3:
160+
# We print each base separately instead of as one big table
161+
# because the timestamp values can differ greatly between
162+
# bases and make the formatting look funny if they're all
163+
# aligned to the largest one.
164+
print_table(rows)
165+
else:
166+
print(" (empty)")
167+
else:
168+
if args.drgn:
169+
code = CrashDrgnCodeBuilder(prog)
170+
code.append('jiffies = prog["jiffies"]\n\n')
171+
code.add_from_import("drgn.helpers.linux.percpu", "per_cpu")
172+
code.add_from_import(
173+
"drgn.helpers.linux.timer", "timer_base_for_each", "timer_base_names"
174+
)
175+
code.append_cpuspec(
176+
cpuspec,
177+
'for name, base in zip(timer_base_names(), per_cpu(prog["timer_bases"], cpu)):\n'
178+
+ code.wrap_retry_loop_if_live(
179+
"""\
180+
for timer in timer_base_for_each(base):
181+
expires = timer.expires
182+
tte = expires - jiffies
183+
function = timer.function
184+
""",
185+
1000,
186+
),
187+
)
188+
return code.print()
189+
190+
jiffies = prog["jiffies"].value_()
191+
print(f"JIFFIES\n{jiffies}")
192+
193+
rows = []
194+
timer_bases = prog["timer_bases"]
195+
for cpu in cpuspec.cpus(prog):
196+
rows.append(())
197+
for name, base in zip(timer_base_names(prog), per_cpu(timer_bases, cpu)):
198+
rows.append(
199+
RowOptions(
200+
(f"TIMER_BASES[{cpu}][{name}]: {base.address_:x}",),
201+
group=1,
202+
),
203+
)
204+
205+
rows.append(
206+
(
207+
"",
208+
"EXPIRES",
209+
CellFormat("TTE", ">"),
210+
CellFormat("TIMER_LIST", "^"),
211+
"FUNCTION",
212+
),
213+
)
214+
timer_rows = []
215+
# Walking the timer lists is racy. Retry a limited number of
216+
# times on live kernels.
217+
for _ in range(1000 if (prog.flags & ProgramFlags.IS_LIVE) else 1):
218+
try:
219+
for timer in timer_base_for_each(base):
220+
expires = timer.expires.value_()
221+
timer_rows.append(
222+
(
223+
"",
224+
CellFormat(expires, "<"),
225+
expires - jiffies,
226+
CellFormat(timer.value_(), "^x"),
227+
_function_cell(timer.function),
228+
)
229+
)
230+
break
231+
except FaultError:
232+
timer_rows.clear()
233+
else:
234+
rows.append(("", "(corrupted)"))
235+
continue
236+
if timer_rows:
237+
rows.extend(timer_rows)
238+
else:
239+
rows.append(("", "(none)"))
240+
print_table(rows)

drgn/commands/crash.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -404,10 +404,6 @@ class CrashDrgnCodeBuilder(DrgnCodeBuilder):
404404
commands.
405405
"""
406406

407-
def __init__(self, prog: Program) -> None:
408-
super().__init__()
409-
self._prog = prog
410-
411407
def _append_crash_panic_context(self) -> None:
412408
if (self._prog.flags & (ProgramFlags.IS_LIVE | ProgramFlags.IS_LOCAL)) == (
413409
ProgramFlags.IS_LIVE | ProgramFlags.IS_LOCAL

0 commit comments

Comments
 (0)