Linux Kernel Selftest development
 help / color / mirror / Atom feed
From: "Thomas Weißschuh" <linux@weissschuh.net>
To: Benjamin Berg <benjamin@sipsolutions.net>
Cc: "Willy Tarreau" <w@1wt.eu>,
	linux-kselftest@vger.kernel.org,
	"Thomas Weißschuh" <thomas.weissschuh@linutronix.de>,
	"Benjamin Berg" <benjamin.berg@intel.com>
Subject: Re: [RFC v2] tools/nolibc: add sigaction()
Date: Wed, 2 Jul 2025 00:04:47 +0200	[thread overview]
Message-ID: <21cf1fee-21ce-43b2-95cc-18aa58adcd87@t-8ch.de> (raw)
In-Reply-To: <20250701122910.45823-1-benjamin@sipsolutions.net>

Hi Benjamin,

Thanks for your patch!

On 2025-07-01 14:29:10+0200, Benjamin Berg wrote:
> From: Benjamin Berg <benjamin.berg@intel.com>

Please send the next revisions to the nolibc maintainers from the
MAINTAINERS file. Willy and my @weissschuh.net address.

> In preparation to add tests that use it.

Here "tests" is not clear about its meaning.

> Note that some architectures do not have a usable linux/signal.h include
> file. However, in those cases we can use asm-generic/signal.h instead.

This should explain what "unusable" means.

> Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
> 
> ---
> 
> Another attempt at signal handling for nolibc which should actually be
> working. Some trickery is needed to get the right definition, but I feel
> it is sufficiently clean this way.
> 
> Submitting this as RFC mostly because I do not yet have a proper patch
> to add a test that uses the feature.

We do pick up new features in nolibc without them having in-kernel users.
So if you want to get this in already you can drop the RFC state.

> Benjamin
> ---
>  tools/include/nolibc/arch-aarch64.h          |  3 +

In nolibc/for-next, arch-aarch64.h is now called arch-arm64.h.

https://git.kernel.org/pub/scm/linux/kernel/git/nolibc/linux-nolibc.git/log/?h=for-next

>  tools/include/nolibc/arch-arm.h              |  7 ++
>  tools/include/nolibc/arch-i386.h             | 13 +++

... and arch-i386.h was renamed to arch-x86.h

>  tools/include/nolibc/arch-loongarch.h        |  3 +
>  tools/include/nolibc/arch-m68k.h             | 10 ++
>  tools/include/nolibc/arch-mips.h             |  3 +
>  tools/include/nolibc/arch-powerpc.h          |  8 ++
>  tools/include/nolibc/arch-riscv.h            |  3 +
>  tools/include/nolibc/arch-s390.h             |  8 +-
>  tools/include/nolibc/arch-sparc.h            | 43 +++++++++
>  tools/include/nolibc/arch-x86_64.h           | 10 ++

same for arch-x86_64.h, Unlucky timing.

>  tools/include/nolibc/signal.h                | 97 ++++++++++++++++++++
>  tools/include/nolibc/sys.h                   |  2 +-
>  tools/include/nolibc/time.h                  |  3 +-
>  tools/testing/selftests/nolibc/nolibc-test.c | 52 +++++++++++
>  15 files changed, 261 insertions(+), 4 deletions(-)

<snip>

> --- a/tools/include/nolibc/arch-i386.h
> +++ b/tools/include/nolibc/arch-i386.h
> @@ -10,6 +10,19 @@
>  #include "compiler.h"
>  #include "crt.h"
>  
> +/* Needed to get the correct struct sigaction definition */
> +#define SA_RESTORER	0x04000000
> +
> +/* Restorer must be set on i386 */
> +#define _NOLIBC_ARCH_NEEDS_SA_RESTORER
> +
> +/* Otherwise we would need to use sigreturn instead of rt_sigreturn */
> +#define _NOLIBC_ARCH_FORCE_SIG_FLAGS SA_SIGINFO
> +
> +/* Avoid linux/signal.h, it has an incorrect _NSIG and sigset_t */
> +#include <asm-generic/signal.h>
> +#include <asm-generic/siginfo.h>

This doesn't work if the user already has <linux/signal.h> included for
some other reason. The symbol names will conflict.

Can we do something like this:

#include <linux/signal.h>

