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

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>
This commit is contained in:
Stefan Hajnoczi 2025-02-10 13:26:17 -05:00
commit ffaf7f0376
40 changed files with 491 additions and 136 deletions

View file

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

View file

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

View file

@ -42,7 +42,6 @@ void process_pending_signals(CPUArchState *env);
void queue_signal(CPUArchState *env, int sig, int si_type,
target_siginfo_t *info);
void signal_init(void);
int target_to_host_signal(int sig);
void target_to_host_sigset(sigset_t *d, const target_sigset_t *s);
/*

View file

@ -24,6 +24,7 @@
#include "user/cpu_loop.h"
#include "exec/page-protection.h"
#include "user/page-protection.h"
#include "user/signal.h"
#include "user/tswap-target.h"
#include "gdbstub/user.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;
}
int host_interrupt_signal = SIGRTMAX;
/*
* 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
@ -490,6 +493,12 @@ static void host_signal_handler(int host_sig, siginfo_t *info, void *puc)
uintptr_t pc = 0;
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
* handling wrt signal blocking and unwinding.
@ -853,6 +862,9 @@ void signal_init(void)
for (i = 1; i <= TARGET_NSIG; i++) {
host_sig = target_to_host_signal(i);
if (host_sig == host_interrupt_signal) {
continue;
}
sigaction(host_sig, NULL, &oact);
if (oact.sa_sigaction == (void *)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_interrupt_signal, &act, NULL);
}
static void handle_pending_signal(CPUArchState *env, int sig,

View file

@ -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``
Print the help
@ -91,8 +91,18 @@ Debug options:
Activate logging of the specified items (use '-d help' for a list of
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``
Run the emulation with one guest instruction per translation block.

View file

@ -22,6 +22,7 @@
#include "gdbstub/user.h"
#include "gdbstub/enums.h"
#include "hw/core/cpu.h"
#include "user/signal.h"
#include "trace.h"
#include "internals.h"
@ -315,33 +316,20 @@ static bool gdb_accept_socket(int gdb_fd)
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)
@ -406,32 +394,122 @@ static int gdbserver_open_port(int port, Error **errp)
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);
int gdb_fd;
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;
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) {
gdb_fd = gdbserver_open_port(port, errp);
} else {
gdb_fd = gdbserver_open_socket(port_or_path);
gdb_fd = gdbserver_open_socket(port_or_path, errp);
}
if (gdb_fd < 0) {
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;
}
/* gone wrong */
close(gdb_fd);
error_setg(errp, "gdbstub: failed to accept connection");
return false;
}
void gdbserver_fork_start(void)

View file

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

View file

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

View file

@ -631,6 +631,15 @@ bool qemu_write_pidfile(const char *pidfile, Error **errp);
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
struct iovec {
void *iov_base;

View file

@ -24,7 +24,6 @@ static inline bool qtest_enabled(void)
}
#ifndef CONFIG_USER_ONLY
void qtest_send_prefix(CharBackend *chr);
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));
bool qtest_driver(void);

25
include/user/signal.h Normal file
View file

@ -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

View file

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

View file

@ -61,7 +61,6 @@ void queue_signal(CPUArchState *env, int sig, int si_type,
target_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);
int target_to_host_signal(int sig);
int host_to_target_signal(int sig);
long do_sigreturn(CPUArchState *env);
long do_rt_sigreturn(CPUArchState *env);

View file

