瀏覽代碼

Merge tag 'pull-10.0-testing-and-gdstub-updates-100225-1' of https://gitlab.com/stsquad/qemu into staging

testing and gdbstub updates:

  - add a check-rust test to docker builds
  - re-factor the qtest logic to be cleaner
  - fix tests to not clock_step when no timers enabled
  - roll-up log prefix into qtest_send
  - cleaner error reporting when qtest_clock_set fails
  - revert old deadlock fix now tests are updated
  - only run full set of migration tests under HW acceleration
  - support late attachment to user-mode gdbstubs

# -----BEGIN PGP SIGNATURE-----
#
# iQEzBAABCgAdFiEEZoWumedRZ7yvyN81+9DbCVqeKkQFAmeqBSsACgkQ+9DbCVqe
# KkQS/Af+K0hpdGc1msiuMsqmuESBvhoQniYZFLN1/pwe2KpG8i/+fq2fsCuxJhJ1
# 2TzPH7aj54p9MGCZf2k9JLhO22XldN+oezZMc1crhoWK0AtrWhnLs58I2oEPIsUo
# NmGO6Zfm98ge89o2y8GCvd0QXAtUf+jduDKnW0mfnOnw+w/mky5KzWS7/1091VGW
# 42LSY4KnqgdLSqLyuLBOrgADEjB1ChWS4/bSC+kEYSGrmNQB+n1KeIzzlJBGpOr0
# Z9yzmhMCm7TWdkFNPmnVfYH/7ZUNcpv6PtQSpkku4f6b/gybyvJBknHpM4i+Gpb5
# 87wSjljrCpdNm/9KFRjiJuUWdS/jCg==
# =UF0n
# -----END PGP SIGNATURE-----
# gpg: Signature made Mon 10 Feb 2025 08:54:51 EST
# gpg:                using RSA key 6685AE99E75167BCAFC8DF35FBD0DB095A9E2A44
# gpg: Good signature from "Alex Bennée (Master Work Key) <alex.bennee@linaro.org>" [unknown]
# gpg: WARNING: This key is not certified with a trusted signature!
# gpg:          There is no indication that the signature belongs to the owner.
# Primary key fingerprint: 6685 AE99 E751 67BC AFC8  DF35 FBD0 DB09 5A9E 2A44

* tag 'pull-10.0-testing-and-gdstub-updates-100225-1' of https://gitlab.com/stsquad/qemu:
  tests/tcg: Add late gdbstub attach test
  docs/user: Document the %d placeholder and suspend=n QEMU_GDB features
  gdbstub: Allow late attachment
  osdep: Introduce qemu_kill_thread()
  user: Introduce host_interrupt_signal
  user: Introduce user/signal.h
  gdbstub: Try unlinking the unix socket before binding
  gdbstub: Allow the %d placeholder in the socket path
  tests/qtest/migration: Pick smoke tests
  tests/qtest/migration: Add --full option
  Revert "util/timer: avoid deadlock when shutting down"
  tests/qtest: tighten up the checks on clock_step
  tests/qtest: rename qtest_send_prefix and roll-up into qtest_send
  tests/qtest: simplify qtest_process_inbuf
  tests/qtest: don't step clock at start of npcm7xx periodic IRQ test
  tests/qtest: don't attempt to clock_step while waiting for virtio ISR
  tests/docker: replicate the check-rust-tools-nightly CI job

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Stefan Hajnoczi 6 月之前
父節點
當前提交
ffaf7f0376

+ 1 - 0
MAINTAINERS

@@ -3383,6 +3383,7 @@ F: rust/rustfmt.toml
 
 
 Rust-related patches CC here
 Rust-related patches CC here
 L: qemu-rust@nongnu.org
 L: qemu-rust@nongnu.org
+F: tests/docker/test-rust
 F: rust/
 F: rust/
 
 
 SLIRP
 SLIRP

+ 0 - 1
bsd-user/main.c

@@ -629,7 +629,6 @@ int main(int argc, char **argv)
 
 
     if (gdbstub) {
     if (gdbstub) {
         gdbserver_start(gdbstub, &error_fatal);
         gdbserver_start(gdbstub, &error_fatal);
-        gdb_handlesig(cpu, 0, NULL, NULL, 0);
     }
     }
     cpu_loop(env);
     cpu_loop(env);
     /* never exits */
     /* never exits */

+ 0 - 1
bsd-user/signal-common.h

@@ -42,7 +42,6 @@ void process_pending_signals(CPUArchState *env);
 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 signal_init(void);
 void signal_init(void);
