Skip to content

Commit

Permalink
selftests/x86/xstate: Refactor ptrace ABI test
Browse files Browse the repository at this point in the history
Following the refactoring of the context switching test, the ptrace test is
another component reusable for other xstate features. As part of this
restructuring, add a missing check to validate the
user_xstateregs->xstate_fx_sw field in the ABI.

Also, replace err() and fatal_error() with ksft_exit_fail_msg() for
consistency in error handling.

  Expected output:
  $ amx_64
  ...
  [RUN]   AMX Tile data: inject xstate via ptrace().
  [OK]    'xfeatures' in SW reserved area was correctly written
  [OK]    xstate was correctly updated.

Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Link: https://lore.kernel.org/r/20250226010731.2456-6-chang.seok.bae@intel.com
  • Loading branch information
Chang S. Bae authored and Ingo Molnar committed Feb 26, 2025
1 parent 40f6852 commit 7cb2fbe
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 107 deletions.
108 changes: 1 addition & 107 deletions tools/testing/selftests/x86/amx.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@
#include <sys/auxv.h>
#include <sys/mman.h>
#include <sys/shm.h>
#include <sys/ptrace.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <sys/uio.h>

#include "helpers.h"
#include "xstate.h"
Expand All @@ -32,8 +30,6 @@
#define XFEATURE_MASK_XTILEDATA (1 << XFEATURE_XTILEDATA)
#define XFEATURE_MASK_XTILE (XFEATURE_MASK_XTILECFG | XFEATURE_MASK_XTILEDATA)

static uint32_t xbuf_size;

struct xstate_info xtiledata;

/* The helpers for managing XSAVE buffer and tile states: */
Expand Down Expand Up @@ -154,13 +150,6 @@ static inline bool load_rand_tiledata(struct xsave_buffer *xbuf)
return xrstor_safe(xbuf, XFEATURE_MASK_XTILEDATA);
}

/* Return XTILEDATA to its initial configuration. */
static inline void init_xtiledata(void)
{
clear_xstate_header(stashed_xsave);
xrstor_safe(stashed_xsave, XFEATURE_MASK_XTILEDATA);
}

enum expected_result { FAIL_EXPECTED, SUCCESS_EXPECTED };

/* arch_prctl() and sigaltstack() test */
Expand Down Expand Up @@ -489,99 +478,6 @@ static void test_fork(void)
_exit(0);
}

/* Ptrace test */

/*
* 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.
*/
static inline void ptracee_firstuse_tiledata(void)
{
load_rand_tiledata(stashed_xsave);
init_xtiledata();
}

/*
* Ptracer injects the randomized tile data state. It also reads
* before and after that, which will execute the kernel's state copy
* functions. So, the tester is advised to double-check any emitted
* kernel messages.
*/
static void ptracer_inject_tiledata(pid_t target)
{
struct xsave_buffer *xbuf;
struct iovec iov;

xbuf = alloc_xbuf();
if (!xbuf)
fatal_error("unable to allocate XSAVE buffer");

printf("\tRead the init'ed tiledata via ptrace().\n");

iov.iov_base = xbuf;
iov.iov_len = xbuf_size;

memset(stashed_xsave, 0, xbuf_size);

if (ptrace(PTRACE_GETREGSET, target, (uint32_t)NT_X86_XSTATE, &iov))
fatal_error("PTRACE_GETREGSET");

if (!__compare_tiledata_state(stashed_xsave, xbuf))
printf("[OK]\tThe init'ed tiledata was read from ptracee.\n");
else
printf("[FAIL]\tThe init'ed tiledata was not read from ptracee.\n");

printf("\tInject tiledata via ptrace().\n");

load_rand_tiledata(xbuf);

memcpy(&stashed_xsave->bytes[xtiledata.xbuf_offset],
&xbuf->bytes[xtiledata.xbuf_offset],
xtiledata.size);

if (ptrace(PTRACE_SETREGSET, target, (uint32_t)NT_X86_XSTATE, &iov))
fatal_error("PTRACE_SETREGSET");

if (ptrace(PTRACE_GETREGSET, target, (uint32_t)NT_X86_XSTATE, &iov))
fatal_error("PTRACE_GETREGSET");

if (!__compare_tiledata_state(stashed_xsave, xbuf))
printf("[OK]\tTiledata was correctly written to ptracee.\n");
else
printf("[FAIL]\tTiledata was not correctly written to ptracee.\n");
}

static void test_ptrace(void)
{
pid_t child;
int status;

child = fork();
if (child < 0) {
err(1, "fork");
} else if (!child) {
if (ptrace(PTRACE_TRACEME, 0, NULL, NULL))
err(1, "PTRACE_TRACEME");

ptracee_firstuse_tiledata();

raise(SIGTRAP);
_exit(0);
}

do {
wait(&status);
} while (WSTOPSIG(status) != SIGTRAP);

ptracer_inject_tiledata(child);

ptrace(PTRACE_DETACH, child, NULL, NULL);
wait(&status);
if (!WIFEXITED(status) || WEXITSTATUS(status))
err(1, "ptrace test");
}

