Merge tag 'pull-maintainer-updates-090224-1' of https://gitlab.com/stsquad/qemu into staging
testing, doc and gdbstub updates:
- add sqlite3 to openSUSE image
- mark CRIS as deprecated
- re-enable the TCG plugin tests
- use select for semihosting
- implement syscall catching in gdbstub
# -----BEGIN PGP SIGNATURE-----
#
# iQEzBAABCgAdFiEEZoWumedRZ7yvyN81+9DbCVqeKkQFAmXGZtQACgkQ+9DbCVqe
# KkTegAgAg7Kkd0Pa2cgkPfNPYR5yT4hYurjVSfi/69cOpj/Vadsav4WWFsuUiQMI
# FfKWCNDo5lmNcWIPKgC0oeWVwxal4IhqhO8f9lRIYqg9z4bRkZbdsrvh8J52Q6Xh
# Bu9UAEk5KBGrMfEdsAwHnVDg435c7IGdJZnE/iKBK1aKljS5LOiX1J6ZxrqUoMsJ
# keTAcdXyvs/BIz68c87AHrW/HKO9KKLgHA2n7T/l2N5KJdoa3p8La9py+v1SGUh8
# 6JlDRYR1H16mITS8tm6ZffPqcOpfHXp7DRquu7obVS5+CVTJ2H9w8TICw56rqMg7
# cVnysNLhdS18cMNKD2Jx3zgd5Nq/cg==
# =deHm
# -----END PGP SIGNATURE-----
# gpg: Signature made Fri 09 Feb 2024 17:54:28 GMT
# gpg: using RSA key 6685AE99E75167BCAFC8DF35FBD0DB095A9E2A44
# gpg: Good signature from "Alex Bennée (Master Work Key) <alex.bennee@linaro.org>" [full]
# Primary key fingerprint: 6685 AE99 E751 67BC AFC8 DF35 FBD0 DB09 5A9E 2A44
* tag 'pull-maintainer-updates-090224-1' of https://gitlab.com/stsquad/qemu:
tests/tcg: Add the syscall catchpoint gdbstub test
gdbstub: Implement catching syscalls
gdbstub: Add syscall entry/return hooks
gdbstub: Allow specifying a reason in stop packets
gdbstub: Expose TARGET_SIGTRAP in a target-agnostic way
kconfig: use "select" to enable semihosting
Revert "hw/elf_ops: Ignore loadable segments with zero size"
configure: run plugin TCG tests again
docs: mark CRIS support as deprecated
tests/docker: Add sqlite3 module to openSUSE Leap container
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
diff --git a/configs/devices/m68k-softmmu/default.mak b/configs/devices/m68k-softmmu/default.mak
index 7f8619e..8dcaa28 100644
--- a/configs/devices/m68k-softmmu/default.mak
+++ b/configs/devices/m68k-softmmu/default.mak
@@ -1,7 +1,5 @@
# Default configuration for m68k-softmmu
-CONFIG_SEMIHOSTING=y
-
# Boards:
#
CONFIG_AN5206=y
diff --git a/configs/devices/mips-softmmu/common.mak b/configs/devices/mips-softmmu/common.mak
index 7da9932..1a85384 100644
--- a/configs/devices/mips-softmmu/common.mak
+++ b/configs/devices/mips-softmmu/common.mak
@@ -1,8 +1,5 @@
# Common mips*-softmmu CONFIG defines
-# CONFIG_SEMIHOSTING is always required on this architecture
-CONFIG_SEMIHOSTING=y
-
CONFIG_ISA_BUS=y
CONFIG_PCI=y
CONFIG_PCI_DEVICES=y
diff --git a/configs/devices/nios2-softmmu/default.mak b/configs/devices/nios2-softmmu/default.mak
index 1bc4082..e130d02 100644
--- a/configs/devices/nios2-softmmu/default.mak
+++ b/configs/devices/nios2-softmmu/default.mak
@@ -1,7 +1,5 @@
# Default configuration for nios2-softmmu
-CONFIG_SEMIHOSTING=y
-
# Boards:
#
CONFIG_NIOS2_10M50=y
diff --git a/configs/devices/riscv32-softmmu/default.mak b/configs/devices/riscv32-softmmu/default.mak
index d847bd5..94a236c 100644
--- a/configs/devices/riscv32-softmmu/default.mak
+++ b/configs/devices/riscv32-softmmu/default.mak
@@ -3,8 +3,6 @@
# Uncomment the following lines to disable these optional devices:
#
#CONFIG_PCI_DEVICES=n
-CONFIG_SEMIHOSTING=y
-CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
# Boards:
#
diff --git a/configs/devices/riscv64-softmmu/default.mak b/configs/devices/riscv64-softmmu/default.mak
index bc69301..3f68059 100644
--- a/configs/devices/riscv64-softmmu/default.mak
+++ b/configs/devices/riscv64-softmmu/default.mak
@@ -3,8 +3,6 @@
# Uncomment the following lines to disable these optional devices:
#
#CONFIG_PCI_DEVICES=n
-CONFIG_SEMIHOSTING=y
-CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
# Boards:
#
diff --git a/configs/devices/xtensa-softmmu/default.mak b/configs/devices/xtensa-softmmu/default.mak
index 4fe1bf0..49e4c9d 100644
--- a/configs/devices/xtensa-softmmu/default.mak
+++ b/configs/devices/xtensa-softmmu/default.mak
@@ -1,7 +1,5 @@
# Default configuration for Xtensa
-CONFIG_SEMIHOSTING=y
-
# Boards:
#
CONFIG_XTENSA_SIM=y
diff --git a/configure b/configure
index 3d8e24a..ff058d6 100755
--- a/configure
+++ b/configure
@@ -1644,6 +1644,9 @@
mkdir -p tests/tcg
echo "# Automatically generated by configure - do not modify" > tests/tcg/$config_host_mak
echo "SRC_PATH=$source_path" >> tests/tcg/$config_host_mak
+if test "$plugins" = "yes" ; then
+ echo "CONFIG_PLUGIN=y" >> tests/tcg/$config_host_mak
+fi
tcg_tests_targets=
for target in $target_list; do
diff --git a/docs/about/deprecated.rst b/docs/about/deprecated.rst
index c7b95e6..7b0c599 100644
--- a/docs/about/deprecated.rst
+++ b/docs/about/deprecated.rst
@@ -192,6 +192,13 @@
an alias, but for consistency these will get removed in a future
release, too. Use ``power5p_v2.1`` and ``power7p_v2.1`` instead.
+CRIS CPU architecture (since 9.0)
+'''''''''''''''''''''''''''''''''
+
+The CRIS architecture was pulled from Linux in 4.17 and the compiler
+is no longer packaged in any distro making it harder to run the
+``check-tcg`` tests. Unless we can improve the testing situation there
+is a chance the code will bitrot without anyone noticing.
System emulator machines
------------------------
diff --git a/gdbstub/gdbstub.c b/gdbstub/gdbstub.c
index 46d752b..7e73e91 100644
--- a/gdbstub/gdbstub.c
+++ b/gdbstub/gdbstub.c
@@ -1617,6 +1617,7 @@
if (gdbserver_state.c_cpu->opaque) {
g_string_append(gdbserver_state.str_buf, ";qXfer:auxv:read+");
}
+ g_string_append(gdbserver_state.str_buf, ";QCatchSyscalls+");
#endif
g_string_append(gdbserver_state.str_buf, ";qXfer:exec-file:read+");
#endif
@@ -1810,6 +1811,14 @@
.schema = "l0"
},
#endif
+#if defined(CONFIG_USER_ONLY)
+ {
+ .handler = gdb_handle_set_catch_syscalls,
+ .cmd = "CatchSyscalls:",
+ .cmd_startswith = 1,
+ .schema = "s0",
+ },
+#endif
};
static void handle_gen_query(GArray *params, void *user_ctx)
diff --git a/gdbstub/internals.h b/gdbstub/internals.h
index 5c0c725..56b7c13 100644
--- a/gdbstub/internals.h
+++ b/gdbstub/internals.h
@@ -136,6 +136,7 @@
int gdb_get_cpu_index(CPUState *cpu);
unsigned int gdb_get_max_cpus(void); /* both */
bool gdb_can_reverse(void); /* softmmu, stub for user */
+int gdb_target_sigtrap(void); /* user */
void gdb_create_default_process(GDBState *s);
@@ -194,6 +195,7 @@
void gdb_handle_v_file_pread(GArray *params, void *user_ctx); /* user */
void gdb_handle_v_file_readlink(GArray *params, void *user_ctx); /* user */
void gdb_handle_query_xfer_exec_file(GArray *params, void *user_ctx); /* user */
+void gdb_handle_set_catch_syscalls(GArray *params, void *user_ctx); /* user */
void gdb_handle_query_attached(GArray *params, void *user_ctx); /* both */
diff --git a/gdbstub/user-target.c b/gdbstub/user-target.c
index c4bba4c..b7d4c37 100644
--- a/gdbstub/user-target.c
+++ b/gdbstub/user-target.c
@@ -418,3 +418,8 @@
ts->bprm->filename + offset);
gdb_put_strbuf();
}
+
+int gdb_target_sigtrap(void)
+{
+ return TARGET_SIGTRAP;
+}
diff --git a/gdbstub/user.c b/gdbstub/user.c
index dbe1d9b..14918d1 100644
--- a/gdbstub/user.c
+++ b/gdbstub/user.c
@@ -10,6 +10,7 @@
*/
#include "qemu/osdep.h"
+#include "qemu/bitops.h"
#include "qemu/cutils.h"
#include "qemu/sockets.h"
#include "exec/hwaddr.h"
@@ -21,11 +22,20 @@
#include "trace.h"
#include "internals.h"
+#define GDB_NR_SYSCALLS 1024
+typedef unsigned long GDBSyscallsMask[BITS_TO_LONGS(GDB_NR_SYSCALLS)];
+
/* User-mode specific state */
typedef struct {
int fd;
char *socket_path;
int running_state;
+ /*
+ * Store syscalls mask without memory allocation in order to avoid
+ * implementing synchronization.
+ */
+ bool catch_all_syscalls;
+ GDBSyscallsMask catch_syscalls_mask;
} GDBUserState;
static GDBUserState gdbserver_user_state;
@@ -121,7 +131,7 @@
exit(code);
}
-int gdb_handlesig(CPUState *cpu, int sig)
+int gdb_handlesig_reason(CPUState *cpu, int sig, const char *reason)
{
char buf[256];
int n;
@@ -141,6 +151,9 @@
"T%02xthread:", gdb_target_signal_to_gdb(sig));
gdb_append_thread_id(cpu, gdbserver_state.str_buf);
g_string_append_c(gdbserver_state.str_buf, ';');
+ if (reason) {
+ g_string_append(gdbserver_state.str_buf, reason);
+ }
gdb_put_strbuf();
gdbserver_state.allow_stop_reply = false;
}
@@ -499,3 +512,92 @@
gdb_put_packet(syscall_packet);
gdb_handlesig(gdbserver_state.c_cpu, 0);
}
+
+static bool should_catch_syscall(int num)
+{
+ if (gdbserver_user_state.catch_all_syscalls) {
+ return true;
+ }
+ if (num < 0 || num >= GDB_NR_SYSCALLS) {
+ return false;
+ }
+ return test_bit(num, gdbserver_user_state.catch_syscalls_mask);
+}
+
+void gdb_syscall_entry(CPUState *cs, int num)
+{
+ if (should_catch_syscall(num)) {
+ g_autofree char *reason = g_strdup_printf("syscall_entry:%x;", num);
+ gdb_handlesig_reason(cs, gdb_target_sigtrap(), reason);
+ }
+}
+
+void gdb_syscall_return(CPUState *cs, int num)
+{
+ if (should_catch_syscall(num)) {
+ g_autofree char *reason = g_strdup_printf("syscall_return:%x;", num);
+ gdb_handlesig_reason(cs, gdb_target_sigtrap(), reason);
+ }
+}
+
+void gdb_handle_set_catch_syscalls(GArray *params, void *user_ctx)
+{
+ const char *param = get_param(params, 0)->data;
+ GDBSyscallsMask catch_syscalls_mask;
+ bool catch_all_syscalls;
+ unsigned int num;
+ const char *p;
+
+ /* "0" means not catching any syscalls. */
+ if (strcmp(param, "0") == 0) {
+ gdbserver_user_state.catch_all_syscalls = false;
+ memset(gdbserver_user_state.catch_syscalls_mask, 0,
+ sizeof(gdbserver_user_state.catch_syscalls_mask));
+ gdb_put_packet("OK");
+ return;
+ }
+
+ /* "1" means catching all syscalls. */
+ if (strcmp(param, "1") == 0) {
+ gdbserver_user_state.catch_all_syscalls = true;
+ gdb_put_packet("OK");
+ return;
+ }
+
+ /*
+ * "1;..." means catching only the specified syscalls.
+ * The syscall list must not be empty.
+ */
+ if (param[0] == '1' && param[1] == ';') {
+ catch_all_syscalls = false;
+ memset(catch_syscalls_mask, 0, sizeof(catch_syscalls_mask));
+ for (p = ¶m[2];; p++) {
+ if (qemu_strtoui(p, &p, 16, &num) || (*p && *p != ';')) {
+ goto err;
+ }
+ if (num >= GDB_NR_SYSCALLS) {
+ /*
+ * Fall back to reporting all syscalls. Reporting extra
+ * syscalls is inefficient, but the spec explicitly allows it.
+ * Keep parsing in case there is a syntax error ahead.
+ */
+ catch_all_syscalls = true;
+ } else {
+ set_bit(num, catch_syscalls_mask);
+ }
+ if (!*p) {
+ break;
+ }
+ }
+ gdbserver_user_state.catch_all_syscalls = catch_all_syscalls;
+ if (!catch_all_syscalls) {
+ memcpy(gdbserver_user_state.catch_syscalls_mask,
+ catch_syscalls_mask, sizeof(catch_syscalls_mask));
+ }
+ gdb_put_packet("OK");
+ return;
+ }
+
+err:
+ gdb_put_packet("E00");
+}
diff --git a/include/gdbstub/user.h b/include/gdbstub/user.h
index d392e51..68b6534 100644
--- a/include/gdbstub/user.h
+++ b/include/gdbstub/user.h
@@ -10,9 +10,10 @@
#define GDBSTUB_USER_H
/**
- * gdb_handlesig() - yield control to gdb
+ * gdb_handlesig_reason() - yield control to gdb
* @cpu: CPU
* @sig: if non-zero, the signal number which caused us to stop
+ * @reason: stop reason for stop reply packet or NULL
*
* This function yields control to gdb, when a user-mode-only target
* needs to stop execution. If @sig is non-zero, then we will send a
@@ -24,7 +25,18 @@
* or 0 if no signal should be delivered, ie the signal that caused
* us to stop should be ignored.
*/
-int gdb_handlesig(CPUState *, int);
+int gdb_handlesig_reason(CPUState *, int, const char *);
+
+/**
+ * gdb_handlesig() - yield control to gdb
+ * @cpu CPU
+ * @sig: if non-zero, the signal number which caused us to stop
+ * @see gdb_handlesig_reason()
+ */
+static inline int gdb_handlesig(CPUState *cpu, int sig)
+{
+ return gdb_handlesig_reason(cpu, sig, NULL);
+}
/**
* gdb_signalled() - inform remote gdb of sig exit
@@ -39,5 +51,18 @@
*/
void gdbserver_fork(CPUState *cs);
+/**
+ * gdb_syscall_entry() - inform gdb of syscall entry and yield control to it
+ * @cs: CPU
+ * @num: syscall number
+ */
+void gdb_syscall_entry(CPUState *cs, int num);
+
+/**
+ * gdb_syscall_entry() - inform gdb of syscall return and yield control to it
+ * @cs: CPU
+ * @num: syscall number
+ */
+void gdb_syscall_return(CPUState *cs, int num);
#endif /* GDBSTUB_USER_H */
diff --git a/include/hw/elf_ops.h b/include/hw/elf_ops.h
index 3e966dd..9c35d1b 100644
--- a/include/hw/elf_ops.h
+++ b/include/hw/elf_ops.h
@@ -427,16 +427,6 @@
file_size = ph->p_filesz; /* Size of the allocated data */
data_offset = ph->p_offset; /* Offset where the data is located */
- /*
- * Some ELF files really do have segments of zero size;
- * just ignore them rather than trying to set the wrong addr,
- * or create empty ROM blobs, because the zero-length blob can
- * falsely trigger the overlapping-ROM-blobs check.
- */
- if (mem_size == 0) {
- continue;
- }
-
if (file_size > 0) {
if (g_mapped_file_get_length(mapped_file) <
file_size + data_offset) {
@@ -540,38 +530,45 @@
*pentry = ehdr.e_entry - ph->p_vaddr + ph->p_paddr;
}
- if (load_rom) {
- g_autofree char *label =
- g_strdup_printf("%s ELF program header segment %d",
- name, i);
+ /* Some ELF files really do have segments of zero size;
+ * just ignore them rather than trying to create empty
+ * ROM blobs, because the zero-length blob can falsely
+ * trigger the overlapping-ROM-blobs check.
+ */
+ if (mem_size != 0) {
+ if (load_rom) {
+ g_autofree char *label =
+ g_strdup_printf("%s ELF program header segment %d",
+ name, i);
- /*
- * rom_add_elf_program() takes its own reference to
- * 'mapped_file'.
- */
- rom_add_elf_program(label, mapped_file, data, file_size,
- mem_size, addr, as);
- } else {
- MemTxResult res;
+ /*
+ * rom_add_elf_program() takes its own reference to
+ * 'mapped_file'.
+ */
+ rom_add_elf_program(label, mapped_file, data, file_size,
+ mem_size, addr, as);
+ } else {
+ MemTxResult res;
- res = address_space_write(as ? as : &address_space_memory,
- addr, MEMTXATTRS_UNSPECIFIED,
- data, file_size);
- if (res != MEMTX_OK) {
- goto fail;
- }
- /*
- * We need to zero'ify the space that is not copied
- * from file
- */
- if (file_size < mem_size) {
- res = address_space_set(as ? as : &address_space_memory,
- addr + file_size, 0,
- mem_size - file_size,
- MEMTXATTRS_UNSPECIFIED);
+ res = address_space_write(as ? as : &address_space_memory,
+ addr, MEMTXATTRS_UNSPECIFIED,
+ data, file_size);
if (res != MEMTX_OK) {
goto fail;
}
+ /*
+ * We need to zero'ify the space that is not copied
+ * from file
+ */
+ if (file_size < mem_size) {
+ res = address_space_set(as ? as : &address_space_memory,
+ addr + file_size, 0,
+ mem_size - file_size,
+ MEMTXATTRS_UNSPECIFIED);
+ if (res != MEMTX_OK) {
+ goto fail;
+ }
+ }
}
}
diff --git a/include/user/syscall-trace.h b/include/user/syscall-trace.h
index 557f881..b48b2b2 100644
--- a/include/user/syscall-trace.h
+++ b/include/user/syscall-trace.h
@@ -11,6 +11,7 @@
#define SYSCALL_TRACE_H
#include "exec/user/abitypes.h"
+#include "gdbstub/user.h"
#include "qemu/plugin.h"
#include "trace/trace-root.h"
@@ -20,7 +21,7 @@
* could potentially unify the -strace code here as well.
*/
-static inline void record_syscall_start(void *cpu, int num,
+static inline void record_syscall_start(CPUState *cpu, int num,
abi_long arg1, abi_long arg2,
abi_long arg3, abi_long arg4,
abi_long arg5, abi_long arg6,
@@ -29,11 +30,13 @@
qemu_plugin_vcpu_syscall(cpu, num,
arg1, arg2, arg3, arg4,
arg5, arg6, arg7, arg8);
+ gdb_syscall_entry(cpu, num);
}
-static inline void record_syscall_return(void *cpu, int num, abi_long ret)
+static inline void record_syscall_return(CPUState *cpu, int num, abi_long ret)
{
qemu_plugin_vcpu_syscall_ret(cpu, num, ret);
+ gdb_syscall_return(cpu, num);
}
diff --git a/target/m68k/Kconfig b/target/m68k/Kconfig
index 23debad..9eae714 100644
--- a/target/m68k/Kconfig
+++ b/target/m68k/Kconfig
@@ -1,2 +1,3 @@
config M68K
bool
+ select SEMIHOSTING
diff --git a/target/mips/Kconfig b/target/mips/Kconfig
index 6adf145..eb19c94 100644
--- a/target/mips/Kconfig
+++ b/target/mips/Kconfig
@@ -1,5 +1,6 @@
config MIPS
bool
+ select SEMIHOSTING
config MIPS64
bool
diff --git a/target/nios2/Kconfig b/target/nios2/Kconfig
index 1529ab8..c65550c 100644
--- a/target/nios2/Kconfig
+++ b/target/nios2/Kconfig
@@ -1,2 +1,3 @@
config NIOS2
bool
+ select SEMIHOSTING
diff --git a/target/riscv/Kconfig b/target/riscv/Kconfig
index b9e5932..adb7de3 100644
--- a/target/riscv/Kconfig
+++ b/target/riscv/Kconfig
@@ -1,5 +1,7 @@
config RISCV32
bool
+ select ARM_COMPATIBLE_SEMIHOSTING # for do_common_semihosting()
config RISCV64
bool
+ select ARM_COMPATIBLE_SEMIHOSTING # for do_common_semihosting()
diff --git a/target/xtensa/Kconfig b/target/xtensa/Kconfig
index a3c8dc7..5e46049 100644
--- a/target/xtensa/Kconfig
+++ b/target/xtensa/Kconfig
@@ -1,2 +1,3 @@
config XTENSA
bool
+ select SEMIHOSTING
diff --git a/tests/docker/dockerfiles/opensuse-leap.docker b/tests/docker/dockerfiles/opensuse-leap.docker
index dc0e36c..cf75338 100644
--- a/tests/docker/dockerfiles/opensuse-leap.docker
+++ b/tests/docker/dockerfiles/opensuse-leap.docker
@@ -90,6 +90,7 @@
pcre-devel-static \
pipewire-devel \
pkgconfig \
+ python311 \
python311-base \
python311-pip \
python311-setuptools \
diff --git a/tests/lcitool/mappings.yml b/tests/lcitool/mappings.yml
index 0b90888..407c033 100644
--- a/tests/lcitool/mappings.yml
+++ b/tests/lcitool/mappings.yml
@@ -59,6 +59,10 @@
CentOSStream8:
OpenSUSELeap15:
+ python3-sqlite3:
+ CentOSStream8: python38
+ OpenSUSELeap15: python311
+
python3-tomli:
# test using tomllib
apk:
diff --git a/tests/lcitool/projects/qemu.yml b/tests/lcitool/projects/qemu.yml
index 82092c9..149b15d 100644
--- a/tests/lcitool/projects/qemu.yml
+++ b/tests/lcitool/projects/qemu.yml
@@ -97,6 +97,7 @@
- python3-pip
- python3-sphinx
- python3-sphinx-rtd-theme
+ - python3-sqlite3
- python3-tomli
- python3-venv
- rpm2cpio
diff --git a/tests/tcg/multiarch/Makefile.target b/tests/tcg/multiarch/Makefile.target
index 315a2e1..e10951a 100644
--- a/tests/tcg/multiarch/Makefile.target
+++ b/tests/tcg/multiarch/Makefile.target
@@ -108,13 +108,21 @@
--bin $< --test $(MULTIARCH_SRC)/gdbstub/prot-none.py, \
accessing PROT_NONE memory)
+run-gdbstub-catch-syscalls: catch-syscalls
+ $(call run-test, $@, $(GDB_SCRIPT) \
+ --gdb $(GDB) \
+ --qemu $(QEMU) --qargs "$(QEMU_OPTS)" \
+ --bin $< --test $(MULTIARCH_SRC)/gdbstub/catch-syscalls.py, \
+ hitting a syscall catchpoint)
+
else
run-gdbstub-%:
$(call skip-test, "gdbstub test $*", "need working gdb with $(patsubst -%,,$(TARGET_NAME)) support")
endif
EXTRA_RUNS += run-gdbstub-sha1 run-gdbstub-qxfer-auxv-read \
run-gdbstub-proc-mappings run-gdbstub-thread-breakpoint \
- run-gdbstub-registers run-gdbstub-prot-none
+ run-gdbstub-registers run-gdbstub-prot-none \
+ run-gdbstub-catch-syscalls
# ARM Compatible Semi Hosting Tests
#
diff --git a/tests/tcg/multiarch/catch-syscalls.c b/tests/tcg/multiarch/catch-syscalls.c
new file mode 100644
index 0000000..d1ff193
--- /dev/null
+++ b/tests/tcg/multiarch/catch-syscalls.c
@@ -0,0 +1,51 @@
+/*
+ * Test GDB syscall catchpoints.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <unistd.h>
+
+const char *catch_syscalls_state = "start";
+
+void end_of_main(void)
+{
+}
+
+int main(void)
+{
+ int ret = EXIT_FAILURE;
+ char c0 = 'A', c1;
+ int fd[2];
+
+ catch_syscalls_state = "pipe2";
+ if (pipe2(fd, 0)) {
+ goto out;
+ }
+
+ catch_syscalls_state = "write";
+ if (write(fd[1], &c0, sizeof(c0)) != sizeof(c0)) {
+ goto out_close;
+ }
+
+ catch_syscalls_state = "read";
+ if (read(fd[0], &c1, sizeof(c1)) != sizeof(c1)) {
+ goto out_close;
+ }
+
+ catch_syscalls_state = "check";
+ if (c0 == c1) {
+ ret = EXIT_SUCCESS;
+ }
+
+out_close:
+ catch_syscalls_state = "close";
+ close(fd[0]);
+ close(fd[1]);
+
+out:
+ catch_syscalls_state = "end";
+ end_of_main();
+ return ret;
+}
diff --git a/tests/tcg/multiarch/gdbstub/catch-syscalls.py b/tests/tcg/multiarch/gdbstub/catch-syscalls.py
new file mode 100644
index 0000000..ccce359
--- /dev/null
+++ b/tests/tcg/multiarch/gdbstub/catch-syscalls.py
@@ -0,0 +1,53 @@
+"""Test GDB syscall catchpoints.
+
+SPDX-License-Identifier: GPL-2.0-or-later
+"""
+from test_gdbstub import main, report
+
+
+def check_state(expected):
+ """Check the catch_syscalls_state value"""
+ actual = gdb.parse_and_eval("catch_syscalls_state").string()
+ report(actual == expected, "{} == {}".format(actual, expected))
+
+
+def run_test():
+ """Run through the tests one by one"""
+ gdb.Breakpoint("main")
+ gdb.execute("continue")
+
+ # Check that GDB stops for pipe2/read calls/returns, but not for write.
+ gdb.execute("delete")
+ try:
+ gdb.execute("catch syscall pipe2 read")
+ except gdb.error as exc:
+ exc_str = str(exc)
+ if "not supported on this architecture" in exc_str:
+ print("SKIP: {}".format(exc_str))
+ return
+ raise
+ for _ in range(2):
+ gdb.execute("continue")
+ check_state("pipe2")
+ for _ in range(2):
+ gdb.execute("continue")
+ check_state("read")
+
+ # Check that deletion works.
+ gdb.execute("delete")
+ gdb.Breakpoint("end_of_main")
+ gdb.execute("continue")
+ check_state("end")
+
+ # Check that catch-all works (libc should at least call exit).
+ gdb.execute("delete")
+ gdb.execute("catch syscall")
+ gdb.execute("continue")
+ gdb.execute("delete")
+ gdb.execute("continue")
+
+ exitcode = int(gdb.parse_and_eval("$_exitcode"))
+ report(exitcode == 0, "{} == 0".format(exitcode))
+
+
+main(run_test)