-int target_to_host_signal(int sig);
 void target_to_host_sigset(sigset_t *d, const target_sigset_t *s);
 void target_to_host_sigset(sigset_t *d, const target_sigset_t *s);
 
 
 /*
 /*

+ 13 - 0
bsd-user/signal.c

@@ -24,6 +24,7 @@
 #include "user/cpu_loop.h"
 #include "user/cpu_loop.h"
 #include "exec/page-protection.h"
 #include "exec/page-protection.h"
 #include "user/page-protection.h"
 #include "user/page-protection.h"
+#include "user/signal.h"
 #include "user/tswap-target.h"
 #include "user/tswap-target.h"
 #include "gdbstub/user.h"
 #include "gdbstub/user.h"
 #include "signal-common.h"
 #include "signal-common.h"
@@ -50,6 +51,8 @@ static inline int sas_ss_flags(TaskState *ts, unsigned long sp)
         on_sig_stack(ts, sp) ? SS_ONSTACK : 0;
         on_sig_stack(ts, sp) ? SS_ONSTACK : 0;
 }
 }
 
 
+int host_interrupt_signal = SIGRTMAX;
+
 /*
 /*
  * The BSD ABIs use the same signal numbers across all the CPU architectures, so
  * The BSD ABIs use the same signal numbers across all the CPU architectures, so
  * (unlike Linux) these functions are just the identity mapping. This might not
  * (unlike Linux) these functions are just the identity mapping. This might not
@@ -490,6 +493,12 @@ static void host_signal_handler(int host_sig, siginfo_t *info, void *puc)
     uintptr_t pc = 0;
     uintptr_t pc = 0;
     bool sync_sig = false;
     bool sync_sig = false;
 
 
+    if (host_sig == host_interrupt_signal) {
+        ts->signal_pending = 1;
+        cpu_exit(thread_cpu);
+        return;
+    }
+
     /*
     /*
      * Non-spoofed SIGSEGV and SIGBUS are synchronous, and need special
      * Non-spoofed SIGSEGV and SIGBUS are synchronous, and need special
      * handling wrt signal blocking and unwinding.
      * handling wrt signal blocking and unwinding.
@@ -853,6 +862,9 @@ void signal_init(void)
 
 
     for (i = 1; i <= TARGET_NSIG; i++) {
     for (i = 1; i <= TARGET_NSIG; i++) {
         host_sig = target_to_host_signal(i);
         host_sig = target_to_host_signal(i);
+        if (host_sig == host_interrupt_signal) {
+            continue;
+        }
         sigaction(host_sig, NULL, &oact);
         sigaction(host_sig, NULL, &oact);
         if (oact.sa_sigaction == (void *)SIG_IGN) {
         if (oact.sa_sigaction == (void *)SIG_IGN) {
             sigact_table[i - 1]._sa_handler = TARGET_SIG_IGN;
             sigact_table[i - 1]._sa_handler = TARGET_SIG_IGN;
@@ -871,6 +883,7 @@ void signal_init(void)
             sigaction(host_sig, &act, NULL);
             sigaction(host_sig, &act, NULL);
         }
         }
     }
     }
+    sigaction(host_interrupt_signal, &act, NULL);
 }
 }
 
 
 static void handle_pending_signal(CPUArchState *env, int sig,
 static void handle_pending_signal(CPUArchState *env, int sig,

+ 13 - 3
docs/user/main.rst

@@ -54,7 +54,7 @@ Command line options
 
 
 ::
 ::
 
 
-   qemu-i386 [-h] [-d] [-L path] [-s size] [-cpu model] [-g port] [-B offset] [-R size] program [arguments...]
+   qemu-i386 [-h] [-d] [-L path] [-s size] [-cpu model] [-g endpoint] [-B offset] [-R size] program [arguments...]
 
 
 ``-h``
 ``-h``
    Print the help
    Print the help
@@ -91,8 +91,18 @@ Debug options:
    Activate logging of the specified items (use '-d help' for a list of
    Activate logging of the specified items (use '-d help' for a list of
    log items)
    log items)
 
 
-``-g port``
-   Wait gdb connection to port
+``-g endpoint``
+   Wait gdb connection to a port (e.g., ``1234``) or a unix socket (e.g.,
+   ``/tmp/qemu.sock``).
+
+   If a unix socket path contains single ``%d`` placeholder (e.g.,
+   ``/tmp/qemu-%d.sock``), it is replaced by the emulator PID, which is useful
+   when passing this option via the ``QEMU_GDB`` environment variable to a
+   multi-process application.
+
+   If the endpoint address is followed by ``,suspend=n`` (e.g.,
+   ``1234,suspend=n``), then the emulated program starts without waiting for a
+   connection, which can be established at any later point in time.
 
 
 ``-one-insn-per-tb``
 ``-one-insn-per-tb``
    Run the emulation with one guest instruction per translation block.
    Run the emulation with one guest instruction per translation block.

+ 114 - 36
gdbstub/user.c

@@ -22,6 +22,7 @@
 #include "gdbstub/user.h"
 #include "gdbstub/user.h"
 #include "gdbstub/enums.h"
 #include "gdbstub/enums.h"
 #include "hw/core/cpu.h"
 #include "hw/core/cpu.h"
+#include "user/signal.h"
 #include "trace.h"
 #include "trace.h"
 #include "internals.h"
 #include "internals.h"
 
 
@@ -315,33 +316,20 @@ static bool gdb_accept_socket(int gdb_fd)
     return true;
     return true;
 }
 }
 
 
-static int gdbserver_open_socket(const char *path)
+static int gdbserver_open_socket(const char *path, Error **errp)
 {
 {
-    struct sockaddr_un sockaddr = {};
-    int fd, ret;
+    g_autoptr(GString) buf = g_string_new("");
+    char *pid_placeholder;
 
 
-    fd = socket(AF_UNIX, SOCK_STREAM, 0);
-    if (fd < 0) {
-        perror("create socket");
-        return -1;
+    pid_placeholder = strstr(path, "%d");
+    if (pid_placeholder != NULL) {
+        g_string_append_len(buf, path, pid_placeholder - path);
+        g_string_append_printf(buf, "%d", qemu_get_thread_id());
+        g_string_append(buf, pid_placeholder + 2);
+        path = buf->str;
     }
     }
 
 
-    sockaddr.sun_family = AF_UNIX;
-    pstrcpy(sockaddr.sun_path, sizeof(sockaddr.sun_path) - 1, path);
-    ret = bind(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
-    if (ret < 0) {
-        perror("bind socket");
-        close(fd);
-        return -1;
-    }
-    ret = listen(fd, 1);
-    if (ret < 0) {
-        perror("listen socket");
-        close(fd);
-        return -1;
-    }
-
-    return fd;
+    return unix_listen(path, errp);
 }
 }
 
 
 static bool gdb_accept_tcp(int gdb_fd)
 static bool gdb_accept_tcp(int gdb_fd)
@@ -406,32 +394,122 @@ static int gdbserver_open_port(int port, Error **errp)
     return fd;
     return fd;
 }
 }
 
 
-bool gdbserver_start(const char *port_or_path, Error **errp)
+static bool gdbserver_accept(int port, int gdb_fd, const char *path)
 {
 {
-    int port = g_ascii_strtoull(port_or_path, NULL, 10);
+    bool ret;
+
+    if (port > 0) {
+        ret = gdb_accept_tcp(gdb_fd);
+    } else {
+        ret = gdb_accept_socket(gdb_fd);
+        if (ret) {
+            gdbserver_user_state.socket_path = g_strdup(path);
+        }
+    }
+
+    if (!ret) {
+        close(gdb_fd);
+    }
+
+    return ret;
+}
+
+struct {
+    int port;
     int gdb_fd;
     int gdb_fd;
+    char *path;
+} gdbserver_args;
+
+static void do_gdb_handlesig(CPUState *cs, run_on_cpu_data arg)
+{
+    int sig;
+
+    sig = target_to_host_signal(gdb_handlesig(cs, 0, NULL, NULL, 0));
+    if (sig >= 1 && sig < NSIG) {
+        qemu_kill_thread(gdb_get_cpu_index(cs), sig);
+    }
+}
+
+static void *gdbserver_accept_thread(void *arg)
+{
+    if (gdbserver_accept(gdbserver_args.port, gdbserver_args.gdb_fd,
+                         gdbserver_args.path)) {
+        CPUState *cs = first_cpu;
+
+        async_safe_run_on_cpu(cs, do_gdb_handlesig, RUN_ON_CPU_NULL);
+        qemu_kill_thread(gdb_get_cpu_index(cs), host_interrupt_signal);
+    }
+
+    g_free(gdbserver_args.path);
+    gdbserver_args.path = NULL;
+
+    return NULL;
+}
 
 
+#define USAGE "\nUsage: -g {port|path}[,suspend={y|n}]"
+
+bool gdbserver_start(const char *args, Error **errp)
+{
+    g_auto(GStrv) argv = g_strsplit(args, ",", 0);
+    const char *port_or_path = NULL;
+    bool suspend = true;
+    int gdb_fd, port;
+    GStrv arg;
+
+    for (arg = argv; *arg; arg++) {
+        g_auto(GStrv) tokens = g_strsplit(*arg, "=", 2);
+
+        if (g_strcmp0(tokens[0], "suspend") == 0) {
+            if (tokens[1] == NULL) {
+                error_setg(errp,
+                           "gdbstub: missing \"suspend\" option value" USAGE);
+                return false;
+            } else if (!qapi_bool_parse(tokens[0], tokens[1],
+                                        &suspend, errp)) {
+                return false;
+            }
+        } else {
+            if (port_or_path) {
+                error_setg(errp, "gdbstub: unknown option \"%s\"" USAGE, *arg);
+                return false;
+            }
+            port_or_path = *arg;
+        }
+    }
+    if (!port_or_path) {
+        error_setg(errp, "gdbstub: port or path not specified" USAGE);
+        return false;
+    }
+
+    port = g_ascii_strtoull(port_or_path, NULL, 10);
     if (port > 0) {
     if (port > 0) {
         gdb_fd = gdbserver_open_port(port, errp);
         gdb_fd = gdbserver_open_port(port, errp);
     } else {
     } else {
-        gdb_fd = gdbserver_open_socket(port_or_path);
+        gdb_fd = gdbserver_open_socket(port_or_path, errp);
     }
     }
-
     if (gdb_fd < 0) {
     if (gdb_fd < 0) {
         return false;
         return false;
     }
     }
 
 
-    if (port > 0 && gdb_accept_tcp(gdb_fd)) {
-        return true;
-    } else if (gdb_accept_socket(gdb_fd)) {
-        gdbserver_user_state.socket_path = g_strdup(port_or_path);
+    if (suspend) {
+        if (gdbserver_accept(port, gdb_fd, port_or_path)) {
+            gdb_handlesig(first_cpu, 0, NULL, NULL, 0);
+            return true;
+        } else {
+            error_setg(errp, "gdbstub: failed to accept connection");
+            return false;
+        }
+    } else {
+        QemuThread thread;
+
+        gdbserver_args.port = port;
+        gdbserver_args.gdb_fd = gdb_fd;
+        gdbserver_args.path = g_strdup(port_or_path);
+        qemu_thread_create(&thread, "gdb-accept",
+                           &gdbserver_accept_thread, NULL,
+                           QEMU_THREAD_DETACHED);
         return true;
         return true;
     }
     }
-
-    /* gone wrong */
-    close(gdb_fd);
-    error_setg(errp, "gdbstub: failed to accept connection");
-    return false;
 }
 }
 
 
 void gdbserver_fork_start(void)
 void gdbserver_fork_start(void)

+ 0 - 1
hw/ppc/spapr_rtas.c

@@ -565,7 +565,6 @@ static bool spapr_qtest_callback(CharBackend *chr, gchar **words)
         g_assert(rc == 0);
         g_assert(rc == 0);
         res = qtest_rtas_call(words[1], nargs, args, nret, ret);
         res = qtest_rtas_call(words[1], nargs, args, nret, ret);
 
 
-        qtest_send_prefix(chr);
         qtest_sendf(chr, "OK %"PRIu64"\n", res);
         qtest_sendf(chr, "OK %"PRIu64"\n", res);
 
 
         return true;
         return true;

+ 0 - 1
hw/riscv/riscv_hart.c

@@ -94,7 +94,6 @@ static bool csr_qtest_callback(CharBackend *chr, gchar **words)
         g_assert(rc == 0);
         g_assert(rc == 0);
         csr_call(words[1], cpu, csr, &val);
         csr_call(words[1], cpu, csr, &val);
 
 
