Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
sigtrap_threads
remove_on_exec
watermark_signal
mmap
@@ -0,0 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
CFLAGS += -Wl,-no-as-needed -Wall $(KHDR_INCLUDES)
LDFLAGS += -lpthread
TEST_GEN_PROGS := sigtrap_threads remove_on_exec watermark_signal mmap
include ../lib.mk
@@ -0,0 +1 @@
CONFIG_PERF_EVENTS=y
@@ -0,0 +1,236 @@
// SPDX-License-Identifier: GPL-2.0-only
#define _GNU_SOURCE
#include <dirent.h>
#include <sched.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <linux/perf_event.h>
#include "kselftest_harness.h"
#define RB_SIZE 0x3000
#define AUX_SIZE 0x10000
#define AUX_OFFS 0x4000
#define HOLE_SIZE 0x1000
/* Reserve space for rb, aux with space for shrink-beyond-vma testing. */
#define REGION_SIZE (2 * RB_SIZE + 2 * AUX_SIZE)
#define REGION_AUX_OFFS (2 * RB_SIZE)
#define MAP_BASE 1
#define MAP_AUX 2
#define EVENT_SRC_DIR "/sys/bus/event_source/devices"
FIXTURE(perf_mmap)
{
int fd;
void *ptr;
void *region;
};
FIXTURE_VARIANT(perf_mmap)
{
bool aux;
unsigned long ptr_size;
};
FIXTURE_VARIANT_ADD(perf_mmap, rb)
{
.aux = false,
.ptr_size = RB_SIZE,
};
FIXTURE_VARIANT_ADD(perf_mmap, aux)
{
.aux = true,
.ptr_size = AUX_SIZE,
};
static bool read_event_type(struct dirent *dent, __u32 *type)
{
char typefn[512];
FILE *fp;
int res;
snprintf(typefn, sizeof(typefn), "%s/%s/type", EVENT_SRC_DIR, dent->d_name);
fp = fopen(typefn, "r");
if (!fp)
return false;
res = fscanf(fp, "%u", type);
fclose(fp);
return res > 0;
}
FIXTURE_SETUP(perf_mmap)
{
struct perf_event_attr attr = {
.size = sizeof(attr),
.disabled = 1,
.exclude_kernel = 1,
.exclude_hv = 1,
};
struct perf_event_attr attr_ok = {};
unsigned int eacces = 0, map = 0;
struct perf_event_mmap_page *rb;
struct dirent *dent;
void *aux, *region;
DIR *dir;
self->ptr = NULL;
dir = opendir(EVENT_SRC_DIR);
if (!dir)
SKIP(return, "perf not available.");
region = mmap(NULL, REGION_SIZE, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0);
ASSERT_NE(region, MAP_FAILED);
self->region = region;
// Try to find a suitable event on this system
while ((dent = readdir(dir))) {
int fd;
if (!read_event_type(dent, &attr.type))
continue;
fd = syscall(SYS_perf_event_open, &attr, 0, -1, -1, 0);
if (fd < 0) {
if (errno == EACCES)
eacces++;
continue;
}
// Check whether the event supports mmap()
rb = mmap(region, RB_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, 0);
if (rb == MAP_FAILED) {
close(fd);
continue;
}
if (!map) {
// Save the event in case that no AUX capable event is found
attr_ok = attr;
map = MAP_BASE;
}
if (!variant->aux)
continue;
rb->aux_offset = AUX_OFFS;
rb->aux_size = AUX_SIZE;
// Check whether it supports a AUX buffer
aux = mmap(region + REGION_AUX_OFFS, AUX_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_FIXED, fd, AUX_OFFS);
if (aux == MAP_FAILED) {
munmap(rb, RB_SIZE);
close(fd);
continue;
}
attr_ok = attr;
map = MAP_AUX;
munmap(aux, AUX_SIZE);
munmap(rb, RB_SIZE);
close(fd);
break;
}
closedir(dir);
if (!map) {
if (!eacces)
SKIP(return, "No mappable perf event found.");
else
SKIP(return, "No permissions for perf_event_open()");
}
self->fd = syscall(SYS_perf_event_open, &attr_ok, 0, -1, -1, 0);
ASSERT_NE(self->fd, -1);
rb = mmap(region, RB_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, self->fd, 0);
ASSERT_NE(rb, MAP_FAILED);
if (!variant->aux) {
self->ptr = rb;
return;
}
if (map != MAP_AUX)
SKIP(return, "No AUX event found.");
rb->aux_offset = AUX_OFFS;
rb->aux_size = AUX_SIZE;
aux = mmap(region + REGION_AUX_OFFS, AUX_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_FIXED, self->fd, AUX_OFFS);
ASSERT_NE(aux, MAP_FAILED);
self->ptr = aux;
}
FIXTURE_TEARDOWN(perf_mmap)
{
ASSERT_EQ(munmap(self->region, REGION_SIZE), 0);
if (self->fd != -1)
ASSERT_EQ(close(self->fd), 0);
}
TEST_F(perf_mmap, remap)
{
void *tmp, *ptr = self->ptr;
unsigned long size = variant->ptr_size;
// Test the invalid remaps
ASSERT_EQ(mremap(ptr, size, HOLE_SIZE, MREMAP_MAYMOVE), MAP_FAILED);
ASSERT_EQ(mremap(ptr + HOLE_SIZE, size, HOLE_SIZE, MREMAP_MAYMOVE), MAP_FAILED);
ASSERT_EQ(mremap(ptr + size - HOLE_SIZE, HOLE_SIZE, size, MREMAP_MAYMOVE), MAP_FAILED);
// Shrink the end of the mapping such that we only unmap past end of the VMA,
// which should succeed and poke a hole into the PROT_NONE region
ASSERT_NE(mremap(ptr + size - HOLE_SIZE, size, HOLE_SIZE, MREMAP_MAYMOVE), MAP_FAILED);
// Remap the whole buffer to a new address
tmp = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
ASSERT_NE(tmp, MAP_FAILED);
// Try splitting offset 1 hole size into VMA, this should fail
ASSERT_EQ(mremap(ptr + HOLE_SIZE, size - HOLE_SIZE, size - HOLE_SIZE,
MREMAP_MAYMOVE | MREMAP_FIXED, tmp), MAP_FAILED);
// Remapping the whole thing should succeed fine
ptr = mremap(ptr, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, tmp);
ASSERT_EQ(ptr, tmp);
ASSERT_EQ(munmap(tmp, size), 0);
}
TEST_F(perf_mmap, unmap)
{
unsigned long size = variant->ptr_size;
// Try to poke holes into the mappings
ASSERT_NE(munmap(self->ptr, HOLE_SIZE), 0);
ASSERT_NE(munmap(self->ptr + HOLE_SIZE, HOLE_SIZE), 0);
ASSERT_NE(munmap(self->ptr + size - HOLE_SIZE, HOLE_SIZE), 0);
}
TEST_F(perf_mmap, map)
{
unsigned long size = variant->ptr_size;
// Try to poke holes into the mappings by mapping anonymous memory over it
ASSERT_EQ(mmap(self->ptr, HOLE_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0), MAP_FAILED);
ASSERT_EQ(mmap(self->ptr + HOLE_SIZE, HOLE_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0), MAP_FAILED);
ASSERT_EQ(mmap(self->ptr + size - HOLE_SIZE, HOLE_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0), MAP_FAILED);
}
TEST_HARNESS_MAIN
@@ -0,0 +1,260 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Test for remove_on_exec.
*
* Copyright (C) 2021, Google LLC.
*/
#define _GNU_SOURCE
/* We need the latest siginfo from the kernel repo. */
#include <sys/types.h>
#include <asm/siginfo.h>
#define __have_siginfo_t 1
#define __have_sigval_t 1
#define __have_sigevent_t 1
#define __siginfo_t_defined
#define __sigval_t_defined
#define __sigevent_t_defined
#define _BITS_SIGINFO_CONSTS_H 1
#define _BITS_SIGEVENT_CONSTS_H 1
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <linux/perf_event.h>
#include <pthread.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <unistd.h>
#include "kselftest_harness.h"
static volatile int signal_count;
static struct perf_event_attr make_event_attr(void)
{
struct perf_event_attr attr = {
.type = PERF_TYPE_HARDWARE,
.size = sizeof(attr),
.config = PERF_COUNT_HW_INSTRUCTIONS,
.sample_period = 1000,
.exclude_kernel = 1,
.exclude_hv = 1,
.disabled = 1,
.inherit = 1,
/*
* Children normally retain their inherited event on exec; with
* remove_on_exec, we'll remove their event, but the parent and
* any other non-exec'd children will keep their events.
*/
.remove_on_exec = 1,
.sigtrap = 1,
};
return attr;
}
static void sigtrap_handler(int signum, siginfo_t *info, void *ucontext)
{
if (info->si_code != TRAP_PERF) {
fprintf(stderr, "%s: unexpected si_code %d\n", __func__, info->si_code);
return;
}
signal_count++;
}
FIXTURE(remove_on_exec)
{
struct sigaction oldact;
int fd;
};
FIXTURE_SETUP(remove_on_exec)
{
struct perf_event_attr attr = make_event_attr();
struct sigaction action = {};
signal_count = 0;
/* Initialize sigtrap handler. */
action.sa_flags = SA_SIGINFO | SA_NODEFER;
action.sa_sigaction = sigtrap_handler;
sigemptyset(&action.sa_mask);
ASSERT_EQ(sigaction(SIGTRAP, &action, &self->oldact), 0);
/* Initialize perf event. */
self->fd = syscall(__NR_perf_event_open, &attr, 0, -1, -1, PERF_FLAG_FD_CLOEXEC);
ASSERT_NE(self->fd, -1);
}
FIXTURE_TEARDOWN(remove_on_exec)
{
close(self->fd);
sigaction(SIGTRAP, &self->oldact, NULL);
}
/* Verify event propagates to fork'd child. */
TEST_F(remove_on_exec, fork_only)
{
int status;
pid_t pid = fork();
if (pid == 0) {
ASSERT_EQ(signal_count, 0);
ASSERT_EQ(ioctl(self->fd, PERF_EVENT_IOC_ENABLE, 0), 0);
while (!signal_count);
_exit(42);
}
while (!signal_count); /* Child enables event. */
EXPECT_EQ(waitpid(pid, &status, 0), pid);
EXPECT_EQ(WEXITSTATUS(status), 42);
}
/*
* Verify that event does _not_ propagate to fork+exec'd child; event enabled
* after fork+exec.
*/
TEST_F(remove_on_exec, fork_exec_then_enable)
{
pid_t pid_exec, pid_only_fork;
int pipefd[2];
int tmp;
/*
* Non-exec child, to ensure exec does not affect inherited events of
* other children.
*/
pid_only_fork = fork();
if (pid_only_fork == 0) {
/* Block until parent enables event. */
while (!signal_count);
_exit(42);
}
ASSERT_NE(pipe(pipefd), -1);
pid_exec = fork();
if (pid_exec == 0) {
ASSERT_NE(dup2(pipefd[1], STDOUT_FILENO), -1);
close(pipefd[0]);
execl("/proc/self/exe", "exec_child", NULL);
_exit((perror("exec failed"), 1));
}
close(pipefd[1]);
ASSERT_EQ(waitpid(pid_exec, &tmp, WNOHANG), 0); /* Child is running. */
/* Wait for exec'd child to start spinning. */
EXPECT_EQ(read(pipefd[0], &tmp, sizeof(int)), sizeof(int));
EXPECT_EQ(tmp, 42);
close(pipefd[0]);
/* Now we can enable the event, knowing the child is doing work. */
EXPECT_EQ(ioctl(self->fd, PERF_EVENT_IOC_ENABLE, 0), 0);
/* If the event propagated to the exec'd child, it will exit normally... */
usleep(100000); /* ... give time for event to trigger (in case of bug). */
EXPECT_EQ(waitpid(pid_exec, &tmp, WNOHANG), 0); /* Should still be running. */
EXPECT_EQ(kill(pid_exec, SIGKILL), 0);
/* Verify removal from child did not affect this task's event. */
tmp = signal_count;
while (signal_count == tmp); /* Should not hang! */
/* Nor should it have affected the first child. */
EXPECT_EQ(waitpid(pid_only_fork, &tmp, 0), pid_only_fork);
EXPECT_EQ(WEXITSTATUS(tmp), 42);
}
/*
* Verify that event does _not_ propagate to fork+exec'd child; event enabled
* before fork+exec.
*/
TEST_F(remove_on_exec, enable_then_fork_exec)
{
pid_t pid_exec;
int tmp;
EXPECT_EQ(ioctl(self->fd, PERF_EVENT_IOC_ENABLE, 0), 0);
pid_exec = fork();
if (pid_exec == 0) {
execl("/proc/self/exe", "exec_child", NULL);
_exit((perror("exec failed"), 1));
}
/*
* The child may exit abnormally at any time if the event propagated and
* a SIGTRAP is sent before the handler was set up.
*/
usleep(100000); /* ... give time for event to trigger (in case of bug). */
EXPECT_EQ(waitpid(pid_exec, &tmp, WNOHANG), 0); /* Should still be running. */
EXPECT_EQ(kill(pid_exec, SIGKILL), 0);
/* Verify removal from child did not affect this task's event. */
tmp = signal_count;
while (signal_count == tmp); /* Should not hang! */
}
TEST_F(remove_on_exec, exec_stress)
{
pid_t pids[30];
int i, tmp;
for (i = 0; i < sizeof(pids) / sizeof(pids[0]); i++) {
pids[i] = fork();
if (pids[i] == 0) {
execl("/proc/self/exe", "exec_child", NULL);
_exit((perror("exec failed"), 1));
}
/* Some forked with event disabled, rest with enabled. */
if (i > 10)
EXPECT_EQ(ioctl(self->fd, PERF_EVENT_IOC_ENABLE, 0), 0);
}
usleep(100000); /* ... give time for event to trigger (in case of bug). */
for (i = 0; i < sizeof(pids) / sizeof(pids[0]); i++) {
/* All children should still be running. */
EXPECT_EQ(waitpid(pids[i], &tmp, WNOHANG), 0);
EXPECT_EQ(kill(pids[i], SIGKILL), 0);
}
/* Verify event is still alive. */
tmp = signal_count;
while (signal_count == tmp);
}
/* For exec'd child. */
static void exec_child(void)
{
struct sigaction action = {};
const int val = 42;
/* Set up sigtrap handler in case we erroneously receive a trap. */
action.sa_flags = SA_SIGINFO | SA_NODEFER;
action.sa_sigaction = sigtrap_handler;
sigemptyset(&action.sa_mask);
if (sigaction(SIGTRAP, &action, NULL))
_exit((perror("sigaction failed"), 1));
/* Signal parent that we're starting to spin. */
if (write(STDOUT_FILENO, &val, sizeof(int)) == -1)
_exit((perror("write failed"), 1));
/* Should hang here until killed. */
while (!signal_count);
}
#define main test_main
TEST_HARNESS_MAIN
#undef main
int main(int argc, char *argv[])
{
if (!strcmp(argv[0], "exec_child")) {
exec_child();
return 1;
}
return test_main(argc, argv);
}
@@ -0,0 +1 @@
timeout=120
@@ -0,0 +1,240 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Test for perf events with SIGTRAP across all threads.
*
* Copyright (C) 2021, Google LLC.
*/
#define _GNU_SOURCE
/* We need the latest siginfo from the kernel repo. */
#include <sys/types.h>
#include <asm/siginfo.h>
#define __have_siginfo_t 1
#define __have_sigval_t 1
#define __have_sigevent_t 1
#define __siginfo_t_defined
#define __sigval_t_defined
#define __sigevent_t_defined
#define _BITS_SIGINFO_CONSTS_H 1
#define _BITS_SIGEVENT_CONSTS_H 1
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <linux/hw_breakpoint.h>
#include <linux/perf_event.h>
#include <pthread.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <unistd.h>
#include "kselftest_harness.h"
#define NUM_THREADS 5
/* Data shared between test body, threads, and signal handler. */
static struct {
int tids_want_signal; /* Which threads still want a signal. */
int signal_count; /* Sanity check number of signals received. */
volatile int iterate_on; /* Variable to set breakpoint on. */
siginfo_t first_siginfo; /* First observed siginfo_t. */
} ctx;
/* Unique value to check si_perf_data is correctly set from perf_event_attr::sig_data. */
#define TEST_SIG_DATA(addr, id) (~(unsigned long)(addr) + id)
static struct perf_event_attr make_event_attr(bool enabled, volatile void *addr,
unsigned long id)
{
struct perf_event_attr attr = {
.type = PERF_TYPE_BREAKPOINT,
.size = sizeof(attr),
.sample_period = 1,
.disabled = !enabled,
.bp_addr = (unsigned long)addr,
.bp_type = HW_BREAKPOINT_RW,
.bp_len = HW_BREAKPOINT_LEN_1,
.inherit = 1, /* Children inherit events ... */
.inherit_thread = 1, /* ... but only cloned with CLONE_THREAD. */
.remove_on_exec = 1, /* Required by sigtrap. */
.sigtrap = 1, /* Request synchronous SIGTRAP on event. */
.sig_data = TEST_SIG_DATA(addr, id),
.exclude_kernel = 1, /* To allow */
.exclude_hv = 1, /* running as !root */
};
return attr;
}
static void sigtrap_handler(int signum, siginfo_t *info, void *ucontext)
{
if (info->si_code != TRAP_PERF) {
fprintf(stderr, "%s: unexpected si_code %d\n", __func__, info->si_code);
return;
}
/*
* The data in siginfo_t we're interested in should all be the same
* across threads.
*/
if (!__atomic_fetch_add(&ctx.signal_count, 1, __ATOMIC_RELAXED))
ctx.first_siginfo = *info;
__atomic_fetch_sub(&ctx.tids_want_signal, syscall(__NR_gettid), __ATOMIC_RELAXED);
}
static void *test_thread(void *arg)
{
pthread_barrier_t *barrier = (pthread_barrier_t *)arg;
pid_t tid = syscall(__NR_gettid);
int iter;
int i;
pthread_barrier_wait(barrier);
__atomic_fetch_add(&ctx.tids_want_signal, tid, __ATOMIC_RELAXED);
iter = ctx.iterate_on; /* read */
if (iter >= 0) {
for (i = 0; i < iter - 1; i++) {
__atomic_fetch_add(&ctx.tids_want_signal, tid, __ATOMIC_RELAXED);
ctx.iterate_on = iter; /* idempotent write */
}
} else {
while (ctx.iterate_on);
}
return NULL;
}
FIXTURE(sigtrap_threads)
{
struct sigaction oldact;
pthread_t threads[NUM_THREADS];
pthread_barrier_t barrier;
int fd;
};
FIXTURE_SETUP(sigtrap_threads)
{
struct perf_event_attr attr = make_event_attr(false, &ctx.iterate_on, 0);
struct sigaction action = {};
int i;
memset(&ctx, 0, sizeof(ctx));
/* Initialize sigtrap handler. */
action.sa_flags = SA_SIGINFO | SA_NODEFER;
action.sa_sigaction = sigtrap_handler;
sigemptyset(&action.sa_mask);
ASSERT_EQ(sigaction(SIGTRAP, &action, &self->oldact), 0);
/* Initialize perf event. */
self->fd = syscall(__NR_perf_event_open, &attr, 0, -1, -1, PERF_FLAG_FD_CLOEXEC);
ASSERT_NE(self->fd, -1);
/* Spawn threads inheriting perf event. */
pthread_barrier_init(&self->barrier, NULL, NUM_THREADS + 1);
for (i = 0; i < NUM_THREADS; i++)
ASSERT_EQ(pthread_create(&self->threads[i], NULL, test_thread, &self->barrier), 0);
}
FIXTURE_TEARDOWN(sigtrap_threads)
{
pthread_barrier_destroy(&self->barrier);
close(self->fd);
sigaction(SIGTRAP, &self->oldact, NULL);
}
static void run_test_threads(struct __test_metadata *_metadata,
FIXTURE_DATA(sigtrap_threads) *self)
{
int i;
pthread_barrier_wait(&self->barrier);
for (i = 0; i < NUM_THREADS; i++)
ASSERT_EQ(pthread_join(self->threads[i], NULL), 0);
}
TEST_F(sigtrap_threads, remain_disabled)
{
run_test_threads(_metadata, self);
EXPECT_EQ(ctx.signal_count, 0);
EXPECT_NE(ctx.tids_want_signal, 0);
}
TEST_F(sigtrap_threads, enable_event)
{
EXPECT_EQ(ioctl(self->fd, PERF_EVENT_IOC_ENABLE, 0), 0);
run_test_threads(_metadata, self);
EXPECT_EQ(ctx.signal_count, NUM_THREADS);
EXPECT_EQ(ctx.tids_want_signal, 0);
EXPECT_EQ(ctx.first_siginfo.si_addr, &ctx.iterate_on);
EXPECT_EQ(ctx.first_siginfo.si_perf_type, PERF_TYPE_BREAKPOINT);
EXPECT_EQ(ctx.first_siginfo.si_perf_data, TEST_SIG_DATA(&ctx.iterate_on, 0));
/* Check enabled for parent. */
ctx.iterate_on = 0;
EXPECT_EQ(ctx.signal_count, NUM_THREADS + 1);
}
/* Test that modification propagates to all inherited events. */
TEST_F(sigtrap_threads, modify_and_enable_event)
{
struct perf_event_attr new_attr = make_event_attr(true, &ctx.iterate_on, 42);
EXPECT_EQ(ioctl(self->fd, PERF_EVENT_IOC_MODIFY_ATTRIBUTES, &new_attr), 0);
run_test_threads(_metadata, self);
EXPECT_EQ(ctx.signal_count, NUM_THREADS);
EXPECT_EQ(ctx.tids_want_signal, 0);
EXPECT_EQ(ctx.first_siginfo.si_addr, &ctx.iterate_on);
EXPECT_EQ(ctx.first_siginfo.si_perf_type, PERF_TYPE_BREAKPOINT);
EXPECT_EQ(ctx.first_siginfo.si_perf_data, TEST_SIG_DATA(&ctx.iterate_on, 42));
/* Check enabled for parent. */
ctx.iterate_on = 0;
EXPECT_EQ(ctx.signal_count, NUM_THREADS + 1);
}
/* Stress test event + signal handling. */
TEST_F(sigtrap_threads, signal_stress)
{
ctx.iterate_on = 3000;
EXPECT_EQ(ioctl(self->fd, PERF_EVENT_IOC_ENABLE, 0), 0);
run_test_threads(_metadata, self);
EXPECT_EQ(ioctl(self->fd, PERF_EVENT_IOC_DISABLE, 0), 0);
EXPECT_EQ(ctx.signal_count, NUM_THREADS * ctx.iterate_on);
EXPECT_EQ(ctx.tids_want_signal, 0);
EXPECT_EQ(ctx.first_siginfo.si_addr, &ctx.iterate_on);
EXPECT_EQ(ctx.first_siginfo.si_perf_type, PERF_TYPE_BREAKPOINT);
EXPECT_EQ(ctx.first_siginfo.si_perf_data, TEST_SIG_DATA(&ctx.iterate_on, 0));
}
TEST_F(sigtrap_threads, signal_stress_with_disable)
{
const int target_count = NUM_THREADS * 3000;
int i;
ctx.iterate_on = -1;
EXPECT_EQ(ioctl(self->fd, PERF_EVENT_IOC_ENABLE, 0), 0);
pthread_barrier_wait(&self->barrier);
while (__atomic_load_n(&ctx.signal_count, __ATOMIC_RELAXED) < target_count) {
EXPECT_EQ(ioctl(self->fd, PERF_EVENT_IOC_DISABLE, 0), 0);
EXPECT_EQ(ioctl(self->fd, PERF_EVENT_IOC_ENABLE, 0), 0);
}
ctx.iterate_on = 0;
for (i = 0; i < NUM_THREADS; i++)
ASSERT_EQ(pthread_join(self->threads[i], NULL), 0);
EXPECT_EQ(ioctl(self->fd, PERF_EVENT_IOC_DISABLE, 0), 0);
EXPECT_EQ(ctx.first_siginfo.si_addr, &ctx.iterate_on);
EXPECT_EQ(ctx.first_siginfo.si_perf_type, PERF_TYPE_BREAKPOINT);
EXPECT_EQ(ctx.first_siginfo.si_perf_data, TEST_SIG_DATA(&ctx.iterate_on, 0));
}
TEST_HARNESS_MAIN
@@ -0,0 +1,144 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <linux/perf_event.h>
#include <stddef.h>
#include <sched.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <unistd.h>
#include "kselftest_harness.h"
static int sigio_count;
static void handle_sigio(int signum __maybe_unused,
siginfo_t *oh __maybe_unused,
void *uc __maybe_unused)
{
++sigio_count;
}
static void do_child(void)
{
raise(SIGSTOP);
for (int i = 0; i < 20; ++i)
sleep(1);
raise(SIGSTOP);
exit(0);
}
TEST(watermark_signal)
{
struct perf_event_attr attr;
struct perf_event_mmap_page *p = NULL;
struct sigaction previous_sigio, sigio = { 0 };
pid_t child = -1;
int child_status;
int fd = -1;
long page_size = sysconf(_SC_PAGE_SIZE);
sigio.sa_sigaction = handle_sigio;
EXPECT_EQ(sigaction(SIGIO, &sigio, &previous_sigio), 0);
memset(&attr, 0, sizeof(attr));
attr.size = sizeof(attr);
attr.type = PERF_TYPE_SOFTWARE;
attr.config = PERF_COUNT_SW_DUMMY;
attr.sample_period = 1;
attr.disabled = 1;
attr.watermark = 1;
attr.context_switch = 1;
attr.wakeup_watermark = 1;
child = fork();
EXPECT_GE(child, 0);
if (child == 0)
do_child();
else if (child < 0) {
perror("fork()");
goto cleanup;
}
if (waitpid(child, &child_status, WSTOPPED) != child ||
!(WIFSTOPPED(child_status) && WSTOPSIG(child_status) == SIGSTOP)) {
fprintf(stderr,
"failed to synchronize with child errno=%d status=%x\n",
errno,
child_status);
goto cleanup;
}
fd = syscall(__NR_perf_event_open, &attr, child, -1, -1,
PERF_FLAG_FD_CLOEXEC);
if (fd < 0) {
fprintf(stderr, "failed opening event %llx\n", attr.config);
goto cleanup;
}
if (fcntl(fd, F_SETFL, FASYNC)) {
perror("F_SETFL FASYNC");
goto cleanup;
}
if (fcntl(fd, F_SETOWN, getpid())) {
perror("F_SETOWN getpid()");
goto cleanup;
}
if (fcntl(fd, F_SETSIG, SIGIO)) {
perror("F_SETSIG SIGIO");
goto cleanup;
}
p = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED) {
perror("mmap");
goto cleanup;
}
if (ioctl(fd, PERF_EVENT_IOC_ENABLE, 0)) {
perror("PERF_EVENT_IOC_ENABLE");
goto cleanup;
}
if (kill(child, SIGCONT) < 0) {
perror("SIGCONT");
goto cleanup;
}
if (waitpid(child, &child_status, WSTOPPED) != -1 || errno != EINTR)
fprintf(stderr,
"expected SIGIO to terminate wait errno=%d status=%x\n%d",
errno,
child_status,
sigio_count);
EXPECT_GE(sigio_count, 1);
cleanup:
if (p != NULL)
munmap(p, 2 * page_size);
if (fd >= 0)
close(fd);
if (child > 0) {
kill(child, SIGKILL);
waitpid(child, NULL, 0);
}
sigaction(SIGIO, &previous_sigio, NULL);
}
TEST_HARNESS_MAIN