drgn/tests/linux_kernel/test_stack_trace.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

123 lines
4.3 KiB
Python
Raw Normal View History

# Copyright (c) Meta Platforms, Inc. and affiliates.
# SPDX-License-Identifier: LGPL-2.1-or-later
import os
import unittest
from drgn import Object, Program, cast
from drgn.helpers.linux.pid import find_task
from tests import assertReprPrettyEqualsStr
from tests.linux_kernel import (
LinuxKernelTestCase,
fork_and_sigwait,
setenv,
skip_unless_have_test_kmod,
)
from util import NORMALIZED_MACHINE_NAME
class TestStackTrace(LinuxKernelTestCase):
def _test_drgn_test_kthread_trace(self, trace):
for i, frame in enumerate(trace):
if frame.name == "drgn_test_kthread_fn3":
break
else:
self.fail("Couldn't find drgn_test_kthread_fn3 frame")
self.assertEqual(trace[i + 1].name, "drgn_test_kthread_fn2")
self.assertEqual(trace[i + 2].name, "drgn_test_kthread_fn")
@skip_unless_have_test_kmod
def test_by_task_struct(self):
self._test_drgn_test_kthread_trace(
self.prog.stack_trace(self.prog["drgn_test_kthread"])
)
def _test_by_pid(self, orc):
old_orc = int(os.environ.get("DRGN_PREFER_ORC_UNWINDER", "0")) != 0
with setenv("DRGN_PREFER_ORC_UNWINDER", "1" if orc else "0"):
if orc == old_orc:
prog = self.prog
else:
prog = Program()
prog.set_kernel()
self._load_debug_info(prog)
self._test_drgn_test_kthread_trace(
self.prog.stack_trace(self.prog["drgn_test_kthread"].pid)
)
@skip_unless_have_test_kmod
def test_by_pid_dwarf(self):
self._test_by_pid(False)
@unittest.skipUnless(
NORMALIZED_MACHINE_NAME == "x86_64",
f"{NORMALIZED_MACHINE_NAME} does not use ORC",
)
@skip_unless_have_test_kmod
def test_by_pid_orc(self):
self._test_by_pid(True)
@skip_unless_have_test_kmod
def test_local_variable(self):
for frame in self.prog.stack_trace(self.prog["drgn_test_kthread"]):
if frame.name == "drgn_test_kthread_fn3":
break
else:
self.fail("Couldn't find drgn_test_kthread_fn3 frame")
self.assertEqual(frame["a"], 1)
self.assertEqual(frame["b"], 2)
self.assertEqual(frame["c"], 3)
@skip_unless_have_test_kmod
def test_locals(self):
task = self.prog["drgn_test_kthread"]
stack_trace = self.prog.stack_trace(task)
for frame in stack_trace:
if frame.name == "drgn_test_kthread_fn3":
self.assertSetEqual(set(frame.locals()), {"a", "b", "c"})
break
else:
self.fail("Couldn't find drgn_test_kthread_fn3 frame")
def test_pt_regs(self):
# This won't unwind anything useful, but at least make sure it accepts
# a struct pt_regs.
self.prog.stack_trace(Object(self.prog, "struct pt_regs", value={}))
# Likewise, this is nonsense, but we should also accept a struct
# pt_regs *.
task = find_task(self.prog, os.getpid())
self.prog.stack_trace(cast("struct pt_regs *", task.stack))
def test_registers(self):
# Smoke test that we get at least one register and that
# StackFrame.registers() agrees with StackFrame.register().
with fork_and_sigwait() as pid:
trace = self.prog.stack_trace(pid)
have_registers = False
for frame in trace:
for name, value in frame.registers().items():
self.assertEqual(frame.register(name), value)
have_registers = True
self.assertTrue(have_registers)
def test_sp(self):
# Smoke test that the stack pointer register shows up in
# StackFrame.registers().
with fork_and_sigwait() as pid:
trace = self.prog.stack_trace(pid)
self.assertIn(trace[0].sp, trace[0].registers().values())
def test_prog(self):
self.assertEqual(
self.prog.stack_trace(Object(self.prog, "struct pt_regs", value={})).prog,
self.prog,
)
def test_stack__repr_pretty_(self):
with fork_and_sigwait() as pid:
trace = self.prog.stack_trace(pid)
assertReprPrettyEqualsStr(trace)
for frame in trace:
assertReprPrettyEqualsStr(frame)