-        qtest_send_prefix(chr);
         qtest_sendf(chr, "OK 0 "TARGET_FMT_lx"\n", (target_ulong)val);
         qtest_sendf(chr, "OK 0 "TARGET_FMT_lx"\n", (target_ulong)val);
 
 
         return true;
         return true;

+ 9 - 0
include/qemu/osdep.h

@@ -631,6 +631,15 @@ bool qemu_write_pidfile(const char *pidfile, Error **errp);
 
 
 int qemu_get_thread_id(void);
 int qemu_get_thread_id(void);
 
 
+/**
+ * qemu_kill_thread:
+ * @tid: thread id.
+ * @sig: host signal.
+ *
+ * Send @sig to one of QEMU's own threads with identifier @tid.
+ */
+int qemu_kill_thread(int tid, int sig);
+
 #ifndef CONFIG_IOVEC
 #ifndef CONFIG_IOVEC
 struct iovec {
 struct iovec {
     void *iov_base;
     void *iov_base;

+ 0 - 1
include/system/qtest.h

@@ -24,7 +24,6 @@ static inline bool qtest_enabled(void)
 }
 }
 
 
 #ifndef CONFIG_USER_ONLY
 #ifndef CONFIG_USER_ONLY
-void qtest_send_prefix(CharBackend *chr);
 void G_GNUC_PRINTF(2, 3) qtest_sendf(CharBackend *chr, const char *fmt, ...);
 void G_GNUC_PRINTF(2, 3) qtest_sendf(CharBackend *chr, const char *fmt, ...);
 void qtest_set_command_cb(bool (*pc_cb)(CharBackend *chr, gchar **words));
 void qtest_set_command_cb(bool (*pc_cb)(CharBackend *chr, gchar **words));
 bool qtest_driver(void);
 bool qtest_driver(void);

+ 25 - 0
include/user/signal.h

@@ -0,0 +1,25 @@
+/*
+ * Signal-related declarations.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#ifndef USER_SIGNAL_H
+#define USER_SIGNAL_H
+
+#ifndef CONFIG_USER_ONLY
+#error Cannot include this header from system emulation
+#endif
+
+/**
+ * target_to_host_signal:
+ * @sig: target signal.
+ *
+ * On success, return the host signal between 0 (inclusive) and NSIG
+ * (exclusive) corresponding to the target signal @sig. Return any other value
+ * on failure.
+ */
+int target_to_host_signal(int sig);
+
+extern int host_interrupt_signal;
+
+#endif

+ 0 - 1
linux-user/main.c

@@ -1024,7 +1024,6 @@ int main(int argc, char **argv, char **envp)
 
 
     if (gdbstub) {
     if (gdbstub) {
         gdbserver_start(gdbstub, &error_fatal);
         gdbserver_start(gdbstub, &error_fatal);
-        gdb_handlesig(cpu, 0, NULL, NULL, 0);
     }
     }
 
 
 #ifdef CONFIG_SEMIHOSTING
 #ifdef CONFIG_SEMIHOSTING

+ 0 - 1
linux-user/signal-common.h

@@ -61,7 +61,6 @@ 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);
 void target_to_host_siginfo(siginfo_t *info, const target_siginfo_t *tinfo);
 void target_to_host_siginfo(siginfo_t *info, const target_siginfo_t *tinfo);
-int target_to_host_signal(int sig);
 int host_to_target_signal(int sig);
 int host_to_target_signal(int sig);
 long do_sigreturn(CPUArchState *env);
 long do_sigreturn(CPUArchState *env);
 long do_rt_sigreturn(CPUArchState *env);
 long do_rt_sigreturn(CPUArchState *env);

+ 21 - 5
linux-user/signal.c

@@ -36,6 +36,7 @@
 #include "user/cpu_loop.h"
 #include "user/cpu_loop.h"
 #include "user/page-protection.h"
 #include "user/page-protection.h"
 #include "user/safe-syscall.h"
 #include "user/safe-syscall.h"
+#include "user/signal.h"
 #include "tcg/tcg.h"
 #include "tcg/tcg.h"
 
 
 /* target_siginfo_t must fit in gdbstub's siginfo save area. */
 /* target_siginfo_t must fit in gdbstub's siginfo save area. */
@@ -516,6 +517,8 @@ static int core_dump_signal(int sig)
     }
     }
 }
 }
 
 
+int host_interrupt_signal;
+
 static void signal_table_init(const char *rtsig_map)
 static void signal_table_init(const char *rtsig_map)
 {
 {
     int hsig, tsig, count;
     int hsig, tsig, count;
@@ -579,10 +582,10 @@ static void signal_table_init(const char *rtsig_map)
          * 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).
+         * Reserve two signals for internal usage (see below).
          */
          */
 
 
-        hsig = SIGRTMIN + 1;
+        hsig = SIGRTMIN + 2;
         for (tsig = TARGET_SIGRTMIN;
         for (tsig = TARGET_SIGRTMIN;
              hsig <= SIGRTMAX && tsig <= TARGET_NSIG;
              hsig <= SIGRTMAX && tsig <= TARGET_NSIG;
              hsig++, tsig++) {
              hsig++, tsig++) {
@@ -603,12 +606,17 @@ static void signal_table_init(const char *rtsig_map)
     host_to_target_signal_table[SIGABRT] = 0;
     host_to_target_signal_table[SIGABRT] = 0;
     for (hsig = SIGRTMIN; hsig <= SIGRTMAX; hsig++) {
     for (hsig = SIGRTMIN; hsig <= SIGRTMAX; hsig++) {
         if (!host_to_target_signal_table[hsig]) {
         if (!host_to_target_signal_table[hsig]) {
-            host_to_target_signal_table[hsig] = TARGET_SIGABRT;
-            break;
+            if (host_interrupt_signal) {
+                host_to_target_signal_table[hsig] = TARGET_SIGABRT;
+                break;
+            } else {
+                host_interrupt_signal = hsig;
+            }
         }
         }
     }
     }
     if (hsig > SIGRTMAX) {
     if (hsig > SIGRTMAX) {
-        fprintf(stderr, "No rt signals left for SIGABRT mapping\n");
+        fprintf(stderr,
+                "No rt signals left for interrupt and SIGABRT mapping\n");
         exit(EXIT_FAILURE);
         exit(EXIT_FAILURE);
     }
     }
 
 
@@ -688,6 +696,8 @@ void signal_init(const char *rtsig_map)
         }
         }
         sigact_table[tsig - 1]._sa_handler = thand;
         sigact_table[tsig - 1]._sa_handler = thand;
     }
     }
