From: Tengda Wu <wutengda@huaweicloud.com>
To: Peter Zijlstra <peterz@infradead.org>
Cc: Ingo Molnar <mingo@redhat.com>,
Arnaldo Carvalho de Melo <acme@kernel.org>,
Namhyung Kim <namhyung@kernel.org>,
Mark Rutland <mark.rutland@arm.com>,
Alexander Shishkin <alexander.shishkin@linux.intel.com>,
Jiri Olsa <jolsa@kernel.org>, Ian Rogers <irogers@google.com>,
Adrian Hunter <adrian.hunter@intel.com>,
James Clark <james.clark@linaro.org>,
Sebastian Andrzej Siewior <bigeasy@linutronix.de>,
linux-perf-users@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH v3 0/2] perf/core: Fix sigtrap task_work races
Date: Thu, 5 Nov 2026 17:47:45 +0800 [thread overview]
Message-ID: <d1b24bbf-9111-4766-87c8-ff97adcac727@huaweicloud.com> (raw)
In-Reply-To: <cover.1793870900.git.wutengda@huaweicloud.com>
On 2026/11/5 17:30, Tengda Wu wrote:
> Two fixes for sigtrap task_work handling since the switch to always
> enqueue SIGTRAP via task_work.
>
> The warning splats and PoCs for both patches are too long to inline in
> the commit messages. They are sent as separate replies to this cover
> letter, one per patch.
>
> Patch 1 fixes a re-entrancy race in __perf_event_overflow(): pending_work
> was set only after task_work_add(), so an NMI could re-enter and enqueue
> the same pending_task twice, creating a cycle in the task work list and
> unbalancing the event refcount. Claim pending_work with cmpxchg() before
> enqueuing, and clear it if the enqueue fails.
>
> Patch 2 fixes a WARN_ON_ONCE() in perf_sigtrap() triggered by the context
> fast-swap path swapping ctx->task while a sigtrap event has pending task
> work. SIGTRAP is targeted at ctx->task, so exclude sigtrap events from
> fast-swap alongside inherit+sample-read events.
>
This is the warning splat and PoC for patch 2
("perf/core: Disable context fast-swap for sigtrap events").
Warning observed before the fix:
[ 370.271075] ------------[ cut here ]------------
[ 370.271206] WARNING: kernel/events/core.c:7654 at perf_pending_task+0x2ad/0x330, CPU#1: fastswap/1414
[ 370.273456] Modules linked in:
[ 370.273990] CPU: 1 UID: 0 PID: 1414 Comm: fastswap Not tainted 7.3.0-next-20261027+ #4 PREEMPT(lazy)
[ 370.275315] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.15.0-0-g2dd4b9b3f840-prebuilt.qemu.org 04/4
[ 370.277114] RIP: 0010:perf_pending_task+0x2ad/0x330
[ 370.277989] Code: 4c 8b ab c8 fe ff ff 49 ff 8d e8 00 00 00 e9 37 fe ff ff 4c 89 ef e8 52 c2 24 00 e9 e6 fd ff ff e8 a8 9
[ 370.280596] RSP: 0018:ffff88810922fe10 EFLAGS: 00010283
[ 370.281502] RAX: ffff8881093b9240 RBX: ffff888108f18398 RCX: 0000000000000000
[ 370.282606] RDX: ffff88811679b6c0 RSI: 0000000000000004 RDI: ffff888103287680
[ 370.283691] RBP: ffff888108f18000 R08: 0000000000000001 R09: ffffed1021245fbf
[ 370.284834] R10: 0000000000000003 R11: 0000000000000000 R12: 0000000000000000
[ 370.285958] R13: ffff888103287600 R14: ffff888108f18260 R15: 0000000000000000
[ 370.287097] FS: 00007f6f8537e600(0000) GS:ffff88826ff74000(0000) knlGS:0000000000000000
[ 370.288387] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 370.289168] CR2: 00007f6f84a0c000 CR3: 00000001100a2006 CR4: 0000000000170ef0
[ 370.290336] Call Trace:
[ 370.290688] <TASK>
[ 370.291034] ? count_memcg_events+0x273/0x410
[ 370.291720] task_work_run+0x13d/0x200
[ 370.292365] ? __pfx_task_work_run+0x10/0x10
[ 370.292956] irqentry_exit+0x3a8/0x700
[ 370.293579] asm_exc_page_fault+0x26/0x30
[ 370.294143] RIP: 0033:0x401316
[ 370.294695] Code: c6 bf 2a 01 00 00 b8 00 00 00 00 e8 14 fe ff ff c9 c3 55 48 89 e5 89 7d fc 48 89 75 f0 48 89 55 e8 8b d
[ 370.297200] RSP: 002b:00007ffe7c0027f0 EFLAGS: 00010206
[ 370.297963] RAX: 000000000000002e RBX: 0000000000000001 RCX: 00007f6f85254d13
[ 370.299006] RDX: 00007ffe7c002800 RSI: 00007ffe7c002930 RDI: 0000000000000005
[ 370.300021] RBP: 00007ffe7c0027f0 R08: 0000000000000000 R09: 00007ffe7c002b67
[ 370.301043] R10: 00007f6f8537e8d0 R11: 0000000000000246 R12: 00007ffe7c0030f8
[ 370.302066] R13: 0000000000401a8a R14: 00007f6f853b7aa0 R15: 0000000000403e08
[ 370.303112] </TASK>
[ 370.303495] ---[ end trace 0000000000000000 ]---
PoC (not easily reproducible, needs several rounds of running):
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include "perf_event.h"
#include <sched.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
static volatile sig_atomic_t sigtrap_count;
static int perf_open(struct perf_event_attr *attr, pid_t pid, int cpu)
{
return syscall(__NR_perf_event_open, attr, pid, cpu, -1,
PERF_FLAG_FD_CLOEXEC);
}
static void sigtrap_handler(int sig, siginfo_t *info, void *ctx)
{
(void)sig;
(void)info;
(void)ctx;
sigtrap_count++;
}
static int set_cpu(int cpu)
{
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(cpu, &set);
return sched_setaffinity(0, sizeof(set), &set);
}
static int read_msr_tracepoint_id(void)
{
static const char * const paths[] = {
"/sys/kernel/tracing/events/msr/write_msr/id",
"/sys/kernel/debug/tracing/events/msr/write_msr/id",
};
char buf[64];
unsigned int i;
for (i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) {
int fd = open(paths[i], O_RDONLY | O_CLOEXEC);
ssize_t n;
if (fd < 0)
continue;
n = read(fd, buf, sizeof(buf) - 1);
close(fd);
if (n <= 0)
continue;
buf[n] = '\0';
return (int)strtol(buf, NULL, 10);
}
return -1;
}
/*
* Open inherited MSR tracepoint event.
*
* The event is created by the worker. Forked children inherit
* the event and get their own child perf context.
*/
static int open_msr_sigtrap_event(int tracepoint_id, int cpu)
{
struct perf_event_attr attr;
memset(&attr, 0, sizeof(attr));
attr.size = sizeof(attr);
attr.type = PERF_TYPE_TRACEPOINT;
attr.config = tracepoint_id;
attr.sample_period = 64;
attr.sample_type = PERF_SAMPLE_IP;
attr.pinned = 1;
attr.exclusive = 1;
attr.exclude_idle = 1;
attr.exclude_user = 0;
attr.exclude_kernel = 0;
attr.inherit = 1;
attr.remove_on_exec = 1;
attr.sigtrap = 1;
attr.sig_data = 7;
return perf_open(&attr, 0, cpu);
}
/*
* Open inherited cache-miss event.
*/
static int open_cache_miss_event(int cpu)
{
struct perf_event_attr attr;
memset(&attr, 0, sizeof(attr));
attr.size = sizeof(attr);
attr.type = PERF_TYPE_HARDWARE;
attr.config = PERF_COUNT_HW_CACHE_MISSES;
attr.sample_period = 100;
attr.sample_type = PERF_SAMPLE_IP;
attr.pinned = 1;
attr.exclusive = 1;
attr.exclude_idle = 1;
attr.exclude_user = 0;
attr.exclude_kernel = 0;
attr.inherit = 1;
/* The hardware event does not own pending_task. */
return perf_open(&attr, 0, cpu);
}
/*
* Generate memory pressure and frequent voluntary context switches.
*/
static void workload(unsigned int ms)
{
struct timespec start, now;
uint64_t elapsed;
const size_t bytes = 8 * 1024 * 1024;
volatile unsigned char *buf;
volatile uint64_t x = 1;
uint32_t index = 0x12345678;
buf = malloc(bytes);
if (buf)
memset((void *)buf, 0, bytes);
clock_gettime(CLOCK_MONOTONIC, &start);
do {
for (unsigned int i = 0; i < 100000; i++) {
x = x * 1664525 + 1013904223;
index = index * 1664525 + 1013904223;
if (buf)
buf[(index & (bytes - 1)) & ~63U]++;
/*
* Yield every 64 iterations to increase
* sched_out/in activity on the same CPU.
*/
if (!(i & 63))
sched_yield();
}
clock_gettime(CLOCK_MONOTONIC, &now);
elapsed = (uint64_t)(now.tv_sec - start.tv_sec) * 1000;
if (now.tv_nsec >= start.tv_nsec)
elapsed += (uint64_t)(now.tv_nsec -
start.tv_nsec) / 1000000;
else
elapsed -= (uint64_t)(start.tv_nsec -
now.tv_nsec) / 1000000;
} while (elapsed < ms);
free((void *)buf);
(void)x;
}
/*
* Forked child:
*
* - inherits worker's perf events;
* - inherits worker's CPU affinity;
* - executes a short workload;
* - exits without explicitly closing perf event FDs.
*/
static void one_child(void)
{
sigtrap_count = 0;
workload(5);
_exit(0);
}
/*
* Worker:
*
* 1. Bind to the target CPU.
* 2. Create perf events with inherit=1.
* 3. Repeatedly fork short-lived children.
* 4. Wait for child exit.
*
* All workers share the same CPU.
*/
static void worker(int cpu, int tracepoint_id, time_t end)
{
struct sigaction sa;
unsigned long iter = 0;
int hw_fd, tp_fd;
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = sigtrap_handler;
sa.sa_flags = SA_SIGINFO | SA_RESTART;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGTRAP, &sa, NULL))
_exit(10);
if (set_cpu(cpu)) {
dprintf(2, "[%d] sched_setaffinity failed: %s\n",
getpid(), strerror(errno));
_exit(13);
}
/*
* Open hardware event first, then tracepoint event.
*/
hw_fd = open_cache_miss_event(cpu);
if (hw_fd < 0) {
dprintf(2, "[%d] open cache-misses failed: %s\n",
getpid(), strerror(errno));
_exit(11);
}
tp_fd = open_msr_sigtrap_event(tracepoint_id, cpu);
if (tp_fd < 0) {
dprintf(2, "[%d] open msr tracepoint failed: %s\n",
getpid(), strerror(errno));
close(hw_fd);
_exit(12);
}
dprintf(2,
"[worker %d] cpu=%d hw_fd=%d tp_fd=%d started\n",
getpid(), cpu, hw_fd, tp_fd);
while (time(NULL) < end) {
pid_t pid;
int status;
pid = fork();
if (pid < 0) {
if (errno == EINTR)
continue;
perror("fork");
break;
}
if (pid == 0)
one_child();
/*
* Worker sleeps in waitpid().
* Child runs on the same CPU.
* Child exit wakes the worker.
*/
for (;;) {
pid_t ret = waitpid(pid, &status, 0);
if (ret == pid)
break;
if (ret < 0 && errno == EINTR)
continue;
if (ret < 0)
perror("waitpid");
break;
}
iter++;
}
dprintf(2,
"[worker %d] cpu=%d iterations=%lu sigtrap=%d\n",
getpid(), cpu, iter, (int)sigtrap_count);
/*
* Keep the parent events alive throughout the workload.
* Child inherited events are released through child exit.
*/
close(hw_fd);
close(tp_fd);
_exit(0);
}
int main(int argc, char **argv)
{
int cpu = argc > 1 ? atoi(argv[1]) : 1;
int workers = argc > 2 ? atoi(argv[2]) : 8;
int duration = argc > 3 ? atoi(argv[3]) : 60;
int tracepoint_id;
int ncpu;
int created = 0;
pid_t *pids;
time_t end;
if (workers < 1)
workers = 1;
if (duration < 1)
duration = 1;
ncpu = (int)sysconf(_SC_NPROCESSORS_ONLN);
if (cpu < 0 || cpu >= ncpu) {
fprintf(stderr,
"invalid cpu %d (online cpus: %d)\n",
cpu, ncpu);
return 1;
}
tracepoint_id = read_msr_tracepoint_id();
if (tracepoint_id < 0) {
fprintf(stderr,
"cannot read msr:write_msr tracepoint id; "
"check tracefs/debugfs and CONFIG_X86_MSR\n");
return 1;
}
pids = calloc((size_t)workers, sizeof(*pids));
if (!pids)
return 1;
end = time(NULL) + duration;
fprintf(stderr,
"msr:write_msr id=%d cpu=%d workers=%d duration=%ds\n",
tracepoint_id, cpu, workers, duration);
fprintf(stderr,
"Both perf events use inherit=1.\n");
fprintf(stderr,
"All workers and children are pinned to CPU %d.\n", cpu);
fprintf(stderr,
"Children yield frequently and exit after short workloads.\n");
for (int i = 0; i < workers; i++) {
pid_t pid = fork();
if (pid < 0) {
perror("fork worker");
break;
}
if (pid == 0)
worker(cpu, tracepoint_id, end);
pids[created++] = pid;
}
for (int i = 0; i < created; i++) {
while (waitpid(pids[i], NULL, 0) < 0) {
if (errno == EINTR)
continue;
break;
}
}
free(pids);
fprintf(stderr, "All workers finished.\n");
return 0;
}
Thanks,
Tengda
prev parent reply other threads:[~2026-11-05 9:47 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-05 9:30 Tengda Wu
2026-11-05 9:30 ` [PATCH v3 1/2] perf/core: Prevent duplicate sigtrap task work enqueue Tengda Wu
2026-11-05 9:30 ` [PATCH v3 2/2] perf/core: Disable context fast-swap for sigtrap events Tengda Wu
2026-11-05 9:43 ` [PATCH v3 0/2] perf/core: Fix sigtrap task_work races Tengda Wu
2026-11-05 9:47 ` Tengda Wu [this message]
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=d1b24bbf-9111-4766-87c8-ff97adcac727@huaweicloud.com \
--to=wutengda@huaweicloud.com \
--cc=acme@kernel.org \
--cc=adrian.hunter@intel.com \
--cc=alexander.shishkin@linux.intel.com \
--cc=bigeasy@linutronix.de \
--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=mark.rutland@arm.com \
--cc=mingo@redhat.com \
--cc=namhyung@kernel.org \
--cc=peterz@infradead.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®