/* lifted from asm-generic/signal.h */
struct __nolibc_sigaction {
	__sighandler_t sa_handler;
	unsigned long sa_flags;
#ifdef SA_RESTORER
	__sigrestore_t sa_restorer;
#endif
	__nolibc_sigset_t sa_mask;
};

int sys_rt_sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
{
	struct __nolibc_sigaction nolibc_act, nolibc_oldact;
	int ret;

	/* Convert whatever gunk we got from linux/signal.h to what the
	 * kernel actually expects. If it is the same structure,
	 * hopefully the compiler manages to optimize it away.
	 * See __nolibc_user_timespec_to_kernel() et al.
	 * (Comment not meant to be copied verbatim)
	 */
	__nolibc_sigaction_user_to_kernel(act, &nolibc_act);

#if defined(_NOLIBC_ARCH_NEEDS_SA_RESTORER)
...
#endif

	ret = my_syscall4(__NR_rt_sigaction, signum, &nolibc_act, &nolibc_oldact, sizeof(nolibc_act->sa_mask));

	__nolibc_sigaction_kernel_to_user(&nolibc_oldact, oldact);

	return ret;
}

Or am I missing something?

> +
>  /* Syscalls for i386 :
>   *   - mostly similar to x86_64
>   *   - registers are 32-bit

<snip>

> --- a/tools/include/nolibc/arch-sparc.h
> +++ b/tools/include/nolibc/arch-sparc.h
> @@ -12,6 +12,19 @@
>  #include "compiler.h"
>  #include "crt.h"
>  
> +/* Otherwise we would need to use sigreturn instead of rt_sigreturn */
> +#define _NOLIBC_ARCH_FORCE_SIG_FLAGS SA_SIGINFO
> +
> +/* The includes are sane, if one sets __WANT_POSIX1B_SIGNALS__ */
> +#define __WANT_POSIX1B_SIGNALS__
> +#include <linux/signal.h>

This also assumes the user did not already include <linux/signal.h>
before including nolibc.

> +
> +/*
> + * sparc has ODD_RT_SIGACTION, we always pass our restorer as an argument
> + * to rt_sigaction. The restorer is implemented in this file.
> + */
> +#define _NOLIBC_RT_SIGACTION_PASSES_RESTORER
> +
>  /*
>   * Syscalls for SPARC:
>   *   - registers are native word size
> @@ -188,4 +201,34 @@ pid_t sys_fork(void)
>  }
>  #define sys_fork sys_fork
>  
> +#define __nolibc_stringify_1(x...)     #x
> +#define __nolibc_stringify(x...)       __stringify_1(x)

If we need this, IMO it belongs into compiler.h.

> +
> +/* The compiler insists on adding a SAVE call to the start of every function */
> +#define __nolibc_sa_restorer __nolibc_sa_restorer
> +void __nolibc_sa_restorer (void);
> +#ifdef __arch64__
> +__asm__(                                                        \

We are avoiding bare toplevel asm calls.
You could use the same trick as my SuperH _start() function and use
asm() inside a function.

https://lore.kernel.org/lkml/20250623-nolibc-sh-v2-3-0f5b4b303025@weissschuh.net/

> +	".section .text\n"                                      \
> +	".align  4 \n"                                          \
> +	"__nolibc_sa_restorer:\n"                               \
> +	"nop\n"                                                 \
> +	"nop\n"                                                 \
> +	"mov     " __stringify(__NR_rt_sigreturn) ", %g1 \n"    \
> +	"t       0x6d \n");
> +#else
> +__asm__(                                                        \
> +	".section .text\n"                                      \
> +	".align  4 \n"                                          \
> +	"__nolibc_sa_restorer:\n"                               \
> +	"nop\n"                                                 \
> +	"nop\n"                                                 \
> +	"mov     " __stringify(__NR_rt_sigreturn) ", %g1 \n"    \
> +	"t       0x10 \n"                                       \

Only one line differs. I'd prefer the #ifdef around that.

> +	);
> +#endif
> +
> +#undef __nolibc_stringify_1(x...)
> +#undef __nolibc_stringify

And there is no need to undef it again.

> +
>  #endif /* _NOLIBC_ARCH_SPARC_H */
> diff --git a/tools/include/nolibc/arch-x86_64.h b/tools/include/nolibc/arch-x86_64.h
> index 67305e24dbef..9f13a2205876 100644