+
+    sigaction(host_interrupt_signal, &act, NULL);
 }
 }
 
 
 /* Force a synchronously taken signal. The kernel force_sig() function
 /* Force a synchronously taken signal. The kernel force_sig() function
@@ -1035,6 +1045,12 @@ static void host_signal_handler(int host_sig, siginfo_t *info, void *puc)
     bool sync_sig = false;
     bool sync_sig = false;
     void *sigmask;
     void *sigmask;
 
 
+    if (host_sig == host_interrupt_signal) {
+        ts->signal_pending = 1;
+        cpu_exit(thread_cpu);
+        return;
+    }
+
     /*
     /*
      * Non-spoofed SIGSEGV and SIGBUS are synchronous, and need special
      * Non-spoofed SIGSEGV and SIGBUS are synchronous, and need special
      * handling wrt signal blocking and unwinding.  Non-spoofed SIGILL,
      * handling wrt signal blocking and unwinding.  Non-spoofed SIGILL,

+ 1 - 0
linux-user/syscall.c

@@ -138,6 +138,7 @@
 #include "user-mmap.h"
 #include "user-mmap.h"
 #include "user/page-protection.h"
 #include "user/page-protection.h"
 #include "user/safe-syscall.h"
 #include "user/safe-syscall.h"
+#include "user/signal.h"
 #include "qemu/guest-random.h"
 #include "qemu/guest-random.h"
 #include "qemu/selfmap.h"
 #include "qemu/selfmap.h"
 #include "user/syscall-trace.h"
 #include "user/syscall-trace.h"

+ 2 - 0
stubs/meson.build

@@ -61,6 +61,8 @@ if have_user
   if not have_system
   if not have_system
     stub_ss.add(files('qdev.c'))
     stub_ss.add(files('qdev.c'))
   endif
   endif
+
+  stub_ss.add(files('monitor-fd.c'))
 endif
 endif
 
 
 if have_system
 if have_system

+ 9 - 0
stubs/monitor-fd.c

@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "qemu/osdep.h"
+#include "monitor/monitor.h"
+
+int monitor_get_fd(Monitor *mon, const char *fdname, Error **errp)
+{
+    abort();
+}

+ 22 - 37
system/qtest.c

@@ -265,7 +265,7 @@ static int hex2nib(char ch)
     }
     }
 }
 }
 
 
-void qtest_send_prefix(CharBackend *chr)
+static void qtest_log_timestamp(void)
 {
 {
     if (!qtest_log_fp || !qtest_opened) {
     if (!qtest_log_fp || !qtest_opened) {
         return;
         return;
@@ -282,7 +282,7 @@ static void G_GNUC_PRINTF(1, 2) qtest_log_send(const char *fmt, ...)
         return;
         return;
     }
     }
 
 
-    qtest_send_prefix(NULL);
+    qtest_log_timestamp();
 
 
     va_start(ap, fmt);
     va_start(ap, fmt);
     vfprintf(qtest_log_fp, fmt, ap);
     vfprintf(qtest_log_fp, fmt, ap);
@@ -301,6 +301,7 @@ static void qtest_server_char_be_send(void *opaque, const char *str)
 
 
 static void qtest_send(CharBackend *chr, const char *str)
 static void qtest_send(CharBackend *chr, const char *str)
 {
 {
+    qtest_log_timestamp();
     qtest_server_send(qtest_server_send_opaque, str);
     qtest_server_send(qtest_server_send_opaque, str);
 }
 }
 
 
@@ -324,7 +325,6 @@ static void qtest_irq_handler(void *opaque, int n, int level)
     if (irq_levels[n] != level) {
     if (irq_levels[n] != level) {
         CharBackend *chr = &qtest->qtest_chr;
         CharBackend *chr = &qtest->qtest_chr;
         irq_levels[n] = level;
         irq_levels[n] = level;
-        qtest_send_prefix(chr);
         qtest_sendf(chr, "IRQ %s %d\n",
         qtest_sendf(chr, "IRQ %s %d\n",
                     level ? "raise" : "lower", n);
                     level ? "raise" : "lower", n);
     }
     }
@@ -380,19 +380,16 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
         is_outbound = words[0][14] == 'o';
         is_outbound = words[0][14] == 'o';
         dev = DEVICE(object_resolve_path(words[1], NULL));
         dev = DEVICE(object_resolve_path(words[1], NULL));
         if (!dev) {
         if (!dev) {
-            qtest_send_prefix(chr);
             qtest_send(chr, "FAIL Unknown device\n");
             qtest_send(chr, "FAIL Unknown device\n");
             return;
             return;
         }
         }
 
 
         if (is_named && !is_outbound) {
         if (is_named && !is_outbound) {
-            qtest_send_prefix(chr);
             qtest_send(chr, "FAIL Interception of named in-GPIOs not yet supported\n");
             qtest_send(chr, "FAIL Interception of named in-GPIOs not yet supported\n");
             return;
             return;
         }
         }
 
 
         if (irq_intercept_dev) {
         if (irq_intercept_dev) {
-            qtest_send_prefix(chr);
             if (irq_intercept_dev != dev) {
             if (irq_intercept_dev != dev) {
                 qtest_send(chr, "FAIL IRQ intercept already enabled\n");
                 qtest_send(chr, "FAIL IRQ intercept already enabled\n");
             } else {
             } else {
@@ -419,7 +416,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
             }
             }
         }
         }
 
 
-        qtest_send_prefix(chr);
         if (interception_succeeded) {
         if (interception_succeeded) {
             irq_intercept_dev = dev;
             irq_intercept_dev = dev;
             qtest_send(chr, "OK\n");
             qtest_send(chr, "OK\n");
@@ -438,7 +434,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
 
 
         dev = DEVICE(object_resolve_path(words[1], NULL));
         dev = DEVICE(object_resolve_path(words[1], NULL));
         if (!dev) {
         if (!dev) {
-            qtest_send_prefix(chr);
             qtest_send(chr, "FAIL Unknown device\n");
             qtest_send(chr, "FAIL Unknown device\n");
             return;
             return;
         }
         }
@@ -457,7 +452,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
         irq = qdev_get_gpio_in_named(dev, name, num);
         irq = qdev_get_gpio_in_named(dev, name, num);
 
 
         qemu_set_irq(irq, level);
         qemu_set_irq(irq, level);
-        qtest_send_prefix(chr);
         qtest_send(chr, "OK\n");
         qtest_send(chr, "OK\n");
     } else if (strcmp(words[0], "outb") == 0 ||
     } else if (strcmp(words[0], "outb") == 0 ||
                strcmp(words[0], "outw") == 0 ||
                strcmp(words[0], "outw") == 0 ||
@@ -480,7 +474,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
         } else if (words[0][3] == 'l') {
         } else if (words[0][3] == 'l') {
             cpu_outl(addr, value);
             cpu_outl(addr, value);
         }
         }
-        qtest_send_prefix(chr);
         qtest_send(chr, "OK\n");
         qtest_send(chr, "OK\n");
     } else if (strcmp(words[0], "inb") == 0 ||
     } else if (strcmp(words[0], "inb") == 0 ||
         strcmp(words[0], "inw") == 0 ||
         strcmp(words[0], "inw") == 0 ||
@@ -501,7 +494,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
         } else if (words[0][2] == 'l') {
         } else if (words[0][2] == 'l') {
             value = cpu_inl(addr);
             value = cpu_inl(addr);
         }
         }
-        qtest_send_prefix(chr);
         qtest_sendf(chr, "OK 0x%04x\n", value);
         qtest_sendf(chr, "OK 0x%04x\n", value);
     } else if (strcmp(words[0], "writeb") == 0 ||
     } else if (strcmp(words[0], "writeb") == 0 ||
                strcmp(words[0], "writew") == 0 ||
                strcmp(words[0], "writew") == 0 ||
@@ -537,7 +529,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
             address_space_write(first_cpu->as, addr, MEMTXATTRS_UNSPECIFIED,
             address_space_write(first_cpu->as, addr, MEMTXATTRS_UNSPECIFIED,
                                 &data, 8);
                                 &data, 8);
         }
         }
-        qtest_send_prefix(chr);
         qtest_send(chr, "OK\n");
         qtest_send(chr, "OK\n");
     } else if (strcmp(words[0], "readb") == 0 ||
     } else if (strcmp(words[0], "readb") == 0 ||
                strcmp(words[0], "readw") == 0 ||
                strcmp(words[0], "readw") == 0 ||
@@ -571,7 +562,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
                                &value, 8);
                                &value, 8);
             tswap64s(&value);
             tswap64s(&value);
         }
         }
-        qtest_send_prefix(chr);
         qtest_sendf(chr, "OK 0x%016" PRIx64 "\n", value);
         qtest_sendf(chr, "OK 0x%016" PRIx64 "\n", value);
     } else if (strcmp(words[0], "read") == 0) {
     } else if (strcmp(words[0], "read") == 0) {
         g_autoptr(GString) enc = NULL;
         g_autoptr(GString) enc = NULL;
@@ -593,7 +583,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
 
 
         enc = qemu_hexdump_line(NULL, data, len, 0, 0);
         enc = qemu_hexdump_line(NULL, data, len, 0, 0);
 
 
-        qtest_send_prefix(chr);
         qtest_sendf(chr, "OK 0x%s\n", enc->str);
         qtest_sendf(chr, "OK 0x%s\n", enc->str);
 
 
         g_free(data);
         g_free(data);
@@ -613,7 +602,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
         address_space_read(first_cpu->as, addr, MEMTXATTRS_UNSPECIFIED, data,
         address_space_read(first_cpu->as, addr, MEMTXATTRS_UNSPECIFIED, data,
                            len);
                            len);
         b64_data = g_base64_encode(data, len);
         b64_data = g_base64_encode(data, len);
-        qtest_send_prefix(chr);
         qtest_sendf(chr, "OK %s\n", b64_data);
         qtest_sendf(chr, "OK %s\n", b64_data);
 
 
         g_free(data);
         g_free(data);
@@ -649,7 +637,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
                             len);
                             len);
         g_free(data);
         g_free(data);
 
 
-        qtest_send_prefix(chr);
         qtest_send(chr, "OK\n");
         qtest_send(chr, "OK\n");
     } else if (strcmp(words[0], "memset") == 0) {
     } else if (strcmp(words[0], "memset") == 0) {
         uint64_t addr, len;
         uint64_t addr, len;
@@ -673,7 +660,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
             g_free(data);
             g_free(data);
         }
         }
 
 
-        qtest_send_prefix(chr);
         qtest_send(chr, "OK\n");
         qtest_send(chr, "OK\n");
     }  else if (strcmp(words[0], "b64write") == 0) {
     }  else if (strcmp(words[0], "b64write") == 0) {
         uint64_t addr, len;
         uint64_t addr, len;
@@ -705,10 +691,8 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
         address_space_write(first_cpu->as, addr, MEMTXATTRS_UNSPECIFIED, data,
         address_space_write(first_cpu->as, addr, MEMTXATTRS_UNSPECIFIED, data,
                             len);
                             len);
 
 
-        qtest_send_prefix(chr);
         qtest_send(chr, "OK\n");
         qtest_send(chr, "OK\n");
     } else if (strcmp(words[0], "endianness") == 0) {
     } else if (strcmp(words[0], "endianness") == 0) {
-        qtest_send_prefix(chr);
         if (target_words_bigendian()) {
         if (target_words_bigendian()) {
             qtest_sendf(chr, "OK big\n");
             qtest_sendf(chr, "OK big\n");
         } else {
         } else {
@@ -724,17 +708,24 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
         } else {
         } else {
             ns = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL,
             ns = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL,
                                             QEMU_TIMER_ATTR_ALL);
                                             QEMU_TIMER_ATTR_ALL);
+            if (ns < 0) {
+                qtest_send(chr, "FAIL "
+                           "cannot advance clock to the next deadline "
+                           "because there is no pending deadline\n");
+                return;
+            }
         }
         }
         new_ns = qemu_clock_advance_virtual_time(old_ns + ns);
         new_ns = qemu_clock_advance_virtual_time(old_ns + ns);
-        qtest_send_prefix(chr);
-        qtest_sendf(chr, "%s %"PRIi64"\n",
-                    new_ns > old_ns ? "OK" : "FAIL", new_ns);
+        if (new_ns > old_ns) {
+            qtest_sendf(chr, "OK %"PRIi64"\n", new_ns);
+        } else {
+            qtest_sendf(chr, "FAIL could not advance time\n");
+        }
     } else if (strcmp(words[0], "module_load") == 0) {
     } else if (strcmp(words[0], "module_load") == 0) {
         Error *local_err = NULL;
         Error *local_err = NULL;
         int rv;
         int rv;
         g_assert(words[1] && words[2]);
         g_assert(words[1] && words[2]);
 
 
-        qtest_send_prefix(chr);
         rv = module_load(words[1], words[2], &local_err);
         rv = module_load(words[1], words[2], &local_err);
         if (rv > 0) {
         if (rv > 0) {
             qtest_sendf(chr, "OK\n");
             qtest_sendf(chr, "OK\n");
@@ -752,36 +743,30 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
         ret = qemu_strtoi64(words[1], NULL, 0, &ns);
         ret = qemu_strtoi64(words[1], NULL, 0, &ns);
         g_assert(ret == 0);
         g_assert(ret == 0);
         new_ns = qemu_clock_advance_virtual_time(ns);
         new_ns = qemu_clock_advance_virtual_time(ns);
-        qtest_send_prefix(chr);
         qtest_sendf(chr, "%s %"PRIi64"\n",
         qtest_sendf(chr, "%s %"PRIi64"\n",
                     new_ns == ns ? "OK" : "FAIL", new_ns);
                     new_ns == ns ? "OK" : "FAIL", new_ns);
     } else if (process_command_cb && process_command_cb(chr, words)) {
     } else if (process_command_cb && process_command_cb(chr, words)) {
         /* Command got consumed by the callback handler */
         /* Command got consumed by the callback handler */
     } else {
     } else {
-        qtest_send_prefix(chr);
         qtest_sendf(chr, "FAIL Unknown command '%s'\n", words[0]);
         qtest_sendf(chr, "FAIL Unknown command '%s'\n", words[0]);
     }
     }
 }
 }
 
 