@ -36,6 +36,7 @@
#include "user/cpu_loop.h"
#include "user/page-protection.h"
#include "user/safe-syscall.h"
#include "user/signal.h"
#include "tcg/tcg.h"
/* 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)
{
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
* 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;
hsig <= SIGRTMAX && tsig <= TARGET_NSIG;
hsig++, tsig++) {
@ -603,12 +606,17 @@ static void signal_table_init(const char *rtsig_map)
host_to_target_signal_table[SIGABRT] = 0;
for (hsig = SIGRTMIN; hsig <= SIGRTMAX; 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) {
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);
}
@ -688,6 +696,8 @@ void signal_init(const char *rtsig_map)
}
sigact_table[tsig - 1]._sa_handler = thand;
}
sigaction(host_interrupt_signal, &act, NULL);
}
/* 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;
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
* handling wrt signal blocking and unwinding. Non-spoofed SIGILL,

View file

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

View file

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

9
stubs/monitor-fd.c Normal file
View file

@ -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();
}

View file

@ -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) {
return;
@ -282,7 +282,7 @@ static void G_GNUC_PRINTF(1, 2) qtest_log_send(const char *fmt, ...)
return;
}
qtest_send_prefix(NULL);
qtest_log_timestamp();
va_start(ap, fmt);
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)
{
qtest_log_timestamp();
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) {
CharBackend *chr = &qtest->qtest_chr;
irq_levels[n] = level;
qtest_send_prefix(chr);
qtest_sendf(chr, "IRQ %s %d\n",
level ? "raise" : "lower", n);
}
@ -380,19 +380,16 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
is_outbound = words[0][14] == 'o';
dev = DEVICE(object_resolve_path(words[1], NULL));
if (!dev) {
qtest_send_prefix(chr);
qtest_send(chr, "FAIL Unknown device\n");
return;
}
if (is_named && !is_outbound) {
qtest_send_prefix(chr);
qtest_send(chr, "FAIL Interception of named in-GPIOs not yet supported\n");
return;
}
if (irq_intercept_dev) {
qtest_send_prefix(chr);
if (irq_intercept_dev != dev) {
qtest_send(chr, "FAIL IRQ intercept already enabled\n");
} else {
@ -419,7 +416,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
}
}
qtest_send_prefix(chr);
if (interception_succeeded) {
irq_intercept_dev = dev;
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));
if (!dev) {
qtest_send_prefix(chr);
qtest_send(chr, "FAIL Unknown device\n");
return;
}
@ -457,7 +452,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
irq = qdev_get_gpio_in_named(dev, name, num);
qemu_set_irq(irq, level);
qtest_send_prefix(chr);
qtest_send(chr, "OK\n");
} else if (strcmp(words[0], "outb") == 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') {
cpu_outl(addr, value);
}
qtest_send_prefix(chr);
qtest_send(chr, "OK\n");
} else if (strcmp(words[0], "inb") == 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') {
value = cpu_inl(addr);
}
qtest_send_prefix(chr);
qtest_sendf(chr, "OK 0x%04x\n", value);
} else if (strcmp(words[0], "writeb") == 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,
&data, 8);
}
qtest_send_prefix(chr);
qtest_send(chr, "OK\n");
} else if (strcmp(words[0], "readb") == 0 ||
strcmp(words[0], "readw") == 0 ||
@ -571,7 +562,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
&value, 8);
tswap64s(&value);
}
qtest_send_prefix(chr);
qtest_sendf(chr, "OK 0x%016" PRIx64 "\n", value);
} else if (strcmp(words[0], "read") == 0) {
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);
qtest_send_prefix(chr);
qtest_sendf(chr, "OK 0x%s\n", enc->str);
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,
len);
b64_data = g_base64_encode(data, len);
qtest_send_prefix(chr);
qtest_sendf(chr, "OK %s\n", b64_data);
g_free(data);
@ -649,7 +637,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
len);
g_free(data);
qtest_send_prefix(chr);
qtest_send(chr, "OK\n");
} else if (strcmp(words[0], "memset") == 0) {
uint64_t addr, len;
@ -673,7 +660,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
g_free(data);
}
qtest_send_prefix(chr);
qtest_send(chr, "OK\n");
} else if (strcmp(words[0], "b64write") == 0) {
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,
len);
qtest_send_prefix(chr);
qtest_send(chr, "OK\n");
} else if (strcmp(words[0], "endianness") == 0) {
qtest_send_prefix(chr);
if (target_words_bigendian()) {
qtest_sendf(chr, "OK big\n");
} else {
@ -724,17 +708,24 @@ static void qtest_process_command(CharBackend *chr, gchar **words)
} else {
ns = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL,
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);
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) {
Error *local_err = NULL;
int rv;
g_assert(words[1] && words[2]);
qtest_send_prefix(chr);
rv = module_load(words[1], words[2], &local_err);
if (rv > 0) {
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);
g_assert(ret == 0);
new_ns = qemu_clock_advance_virtual_time(ns);
qtest_send_prefix(chr);
qtest_sendf(chr, "%s %"PRIi64"\n",
new_ns == ns ? "OK" : "FAIL", new_ns);
} else if (process_command_cb && process_command_cb(chr, words)) {
/* Command got consumed by the callback handler */
} else {
qtest_send_prefix(chr);
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)
{
char *end;
while ((end = strchr(inbuf->str, '\n')) != NULL) {
size_t offset;
GString *cmd;
gchar **words;
size_t len = end - inbuf->str;
g_autofree char *cmd = g_strndup(inbuf->str, len);
g_auto(GStrv) words = g_strsplit(cmd, " ", 0);
offset = end - inbuf->str;
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);
g_strfreev(words);
g_string_free(cmd, TRUE);
}
}

View file

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

21
tests/docker/test-rust Executable file
View file

@ -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

View file

@ -36,6 +36,8 @@ def get_args():
parser.add_argument("--gdb-args", help="Additional gdb arguments")
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("--no-suspend", action="store_true",
help="Ask the binary to not wait for GDB connection")
return parser.parse_args()
@ -73,10 +75,19 @@ if __name__ == '__main__':
# Launch QEMU with binary
if "system" in args.qemu:
if args.no_suspend:
suspend = ''
else:
suspend = ' -S'
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:
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))
inferior = subprocess.Popen(shlex.split(cmd))