int main(void)
{
const unsigned int ctxtsw_num_threads = 5, ctxtsw_iterations = 10;
Expand All @@ -594,8 +490,6 @@ int main(void)
return KSFT_SKIP;
}

xbuf_size = get_xbuf_size();

xtiledata = get_xstate_info(XFEATURE_XTILEDATA);
if (!xtiledata.size || !xtiledata.xbuf_offset) {
fatal_error("xstate cpuid: invalid tile data size/offset: %d/%d",
Expand All @@ -614,7 +508,7 @@ int main(void)

test_context_switch(XFEATURE_XTILEDATA, ctxtsw_num_threads, ctxtsw_iterations);

test_ptrace();
test_ptrace(XFEATURE_XTILEDATA);

clearhandler(SIGILL);
free_stashed_xsave();
Expand Down
129 changes: 129 additions & 0 deletions tools/testing/selftests/x86/xstate.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,25 @@

#define _GNU_SOURCE

#include <elf.h>
#include <pthread.h>
#include <stdbool.h>

#include <sys/ptrace.h>
#include <sys/uio.h>
#include <sys/wait.h>

#include "helpers.h"
#include "xstate.h"

static inline uint64_t xgetbv(uint32_t index)
{
uint32_t eax, edx;

asm volatile("xgetbv" : "=a" (eax), "=d" (edx) : "c" (index));
return eax + ((uint64_t)edx << 32);
}

static struct xstate_info xstate;

struct futex_info {
Expand All @@ -27,6 +40,19 @@ static inline void load_rand_xstate(struct xstate_info *xstate, struct xsave_buf
xrstor(xbuf, xstate->mask);
}

static inline void load_init_xstate(struct xstate_info *xstate, struct xsave_buffer *xbuf)
{
clear_xstate_header(xbuf);
xrstor(xbuf, xstate->mask);
}

static inline void copy_xstate(struct xsave_buffer *xbuf_dst, struct xsave_buffer *xbuf_src)
{
memcpy(&xbuf_dst->bytes[xstate.xbuf_offset],
&xbuf_src->bytes[xstate.xbuf_offset],
xstate.size);
}

static inline bool validate_xstate_same(struct xsave_buffer *xbuf1, struct xsave_buffer *xbuf2)
{
int ret;
Expand Down Expand Up @@ -196,3 +222,106 @@ void test_context_switch(uint32_t feature_num, uint32_t num_threads, uint32_t it

free(finfo);
}

/*
* 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)
{
struct xsave_buffer *xbuf;

xbuf = alloc_xbuf();

load_rand_xstate(&xstate, xbuf);
load_init_xstate(&xstate, xbuf);

free(xbuf);
}

/*
* Ptracer injects the randomized xstate data. It also reads before and
* after that, which will execute the kernel's state copy functions.
*/
static void ptracer_inject_xstate(pid_t target)
{
uint32_t xbuf_size = get_xbuf_size();
struct xsave_buffer *xbuf1, *xbuf2;
struct iovec iov;

/*
* Allocate buffers to keep data while ptracer can write the
* other buffer
*/
xbuf1 = alloc_xbuf();
xbuf2 = alloc_xbuf();
if (!xbuf1 || !xbuf2)
ksft_exit_fail_msg("unable to allocate XSAVE buffer\n");

iov.iov_base = xbuf1;
iov.iov_len = xbuf_size;

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);

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");
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");

free(xbuf1);
free(xbuf2);
}

void test_ptrace(uint32_t feature_num)
{
pid_t child;
int status;

xstate = get_xstate_info(feature_num);

child = fork();
if (child < 0) {
ksft_exit_fail_msg("fork() failed\n");
} else if (!child) {
if (ptrace(PTRACE_TRACEME, 0, NULL, NULL))
ksft_exit_fail_msg("PTRACE_TRACEME failed\n");

ptracee_touch_xstate();

raise(SIGTRAP);
_exit(0);
}

do {
wait(&status);
} while (WSTOPSIG(status) != SIGTRAP);

ptracer_inject_xstate(child);

ptrace(PTRACE_DETACH, child, NULL, NULL);
wait(&status);
if (!WIFEXITED(status) || WEXITSTATUS(status))
ksft_exit_fail_msg("ptracee exit error\n");
}
1 change: 1 addition & 0 deletions tools/testing/selftests/x86/xstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,5 +190,6 @@ static inline void set_rand_data(struct xstate_info *xstate, struct xsave_buffer
}

void test_context_switch(uint32_t feature_num, uint32_t num_threads, uint32_t iterations);
void test_ptrace(uint32_t feature_num);

#endif /* __SELFTESTS_X86_XSTATE_H */

0 comments on commit 7cb2fbe

Please sign in to comment.