+/*
+ * Process as much of @inbuf as we can in newline terminated chunks.
+ * Remove the processed commands from @inbuf as we go.
+ */
 static void qtest_process_inbuf(CharBackend *chr, GString *inbuf)
 static void qtest_process_inbuf(CharBackend *chr, GString *inbuf)
 {
 {
     char *end;
     char *end;
 
 
     while ((end = strchr(inbuf->str, '\n')) != NULL) {
     while ((end = strchr(inbuf->str, '\n')) != NULL) {
-        size_t offset;
-        GString *cmd;
-        gchar **words;
-
-        offset = end - inbuf->str;
+        size_t len = end - inbuf->str;
+        g_autofree char *cmd = g_strndup(inbuf->str, len);
+        g_auto(GStrv) words = g_strsplit(cmd, " ", 0);
 
 
-        cmd = g_string_new_len(inbuf->str, offset);
-        g_string_erase(inbuf, 0, offset + 1);
-
-        words = g_strsplit(cmd->str, " ", 0);
+        g_string_erase(inbuf, 0, len + 1);
         qtest_process_command(chr, words);
         qtest_process_command(chr, words);
-        g_strfreev(words);
-
-        g_string_free(cmd, TRUE);
     }
     }
 }
 }
 
 

+ 3 - 0
tests/docker/Makefile.include

@@ -236,3 +236,6 @@ docker-image: ${DOCKER_IMAGES:%=docker-image-%}
 
 
 docker-clean:
 docker-clean:
 	$(call quiet-command, $(DOCKER_SCRIPT) clean)
 	$(call quiet-command, $(DOCKER_SCRIPT) clean)
+
+# Overrides
+docker-test-rust%: NETWORK=1

+ 21 - 0
tests/docker/test-rust

@@ -0,0 +1,21 @@
+#!/bin/bash -e
+#
+# Run the rust code checks (a.k.a. check-rust-tools-nightly)
+#
+# Copyright (c) 2025 Linaro Ltd
+#
+# Authors:
+#  Alex Bennée <alex.bennee@linaro.org>
+#
+# This work is licensed under the terms of the GNU GPL, version 2
+# or (at your option) any later version. See the COPYING file in
+# the top-level directory.
+
+. common.rc
+
+cd "$BUILD_DIR"
+
+configure_qemu --disable-user --disable-docs --enable-rust
+pyvenv/bin/meson devenv -w $QEMU_SRC/rust ${CARGO-cargo} fmt --check
+make clippy
+make rustdoc

+ 13 - 2
tests/guest-debug/run-test.py

@@ -36,6 +36,8 @@ def get_args():
     parser.add_argument("--gdb-args", help="Additional gdb arguments")
     parser.add_argument("--gdb-args", help="Additional gdb arguments")
     parser.add_argument("--output", help="A file to redirect output to")
     parser.add_argument("--output", help="A file to redirect output to")
     parser.add_argument("--stderr", help="A file to redirect stderr to")
     parser.add_argument("--stderr", help="A file to redirect stderr to")
+    parser.add_argument("--no-suspend", action="store_true",
+                        help="Ask the binary to not wait for GDB connection")
 
 
     return parser.parse_args()
     return parser.parse_args()
 
 
@@ -73,10 +75,19 @@ def log(output, msg):
 
 
     # Launch QEMU with binary
     # Launch QEMU with binary
     if "system" in args.qemu:
     if "system" in args.qemu:
+        if args.no_suspend:
+            suspend = ''
+        else:
+            suspend = ' -S'
         cmd = f'{args.qemu} {args.qargs} {args.binary}' \
         cmd = f'{args.qemu} {args.qargs} {args.binary}' \
-            f' -S -gdb unix:path={socket_name},server=on'
+            f'{suspend} -gdb unix:path={socket_name},server=on'
     else:
     else:
-        cmd = f'{args.qemu} {args.qargs} -g {socket_name} {args.binary}'
+        if args.no_suspend:
+            suspend = ',suspend=n'
+        else:
+            suspend = ''
+        cmd = f'{args.qemu} {args.qargs} -g {socket_name}{suspend}' \
+            f' {args.binary}'
 
 
     log(output, "QEMU CMD: %s" % (cmd))
     log(output, "QEMU CMD: %s" % (cmd))
     inferior = subprocess.Popen(shlex.split(cmd))
     inferior = subprocess.Popen(shlex.split(cmd))

+ 2 - 4
tests/qtest/libqos/virtio-pci-modern.c