View file

@ -173,13 +173,11 @@ static bool get_config_isr_status(QVirtioDevice *d)
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();
do {
while (!get_config_isr_status(d)) {
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)

View file

@ -171,13 +171,11 @@ static bool qvirtio_pci_get_config_isr_status(QVirtioDevice *d)
static void qvirtio_pci_wait_config_isr_status(QVirtioDevice *d,
gint64 timeout_us)
{
QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev);
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);
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)

View file

@ -405,6 +405,8 @@ foreach dir : target_dirs
target_base = dir.split('-')[0]
qtest_emulator = emulators['qemu-system-' + target_base]
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
qtest_env = environment()
@ -438,11 +440,18 @@ foreach dir : target_dirs
test: executable(test, src, dependencies: deps)
}
endif
test_args = ['--tap', '-k']
if test == 'migration-test' and has_kvm
test_args += ['--full']
endif
test('qtest-@0@/@1@'.format(target_base, test),
qtest_executables[test],
depends: [test_deps, qtest_emulator, emulator_modules],
env: qtest_env,
args: ['--tap', '-k'],
args: test_args,
protocol: 'tap',
timeout: slow_qtests.get(test, 60),
priority: slow_qtests.get(test, 60),

View file

@ -14,13 +14,38 @@
#include "migration/framework.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)
{
MigrationTestEnv *env;
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);
env = migration_get_env();
env->full_set = full_set;
module_call_init(MODULE_INIT_QOM);
migration_test_add_tls(env);

View file

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

View file

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

View file

@ -300,12 +300,24 @@ static void test_multifd_file_mapped_ram_fdset_dio(void)
}
#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)
{
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",
test_precopy_file_offset);
@ -326,9 +338,6 @@ void migration_test_add_file(MigrationTestEnv *env)
migration_test_add("/migration/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
migration_test_add("/migration/multifd/file/mapped-ram/fdset",
test_multifd_file_mapped_ram_fdset);

View file

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

View file

@ -258,14 +258,24 @@ static void test_validate_uri_channels_none_set(void)
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)
{
tmpfs = env->tmpfs;
migration_test_add_misc_smoke(env);
if (!env->full_set) {
return;
}
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.

View file

@ -79,7 +79,7 @@ static void test_postcopy_preempt_recovery(void)
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) {
migration_test_add("/migration/postcopy/plain", test_postcopy);
@ -87,6 +87,18 @@ void migration_test_add_postcopy(MigrationTestEnv *env)
test_postcopy_recovery);
migration_test_add("/migration/postcopy/preempt/plain",
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",
test_postcopy_preempt_recovery);

View file

@ -951,10 +951,8 @@ static void test_dirty_limit(void)
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) {
migration_test_add("/migration/precopy/unix/suspend/live",
test_precopy_unix_suspend_live);
@ -966,6 +964,21 @@ void migration_test_add_precopy(MigrationTestEnv *env)
test_precopy_unix_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",
test_precopy_tcp_switchover_ack);
@ -989,16 +1002,12 @@ void migration_test_add_precopy(MigrationTestEnv *env)
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",
test_multifd_tcp_channels_none);
migration_test_add("/migration/multifd/tcp/plain/zero-page/legacy",
test_multifd_tcp_zero_page_legacy);
migration_test_add("/migration/multifd/tcp/plain/zero-page/none",
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")
&& env->has_kvm && env->has_dirty_ring) {

View file

@ -722,10 +722,22 @@ static void test_multifd_tcp_tls_x509_reject_anon_client(void)
}
#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)
{
tmpfs = env->tmpfs;
migration_test_add_tls_smoke(env);
if (!env->full_set) {
return;
}
migration_test_add("/migration/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);
#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",
test_precopy_tcp_tls_psk_mismatch);
#ifdef CONFIG_TASN1

View file

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

View file

@ -130,6 +130,13 @@ run-gdbstub-follow-fork-mode-parent: follow-fork-mode
--bin $< --test $(MULTIARCH_SRC)/gdbstub/follow-fork-mode-parent.py, \
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
run-gdbstub-%:
$(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-catch-syscalls run-gdbstub-follow-fork-mode-child \
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
#

View file

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

View file

@ -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;
}

View file

@ -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(f'coroutine-@coroutine_backend@.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'))
endif
if have_block

View file

@ -111,6 +111,21 @@ int qemu_get_thread_id(void)
#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)
{
return daemon(nochdir, noclose);

View file

@ -675,17 +675,10 @@ int64_t qemu_clock_advance_virtual_time(int64_t dest)
{
int64_t clock = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
AioContext *aio_context;
int64_t deadline;
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);
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);
timerlist_run_timers(aio_context->tlg.tl[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);