liuhangbin/lock-trace

a Python tool that analyzes Linux kernel code to track lock usage

★ 0Forks 0PythonGitHub ↗Compare

README

Lock-Trace

Static analysis tool for kernel function call stack analysis and lock context checking.

Features

  1. Function Call Stack Tracing: Static analysis of function call relationships with upward caller tracing
  2. Lock Context Analysis: Check if function calls are protected by specified locks
  3. Cscope-based: Fast querying using cscope database

Requirements

  • Python 3.12+
  • cscope tool
  • Pre-built cscope database

Installation

Option 1: Python Package Installation

# Install dependencies
uv sync

# Development installation
uv sync --group dev

Option 2: Binary Compilation

Build a standalone binary executable:

# Install build dependencies
make install-dev

# Build binary executable
make binary

# Test the binary
./dist/lock-trace --help

# Install binary system-wide (optional)
make install-binary

Build System

The project includes a comprehensive Makefile with the following targets:

# Development
make install        # Install dependencies
make install-dev    # Install development dependencies
make test          # Run tests
make lint          # Run code linting
make format        # Format code

# Binary compilation
make binary        # Build standalone binary
make binary-portable  # Build portable binary with dependencies
make binary-debug     # Build debug binary

# Installation
make install-binary   # Install binary to /usr/local/bin
make uninstall-binary # Remove installed binary

# Cleaning
make clean         # Clean build artifacts
make clean-all     # Clean all generated files

# Utilities
make help          # Show all available targets
make version       # Show version information
make check-deps    # Check system dependencies

Usage

1. Prepare cscope database

Build cscope database in kernel source directory:

# Generate file list (optional)
find . -name "*.c" -o -name "*.h" > cscope.files

# Build database
cscope -Rkbq

2. Configuration Options

The tool supports flexible configuration for different project layouts:

  • Default: cscope.out and source code in current directory
  • Custom database directory: Use -d /path/to/database
  • Custom cscope.out file: Use -f /path/to/cscope.out
  • Custom source directory: Use -s /path/to/source

Examples:

# Default: everything in current directory
lock-trace callers schedule

# Custom database directory
lock-trace -d /path/to/kernel callers schedule  

# Custom paths (useful for complex project structures)
lock-trace -f /data/cscope.out -s /code/linux callers schedule

3. Display Options

All analysis commands (callers, callees, lock-check, lock-context, unprotected) support three display modes:

  • Default (no flags): Shows unique call chains in flat list format, removing duplicate paths for cleaner output
  • Tree mode (--tree, -t): Shows unique call chains in tree structure format for better visualization
  • Verbose mode (--verbose, -v): Shows all paths including duplicates (original behavior)

These options can be combined with any command for consistent analysis across different tools.

4. Basic Commands

Trace function callers

# See who calls the schedule function (default: unique call chains)
lock-trace callers schedule --max-depth 5

# Show as tree structure
lock-trace callers schedule --tree

# Show all paths including duplicates
lock-trace callers schedule --verbose

# Specify cscope database path
lock-trace -d /path/to/kernel/source callers schedule

# Use custom cscope.out file and source directory
lock-trace -f /path/to/cscope.out -s /path/to/source callers schedule

Trace function callees

# See what functions kmalloc calls (default: unique call chains)
lock-trace callees kmalloc --max-depth 3

# Show as tree structure
lock-trace callees kmalloc --tree

# Show all paths including duplicates
lock-trace callees kmalloc --verbose

Check lock protection

# Check if my_function is called under spinlock protection (default: unique call chains)
lock-trace lock-check my_function spin

# Show as tree structure
lock-trace lock-check my_function rcu --tree

# Show all paths including duplicates
lock-trace lock-check my_function mutex --verbose

Analyze lock context

# Analyze lock context of my_function (default: unique call chains)
lock-trace lock-context my_function

# Track specific locks only
lock-trace lock-context my_function --locks spin,rcu

# Show as tree structure
lock-trace lock-context my_function --tree

# Show all paths including duplicates
lock-trace lock-context my_function --verbose

Find unprotected calls

# Find my_function calls without required lock protection (default: unique call chains)
lock-trace unprotected my_function --required-locks spin,mutex