@@ -173,13 +173,11 @@ static bool get_config_isr_status(QVirtioDevice *d)
 
 
 static void wait_config_isr_status(QVirtioDevice *d, gint64 timeout_us)
 static void wait_config_isr_status(QVirtioDevice *d, gint64 timeout_us)
 {
 {
-    QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev);
     gint64 start_time = g_get_monotonic_time();
     gint64 start_time = g_get_monotonic_time();
 
 
-    do {
+    while (!get_config_isr_status(d)) {
         g_assert(g_get_monotonic_time() - start_time <= timeout_us);
         g_assert(g_get_monotonic_time() - start_time <= timeout_us);
-        qtest_clock_step(dev->pdev->bus->qts, 100);
-    } while (!get_config_isr_status(d));
+    }
 }
 }
 
 
 static void queue_select(QVirtioDevice *d, uint16_t index)
 static void queue_select(QVirtioDevice *d, uint16_t index)

+ 2 - 4
tests/qtest/libqos/virtio-pci.c

@@ -171,13 +171,11 @@ static bool qvirtio_pci_get_config_isr_status(QVirtioDevice *d)
 static void qvirtio_pci_wait_config_isr_status(QVirtioDevice *d,
 static void qvirtio_pci_wait_config_isr_status(QVirtioDevice *d,
                                                gint64 timeout_us)
                                                gint64 timeout_us)
 {
 {
-    QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev);
     gint64 start_time = g_get_monotonic_time();
     gint64 start_time = g_get_monotonic_time();
 
 
-    do {
+    while (!qvirtio_pci_get_config_isr_status(d)) {
         g_assert(g_get_monotonic_time() - start_time <= timeout_us);
         g_assert(g_get_monotonic_time() - start_time <= timeout_us);
-        qtest_clock_step(dev->pdev->bus->qts, 100);
-    } while (!qvirtio_pci_get_config_isr_status(d));
+    }
 }
 }
 
 
 static void qvirtio_pci_queue_select(QVirtioDevice *d, uint16_t index)
 static void qvirtio_pci_queue_select(QVirtioDevice *d, uint16_t index)

+ 10 - 1
tests/qtest/meson.build

@@ -405,6 +405,8 @@ foreach dir : target_dirs
   target_base = dir.split('-')[0]
   target_base = dir.split('-')[0]
   qtest_emulator = emulators['qemu-system-' + target_base]
   qtest_emulator = emulators['qemu-system-' + target_base]
   target_qtests = get_variable('qtests_' + target_base, []) + qtests_generic
   target_qtests = get_variable('qtests_' + target_base, []) + qtests_generic
+  has_kvm = ('CONFIG_KVM' in config_all_accel and host_os == 'linux'
+             and cpu == target_base and fs.exists('/dev/kvm'))
 
 
   test_deps = roms
   test_deps = roms
   qtest_env = environment()
   qtest_env = environment()
@@ -438,11 +440,18 @@ foreach dir : target_dirs
         test: executable(test, src, dependencies: deps)
         test: executable(test, src, dependencies: deps)
       }
       }
     endif
     endif
+
+    test_args = ['--tap', '-k']
+
+    if test == 'migration-test' and has_kvm
+      test_args += ['--full']
+    endif
+
     test('qtest-@0@/@1@'.format(target_base, test),
     test('qtest-@0@/@1@'.format(target_base, test),
          qtest_executables[test],
          qtest_executables[test],
          depends: [test_deps, qtest_emulator, emulator_modules],
          depends: [test_deps, qtest_emulator, emulator_modules],
          env: qtest_env,
          env: qtest_env,
-         args: ['--tap', '-k'],
+         args: test_args,
          protocol: 'tap',
          protocol: 'tap',
          timeout: slow_qtests.get(test, 60),
          timeout: slow_qtests.get(test, 60),
          priority: slow_qtests.get(test, 60),
          priority: slow_qtests.get(test, 60),

+ 25 - 0
tests/qtest/migration-test.c

@@ -14,13 +14,38 @@
 #include "migration/framework.h"
 #include "migration/framework.h"
 #include "qemu/module.h"
 #include "qemu/module.h"
 
 
+static void parse_args(int *argc_p, char ***argv_p, bool *full_set)
+{
+    int argc = *argc_p;
+    char **argv = *argv_p;
+    int i, j;
+
+    j = 1;
+    for (i = 1; i < argc; i++) {
+        if (g_str_equal(argv[i], "--full")) {
+            *full_set = true;
+            continue;
+        }
+        argv[j++] = argv[i];
+        if (i >= j) {
+            argv[i] = NULL;
+        }
+    }
+    *argc_p = j;
+}
+
 int main(int argc, char **argv)
 int main(int argc, char **argv)
 {
 {
     MigrationTestEnv *env;
     MigrationTestEnv *env;
     int ret;
     int ret;
+    bool full_set = false;
+
+    /* strip the --full option if it's present */
+    parse_args(&argc, &argv, &full_set);
 
 
     g_test_init(&argc, &argv, NULL);
     g_test_init(&argc, &argv, NULL);
     env = migration_get_env();
     env = migration_get_env();
+    env->full_set = full_set;
     module_call_init(MODULE_INIT_QOM);
     module_call_init(MODULE_INIT_QOM);
 
 
     migration_test_add_tls(env);
     migration_test_add_tls(env);

+ 12 - 3
tests/qtest/migration/compression-tests.c

@@ -151,10 +151,22 @@ static void test_multifd_tcp_zlib(void)
     test_precopy_common(&args);
     test_precopy_common(&args);
 }
 }
 
 
+static void migration_test_add_compression_smoke(MigrationTestEnv *env)
+{
+    migration_test_add("/migration/multifd/tcp/plain/zlib",
+                       test_multifd_tcp_zlib);
+}
+
 void migration_test_add_compression(MigrationTestEnv *env)
 void migration_test_add_compression(MigrationTestEnv *env)
 {
 {
     tmpfs = env->tmpfs;
     tmpfs = env->tmpfs;
 
 
+    migration_test_add_compression_smoke(env);
+
+    if (!env->full_set) {
+        return;
+    }
+
 #ifdef CONFIG_ZSTD
 #ifdef CONFIG_ZSTD
     migration_test_add("/migration/multifd/tcp/plain/zstd",
     migration_test_add("/migration/multifd/tcp/plain/zstd",
                        test_multifd_tcp_zstd);
                        test_multifd_tcp_zstd);
@@ -179,7 +191,4 @@ void migration_test_add_compression(MigrationTestEnv *env)
         migration_test_add("/migration/precopy/unix/xbzrle",
         migration_test_add("/migration/precopy/unix/xbzrle",
                            test_precopy_unix_xbzrle);
                            test_precopy_unix_xbzrle);
     }
     }
-
-    migration_test_add("/migration/multifd/tcp/plain/zlib",
-                       test_multifd_tcp_zlib);
 }
 }

+ 6 - 0
tests/qtest/migration/cpr-tests.c

