All of lore.kernel.org
 help / color / mirror / Atom feed
From: Namhyung Kim <namhyung@kernel.org>
To: Ian Rogers <irogers@google.com>
Cc: acme@kernel.org, adrian.hunter@intel.com, ddhariwal@google.com,
	eranian@google.com, james.clark@linaro.org, jolsa@kernel.org,
	linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org,
	mingo@redhat.com, peterz@infradead.org, rcu@vger.kernel.org
Subject: Re: [PATCH v6 05/11] perf bpf-filter: Add lock_held() sample filter for global mutexes
Date: Thu, 22 Oct 2026 15:55:06 -0700	[thread overview]
Message-ID: <atqUSrmtsfCojGcA@google.com> (raw)
In-Reply-To: <20261022203003.1779152-6-irogers@google.com>

On Thu, Oct 22, 2026 at 01:29:57PM -0700, Ian Rogers wrote:
> Add BPF sample filter predicates lock_held(<lock>),
> lock_held_read(<lock>) / lock_held_shared(<lock>), and
> lock_held_write(<lock>) / lock_held_exclusive(<lock>) (as well as ':R'
> and ':W' suffixes on <lock>) to allow recording perf event samples only
> when a specified kernel lock is held (or not held with '!').
> 
> In this initial step, support global/static kernel mutex symbols (e.g.
> lock_held(cgroup_mutex)) resolved via /proc/kallsyms. On sample
> overflow, perf_sample_filter and perf_tp_sample_filter inspect the
> mutex's owner field directly in kernel memory via get_lock_owner()
> without attaching any BPF fentry/fexit hooks.
> 
> Subsequent commits extend lock_held() to other kernel lock types,
> per-process and semi-global locks, slab cache locks, and RCU/SRCU.
> 
> Signed-off-by: Ian Rogers <irogers@google.com>
> ---
>  tools/perf/Documentation/perf-record.txt     |  16 +-
>  tools/perf/util/bpf-filter.c                 | 350 ++++++++++++++++++-
>  tools/perf/util/bpf-filter.h                 |  13 +
>  tools/perf/util/bpf-filter.l                 |  31 +-
>  tools/perf/util/bpf-filter.y                 |  47 ++-
>  tools/perf/util/bpf_skel/sample-filter.h     |  36 ++
>  tools/perf/util/bpf_skel/sample_filter.bpf.c |  54 +++
>  tools/perf/util/parse-events.c               |   2 +
>  8 files changed, 530 insertions(+), 19 deletions(-)
> 
> diff --git a/tools/perf/Documentation/perf-record.txt b/tools/perf/Documentation/perf-record.txt
> index fcf1ea62bf1c..445cc98a6c33 100644
> --- a/tools/perf/Documentation/perf-record.txt
> +++ b/tools/perf/Documentation/perf-record.txt
> @@ -210,7 +210,21 @@ OPTIONS
>  	  ip, id, tid, pid, cpu, time, addr, period, txn, weight, phys_addr,
>  	  code_pgsz, data_pgsz, weight1, weight2, weight3, ins_lat, retire_lat,
>  	  p_stage_cyc, mem_op, mem_lvl, mem_snoop, mem_remote, mem_lock,
> -	  mem_dtlb, mem_blk, mem_hops, uid, gid
> +	  mem_dtlb, mem_blk, mem_hops, uid, gid, cgroup, lock_held(<lock>),
> +	  lock_held_read(<lock>), lock_held_shared(<lock>),
> +	  lock_held_write(<lock>), lock_held_exclusive(<lock>)
> +
> +	The 'lock_held(<lock>)' predicate (or '!lock_held(<lock>)') filters
> +	samples based on whether the specified kernel lock is currently held
> +	by the kernel thread.  To differentiate between shared/read holding
> +	and exclusive/write holding, use 'lock_held_read(<lock>)' (alias
> +	'lock_held_shared(<lock>)') or 'lock_held_write(<lock>)' (alias
> +	'lock_held_exclusive(<lock>)'), or append ':R' or ':W' to <lock>.
> +	<lock> can be a kernel mutex symbol name (e.g.
> +	'lock_held(cgroup_mutex)').  When filtering on a mutex symbol
> +	('*_mutex'), 'perf_sample_filter' inspects the lock's 'owner' field
> +	directly in kernel memory on each sample without attaching any BPF
> +	fentry/fexit hooks.
>  
>  	The <operator> can be one of:
>  	  ==, !=, >, >=, <, <=, &
> diff --git a/tools/perf/util/bpf-filter.c b/tools/perf/util/bpf-filter.c
> index 0d3c700d3dcd..639db6c03137 100644
> --- a/tools/perf/util/bpf-filter.c
> +++ b/tools/perf/util/bpf-filter.c
> @@ -43,6 +43,9 @@
>  #include <errno.h>
>  #include <stdio.h>
>  #include <stdlib.h>
> +#include <stdbool.h>
> +#include <stddef.h>
> +#include <string.h>
>  #include <fcntl.h>
>  #include <unistd.h>
>  #include <sys/ioctl.h>
> @@ -50,14 +53,19 @@
>  
>  #include <bpf/bpf.h>
>  #include <linux/err.h>
> +#include <linux/kernel.h>
>  #include <linux/list.h>
> +#include <linux/string.h>
> +#include <linux/zalloc.h>
>  #include <api/fs/fs.h>
>  #include <internal/xyarray.h>
>  #include <perf/threadmap.h>
> +#include <symbol/kallsyms.h>
>  
>  #include "util/cap.h"
>  #include "util/debug.h"
>  #include "util/evsel.h"
> +#include "util/symbol_conf.h"
>  #include "util/target.h"
>  #include "util/thread_map.h"
>  #include "util/bpf-utils.h"
> @@ -126,7 +134,8 @@ static int check_sample_flags(struct evsel *evsel, struct perf_bpf_filter_expr *
>  {
>  	const struct perf_sample_info *info;
>  
> -	if (expr->term == PBF_TERM_UID || expr->term == PBF_TERM_GID) {
> +	if (expr->term == PBF_TERM_UID || expr->term == PBF_TERM_GID ||
> +	    expr->term == PBF_TERM_LOCK_HELD) {
>  		/* Not dependent on the sample_type as computed from a BPF helper. */
>  		return 0;
>  	}
> @@ -169,9 +178,263 @@ static int check_sample_flags(struct evsel *evsel, struct perf_bpf_filter_expr *
>  	return -1;
>  }
>  
> +static int split_lock_name_mode(const char *name, int expr_part,
> +				char *base_buf, size_t buf_sz,
> +				bool *has_amp, unsigned int *mode_out)
> +{
> +	const char *bare;
> +	size_t len;
> +	unsigned int expr_mode = expr_part & PBF_LOCK_MODE_MASK;
> +	unsigned int suffix_mode = 0;
> +
> +	if (!name || !*name)
> +		return -EINVAL;
> +
> +	*has_amp = (name[0] == '&');
> +	bare = *has_amp ? name + 1 : name;
> +	len = strlen(bare);
> +	if (len == 0)
> +		return -EINVAL;
> +
> +	if (len > 2 && !strcmp(bare + len - 2, ":R")) {
> +		suffix_mode = PBF_LOCK_MODE_READ;
> +		len -= 2;
> +	} else if (len > 2 && !strcmp(bare + len - 2, ":W")) {
> +		suffix_mode = PBF_LOCK_MODE_WRITE;
> +		len -= 2;
> +	}
> +
> +	if (len == 0 || len >= buf_sz)
> +		return -EINVAL;
> +
> +	if (expr_mode && expr_mode != PBF_LOCK_MODE_ANY &&
> +	    suffix_mode && expr_mode != suffix_mode)
> +		return -EINVAL;
> +
> +	memcpy(base_buf, bare, len);
> +	base_buf[len] = '\0';
> +
> +	if (suffix_mode)
> +		*mode_out = suffix_mode;
> +	else if (expr_mode)
> +		*mode_out = expr_mode;
> +	else
> +		*mode_out = PBF_LOCK_MODE_ANY;
> +
> +	return 0;
> +}
> +
> +struct lock_sym_cb_args {
> +	const char *name;
> +	u64 addr;
> +	bool in_text;
> +	bool in_data;
> +	bool found;
> +};
> +
> +static int find_lock_sym_cb(void *arg, const char *name, char type, u64 start)
> +{
> +	struct lock_sym_cb_args *args = arg;
> +
> +	if (!strcmp(name, "_text") || !strcmp(name, "_stext") ||
> +	    !strcmp(name, "_sinittext")) {
> +		args->in_text = true;
> +		return 0;
> +	}
> +	if (!strcmp(name, "_etext") || !strcmp(name, "_einittext")) {
> +		args->in_text = false;
> +		return 0;
> +	}
> +	if (!strcmp(name, "_sdata") || !strcmp(name, "__bss_start")) {
> +		args->in_text = false;
> +		args->in_data = true;
> +		return 0;
> +	}
> +	if (!strcmp(name, "__bss_stop")) {
> +		args->in_data = false;
> +		return 0;
> +	}
> +
> +	/*
> +	 * Ignore kernel text functions, module text functions, and
> +	 * absolute/undefined symbols. On CONFIG_LTO_CLANG kernels, vmlinux
> +	 * .data symbols between _sdata and _edata may have kallsyms type
> +	 * 'T'/'t', so allow 'T'/'t' within vmlinux data/bss (args->in_data).
> +	 */
> +	if (args->in_text || type == 'A' || type == 'a' ||
> +	    type == 'U' || type == 'u' ||
> +	    (!args->in_data && kallsyms__is_function(type)))
> +		return 0;
> +
> +	if (!strcmp(name, args->name)) {
> +		args->found = true;
> +		args->addr = start;
> +		if (start != 0)
> +			return 1;
> +	}
> +	return 0;
> +}
> +
> +static int find_kernel_lock_sym(const char *name, u64 *addr)
> +{
> +	struct lock_sym_cb_args args = {
> +		.name = name,
> +	};
> +	const char *kallsyms_filename = "/proc/kallsyms";
> +
> +	if (symbol_conf.kallsyms_name != NULL)
> +		kallsyms_filename = symbol_conf.kallsyms_name;
> +
> +	if (kallsyms__parse(kallsyms_filename, &args, find_lock_sym_cb) < 0)
> +		return -ENOENT;
> +
> +	if (!args.found)
> +		return -ENOENT;
> +
> +	if (args.addr == 0) {
> +		pr_err("Error: Kernel symbol '%s' address is 0 (check permissions / kptr_restrict)\n",
> +		       name);
> +		return -EACCES;
> +	}
> +
> +	*addr = args.addr;
> +	return 0;
> +}
> +
> +static void prescan_one_lock_expr(struct perf_bpf_filter_expr *expr,
> +				  bool *has_lock_filter)
> +{
> +	if (expr->term != PBF_TERM_LOCK_HELD || !expr->lock_name)
> +		return;
> +
> +	*has_lock_filter = true;
> +}
> +
> +static void prescan_lock_filters(struct evsel *evsel, bool *has_lock_filter)
> +{
> +	struct perf_bpf_filter_expr *expr, *group;
> +
> +	*has_lock_filter = false;
> +
> +	list_for_each_entry(expr, &evsel->bpf_filters, list) {
> +		prescan_one_lock_expr(expr, has_lock_filter);
> +		if (expr->op == PBF_OP_GROUP_BEGIN) {
> +			list_for_each_entry(group, &expr->groups, list)
> +				prescan_one_lock_expr(group, has_lock_filter);
> +		}
> +	}
> +}
> +
> +static int normalize_lock_expr_op(const struct perf_bpf_filter_expr *expr,
> +				  enum perf_bpf_filter_op *op_out)
> +{
> +	switch (expr->op) {
> +	case PBF_OP_EQ:
> +		if (expr->val == 1) {
> +			*op_out = PBF_OP_EQ;
> +			return 0;
> +		}
> +		if (expr->val == 0) {
> +			*op_out = PBF_OP_NEQ;
> +			return 0;
> +		}
> +		break;
> +	case PBF_OP_NEQ:
> +		if (expr->val == 0) {
> +			*op_out = PBF_OP_EQ;
> +			return 0;
> +		}
> +		if (expr->val == 1) {
> +			*op_out = PBF_OP_NEQ;
> +			return 0;
> +		}
> +		break;
> +	case PBF_OP_GT:
> +		if (expr->val == 0) {
> +			*op_out = PBF_OP_EQ;
> +			return 0;
> +		}
> +		break;
> +	case PBF_OP_GE:
> +		if (expr->val == 1) {
> +			*op_out = PBF_OP_EQ;
> +			return 0;
> +		}
> +		break;
> +	case PBF_OP_LT:
> +		if (expr->val == 1) {
> +			*op_out = PBF_OP_NEQ;
> +			return 0;
> +		}
> +		break;
> +	case PBF_OP_LE:
> +		if (expr->val == 0) {
> +			*op_out = PBF_OP_NEQ;
> +			return 0;
> +		}
> +		break;
> +	case PBF_OP_AND:
> +		if (expr->val & 1) {
> +			*op_out = PBF_OP_EQ;
> +			return 0;
> +		}
> +		break;
> +	case PBF_OP_GROUP_BEGIN:
> +	case PBF_OP_GROUP_END:
> +	case PBF_OP_DONE:
> +	default:
> +		break;
> +	}
> +	pr_err("Error: Invalid comparison for boolean lock_held(%s) predicate\n",
> +	       expr->lock_name ?: "");
> +	return -EINVAL;
> +}
> +
> +static int resolve_lock_expr(struct perf_bpf_filter_expr *expr,
> +			     struct perf_bpf_filter_entry *entry)
> +{
> +	const char *name = expr->lock_name;
> +	char base[128];
> +	bool has_amp;
> +	unsigned int mode;
> +	u64 addr = 0;
> +	int ret;
> +
> +	ret = normalize_lock_expr_op(expr, &entry->op);
> +	if (ret < 0)
> +		return ret;
> +
> +	if (split_lock_name_mode(name, expr->part, base, sizeof(base),
> +				 &has_amp, &mode) < 0 || has_amp) {
> +		pr_err("Error: Invalid kernel lock filter '%s'\n", name ?: "");
> +		return -EINVAL;
> +	}
> +
> +	if (str_ends_with(base, "mutex") &&
> +	    !str_ends_with(base, "rt_mutex") &&
> +	    !str_ends_with(base, "rtmutex")) {

Is it just checking the symbol name ends with "mutex" or so?  It seems
risky and limited, can we check BTF to verify if it's really a mutex?

Thanks,
Namhyung


> +		ret = find_kernel_lock_sym(base, &addr);
> +		if (ret == 0) {
> +			if (!(mode & PBF_LOCK_MODE_WRITE)) {
> +				pr_err("Error: Lock '%s' is exclusive and does not support read/shared mode\n",
> +				       name);
> +				return -EINVAL;
> +			}
> +			entry->part = PBF_LOCK_ADDR | mode | PBF_LOCK_DIRECT_MUTEX;
> +			entry->value = addr;
> +			return 0;
> +		}
> +		if (ret == -EACCES)
> +			return ret;
> +	}
> +
> +	pr_err("Error: Cannot resolve kernel lock '%s'\n", name);
> +	return -EINVAL;
> +}
> +
>  static int get_filter_entries(struct evsel *evsel, struct perf_bpf_filter_entry *entry)
>  {
> -	int i = 0;
> +	int i = 0, ret;
>  	struct perf_bpf_filter_expr *expr;
>  
>  	list_for_each_entry(expr, &evsel->bpf_filters, list) {
> @@ -185,6 +448,11 @@ static int get_filter_entries(struct evsel *evsel, struct perf_bpf_filter_entry
>  		entry[i].part = expr->part;
>  		entry[i].term = expr->term;
>  		entry[i].value = expr->val;
> +		if (expr->term == PBF_TERM_LOCK_HELD) {
> +			ret = resolve_lock_expr(expr, &entry[i]);
> +			if (ret < 0)
> +				return ret;
> +		}
>  		i++;
>  
>  		if (expr->op == PBF_OP_GROUP_BEGIN) {
> @@ -198,6 +466,11 @@ static int get_filter_entries(struct evsel *evsel, struct perf_bpf_filter_entry
>  				entry[i].part = group->part;
>  				entry[i].term = group->term;
>  				entry[i].value = group->val;
> +				if (group->term == PBF_TERM_LOCK_HELD) {
> +					ret = resolve_lock_expr(group, &entry[i]);
> +					if (ret < 0)
> +						return ret;
> +				}
>  				i++;
>  			}
>  
> @@ -440,20 +713,24 @@ int perf_bpf_filter__prepare(struct evsel *evsel, struct target *target)
>  	struct perf_bpf_filter_entry *entry;
>  	bool needs_idx_hash = !target__has_cpu(target);
>  	bool is_tracepoint = (evsel->core.attr.type == PERF_TYPE_TRACEPOINT);
> +	bool has_lock_filter = false;
>  
>  	entry = calloc(MAX_FILTERS, sizeof(*entry));
>  	if (entry == NULL)
>  		return -1;
>  
> -	ret = get_filter_entries(evsel, entry);
> -	if (ret < 0) {
> -		pr_err("Failed to process filter entries\n");
> +	prescan_lock_filters(evsel, &has_lock_filter);
> +
> +	if ((has_lock_filter || is_tracepoint) && geteuid() != 0 &&
> +	    !perf_cap__capable(CAP_BPF)) {
> +		pr_err("Error: BPF filter requires CAP_BPF capability\n");
> +		ret = -EPERM;
>  		goto err;
>  	}
>  
> -	if (is_tracepoint && geteuid() != 0 && !perf_cap__capable(CAP_BPF)) {
> -		pr_err("Error: Tracepoint BPF filter requires CAP_BPF capability\n");
> -		ret = -EPERM;
> +	ret = get_filter_entries(evsel, entry);
> +	if (ret < 0) {
> +		pr_err("Failed to process filter entries\n");
>  		goto err;
>  	}
>  
> @@ -513,6 +790,9 @@ int perf_bpf_filter__prepare(struct evsel *evsel, struct target *target)
>  		goto err;
>  	}
>  
> +	if (has_lock_filter)
> +		skel->bss->lock_enabled = 1;
> +
>  	i = 0;
>  	fd = bpf_map__fd(skel->maps.filters);
>  
> @@ -571,6 +851,21 @@ int perf_bpf_filter__prepare(struct evsel *evsel, struct target *target)
>  	return ret;
>  }
>  
> +void perf_bpf_filter_expr__delete(struct perf_bpf_filter_expr *expr)
> +{
> +	struct perf_bpf_filter_expr *group, *tmp;
> +
> +	if (!expr)
> +		return;
> +
> +	list_for_each_entry_safe(group, tmp, &expr->groups, list) {
> +		list_del(&group->list);
> +		perf_bpf_filter_expr__delete(group);
> +	}
> +	zfree(&expr->lock_name);
> +	free(expr);
> +}
> +
>  int perf_bpf_filter__destroy(struct evsel *evsel)
>  {
>  	struct perf_bpf_filter_expr *expr, *tmp;
> @@ -578,7 +873,7 @@ int perf_bpf_filter__destroy(struct evsel *evsel)
>  
>  	list_for_each_entry_safe(expr, tmp, &evsel->bpf_filters, list) {
>  		list_del(&expr->list);
> -		free(expr);
> +		perf_bpf_filter_expr__delete(expr);
>  	}
>  	sample_filter_bpf__destroy(evsel->bpf_skel);
>  
> @@ -630,7 +925,7 @@ struct perf_bpf_filter_expr *perf_bpf_filter_expr__new(enum perf_bpf_filter_term
>  {
>  	struct perf_bpf_filter_expr *expr;
>  
> -	expr = malloc(sizeof(*expr));
> +	expr = zalloc(sizeof(*expr));
>  	if (expr != NULL) {
>  		expr->term = term;
>  		expr->part = part;
> @@ -641,6 +936,28 @@ struct perf_bpf_filter_expr *perf_bpf_filter_expr__new(enum perf_bpf_filter_term
>  	return expr;
>  }
>  
> +struct perf_bpf_filter_expr *perf_bpf_filter_expr__new_lock(const char *lock_name,
> +							    int mode,
> +							    enum perf_bpf_filter_op op,
> +							    unsigned long val)
> +{
> +	struct perf_bpf_filter_expr *expr;
> +
> +	if (!lock_name || !*lock_name)
> +		return NULL;
> +
> +	expr = perf_bpf_filter_expr__new(PBF_TERM_LOCK_HELD, mode, op, val);
> +	if (!expr)
> +		return NULL;
> +
> +	expr->lock_name = strdup(lock_name);
> +	if (!expr->lock_name) {
> +		free(expr);
> +		return NULL;
> +	}
> +	return expr;
> +}
> +
>  static bool check_bpf_filter_capable(void)
>  {
>  	int fd;
> @@ -661,14 +978,11 @@ static bool check_bpf_filter_capable(void)
>  	return false;
>  }
>  
> -int perf_bpf_filter__parse(struct list_head *expr_head, const char *str)
> +int __perf_bpf_filter__parse(struct list_head *expr_head, const char *str)
>  {
>  	YY_BUFFER_STATE buffer;
>  	int ret;
>  
> -	if (!check_bpf_filter_capable())
> -		return -EPERM;
> -
>  	buffer = perf_bpf_filter__scan_string(str);
>  
>  	ret = perf_bpf_filter_parse(expr_head);
> @@ -680,6 +994,14 @@ int perf_bpf_filter__parse(struct list_head *expr_head, const char *str)
>  	return ret;
>  }
>  
> +int perf_bpf_filter__parse(struct list_head *expr_head, const char *str)
> +{
> +	if (!check_bpf_filter_capable())
> +		return -EPERM;
> +
> +	return __perf_bpf_filter__parse(expr_head, str);
> +}
> +
>  /*
>   * perf_bpf_filter__pin() and perf_bpf_filter__unpin() only load
>   * perf_sample_filter and disable autoload on other programs in the skeleton
> diff --git a/tools/perf/util/bpf-filter.h b/tools/perf/util/bpf-filter.h
> index 818c554b91b2..fda95ff226b3 100644
> --- a/tools/perf/util/bpf-filter.h
> +++ b/tools/perf/util/bpf-filter.h
> @@ -14,6 +14,13 @@ struct perf_bpf_filter_expr {
>  	int part;
>  	enum perf_bpf_filter_term term;
>  	unsigned long val;
> +	/*
> +	 * Raw lock descriptor string for PBF_TERM_LOCK_HELD expressions
> +	 * (e.g. "&cgroup_mutex", "mmap_lock", "&dentry", "rwsem:R").
> +	 * Resolved into `entry.part` and `entry.target` during
> +	 * perf_bpf_filter__prepare().
> +	 */
> +	char *lock_name;
>  };
>  
>  struct evsel;
> @@ -27,6 +34,12 @@ struct perf_bpf_filter_expr *perf_bpf_filter_expr__new(enum perf_bpf_filter_term
>  						       int part,
>  						       enum perf_bpf_filter_op op,
>  						       unsigned long val);
> +struct perf_bpf_filter_expr *perf_bpf_filter_expr__new_lock(const char *lock_name,
> +							    int mode,
> +							    enum perf_bpf_filter_op op,
> +							    unsigned long val);
> +void perf_bpf_filter_expr__delete(struct perf_bpf_filter_expr *expr);
> +int __perf_bpf_filter__parse(struct list_head *expr_head, const char *str);
>  int perf_bpf_filter__parse(struct list_head *expr_head, const char *str);
>  int perf_bpf_filter__prepare(struct evsel *evsel, struct target *target);
>  int perf_bpf_filter__destroy(struct evsel *evsel);
> diff --git a/tools/perf/util/bpf-filter.l b/tools/perf/util/bpf-filter.l
> index 1be9df6550fc..b4022110f1e5 100644
> --- a/tools/perf/util/bpf-filter.l
> +++ b/tools/perf/util/bpf-filter.l
> @@ -1,9 +1,12 @@
>  %option prefix="perf_bpf_filter_"
>  %option noyywrap
>  
> +%x LOCK_ARG
> +
>  %{
>  #include <stdio.h>
>  #include <stdlib.h>
> +#include <string.h>
>  #include <linux/perf_event.h>
>  
>  #include "bpf-filter.h"
> @@ -76,7 +79,7 @@ static int path_or_error(void)
>  num_dec		[0-9]+
>  num_hex		0[Xx][0-9a-fA-F]+
>  space		[ \t]+
> -path		[^ \t\n,]+
> +path		[^ \t\n,()!=>&|<]+
>  ident		[_a-zA-Z][_a-zA-Z0-9]+
>  
>  %%
> @@ -116,7 +119,31 @@ mem_hops	{ return sample_part(PBF_TERM_DATA_SRC, 8); }
>  uid		{ return sample(PBF_TERM_UID); }
>  gid		{ return sample(PBF_TERM_GID); }
>  cgroup		{ return sample_path(PBF_TERM_CGROUP); }
> -
> +lock_held[ \t]*"("	{
> +				BEGIN(LOCK_ARG);
> +				perf_bpf_filter_lval.num = PBF_LOCK_MODE_ANY;
> +				return BFT_LOCK_HELD;
> +			}
> +(lock_held_read|lock_held_shared)[ \t]*"("	{
> +				BEGIN(LOCK_ARG);
> +				perf_bpf_filter_lval.num = PBF_LOCK_MODE_READ;
> +				return BFT_LOCK_HELD;
> +			}
> +(lock_held_write|lock_held_exclusive)[ \t]*"("	{
> +				BEGIN(LOCK_ARG);
> +				perf_bpf_filter_lval.num = PBF_LOCK_MODE_WRITE;
> +				return BFT_LOCK_HELD;
> +			}
> +
> +<LOCK_ARG>{space}	{ }
> +<LOCK_ARG>[^ \t\n()]+	{
> +				perf_bpf_filter_lval.lock_name = strdup(perf_bpf_filter_text);
> +				return BFT_LOCK_NAME;
> +			}
> +<LOCK_ARG>")"		{ BEGIN(INITIAL); return ')'; }
> +<LOCK_ARG>.|\n		{ BEGIN(INITIAL); return BFT_ERROR; }
> +
> +"!"		{ return '!'; }
>  "=="		{ return operator(PBF_OP_EQ); }
>  "!="		{ return operator(PBF_OP_NEQ); }
>  ">"		{ return operator(PBF_OP_GT); }
> diff --git a/tools/perf/util/bpf-filter.y b/tools/perf/util/bpf-filter.y
> index 5a79a8e7a45b..5ae05d4608c6 100644
> --- a/tools/perf/util/bpf-filter.y
> +++ b/tools/perf/util/bpf-filter.y
> @@ -8,9 +8,11 @@
>  #endif
>  
>  #include <stdio.h>
> +#include <stdlib.h>
>  #include <string.h>
>  #include <linux/compiler.h>
>  #include <linux/list.h>
> +#include <linux/zalloc.h>
>  #include "bpf-filter.h"
>  #include "cgroup.h"
>  
> @@ -31,6 +33,11 @@ static void perf_bpf_filter_error(struct list_head *expr __maybe_unused,
>  {
>  	unsigned long num;
>  	char *path;
> +	char *lock_name;
> +	struct {
> +		char *name;
> +		int mode;
> +	} lock;
>  	struct {
>  		enum perf_bpf_filter_term term;
>  		int part;
> @@ -40,12 +47,17 @@ static void perf_bpf_filter_error(struct list_head *expr __maybe_unused,
>  }
>  
>  %token BFT_SAMPLE BFT_SAMPLE_PATH BFT_OP BFT_ERROR BFT_NUM BFT_LOGICAL_OR BFT_PATH
> +%token BFT_LOCK_HELD BFT_LOCK_NAME
>  %type <expr> filter_term filter_expr
> -%destructor { free ($$); } <expr>
> +%destructor { perf_bpf_filter_expr__delete($$); } <expr>
> +%destructor { free($$); } <lock_name>
> +%destructor { free($$.name); } <lock>
>  %type <sample> BFT_SAMPLE BFT_SAMPLE_PATH
>  %type <op> BFT_OP
> -%type <num> BFT_NUM
> +%type <num> BFT_NUM BFT_LOCK_HELD
>  %type <path> BFT_PATH
> +%type <lock_name> BFT_LOCK_NAME
> +%type <lock> lock_term
>  
>  %%
>  
> @@ -105,5 +117,36 @@ BFT_SAMPLE_PATH BFT_OP BFT_PATH
>  	$$ = perf_bpf_filter_expr__new($1.term, $1.part, $2, cgroup_id);
>  	cgroup__put(cgrp);
>  }
> +|
> +lock_term
> +{
> +	$$ = perf_bpf_filter_expr__new_lock($1.name, $1.mode, PBF_OP_EQ, 1);
> +	zfree(&$1.name);
> +	if (!$$)
> +		YYERROR;
> +}
> +|
> +'!' lock_term
> +{
> +	$$ = perf_bpf_filter_expr__new_lock($2.name, $2.mode, PBF_OP_EQ, 0);
> +	zfree(&$2.name);
> +	if (!$$)
> +		YYERROR;
> +}
> +|
> +lock_term BFT_OP BFT_NUM
> +{
> +	$$ = perf_bpf_filter_expr__new_lock($1.name, $1.mode, $2, $3);
> +	zfree(&$1.name);
> +	if (!$$)
> +		YYERROR;
> +}
> +
> +lock_term:
> +BFT_LOCK_HELD BFT_LOCK_NAME ')'
> +{
> +	$$.name = $2;
> +	$$.mode = $1;
> +}
>  
>  %%
> diff --git a/tools/perf/util/bpf_skel/sample-filter.h b/tools/perf/util/bpf_skel/sample-filter.h
> index 683fec85e71e..ca1cf586ed6e 100644
> --- a/tools/perf/util/bpf_skel/sample-filter.h
> +++ b/tools/perf/util/bpf_skel/sample-filter.h
> @@ -53,8 +53,44 @@ enum perf_bpf_filter_term {
>  	/* Terms computed from BPF helpers. */
>  	PBF_TERM_UID,
>  	PBF_TERM_GID,
> +	/*
> +	 * Kernel lock held predicate (`lock_held(...)`). Evaluates whether the
> +	 * specified kernel lock is currently held by the running task/CPU.
> +	 * The lock specification kind is stored in `entry.part`
> +	 * (`enum perf_bpf_lock_target_kind`), `entry.op` is normalized to
> +	 * `PBF_OP_EQ` (match when held) or `PBF_OP_NEQ` (match when not held),
> +	 * and the resolved target address is stored in `entry.value`.
> +	 */
> +	PBF_TERM_LOCK_HELD,
>  };
>  
> +/*
> + * Lock target categories for PBF_TERM_LOCK_HELD filters.
> + * Stored in `perf_bpf_filter_entry.part`.
> + */
> +enum perf_bpf_lock_target_kind {
> +	PBF_LOCK_NONE = 0,
> +	/*
> +	 * Specific global or static lock address resolved from kallsyms.
> +	 * `entry.value` holds the kernel address.
> +	 */
> +	PBF_LOCK_ADDR,
> +};
> +
> +/*
> + * `perf_bpf_filter_entry.part` layout for PBF_TERM_LOCK_HELD:
> + *   Bits 0..3: `enum perf_bpf_lock_target_kind`
> + *   Bit  4:    PBF_LOCK_MODE_READ      (match read/shared lock holding)
> + *   Bit  5:    PBF_LOCK_MODE_WRITE     (match write/exclusive lock holding)
> + *   Bit  6:    PBF_LOCK_DIRECT_MUTEX   (inspect struct mutex owner)
> + */
> +#define PBF_LOCK_KIND_MASK	0x0fU
> +#define PBF_LOCK_MODE_READ	(1U << 4)
> +#define PBF_LOCK_MODE_WRITE	(1U << 5)
> +#define PBF_LOCK_MODE_ANY	(PBF_LOCK_MODE_READ | PBF_LOCK_MODE_WRITE)
> +#define PBF_LOCK_MODE_MASK	PBF_LOCK_MODE_ANY
> +#define PBF_LOCK_DIRECT_MUTEX	(1U << 6)
> +
>  /* BPF map entry for filtering */
>  struct perf_bpf_filter_entry {
>  	enum perf_bpf_filter_op op;
> diff --git a/tools/perf/util/bpf_skel/sample_filter.bpf.c b/tools/perf/util/bpf_skel/sample_filter.bpf.c
> index a190374ed87a..42556ab0e2d5 100644
> --- a/tools/perf/util/bpf_skel/sample_filter.bpf.c
> +++ b/tools/perf/util/bpf_skel/sample_filter.bpf.c
> @@ -5,6 +5,7 @@
>  #include <bpf/bpf_tracing.h>
>  #include <bpf/bpf_core_read.h>
>  
> +#include "lock_data.h"
>  #include "sample-filter.h"
>  
>  /* BPF map that will be filled by user space */
> @@ -45,8 +46,57 @@ struct lost_count {
>  
>  volatile const int use_idx_hash;
>  
> +/* Control flag set by userspace when lock_held(...) filters are active. */
> +int lock_enabled;
> +
>  void *bpf_cast_to_kern_ctx(void *) __ksym;
>  
> +static __always_inline bool is_direct_lock_owner(__u64 lock_addr, __u64 curr, __u32 part)
> +{
> +	if (!curr)
> +		return false;
> +
> +	if ((part & PBF_LOCK_DIRECT_MUTEX) &&
> +	    (__u64)get_lock_owner(lock_addr, LCB_F_MUTEX) == curr)
> +		return true;
> +	return false;
> +}
> +
> +static __always_inline bool is_lock_held(struct perf_bpf_filter_entry *entry)
> +{
> +	__u64 curr = bpf_get_current_task();
> +	__u32 kind = entry->part & PBF_LOCK_KIND_MASK;
> +	__u32 mode = entry->part & PBF_LOCK_MODE_MASK;
> +
> +	if (!mode)
> +		mode = PBF_LOCK_MODE_ANY;
> +
> +	switch (kind) {
> +	case PBF_LOCK_ADDR:
> +		if ((mode & PBF_LOCK_MODE_WRITE) &&
> +		    is_direct_lock_owner(entry->value, curr, entry->part))
> +			return true;
> +		return false;
> +
> +	default:
> +		return false;
> +	}
> +}
> +
> +static __always_inline __u64 check_lock_held(struct perf_bpf_filter_entry *entry)
> +{
> +	/*
> +	 * Pinned filters (use_idx_hash != 0) share a world-writable filters
> +	 * map and do not support lock_held(). Reject evaluation when pinned
> +	 * or when lock_enabled is not set so unprivileged users cannot probe
> +	 * arbitrary kernel addresses via entry->value.
> +	 */
> +	if (use_idx_hash || !lock_enabled)
> +		return entry->op == PBF_OP_EQ ? ~entry->value : entry->value;
> +
> +	return is_lock_held(entry) ? entry->value : ~entry->value;
> +}
> +
>  /* new kernel perf_sample_data definition */
>  struct perf_sample_data___new {
>  	__u64 sample_flags;
> @@ -175,6 +225,8 @@ static inline __u64 perf_get_sample(struct bpf_perf_event_data_kern *kctx,
>  		return bpf_get_current_uid_gid() & 0xFFFFFFFF;
>  	case PBF_TERM_GID:
>  		return bpf_get_current_uid_gid() >> 32;
> +	case PBF_TERM_LOCK_HELD:
> +		return check_lock_held(entry);
>  	case PBF_TERM_NONE:
>  	case __PBF_UNUSED_TERM4:
>  	case __PBF_UNUSED_TERM5:
> @@ -209,6 +261,8 @@ static __always_inline __u64 perf_get_common_sample(struct perf_bpf_filter_entry
>  		return bpf_get_current_uid_gid() & 0xFFFFFFFF;
>  	case PBF_TERM_GID:
>  		return bpf_get_current_uid_gid() >> 32;
> +	case PBF_TERM_LOCK_HELD:
> +		return check_lock_held(entry);
>  	default:
>  		return 0;
>  	}
> diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c
> index d6bab89ade5d..da687e9d1a67 100644
> --- a/tools/perf/util/parse-events.c
> +++ b/tools/perf/util/parse-events.c
> @@ -2621,6 +2621,8 @@ static bool is_possible_tp_filter(struct evsel *evsel, const char *str)
>  {
>  	static const char *const bpf_tp_terms[] = {
>  		"uid", "gid", "tid", "pid", "cpu", "time", "cgroup",
> +		"lock_held", "lock_held_read", "lock_held_shared",
> +		"lock_held_write", "lock_held_exclusive",
>  	};
>  
>  	for (size_t i = 0; i < ARRAY_SIZE(bpf_tp_terms); i++) {
> -- 
> 2.56.0.307.g5fe7bd680c-goog
> 

  parent reply	other threads:[~2026-10-22 22:55 UTC|newest]

Thread overview: 106+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-17  5:45 [PATCH v1 0/5] perf bpf-filter / lock contention: Add lock_held() sample filter and RCU/SRCU support Ian Rogers
2026-10-17  5:45 ` [PATCH v1 1/5] perf lock contention: Share common BPF and lock type definitions in lock_data.h Ian Rogers
2026-10-17  5:52   ` sashiko-bot
2026-10-17  5:45 ` [PATCH v1 2/5] perf bpf-filter: Add lock_held() sample filter Ian Rogers
2026-10-17  6:09   ` sashiko-bot
2026-10-17  5:45 ` [PATCH v1 3/5] perf test: Add tests for lock_held() BPF " Ian Rogers
2026-10-17  5:49   ` sashiko-bot
2026-10-17  5:45 ` [PATCH v1 4/5] perf bpf-filter: Extend lock_held() to support RCU, Tasks Trace RCU, and SRCU Ian Rogers
2026-10-17  5:56   ` sashiko-bot
2026-10-17  5:45 ` [PATCH v1 5/5] perf lock contention: Extend BPF lock contention to RCU " Ian Rogers
2026-10-17  6:08   ` sashiko-bot
2026-10-17 17:14 ` [PATCH v2 0/6] perf bpf-filter / lock contention: Add lock_held() sample filter and RCU/SRCU support Ian Rogers
2026-10-17 17:14   ` [PATCH v2 1/6] perf lock contention: Share common BPF and lock type definitions in lock_data.h Ian Rogers
2026-10-17 17:31     ` sashiko-bot
2026-10-17 17:14   ` [PATCH v2 2/6] perf bpf-filter: Add lock_held() sample filter Ian Rogers
2026-10-17 17:35     ` sashiko-bot
2026-10-17 17:14   ` [PATCH v2 3/6] perf test: Add tests for lock_held() BPF " Ian Rogers
2026-10-17 17:17     ` sashiko-bot
2026-10-17 17:14   ` [PATCH v2 4/6] perf bpf-filter: Extend lock_held() to support RCU, Tasks Trace RCU, and SRCU Ian Rogers
2026-10-17 17:25     ` sashiko-bot
2026-10-17 17:14   ` [PATCH v2 5/6] perf lock contention: Extend BPF lock contention to RCU " Ian Rogers
2026-10-17 17:30     ` sashiko-bot
2026-10-17 17:14   ` [PATCH v2 6/6] perf lock contention: Filter events before creating lock_stat Ian Rogers
2026-10-17 17:17     ` sashiko-bot
2026-10-18  5:37   ` [PATCH v3 0/6] perf bpf-filter / lock contention: Add lock_held() sample filter and RCU/SRCU support Ian Rogers
2026-10-18  5:37     ` [PATCH v3 1/6] perf lock contention: Share common BPF and lock type definitions in lock_data.h Ian Rogers
2026-10-18  5:50       ` sashiko-bot
2026-10-18  5:37     ` [PATCH v3 2/6] perf bpf-filter: Add lock_held() sample filter Ian Rogers
2026-10-18  6:12       ` sashiko-bot
2026-10-18  5:37     ` [PATCH v3 3/6] perf test: Add tests for lock_held() BPF " Ian Rogers
2026-10-18  5:41       ` sashiko-bot
2026-10-18  5:37     ` [PATCH v3 4/6] perf bpf-filter: Extend lock_held() to support RCU, Tasks Trace RCU, and SRCU Ian Rogers
2026-10-18  5:51       ` sashiko-bot
2026-10-18  5:37     ` [PATCH v3 5/6] perf lock contention: Extend BPF lock contention to RCU " Ian Rogers
2026-10-18  5:48       ` sashiko-bot
2026-10-18  5:37     ` [PATCH v3 6/6] perf lock contention: Filter events before creating lock_stat Ian Rogers
2026-10-18  5:40       ` sashiko-bot
2026-10-19  7:00     ` [PATCH v3 0/6] perf bpf-filter / lock contention: Add lock_held() sample filter and RCU/SRCU support Namhyung Kim
2026-10-19 19:08       ` Ian Rogers
2026-10-19 22:09     ` [PATCH v4 0/7] " Ian Rogers
2026-10-19 22:09       ` [PATCH v4 1/7] perf lock contention: Filter events before creating lock_stat Ian Rogers
2026-10-19 22:14         ` sashiko-bot
2026-10-20  4:34         ` Namhyung Kim
2026-10-21 21:39           ` Arnaldo Carvalho de Melo
2026-10-19 22:09       ` [PATCH v4 2/7] perf bpf-filter: Support BPF sample filters on tracepoint events Ian Rogers
2026-10-19 22:26         ` sashiko-bot
2026-10-20  5:41         ` Namhyung Kim
2026-10-20 15:30           ` Ian Rogers
2026-10-20 22:29             ` Namhyung Kim
2026-10-19 22:09       ` [PATCH v4 3/7] perf lock contention: Share common BPF and lock type definitions in lock_data.h Ian Rogers
2026-10-19 22:17         ` sashiko-bot
2026-10-19 22:09       ` [PATCH v4 4/7] perf bpf-filter: Add lock_held() sample filter Ian Rogers
2026-10-19 22:28         ` sashiko-bot
2026-10-19 22:09       ` [PATCH v4 5/7] perf test: Add tests for lock_held() BPF " Ian Rogers
2026-10-19 22:12         ` sashiko-bot
2026-10-19 22:09       ` [PATCH v4 6/7] perf bpf-filter: Extend lock_held() to support RCU, Tasks Trace RCU, and SRCU Ian Rogers
2026-10-19 22:19         ` sashiko-bot
2026-10-19 22:09       ` [PATCH v4 7/7] perf lock contention: Extend BPF lock contention to RCU " Ian Rogers
2026-10-19 22:19         ` sashiko-bot
2026-10-20 20:58       ` [PATCH v5 0/8] perf bpf-filter / lock contention: Add lock_held() sample filter and RCU/SRCU support Ian Rogers
2026-10-20 20:58         ` [PATCH v5 1/8] perf lock contention: Filter events before creating lock_stat Ian Rogers
2026-10-20 22:38           ` sashiko-bot
2026-10-20 20:58         ` [PATCH v5 2/8] perf bpf-filter: Fix bpf_link leak by using PERF_EVENT_IOC_SET_BPF Ian Rogers
2026-10-20 22:41           ` sashiko-bot
2026-10-20 20:58         ` [PATCH v5 3/8] perf bpf-filter: Support BPF sample filters on tracepoint events Ian Rogers
2026-10-20 22:57           ` sashiko-bot
2026-10-20 20:58         ` [PATCH v5 4/8] perf lock contention: Share common BPF and lock type definitions in lock_data.h Ian Rogers
2026-10-20 22:42           ` Namhyung Kim
2026-10-20 23:02           ` sashiko-bot
2026-10-20 20:58         ` [PATCH v5 5/8] perf bpf-filter: Add lock_held() sample filter Ian Rogers
2026-10-20 22:49           ` Namhyung Kim
2026-10-20 23:17           ` sashiko-bot
2026-10-20 20:58         ` [PATCH v5 6/8] perf test: Add tests for lock_held() BPF " Ian Rogers
2026-10-20 23:20           ` sashiko-bot
2026-10-20 20:58         ` [PATCH v5 7/8] perf bpf-filter: Extend lock_held() to support RCU, Tasks Trace RCU, and SRCU Ian Rogers
2026-10-20 23:29           ` sashiko-bot
2026-10-20 20:58         ` [PATCH v5 8/8] perf lock contention: Extend BPF lock contention to RCU " Ian Rogers
2026-10-20 23:37           ` sashiko-bot
2026-10-22 20:29         ` [PATCH v6 00/11] perf bpf-filter / lock contention: Add lock_held() sample filter and RCU/SRCU support Ian Rogers
2026-10-22 20:29           ` [PATCH v6 01/11] perf bpf-filter: Fix bpf_link leak by using PERF_EVENT_IOC_SET_BPF Ian Rogers
2026-10-22 20:35             ` sashiko-bot
2026-10-22 22:06             ` Namhyung Kim
2026-10-22 20:29           ` [PATCH v6 02/11] perf bpf-filter: Support BPF sample filters on tracepoint events Ian Rogers
2026-10-22 20:55             ` sashiko-bot
2026-10-22 20:29           ` [PATCH v6 03/11] perf lock contention: Share common BPF and lock type definitions in lock_data.h Ian Rogers
2026-10-22 20:36             ` sashiko-bot
2026-10-22 20:29           ` [PATCH v6 04/11] perf lock contention: Extend get_lock_owner() for rt_mutex and mutex handoff Ian Rogers
2026-10-22 20:41             ` sashiko-bot
2026-10-22 20:29           ` [PATCH v6 05/11] perf bpf-filter: Add lock_held() sample filter for global mutexes Ian Rogers
2026-10-22 20:46             ` sashiko-bot
2026-10-22 22:55             ` Namhyung Kim [this message]
2026-10-23  5:16               ` Ian Rogers
2026-10-22 20:29           ` [PATCH v6 06/11] perf bpf-filter: Extend lock_held() to other kernel lock types Ian Rogers
2026-10-22 20:55             ` sashiko-bot
2026-10-22 22:56             ` Namhyung Kim
2026-10-23  5:19               ` Ian Rogers
2026-10-22 20:29           ` [PATCH v6 07/11] perf bpf-filter: Support per-process and semi-global locks in lock_held() Ian Rogers
2026-10-22 20:49             ` sashiko-bot
2026-10-22 20:30           ` [PATCH v6 08/11] perf bpf-filter: Support slab cache locks (&<name>) " Ian Rogers
2026-10-22 20:37             ` sashiko-bot
2026-10-22 20:30           ` [PATCH v6 09/11] perf test: Add tests for lock_held() BPF sample filter Ian Rogers
2026-10-22 20:37             ` sashiko-bot
2026-10-22 20:30           ` [PATCH v6 10/11] perf bpf-filter: Extend lock_held() to support RCU, Tasks Trace RCU, and SRCU Ian Rogers
2026-10-22 20:44             ` sashiko-bot
2026-10-22 20:30           ` [PATCH v6 11/11] perf lock contention: Extend BPF lock contention to RCU " Ian Rogers
2026-10-22 20:43             ` sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=atqUSrmtsfCojGcA@google.com \
    --to=namhyung@kernel.org \
    --cc=acme@kernel.org \
    --cc=adrian.hunter@intel.com \
    --cc=ddhariwal@google.com \
    --cc=eranian@google.com \
    --cc=irogers@google.com \
    --cc=james.clark@linaro.org \
    --cc=jolsa@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-perf-users@vger.kernel.org \
    --cc=mingo@redhat.com \
    --cc=peterz@infradead.org \
    --cc=rcu@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.