Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add some VFS helpers #90

Merged
merged 7 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 37 additions & 11 deletions drgn_tools/bt.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ def bt_frames(
return expand_frames(stack_trace)


def print_task_header(task: drgn.Object) -> None:
def print_task_header(task: drgn.Object, indent: int = 0) -> None:
"""
Given a task struct, print the header line of the stack trace.
"""
Expand All @@ -253,8 +253,9 @@ def print_task_header(task: drgn.Object) -> None:
cpu_note = ""
if cpu_curr(task.prog_, cpu) == task:
cpu_note = "!"
pfx = " " * indent
print(
f"PID: {pid:<7d} TASK: {taskp:x} [{st}] CPU: {cpu}{cpu_note}"
f"{pfx}PID: {pid:<7d} TASK: {taskp:x} [{st}] CPU: {cpu}{cpu_note}"
f' COMMAND: "{comm}"'
)

Expand All @@ -265,6 +266,7 @@ def print_frames(
show_vars: bool = False,
show_absent: bool = False,
start_idx: int = 0,
indent: int = 0,
) -> None:
"""
Print stack frames using the drgn-tools (crash-like) format
Expand All @@ -274,8 +276,10 @@ def print_frames(
:param trace: The stack trace or list of frames to print
:param show_vars: True if you want to show variables
:param show_absent: True if you further want to show absent variables
:start_idx: Where to start counting the frame indices from
:param start_idx: Where to start counting the frame indices from
:param indent: How many spaces to indent the output
"""
pfx = " " * indent
for i, frame in enumerate(trace):
sp = frame.sp # drgn 0.0.22
intr = "!" if frame.interrupted else " "
Expand All @@ -285,7 +289,7 @@ def print_frames(
pc = "???"
name = frame_name(prog, frame)
idx = start_idx + i
out_line = f"{intr}#{idx:2d} [{sp:x}] {name} at {pc}"
out_line = f"{pfx}{intr}#{idx:2d} [{sp:x}] {name} at {pc}"
try:
file_, line, col = frame.source()
out_line += f" {file_}:{line}:{col}"
Expand All @@ -306,7 +310,8 @@ def print_frames(
# This formats the registers in three columns.
for j in range(0, len(regnames), 3):
print(
" " * 5
pfx
+ " " * 5
+ " ".join(
f"{reg.upper():>3s}: {registers[reg]:016x}"
for reg in regnames[j : j + 3]
Expand Down Expand Up @@ -336,13 +341,14 @@ def print_frames(
if val.absent_ and not show_absent:
continue
val_str = val.format_(dereference=False).replace("\n", "\n ")
print(" " * 5 + f"{local} = {val_str}")
print(pfx + " " * 5 + f"{local} = {val_str}")


def print_traces(
traces: t.List[drgn.StackTrace],
show_vars: bool = False,
show_absent: bool = False,
indent: int = 0,
) -> None:
"""
Given a list of stack traces, print them in the crash-like format
Expand All @@ -357,13 +363,15 @@ def print_traces(
idx = 0
prog = traces[0].prog
for trace_idx, trace in enumerate(traces):
print_frames(prog, trace, show_vars=show_vars, start_idx=idx)
print_frames(
prog, trace, show_vars=show_vars, start_idx=idx, indent=indent
)
idx += len(trace)

# Ok, this is the end of the loop over each frame within the trace.
if trace_idx < len(traces) - 1:
# But there is still another segment
print(" -- continuing to previous stack -- ")
print(" " * indent + " -- continuing to previous stack -- ")


def bt(
Expand All @@ -373,6 +381,7 @@ def bt(
show_vars: bool = False,
show_absent: bool = False,
retframes: bool = False,
indent: int = 0,
) -> t.Optional[t.List[drgn.StackFrame]]:
"""
Format a crash-like stack trace.
Expand Down Expand Up @@ -418,6 +427,7 @@ def bt(
to include absent variables. Normally there's no reason to see this, since
absent variables have no information.
:param retframes: When true, returns a list of stack frames.
:param indent: Number of spaces to indent all output lines
:returns: A list of the stack frames which were printed. This can be useful
for accessing the variables out of the frames interactively. If you're
writing a script that needs to access frames, you may want to consider the
Expand All @@ -431,13 +441,15 @@ def bt(
"struct task_struct *",
):
state = task_state_to_char(task)
print_task_header(task)
print_task_header(task, indent=indent)
if state in ("Z", "X"):
print(f"Task is in state: {state} - cannot unwind")
return []

traces = expand_traces(task.prog_.stack_trace(task))
print_traces(traces, show_vars=show_vars, show_absent=show_absent)
print_traces(
traces, show_vars=show_vars, show_absent=show_absent, indent=indent
)
frames = None
if retframes:
frames = []
Expand Down Expand Up @@ -558,13 +570,27 @@ def bt_has(
return bt_has_any(prog, [funcname], task)


def print_online_bt(prog: Program, **kwargs: t.Any) -> None:
def print_online_bt(
prog: Program, skip_idle: bool = True, **kwargs: t.Any
) -> None:
"""
Prints the stack trace of all on-CPU tasks

:kwargs: passed to bt() to control backtrace format
"""
for cpu in for_each_online_cpu(prog):
task = cpu_curr(prog, cpu)
if skip_idle and task.comm.string_().decode() == f"swapper/{cpu}":
# Just because it's the swapper task, does not mean it is idling.
# Check the symbol at the top of the stack to ensure it's the
# architecture idle function.
trace = prog.stack_trace(task)
try:
sym = trace[0].symbol().name
if sym in ("intel_idle",):
brenns10 marked this conversation as resolved.
Show resolved Hide resolved
continue
except (IndexError, LookupError):
pass
bt(prog, cpu=cpu, **kwargs)
print()

Expand Down
71 changes: 70 additions & 1 deletion drgn_tools/dentry.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2023, Oracle and/or its affiliates.
# Copyright (c) 2024, Oracle and/or its affiliates.
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
"""
Helpers for dentries.
Expand All @@ -12,6 +12,8 @@
import drgn
from drgn import Object
from drgn import Program
from drgn.helpers.linux.fs import path_lookup
from drgn.helpers.linux.list import hlist_for_each_entry
from drgn.helpers.linux.list import list_for_each_entry

from drgn_tools.corelens import CorelensModule
Expand All @@ -24,6 +26,27 @@
MNT_INTERNAL = 0x4000


def dentry_for_each_child(dentry: Object) -> Iterator[Object]:
"""
Iterate over every child of a dentry
"""
# Commit da549bdd15c29 ("dentry: switch the lists of children to hlist")
# changes the list names and types. Try the older names first since all UEK
# versions have the older names.
try:
return list_for_each_entry(
"struct dentry",
dentry.d_subdirs.address_of_(),
"d_child",
)
except AttributeError:
return hlist_for_each_entry(
"struct dentry",
dentry.d_children.address_of_(),
"d_sib",
)


def sb_first_mount_point(sb: Object) -> Optional[Object]:
"""
Return the first mountpoint of the superblock
Expand Down Expand Up @@ -358,6 +381,52 @@ def __file_type(mode: Object) -> str:
return "UNKN"


def ls(prog: Program, directory: str, count: bool = False) -> None:
"""
Print dentry children, like the ls command
:param directory: directory to print children of
:param count: when true, only print counts (not the full contents)
"""
dentries = dentry_for_each_child(path_lookup(prog, directory).dentry)

pos = neg = 0
for i, dentry in enumerate(dentries):
path = dentry_path_any_mount(dentry).decode()
if dentry_is_negative(dentry):
neg += 1
else:
pos += 1
if not count:
print(f"{i:05d} {path}")
print(f"{pos} positive, {neg} negative dentries")


class Ls(CorelensModule):
"""List or count child dentries given a file path"""

name = "ls"

# This module shouldn't run for corelens reports, because it has a required
# argument. It's quite useful to run it interactively though.
run_when = "never"

def add_args(self, parser: argparse.ArgumentParser) -> None:
parser.add_argument(
"directory",
type=str,
help="directory to list",
)
parser.add_argument(
"--count",
"-c",
action="store_true",
help="only print counts, rather than every element",
)

def run(self, prog: Program, args: argparse.Namespace) -> None:
ls(prog, args.directory, count=args.count)


class DentryCache(CorelensModule):
"""List dentries from the dentry hash table"""

Expand Down
Loading
Loading