@@ -104,6 +104,12 @@ void migration_test_add_cpr(MigrationTestEnv *env)
 {
 {
     tmpfs = env->tmpfs;
     tmpfs = env->tmpfs;
 
 
+    /* no tests in the smoke set for now */
+
+    if (!env->full_set) {
+        return;
+    }
+
     /*
     /*
      * Our CI system has problems with shared memory.
      * Our CI system has problems with shared memory.
      * Don't run this test until we find a workaround.
      * Don't run this test until we find a workaround.

+ 14 - 5
tests/qtest/migration/file-tests.c

@@ -300,12 +300,24 @@ static void test_multifd_file_mapped_ram_fdset_dio(void)
 }
 }
 #endif /* !_WIN32 */
 #endif /* !_WIN32 */
 
 
+static void migration_test_add_file_smoke(MigrationTestEnv *env)
+{
+    migration_test_add("/migration/precopy/file",
+                       test_precopy_file);
+
+    migration_test_add("/migration/multifd/file/mapped-ram/dio",
+                       test_multifd_file_mapped_ram_dio);
+}
+
 void migration_test_add_file(MigrationTestEnv *env)
 void migration_test_add_file(MigrationTestEnv *env)
 {
 {
     tmpfs = env->tmpfs;
     tmpfs = env->tmpfs;
 
 
-    migration_test_add("/migration/precopy/file",
-                       test_precopy_file);
+    migration_test_add_file_smoke(env);
+
+    if (!env->full_set) {
+        return;
+    }
 
 
     migration_test_add("/migration/precopy/file/offset",
     migration_test_add("/migration/precopy/file/offset",
                        test_precopy_file_offset);
                        test_precopy_file_offset);
@@ -326,9 +338,6 @@ void migration_test_add_file(MigrationTestEnv *env)
     migration_test_add("/migration/multifd/file/mapped-ram/live",
     migration_test_add("/migration/multifd/file/mapped-ram/live",
                        test_multifd_file_mapped_ram_live);
                        test_multifd_file_mapped_ram_live);
 
 
-    migration_test_add("/migration/multifd/file/mapped-ram/dio",
-                       test_multifd_file_mapped_ram_dio);
-
 #ifndef _WIN32
 #ifndef _WIN32
     migration_test_add("/migration/multifd/file/mapped-ram/fdset",
     migration_test_add("/migration/multifd/file/mapped-ram/fdset",
                        test_multifd_file_mapped_ram_fdset);
                        test_multifd_file_mapped_ram_fdset);

+ 1 - 0
tests/qtest/migration/framework.h

@@ -24,6 +24,7 @@ typedef struct MigrationTestEnv {
     bool uffd_feature_thread_id;
     bool uffd_feature_thread_id;
     bool has_dirty_ring;
     bool has_dirty_ring;
     bool is_x86;
     bool is_x86;
+    bool full_set;
     const char *arch;
     const char *arch;
     const char *qemu_src;
     const char *qemu_src;
     const char *qemu_dst;
     const char *qemu_dst;

+ 13 - 3
tests/qtest/migration/misc-tests.c

@@ -258,14 +258,24 @@ static void test_validate_uri_channels_none_set(void)
     do_test_validate_uri_channel(&args);
     do_test_validate_uri_channel(&args);
 }
 }
 
 
+static void migration_test_add_misc_smoke(MigrationTestEnv *env)
+{
+#ifndef _WIN32
+    migration_test_add("/migration/analyze-script", test_analyze_script);
+#endif
+}
+
 void migration_test_add_misc(MigrationTestEnv *env)
 void migration_test_add_misc(MigrationTestEnv *env)
 {
 {
     tmpfs = env->tmpfs;
     tmpfs = env->tmpfs;
 
 
+    migration_test_add_misc_smoke(env);
+
+    if (!env->full_set) {
+        return;
+    }
+
     migration_test_add("/migration/bad_dest", test_baddest);
     migration_test_add("/migration/bad_dest", test_baddest);
-#ifndef _WIN32
-    migration_test_add("/migration/analyze-script", test_analyze_script);
-#endif
 
 
     /*
     /*
      * Our CI system has problems with shared memory.
      * Our CI system has problems with shared memory.

+ 13 - 1
tests/qtest/migration/postcopy-tests.c

@@ -79,7 +79,7 @@ static void test_postcopy_preempt_recovery(void)
     test_postcopy_recovery_common(&args);
     test_postcopy_recovery_common(&args);
 }
 }
 
 
-void migration_test_add_postcopy(MigrationTestEnv *env)
+static void migration_test_add_postcopy_smoke(MigrationTestEnv *env)
 {
 {
     if (env->has_uffd) {
     if (env->has_uffd) {
         migration_test_add("/migration/postcopy/plain", test_postcopy);
         migration_test_add("/migration/postcopy/plain", test_postcopy);
@@ -87,6 +87,18 @@ void migration_test_add_postcopy(MigrationTestEnv *env)
                            test_postcopy_recovery);
                            test_postcopy_recovery);
         migration_test_add("/migration/postcopy/preempt/plain",
         migration_test_add("/migration/postcopy/preempt/plain",
                            test_postcopy_preempt);
                            test_postcopy_preempt);
+    }
+}
+
+void migration_test_add_postcopy(MigrationTestEnv *env)
+{
+    migration_test_add_postcopy_smoke(env);
+
+    if (!env->full_set) {
+        return;
+    }
+
+    if (env->has_uffd) {
         migration_test_add("/migration/postcopy/preempt/recovery/plain",
         migration_test_add("/migration/postcopy/preempt/recovery/plain",
                            test_postcopy_preempt_recovery);
                            test_postcopy_preempt_recovery);
 
 

+ 16 - 7
tests/qtest/migration/precopy-tests.c

@@ -951,10 +951,8 @@ static void test_dirty_limit(void)
     migrate_end(from, to, true);
     migrate_end(from, to, true);
 }
 }
 
 
-void migration_test_add_precopy(MigrationTestEnv *env)
+static void migration_test_add_precopy_smoke(MigrationTestEnv *env)
 {
 {
-    tmpfs = env->tmpfs;
-
     if (env->is_x86) {
     if (env->is_x86) {
         migration_test_add("/migration/precopy/unix/suspend/live",
         migration_test_add("/migration/precopy/unix/suspend/live",
                            test_precopy_unix_suspend_live);
                            test_precopy_unix_suspend_live);
@@ -966,6 +964,21 @@ void migration_test_add_precopy(MigrationTestEnv *env)
                        test_precopy_unix_plain);
                        test_precopy_unix_plain);
 
 
     migration_test_add("/migration/precopy/tcp/plain", test_precopy_tcp_plain);
     migration_test_add("/migration/precopy/tcp/plain", test_precopy_tcp_plain);
+    migration_test_add("/migration/multifd/tcp/uri/plain/none",
+                       test_multifd_tcp_uri_none);
+    migration_test_add("/migration/multifd/tcp/plain/cancel",
+                       test_multifd_tcp_cancel);
+}
+
+void migration_test_add_precopy(MigrationTestEnv *env)
+{
+    tmpfs = env->tmpfs;
+
+    migration_test_add_precopy_smoke(env);
+
+    if (!env->full_set) {
+        return;
+    }
 
 
     migration_test_add("/migration/precopy/tcp/plain/switchover-ack",
     migration_test_add("/migration/precopy/tcp/plain/switchover-ack",
                        test_precopy_tcp_switchover_ack);
                        test_precopy_tcp_switchover_ack);
@@ -989,16 +1002,12 @@ void migration_test_add_precopy(MigrationTestEnv *env)
                                test_dirty_limit);
                                test_dirty_limit);
         }
         }
     }
     }
-    migration_test_add("/migration/multifd/tcp/uri/plain/none",
-                       test_multifd_tcp_uri_none);
     migration_test_add("/migration/multifd/tcp/channels/plain/none",
     migration_test_add("/migration/multifd/tcp/channels/plain/none",
                        test_multifd_tcp_channels_none);
                        test_multifd_tcp_channels_none);
     migration_test_add("/migration/multifd/tcp/plain/zero-page/legacy",
     migration_test_add("/migration/multifd/tcp/plain/zero-page/legacy",
                        test_multifd_tcp_zero_page_legacy);
                        test_multifd_tcp_zero_page_legacy);
     migration_test_add("/migration/multifd/tcp/plain/zero-page/none",
     migration_test_add("/migration/multifd/tcp/plain/zero-page/none",
                        test_multifd_tcp_no_zero_page);
                        test_multifd_tcp_no_zero_page);
-    migration_test_add("/migration/multifd/tcp/plain/cancel",
-                       test_multifd_tcp_cancel);
     if (g_str_equal(env->arch, "x86_64")
     if (g_str_equal(env->arch, "x86_64")
         && env->has_kvm && env->has_dirty_ring) {
         && env->has_kvm && env->has_dirty_ring) {
 
 

+ 12 - 2
tests/qtest/migration/tls-tests.c

@@ -722,10 +722,22 @@ static void test_multifd_tcp_tls_x509_reject_anon_client(void)
 }
 }
 #endif /* CONFIG_TASN1 */
 #endif /* CONFIG_TASN1 */
 
 
+static void migration_test_add_tls_smoke(MigrationTestEnv *env)
+{
+    migration_test_add("/migration/precopy/tcp/tls/psk/match",
+                       test_precopy_tcp_tls_psk_match);
+}
+
 void migration_test_add_tls(MigrationTestEnv *env)
 void migration_test_add_tls(MigrationTestEnv *env)
 {
 {
     tmpfs = env->tmpfs;
     tmpfs = env->tmpfs;
 
 
+    migration_test_add_tls_smoke(env);
+
+    if (!env->full_set) {
+        return;
+    }
+
     migration_test_add("/migration/precopy/unix/tls/psk",
     migration_test_add("/migration/precopy/unix/tls/psk",
                        test_precopy_unix_tls_psk);
                        test_precopy_unix_tls_psk);
 
 
@@ -751,8 +763,6 @@ void migration_test_add_tls(MigrationTestEnv *env)
                        test_precopy_unix_tls_x509_override_host);
                        test_precopy_unix_tls_x509_override_host);
 #endif /* CONFIG_TASN1 */
 #endif /* CONFIG_TASN1 */
 
 
-    migration_test_add("/migration/precopy/tcp/tls/psk/match",
-                       test_precopy_tcp_tls_psk_match);
     migration_test_add("/migration/precopy/tcp/tls/psk/mismatch",
     migration_test_add("/migration/precopy/tcp/tls/psk/mismatch",
                        test_precopy_tcp_tls_psk_mismatch);
                        test_precopy_tcp_tls_psk_mismatch);
 #ifdef CONFIG_TASN1
 #ifdef CONFIG_TASN1

+ 0 - 1
tests/qtest/npcm7xx_timer-test.c

@@ -465,7 +465,6 @@ static void test_periodic_interrupt(gconstpointer test_data)
     int i;
     int i;
 
 
     tim_reset(td);
     tim_reset(td);
-    clock_step_next();
 
 
     tim_write_ticr(td, count);
     tim_write_ticr(td, count);
     tim_write_tcsr(td, CEN | IE | MODE_PERIODIC | PRESCALE(ps));
     tim_write_tcsr(td, CEN | IE | MODE_PERIODIC | PRESCALE(ps));

+ 8 - 1
tests/tcg/multiarch/Makefile.target

@@ -130,6 +130,13 @@ run-gdbstub-follow-fork-mode-parent: follow-fork-mode
 		--bin $< --test $(MULTIARCH_SRC)/gdbstub/follow-fork-mode-parent.py, \
 		--bin $< --test $(MULTIARCH_SRC)/gdbstub/follow-fork-mode-parent.py, \
 	following parents on fork)
 	following parents on fork)
 
 
+run-gdbstub-late-attach: late-attach
+	$(call run-test, $@, env LATE_ATTACH_PY=1 $(GDB_SCRIPT) \
+		--gdb $(GDB) \
+		--qemu $(QEMU) --qargs "$(QEMU_OPTS)" --no-suspend \
+		--bin $< --test $(MULTIARCH_SRC)/gdbstub/late-attach.py, \
+	attaching to a running process)
+
 else
 else
 run-gdbstub-%:
 run-gdbstub-%:
 	$(call skip-test, "gdbstub test $*", "need working gdb with $(patsubst -%,,$(TARGET_NAME)) support")
 	$(call skip-test, "gdbstub test $*", "need working gdb with $(patsubst -%,,$(TARGET_NAME)) support")
@@ -139,7 +146,7 @@ EXTRA_RUNS += run-gdbstub-sha1 run-gdbstub-qxfer-auxv-read \
 	      run-gdbstub-registers run-gdbstub-prot-none \
 	      run-gdbstub-registers run-gdbstub-prot-none \
 	      run-gdbstub-catch-syscalls run-gdbstub-follow-fork-mode-child \
 	      run-gdbstub-catch-syscalls run-gdbstub-follow-fork-mode-child \
 	      run-gdbstub-follow-fork-mode-parent \
 	      run-gdbstub-follow-fork-mode-parent \
-	      run-gdbstub-qxfer-siginfo-read
+	      run-gdbstub-qxfer-siginfo-read run-gdbstub-late-attach
 
 
 # ARM Compatible Semi Hosting Tests
 # ARM Compatible Semi Hosting Tests
 #
 #

+ 28 - 0
tests/tcg/multiarch/gdbstub/late-attach.py

@@ -0,0 +1,28 @@
+"""Test attaching GDB to a running process.
+
+SPDX-License-Identifier: GPL-2.0-or-later
+"""
+from test_gdbstub import main, report
+
+
+def run_test():
+    """Run through the tests one by one"""
+    try:
+        phase = gdb.parse_and_eval("phase").string()
+    except gdb.error:
+        # Assume the guest did not reach main().
+        phase = "start"
+
+    if phase == "start":
+        gdb.execute("break sigwait")
+        gdb.execute("continue")
+        phase = gdb.parse_and_eval("phase").string()
+    report(phase == "sigwait", "{} == \"sigwait\"".format(phase))
+
+    gdb.execute("signal SIGUSR1")
+
+    exitcode = int(gdb.parse_and_eval("$_exitcode"))
+    report(exitcode == 0, "{} == 0".format(exitcode))
+
+
+main(run_test)

+ 41 - 0
tests/tcg/multiarch/late-attach.c

@@ -0,0 +1,41 @@
+/*
+ * Test attaching GDB to a running process.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include <assert.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static const char *phase = "start";
+
+int main(void)
+{
+    sigset_t set;
+    int sig;
+
+    assert(sigfillset(&set) == 0);
+    assert(sigprocmask(SIG_BLOCK, &set, NULL) == 0);
+
+    /* Let GDB know it can send SIGUSR1. */
+    phase = "sigwait";
+    if (getenv("LATE_ATTACH_PY")) {
+        assert(sigwait(&set, &sig) == 0);
+        if (sig != SIGUSR1) {
+            fprintf(stderr, "Unexpected signal %d\n", sig);
+            return EXIT_FAILURE;
+        }
+    }
+
+    /* Check that the guest does not see host_interrupt_signal. */
+    assert(sigpending(&set) == 0);
+    for (sig = 1; sig < NSIG; sig++) {
+        if (sigismember(&set, sig)) {
+            fprintf(stderr, "Unexpected signal %d\n", sig);
+            return EXIT_FAILURE;
+        }
+    }
+
+    return EXIT_SUCCESS;
+}

+ 2 - 0
util/meson.build

@@ -84,6 +84,8 @@ if have_block or have_ga
   util_ss.add(files('qemu-coroutine.c', 'qemu-coroutine-lock.c', 'qemu-coroutine-io.c'))
   util_ss.add(files('qemu-coroutine.c', 'qemu-coroutine-lock.c', 'qemu-coroutine-io.c'))
   util_ss.add(files(f'coroutine-@coroutine_backend@.c'))
   util_ss.add(files(f'coroutine-@coroutine_backend@.c'))
   util_ss.add(files('thread-pool.c', 'qemu-timer.c'))
   util_ss.add(files('thread-pool.c', 'qemu-timer.c'))
+endif
+if have_block or have_ga or have_user
   util_ss.add(files('qemu-sockets.c'))
   util_ss.add(files('qemu-sockets.c'))
 endif
 endif
 if have_block
 if have_block

+ 15 - 0
util/oslib-posix.c

@@ -111,6 +111,21 @@ int qemu_get_thread_id(void)
 #endif
 #endif
 }
 }
 
 
