2021-11-21 23:59:44 +00:00
|
|
|
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
2022-11-02 00:05:16 +00:00
|
|
|
# SPDX-License-Identifier: LGPL-2.1-or-later
|
2020-05-15 23:13:02 +01:00
|
|
|
|
2019-11-19 00:42:30 +00:00
|
|
|
import os
|
2022-10-12 05:40:20 +01:00
|
|
|
import unittest
|
2019-11-19 00:42:30 +00:00
|
|
|
|
2021-03-16 22:39:37 +00:00
|
|
|
from drgn import Object, Program, cast
|
2019-11-19 00:42:30 +00:00
|
|
|
from drgn.helpers.linux.pid import find_task
|
2022-08-15 10:47:02 +01:00
|
|
|
from tests import assertReprPrettyEqualsStr
|
2022-05-27 00:42:20 +01:00
|
|
|
from tests.linux_kernel import (
|
|
|
|
LinuxKernelTestCase,
|
|
|
|
fork_and_sigwait,
|
|
|
|
setenv,
|
|
|
|
skip_unless_have_test_kmod,
|
|
|
|
)
|
2022-10-12 05:40:20 +01:00
|
|
|
from util import NORMALIZED_MACHINE_NAME
|
2019-11-19 00:42:30 +00:00
|
|
|
|
|
|
|
|
2022-03-04 23:40:51 +00:00
|
|
|
class TestStackTrace(LinuxKernelTestCase):
|
2022-11-07 06:04:58 +00:00
|
|
|
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")
|
2022-06-27 05:59:12 +01:00
|
|
|
|
2022-11-07 06:04:58 +00:00
|
|
|
@skip_unless_have_test_kmod
|
2019-11-19 00:42:30 +00:00
|
|
|
def test_by_task_struct(self):
|
2022-11-07 06:04:58 +00:00
|
|
|
self._test_drgn_test_kthread_trace(
|
|
|
|
self.prog.stack_trace(self.prog["drgn_test_kthread"])
|
|
|
|
)
|
2019-11-19 00:42:30 +00:00
|
|
|
|
2021-03-16 22:39:37 +00:00
|
|
|
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()
|
2022-05-16 21:19:02 +01:00
|
|
|
self._load_debug_info(prog)
|
2022-11-07 06:04:58 +00:00
|
|
|
self._test_drgn_test_kthread_trace(
|
|
|
|
self.prog.stack_trace(self.prog["drgn_test_kthread"].pid)
|
|
|
|
)
|
2021-03-16 22:39:37 +00:00
|
|
|
|
2022-11-07 06:04:58 +00:00
|
|
|
@skip_unless_have_test_kmod
|
2021-03-16 22:39:37 +00:00
|
|
|
def test_by_pid_dwarf(self):
|
|
|
|
self._test_by_pid(False)
|
|
|
|
|
2022-10-12 05:40:20 +01:00
|
|
|
@unittest.skipUnless(
|
|
|
|
NORMALIZED_MACHINE_NAME == "x86_64",
|
|
|
|
f"{NORMALIZED_MACHINE_NAME} does not use ORC",
|
|
|
|
)
|
2022-11-07 06:04:58 +00:00
|
|
|
@skip_unless_have_test_kmod
|
2021-03-16 22:39:37 +00:00
|
|
|
def test_by_pid_orc(self):
|
|
|
|
self._test_by_pid(True)
|
2019-11-19 00:42:30 +00:00
|
|
|
|
2022-11-07 06:04:58 +00:00
|
|
|
@skip_unless_have_test_kmod
|
2021-06-02 21:17:41 +01:00
|
|
|
def test_local_variable(self):
|
2022-11-07 06:04:58 +00:00
|
|
|
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")
|
2021-06-02 21:17:41 +01:00
|
|
|
|
2019-11-19 00:42:30 +00:00
|
|
|
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))
|
2021-01-28 23:49:21 +00:00
|
|
|
|
|
|
|
def test_registers(self):
|
|
|
|
# Smoke test that we get at least one register and that
|
|
|
|
# StackFrame.registers() agrees with StackFrame.register().
|
tests: fix race in tests using fork_and_pause()+proc_blocked()
Every once in awhile, a tests.linux_kernel.test_stack_trace test fails
with a "cannot unwind stack of running task" error or without being able
to find pause or ppoll in the trace. I previously attempted to fix this
in commit a5845e63d48d ("tests: fix race condition in stack trace
tests"). However, now I'm seeing the forked process try to open
/etc/ld.so.cache before pausing for some reason. Since this can block,
proc_blocked() might return true before the process is actually in
pause(). If we get a stack trace while the process is in the wrong
syscall or in between calling another syscall and pause(), we will fail
as mentioned above.
Fix it in a few parts:
1. Use sigwait() instead of pause(), which I doubt is used anywhere else
while forking and won't get woken up by stray signals.
2. Wait until /proc/$pid/syscall shows that we're blocked in
rt_sigtimedwait or rt_sigtimedwait_time64 specifically.
3. Replace the manual fork_and_pause(), wait_until(proc_blocked, pid),
os.kill(pid, signal.SIGKILL), os.waitpid(pid, 0) sequence with a
context manager that takes care of all of that, fork_and_sigwait().
Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-09-30 22:58:12 +01:00
|
|
|
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)
|
2022-08-11 22:42:21 +01:00
|
|
|
|
2022-11-23 00:51:44 +00:00
|
|
|
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())
|
|
|
|
|
2022-08-11 22:42:21 +01:00
|
|
|
def test_prog(self):
|
|
|
|
self.assertEqual(
|
|
|
|
self.prog.stack_trace(Object(self.prog, "struct pt_regs", value={})).prog,
|
|
|
|
self.prog,
|
|
|
|
)
|
2022-08-15 10:47:02 +01:00
|
|
|
|
|
|
|
def test_stack__repr_pretty_(self):
|
tests: fix race in tests using fork_and_pause()+proc_blocked()
Every once in awhile, a tests.linux_kernel.test_stack_trace test fails
with a "cannot unwind stack of running task" error or without being able
to find pause or ppoll in the trace. I previously attempted to fix this
in commit a5845e63d48d ("tests: fix race condition in stack trace
tests"). However, now I'm seeing the forked process try to open
/etc/ld.so.cache before pausing for some reason. Since this can block,
proc_blocked() might return true before the process is actually in
pause(). If we get a stack trace while the process is in the wrong
syscall or in between calling another syscall and pause(), we will fail
as mentioned above.
Fix it in a few parts:
1. Use sigwait() instead of pause(), which I doubt is used anywhere else
while forking and won't get woken up by stray signals.
2. Wait until /proc/$pid/syscall shows that we're blocked in
rt_sigtimedwait or rt_sigtimedwait_time64 specifically.
3. Replace the manual fork_and_pause(), wait_until(proc_blocked, pid),
os.kill(pid, signal.SIGKILL), os.waitpid(pid, 0) sequence with a
context manager that takes care of all of that, fork_and_sigwait().
Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-09-30 22:58:12 +01:00
|
|
|
with fork_and_sigwait() as pid:
|
|
|
|
trace = self.prog.stack_trace(pid)
|
|
|
|
assertReprPrettyEqualsStr(trace)
|
|
|
|
for frame in trace:
|
|
|
|
assertReprPrettyEqualsStr(frame)
|