* [PATCH 1/6] selftests/x86/xstate: Track testing status by kselftest API
2026-11-10 3:15 [PATCH 0/6] selftests/x86/xstate: more test cases for coredump and ptrace Chang S. Bae
@ 2026-11-10 3:15 ` Chang S. Bae
2026-11-10 3:15 ` [PATCH 2/6] selftests/x86/xstate: Pre-allocate ptracee XSAVE buffer Chang S. Bae
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Chang S. Bae @ 2026-11-10 3:15 UTC (permalink / raw)
To: linux-kernel, linux-kselftest
Cc: x86, tglx, mingo, bp, dave.hansen, shuah, chang.seok.bae
The XSTATE facility is shared across different component testing.
Currently, it relies on plain printf() for reporting results, which
provides less convenient way to track failures or determine the overall
test status.
Convert the test code to use the kselftest framework for result tracking
and and a return value in the end.
While at it, also shorten and refine the test messages a bit.
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
---
tools/testing/selftests/x86/amx.c | 5 +-
tools/testing/selftests/x86/apx.c | 2 +-
tools/testing/selftests/x86/avx.c | 33 +++++-
tools/testing/selftests/x86/xstate.c | 155 +++++++++++++++++++--------
tools/testing/selftests/x86/xstate.h | 2 +-
5 files changed, 142 insertions(+), 55 deletions(-)
diff --git a/tools/testing/selftests/x86/amx.c b/tools/testing/selftests/x86/amx.c
index 40769c16de1b..1e4206c8a03a 100644
--- a/tools/testing/selftests/x86/amx.c
+++ b/tools/testing/selftests/x86/amx.c
@@ -482,6 +482,7 @@ int main(void)
{
unsigned long features;
long rc;
+ int ret;
rc = syscall(SYS_arch_prctl, ARCH_GET_XCOMP_SUPP, &features);
if (rc || (features & XFEATURE_MASK_XTILE) != XFEATURE_MASK_XTILE) {
@@ -509,10 +510,10 @@ int main(void)
* Perform generic xstate tests for context switching, ptrace,
* and signal.
*/
- test_xstate(XFEATURE_XTILEDATA);
+ ret = test_xstate(XFEATURE_XTILEDATA);
clearhandler(SIGILL);
free_stashed_xsave();
- return 0;
+ return ret;
}
diff --git a/tools/testing/selftests/x86/apx.c b/tools/testing/selftests/x86/apx.c
index d9c8d41b8c5a..8d353b75bca1 100644
--- a/tools/testing/selftests/x86/apx.c
+++ b/tools/testing/selftests/x86/apx.c
@@ -6,5 +6,5 @@
int main(void)
{
- test_xstate(XFEATURE_APX);
+ return test_xstate(XFEATURE_APX);
}
diff --git a/tools/testing/selftests/x86/avx.c b/tools/testing/selftests/x86/avx.c
index 11d5367c235f..2b79da16ee2b 100644
--- a/tools/testing/selftests/x86/avx.c
+++ b/tools/testing/selftests/x86/avx.c
@@ -3,10 +3,35 @@
#define _GNU_SOURCE /* Required for inline xstate helpers */
#include "xstate.h"
+static uint32_t avx_features[] = {
+ XFEATURE_YMM,
+ XFEATURE_OPMASK,
+ XFEATURE_ZMM_Hi256,
+ XFEATURE_Hi16_ZMM,
+};
+
int main(void)
{
- test_xstate(XFEATURE_YMM);
- test_xstate(XFEATURE_OPMASK);
- test_xstate(XFEATURE_ZMM_Hi256);
- test_xstate(XFEATURE_Hi16_ZMM);
+ unsigned int failed = 0, skipped = 0;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(avx_features); i++) {
+ switch (test_xstate(avx_features[i])) {
+ case KSFT_FAIL:
+ failed++;
+ break;
+ case KSFT_SKIP:
+ skipped++;
+ break;
+ case KSFT_PASS:
+ default:
+ }
+ }
+
+ if (skipped == ARRAY_SIZE(avx_features))
+ return KSFT_SKIP;
+ else if (failed)
+ return KSFT_FAIL;
+ else
+ return KSFT_PASS;
}
diff --git a/tools/testing/selftests/x86/xstate.c b/tools/testing/selftests/x86/xstate.c
index 0ab577157cd7..c26dbe33622f 100644
--- a/tools/testing/selftests/x86/xstate.c
+++ b/tools/testing/selftests/x86/xstate.c
@@ -198,15 +198,16 @@ static void affinitize_cpu0(void)
ksft_exit_fail_msg("sched_setaffinity to CPU 0 failed\n");
}
-static void test_context_switch(uint32_t num_threads, uint32_t iterations)
+static void test_context_switch(void)
{
+ const unsigned int num_threads = 5, iterations = 10;
struct futex_info *finfo;
/* Affinitize to one CPU to force context switches */
affinitize_cpu0();
- printf("[RUN]\t%s: check context switches, %d iterations, %d threads.\n",
- xstate.name, iterations, num_threads);
+ ksft_print_msg("[RUN]\t%s: check context switches, %d iterations, %d threads.\n",
+ xstate.name, iterations, num_threads);
finfo = malloc(sizeof(*finfo) * num_threads);
if (!finfo)
@@ -228,9 +229,9 @@ static void test_context_switch(uint32_t num_threads, uint32_t iterations)
/* Wait for all the threads to finish: */
if (checkout_threads(num_threads, finfo))
- printf("[OK]\tNo incorrect case was found.\n");
+ ksft_test_result_pass("%s for %s\n", __func__, xstate.name);
else
- printf("[FAIL]\tFailed with context switching test.\n");
+ ksft_test_result_fail("%s for %s\n", __func__, xstate.name);
free(finfo);
}
@@ -256,6 +257,40 @@ static inline void ptracee_touch_xstate(void)
free(xbuf);
}
+static bool validate_ptracer_xstate(pid_t target, struct iovec iov,
+ struct xsave_buffer *xbuf,
+ struct xsave_buffer *expected)
+{
+ unsigned int failed = 0;
+
+ if (ptrace(PTRACE_SETREGSET, target, (uint32_t)NT_X86_XSTATE, &iov))
+ ksft_exit_fail_msg("PTRACE_SETREGSET failed\n");
+
+ if (ptrace(PTRACE_GETREGSET, target, (uint32_t)NT_X86_XSTATE, &iov))
+ ksft_exit_fail_msg("PTRACE_GETREGSET failed\n");
+
+ /*
+ * To track this whole result only, use printf() for skipping sub-test
+ * counting.
+ */
+
+ if (*(uint64_t *)get_fpx_sw_bytes(xbuf) == xgetbv(0)) {
+ printf("#\tok\tSW reserved area\n");
+ } else {
+ printf("#\tnot ok\tSW reserved area\n");
+ failed++;
+ }
+
+ if (validate_xstate_same(xbuf, expected)) {
+ printf("#\tok\txstate area\n");
+ } else {
+ printf("#\tnot ok\txstate area\n");
+ failed++;
+ }
+
+ return failed == 0;
+}
+
/*
* Ptracer injects the randomized xstate data. It also reads before and
* after that, which will execute the kernel's state copy functions.
@@ -281,26 +316,15 @@ static void ptracer_inject_xstate(pid_t target)
if (ptrace(PTRACE_GETREGSET, target, (uint32_t)NT_X86_XSTATE, &iov))
ksft_exit_fail_msg("PTRACE_GETREGSET failed\n");
- printf("[RUN]\t%s: inject xstate via ptrace().\n", xstate.name);
+ ksft_print_msg("[RUN]\t%s: inject xstate via ptrace().\n", xstate.name);
load_rand_xstate(&xstate, xbuf1);
copy_xstate(xbuf2, xbuf1);
- if (ptrace(PTRACE_SETREGSET, target, (uint32_t)NT_X86_XSTATE, &iov))
- ksft_exit_fail_msg("PTRACE_SETREGSET failed\n");
-
- if (ptrace(PTRACE_GETREGSET, target, (uint32_t)NT_X86_XSTATE, &iov))
- ksft_exit_fail_msg("PTRACE_GETREGSET failed\n");
-
- if (*(uint64_t *)get_fpx_sw_bytes(xbuf1) == xgetbv(0))
- printf("[OK]\t'xfeatures' in SW reserved area was correctly written\n");
+ if (validate_ptracer_xstate(target, iov, xbuf1, xbuf2))
+ ksft_test_result_pass("%s for %s\n", __func__, xstate.name);
else
- printf("[FAIL]\t'xfeatures' in SW reserved area was not correctly written\n");
-
- if (validate_xstate_same(xbuf2, xbuf1))
- printf("[OK]\txstate was correctly updated.\n");
- else
- printf("[FAIL]\txstate was not correctly updated.\n");
+ ksft_test_result_fail("%s for %s\n", __func__, xstate.name);
free(xbuf1);
free(xbuf2);
@@ -347,13 +371,26 @@ static void test_ptrace(void)
*/
#define SIGNAL_BUF_LEN 1000
static char signal_message_buffer[SIGNAL_BUF_LEN];
-static void sig_print(char *msg)
+static inline void sig_print(char *msg)
{
int left = SIGNAL_BUF_LEN - strlen(signal_message_buffer) - 1;
strncat(signal_message_buffer, msg, left);
}
+static unsigned int sig_failed;
+
+static inline void sig_print_pass(char *msg)
+{
+ sig_print(msg);
+}
+
+static inline void sig_print_fail(char *msg)
+{
+ sig_failed++;
+ sig_print(msg);
+}
+
static struct xsave_buffer *stashed_xbuf;
static void validate_sigfpstate(int sig, siginfo_t *si, void *ctx_void)
@@ -368,30 +405,30 @@ static void validate_sigfpstate(int sig, siginfo_t *si, void *ctx_void)
sw_bytes = get_fpx_sw_bytes(xbuf);
if (sw_bytes->magic1 == FP_XSTATE_MAGIC1)
- sig_print("[OK]\t'magic1' is valid\n");
+ sig_print_pass("#\tok\tmagic1\n");
else
- sig_print("[FAIL]\t'magic1' is not valid\n");
+ sig_print_fail("#\tnot ok\tmagic1\n");
if (get_fpx_sw_bytes_features(xbuf) & xstate.mask)
- sig_print("[OK]\t'xfeatures' in SW reserved area is valid\n");
+ sig_print_pass("#\tok\tSW reserved area\n");
else
- sig_print("[FAIL]\t'xfeatures' in SW reserved area is not valid\n");
+ sig_print_fail("#\tnot ok\tSW reserved area\n");
if (get_xstatebv(xbuf) & xstate.mask)
- sig_print("[OK]\t'xfeatures' in XSAVE header is valid\n");
+ sig_print_pass("#\tok\tXSTATE_BV\n");
else
- sig_print("[FAIL]\t'xfeatures' in XSAVE header is not valid\n");
+ sig_print_fail("#\tnot ok\tXSTATE_BV\n");
if (validate_xstate_same(stashed_xbuf, xbuf))
- sig_print("[OK]\txstate delivery was successful\n");
+ sig_print_pass("#\tok\txstate area\n");
else
- sig_print("[FAIL]\txstate delivery was not successful\n");
+ sig_print_fail("#\tnot ok\txstate area\n");
magic2 = *(uint32_t *)(xbuf + sw_bytes->xstate_size);
if (magic2 == FP_XSTATE_MAGIC2)
- sig_print("[OK]\t'magic2' is valid\n");
+ sig_print_pass("#\tok\tmagic2\n");
else
- sig_print("[FAIL]\t'magic2' is not valid\n");
+ sig_print_fail("#\tnot ok\tmagic2\n");
set_rand_data(&xstate, xbuf);
copy_xstate(stashed_xbuf, xbuf);
@@ -399,7 +436,7 @@ static void validate_sigfpstate(int sig, siginfo_t *si, void *ctx_void)
static void test_signal(void)
{
- bool valid_xstate;
+ bool sigreturn_failed;
/*
* The signal handler will access this to verify xstate context
@@ -409,49 +446,62 @@ static void test_signal(void)
if (!stashed_xbuf)
ksft_exit_fail_msg("unable to allocate XSAVE buffer\n");
- printf("[RUN]\t%s: load xstate and raise SIGUSR1\n", xstate.name);
+ ksft_print_msg("[RUN]\t%s: validate signal delivery\n", xstate.name);
sethandler(SIGUSR1, validate_sigfpstate, 0);
load_rand_xstate(&xstate, stashed_xbuf);
+ sig_failed = 0;
raise(SIGUSR1);
/*
* Immediately record the test result, deferring printf() to
* prevent unintended state contamination by that.
*/
- valid_xstate = validate_xregs_same(stashed_xbuf);
+ sigreturn_failed = !validate_xregs_same(stashed_xbuf);
printf("%s", signal_message_buffer);
+ if (!sig_failed)
+ ksft_test_result_pass("%s (delivery) for %s\n", __func__, xstate.name);
+ else
+ ksft_test_result_fail("%s (delivery) for %s\n", __func__, xstate.name);
- printf("[RUN]\t%s: load new xstate from sighandler and check it after sigreturn\n",
- xstate.name);
+ ksft_print_msg("[RUN]\t%s: update xstate on sigframe, then validate xstate on sigreturn\n",
+ xstate.name);
- if (valid_xstate)
- printf("[OK]\txstate was restored correctly\n");
+ if (!sigreturn_failed)
+ ksft_test_result_pass("%s (return) for %s\n", __func__, xstate.name);
else
- printf("[FAIL]\txstate restoration failed\n");
+ ksft_test_result_fail("%s (return) for %s\n", __func__, xstate.name);
clearhandler(SIGUSR1);
free(stashed_xbuf);
}
-void test_xstate(uint32_t feature_num)
+static struct test_info {
+ void (*fn)(void);
+ unsigned int nr;
+} tests[] = {
+ { .nr = 1, .fn = test_context_switch, },
+ { .nr = 1, .fn = test_ptrace, },
+ { .nr = 2, .fn = test_signal, },
+};
+
+int test_xstate(uint32_t feature_num)
{
- const unsigned int ctxtsw_num_threads = 5, ctxtsw_iterations = 10;
unsigned long features;
- long rc;
+ long rc, i, nr = 0;
if (!(XFEATURE_MASK_TEST_SUPPORTED & (1 << feature_num))) {
ksft_print_msg("The xstate test does not fully support the component %u, yet.\n",
feature_num);
- return;
+ return KSFT_SKIP;
}
rc = syscall(SYS_arch_prctl, ARCH_GET_XCOMP_SUPP, &features);
if (rc || !(features & (1 << feature_num))) {
ksft_print_msg("The kernel does not support feature number: %u\n", feature_num);
- return;
+ return KSFT_SKIP;
}
xstate = get_xstate_info(feature_num);
@@ -460,7 +510,18 @@ void test_xstate(uint32_t feature_num)
xstate.size, xstate.xbuf_offset);
}
- test_context_switch(ctxtsw_num_threads, ctxtsw_iterations);
- test_ptrace();
- test_signal();
+ ksft_reset_state();
+ ksft_print_header();
+
+ for (i = 0; i < ARRAY_SIZE(tests); i++)
+ nr += tests[i].nr;
+
+ ksft_set_plan(nr);
+
+ for (i = 0; i < ARRAY_SIZE(tests); i++)
+ tests[i].fn();
+
+ ksft_print_cnts();
+
+ return (ksft_cnt.ksft_fail == 0) ? KSFT_PASS : KSFT_FAIL;
}
diff --git a/tools/testing/selftests/x86/xstate.h b/tools/testing/selftests/x86/xstate.h
index eedf0cab7ccb..5e712c979dc1 100644
--- a/tools/testing/selftests/x86/xstate.h
+++ b/tools/testing/selftests/x86/xstate.h
@@ -212,6 +212,6 @@ static inline void set_rand_data(struct xstate_info *xstate, struct xsave_buffer
}
/* Testing kernel's context switching and ABI support for the xstate. */
-void test_xstate(uint32_t feature_num);
+int test_xstate(uint32_t feature_num);
#endif /* __SELFTESTS_X86_XSTATE_H */
--
2.53.0
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH 2/6] selftests/x86/xstate: Pre-allocate ptracee XSAVE buffer
2026-11-10 3:15 [PATCH 0/6] selftests/x86/xstate: more test cases for coredump and ptrace Chang S. Bae
2026-11-10 3:15 ` [PATCH 1/6] selftests/x86/xstate: Track testing status by kselftest API Chang S. Bae
@ 2026-11-10 3:15 ` Chang S. Bae
2026-11-10 3:16 ` [PATCH 3/6] selftests/x86/xstate: Validate XSTATE_BV in ptrace test Chang S. Bae
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Chang S. Bae @ 2026-11-10 3:15 UTC (permalink / raw)
To: linux-kernel, linux-kselftest
Cc: x86, tglx, mingo, bp, dave.hansen, shuah, chang.seok.bae
Currently, the ptrace test does not handle XSAVE buffer allocation
failures in the child process. But such a failure can lead to an
unexpected exit path, complicating error handling.
Instead, allocate the buffer in the parent process so that
allocation and error handling are handled before forking the child.
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
---
tools/testing/selftests/x86/xstate.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/tools/testing/selftests/x86/xstate.c b/tools/testing/selftests/x86/xstate.c
index c26dbe33622f..240567e79e96 100644
--- a/tools/testing/selftests/x86/xstate.c
+++ b/tools/testing/selftests/x86/xstate.c
@@ -236,6 +236,8 @@ static void test_context_switch(void)
free(finfo);
}
+static struct xsave_buffer *stashed_xbuf;
+
/*
* Ptrace test for the ABI format as described in arch/x86/include/asm/user.h
*/
@@ -247,14 +249,8 @@ static void test_context_switch(void)
*/
static inline void ptracee_touch_xstate(void)
{
- struct xsave_buffer *xbuf;
-
- xbuf = alloc_xbuf();
-
- load_rand_xstate(&xstate, xbuf);
- load_init_xstate(&xstate, xbuf);
-
- free(xbuf);
+ load_rand_xstate(&xstate, stashed_xbuf);
+ load_init_xstate(&xstate, stashed_xbuf);
}
static bool validate_ptracer_xstate(pid_t target, struct iovec iov,
@@ -335,6 +331,10 @@ static void test_ptrace(void)
pid_t child;
int status;
+ stashed_xbuf = alloc_xbuf();
+ if (!stashed_xbuf)
+ ksft_exit_fail_msg("unable to allocate XSAVE buffer\n");
+
child = fork();
if (child < 0) {
ksft_exit_fail_msg("fork() failed\n");
@@ -358,6 +358,8 @@ static void test_ptrace(void)
wait(&status);
if (!WIFEXITED(status) || WEXITSTATUS(status))
ksft_exit_fail_msg("ptracee exit error\n");
+
+ free(stashed_xbuf);
}
/*
@@ -391,8 +393,6 @@ static inline void sig_print_fail(char *msg)
sig_print(msg);
}
-static struct xsave_buffer *stashed_xbuf;
-
static void validate_sigfpstate(int sig, siginfo_t *si, void *ctx_void)
{
ucontext_t *ctx = (ucontext_t *)ctx_void;
--
2.53.0
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH 3/6] selftests/x86/xstate: Validate XSTATE_BV in ptrace test
2026-11-10 3:15 [PATCH 0/6] selftests/x86/xstate: more test cases for coredump and ptrace Chang S. Bae
2026-11-10 3:15 ` [PATCH 1/6] selftests/x86/xstate: Track testing status by kselftest API Chang S. Bae
2026-11-10 3:15 ` [PATCH 2/6] selftests/x86/xstate: Pre-allocate ptracee XSAVE buffer Chang S. Bae
@ 2026-11-10 3:16 ` Chang S. Bae
2026-11-10 3:16 ` [PATCH 4/6] selftests/x86/xstate: Validate state reset " Chang S. Bae
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Chang S. Bae @ 2026-11-10 3:16 UTC (permalink / raw)
To: linux-kernel, linux-kselftest
Cc: x86, tglx, mingo, bp, dave.hansen, shuah, chang.seok.bae
The ptrace test does not currently validate XSTATE_BV in the XSAVE
header. Add a check for it.
The component bit may legitimately be clear when testing a reset state.
So, ensure the expected bit setting first and then compare the component
bit directly.
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
---
tools/testing/selftests/x86/xstate.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/tools/testing/selftests/x86/xstate.c b/tools/testing/selftests/x86/xstate.c
index 240567e79e96..93ba4b5f1a08 100644
--- a/tools/testing/selftests/x86/xstate.c
+++ b/tools/testing/selftests/x86/xstate.c
@@ -62,6 +62,7 @@ static inline void load_init_xstate(struct xstate_info *xstate, struct xsave_buf
static inline void copy_xstate(struct xsave_buffer *xbuf_dst, struct xsave_buffer *xbuf_src)
{
+ memcpy(xbuf_dst->header, xbuf_src->header, XSAVE_HDR_SIZE);
memcpy(&xbuf_dst->bytes[xstate.xbuf_offset],
&xbuf_src->bytes[xstate.xbuf_offset],
xstate.size);
@@ -277,6 +278,13 @@ static bool validate_ptracer_xstate(pid_t target, struct iovec iov,
failed++;
}
+ if ((xstate.mask & get_xstatebv(xbuf)) == (xstate.mask & get_xstatebv(expected))) {
+ printf("#\tok\tXSTATE_BV\n");
+ } else {
+ printf("#\tnot ok\tXSTATE_BV\n");
+ failed++;
+ }
+
if (validate_xstate_same(xbuf, expected)) {
printf("#\tok\txstate area\n");
} else {
--
2.53.0
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH 4/6] selftests/x86/xstate: Validate state reset in ptrace test
2026-11-10 3:15 [PATCH 0/6] selftests/x86/xstate: more test cases for coredump and ptrace Chang S. Bae
` (2 preceding siblings ...)
2026-11-10 3:16 ` [PATCH 3/6] selftests/x86/xstate: Validate XSTATE_BV in ptrace test Chang S. Bae
@ 2026-11-10 3:16 ` Chang S. Bae
2026-11-10 3:16 ` [PATCH 5/6] selftests/x86/xstate: Add coredump test Chang S. Bae
2026-11-10 3:16 ` [PATCH 6/6] selftests/mm: Add coredump test for x86 Chang S. Bae
5 siblings, 0 replies; 7+ messages in thread
From: Chang S. Bae @ 2026-11-10 3:16 UTC (permalink / raw)
To: linux-kernel, linux-kselftest
Cc: x86, tglx, mingo, bp, dave.hansen, shuah, chang.seok.bae, Jim Mattson
Clearing a feature bit in XSTATE_BV should cause the state component to
be initialized when the ptracer retrieves the state. Validate this
behavior.
Cc: Jim Mattson <jmattson@google.com>
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
---
I missed this before. During a review on my other thing, I realized and
now adding... Thanks, Jim Mattson!
---
tools/testing/selftests/x86/xstate.c | 20 +++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/x86/xstate.c b/tools/testing/selftests/x86/xstate.c
index 93ba4b5f1a08..a2f25ca848fb 100644
--- a/tools/testing/selftests/x86/xstate.c
+++ b/tools/testing/selftests/x86/xstate.c
@@ -330,6 +330,24 @@ static void ptracer_inject_xstate(pid_t target)
else
ksft_test_result_fail("%s for %s\n", __func__, xstate.name);
+ ksft_print_msg("[RUN]\t%s: reset xstate via ptrace().\n", xstate.name);
+
+ /* Reset the feature bit in XSTATE_BV only, expecting init-state back */
+ set_xstatebv(xbuf1, get_xstatebv(xbuf1) & ~xstate.mask);
+
+ /*
+ * Prepare reference data. Note not every XSAVE implementation resets
+ * the XSTATE_BV bit even though the component is in init-state.
+ */
+ load_init_xstate(&xstate, xbuf2);
+ xsave(xbuf2, xstate.mask);
+ set_xstatebv(xbuf2, get_xstatebv(xbuf2) & ~xstate.mask);
+
+ if (validate_ptracer_xstate(target, iov, xbuf1, xbuf2))
+ ksft_test_result_pass("%s (reset) for %s\n", __func__, xstate.name);
+ else
+ ksft_test_result_fail("%s (reset) for %s\n", __func__, xstate.name);
+
free(xbuf1);
free(xbuf2);
}
@@ -491,7 +509,7 @@ static struct test_info {
unsigned int nr;
} tests[] = {
{ .nr = 1, .fn = test_context_switch, },
- { .nr = 1, .fn = test_ptrace, },
+ { .nr = 2, .fn = test_ptrace, },
{ .nr = 2, .fn = test_signal, },
};
--
2.53.0
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH 5/6] selftests/x86/xstate: Add coredump test
2026-11-10 3:15 [PATCH 0/6] selftests/x86/xstate: more test cases for coredump and ptrace Chang S. Bae
` (3 preceding siblings ...)
2026-11-10 3:16 ` [PATCH 4/6] selftests/x86/xstate: Validate state reset " Chang S. Bae
@ 2026-11-10 3:16 ` Chang S. Bae
2026-11-10 3:16 ` [PATCH 6/6] selftests/mm: Add coredump test for x86 Chang S. Bae
5 siblings, 0 replies; 7+ messages in thread
From: Chang S. Bae @ 2026-11-10 3:16 UTC (permalink / raw)
To: linux-kernel, linux-kselftest
Cc: x86, tglx, mingo, bp, dave.hansen, shuah, chang.seok.bae
The existing ptrace test exercises much of the XSTATE ABI handling, but
it does not cover the coredump path entirely. Add a dedicated test to
validate XSTATE handling in core dumps.
Refactor the XSAVE buffer validation code from the ptrace test for reuse.
Also, move most of the coredump-specific preparation and handling into a
header so that the PKRU test outside the x86 selftest can reuse it.
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
---
tools/testing/selftests/x86/coredump.h | 229 +++++++++++++++++++++++++
tools/testing/selftests/x86/xstate.c | 99 ++++++++---
2 files changed, 308 insertions(+), 20 deletions(-)
create mode 100644 tools/testing/selftests/x86/coredump.h
diff --git a/tools/testing/selftests/x86/coredump.h b/tools/testing/selftests/x86/coredump.h
new file mode 100644
index 000000000000..8aef96667f87
--- /dev/null
+++ b/tools/testing/selftests/x86/coredump.h
@@ -0,0 +1,229 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#ifndef __SELFTESTS_X86_COREDUMP_H
+#define __SELFTESTS_X86_COREDUMP_H
+
+#include <link.h>
+#include <stdint.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include <sys/resource.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include "kselftest.h"
+
+#define CORE_PATTERN_PATH "/proc/sys/kernel/core_pattern"
+#ifndef CORENAME_MAX_SIZE
+#define CORENAME_MAX_SIZE 128
+#endif
+
+static char orig_pattern[CORENAME_MAX_SIZE];
+
+static inline bool get_core_pattern(char *buf, int size)
+{
+ bool ret;
+ FILE *fp;
+
+ if (!buf)
+ return false;
+
+ fp = fopen(CORE_PATTERN_PATH, "r");
+ if (!fp)
+ return false;
+
+ ret = fgets(buf, size, fp) != NULL;
+ if (ret)
+ buf[strcspn(buf, "\n")] = 0;
+
+ fclose(fp);
+
+ return ret;
+}
+
+static inline bool set_core_pattern(const char *pattern)
+{
+ bool ret;
+ FILE *fp;
+
+ if (!pattern)
+ return false;
+
+ fp = fopen(CORE_PATTERN_PATH, "w");
+ if (!fp)
+ return false;
+
+ ret = fprintf(fp, "%s\n", pattern) >= 0;
+
+ fclose(fp);
+
+ return ret;
+}
+
+static inline bool save_core_pattern(void)
+{
+ return get_core_pattern(orig_pattern, CORENAME_MAX_SIZE);
+}
+
+static inline bool set_test_core_pattern(void)
+{
+ const char test_pattern[] = "core.%p";
+
+ if (!strcmp(orig_pattern, test_pattern))
+ return true;
+
+ return set_core_pattern(test_pattern);
+}
+
+static inline bool restore_core_pattern(void)
+{
+ return set_core_pattern(orig_pattern);
+}
+
+static char core_dir[] = "/tmp/x86-coredump-XXXXXX";
+
+static struct corefile {
+ bool dumped;
+ int fd;
+ struct stat st;
+ char *image;
+ char path[PATH_MAX];
+} core_file = { false, -1, { }, NULL, "\0" };
+
+static inline void corefile_reset(void)
+{
+ close(core_file.fd);
+ unlink(core_file.path);
+
+ if (core_file.image != NULL && core_file.image != MAP_FAILED)
+ munmap((void *)core_file.image, core_file.st.st_size);
+
+ core_file = (struct corefile){ .dumped = false, .fd = -1, .st = { },
+ .image = NULL, .path = "\0" };
+}
+
+static inline void coredump_cleanup(void)
+{
+ if (core_file.dumped)
+ corefile_reset();
+
+ rmdir(core_dir);
+ strcpy(core_dir, "/tmp/x86-coredump-XXXXXX");
+ restore_core_pattern();
+}
+
+/*
+ * Create a temporary directory for the core file and configure core_pattern to
+ * use a predictable file name. Save the original pattern so it can be restored
+ * after the test.
+ */
+static inline bool coredump_prepare(void)
+{
+ struct rlimit lim = { RLIM_INFINITY, RLIM_INFINITY };
+
+ if (setrlimit(RLIMIT_CORE, &lim)) {
+ ksft_test_result_skip("Failed to set rlimit. core Try sudo.\n");
+ return false;
+ }
+
+ if (!save_core_pattern()) {
+ ksft_test_result_skip("Failed to save core pattern.\n");
+ return false;
+ }
+
+ if (!set_test_core_pattern()) {
+ ksft_test_result_skip("Failed to update core pattern. Try sudo.\n");
+ return false;
+ }
+
+ if (!mkdtemp(core_dir)) {
+ coredump_cleanup();
+ ksft_test_result_skip("Failed to create test folder.\n");
+ return false;
+ }
+
+ return true;
+}
+
+/* Always call this for any failed exit after coredump_prepare() */
+static inline void coredump_exit_fail_msg(const char *msg)
+{
+ coredump_cleanup();
+ ksft_exit_fail_msg("%s\n", msg);
+}
+
+static inline void coredump_trigger(void)
+{
+ volatile unsigned int *ptr = NULL;
+
+ *ptr = 0xDEAD;
+}
+
+static inline void corefile_map(pid_t child)
+{
+ snprintf(core_file.path, sizeof(core_file.path), "%s/core.%d", core_dir, child);
+
+ core_file.fd = open(core_file.path, O_RDONLY);
+ if (core_file.fd < 0)
+ coredump_exit_fail_msg("Cannot open core file.");
+
+ core_file.dumped = true;
+
+ if (fstat(core_file.fd, &core_file.st))
+ coredump_exit_fail_msg("Invalid core file.");
+
+ core_file.image = mmap(NULL, core_file.st.st_size, PROT_READ, MAP_PRIVATE, core_file.fd, 0);
+ if (core_file.image == MAP_FAILED)
+ coredump_exit_fail_msg("Cannot map core file.");
+}
+
+#ifndef NT_X86_XSTATE
+#define NT_X86_XSTATE 0x202
+#endif
+
+#define next_pos(pos, size) ((pos) + (((size) + 3) & ~3))
+
+/* Program Header */
+#define phdr_ptr(core, offset) (ElfW(Phdr) *)(core + offset)
+
+/* Note Header */
+#define nhdr_ptr(core, offset) (ElfW(Nhdr) *)(core + offset)
+
+/*
+ * Locate the NT_X86_XSTATE note in the ELF core image and return a pointer to
+ * its XSTATE data.
+ */
+static inline char *retrieve_corefile_xbuf(void)
+{
+ ElfW(Ehdr) *ehdr;
+ ElfW(Phdr) *phdr;
+ ElfW(Nhdr) *nhdr;
+ size_t pos, end;
+ int i;
+
+ ehdr = (ElfW(Ehdr) *)core_file.image;
+ phdr = phdr_ptr(core_file.image, ehdr->e_phoff);
+
+ for (i = 0; i < ehdr->e_phnum; i++) {
+ if (phdr[i].p_type != PT_NOTE)
+ continue;
+
+ end = phdr[i].p_offset + phdr[i].p_filesz;
+ pos = phdr[i].p_offset;
+
+ while (pos + sizeof(*nhdr) <= end) {
+ nhdr = nhdr_ptr(core_file.image, pos);
+ pos = next_pos(pos + sizeof(*nhdr), nhdr->n_namesz);
+
+ if (nhdr->n_type == NT_X86_XSTATE)
+ return core_file.image + pos;
+
+ pos = next_pos(pos, nhdr->n_descsz);
+ }
+ }
+
+ coredump_exit_fail_msg("No NT_X86_XSTATE was found.");
+ _exit(1);
+}
+
+#endif /* __SELFTESTS_X86_COREDUMP_H */
diff --git a/tools/testing/selftests/x86/xstate.c b/tools/testing/selftests/x86/xstate.c
index a2f25ca848fb..fd04fd6fa631 100644
--- a/tools/testing/selftests/x86/xstate.c
+++ b/tools/testing/selftests/x86/xstate.c
@@ -14,6 +14,7 @@
#include "helpers.h"
#include "xstate.h"
+#include "coredump.h"
/*
* The userspace xstate test suite is designed to be generic and operates
@@ -243,29 +244,11 @@ static struct xsave_buffer *stashed_xbuf;
* Ptrace test for the ABI format as described in arch/x86/include/asm/user.h
*/
-/*
- * Make sure the ptracee has the expanded kernel buffer on the first use.
- * Then, initialize the state before performing the state injection from
- * the ptracer. For non-dynamic states, this is benign.
- */
-static inline void ptracee_touch_xstate(void)
-{
- load_rand_xstate(&xstate, stashed_xbuf);
- load_init_xstate(&xstate, stashed_xbuf);
-}
-
-static bool validate_ptracer_xstate(pid_t target, struct iovec iov,
- struct xsave_buffer *xbuf,
- struct xsave_buffer *expected)
+static bool validate_abi_xstate(struct xsave_buffer *xbuf,
+ struct xsave_buffer *expected)
{
unsigned int failed = 0;
- if (ptrace(PTRACE_SETREGSET, target, (uint32_t)NT_X86_XSTATE, &iov))
- ksft_exit_fail_msg("PTRACE_SETREGSET failed\n");
-
- if (ptrace(PTRACE_GETREGSET, target, (uint32_t)NT_X86_XSTATE, &iov))
- ksft_exit_fail_msg("PTRACE_GETREGSET failed\n");
-
/*
* To track this whole result only, use printf() for skipping sub-test
* counting.
@@ -295,6 +278,30 @@ static bool validate_ptracer_xstate(pid_t target, struct iovec iov,
return failed == 0;
}
+/*
+ * Make sure the ptracee has the expanded kernel buffer on the first use.
+ * Then, initialize the state before performing the state injection from
+ * the ptracer. For non-dynamic states, this is benign.
+ */
+static inline void ptracee_touch_xstate(void)
+{
+ load_rand_xstate(&xstate, stashed_xbuf);
+ load_init_xstate(&xstate, stashed_xbuf);
+}
+
+static bool validate_ptracer_xstate(pid_t target, struct iovec iov,
+ struct xsave_buffer *xbuf,
+ struct xsave_buffer *expected)
+{
+ if (ptrace(PTRACE_SETREGSET, target, (uint32_t)NT_X86_XSTATE, &iov))
+ ksft_exit_fail_msg("PTRACE_SETREGSET failed\n");
+
+ if (ptrace(PTRACE_GETREGSET, target, (uint32_t)NT_X86_XSTATE, &iov))
+ ksft_exit_fail_msg("PTRACE_GETREGSET failed\n");
+
+ return validate_abi_xstate(xbuf, expected);
+}
+
/*
* Ptracer injects the randomized xstate data. It also reads before and
* after that, which will execute the kernel's state copy functions.
@@ -388,6 +395,57 @@ static void test_ptrace(void)
free(stashed_xbuf);
}
+static void test_coredump(void)
+{
+ struct xsave_buffer *xbuf, *corefile_xbuf;
+ pid_t child;
+ int status;
+
+ if (!coredump_prepare())
+ return;
+
+ ksft_print_msg("[RUN]\t%s: validate coredump.\n", xstate.name);
+
+ /*
+ * Use a shared buffer to retain the expected XSTATE across fork() and
+ * compare it against the XSTATE saved in the core dump.
+ */
+ xbuf = mmap(NULL, get_xbuf_size(),
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+ if (xbuf == MAP_FAILED)
+ coredump_exit_fail_msg("mmap() for the shared xsave buffer failed.");
+
+ child = fork();
+ if (child < 0)
+ coredump_exit_fail_msg("fork() failed.");
+
+ if (!child) {
+ if (chdir(core_dir)) {
+ printf("\tchdir() failed.\n");
+ _exit(1);
+ }
+ load_rand_xstate(&xstate, xbuf);
+ coredump_trigger();
+ _exit(1);
+ }
+
+ if (waitpid(child, &status, 0) != child ||
+ !WIFSIGNALED(status) || WTERMSIG(status) != SIGSEGV)
+ coredump_exit_fail_msg("Child didn't SIGSEGV successfully.");
+
+ corefile_map(child);
+ corefile_xbuf = (struct xsave_buffer *)retrieve_corefile_xbuf();
+
+ if (validate_abi_xstate(corefile_xbuf, xbuf))
+ ksft_test_result_pass("%s for %s\n", __func__, xstate.name);
+ else
+ ksft_test_result_fail("%s for %s\n", __func__, xstate.name);
+
+ munmap(xbuf, get_xbuf_size());
+ coredump_cleanup();
+}
+
/*
* Test signal delivery for the ABI compatibility.
* See the ABI format: arch/x86/include/uapi/asm/sigcontext.h
@@ -511,6 +569,7 @@ static struct test_info {
{ .nr = 1, .fn = test_context_switch, },
{ .nr = 2, .fn = test_ptrace, },
{ .nr = 2, .fn = test_signal, },
+ { .nr = 1, .fn = test_coredump, },
};
int test_xstate(uint32_t feature_num)
--
2.53.0
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH 6/6] selftests/mm: Add coredump test for x86
2026-11-10 3:15 [PATCH 0/6] selftests/x86/xstate: more test cases for coredump and ptrace Chang S. Bae
` (4 preceding siblings ...)
2026-11-10 3:16 ` [PATCH 5/6] selftests/x86/xstate: Add coredump test Chang S. Bae
@ 2026-11-10 3:16 ` Chang S. Bae
5 siblings, 0 replies; 7+ messages in thread
From: Chang S. Bae @ 2026-11-10 3:16 UTC (permalink / raw)
To: linux-kernel, linux-kselftest
Cc: x86, tglx, mingo, bp, dave.hansen, shuah, chang.seok.bae
There is currently no test covering the coredump path for PKRU. Add one
by reusing the helpers from the x86 selftests.
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
---
Note: The test fails right now. So the fix [*] should get in first.
[*] https://lore.kernel.org/lkml/20261015210545.48208-1-chang.seok.bae@intel.com/
---
tools/testing/selftests/mm/Makefile | 2 +-
tools/testing/selftests/mm/pkey-x86.h | 1 +
.../selftests/mm/pkey_coredump_tests.c | 89 +++++++++++++++++++
tools/testing/selftests/mm/run_vmtests.sh | 11 +++
4 files changed, 102 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/mm/pkey_coredump_tests.c
diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile
index 0ea72e997934..d687936c8144 100644
--- a/tools/testing/selftests/mm/Makefile
+++ b/tools/testing/selftests/mm/Makefile
@@ -116,7 +116,7 @@ CAN_BUILD_X86_64 := $(shell ./../x86/check_cc.sh "$(CC)" ../x86/trivial_64bit_pr
CAN_BUILD_WITH_NOPIE := $(shell ./../x86/check_cc.sh "$(CC)" ../x86/trivial_program.c -no-pie)
VMTARGETS := protection_keys
-VMTARGETS += pkey_sighandler_tests
+VMTARGETS += pkey_sighandler_tests pkey_coredump_tests
BINARIES_32 := $(VMTARGETS:%=%_32)
BINARIES_64 := $(VMTARGETS:%=%_64)
diff --git a/tools/testing/selftests/mm/pkey-x86.h b/tools/testing/selftests/mm/pkey-x86.h
index f7ecd335df1e..5b16e9ebcb2d 100644
--- a/tools/testing/selftests/mm/pkey-x86.h
+++ b/tools/testing/selftests/mm/pkey-x86.h
@@ -112,6 +112,7 @@ static inline u32 pkey_bit_position(int pkey)
#define XSTATE_PKEY_BIT (9)
#define XSTATE_PKEY 0x200
#define XSTATE_BV_OFFSET 512
+#define XSTATE_SW_BYTES_OFFSET 464
static inline int pkey_reg_xstate_offset(void)
{
diff --git a/tools/testing/selftests/mm/pkey_coredump_tests.c b/tools/testing/selftests/mm/pkey_coredump_tests.c
new file mode 100644
index 000000000000..1afb40b71800
--- /dev/null
+++ b/tools/testing/selftests/mm/pkey_coredump_tests.c
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <sys/wait.h>
+
+#include "pkey-helpers.h"
+
+#if defined(__i386__) || defined(__x86_64__)
+#include "../x86/coredump.h"
+
+static bool validate_corefile_pkru(u64 *xsave, u32 pkru)
+{
+ bool ret = false;
+
+ if (!(xsave[XSTATE_SW_BYTES_OFFSET / sizeof(u64)] & XSTATE_PKEY))
+ goto out;
+
+ if (!(xsave[XSTATE_BV_OFFSET / sizeof(u64)] & XSTATE_PKEY))
+ goto out;
+
+ if (*(u32 *)(xsave + pkey_reg_xstate_offset() / sizeof(u64)) != pkru)
+ goto out;
+
+ ret = true;
+out:
+ return ret;
+}
+
+static void test_x86_coredump(void)
+{
+ u64 *corefile_xsave;
+ u32 new_pkru;
+ pid_t child;
+ int status;
+
+ if (!coredump_prepare())
+ return;
+
+ new_pkru = ~__read_pkey_reg();
+ new_pkru &= ~3;
+
+ child = fork();
+ if (child < 0) {
+ coredump_exit_fail_msg("fork() failed.");
+ } else if (!child) {
+ if (chdir(core_dir)) {
+ printf("\tchdir() failed.\n");
+ _exit(1);
+ }
+ __write_pkey_reg(new_pkru);
+ coredump_trigger();
+ _exit(1);
+ }
+
+ if (waitpid(child, &status, 0) != child ||
+ !WIFSIGNALED(status) || WTERMSIG(status) != SIGSEGV)
+ coredump_exit_fail_msg("child didn't SIGSEGV successfully.");
+
+ corefile_map(child);
+ corefile_xsave = (u64 *)retrieve_corefile_xbuf();
+
+ if (validate_corefile_pkru(corefile_xsave, new_pkru))
+ ksft_test_result_pass("PKRU was correctly core-dumped\n");
+ else
+ ksft_test_result_fail("PKRU was not correctly core-dumped\n");
+
+ coredump_cleanup();
+}
+#endif
+
+static void (*pkey_tests[])(void) = {
+#if defined(__i386__) || defined(__x86_64__)
+ test_x86_coredump,
+#endif
+};
+
+int main(int argc, char *argv[])
+{
+ ksft_print_header();
+
+ if (!is_pkeys_supported())
+ ksft_exit_skip("pkeys not supported\n");
+
+ ksft_set_plan(ARRAY_SIZE(pkey_tests));
+
+ for (test_nr = 0; test_nr < ARRAY_SIZE(pkey_tests); test_nr++)
+ (*pkey_tests[test_nr])();
+
+ ksft_finished();
+}
diff --git a/tools/testing/selftests/mm/run_vmtests.sh b/tools/testing/selftests/mm/run_vmtests.sh
index a1b45a3dedae..bf7c8aa0b098 100755
--- a/tools/testing/selftests/mm/run_vmtests.sh
+++ b/tools/testing/selftests/mm/run_vmtests.sh
@@ -373,6 +373,17 @@ then
CATEGORY="pkey" run_test ./protection_keys_64
fi
+# pkey_coredump tests
+if [ -x ./pkey_coredump_tests_32 ]
+then
+ CATEGORY="pkey" run_test ./pkey_coredump_tests_32
+fi
+
+if [ -x ./pkey_coredump_tests_64 ]
+then
+ CATEGORY="pkey" run_test ./pkey_coredump_tests_64
+fi
+
CATEGORY="soft_dirty" run_test ./soft-dirty
CATEGORY="pagemap" run_test ./pagemap_ioctl
--
2.53.0
^ permalink raw reply [flat|nested] 7+ messages in thread