+int qemu_kill_thread(int tid, int sig)
+{
+#if defined(__linux__)
+    return syscall(__NR_tgkill, getpid(), tid, sig);
+#elif defined(__FreeBSD__)
+    return thr_kill2(getpid(), tid, sig);
+#elif defined(__NetBSD__)
+    return _lwp_kill(tid, sig);
+#elif defined(__OpenBSD__)
+    return thrkill(tid, sig, NULL);
+#else
+    return kill(tid, sig);
+#endif
+}
+
 int qemu_daemon(int nochdir, int noclose)
 int qemu_daemon(int nochdir, int noclose)
 {
 {
     return daemon(nochdir, noclose);
     return daemon(nochdir, noclose);

+ 3 - 13
util/qemu-timer.c

@@ -675,17 +675,10 @@ int64_t qemu_clock_advance_virtual_time(int64_t dest)
 {
 {
     int64_t clock = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
     int64_t clock = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
     AioContext *aio_context;
     AioContext *aio_context;
-    int64_t deadline;
-
     aio_context = qemu_get_aio_context();
     aio_context = qemu_get_aio_context();
-
-    deadline = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL,
-                                          QEMU_TIMER_ATTR_ALL);
-    /*
-     * A deadline of < 0 indicates this timer is not enabled, so we
-     * won't get far trying to run it forward.
-     */
-    while (deadline >= 0 && clock < dest) {
+    while (clock < dest) {
+        int64_t deadline = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL,
+                                                      QEMU_TIMER_ATTR_ALL);
         int64_t warp = qemu_soonest_timeout(dest - clock, deadline);
         int64_t warp = qemu_soonest_timeout(dest - clock, deadline);
 
 
         qemu_virtual_clock_set_ns(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + warp);
         qemu_virtual_clock_set_ns(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + warp);
@@ -693,9 +686,6 @@ int64_t qemu_clock_advance_virtual_time(int64_t dest)
         qemu_clock_run_timers(QEMU_CLOCK_VIRTUAL);
         qemu_clock_run_timers(QEMU_CLOCK_VIRTUAL);
         timerlist_run_timers(aio_context->tlg.tl[QEMU_CLOCK_VIRTUAL]);
         timerlist_run_timers(aio_context->tlg.tl[QEMU_CLOCK_VIRTUAL]);
         clock = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
         clock = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
-
-        deadline = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL,
-                                              QEMU_TIMER_ATTR_ALL);
     }
     }
     qemu_clock_notify(QEMU_CLOCK_VIRTUAL);
     qemu_clock_notify(QEMU_CLOCK_VIRTUAL);