Skip to content

Commit 6a15f9e

Browse files
committed
tests: lock: add proper tests for lock fallbacks
We've generated vmcores for every combination of OL, UEK, and architecture. Additionally, for combinations where there are multiple offsets, we've generated a vmcore on the oldest and newest version. Each vmcore is generated after first loading the "lockmod" kernel module, so that we can test loading the lock value out of the stack frame. The test is written to test the fallback on DWARF (in case the stack frame lookup fails) and CTF. Signed-off-by: Stephen Brennan <[email protected]>
1 parent c424dde commit 6a15f9e

File tree

1 file changed

+78
-1
lines changed

1 file changed

+78
-1
lines changed

tests/test_lock.py

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,84 @@
1-
# Copyright (c) 2023, Oracle and/or its affiliates.
1+
# Copyright (c) 2024, Oracle and/or its affiliates.
22
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
3+
import pytest
4+
from drgn.helpers.linux import for_each_task
5+
36
from drgn_tools import lock
7+
from drgn_tools import locking
8+
from drgn_tools.bt import func_name
49

510

11+
# the rwsem code does not support UEK4, no reason to add support
12+
@pytest.mark.skip_vmcore("*uek4*")
613
def test_locks(prog):
714
lock.scan_lock(prog, stack=True)
15+
16+
17+
@pytest.mark.skip_live
18+
@pytest.mark.vmcore("*lockmod*")
19+
def test_with_lockmod(prog, debuginfo_type):
20+
lockmod_threads = []
21+
for task in for_each_task(prog):
22+
if task.comm.string_().startswith(b"lockmod"):
23+
lockmod_threads.append(task)
24+
25+
if not lockmod_threads:
26+
pytest.skip("no lockmod kernel module found")
27+
28+
for task in lockmod_threads:
29+
print(f"PID {task.pid.value_()} COMM {task.comm.string_().decode()}")
30+
comm = task.comm.string_()
31+
if b"owner" in comm:
32+
# this owns the locks
33+
continue
34+
35+
if b"mutex" in comm:
36+
kind = "mutex"
37+
var = "lock"
38+
func_substr = "mutex_lock"
39+
elif b"rwsem" in comm:
40+
kind = "rw_semaphore"
41+
var = "sem"
42+
func_substr = "rwsem"
43+
else:
44+
kind = "semaphore"
45+
var = "sem"
46+
func_substr = "down"
47+
48+
# There can be multiple frames which may contain the lock, we will need
49+
# to try all of them.
50+
trace = prog.stack_trace(task)
51+
frames = []
52+
for frame in trace:
53+
fn = func_name(prog, frame)
54+
if fn and func_substr in fn:
55+
frames.append(frame)
56+
if not frames:
57+
pytest.fail("could not find relevant stack frame in lockmod")
58+
59+
# Test 1: if DWARF debuginfo is present, then this will try to use the
60+
# variable name to access the lock. Otherwise, for CTF we will fall back
61+
# to using the stack offsets.
62+
for frame in frames:
63+
value = locking.get_lock_from_frame(prog, task, frame, kind, var)
64+
if value is not None:
65+
break
66+
else:
67+
pytest.fail(f"Could not find lock using {debuginfo_type}")
68+
69+
if debuginfo_type == "ctf":
70+
# The second test is redundant, skip it.
71+
continue
72+
73+
# Test 2: if DWARF debuginfo is present, we can actually give a fake
74+
# variable name! This will force the code to fall back to the stack
75+
# offsets, which should still work. This essentially simulates the
76+
# possibility of a DWARF unwind where we get an absent object.
77+
for frame in frames:
78+
value = locking.get_lock_from_frame(
79+
prog, task, frame, kind, "invalid variable name"
80+
)
81+
if value is not None:
82+
break
83+
else:
84+
pytest.fail("Could not find lock using fallback method")

0 commit comments

Comments
 (0)