# Show as tree structure
lock-trace unprotected my_function --required-locks rcu --tree

# Show all paths including duplicates
lock-trace unprotected my_function --required-locks spin,rtnl --verbose

Get function statistics

# Get function call statistics
lock-trace stats my_function

Development

Using Makefile

# Run tests
make test

# Code formatting
make format

# Build binary package
make binary

# Build Python package
make package

# Run all CI checks
make ci

Manual commands

# Run tests
uv run pytest

# Code formatting
uv run black .
uv run ruff check .

# Build Python package
uv build

Supported Lock Types

  • Spinlock: spin_lock(), spin_unlock(), spin_lock_irq(), etc.
  • Mutex: mutex_lock(), mutex_unlock(), etc.
  • RW Lock: read_lock(), write_lock(), read_unlock(), write_unlock(), etc.
  • RCU: rcu_read_lock(), rcu_read_unlock(), etc.

Example Output

Call Stack Tracing

Default view (unique call chains)

Unique call chains to function 'schedule':
==================================================
  - init_task → kernel_thread → schedule
  - kthreadd → kthread_create → schedule  
  - worker_thread → process_one_work → schedule

Unique call chains found: 3

Tree view (--tree)

Call tree to function 'schedule':
==================================================
init_task
└── kernel_thread
    └── schedule
kthreadd
└── kthread_create
    └── schedule
worker_thread
└── process_one_work
    └── schedule

Unique call chains found: 3

Verbose view (--verbose)

All call paths to function 'schedule':
==================================================
  1: init_task → kernel_thread → schedule
  2: init_task → kernel_thread → schedule (duplicate)
  3: kthreadd → kthread_create → schedule
  4: worker_thread → process_one_work → schedule
  5: worker_thread → process_one_work → schedule (duplicate)

Total paths found: 5

Lock Protection Check

Default view

Lock protection analysis for function 'my_function' with lock 'my_lock':
======================================================================
Summary: 2/3 paths have lock protection

✓ PROTECTED: caller1 → my_function
✗ UNPROTECTED: caller2 → my_function  
✓ PROTECTED: caller3 → lock_func → my_function

Tree view (--tree)

Lock protection analysis for function 'my_function' with lock 'my_lock':
======================================================================
Summary: 2/3 paths have lock protection

Protection status tree:
caller1
└── my_function [✓ PROTECTED]
caller2
└── my_function [✗ UNPROTECTED]
caller3
└── lock_func
    └── my_function [✓ PROTECTED]

Lock Context Analysis

Default view

Lock context analysis for function 'my_function':
======================================================================
  1: caller1 → my_function
     Held locks: spin_lock_bh
     Lock operations:
       acquire spin_lock_bh (spinlock) in caller1

  2: caller2 → my_function
     Held locks: None

Call chains found: 2

Tree view (--tree)

Lock context analysis for function 'my_function':
======================================================================
Lock context tree:
caller1
└── my_function
caller2
└── my_function

Lock context details:
  1: Held locks: spin_lock_bh
  2: Held locks: None

Interactive Mode

Lock-trace supports an interactive shell mode that allows for easier exploration and iterative analysis of call stacks and lock contexts.

Basic Usage

# Enter interactive mode
lock-trace -i

# Interactive mode with initial configuration
lock-trace -i -m 5 -d ~/kernel/build

# Execute command then enter interactive mode
lock-trace -i callers schedule

# Full example with initial configuration and command
lock-trace -i -m 2 -d ~/git/iproute2 callers ip_addr_list

Interactive Commands

Once in interactive mode, you can use all analysis commands with additional features:

Analysis Commands

lock-trace> callers schedule
lock-trace> callees kmalloc  
lock-trace> lock-check my_function spin
lock-trace> lock-context my_function
lock-trace> lock-context my_function spin,mutex
lock-trace> unprotected my_function spin
lock-trace> stats my_function

Configuration Commands

lock-trace> config                     # Show current configuration
lock-trace> set max-depth 5           # Change max depth
lock-trace> set tree true            # Enable tree mode
lock-trace> set verbose false        # Disable verbose mode
lock-trace> set database-path /path/to/db  # Change database path