<snip>

> --- a/tools/include/nolibc/signal.h
> +++ b/tools/include/nolibc/signal.h
> @@ -14,6 +14,8 @@
>  #include "arch.h"
>  #include "types.h"
>  #include "sys.h"
> +#include "string.h"
> +/* signal definitions are included by arch.h */
>  
>  /* This one is not marked static as it's needed by libgcc for divide by zero */
>  int raise(int signal);
> @@ -23,4 +25,99 @@ int raise(int signal)
>  	return sys_kill(sys_getpid(), signal);
>  }
>  
> +/*
> + * sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
> + */
> +#if defined(_NOLIBC_ARCH_NEEDS_SA_RESTORER) && !defined(__nolibc_sa_restorer)
> +static __attribute__((noreturn)) __nolibc_entrypoint __no_stack_protector

__attribute__((noreturn)) is not always available and should be behind
__nolibc_has_attribute(). 
I'm a bit unhappy about reusing the entrypoint machinery, but I guess
it's necessary.

> +void __nolibc_sa_restorer(void)
> +{
> +	my_syscall0(__NR_rt_sigreturn);
> +	__nolibc_entrypoint_epilogue();
> +}
> +#endif
> +
> +static __attribute__((unused))
> +int sys_rt_sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
> +{
> +	struct sigaction real_act = *act;
> +#if defined(SA_RESTORER) && defined(_NOLIBC_ARCH_NEEDS_SA_RESTORER)

If _NOLIBC_ARCH_NEEDS_SA_RESTORER is set then SA_RESTORER should also be
present. SA_RESTORER doesn't need to be checked also.
Are there cases where SA_RESTORER is defined but not needed?

> +	if (!(real_act.sa_flags & SA_RESTORER)) {
> +		real_act.sa_flags |= SA_RESTORER;
> +		real_act.sa_restorer = __nolibc_sa_restorer;
> +	}
> +#endif
> +#ifdef _NOLIBC_ARCH_FORCE_SIG_FLAGS
> +	real_act.sa_flags |= _NOLIBC_ARCH_FORCE_SIG_FLAGS;
> +#endif
> +
> +#ifndef _NOLIBC_RT_SIGACTION_PASSES_RESTORER
> +	return my_syscall4(__NR_rt_sigaction, signum, &real_act, oldact,
> +			   sizeof(act->sa_mask));
> +#else
> +	return my_syscall5(__NR_rt_sigaction, signum, &real_act, oldact,
> +			   __nolibc_sa_restorer, sizeof(act->sa_mask));

This calling convention is specific to SPARC, so I prefer it to be in
arch-sparc.h. Alpha also uses 5 arguments for the syscall, but of course
in a different order... (I do have nearly done patches for alpha
support).

Also if a user specified their custom sa_restorer in 'act', that one
should be used, no?

> +#endif
> +}
> +
> +static __attribute__((unused))
> +int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
> +{
> +	return __sysret(sys_rt_sigaction(signum, act, oldact));
> +}
> +
> +/*
> + * int sigemptyset(sigset_t *set)
> + */
> +static __attribute__((unused))
> +int sigemptyset(sigset_t *set)
> +{
> +	memset(set, 0, sizeof(*set));
> +	return 0;
> +}
> +
> +/*
> + * int sigfillset(sigset_t *set)
> + */
> +static __attribute__((unused))
> +int sigfillset(sigset_t *set)
> +{
> +	memset(set, 0xff, sizeof(*set));
> +	return 0;
> +}
> +
> +/*
> + * int sigaddset(sigset_t *set, int signum)
> + */
> +static __attribute__((unused))
> +int sigaddset(sigset_t *set, int signum)
> +{
> +	set->sig[(signum - 1) / (8 * sizeof(set->sig[0]))] |=
> +		1UL << ((signum - 1) % (8 * sizeof(set->sig[0])));
> +	return 0;

This is documented to return EINVAL for an invalid signum.

> +}
> +
> +/*
> + * int sigdelset(sigset_t *set, int signum)
> + */
> +static __attribute__((unused))
> +int sigdelset(sigset_t *set, int signum)
> +{
> +	set->sig[(signum - 1) / (8 * sizeof(set->sig[0]))] &=
> +		~(1UL << ((signum - 1) % (8 * sizeof(set->sig[0]))));
> +	return 0;
> +}
> +
> +/*
> + * int sigismember(sigset_t *set, int signum)
> + */
> +static __attribute__((unused))
> +int sigismember(sigset_t *set, int signum)
> +{
> +	unsigned long res =
> +		set->sig[(signum - 1) / (8 * sizeof(set->sig[0]))] &
> +			(1UL << ((signum - 1) % (8 * sizeof(set->sig[0]))));
> +	return !!res;
> +}

