Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
99 changes: 97 additions & 2 deletions drgn/helpers/common/stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@

from typing import Any, Dict

from drgn import FaultError, PlatformFlags, StackTrace
from drgn import Architecture, FaultError, PlatformFlags, Program, StackTrace
from drgn.helpers.common.memory import identify_address

__all__ = ("print_annotated_stack",)
__all__ = ("print_annotated_stack", "print_registers")


def print_annotated_stack(trace: StackTrace) -> None:
Expand Down Expand Up @@ -136,3 +136,98 @@ def print_annotated_stack(trace: StackTrace) -> None:
print(line_format.format(addr, word_val, identified))

start = end


def print_registers(prog: Program, regs: Dict[str, int], indent: int = 4) -> None:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For CLI convenience:

Suggested change
def print_registers(prog: Program, regs: Dict[str, int], indent: int = 4) -> None:
@takes_program_or_default
def print_registers(prog: Program, regs: Dict[str, int], indent: int = 4) -> None:

"""
Print a CPU register dump, in a format similar to that of crash
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Print a CPU register dump, in a format similar to that of crash
Print a CPU register dump, in a format similar to that of :manpage:`crash(8)`.


:param regs: a dictionary of registers, named in a similar way to the
dictionary returned by :py:class:`drgn.StackTrace.registers`.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this references a method, I think it could be :meth:, not :class::

Suggested change
dictionary returned by :py:class:`drgn.StackTrace.registers`.
dictionary returned by :meth:`drgn.StackTrace.registers()`.

:param indent: the number of spaces to indent the output
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we take the indentation string, similar to textwrap.indent(), rather than a number?

"""
if not prog.platform:
raise RuntimeError("Unknown platform & architecture")

widths = {}
formats = {}
if prog.platform.arch == Architecture.X86_64:
rows = [
("rip", "rsp", "rflags"),
("rax", "rbx", "rcx"),
("rdx", "rsi", "rdi"),
("rbp", "r8", "r9"),
("r10", "r11", "r12"),
("r13", "r14", "r15"),
("cs", "ss"),
]
widths = {"cs": 4, "ss": 4, "rflags": 8}
elif prog.platform.arch == Architecture.AARCH64:
rows = [
("pc", "lr", "sp"),
("x29", "x28", "x27"),
("x26", "x25", "x24"),
("x23", "x22", "x21"),
("x20", "x19", "x18"),
("x17", "x16", "x15"),
("x14", "x13", "x12"),
("x11", "x10", "x9"),
("x8", "x7", "x6"),
("x5", "x4", "x3"),
("x2", "x1", "x0"),
]
elif prog.platform.arch == Architecture.ARM:
rows = [
("pc", "lr", "sp", "fp"),
("r10", "r9", "r8"),
("r7", "r6", "r5", "r4"),
("r3", "r2", "r1", "r0"),
]
elif prog.platform.arch == Architecture.S390X:
rows = [
("pswm", "pswa", "r0"),
("r1", "r3", "r3"),
("r4", "r5", "r6"),
("r7", "r8", "r9"),
("r10", "r11", "r12"),
("r13", "r14", "r15"),
]
elif prog.platform.arch == Architecture.PPC64:
rows = [
("r0", "r1", "r2"),
("r3", "r4", "r5"),
("r6", "r7", "r8"),
("r9", "r10", "r11"),
("r12", "r13", "r14"),
("r15", "r16", "r17"),
("r18", "r19", "r20"),
("r21", "r22", "r23"),
("r24", "r25", "r26"),
("r27", "r28", "r29"),
("r30", "r31"),
# This departs from the crash format significantly. Crash recovers
# registers like CTR, XER, LR, and a few others. However, drgn
# doesn't provide these in stack frame registers. They are available
# in struct pt_regs.
("cr0", "cr1", "cr2", "cr3"),
("cr4", "cr5", "cr6", "cr7"),
]
widths = {f"cr{i}": 4 for i in range(8)}
formats = {f"cr{i}": "b" for i in range(8)}
else:
raise RuntimeError(f"Unsupported architecture: {prog.platform.arch}")

default_width = 16 if prog.platform.flags & PlatformFlags.IS_64_BIT else 8

for row in rows:
row_text = []
for i, reg in enumerate(row):
width = widths.get(reg, default_width)
if reg in regs:
value = regs[reg]
fmt = formats.get(reg, "x")
row_text.append(f"{reg.upper():>3s}: {value:0{width}{fmt}}")
else:
row_text.append(f"{reg.upper():>3s}: {'?' * width}")

print(f"{'':{indent}s}{' '.join(row_text)}")
17 changes: 16 additions & 1 deletion tests/linux_kernel/helpers/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
identify_address_all,
print_annotated_memory,
)
from drgn.helpers.common.stack import print_annotated_stack
from drgn.helpers.common.stack import print_annotated_stack, print_registers
from drgn.helpers.linux.common import (
IdentifiedSlabObject,
IdentifiedTaskStack,
Expand Down Expand Up @@ -269,3 +269,18 @@ def test_print_annotated_stack(self):
self.assertIn("slab object: drgn_test_small", printed_trace)
self.assertIn("[function symbol: schedule", printed_trace)
self.assertIn("schedule at ", printed_trace)


class TestPrintRegisters(LinuxKernelTestCase):
@skip_unless_have_stack_tracing
@skip_unless_have_test_kmod
def test_print_registers(self):
trace = self.prog.stack_trace(self.prog["drgn_test_kthread_pt_regs"])
# This is mostly a smoke test: we don't have any well-known registers,
# and we don't have any specific guarantees about the availability of
# registers. We can't even assert that all registers are present as hex
# strings in the output, because some may be printed in decimal, and
# others may be broken down into smaller fields.
f = io.StringIO()
with redirect_stdout(f):
print_registers(self.prog, trace[0].registers())