Result Tracking Commands

lock-trace> track                     # Show all recent results with indices
lock-trace> track 1 3 5              # Show specific results by index

Stack-based Filtering (New Feature)

Interactive mode includes powerful stack filtering capabilities:

# Results are automatically numbered for easy reference
lock-trace> callers ip_addr_list
Unique call chains to function 'ip_addr_list':
==================================================
  1. do_iplink → ipaddr_list_link → ipaddr_list_flush_or_save → ip_addr_list
  2. iplink_usage → do_ipaddr → ipaddr_list_flush_or_save → ip_addr_list
  3. main → do_ipaddr → ip_addr_list
  4. another_function → some_call → ip_addr_list

# Exclude specific stacks by number
lock-trace> exclude stack 2,3
Added stack exclusions: 2, 3

# Re-run command to see filtered results
lock-trace> callers ip_addr_list
Unique call chains to function 'ip_addr_list':
==================================================
  1. do_iplink → ipaddr_list_link → ipaddr_list_flush_or_save → ip_addr_list
  4. another_function → some_call → ip_addr_list

Filtered results: 2 stacks shown (excluded: 2, 3)

# Include only specific stacks
lock-trace> include stack 1,4
Added stack inclusions: 1, 4
Note: Only included stacks will be shown

# Clear all filters
lock-trace> clear stacks             # Clear stack filters
lock-trace> clear all               # Clear all filters

Filtering Commands

# Function and directory filtering
lock-trace> exclude functions debug_print,trace_func
lock-trace> exclude directories drivers,fs
lock-trace> include functions debug_print    # Remove from exclusions
lock-trace> include directories drivers      # Remove from exclusions

# Stack-based filtering
lock-trace> exclude stack 2,3              # Hide specific stacks
lock-trace> include stack 1,4              # Show only specific stacks

Other Commands

lock-trace> help                     # Show help
lock-trace> quit                     # Exit (also: exit, q)

Interactive Mode Benefits

  1. Persistent Configuration: Set parameters once and reuse across multiple commands
  2. Stack Filtering: Focus on specific call paths using numbered stacks
  3. Result Tracking: Reference previous results by index
  4. Iterative Analysis: Gradually refine analysis by adding/removing filters
  5. Easy Exploration: Quick switching between tree, verbose, and default modes

Example Interactive Session

$ lock-trace -i -m 2 -d ~/git/iproute2 callers ip_addr_list

🔒 Lock-trace Interactive Shell
==================================================
Current Configuration:
  Database Path: ~/git/iproute2
  Max Depth: 2
  Display Mode: Default

Executing initial command: callers ip_addr_list
Unique call chains to function 'ip_addr_list':
==================================================
  1. do_iplink → ipaddr_list_link → ipaddr_list_flush_or_save → ip_addr_list
  2. iplink_usage → do_ipaddr → ipaddr_list_flush_or_save → ip_addr_list

Unique call chains found: 2

lock-trace> track
Recent results from command: callers ip_addr_list
==================================================
  1: do_iplink → ipaddr_list_link → ipaddr_list_flush_or_save → ip_addr_list
  2: iplink_usage → do_ipaddr → ipaddr_list_flush_or_save → ip_addr_list

Total: 2 results

lock-trace> exclude stack 2
Added stack exclusions: 2

lock-trace> callers ip_addr_list
Unique call chains to function 'ip_addr_list':
==================================================
  1. do_iplink → ipaddr_list_link → ipaddr_list_flush_or_save → ip_addr_list

Filtered results: 1 stacks shown (excluded: 2)

lock-trace> set tree true
Tree mode enabled

lock-trace> callees ip_addr_list
Call tree from function 'ip_addr_list':
==================================================
ip_addr_list
├── print_addrinfo
└── fflush

lock-trace> quit
Goodbye!

Architecture

lock_trace/
├── __init__.py
├── cscope_interface.py    # Cscope interface wrapper
├── call_tracer.py         # Call stack tracer
├── lock_analyzer.py       # Lock context analyzer
├── cli.py                 # Command-line interface
└── interactive.py         # Interactive shell with stack filtering

License

GPLv3 License

Contributors

liuhangbin

Issues