These are similar to FD_CLR()/FD_SET() etc, no?
Moving both sets of functions to common inline helpers would be nice.

> +
>  #endif /* _NOLIBC_SIGNAL_H */

<snip>

> diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c
> index dbe13000fb1a..af66b739ea18 100644
> --- a/tools/testing/selftests/nolibc/nolibc-test.c
> +++ b/tools/testing/selftests/nolibc/nolibc-test.c
> @@ -1750,6 +1750,57 @@ static int run_protection(int min __attribute__((unused)),
>  	}
>  }
>  
> +volatile int signal_check;

Technically this should be sig_atomic_t.
Which can be a typedef to int.

> +void test_sighandler(int signum)
> +{
> +	if (signum == SIGUSR1) {
> +		kill(getpid(), SIGUSR2);
> +		signal_check = 1;
> +	} else {
> +		signal_check++;
> +	}
> +}
> +
> +int run_signal(int min, int max)
> +{
> +	struct sigaction sa = {
> +		.sa_flags = 0,
> +		.sa_handler = test_sighandler,
> +	};
> +	int llen; /* line length */
> +	int ret = 0;
> +	int res;
> +
> +	(void)min;
> +	(void)max;
> +
> +	signal_check = 0;
> +
> +	sigemptyset(&sa.sa_mask);
> +	sigaddset(&sa.sa_mask, SIGUSR2);

It would be good to do some assertions after sigemptyset() and
sigaddset() to validate the bitfiddling.

> +
> +	res = sigaction(SIGUSR1, &sa, NULL);
> +	llen = printf("register SIGUSR1: %d", res);
> +	EXPECT_EQ(1, 0, res);
> +	res = sigaction(SIGUSR2, &sa, NULL);
> +	llen = printf("register SIGUSR2: %d", res);
> +	EXPECT_EQ(1, 0, res);

Here it would be nice to validate the old action.

> +
> +	/* Trigger the first signal. */
> +	kill(getpid(), SIGUSR1);
> +
> +	/* If signal_check is 1 or higher, then signal emission worked */
> +	llen = printf("signal emission: 1 <= signal_check");
> +	EXPECT_GE(1, signal_check, 1);
> +
> +	/* If it is 2, then signal masking worked */
> +	llen = printf("signal masking: 2 == signal_check");
> +	EXPECT_EQ(1, signal_check, 2);
> +
> +	return ret;
> +}
> +
>  /* prepare what needs to be prepared for pid 1 (stdio, /dev, /proc, etc) */
>  int prepare(void)
>  {
> @@ -1815,6 +1866,7 @@ static const struct test test_names[] = {
>  	{ .name = "stdlib",     .func = run_stdlib     },
>  	{ .name = "printf",     .func = run_printf     },
>  	{ .name = "protection", .func = run_protection },
> +	{ .name = "signal",     .func = run_signal },

These 'struct test's are really more test suites.
This testcase can be part of the syscall suite.

>  	{ 0 }
>  };
>  
> -- 
> 2.50.0
> 

  reply	other threads:[~2025-07-01 22:04 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-07-01 12:29 [RFC v2] tools/nolibc: add sigaction() Benjamin Berg
2025-07-01 22:04 ` Thomas Weißschuh [this message]
2025-07-02 15:33   ` Benjamin Berg
2025-07-04 10:50     ` Thomas Weißschuh
2025-07-04 13:33       ` Willy Tarreau
2025-07-07 15:22         ` Thomas Weißschuh

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=21cf1fee-21ce-43b2-95cc-18aa58adcd87@t-8ch.de \
    --to=linux@weissschuh.net \
    --cc=benjamin.berg@intel.com \
    --cc=benjamin@sipsolutions.net \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=thomas.weissschuh@linutronix.de \
    --cc=w@1wt.eu \
    /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