diff options
28 files changed, 639 insertions, 60 deletions
diff --git a/.gitlab-ci.d/crossbuilds.yml b/.gitlab-ci.d/crossbuilds.yml index 84ff2f6d2b..ac71a2abd3 100644 --- a/.gitlab-ci.d/crossbuilds.yml +++ b/.gitlab-ci.d/crossbuilds.yml @@ -165,7 +165,7 @@ cross-win32-system: job: win32-fedora-cross-container variables: IMAGE: fedora-win32-cross - EXTRA_CONFIGURE_OPTS: --enable-fdt=internal + EXTRA_CONFIGURE_OPTS: --enable-fdt=internal --disable-plugins CROSS_SKIP_TARGETS: alpha-softmmu avr-softmmu hppa-softmmu m68k-softmmu microblazeel-softmmu mips64el-softmmu nios2-softmmu artifacts: @@ -179,7 +179,7 @@ cross-win64-system: job: win64-fedora-cross-container variables: IMAGE: fedora-win64-cross - EXTRA_CONFIGURE_OPTS: --enable-fdt=internal + EXTRA_CONFIGURE_OPTS: --enable-fdt=internal --disable-plugins CROSS_SKIP_TARGETS: alpha-softmmu avr-softmmu hppa-softmmu m68k-softmmu microblazeel-softmmu nios2-softmmu or1k-softmmu rx-softmmu sh4eb-softmmu sparc64-softmmu diff --git a/.gitlab-ci.d/windows.yml b/.gitlab-ci.d/windows.yml index 12a987cd71..f7645f72b7 100644 --- a/.gitlab-ci.d/windows.yml +++ b/.gitlab-ci.d/windows.yml @@ -72,6 +72,7 @@ - .\msys64\usr\bin\bash -lc "pacman -Sy --noconfirm --needed bison diffutils flex git grep make sed + $MINGW_TARGET-binutils $MINGW_TARGET-capstone $MINGW_TARGET-ccache $MINGW_TARGET-curl @@ -30,10 +30,12 @@ malc <av1474@comtv.ru> malc <malc@c046a42c-6fe2-441c-8c8c-71466251a162> # Corrupted Author fields Aaron Larson <alarson@ddci.com> alarson@ddci.com Andreas Färber <andreas.faerber@web.de> Andreas Färber <andreas.faerber> +fanwenjie <fanwj@mail.ustc.edu.cn> fanwj@mail.ustc.edu.cn <fanwj@mail.ustc.edu.cn> Jason Wang <jasowang@redhat.com> Jason Wang <jasowang> Marek Dolata <mkdolata@us.ibm.com> mkdolata@us.ibm.com <mkdolata@us.ibm.com> Michael Ellerman <mpe@ellerman.id.au> michael@ozlabs.org <michael@ozlabs.org> Nick Hudson <hnick@vmware.com> hnick@vmware.com <hnick@vmware.com> +Timothée Cocault <timothee.cocault@gmail.com> timothee.cocault@gmail.com <timothee.cocault@gmail.com> # There is also a: # (no author) <(no author)@c046a42c-6fe2-441c-8c8c-71466251a162> diff --git a/MAINTAINERS b/MAINTAINERS index 33bb1ba0ea..e73a3ff544 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2942,7 +2942,7 @@ F: gdbstub/* F: include/exec/gdbstub.h F: include/gdbstub/* F: gdb-xml/ -F: tests/tcg/multiarch/gdbstub/ +F: tests/tcg/multiarch/gdbstub/* F: scripts/feature_to_c.py F: scripts/probe-gdb-support.py diff --git a/configs/targets/loongarch64-linux-user.mak b/configs/targets/loongarch64-linux-user.mak index 7d1b964020..d878e5a113 100644 --- a/configs/targets/loongarch64-linux-user.mak +++ b/configs/targets/loongarch64-linux-user.mak @@ -1,3 +1,4 @@ # Default configuration for loongarch64-linux-user TARGET_ARCH=loongarch64 TARGET_BASE_ARCH=loongarch +TARGET_XML_FILES=gdb-xml/loongarch-base64.xml gdb-xml/loongarch-fpu.xml @@ -309,6 +309,7 @@ fi ar="${AR-${cross_prefix}ar}" as="${AS-${cross_prefix}as}" ccas="${CCAS-$cc}" +dlltool="${DLLTOOL-${cross_prefix}dlltool}" objcopy="${OBJCOPY-${cross_prefix}objcopy}" ld="${LD-${cross_prefix}ld}" ranlib="${RANLIB-${cross_prefix}ranlib}" @@ -1010,9 +1011,9 @@ if test "$targetos" = "bogus"; then fi # test for any invalid configuration combinations -if test "$targetos" = "windows"; then +if test "$targetos" = "windows" && ! has "$dlltool"; then if test "$plugins" = "yes"; then - error_exit "TCG plugins not currently supported on Windows platforms" + error_exit "TCG plugins requires dlltool to build on Windows platforms" fi plugins="no" fi @@ -1659,9 +1660,15 @@ echo "SRC_PATH=$source_path/contrib/plugins" >> contrib/plugins/$config_host_mak echo "PKG_CONFIG=${pkg_config}" >> contrib/plugins/$config_host_mak echo "CC=$cc $CPU_CFLAGS" >> contrib/plugins/$config_host_mak echo "CFLAGS=${CFLAGS-$default_cflags} $EXTRA_CFLAGS" >> contrib/plugins/$config_host_mak +if test "$targetos" = windows; then + echo "DLLTOOL=$dlltool" >> contrib/plugins/$config_host_mak +fi if test "$targetos" = darwin; then echo "CONFIG_DARWIN=y" >> contrib/plugins/$config_host_mak fi +if test "$targetos" = windows; then + echo "CONFIG_WIN32=y" >> contrib/plugins/$config_host_mak +fi # tests/tcg configuration (config_host_mak=tests/tcg/config-host.mak @@ -1764,6 +1771,7 @@ if test "$skip_meson" = no; then test -n "$cxx" && echo "cpp = [$(meson_quote $cxx $CPU_CFLAGS)]" >> $cross test -n "$objcc" && echo "objc = [$(meson_quote $objcc $CPU_CFLAGS)]" >> $cross echo "ar = [$(meson_quote $ar)]" >> $cross + echo "dlltool = [$(meson_quote $dlltool)]" >> $cross echo "nm = [$(meson_quote $nm)]" >> $cross echo "pkgconfig = [$(meson_quote $pkg_config)]" >> $cross echo "pkg-config = [$(meson_quote $pkg_config)]" >> $cross @@ -1869,6 +1877,7 @@ preserve_env CC preserve_env CFLAGS preserve_env CXX preserve_env CXXFLAGS +preserve_env DLLTOOL preserve_env LD preserve_env LDFLAGS preserve_env LD_LIBRARY_PATH diff --git a/contrib/gitdm/domain-map b/contrib/gitdm/domain-map index 3e31a06245..bf1dce03fd 100644 --- a/contrib/gitdm/domain-map +++ b/contrib/gitdm/domain-map @@ -12,15 +12,18 @@ amd.com AMD aspeedtech.com ASPEED Technology Inc. baidu.com Baidu bytedance.com ByteDance +cestc.cn Cestc cmss.chinamobile.com China Mobile citrix.com Citrix crudebyte.com Crudebyte chinatelecom.cn China Telecom +daynix.com Daynix eldorado.org.br Instituto de Pesquisas Eldorado fb.com Facebook fujitsu.com Fujitsu google.com Google greensocs.com GreenSocs +hisilicon.com Huawei huawei.com Huawei ibm.com IBM igalia.com Igalia @@ -38,6 +41,7 @@ proxmox.com Proxmox quicinc.com Qualcomm Innovation Center redhat.com Red Hat rev.ng rev.ng Labs +rivosinc.com Rivos Inc rt-rk.com RT-RK samsung.com Samsung siemens.com Siemens diff --git a/contrib/plugins/Makefile b/contrib/plugins/Makefile index 8ba78c7a32..1783750cf6 100644 --- a/contrib/plugins/Makefile +++ b/contrib/plugins/Makefile @@ -17,12 +17,25 @@ NAMES += execlog NAMES += hotblocks NAMES += hotpages NAMES += howvec + +# The lockstep example communicates using unix sockets, +# and can't be easily made to work on windows. +ifneq ($(CONFIG_WIN32),y) NAMES += lockstep +endif + NAMES += hwprofile NAMES += cache NAMES += drcov -SONAMES := $(addsuffix .so,$(addprefix lib,$(NAMES))) +ifeq ($(CONFIG_WIN32),y) +SO_SUFFIX := .dll +LDLIBS += $(shell $(PKG_CONFIG) --libs glib-2.0) +else +SO_SUFFIX := .so +endif + +SONAMES := $(addsuffix $(SO_SUFFIX),$(addprefix lib,$(NAMES))) # The main QEMU uses Glib extensively so it's perfectly fine to use it # in plugins (which many example do). @@ -35,15 +48,20 @@ all: $(SONAMES) %.o: %.c $(CC) $(CFLAGS) $(PLUGIN_CFLAGS) -c -o $@ $< -lib%.so: %.o -ifeq ($(CONFIG_DARWIN),y) +ifeq ($(CONFIG_WIN32),y) +lib%$(SO_SUFFIX): %.o win32_linker.o ../../plugins/qemu_plugin_api.lib + $(CC) -shared -o $@ $^ $(LDLIBS) +else ifeq ($(CONFIG_DARWIN),y) +lib%$(SO_SUFFIX): %.o $(CC) -bundle -Wl,-undefined,dynamic_lookup -o $@ $^ $(LDLIBS) else +lib%$(SO_SUFFIX): %.o $(CC) -shared -o $@ $^ $(LDLIBS) endif + clean: - rm -f *.o *.so *.d + rm -f *.o *$(SO_SUFFIX) *.d rm -Rf .libs .PHONY: all clean diff --git a/contrib/plugins/win32_linker.c b/contrib/plugins/win32_linker.c new file mode 100644 index 0000000000..7534b2b8bf --- /dev/null +++ b/contrib/plugins/win32_linker.c @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023, Greg Manning <gmanning@rapitasystems.com> + * + * This hook, __pfnDliFailureHook2, is documented in the microsoft documentation here: + * https://learn.microsoft.com/en-us/cpp/build/reference/error-handling-and-notification + * It gets called when a delay-loaded DLL encounters various errors. + * We handle the specific case of a DLL looking for a "qemu.exe", + * and give it the running executable (regardless of what it is named). + * + * This work is licensed under the terms of the GNU LGPL, version 2 or later. + * See the COPYING.LIB file in the top-level directory. + */ + +#include <windows.h> +#include <delayimp.h> + +FARPROC WINAPI dll_failure_hook(unsigned dliNotify, PDelayLoadInfo pdli); + + +PfnDliHook __pfnDliFailureHook2 = dll_failure_hook; + +FARPROC WINAPI dll_failure_hook(unsigned dliNotify, PDelayLoadInfo pdli) { + if (dliNotify == dliFailLoadLib) { + /* If the failing request was for qemu.exe, ... */ + if (strcmp(pdli->szDll, "qemu.exe") == 0) { + /* Then pass back a pointer to the top level module. */ + HMODULE top = GetModuleHandle(NULL); + return (FARPROC) top; + } + } + /* Otherwise we can't do anything special. */ + return 0; +} + diff --git a/cpu-target.c b/cpu-target.c index f3e1ad8bcd..508013e23d 100644 --- a/cpu-target.c +++ b/cpu-target.c @@ -42,7 +42,6 @@ #include "hw/core/accel-cpu.h" #include "trace/trace-root.h" #include "qemu/accel.h" -#include "qemu/plugin.h" uintptr_t qemu_host_page_size; intptr_t qemu_host_page_mask; @@ -143,11 +142,6 @@ bool cpu_exec_realizefn(CPUState *cpu, Error **errp) /* Wait until cpu initialization complete before exposing cpu. */ cpu_list_add(cpu); - /* Plugin initialization must wait until cpu_index assigned. */ - if (tcg_enabled()) { - qemu_plugin_vcpu_init_hook(cpu); - } - #ifdef CONFIG_USER_ONLY assert(qdev_get_vmsd(DEVICE(cpu)) == NULL || qdev_get_vmsd(DEVICE(cpu))->unmigratable); @@ -176,11 +170,6 @@ void cpu_exec_unrealizefn(CPUState *cpu) } #endif - /* Call the plugin hook before clearing cpu->cpu_index in cpu_list_remove */ - if (tcg_enabled()) { - qemu_plugin_vcpu_exit_hook(cpu); - } - cpu_list_remove(cpu); /* * Now that the vCPU has been removed from the RCU list, we can call diff --git a/gdb-xml/arm-neon.xml b/gdb-xml/arm-neon.xml index 9dce0a996f..d61f6b8549 100644 --- a/gdb-xml/arm-neon.xml +++ b/gdb-xml/arm-neon.xml @@ -76,7 +76,7 @@ <reg name="q8" bitsize="128" type="neon_q"/> <reg name="q9" bitsize="128" type="neon_q"/> <reg name="q10" bitsize="128" type="neon_q"/> - <reg name="q10" bitsize="128" type="neon_q"/> + <reg name="q11" bitsize="128" type="neon_q"/> <reg name="q12" bitsize="128" type="neon_q"/> <reg name="q13" bitsize="128" type="neon_q"/> <reg name="q14" bitsize="128" type="neon_q"/> diff --git a/gdbstub/gdbstub.c b/gdbstub/gdbstub.c index 29540a0284..ebb912da1b 100644 --- a/gdbstub/gdbstub.c +++ b/gdbstub/gdbstub.c @@ -422,6 +422,84 @@ static const char *get_feature_xml(const char *p, const char **newp, return NULL; } +void gdb_feature_builder_init(GDBFeatureBuilder *builder, GDBFeature *feature, + const char *name, const char *xmlname, + int base_reg) +{ + char *header = g_markup_printf_escaped( + "<?xml version=\"1.0\"?>" + "<!DOCTYPE feature SYSTEM \"gdb-target.dtd\">" + "<feature name=\"%s\">", + name); + + builder->feature = feature; + builder->xml = g_ptr_array_new(); + g_ptr_array_add(builder->xml, header); + builder->base_reg = base_reg; + feature->xmlname = xmlname; + feature->num_regs = 0; +} + +void gdb_feature_builder_append_tag(const GDBFeatureBuilder *builder, + const char *format, ...) +{ + va_list ap; + va_start(ap, format); + g_ptr_array_add(builder->xml, g_markup_vprintf_escaped(format, ap)); + va_end(ap); +} + +void gdb_feature_builder_append_reg(const GDBFeatureBuilder *builder, + const char *name, + int bitsize, + int regnum, + const char *type, + const char *group) +{ + if (builder->feature->num_regs < regnum) { + builder->feature->num_regs = regnum; + } + + if (group) { + gdb_feature_builder_append_tag( + builder, + "<reg name=\"%s\" bitsize=\"%d\" regnum=\"%d\" type=\"%s\" group=\"%s\"/>", + name, bitsize, builder->base_reg + regnum, type, group); + } else { + gdb_feature_builder_append_tag( + builder, + "<reg name=\"%s\" bitsize=\"%d\" regnum=\"%d\" type=\"%s\"/>", + name, bitsize, builder->base_reg + regnum, type); + } +} + +void gdb_feature_builder_end(const GDBFeatureBuilder *builder) +{ + g_ptr_array_add(builder->xml, (void *)"</feature>"); + g_ptr_array_add(builder->xml, NULL); + + builder->feature->xml = g_strjoinv(NULL, (void *)builder->xml->pdata); + + for (guint i = 0; i < builder->xml->len - 2; i++) { + g_free(g_ptr_array_index(builder->xml, i)); + } + + g_ptr_array_free(builder->xml, TRUE); +} + +const GDBFeature *gdb_find_static_feature(const char *xmlname) +{ + const GDBFeature *feature; + + for (feature = gdb_static_features; feature->xmlname; feature++) { + if (!strcmp(feature->xmlname, xmlname)) { + return feature; + } + } + + g_assert_not_reached(); +} + static int gdb_read_register(CPUState *cpu, GByteArray *buf, int reg) { CPUClass *cc = CPU_GET_CLASS(cpu); diff --git a/hw/core/cpu-common.c b/hw/core/cpu-common.c index d4112b8919..82dae51a55 100644 --- a/hw/core/cpu-common.c +++ b/hw/core/cpu-common.c @@ -214,6 +214,11 @@ static void cpu_common_realizefn(DeviceState *dev, Error **errp) cpu_resume(cpu); } + /* Plugin initialization must wait until the cpu is fully realized. */ + if (tcg_enabled()) { + qemu_plugin_vcpu_init_hook(cpu); + } + /* NOTE: latest generic point where the cpu is fully realized */ } @@ -221,6 +226,11 @@ static void cpu_common_unrealizefn(DeviceState *dev) { CPUState *cpu = CPU(dev); + /* Call the plugin hook before clearing the cpu is fully unrealized */ + if (tcg_enabled()) { + qemu_plugin_vcpu_exit_hook(cpu); + } + /* NOTE: latest generic point before the cpu is fully unrealized */ cpu_exec_unrealizefn(cpu); } diff --git a/include/exec/gdbstub.h b/include/exec/gdbstub.h index 1a01c35f8e..d8a3c56fa2 100644 --- a/include/exec/gdbstub.h +++ b/include/exec/gdbstub.h @@ -13,8 +13,15 @@ typedef struct GDBFeature { const char *xmlname; const char *xml; + int num_regs; } GDBFeature; +typedef struct GDBFeatureBuilder { + GDBFeature *feature; + GPtrArray *xml; + int base_reg; +} GDBFeatureBuilder; + /* Get or set a register. Returns the size of the register. */ typedef int (*gdb_get_reg_cb)(CPUArchState *env, GByteArray *buf, int reg); @@ -43,6 +50,58 @@ void gdb_register_coprocessor(CPUState *cpu, */ int gdbserver_start(const char *port_or_device); +/** + * gdb_feature_builder_init() - Initialize GDBFeatureBuilder. + * @builder: The builder to be initialized. + * @feature: The feature to be filled. + * @name: The name of the feature. + * @xmlname: The name of the XML. + * @base_reg: The base number of the register ID. + */ +void gdb_feature_builder_init(GDBFeatureBuilder *builder, GDBFeature *feature, + const char *name, const char *xmlname, + int base_reg); + +/** + * gdb_feature_builder_append_tag() - Append a tag. + * @builder: The builder. + * @format: The format of the tag. + * @...: The values to be formatted. + */ +void G_GNUC_PRINTF(2, 3) +gdb_feature_builder_append_tag(const GDBFeatureBuilder *builder, + const char *format, ...); + +/** + * gdb_feature_builder_append_reg() - Append a register. + * @builder: The builder. + * @name: The register's name; it must be unique within a CPU. + * @bitsize: The register's size, in bits. + * @regnum: The offset of the register's number in the feature. + * @type: The type of the register. + * @group: The register group to which this register belongs; it can be NULL. + */ +void gdb_feature_builder_append_reg(const GDBFeatureBuilder *builder, + const char *name, + int bitsize, + int regnum, + const char *type, + const char *group); + +/** + * gdb_feature_builder_end() - End building GDBFeature. + * @builder: The builder. + */ +void gdb_feature_builder_end(const GDBFeatureBuilder *builder); + +/** + * gdb_find_static_feature() - Find a static feature. + * @xmlname: The name of the XML. + * + * Return: The static feature. + */ +const GDBFeature *gdb_find_static_feature(const char *xmlname); + void gdb_set_stop_cpu(CPUState *cpu); /* in gdbstub-xml.c, generated by scripts/feature_to_c.py */ diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h index 50a9957279..4daab6efd2 100644 --- a/include/qemu/qemu-plugin.h +++ b/include/qemu/qemu-plugin.h @@ -22,15 +22,18 @@ * https://gcc.gnu.org/wiki/Visibility */ #if defined _WIN32 || defined __CYGWIN__ - #ifdef BUILDING_DLL - #define QEMU_PLUGIN_EXPORT __declspec(dllexport) - #else + #ifdef CONFIG_PLUGIN #define QEMU_PLUGIN_EXPORT __declspec(dllimport) + #define QEMU_PLUGIN_API __declspec(dllexport) + #else + #define QEMU_PLUGIN_EXPORT __declspec(dllexport) + #define QEMU_PLUGIN_API __declspec(dllimport) #endif #define QEMU_PLUGIN_LOCAL #else #define QEMU_PLUGIN_EXPORT __attribute__((visibility("default"))) #define QEMU_PLUGIN_LOCAL __attribute__((visibility("hidden"))) + #define QEMU_PLUGIN_API #endif /** @@ -147,6 +150,7 @@ typedef void (*qemu_plugin_vcpu_udata_cb_t)(unsigned int vcpu_index, * * Note: Calling this function from qemu_plugin_install() is a bug. */ +QEMU_PLUGIN_API void qemu_plugin_uninstall(qemu_plugin_id_t id, qemu_plugin_simple_cb_t cb); /** @@ -160,6 +164,7 @@ void qemu_plugin_uninstall(qemu_plugin_id_t id, qemu_plugin_simple_cb_t cb); * Plugins are reset asynchronously, and therefore the given plugin receives * callbacks until @cb is called. */ +QEMU_PLUGIN_API void qemu_plugin_reset(qemu_plugin_id_t id, qemu_plugin_simple_cb_t cb); /** @@ -171,6 +176,7 @@ void qemu_plugin_reset(qemu_plugin_id_t id, qemu_plugin_simple_cb_t cb); * * See also: qemu_plugin_register_vcpu_exit_cb() */ +QEMU_PLUGIN_API void qemu_plugin_register_vcpu_init_cb(qemu_plugin_id_t id, qemu_plugin_vcpu_simple_cb_t cb); @@ -183,6 +189,7 @@ void qemu_plugin_register_vcpu_init_cb(qemu_plugin_id_t id, * * See also: qemu_plugin_register_vcpu_init_cb() */ +QEMU_PLUGIN_API void qemu_plugin_register_vcpu_exit_cb(qemu_plugin_id_t id, qemu_plugin_vcpu_simple_cb_t cb); @@ -193,6 +200,7 @@ void qemu_plugin_register_vcpu_exit_cb(qemu_plugin_id_t id, * * The @cb function is called every time a vCPU idles. */ +QEMU_PLUGIN_API void qemu_plugin_register_vcpu_idle_cb(qemu_plugin_id_t id, qemu_plugin_vcpu_simple_cb_t cb); @@ -203,6 +211,7 @@ void qemu_plugin_register_vcpu_idle_cb(qemu_plugin_id_t id, * * The @cb function is called every time a vCPU resumes execution. */ +QEMU_PLUGIN_API void qemu_plugin_register_vcpu_resume_cb(qemu_plugin_id_t id, qemu_plugin_vcpu_simple_cb_t cb); @@ -253,6 +262,7 @@ typedef void (*qemu_plugin_vcpu_tb_trans_cb_t)(qemu_plugin_id_t id, * callbacks to be triggered when the block or individual instruction * executes. */ +QEMU_PLUGIN_API void qemu_plugin_register_vcpu_tb_trans_cb(qemu_plugin_id_t id, qemu_plugin_vcpu_tb_trans_cb_t cb); @@ -265,6 +275,7 @@ void qemu_plugin_register_vcpu_tb_trans_cb(qemu_plugin_id_t id, * * The @cb function is called every time a translated unit executes. */ +QEMU_PLUGIN_API void qemu_plugin_register_vcpu_tb_exec_cb(struct qemu_plugin_tb *tb, qemu_plugin_vcpu_udata_cb_t cb, enum qemu_plugin_cb_flags flags, @@ -296,6 +307,7 @@ enum qemu_plugin_op { * Note: ops are not atomic so in multi-threaded/multi-smp situations * you will get inexact results. */ +QEMU_PLUGIN_API void qemu_plugin_register_vcpu_tb_exec_inline(struct qemu_plugin_tb *tb, enum qemu_plugin_op op, void *ptr, uint64_t imm); @@ -309,6 +321,7 @@ void qemu_plugin_register_vcpu_tb_exec_inline(struct qemu_plugin_tb *tb, * * The @cb function is called every time an instruction is executed */ +QEMU_PLUGIN_API void qemu_plugin_register_vcpu_insn_exec_cb(struct qemu_plugin_insn *insn, qemu_plugin_vcpu_udata_cb_t cb, enum qemu_plugin_cb_flags flags, @@ -324,6 +337,7 @@ void qemu_plugin_register_vcpu_insn_exec_cb(struct qemu_plugin_insn *insn, * Insert an inline op to every time an instruction executes. Useful * if you just want to increment a single counter somewhere in memory. */ +QEMU_PLUGIN_API void qemu_plugin_register_vcpu_insn_exec_inline(struct qemu_plugin_insn *insn, enum qemu_plugin_op op, void *ptr, uint64_t imm); @@ -334,6 +348,7 @@ void qemu_plugin_register_vcpu_insn_exec_inline(struct qemu_plugin_insn *insn, * * Returns: number of instructions in this block */ +QEMU_PLUGIN_API size_t qemu_plugin_tb_n_insns(const struct qemu_plugin_tb *tb); /** @@ -342,6 +357,7 @@ size_t qemu_plugin_tb_n_insns(const struct qemu_plugin_tb *tb); * * Returns: virtual address of block start */ +QEMU_PLUGIN_API uint64_t qemu_plugin_tb_vaddr(const struct qemu_plugin_tb *tb); /** @@ -355,6 +371,7 @@ uint64_t qemu_plugin_tb_vaddr(const struct qemu_plugin_tb *tb); * * Returns: opaque handle to instruction */ +QEMU_PLUGIN_API struct qemu_plugin_insn * qemu_plugin_tb_get_insn(const struct qemu_plugin_tb *tb, size_t idx); @@ -368,6 +385,7 @@ qemu_plugin_tb_get_insn(const struct qemu_plugin_tb *tb, size_t idx); * Returns: pointer to a stream of bytes containing the value of this * instructions opcode. */ +QEMU_PLUGIN_API const void *qemu_plugin_insn_data(const struct qemu_plugin_insn *insn); /** @@ -376,6 +394,7 @@ const void *qemu_plugin_insn_data(const struct qemu_plugin_insn *insn); * * Returns: size of instruction in bytes */ +QEMU_PLUGIN_API size_t qemu_plugin_insn_size(const struct qemu_plugin_insn *insn); /** @@ -384,6 +403,7 @@ size_t qemu_plugin_insn_size(const struct qemu_plugin_insn *insn); * * Returns: virtual address of instruction */ +QEMU_PLUGIN_API uint64_t qemu_plugin_insn_vaddr(const struct qemu_plugin_insn *insn); /** @@ -392,6 +412,7 @@ uint64_t qemu_plugin_insn_vaddr(const struct qemu_plugin_insn *insn); * * Returns: hardware (physical) target address of instruction */ +QEMU_PLUGIN_API void *qemu_plugin_insn_haddr(const struct qemu_plugin_insn *insn); /** @@ -410,6 +431,7 @@ struct qemu_plugin_hwaddr; * * Returns: size of access in ^2 (0=byte, 1=16bit, 2=32bit etc...) */ +QEMU_PLUGIN_API unsigned int qemu_plugin_mem_size_shift(qemu_plugin_meminfo_t info); /** * qemu_plugin_mem_is_sign_extended() - was the access sign extended @@ -417,6 +439,7 @@ unsigned int qemu_plugin_mem_size_shift(qemu_plugin_meminfo_t info); * * Returns: true if it was, otherwise false */ +QEMU_PLUGIN_API bool qemu_plugin_mem_is_sign_extended(qemu_plugin_meminfo_t info); /** * qemu_plugin_mem_is_big_endian() - was the access big endian @@ -424,6 +447,7 @@ bool qemu_plugin_mem_is_sign_extended(qemu_plugin_meminfo_t info); * * Returns: true if it was, otherwise false */ +QEMU_PLUGIN_API bool qemu_plugin_mem_is_big_endian(qemu_plugin_meminfo_t info); /** * qemu_plugin_mem_is_store() - was the access a store @@ -431,6 +455,7 @@ bool qemu_plugin_mem_is_big_endian(qemu_plugin_meminfo_t info); * * Returns: true if it was, otherwise false */ +QEMU_PLUGIN_API bool qemu_plugin_mem_is_store(qemu_plugin_meminfo_t info); /** @@ -446,6 +471,7 @@ bool qemu_plugin_mem_is_store(qemu_plugin_meminfo_t info); * information about the handle should be recovered before the * callback returns. */ +QEMU_PLUGIN_API struct qemu_plugin_hwaddr *qemu_plugin_get_hwaddr(qemu_plugin_meminfo_t info, uint64_t vaddr); @@ -462,6 +488,7 @@ struct qemu_plugin_hwaddr *qemu_plugin_get_hwaddr(qemu_plugin_meminfo_t info, * Returns true if the handle's memory operation is to memory-mapped IO, or * false if it is to RAM */ +QEMU_PLUGIN_API bool qemu_plugin_hwaddr_is_io(const struct qemu_plugin_hwaddr *haddr); /** @@ -473,12 +500,14 @@ bool qemu_plugin_hwaddr_is_io(const struct qemu_plugin_hwaddr *haddr); * Note that the returned physical address may not be unique if you are dealing * with multiple address spaces. */ +QEMU_PLUGIN_API uint64_t qemu_plugin_hwaddr_phys_addr(const struct qemu_plugin_hwaddr *haddr); /* * Returns a string representing the device. The string is valid for * the lifetime of the plugin. */ +QEMU_PLUGIN_API const char *qemu_plugin_hwaddr_device_name(const struct qemu_plugin_hwaddr *h); /** @@ -513,6 +542,7 @@ typedef void (*qemu_plugin_vcpu_mem_cb_t) (unsigned int vcpu_index, * callback so the plugin is responsible for ensuring it doesn't get * confused by making appropriate use of locking if required. */ +QEMU_PLUGIN_API void qemu_plugin_register_vcpu_mem_cb(struct qemu_plugin_insn *insn, qemu_plugin_vcpu_mem_cb_t cb, enum qemu_plugin_cb_flags flags, @@ -531,6 +561,7 @@ void qemu_plugin_register_vcpu_mem_cb(struct qemu_plugin_insn *insn, * instruction. This provides for a lightweight but not thread-safe * way of counting the number of operations done. */ +QEMU_PLUGIN_API void qemu_plugin_register_vcpu_mem_inline(struct qemu_plugin_insn *insn, enum qemu_plugin_mem_rw rw, enum qemu_plugin_op op, void *ptr, @@ -544,6 +575,7 @@ typedef void uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6, uint64_t a7, uint64_t a8); +QEMU_PLUGIN_API void qemu_plugin_register_vcpu_syscall_cb(qemu_plugin_id_t id, qemu_plugin_vcpu_syscall_cb_t cb); @@ -551,6 +583,7 @@ typedef void (*qemu_plugin_vcpu_syscall_ret_cb_t)(qemu_plugin_id_t id, unsigned int vcpu_idx, int64_t num, int64_t ret); +QEMU_PLUGIN_API void qemu_plugin_register_vcpu_syscall_ret_cb(qemu_plugin_id_t id, qemu_plugin_vcpu_syscall_ret_cb_t cb); @@ -563,6 +596,7 @@ qemu_plugin_register_vcpu_syscall_ret_cb(qemu_plugin_id_t id, * Returns an allocated string containing the disassembly */ +QEMU_PLUGIN_API char *qemu_plugin_insn_disas(const struct qemu_plugin_insn *insn); /** @@ -572,6 +606,7 @@ char *qemu_plugin_insn_disas(const struct qemu_plugin_insn *insn); * Return a static string referring to the symbol. This is dependent * on the binary QEMU is running having provided a symbol table. */ +QEMU_PLUGIN_API const char *qemu_plugin_insn_symbol(const struct qemu_plugin_insn *insn); /** @@ -583,9 +618,11 @@ const char *qemu_plugin_insn_symbol(const struct qemu_plugin_insn *insn); * * See also: qemu_plugin_register_vcpu_init_cb() */ +QEMU_PLUGIN_API void qemu_plugin_vcpu_for_each(qemu_plugin_id_t id, qemu_plugin_vcpu_simple_cb_t cb); +QEMU_PLUGIN_API void qemu_plugin_register_flush_cb(qemu_plugin_id_t id, qemu_plugin_simple_cb_t cb); @@ -602,6 +639,7 @@ void qemu_plugin_register_flush_cb(qemu_plugin_id_t id, * In user-mode it is possible a few un-instrumented instructions from * child threads may run before the host kernel reaps the threads. */ +QEMU_PLUGIN_API void qemu_plugin_register_atexit_cb(qemu_plugin_id_t id, qemu_plugin_udata_cb_t cb, void *userdata); @@ -615,6 +653,7 @@ int qemu_plugin_n_max_vcpus(void); * qemu_plugin_outs() - output string via QEMU's logging system * @string: a string */ +QEMU_PLUGIN_API void qemu_plugin_outs(const char *string); /** @@ -628,6 +667,7 @@ void qemu_plugin_outs(const char *string); * returns true if the combination @name=@val parses correctly to a boolean * argument, and false otherwise */ +QEMU_PLUGIN_API bool qemu_plugin_bool_parse(const char *name, const char *val, bool *ret); /** @@ -638,6 +678,7 @@ bool qemu_plugin_bool_parse(const char *name, const char *val, bool *ret); * return NULL. The user should g_free() the string once no longer * needed. */ +QEMU_PLUGIN_API const char *qemu_plugin_path_to_binary(void); /** @@ -646,6 +687,7 @@ const char *qemu_plugin_path_to_binary(void); * Returns the nominal start address of the main text segment in * user-mode. Currently returns 0 for system emulation. */ +QEMU_PLUGIN_API uint64_t qemu_plugin_start_code(void); /** @@ -654,6 +696,7 @@ uint64_t qemu_plugin_start_code(void); * Returns the nominal end address of the main text segment in * user-mode. Currently returns 0 for system emulation. */ +QEMU_PLUGIN_API uint64_t qemu_plugin_end_code(void); /** @@ -662,6 +705,7 @@ uint64_t qemu_plugin_end_code(void); * Returns the nominal entry address of the main text segment in * user-mode. Currently returns 0 for system emulation. */ +QEMU_PLUGIN_API uint64_t qemu_plugin_entry_code(void); #endif /* QEMU_QEMU_PLUGIN_H */ diff --git a/meson.build b/meson.build index 4848930680..d7d841e71e 100644 --- a/meson.build +++ b/meson.build @@ -3944,6 +3944,11 @@ endforeach if get_option('plugins') install_headers('include/qemu/qemu-plugin.h') + if targetos == 'windows' + # On windows, we want to deliver the qemu_plugin_api.lib file in the qemu installer, + # so that plugin authors can compile against it. + install_data(win32_qemu_plugin_api_lib, install_dir: 'lib') + endif endif subdir('qga') diff --git a/plugins/meson.build b/plugins/meson.build index 71ed996ed3..40d24529c0 100644 --- a/plugins/meson.build +++ b/plugins/meson.build @@ -14,6 +14,25 @@ if not enable_modules endif if get_option('plugins') + if targetos == 'windows' + dlltool = find_program('dlltool', required: true) + + # Generate a .lib file for plugins to link against. + # First, create a .def file listing all the symbols a plugin should expect to have + # available in qemu + win32_plugin_def = configure_file( + input: files('qemu-plugins.symbols'), + output: 'qemu_plugin_api.def', + capture: true, + command: ['sed', '-e', '0,/^/s//EXPORTS/; s/[{};]//g', '@INPUT@']) + # then use dlltool to assemble a delaylib. + win32_qemu_plugin_api_lib = configure_file( + input: win32_plugin_def, + output: 'qemu_plugin_api.lib', + command: [dlltool, '--input-def', '@INPUT@', + '--output-delaylib', '@OUTPUT@', '--dllname', 'qemu.exe'] + ) + endif specific_ss.add(files( 'loader.c', 'core.c', diff --git a/scripts/feature_to_c.py b/scripts/feature_to_c.py index bcbcb83beb..e04d6b2df7 100644 --- a/scripts/feature_to_c.py +++ b/scripts/feature_to_c.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0-or-later -import os, sys +import os, sys, xml.etree.ElementTree def writeliteral(indent, bytes): sys.stdout.write(' ' * indent) @@ -39,10 +39,52 @@ for input in sys.argv[1:]: with open(input, 'rb') as file: read = file.read() + parser = xml.etree.ElementTree.XMLPullParser(['start', 'end']) + parser.feed(read) + events = parser.read_events() + event, element = next(events) + if event != 'start': + sys.stderr.write(f'unexpected event: {event}\n') + exit(1) + if element.tag != 'feature': + sys.stderr.write(f'unexpected start tag: {element.tag}\n') + exit(1) + + regnum = 0 + regnums = [] + tags = ['feature'] + for event, element in events: + if event == 'end': + if element.tag != tags[len(tags) - 1]: + sys.stderr.write(f'unexpected end tag: {element.tag}\n') + exit(1) + + tags.pop() + if element.tag == 'feature': + break + elif event == 'start': + if len(tags) < 2 and element.tag == 'reg': + if 'regnum' in element.attrib: + regnum = int(element.attrib['regnum']) + + regnums.append(regnum) + regnum += 1 + + tags.append(element.tag) + else: + raise Exception(f'unexpected event: {event}\n') + + if len(tags): + sys.stderr.write('unterminated feature tag\n') + exit(1) + + base_reg = min(regnums) + num_regs = max(regnums) - base_reg + 1 if len(regnums) else 0 + sys.stdout.write(' {\n') writeliteral(8, bytes(os.path.basename(input), 'utf-8')) sys.stdout.write(',\n') writeliteral(8, read) - sys.stdout.write('\n },\n') + sys.stdout.write(f',\n {num_regs},\n }},\n') sys.stdout.write(' { NULL }\n};\n') diff --git a/target/arm/debug_helper.c b/target/arm/debug_helper.c index 79a3659c0c..cbfba532f5 100644 --- a/target/arm/debug_helper.c +++ b/target/arm/debug_helper.c @@ -937,14 +937,14 @@ static const ARMCPRegInfo debug_cp_reginfo[] = { */ { .name = "DBGDRAR", .cp = 14, .crn = 1, .crm = 0, .opc1 = 0, .opc2 = 0, .access = PL0_R, .accessfn = access_tdra, - .type = ARM_CP_CONST, .resetvalue = 0 }, + .type = ARM_CP_CONST | ARM_CP_NO_GDB, .resetvalue = 0 }, { .name = "MDRAR_EL1", .state = ARM_CP_STATE_AA64, .opc0 = 2, .opc1 = 0, .crn = 1, .crm = 0, .opc2 = 0, .access = PL1_R, .accessfn = access_tdra, .type = ARM_CP_CONST, .resetvalue = 0 }, { .name = "DBGDSAR", .cp = 14, .crn = 2, .crm = 0, .opc1 = 0, .opc2 = 0, .access = PL0_R, .accessfn = access_tdra, - .type = ARM_CP_CONST, .resetvalue = 0 }, + .type = ARM_CP_CONST | ARM_CP_NO_GDB, .resetvalue = 0 }, /* Monitor debug system control register; the 32-bit alias is DBGDSCRext. */ { .name = "MDSCR_EL1", .state = ARM_CP_STATE_BOTH, .cp = 14, .opc0 = 2, .opc1 = 0, .crn = 0, .crm = 2, .opc2 = 2, @@ -1065,9 +1065,11 @@ static const ARMCPRegInfo debug_cp_reginfo[] = { static const ARMCPRegInfo debug_lpae_cp_reginfo[] = { /* 64 bit access versions of the (dummy) debug registers */ { .name = "DBGDRAR", .cp = 14, .crm = 1, .opc1 = 0, - .access = PL0_R, .type = ARM_CP_CONST | ARM_CP_64BIT, .resetvalue = 0 }, + .access = PL0_R, .type = ARM_CP_CONST | ARM_CP_64BIT | ARM_CP_NO_GDB, + .resetvalue = 0 }, { .name = "DBGDSAR", .cp = 14, .crm = 2, .opc1 = 0, - .access = PL0_R, .type = ARM_CP_CONST | ARM_CP_64BIT, .resetvalue = 0 }, + .access = PL0_R, .type = ARM_CP_CONST | ARM_CP_64BIT | ARM_CP_NO_GDB, + .resetvalue = 0 }, }; static void dbgwvr_write(CPUARMState *env, const ARMCPRegInfo *ri, diff --git a/target/arm/helper.c b/target/arm/helper.c index 5dc0d20a84..ff1970981e 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -3722,20 +3722,6 @@ static void ats_write64(CPUARMState *env, const ARMCPRegInfo *ri, } #endif -static const ARMCPRegInfo vapa_cp_reginfo[] = { - { .name = "PAR", .cp = 15, .crn = 7, .crm = 4, .opc1 = 0, .opc2 = 0, - .access = PL1_RW, .resetvalue = 0, - .bank_fieldoffsets = { offsetoflow32(CPUARMState, cp15.par_s), - offsetoflow32(CPUARMState, cp15.par_ns) }, - .writefn = par_write }, -#ifndef CONFIG_USER_ONLY - /* This underdecoding is safe because the reginfo is NO_RAW. */ - { .name = "ATS", .cp = 15, .crn = 7, .crm = 8, .opc1 = 0, .opc2 = CP_ANY, - .access = PL1_W, .accessfn = ats_access, - .writefn = ats_write, .type = ARM_CP_NO_RAW | ARM_CP_RAISES_EXC }, -#endif -}; - /* Return basic MPU access permission bits. */ static uint32_t simple_mpu_ap_bits(uint32_t val) { @@ -8904,6 +8890,27 @@ void register_cp_regs_for_features(ARMCPU *cpu) define_arm_cp_regs(cpu, generic_timer_cp_reginfo); } if (arm_feature(env, ARM_FEATURE_VAPA)) { + ARMCPRegInfo vapa_cp_reginfo[] = { + { .name = "PAR", .cp = 15, .crn = 7, .crm = 4, .opc1 = 0, .opc2 = 0, + .access = PL1_RW, .resetvalue = 0, + .bank_fieldoffsets = { offsetoflow32(CPUARMState, cp15.par_s), + offsetoflow32(CPUARMState, cp15.par_ns) }, + .writefn = par_write}, +#ifndef CONFIG_USER_ONLY + /* This underdecoding is safe because the reginfo is NO_RAW. */ + { .name = "ATS", .cp = 15, .crn = 7, .crm = 8, .opc1 = 0, .opc2 = CP_ANY, + .access = PL1_W, .accessfn = ats_access, + .writefn = ats_write, .type = ARM_CP_NO_RAW | ARM_CP_RAISES_EXC }, +#endif + }; + + /* + * When LPAE exists this 32-bit PAR register is an alias of the + * 64-bit AArch32 PAR register defined in lpae_cp_reginfo[] + */ + if (arm_feature(env, ARM_FEATURE_LPAE)) { + vapa_cp_reginfo[0].type = ARM_CP_ALIAS | ARM_CP_NO_GDB; + } define_arm_cp_regs(cpu, vapa_cp_reginfo); } if (arm_feature(env, ARM_FEATURE_CACHE_TEST_CLEAN)) { @@ -8993,7 +9000,7 @@ void register_cp_regs_for_features(ARMCPU *cpu) .type = ARM_CP_CONST, .resetvalue = cpu->revidr }, }; ARMCPRegInfo id_v8_midr_alias_cp_reginfo = { - .name = "MIDR", .type = ARM_CP_ALIAS | ARM_CP_CONST, + .name = "MIDR", .type = ARM_CP_ALIAS | ARM_CP_CONST | ARM_CP_NO_GDB, .cp = 15, .crn = 0, .crm = 0, .opc1 = 0, .opc2 = 4, .access = PL1_R, .resetvalue = cpu->midr }; diff --git a/tests/avocado/tcg_plugins.py b/tests/avocado/tcg_plugins.py index 642d2e49e3..15fd87b2c1 100644 --- a/tests/avocado/tcg_plugins.py +++ b/tests/avocado/tcg_plugins.py @@ -54,13 +54,11 @@ class PluginKernelBase(LinuxKernelTest): class PluginKernelNormal(PluginKernelBase): def _grab_aarch64_kernel(self): - kernel_url = ('http://security.debian.org/' - 'debian-security/pool/updates/main/l/linux-signed-arm64/' - 'linux-image-4.19.0-12-arm64_4.19.152-1_arm64.deb') - kernel_sha1 = '2036c2792f80ac9c4ccaae742b2e0a28385b6010' - kernel_deb = self.fetch_asset(kernel_url, asset_hash=kernel_sha1) - kernel_path = self.extract_from_deb(kernel_deb, - "/boot/vmlinuz-4.19.0-12-arm64") + kernel_url = ('https://storage.tuxboot.com/20230331/arm64/Image') + kernel_sha256 = 'ce95a7101a5fecebe0fe630deee6bd97b32ba41bc8754090e9ad8961ea8674c7' + kernel_path = self.fetch_asset(kernel_url, + asset_hash=kernel_sha256, + algorithm = "sha256") return kernel_path def test_aarch64_virt_insn(self): @@ -88,6 +86,10 @@ class PluginKernelNormal(PluginKernelBase): m = re.search(br"insns: (?P<count>\d+)", s) if "count" not in m.groupdict(): self.fail("Failed to find instruction count") + else: + count = int(m.group("count")) + self.log.info(f"Counted: {count} instructions") + def test_aarch64_virt_insn_icount(self): """ @@ -111,9 +113,13 @@ class PluginKernelNormal(PluginKernelBase): with plugin_log as lf, \ mmap.mmap(lf.fileno(), 0, access=mmap.ACCESS_READ) as s: - m = re.search(br"detected repeat execution @ (?P<addr>0x[0-9A-Fa-f]+)", s) - if m is not None and "addr" in m.groupdict(): - self.fail("detected repeated instructions") + + m = re.search(br"insns: (?P<count>\d+)", s) + if "count" not in m.groupdict(): + self.fail("Failed to find instruction count") + else: + count = int(m.group("count")) + self.log.info(f"Counted: {count} instructions") def test_aarch64_virt_mem_icount(self): """ @@ -145,3 +151,5 @@ class PluginKernelNormal(PluginKernelBase): callback = int(m[1]) if inline != callback: self.fail("mismatched access counts") + else: + self.log.info(f"Counted {inline} memory accesses") diff --git a/tests/plugin/meson.build b/tests/plugin/meson.build index 322cafcdf6..528bb9d86c 100644 --- a/tests/plugin/meson.build +++ b/tests/plugin/meson.build @@ -1,9 +1,17 @@ t = [] if get_option('plugins') foreach i : ['bb', 'empty', 'insn', 'mem', 'syscall'] - t += shared_module(i, files(i + '.c'), - include_directories: '../../include/qemu', - dependencies: glib) + if targetos == 'windows' + t += shared_module(i, files(i + '.c') + '../../contrib/plugins/win32_linker.c', + include_directories: '../../include/qemu', + objects: [win32_qemu_plugin_api_lib], + dependencies: glib) + + else + t += shared_module(i, files(i + '.c'), + include_directories: '../../include/qemu', + dependencies: glib) + endif endforeach endif if t.length() > 0 diff --git a/tests/tcg/multiarch/Makefile.target b/tests/tcg/multiarch/Makefile.target index f3bfaf1a22..d31ba8d6ae 100644 --- a/tests/tcg/multiarch/Makefile.target +++ b/tests/tcg/multiarch/Makefile.target @@ -93,12 +93,21 @@ run-gdbstub-thread-breakpoint: testthread --qemu $(QEMU) --qargs "$(QEMU_OPTS)" \ --bin $< --test $(MULTIARCH_SRC)/gdbstub/test-thread-breakpoint.py, \ hitting a breakpoint on non-main thread) + +run-gdbstub-registers: sha512 + $(call run-test, $@, $(GDB_SCRIPT) \ + --gdb $(GDB) \ + --qemu $(QEMU) --qargs "$(QEMU_OPTS)" \ + --bin $< --test $(MULTIARCH_SRC)/gdbstub/registers.py, \ + checking register enumeration) + 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-proc-mappings run-gdbstub-thread-breakpoint \ + run-gdbstub-registers # ARM Compatible Semi Hosting Tests # diff --git a/tests/tcg/multiarch/gdbstub/registers.py b/tests/tcg/multiarch/gdbstub/registers.py new file mode 100644 index 0000000000..ff6076b09e --- /dev/null +++ b/tests/tcg/multiarch/gdbstub/registers.py @@ -0,0 +1,197 @@ +# Exercise the register functionality by exhaustively iterating +# through all supported registers on the system. +# +# This is launched via tests/guest-debug/run-test.py but you can also +# call it directly if using it for debugging/introspection: +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import gdb +import sys +import xml.etree.ElementTree as ET + +initial_vlen = 0 +failcount = 0 + +def report(cond, msg): + "Report success/fail of test." + if cond: + print("PASS: %s" % (msg)) + else: + print("FAIL: %s" % (msg)) + global failcount + failcount += 1 + + +def fetch_xml_regmap(): + """ + Iterate through the XML descriptions and validate. + + We check for any duplicate registers and report them. Return a + reg_map hash containing the names, regnums and initial values of + all registers. + """ + + # First check the XML descriptions we have sent. Most arches + # support XML but a few of the ancient ones don't in which case we + # need to gracefully fail. + + try: + xml = gdb.execute("maint print xml-tdesc", False, True) + except (gdb.error): + print("SKIP: target does not support XML") + return None + + total_regs = 0 + reg_map = {} + frame = gdb.selected_frame() + + tree = ET.fromstring(xml) + for f in tree.findall("feature"): + name = f.attrib["name"] + regs = f.findall("reg") + + total = len(regs) + total_regs += total + base = int(regs[0].attrib["regnum"]) + top = int(regs[-1].attrib["regnum"]) + + print(f"feature: {name} has {total} registers from {base} to {top}") + + for r in regs: + name = r.attrib["name"] + regnum = int(r.attrib["regnum"]) + try: + value = frame.read_register(name) + except ValueError: + report(False, f"failed to read reg: {name}") + + entry = { "name": name, "initial": value, "regnum": regnum } + + if name in reg_map: + report(False, f"duplicate register {entry} vs {reg_map[name]}") + continue + + reg_map[name] = entry + + # Validate we match + report(total_regs == len(reg_map.keys()), + f"counted all {total_regs} registers in XML") + + return reg_map + +def crosscheck_remote_xml(reg_map): + """ + Cross-check the list of remote-registers with the XML info. + """ + + remote = gdb.execute("maint print remote-registers", False, True) + r_regs = remote.split("\n") + + total_regs = len(reg_map.keys()) + total_r_regs = 0 + + for r in r_regs: + fields = r.split() + # Some of the registers reported here are "pseudo" registers that + # gdb invents based on actual registers so we need to filter them + # out. + if len(fields) == 8: + r_name = fields[0] + r_regnum = int(fields[6]) + + # check in the XML + try: + x_reg = reg_map[r_name] + except KeyError: + report(False, f"{r_name} not in XML description") + continue + + x_reg["seen"] = True + x_regnum = x_reg["regnum"] + if r_regnum != x_regnum: + report(False, f"{r_name} {r_regnum} == {x_regnum} (xml)") + else: + total_r_regs += 1 + + # Just print a mismatch in totals as gdb will filter out 64 bit + # registers on a 32 bit machine. Also print what is missing to + # help with debug. + if total_regs != total_r_regs: + print(f"xml-tdesc has ({total_regs}) registers") + print(f"remote-registers has ({total_r_regs}) registers") + + for x_key in reg_map.keys(): + x_reg = reg_map[x_key] + if "seen" not in x_reg: + print(f"{x_reg} wasn't seen in remote-registers") + +def complete_and_diff(reg_map): + """ + Let the program run to (almost) completion and then iterate + through all the registers we know about and report which ones have + changed. + """ + # Let the program get to the end and we can check what changed + b = gdb.Breakpoint("_exit") + if b.pending: # workaround Microblaze weirdness + b.delete() + gdb.Breakpoint("_Exit") + + gdb.execute("continue") + + frame = gdb.selected_frame() + changed = 0 + + for e in reg_map.values(): + name = e["name"] + old_val = e["initial"] + + try: + new_val = frame.read_register(name) + except: + report(False, f"failed to read {name} at end of run") + continue + + if new_val != old_val: + print(f"{name} changes from {old_val} to {new_val}") + changed += 1 + + # as long as something changed we can be confident its working + report(changed > 0, f"{changed} registers were changed") + + +def run_test(): + "Run through the tests" + + reg_map = fetch_xml_regmap() + + if reg_map is not None: + crosscheck_remote_xml(reg_map) + complete_and_diff(reg_map) + + +# +# This runs as the script it sourced (via -x, via run-test.py) +# +try: + inferior = gdb.selected_inferior() + arch = inferior.architecture() + print("ATTACHED: %s" % arch.name()) +except (gdb.error, AttributeError): + print("SKIPPING (not connected)", file=sys.stderr) + exit(0) + +if gdb.parse_and_eval('$pc') == 0: + print("SKIP: PC not set") + exit(0) + +try: + run_test() +except (gdb.error): + print ("GDB Exception: %s" % (sys.exc_info()[0])) + failcount += 1 + pass + +print("All tests complete: %d failures" % failcount) +exit(failcount) diff --git a/tests/tcg/multiarch/system/Makefile.softmmu-target b/tests/tcg/multiarch/system/Makefile.softmmu-target index dee4f58dea..32dc0f9830 100644 --- a/tests/tcg/multiarch/system/Makefile.softmmu-target +++ b/tests/tcg/multiarch/system/Makefile.softmmu-target @@ -48,9 +48,20 @@ run-gdbstub-untimely-packet: hello $(call quiet-command, \ (! grep -Fq 'Packet instead of Ack, ignoring it' untimely-packet.gdb.err), \ "GREP", file untimely-packet.gdb.err) + +run-gdbstub-registers: memory + $(call run-test, $@, $(GDB_SCRIPT) \ + --gdb $(GDB) \ + --qemu $(QEMU) \ + --output $<.registers.gdb.out \ + --qargs \ + "-monitor none -display none -chardev file$(COMMA)path=$<.out$(COMMA)id=output $(QEMU_OPTS)" \ + --bin $< --test $(MULTIARCH_SRC)/gdbstub/registers.py, \ + softmmu gdbstub support) else run-gdbstub-%: $(call skip-test, "gdbstub test $*", "need working gdb with $(patsubst -%,,$(TARGET_NAME)) support") endif -MULTIARCH_RUNS += run-gdbstub-memory run-gdbstub-interrupt run-gdbstub-untimely-packet +MULTIARCH_RUNS += run-gdbstub-memory run-gdbstub-interrupt \ + run-gdbstub-untimely-packet run-gdbstub-registers diff --git a/tests/tcg/nios2/Makefile.target b/tests/tcg/nios2/Makefile.target new file mode 100644 index 0000000000..b38e2352b7 --- /dev/null +++ b/tests/tcg/nios2/Makefile.target @@ -0,0 +1,11 @@ +# nios2 specific test tweaks + +# Currently nios2 signal handling is broken +run-signals: signals + $(call skip-test, $<, "BROKEN") +run-plugin-signals-with-%: + $(call skip-test, $<, "BROKEN") +run-linux-test: linux-test + $(call skip-test, $<, "BROKEN") +run-plugin-linux-test-with-%: + $(call skip-test, $<, "BROKEN") diff --git a/tests/tcg/ppc64/Makefile.target b/tests/tcg/ppc64/Makefile.target index 5721c159f2..1d08076756 100644 --- a/tests/tcg/ppc64/Makefile.target +++ b/tests/tcg/ppc64/Makefile.target @@ -38,4 +38,11 @@ PPC64_TESTS += signal_save_restore_xer PPC64_TESTS += xxspltw PPC64_TESTS += test-aes +ifneq ($(GDB),) +# Skip for now until vsx registers sorted out +run-gdbstub-registers: + $(call skip-test, $<, "BROKEN reading VSX registers") +endif + + TESTS += $(PPC64_TESTS) diff --git a/tests/tcg/s390x/Makefile.target b/tests/tcg/s390x/Makefile.target index 0e670f3f8b..46544fecd4 100644 --- a/tests/tcg/s390x/Makefile.target +++ b/tests/tcg/s390x/Makefile.target @@ -103,6 +103,10 @@ run-gdbstub-svc: hello-s390x-asm --bin $< --test $(S390X_SRC)/gdbstub/test-svc.py, \ single-stepping svc) +# Skip for now until vx registers sorted out +run-gdbstub-registers: + $(call skip-test, $<, "BROKEN reading VX registers") + EXTRA_RUNS += run-gdbstub-signals-s390x run-gdbstub-svc endif |