linux-user: Allow custom rt signal mappings

Some applications want to use low priority realtime signals (e.g.,
SIGRTMAX). Currently QEMU cannot map all target realtime signals to
host realtime signals, and chooses to sacrifice the end of the target
realtime signal range.

Allow users to choose how to map target realtime signals to host
realtime signals using the new -t option, the new QEMU_RTSIG_MAP
environment variable, and the new -Drtsig_map=\"...\" meson flag.
To simplify things, the meson flag is not per-target, because the
intended use case is app-specific qemu-user builds.

The mapping is specified using the "tsig hsig count[,...]" syntax.
Target realtime signals [tsig,tsig+count) are mapped to host realtime
signals [hsig,hsig+count). Care is taken to avoid double and
out-of-range mappings.

Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com>
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Message-ID: <20241029232211.206766-2-iii@linux.ibm.com>
Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
This commit is contained in:
Ilya Leoshkevich 2024-10-30 00:17:47 +01:00 committed by Richard Henderson
parent 6e9dcfb906
commit c107521e0e
6 changed files with 106 additions and 23 deletions

View File

@ -412,6 +412,13 @@ static void handle_arg_reserved_va(const char *arg)
reserved_va = val ? val - 1 : 0; reserved_va = val ? val - 1 : 0;
} }
static const char *rtsig_map = CONFIG_QEMU_RTSIG_MAP;
static void handle_arg_rtsig_map(const char *arg)
{
rtsig_map = arg;
}
static void handle_arg_one_insn_per_tb(const char *arg) static void handle_arg_one_insn_per_tb(const char *arg)
{ {
opt_one_insn_per_tb = true; opt_one_insn_per_tb = true;
@ -494,6 +501,9 @@ static const struct qemu_argument arg_table[] = {
"address", "set guest_base address to 'address'"}, "address", "set guest_base address to 'address'"},
{"R", "QEMU_RESERVED_VA", true, handle_arg_reserved_va, {"R", "QEMU_RESERVED_VA", true, handle_arg_reserved_va,
"size", "reserve 'size' bytes for guest virtual address space"}, "size", "reserve 'size' bytes for guest virtual address space"},
{"t", "QEMU_RTSIG_MAP", true, handle_arg_rtsig_map,
"tsig hsig n[,...]",
"map target rt signals [tsig,tsig+n) to [hsig,hsig+n]"},
{"d", "QEMU_LOG", true, handle_arg_log, {"d", "QEMU_LOG", true, handle_arg_log,
"item[,...]", "enable logging of specified items " "item[,...]", "enable logging of specified items "
"(use '-d help' for a list of items)"}, "(use '-d help' for a list of items)"},
@ -1002,7 +1012,7 @@ int main(int argc, char **argv, char **envp)
target_set_brk(info->brk); target_set_brk(info->brk);
syscall_init(); syscall_init();
signal_init(); signal_init(rtsig_map);
/* Now that we've loaded the binary, GUEST_BASE is fixed. Delay /* Now that we've loaded the binary, GUEST_BASE is fixed. Delay
generating the prologue until now so that the prologue can take generating the prologue until now so that the prologue can take

View File

@ -56,7 +56,7 @@ void setup_rt_frame(int sig, struct target_sigaction *ka,
target_sigset_t *set, CPUArchState *env); target_sigset_t *set, CPUArchState *env);
void process_pending_signals(CPUArchState *cpu_env); void process_pending_signals(CPUArchState *cpu_env);
void signal_init(void); void signal_init(const char *rtsig_map);
void queue_signal(CPUArchState *env, int sig, int si_type, void queue_signal(CPUArchState *env, int sig, int si_type,
target_siginfo_t *info); target_siginfo_t *info);
void host_to_target_siginfo(target_siginfo_t *tinfo, const siginfo_t *info); void host_to_target_siginfo(target_siginfo_t *tinfo, const siginfo_t *info);

View File

@ -18,6 +18,7 @@
*/ */
#include "qemu/osdep.h" #include "qemu/osdep.h"
#include "qemu/bitops.h" #include "qemu/bitops.h"
#include "qemu/cutils.h"
#include "gdbstub/user.h" #include "gdbstub/user.h"
#include "exec/page-protection.h" #include "exec/page-protection.h"
#include "hw/core/tcg-cpu-ops.h" #include "hw/core/tcg-cpu-ops.h"
@ -513,11 +514,60 @@ static int core_dump_signal(int sig)
} }
} }
static void signal_table_init(void) static void signal_table_init(const char *rtsig_map)
{ {
int hsig, tsig, count; int hsig, tsig, count;
if (rtsig_map) {
/* /*
* Map host RT signals to target RT signals according to the
* user-provided specification.
*/
const char *s = rtsig_map;
while (true) {
int i;
if (qemu_strtoi(s, &s, 10, &tsig) || *s++ != ' ') {
fprintf(stderr, "Malformed target signal in QEMU_RTSIG_MAP\n");
exit(EXIT_FAILURE);
}
if (qemu_strtoi(s, &s, 10, &hsig) || *s++ != ' ') {
fprintf(stderr, "Malformed host signal in QEMU_RTSIG_MAP\n");
exit(EXIT_FAILURE);
}
if (qemu_strtoi(s, &s, 10, &count) || (*s && *s != ',')) {
fprintf(stderr, "Malformed signal count in QEMU_RTSIG_MAP\n");
exit(EXIT_FAILURE);
}
for (i = 0; i < count; i++, tsig++, hsig++) {
if (tsig < TARGET_SIGRTMIN || tsig > TARGET_NSIG) {
fprintf(stderr, "%d is not a target rt signal\n", tsig);
exit(EXIT_FAILURE);
}
if (hsig < SIGRTMIN || hsig > SIGRTMAX) {
fprintf(stderr, "%d is not a host rt signal\n", hsig);
exit(EXIT_FAILURE);
}
if (host_to_target_signal_table[hsig]) {
fprintf(stderr, "%d already maps %d\n",
hsig, host_to_target_signal_table[hsig]);
exit(EXIT_FAILURE);
}
host_to_target_signal_table[hsig] = tsig;
}
if (*s) {
s++;
} else {
break;
}
}
} else {
/*
* Default host-to-target RT signal mapping.
*
* Signals are supported starting from TARGET_SIGRTMIN and going up * Signals are supported starting from TARGET_SIGRTMIN and going up
* until we run out of host realtime signals. Glibc uses the lower 2 * until we run out of host realtime signals. Glibc uses the lower 2
* RT signals and (hopefully) nobody uses the upper ones. * RT signals and (hopefully) nobody uses the upper ones.
@ -527,6 +577,18 @@ static void signal_table_init(void)
* Attempts for configure "missing" signals via sigaction will be * Attempts for configure "missing" signals via sigaction will be
* silently ignored. * silently ignored.
* *
* Reserve one signal for internal usage (see below).
*/
hsig = SIGRTMIN + 1;
for (tsig = TARGET_SIGRTMIN;
hsig <= SIGRTMAX && tsig <= TARGET_NSIG;
hsig++, tsig++) {
host_to_target_signal_table[hsig] = tsig;
}
}
/*
* Remap the target SIGABRT, so that we can distinguish host abort * Remap the target SIGABRT, so that we can distinguish host abort
* from guest abort. When the guest registers a signal handler or * from guest abort. When the guest registers a signal handler or
* calls raise(SIGABRT), the host will raise SIG_RTn. If the guest * calls raise(SIGABRT), the host will raise SIG_RTn. If the guest
@ -536,21 +598,27 @@ static void signal_table_init(void)
* parent sees the correct mapping from wait status. * parent sees the correct mapping from wait status.
*/ */
hsig = SIGRTMIN;
host_to_target_signal_table[SIGABRT] = 0; host_to_target_signal_table[SIGABRT] = 0;
host_to_target_signal_table[hsig++] = TARGET_SIGABRT; for (hsig = SIGRTMIN; hsig <= SIGRTMAX; hsig++) {
if (!host_to_target_signal_table[hsig]) {
for (tsig = TARGET_SIGRTMIN; host_to_target_signal_table[hsig] = TARGET_SIGABRT;
hsig <= SIGRTMAX && tsig <= TARGET_NSIG; break;
hsig++, tsig++) { }
host_to_target_signal_table[hsig] = tsig; }
if (hsig > SIGRTMAX) {
fprintf(stderr, "No rt signals left for SIGABRT mapping\n");
exit(EXIT_FAILURE);
} }
/* Invert the mapping that has already been assigned. */ /* Invert the mapping that has already been assigned. */
for (hsig = 1; hsig < _NSIG; hsig++) { for (hsig = 1; hsig < _NSIG; hsig++) {
tsig = host_to_target_signal_table[hsig]; tsig = host_to_target_signal_table[hsig];
if (tsig) { if (tsig) {
assert(target_to_host_signal_table[tsig] == 0); if (target_to_host_signal_table[tsig]) {
fprintf(stderr, "%d is already mapped to %d\n",
tsig, target_to_host_signal_table[tsig]);
exit(EXIT_FAILURE);
}
target_to_host_signal_table[tsig] = hsig; target_to_host_signal_table[tsig] = hsig;
} }
} }
@ -573,13 +641,13 @@ static void signal_table_init(void)
trace_signal_table_init(count); trace_signal_table_init(count);
} }
void signal_init(void) void signal_init(const char *rtsig_map)
{ {
TaskState *ts = get_task_state(thread_cpu); TaskState *ts = get_task_state(thread_cpu);
struct sigaction act, oact; struct sigaction act, oact;
/* initialize signal conversion tables */ /* initialize signal conversion tables */
signal_table_init(); signal_table_init(rtsig_map);
/* Set the signal mask from the host mask. */ /* Set the signal mask from the host mask. */
sigprocmask(0, 0, &ts->signal_mask); sigprocmask(0, 0, &ts->signal_mask);

View File

@ -3178,7 +3178,8 @@ foreach target : target_dirs
config_target += { config_target += {
'CONFIG_USER_ONLY': 'y', 'CONFIG_USER_ONLY': 'y',
'CONFIG_QEMU_INTERP_PREFIX': 'CONFIG_QEMU_INTERP_PREFIX':
get_option('interp_prefix').replace('%M', config_target['TARGET_NAME']) get_option('interp_prefix').replace('%M', config_target['TARGET_NAME']),
'CONFIG_QEMU_RTSIG_MAP': get_option('rtsig_map'),
} }
endif endif

View File

@ -27,6 +27,8 @@ option('block_drv_ro_whitelist', type : 'string', value : '',
description: 'set block driver read-only whitelist (by default affects only QEMU, not tools like qemu-img)') description: 'set block driver read-only whitelist (by default affects only QEMU, not tools like qemu-img)')
option('interp_prefix', type : 'string', value : '/usr/gnemul/qemu-%M', option('interp_prefix', type : 'string', value : '/usr/gnemul/qemu-%M',
description: 'where to find shared libraries etc., use %M for cpu name') description: 'where to find shared libraries etc., use %M for cpu name')
option('rtsig_map', type : 'string', value : 'NULL',
description: 'default value of QEMU_RTSIG_MAP')
option('fuzzing_engine', type : 'string', value : '', option('fuzzing_engine', type : 'string', value : '',
description: 'fuzzing engine library for OSS-Fuzz') description: 'fuzzing engine library for OSS-Fuzz')
option('trace_file', type: 'string', value: 'trace', option('trace_file', type: 'string', value: 'trace',

View File

@ -72,6 +72,7 @@ meson_options_help() {
printf "%s\n" ' "manufacturer" name for qemu-ga registry entries' printf "%s\n" ' "manufacturer" name for qemu-ga registry entries'
printf "%s\n" ' [QEMU]' printf "%s\n" ' [QEMU]'
printf "%s\n" ' --qemu-ga-version=VALUE version number for qemu-ga installer' printf "%s\n" ' --qemu-ga-version=VALUE version number for qemu-ga installer'
printf "%s\n" ' --rtsig-map=VALUE default value of QEMU_RTSIG_MAP [NULL]'
printf "%s\n" ' --smbd=VALUE Path to smbd for slirp networking' printf "%s\n" ' --smbd=VALUE Path to smbd for slirp networking'
printf "%s\n" ' --sysconfdir=VALUE Sysconf data directory [etc]' printf "%s\n" ' --sysconfdir=VALUE Sysconf data directory [etc]'
printf "%s\n" ' --tls-priority=VALUE Default TLS protocol/cipher priority string' printf "%s\n" ' --tls-priority=VALUE Default TLS protocol/cipher priority string'
@ -460,6 +461,7 @@ _meson_option_parse() {
--disable-replication) printf "%s" -Dreplication=disabled ;; --disable-replication) printf "%s" -Dreplication=disabled ;;
--enable-rng-none) printf "%s" -Drng_none=true ;; --enable-rng-none) printf "%s" -Drng_none=true ;;
--disable-rng-none) printf "%s" -Drng_none=false ;; --disable-rng-none) printf "%s" -Drng_none=false ;;
--rtsig-map=*) quote_sh "-Drtsig_map=$2" ;;
--enable-rust) printf "%s" -Drust=enabled ;; --enable-rust) printf "%s" -Drust=enabled ;;
--disable-rust) printf "%s" -Drust=disabled ;; --disable-rust) printf "%s" -Drust=disabled ;;
--enable-rutabaga-gfx) printf "%s" -Drutabaga_gfx=enabled ;; --enable-rutabaga-gfx) printf "%s" -Drutabaga_gfx=enabled ;;