From 4f64f80e123f04664af5337db001ad5c74fa6f57 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Thu, 28 Apr 2022 01:29:54 -0700 Subject: [PATCH 01/69] include/exec: Move gdb open flags to gdbstub.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There were 3 copies of these flags. Place them in the file with gdb_do_syscall, with which they belong. Reviewed-by: Alex Bennée Reviewed-by: Peter Maydell Signed-off-by: Richard Henderson (cherry picked from commit 94b14fe08f9f4f1f0e7aba639fc98e65a13e5235) --- include/exec/gdbstub.h | 9 +++++++++ semihosting/arm-compat-semi.c | 8 -------- target/m68k/m68k-semi.c | 8 -------- target/nios2/nios2-semi.c | 8 -------- 4 files changed, 9 insertions(+), 24 deletions(-) diff --git a/include/exec/gdbstub.h b/include/exec/gdbstub.h index 89edf94d28608..452beb21f65ab 100644 --- a/include/exec/gdbstub.h +++ b/include/exec/gdbstub.h @@ -10,6 +10,15 @@ #define GDB_WATCHPOINT_READ 3 #define GDB_WATCHPOINT_ACCESS 4 +/* For gdb file i/o remote protocol open flags. */ +#define GDB_O_RDONLY 0 +#define GDB_O_WRONLY 1 +#define GDB_O_RDWR 2 +#define GDB_O_APPEND 8 +#define GDB_O_CREAT 0x200 +#define GDB_O_TRUNC 0x400 +#define GDB_O_EXCL 0x800 + #ifdef NEED_CPU_H #include "cpu.h" diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index 7a51fd0737d15..ba2ce8931487e 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -85,14 +85,6 @@ #define O_BINARY 0 #endif -#define GDB_O_RDONLY 0x000 -#define GDB_O_WRONLY 0x001 -#define GDB_O_RDWR 0x002 -#define GDB_O_APPEND 0x008 -#define GDB_O_CREAT 0x200 -#define GDB_O_TRUNC 0x400 -#define GDB_O_BINARY 0 - static int gdb_open_modeflags[12] = { GDB_O_RDONLY, GDB_O_RDONLY | GDB_O_BINARY, diff --git a/target/m68k/m68k-semi.c b/target/m68k/m68k-semi.c index c5c164e096c8f..5396fbdeef24b 100644 --- a/target/m68k/m68k-semi.c +++ b/target/m68k/m68k-semi.c @@ -69,14 +69,6 @@ struct gdb_timeval { uint64_t tv_usec; /* microsecond */ } QEMU_PACKED; -#define GDB_O_RDONLY 0x0 -#define GDB_O_WRONLY 0x1 -#define GDB_O_RDWR 0x2 -#define GDB_O_APPEND 0x8 -#define GDB_O_CREAT 0x200 -#define GDB_O_TRUNC 0x400 -#define GDB_O_EXCL 0x800 - static int translate_openflags(int flags) { int hf; diff --git a/target/nios2/nios2-semi.c b/target/nios2/nios2-semi.c index 5a7ad0c7108d4..7020cd6dd3690 100644 --- a/target/nios2/nios2-semi.c +++ b/target/nios2/nios2-semi.c @@ -72,14 +72,6 @@ struct gdb_timeval { uint64_t tv_usec; /* microsecond */ } QEMU_PACKED; -#define GDB_O_RDONLY 0x0 -#define GDB_O_WRONLY 0x1 -#define GDB_O_RDWR 0x2 -#define GDB_O_APPEND 0x8 -#define GDB_O_CREAT 0x200 -#define GDB_O_TRUNC 0x400 -#define GDB_O_EXCL 0x800 - static int translate_openflags(int flags) { int hf; From d7079b914e152a5b181435105c487b3ea17dab95 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Fri, 29 Apr 2022 14:41:37 -0700 Subject: [PATCH 02/69] include/exec: Move gdb_stat and gdb_timeval to gdbstub.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have two copies of these structures, and require them in semihosting/ going forward. Reviewed-by: Alex Bennée Reviewed-by: Peter Maydell Signed-off-by: Richard Henderson (cherry picked from commit 7c56c2d3da2d25cb202e6b47ad7348a42b950b76) --- include/exec/gdbstub.h | 25 +++++++++++++++++++++++++ target/m68k/m68k-semi.c | 32 +++++--------------------------- target/nios2/nios2-semi.c | 30 +++--------------------------- 3 files changed, 33 insertions(+), 54 deletions(-) diff --git a/include/exec/gdbstub.h b/include/exec/gdbstub.h index 452beb21f65ab..d6773a466f5a1 100644 --- a/include/exec/gdbstub.h +++ b/include/exec/gdbstub.h @@ -19,6 +19,31 @@ #define GDB_O_TRUNC 0x400 #define GDB_O_EXCL 0x800 +/* For gdb file i/o stat/fstat. */ +typedef uint32_t gdb_mode_t; +typedef uint32_t gdb_time_t; + +struct gdb_stat { + uint32_t gdb_st_dev; /* device */ + uint32_t gdb_st_ino; /* inode */ + gdb_mode_t gdb_st_mode; /* protection */ + uint32_t gdb_st_nlink; /* number of hard links */ + uint32_t gdb_st_uid; /* user ID of owner */ + uint32_t gdb_st_gid; /* group ID of owner */ + uint32_t gdb_st_rdev; /* device type (if inode device) */ + uint64_t gdb_st_size; /* total size, in bytes */ + uint64_t gdb_st_blksize; /* blocksize for filesystem I/O */ + uint64_t gdb_st_blocks; /* number of blocks allocated */ + gdb_time_t gdb_st_atime; /* time of last access */ + gdb_time_t gdb_st_mtime; /* time of last modification */ + gdb_time_t gdb_st_ctime; /* time of last change */ +} QEMU_PACKED; + +struct gdb_timeval { + gdb_time_t tv_sec; /* second */ + uint64_t tv_usec; /* microsecond */ +} QEMU_PACKED; + #ifdef NEED_CPU_H #include "cpu.h" diff --git a/target/m68k/m68k-semi.c b/target/m68k/m68k-semi.c index 5396fbdeef24b..8da288bf9fd6a 100644 --- a/target/m68k/m68k-semi.c +++ b/target/m68k/m68k-semi.c @@ -45,30 +45,6 @@ #define HOSTED_ISATTY 12 #define HOSTED_SYSTEM 13 -typedef uint32_t gdb_mode_t; -typedef uint32_t gdb_time_t; - -struct m68k_gdb_stat { - uint32_t gdb_st_dev; /* device */ - uint32_t gdb_st_ino; /* inode */ - gdb_mode_t gdb_st_mode; /* protection */ - uint32_t gdb_st_nlink; /* number of hard links */ - uint32_t gdb_st_uid; /* user ID of owner */ - uint32_t gdb_st_gid; /* group ID of owner */ - uint32_t gdb_st_rdev; /* device type (if inode device) */ - uint64_t gdb_st_size; /* total size, in bytes */ - uint64_t gdb_st_blksize; /* blocksize for filesystem I/O */ - uint64_t gdb_st_blocks; /* number of blocks allocated */ - gdb_time_t gdb_st_atime; /* time of last access */ - gdb_time_t gdb_st_mtime; /* time of last modification */ - gdb_time_t gdb_st_ctime; /* time of last change */ -} QEMU_PACKED; - -struct gdb_timeval { - gdb_time_t tv_sec; /* second */ - uint64_t tv_usec; /* microsecond */ -} QEMU_PACKED; - static int translate_openflags(int flags) { int hf; @@ -90,11 +66,13 @@ static int translate_openflags(int flags) static void translate_stat(CPUM68KState *env, target_ulong addr, struct stat *s) { - struct m68k_gdb_stat *p; + struct gdb_stat *p; - if (!(p = lock_user(VERIFY_WRITE, addr, sizeof(struct m68k_gdb_stat), 0))) + p = lock_user(VERIFY_WRITE, addr, sizeof(struct gdb_stat), 0); + if (!p) { /* FIXME - should this return an error code? */ return; + } p->gdb_st_dev = cpu_to_be32(s->st_dev); p->gdb_st_ino = cpu_to_be32(s->st_ino); p->gdb_st_mode = cpu_to_be32(s->st_mode); @@ -114,7 +92,7 @@ static void translate_stat(CPUM68KState *env, target_ulong addr, struct stat *s) p->gdb_st_atime = cpu_to_be32(s->st_atime); p->gdb_st_mtime = cpu_to_be32(s->st_mtime); p->gdb_st_ctime = cpu_to_be32(s->st_ctime); - unlock_user(p, addr, sizeof(struct m68k_gdb_stat)); + unlock_user(p, addr, sizeof(struct gdb_stat)); } static void m68k_semi_return_u32(CPUM68KState *env, uint32_t ret, uint32_t err) diff --git a/target/nios2/nios2-semi.c b/target/nios2/nios2-semi.c index 7020cd6dd3690..6cdc440045b65 100644 --- a/target/nios2/nios2-semi.c +++ b/target/nios2/nios2-semi.c @@ -48,30 +48,6 @@ #define HOSTED_ISATTY 12 #define HOSTED_SYSTEM 13 -typedef uint32_t gdb_mode_t; -typedef uint32_t gdb_time_t; - -struct nios2_gdb_stat { - uint32_t gdb_st_dev; /* device */ - uint32_t gdb_st_ino; /* inode */ - gdb_mode_t gdb_st_mode; /* protection */ - uint32_t gdb_st_nlink; /* number of hard links */ - uint32_t gdb_st_uid; /* user ID of owner */ - uint32_t gdb_st_gid; /* group ID of owner */ - uint32_t gdb_st_rdev; /* device type (if inode device) */ - uint64_t gdb_st_size; /* total size, in bytes */ - uint64_t gdb_st_blksize; /* blocksize for filesystem I/O */ - uint64_t gdb_st_blocks; /* number of blocks allocated */ - gdb_time_t gdb_st_atime; /* time of last access */ - gdb_time_t gdb_st_mtime; /* time of last modification */ - gdb_time_t gdb_st_ctime; /* time of last change */ -} QEMU_PACKED; - -struct gdb_timeval { - gdb_time_t tv_sec; /* second */ - uint64_t tv_usec; /* microsecond */ -} QEMU_PACKED; - static int translate_openflags(int flags) { int hf; @@ -103,9 +79,9 @@ static int translate_openflags(int flags) static bool translate_stat(CPUNios2State *env, target_ulong addr, struct stat *s) { - struct nios2_gdb_stat *p; + struct gdb_stat *p; - p = lock_user(VERIFY_WRITE, addr, sizeof(struct nios2_gdb_stat), 0); + p = lock_user(VERIFY_WRITE, addr, sizeof(struct gdb_stat), 0); if (!p) { return false; @@ -129,7 +105,7 @@ static bool translate_stat(CPUNios2State *env, target_ulong addr, p->gdb_st_atime = cpu_to_be32(s->st_atime); p->gdb_st_mtime = cpu_to_be32(s->st_mtime); p->gdb_st_ctime = cpu_to_be32(s->st_ctime); - unlock_user(p, addr, sizeof(struct nios2_gdb_stat)); + unlock_user(p, addr, sizeof(struct gdb_stat)); return true; } From ed41ae29b86bbc548ba574dd43222b2c18abf69a Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Sun, 1 May 2022 18:35:18 -0700 Subject: [PATCH 03/69] include/exec: Define errno values in gdbstub.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Define constants for the errno values defined by the gdb remote fileio protocol. Reviewed-by: Alex Bennée Reviewed-by: Peter Maydell Signed-off-by: Richard Henderson (cherry picked from commit 9814483d6346fb697c9f852f3a1edab0b6910486) --- include/exec/gdbstub.h | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/include/exec/gdbstub.h b/include/exec/gdbstub.h index d6773a466f5a1..53644303bd1fe 100644 --- a/include/exec/gdbstub.h +++ b/include/exec/gdbstub.h @@ -19,6 +19,28 @@ #define GDB_O_TRUNC 0x400 #define GDB_O_EXCL 0x800 +/* For gdb file i/o remote protocol errno values */ +#define GDB_EPERM 1 +#define GDB_ENOENT 2 +#define GDB_EINTR 4 +#define GDB_EBADF 9 +#define GDB_EACCES 13 +#define GDB_EFAULT 14 +#define GDB_EBUSY 16 +#define GDB_EEXIST 17 +#define GDB_ENODEV 19 +#define GDB_ENOTDIR 20 +#define GDB_EISDIR 21 +#define GDB_EINVAL 22 +#define GDB_ENFILE 23 +#define GDB_EMFILE 24 +#define GDB_EFBIG 27 +#define GDB_ENOSPC 28 +#define GDB_ESPIPE 29 +#define GDB_EROFS 30 +#define GDB_ENAMETOOLONG 91 +#define GDB_EUNKNOWN 9999 + /* For gdb file i/o stat/fstat. */ typedef uint32_t gdb_mode_t; typedef uint32_t gdb_time_t; From 3dc7459541f60c616055c0560996700df5c34b97 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Tue, 26 Apr 2022 19:51:29 -0700 Subject: [PATCH 04/69] linux-user: Clean up arg_start/arg_end confusion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We had two sets of variables: arg_start/arg_end, and arg_strings/env_strings. In linuxload.c, we set the first pair to the bounds of the argv strings, but in elfload.c, we set the first pair to the bounds of the argv pointers and the second pair to the bounds of the argv strings. Remove arg_start/arg_end, replacing them with the standard argc/argv/envc/envp values. Retain arg_strings/env_strings with the meaning we were using in elfload.c. Resolves: https://gitlab.com/qemu-project/qemu/-/issues/714 Reviewed-by: Peter Maydell Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Richard Henderson Message-Id: <20220427025129.160184-1-richard.henderson@linaro.org> Signed-off-by: Laurent Vivier (cherry picked from commit 60f1c8017a2b137013a8ae83911d74700f692425) --- linux-user/elfload.c | 10 ++++++---- linux-user/linuxload.c | 12 ++++++++++-- linux-user/qemu.h | 12 ++++++++---- semihosting/arm-compat-semi.c | 4 ++-- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/linux-user/elfload.c b/linux-user/elfload.c index c45da4d633759..54b4bac2b03b9 100644 --- a/linux-user/elfload.c +++ b/linux-user/elfload.c @@ -1517,8 +1517,8 @@ static inline void init_thread(struct target_pt_regs *regs, regs->iaoq[0] = infop->entry; regs->iaoq[1] = infop->entry + 4; regs->gr[23] = 0; - regs->gr[24] = infop->arg_start; - regs->gr[25] = (infop->arg_end - infop->arg_start) / sizeof(abi_ulong); + regs->gr[24] = infop->argv; + regs->gr[25] = infop->argc; /* The top-of-stack contains a linkage buffer. */ regs->gr[30] = infop->start_stack + 64; regs->gr[31] = infop->entry; @@ -2121,8 +2121,10 @@ static abi_ulong create_elf_tables(abi_ulong p, int argc, int envc, u_envp = u_argv + (argc + 1) * n; u_auxv = u_envp + (envc + 1) * n; info->saved_auxv = u_auxv; - info->arg_start = u_argv; - info->arg_end = u_argv + argc * n; + info->argc = argc; + info->envc = envc; + info->argv = u_argv; + info->envp = u_envp; /* This is correct because Linux defines * elf_addr_t as Elf32_Off / Elf64_Off diff --git a/linux-user/linuxload.c b/linux-user/linuxload.c index 2ed5fc45ed8c1..745cce70abfab 100644 --- a/linux-user/linuxload.c +++ b/linux-user/linuxload.c @@ -92,6 +92,11 @@ abi_ulong loader_build_argptr(int envc, int argc, abi_ulong sp, envp = sp; sp -= (argc + 1) * n; argv = sp; + ts->info->envp = envp; + ts->info->envc = envc; + ts->info->argv = argv; + ts->info->argc = argc; + if (push_ptr) { /* FIXME - handle put_user() failures */ sp -= n; @@ -99,19 +104,22 @@ abi_ulong loader_build_argptr(int envc, int argc, abi_ulong sp, sp -= n; put_user_ual(argv, sp); } + sp -= n; /* FIXME - handle put_user() failures */ put_user_ual(argc, sp); - ts->info->arg_start = stringp; + + ts->info->arg_strings = stringp; while (argc-- > 0) { /* FIXME - handle put_user() failures */ put_user_ual(stringp, argv); argv += n; stringp += target_strlen(stringp) + 1; } - ts->info->arg_end = stringp; /* FIXME - handle put_user() failures */ put_user_ual(0, argv); + + ts->info->env_strings = stringp; while (envc-- > 0) { /* FIXME - handle put_user() failures */ put_user_ual(stringp, envp); diff --git a/linux-user/qemu.h b/linux-user/qemu.h index 98dfbf20962b6..3ac39793e17ec 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -40,15 +40,19 @@ struct image_info { abi_ulong data_offset; abi_ulong saved_auxv; abi_ulong auxv_len; - abi_ulong arg_start; - abi_ulong arg_end; - abi_ulong arg_strings; - abi_ulong env_strings; + abi_ulong argc; + abi_ulong argv; + abi_ulong envc; + abi_ulong envp; abi_ulong file_string; uint32_t elf_flags; int personality; abi_ulong alignment; + /* Generic semihosting knows about these pointers. */ + abi_ulong arg_strings; /* strings for argv */ + abi_ulong env_strings; /* strings for envp; ends arg_strings */ + /* The fields below are used in FDPIC mode. */ abi_ulong loadmap_addr; uint16_t nsegs; diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index ba2ce8931487e..1962e7a885294 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -1098,7 +1098,7 @@ target_ulong do_common_semihosting(CPUState *cs) #else unsigned int i; - output_size = ts->info->arg_end - ts->info->arg_start; + output_size = ts->info->env_strings - ts->info->arg_strings; if (!output_size) { /* * We special-case the "empty command line" case (argc==0). @@ -1138,7 +1138,7 @@ target_ulong do_common_semihosting(CPUState *cs) goto out; } - if (copy_from_user(output_buffer, ts->info->arg_start, + if (copy_from_user(output_buffer, ts->info->arg_strings, output_size)) { errno = EFAULT; status = set_swi_errno(cs, -1); From 3ea3a0b0d7724b8243afe5c0ceaa00287852c9de Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Fri, 10 Jun 2022 14:32:36 +0100 Subject: [PATCH 05/69] semihosting/config: Merge --semihosting-config option groups Currently we mishandle the --semihosting-config option if the user specifies it on the command line more than once. For example with: --semihosting-config target=gdb --semihosting-config arg=foo,arg=bar the function qemu_semihosting_config_options() is called twice, once for each argument. But that function expects to be called only once, and it always unconditionally sets the semihosting.enabled, semihost_chardev and semihosting.target variables. This means that if any of those options were set anywhere except the last --semihosting-config option on the command line, those settings are ignored. In the example above, 'target=gdb' in the first option is overridden by an implied default 'target=auto' in the second. The QemuOptsList machinery has a flag for handling this kind of "option group is setting global state": by setting .merge_lists = true; we make the machinery merge all the --semihosting-config arguments the user passes into a single set of options and call our qemu_semihosting_config_options() just once. Signed-off-by: Peter Maydell Reviewed-by: Luc Michel Message-id: 20220526190053.521505-3-peter.maydell@linaro.org (cherry picked from commit 90c072e063737e9e8f431489bbd334452f89056e) --- semihosting/config.c | 1 + 1 file changed, 1 insertion(+) diff --git a/semihosting/config.c b/semihosting/config.c index 50d82108e6ef7..3afacf54ab25e 100644 --- a/semihosting/config.c +++ b/semihosting/config.c @@ -27,6 +27,7 @@ QemuOptsList qemu_semihosting_config_opts = { .name = "semihosting-config", + .merge_lists = true, .implied_opt_name = "enable", .head = QTAILQ_HEAD_INITIALIZER(qemu_semihosting_config_opts.head), .desc = { From f4c2410715e66613422174da0bbbf91d08f30c8c Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Wed, 27 Apr 2022 20:48:41 -0700 Subject: [PATCH 06/69] semihosting: Move exec/softmmu-semi.h to semihosting/softmmu-uaccess.h We have a subdirectory for semihosting; move this file out of exec. Rename to emphasize the contents are a replacement for the functions in linux-user/bsd-user uaccess.c. Reviewed-by: Peter Maydell Signed-off-by: Richard Henderson (cherry picked from commit c89a14ad2c2be24f786d80d33362279f5de61c74) --- .../{exec/softmmu-semi.h => semihosting/softmmu-uaccess.h} | 6 +++--- semihosting/arm-compat-semi.c | 2 +- target/m68k/m68k-semi.c | 2 +- target/mips/tcg/sysemu/mips-semi.c | 2 +- target/nios2/nios2-semi.c | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) rename include/{exec/softmmu-semi.h => semihosting/softmmu-uaccess.h} (95%) diff --git a/include/exec/softmmu-semi.h b/include/semihosting/softmmu-uaccess.h similarity index 95% rename from include/exec/softmmu-semi.h rename to include/semihosting/softmmu-uaccess.h index fbcae88f4bab7..e69e3c85488d8 100644 --- a/include/exec/softmmu-semi.h +++ b/include/semihosting/softmmu-uaccess.h @@ -7,8 +7,8 @@ * This code is licensed under the GPL */ -#ifndef SOFTMMU_SEMI_H -#define SOFTMMU_SEMI_H +#ifndef SEMIHOSTING_SOFTMMU_UACCESS_H +#define SEMIHOSTING_SOFTMMU_UACCESS_H #include "cpu.h" @@ -98,4 +98,4 @@ static void softmmu_unlock_user(CPUArchState *env, void *p, target_ulong addr, } #define unlock_user(s, args, len) softmmu_unlock_user(env, s, args, len) -#endif +#endif /* SEMIHOSTING_SOFTMMU_UACCESS_H */ diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index 1962e7a885294..22a8d2f7571c9 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -362,7 +362,7 @@ static GuestFD *get_guestfd(int guestfd) #ifndef CONFIG_USER_ONLY static target_ulong syscall_err; -#include "exec/softmmu-semi.h" +#include "semihosting/softmmu-uaccess.h" #endif static inline uint32_t set_swi_errno(CPUState *cs, uint32_t code) diff --git a/target/m68k/m68k-semi.c b/target/m68k/m68k-semi.c index 8da288bf9fd6a..1d72ab270bed6 100644 --- a/target/m68k/m68k-semi.c +++ b/target/m68k/m68k-semi.c @@ -25,7 +25,7 @@ #include "qemu.h" #define SEMIHOSTING_HEAP_SIZE (128 * 1024 * 1024) #else -#include "exec/softmmu-semi.h" +#include "semihosting/softmmu-uaccess.h" #include "hw/boards.h" #endif #include "qemu/log.h" diff --git a/target/mips/tcg/sysemu/mips-semi.c b/target/mips/tcg/sysemu/mips-semi.c index b4a383ae90c32..6d6296e7095fa 100644 --- a/target/mips/tcg/sysemu/mips-semi.c +++ b/target/mips/tcg/sysemu/mips-semi.c @@ -21,7 +21,7 @@ #include "cpu.h" #include "qemu/log.h" #include "exec/helper-proto.h" -#include "exec/softmmu-semi.h" +#include "semihosting/softmmu-uaccess.h" #include "semihosting/semihost.h" #include "semihosting/console.h" diff --git a/target/nios2/nios2-semi.c b/target/nios2/nios2-semi.c index 6cdc440045b65..0f8ef785c1ff8 100644 --- a/target/nios2/nios2-semi.c +++ b/target/nios2/nios2-semi.c @@ -29,7 +29,7 @@ #include "qemu.h" #else #include "qemu-common.h" -#include "exec/softmmu-semi.h" +#include "semihosting/softmmu-uaccess.h" #endif #include "qemu/log.h" From 0bce20840aa72cfc0f3532440eea5dd0e7d375d4 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Mon, 2 May 2022 00:11:25 -0700 Subject: [PATCH 07/69] target/mips: Use an exception for semihosting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Within do_interrupt, we hold the iothread lock, which is required for Chardev access for the console, and for the round trip for use_gdb_syscalls(). Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Richard Henderson (cherry picked from commit 8ec7e3c53d48c61e31dd251b84a2b8190a14542d) --- target/mips/cpu.h | 3 ++- target/mips/tcg/exception.c | 1 + target/mips/tcg/micromips_translate.c.inc | 6 +++--- target/mips/tcg/mips16e_translate.c.inc | 2 +- target/mips/tcg/nanomips_translate.c.inc | 4 ++-- target/mips/tcg/sysemu/mips-semi.c | 4 ++-- target/mips/tcg/sysemu/tlb_helper.c | 4 ++++ target/mips/tcg/sysemu_helper.h.inc | 2 -- target/mips/tcg/tcg-internal.h | 2 ++ target/mips/tcg/translate.c | 12 ++---------- 10 files changed, 19 insertions(+), 21 deletions(-) diff --git a/target/mips/cpu.h b/target/mips/cpu.h index 52ce08a94d368..6978898377846 100644 --- a/target/mips/cpu.h +++ b/target/mips/cpu.h @@ -1251,8 +1251,9 @@ enum { EXCP_MSAFPE, EXCP_TLBXI, EXCP_TLBRI, + EXCP_SEMIHOST, - EXCP_LAST = EXCP_TLBRI, + EXCP_LAST = EXCP_SEMIHOST, }; /* diff --git a/target/mips/tcg/exception.c b/target/mips/tcg/exception.c index 0b21e0872bd14..2bd77a61de670 100644 --- a/target/mips/tcg/exception.c +++ b/target/mips/tcg/exception.c @@ -125,6 +125,7 @@ static const char * const excp_names[EXCP_LAST + 1] = { [EXCP_TLBRI] = "TLB read-inhibit", [EXCP_MSADIS] = "MSA disabled", [EXCP_MSAFPE] = "MSA floating point", + [EXCP_SEMIHOST] = "Semihosting", }; const char *mips_exception_name(int32_t exception) diff --git a/target/mips/tcg/micromips_translate.c.inc b/target/mips/tcg/micromips_translate.c.inc index fc6ede75b804f..274caf2c3c478 100644 --- a/target/mips/tcg/micromips_translate.c.inc +++ b/target/mips/tcg/micromips_translate.c.inc @@ -826,7 +826,7 @@ static void gen_pool16c_insn(DisasContext *ctx) break; case SDBBP16: if (is_uhi(extract32(ctx->opcode, 0, 4))) { - gen_helper_do_semihosting(cpu_env); + generate_exception_end(ctx, EXCP_SEMIHOST); } else { /* * XXX: not clear which exception should be raised @@ -942,7 +942,7 @@ static void gen_pool16c_r6_insn(DisasContext *ctx) case R6_SDBBP16: /* SDBBP16 */ if (is_uhi(extract32(ctx->opcode, 6, 4))) { - gen_helper_do_semihosting(cpu_env); + generate_exception_end(ctx, EXCP_SEMIHOST); } else { if (ctx->hflags & MIPS_HFLAG_SBRI) { generate_exception(ctx, EXCP_RI); @@ -1311,7 +1311,7 @@ static void gen_pool32axf(CPUMIPSState *env, DisasContext *ctx, int rt, int rs) break; case SDBBP: if (is_uhi(extract32(ctx->opcode, 16, 10))) { - gen_helper_do_semihosting(cpu_env); + generate_exception_end(ctx, EXCP_SEMIHOST); } else { check_insn(ctx, ISA_MIPS_R1); if (ctx->hflags & MIPS_HFLAG_SBRI) { diff --git a/target/mips/tcg/mips16e_translate.c.inc b/target/mips/tcg/mips16e_translate.c.inc index f57e0a5f2a955..0a3ba252e4daf 100644 --- a/target/mips/tcg/mips16e_translate.c.inc +++ b/target/mips/tcg/mips16e_translate.c.inc @@ -952,7 +952,7 @@ static int decode_ase_mips16e(CPUMIPSState *env, DisasContext *ctx) break; case RR_SDBBP: if (is_uhi(extract32(ctx->opcode, 5, 6))) { - gen_helper_do_semihosting(cpu_env); + generate_exception_end(ctx, EXCP_SEMIHOST); } else { /* * XXX: not clear which exception should be raised diff --git a/target/mips/tcg/nanomips_translate.c.inc b/target/mips/tcg/nanomips_translate.c.inc index 916cece4d27e2..5b0e4683a7692 100644 --- a/target/mips/tcg/nanomips_translate.c.inc +++ b/target/mips/tcg/nanomips_translate.c.inc @@ -3691,7 +3691,7 @@ static int decode_nanomips_32_48_opc(CPUMIPSState *env, DisasContext *ctx) break; case NM_SDBBP: if (is_uhi(extract32(ctx->opcode, 0, 19))) { - gen_helper_do_semihosting(cpu_env); + generate_exception_end(ctx, EXCP_SEMIHOST); } else { if (ctx->hflags & MIPS_HFLAG_SBRI) { gen_reserved_instruction(ctx); @@ -4609,7 +4609,7 @@ static int decode_isa_nanomips(CPUMIPSState *env, DisasContext *ctx) break; case NM_SDBBP16: if (is_uhi(extract32(ctx->opcode, 0, 3))) { - gen_helper_do_semihosting(cpu_env); + generate_exception_end(ctx, EXCP_SEMIHOST); } else { if (ctx->hflags & MIPS_HFLAG_SBRI) { gen_reserved_instruction(ctx); diff --git a/target/mips/tcg/sysemu/mips-semi.c b/target/mips/tcg/sysemu/mips-semi.c index 6d6296e7095fa..ac12c802a3dee 100644 --- a/target/mips/tcg/sysemu/mips-semi.c +++ b/target/mips/tcg/sysemu/mips-semi.c @@ -20,10 +20,10 @@ #include "qemu/osdep.h" #include "cpu.h" #include "qemu/log.h" -#include "exec/helper-proto.h" #include "semihosting/softmmu-uaccess.h" #include "semihosting/semihost.h" #include "semihosting/console.h" +#include "internal.h" typedef enum UHIOp { UHI_exit = 1, @@ -238,7 +238,7 @@ static int copy_argn_to_target(CPUMIPSState *env, int arg_num, unlock_user(p, gpr, 0); \ } while (0) -void helper_do_semihosting(CPUMIPSState *env) +void mips_semihosting(CPUMIPSState *env) { target_ulong *gpr = env->active_tc.gpr; const UHIOp op = gpr[25]; diff --git a/target/mips/tcg/sysemu/tlb_helper.c b/target/mips/tcg/sysemu/tlb_helper.c index 73254d19298d6..57ffad2902e15 100644 --- a/target/mips/tcg/sysemu/tlb_helper.c +++ b/target/mips/tcg/sysemu/tlb_helper.c @@ -1053,6 +1053,10 @@ void mips_cpu_do_interrupt(CPUState *cs) } offset = 0x180; switch (cs->exception_index) { + case EXCP_SEMIHOST: + cs->exception_index = EXCP_NONE; + mips_semihosting(env); + return; case EXCP_DSS: env->CP0_Debug |= 1 << CP0DB_DSS; /* diff --git a/target/mips/tcg/sysemu_helper.h.inc b/target/mips/tcg/sysemu_helper.h.inc index 4353a966f976c..af585b5d9c13e 100644 --- a/target/mips/tcg/sysemu_helper.h.inc +++ b/target/mips/tcg/sysemu_helper.h.inc @@ -9,8 +9,6 @@ * SPDX-License-Identifier: LGPL-2.1-or-later */ -DEF_HELPER_1(do_semihosting, void, env) - /* CP0 helpers */ DEF_HELPER_1(mfc0_mvpcontrol, tl, env) DEF_HELPER_1(mfc0_mvpconf0, tl, env) diff --git a/target/mips/tcg/tcg-internal.h b/target/mips/tcg/tcg-internal.h index 466768aec487c..5da4ae6a9313a 100644 --- a/target/mips/tcg/tcg-internal.h +++ b/target/mips/tcg/tcg-internal.h @@ -61,6 +61,8 @@ bool mips_cpu_tlb_fill(CPUState *cs, vaddr address, int size, MMUAccessType access_type, int mmu_idx, bool probe, uintptr_t retaddr); +void mips_semihosting(CPUMIPSState *env); + #endif /* !CONFIG_USER_ONLY */ #endif diff --git a/target/mips/tcg/translate.c b/target/mips/tcg/translate.c index b82a7ec6ad500..8cff454c68d4e 100644 --- a/target/mips/tcg/translate.c +++ b/target/mips/tcg/translate.c @@ -12094,14 +12094,6 @@ static inline bool is_uhi(int sdbbp_code) #endif } -#ifdef CONFIG_USER_ONLY -/* The above should dead-code away any calls to this..*/ -static inline void gen_helper_do_semihosting(void *env) -{ - g_assert_not_reached(); -} -#endif - void gen_ldxs(DisasContext *ctx, int base, int index, int rd) { TCGv t0 = tcg_temp_new(); @@ -13910,7 +13902,7 @@ static void decode_opc_special_r6(CPUMIPSState *env, DisasContext *ctx) break; case R6_OPC_SDBBP: if (is_uhi(extract32(ctx->opcode, 6, 20))) { - gen_helper_do_semihosting(cpu_env); + generate_exception_end(ctx, EXCP_SEMIHOST); } else { if (ctx->hflags & MIPS_HFLAG_SBRI) { gen_reserved_instruction(ctx); @@ -14322,7 +14314,7 @@ static void decode_opc_special2_legacy(CPUMIPSState *env, DisasContext *ctx) break; case OPC_SDBBP: if (is_uhi(extract32(ctx->opcode, 6, 20))) { - gen_helper_do_semihosting(cpu_env); + generate_exception_end(ctx, EXCP_SEMIHOST); } else { /* * XXX: not clear which exception should be raised From 71768acb7ce140a2072f4cbd48fe2e305b4c8dfc Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Sun, 1 May 2022 20:03:01 -0700 Subject: [PATCH 08/69] target/mips: Add UHI errno values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From the Unified Hosting Interface, MD01069 Reference Manual, version 1.1.6, 06 July 2015. Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Richard Henderson (cherry picked from commit 7ba6e53a9ddf9b4ae04ad5fc658105440889cf17) --- target/mips/tcg/sysemu/mips-semi.c | 40 ++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/target/mips/tcg/sysemu/mips-semi.c b/target/mips/tcg/sysemu/mips-semi.c index ac12c802a3dee..2a039baf4ce08 100644 --- a/target/mips/tcg/sysemu/mips-semi.c +++ b/target/mips/tcg/sysemu/mips-semi.c @@ -74,6 +74,46 @@ enum UHIOpenFlags { UHIOpen_EXCL = 0x800 }; +enum UHIErrno { + UHI_EACCESS = 13, + UHI_EAGAIN = 11, + UHI_EBADF = 9, + UHI_EBADMSG = 77, + UHI_EBUSY = 16, + UHI_ECONNRESET = 104, + UHI_EEXIST = 17, + UHI_EFBIG = 27, + UHI_EINTR = 4, + UHI_EINVAL = 22, + UHI_EIO = 5, + UHI_EISDIR = 21, + UHI_ELOOP = 92, + UHI_EMFILE = 24, + UHI_EMLINK = 31, + UHI_ENAMETOOLONG = 91, + UHI_ENETDOWN = 115, + UHI_ENETUNREACH = 114, + UHI_ENFILE = 23, + UHI_ENOBUFS = 105, + UHI_ENOENT = 2, + UHI_ENOMEM = 12, + UHI_ENOSPC = 28, + UHI_ENOSR = 63, + UHI_ENOTCONN = 128, + UHI_ENOTDIR = 20, + UHI_ENXIO = 6, + UHI_EOVERFLOW = 139, + UHI_EPERM = 1, + UHI_EPIPE = 32, + UHI_ERANGE = 34, + UHI_EROFS = 30, + UHI_ESPIPE = 29, + UHI_ETIMEDOUT = 116, + UHI_ETXTBSY = 26, + UHI_EWOULDBLOCK = 11, + UHI_EXDEV = 18, +}; + static int errno_mips(int host_errno) { /* Errno values taken from asm-mips/errno.h */ From f358c759057baba31a0ffdc36cb6acd8db71bfa1 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Mon, 2 May 2022 22:52:19 -0700 Subject: [PATCH 09/69] target/mips: Drop pread and pwrite syscalls from semihosting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We don't implement it with _WIN32 hosts, and the syscalls are missing from the gdb remote file i/o interface. Since we can't implement them universally, drop them. Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Richard Henderson (cherry picked from commit 6863e92d04c9ba23cbf7c0c1498e977c646e1faf) --- target/mips/tcg/sysemu/mips-semi.c | 39 ++++++------------------------ 1 file changed, 7 insertions(+), 32 deletions(-) diff --git a/target/mips/tcg/sysemu/mips-semi.c b/target/mips/tcg/sysemu/mips-semi.c index 2a039baf4ce08..67c35fe7f9c79 100644 --- a/target/mips/tcg/sysemu/mips-semi.c +++ b/target/mips/tcg/sysemu/mips-semi.c @@ -182,8 +182,8 @@ static int get_open_flags(target_ulong target_flags) return open_flags; } -static int write_to_file(CPUMIPSState *env, target_ulong fd, target_ulong vaddr, - target_ulong len, target_ulong offset) +static int write_to_file(CPUMIPSState *env, target_ulong fd, + target_ulong vaddr, target_ulong len) { int num_of_bytes; void *dst = lock_user(VERIFY_READ, vaddr, len, 1); @@ -192,23 +192,14 @@ static int write_to_file(CPUMIPSState *env, target_ulong fd, target_ulong vaddr, return -1; } - if (offset) { -#ifdef _WIN32 - num_of_bytes = 0; -#else - num_of_bytes = pwrite(fd, dst, len, offset); -#endif - } else { - num_of_bytes = write(fd, dst, len); - } + num_of_bytes = write(fd, dst, len); unlock_user(dst, vaddr, 0); return num_of_bytes; } static int read_from_file(CPUMIPSState *env, target_ulong fd, - target_ulong vaddr, target_ulong len, - target_ulong offset) + target_ulong vaddr, target_ulong len) { int num_of_bytes; void *dst = lock_user(VERIFY_WRITE, vaddr, len, 0); @@ -217,15 +208,7 @@ static int read_from_file(CPUMIPSState *env, target_ulong fd, return -1; } - if (offset) { -#ifdef _WIN32 - num_of_bytes = 0; -#else - num_of_bytes = pread(fd, dst, len, offset); -#endif - } else { - num_of_bytes = read(fd, dst, len); - } + num_of_bytes = read(fd, dst, len); unlock_user(dst, vaddr, len); return num_of_bytes; @@ -312,11 +295,11 @@ void mips_semihosting(CPUMIPSState *env) gpr[3] = errno_mips(errno); break; case UHI_read: - gpr[2] = read_from_file(env, gpr[4], gpr[5], gpr[6], 0); + gpr[2] = read_from_file(env, gpr[4], gpr[5], gpr[6]); gpr[3] = errno_mips(errno); break; case UHI_write: - gpr[2] = write_to_file(env, gpr[4], gpr[5], gpr[6], 0); + gpr[2] = write_to_file(env, gpr[4], gpr[5], gpr[6]); gpr[3] = errno_mips(errno); break; case UHI_lseek: @@ -382,14 +365,6 @@ void mips_semihosting(CPUMIPSState *env) FREE_TARGET_STRING(p, gpr[4]); abort(); break; - case UHI_pread: - gpr[2] = read_from_file(env, gpr[4], gpr[5], gpr[6], gpr[7]); - gpr[3] = errno_mips(errno); - break; - case UHI_pwrite: - gpr[2] = write_to_file(env, gpr[4], gpr[5], gpr[6], gpr[7]); - gpr[3] = errno_mips(errno); - break; #ifndef _WIN32 case UHI_link: GET_TARGET_STRINGS_2(p, gpr[4], p2, gpr[5]); From 67ba5c52c074aa5f0d22fbeff79dca7fc9664308 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Fri, 29 Apr 2022 18:32:35 -0700 Subject: [PATCH 10/69] semihosting: Improve condition for config.c and console.c While CONFIG_SEMIHOSTING is currently only set for softmmu, this will not continue to be true. Reviewed-by: Peter Maydell Signed-off-by: Richard Henderson (cherry picked from commit 259739ce74300ae9e5df7c8c3a8a672341af5546) --- semihosting/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semihosting/meson.build b/semihosting/meson.build index ea8090abe34c6..4344e43fb95c6 100644 --- a/semihosting/meson.build +++ b/semihosting/meson.build @@ -1,4 +1,4 @@ -specific_ss.add(when: 'CONFIG_SEMIHOSTING', if_true: files( +specific_ss.add(when: ['CONFIG_SEMIHOSTING', 'CONFIG_SOFTMMU'], if_true: files( 'config.c', 'console.c', )) From 7a5487146373dd4dd40f0fedaf9140fd982f413c Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Wed, 27 Apr 2022 20:44:17 -0700 Subject: [PATCH 11/69] semihosting: Move softmmu-uaccess.h functions out of line Rather that static (and not even inline) functions within a header, move the functions to semihosting/uaccess.c. Reviewed-by: Peter Maydell Signed-off-by: Richard Henderson (cherry picked from commit 0a9221810ce08fd5b0c4cff6055e640d4bd6876d) --- include/semihosting/softmmu-uaccess.h | 43 ++++------------------ semihosting/meson.build | 1 + semihosting/uaccess.c | 51 +++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 36 deletions(-) create mode 100644 semihosting/uaccess.c diff --git a/include/semihosting/softmmu-uaccess.h b/include/semihosting/softmmu-uaccess.h index e69e3c85488d8..b2bec5665b4f1 100644 --- a/include/semihosting/softmmu-uaccess.h +++ b/include/semihosting/softmmu-uaccess.h @@ -58,44 +58,15 @@ static inline void softmmu_tput32(CPUArchState *env, #define put_user_u32(arg, p) ({ softmmu_tput32(env, p, arg) ; 0; }) #define put_user_ual(arg, p) put_user_u32(arg, p) -static void *softmmu_lock_user(CPUArchState *env, - target_ulong addr, target_ulong len, int copy) -{ - uint8_t *p; - /* TODO: Make this something that isn't fixed size. */ - p = malloc(len); - if (p && copy) { - cpu_memory_rw_debug(env_cpu(env), addr, p, len, 0); - } - return p; -} +void *softmmu_lock_user(CPUArchState *env, target_ulong addr, + target_ulong len, bool copy); #define lock_user(type, p, len, copy) softmmu_lock_user(env, p, len, copy) -static char *softmmu_lock_user_string(CPUArchState *env, target_ulong addr) -{ - char *p; - char *s; - uint8_t c; - /* TODO: Make this something that isn't fixed size. */ - s = p = malloc(1024); - if (!s) { - return NULL; - } - do { - cpu_memory_rw_debug(env_cpu(env), addr, &c, 1, 0); - addr++; - *(p++) = c; - } while (c); - return s; -} + +char *softmmu_lock_user_string(CPUArchState *env, target_ulong addr); #define lock_user_string(p) softmmu_lock_user_string(env, p) -static void softmmu_unlock_user(CPUArchState *env, void *p, target_ulong addr, - target_ulong len) -{ - if (len) { - cpu_memory_rw_debug(env_cpu(env), addr, p, len, 1); - } - free(p); -} + +void softmmu_unlock_user(CPUArchState *env, void *p, + target_ulong addr, target_ulong len); #define unlock_user(s, args, len) softmmu_unlock_user(env, s, args, len) #endif /* SEMIHOSTING_SOFTMMU_UACCESS_H */ diff --git a/semihosting/meson.build b/semihosting/meson.build index 4344e43fb95c6..10b3b99921f9c 100644 --- a/semihosting/meson.build +++ b/semihosting/meson.build @@ -1,6 +1,7 @@ specific_ss.add(when: ['CONFIG_SEMIHOSTING', 'CONFIG_SOFTMMU'], if_true: files( 'config.c', 'console.c', + 'uaccess.c', )) specific_ss.add(when: ['CONFIG_ARM_COMPATIBLE_SEMIHOSTING'], diff --git a/semihosting/uaccess.c b/semihosting/uaccess.c new file mode 100644 index 0000000000000..0d3b32b75d1af --- /dev/null +++ b/semihosting/uaccess.c @@ -0,0 +1,51 @@ +/* + * Helper routines to provide target memory access for semihosting + * syscalls in system emulation mode. + * + * Copyright (c) 2007 CodeSourcery. + * + * This code is licensed under the GPL + */ + +#include "qemu/osdep.h" +#include "semihosting/softmmu-uaccess.h" + +void *softmmu_lock_user(CPUArchState *env, target_ulong addr, + target_ulong len, bool copy) +{ + void *p = malloc(len); + if (p && copy) { + if (cpu_memory_rw_debug(env_cpu(env), addr, p, len, 0)) { + free(p); + p = NULL; + } + } + return p; +} + +char *softmmu_lock_user_string(CPUArchState *env, target_ulong addr) +{ + /* TODO: Make this something that isn't fixed size. */ + char *s = malloc(1024); + size_t len = 0; + + if (!s) { + return NULL; + } + do { + if (cpu_memory_rw_debug(env_cpu(env), addr++, s + len, 1, 0)) { + free(s); + return NULL; + } + } while (s[len++]); + return s; +} + +void softmmu_unlock_user(CPUArchState *env, void *p, + target_ulong addr, target_ulong len) +{ + if (len) { + cpu_memory_rw_debug(env_cpu(env), addr, p, len, 1); + } + free(p); +} From 3563e088945495a513636fe83bed052aebd9896a Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Wed, 27 Apr 2022 21:03:12 -0700 Subject: [PATCH 12/69] semihosting: Add target_strlen for softmmu-uaccess.h Mirror the interface of the user-only function of the same name. Use probe_access_flags for the common case of ram, and cpu_memory_rw_debug for the uncommon case of mmio. Reviewed-by: Peter Maydell Signed-off-by: Richard Henderson --- v3: Use probe_access_flags (pmm) (cherry picked from commit 5f9ca6f3c5111fadb0b1e76755ceaf738a98db4c) --- include/semihosting/softmmu-uaccess.h | 3 ++ semihosting/uaccess.c | 49 +++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/include/semihosting/softmmu-uaccess.h b/include/semihosting/softmmu-uaccess.h index b2bec5665b4f1..bd9eb90e9d0e3 100644 --- a/include/semihosting/softmmu-uaccess.h +++ b/include/semihosting/softmmu-uaccess.h @@ -69,4 +69,7 @@ void softmmu_unlock_user(CPUArchState *env, void *p, target_ulong addr, target_ulong len); #define unlock_user(s, args, len) softmmu_unlock_user(env, s, args, len) +ssize_t softmmu_strlen_user(CPUArchState *env, target_ulong addr); +#define target_strlen(p) softmmu_strlen_user(env, p) + #endif /* SEMIHOSTING_SOFTMMU_UACCESS_H */ diff --git a/semihosting/uaccess.c b/semihosting/uaccess.c index 0d3b32b75d1af..d6997e3c652bb 100644 --- a/semihosting/uaccess.c +++ b/semihosting/uaccess.c @@ -8,6 +8,7 @@ */ #include "qemu/osdep.h" +#include "exec/exec-all.h" #include "semihosting/softmmu-uaccess.h" void *softmmu_lock_user(CPUArchState *env, target_ulong addr, @@ -23,6 +24,54 @@ void *softmmu_lock_user(CPUArchState *env, target_ulong addr, return p; } +ssize_t softmmu_strlen_user(CPUArchState *env, target_ulong addr) +{ + int mmu_idx = cpu_mmu_index(env, false); + size_t len = 0; + + while (1) { + size_t left_in_page; + int flags; + void *h; + + /* Find the number of bytes remaining in the page. */ + left_in_page = TARGET_PAGE_SIZE - (addr & ~TARGET_PAGE_MASK); + + flags = probe_access_flags(env, addr, MMU_DATA_LOAD, + mmu_idx, true, &h, 0); + if (flags & TLB_INVALID_MASK) { + return -1; + } + if (flags & TLB_MMIO) { + do { + uint8_t c; + if (cpu_memory_rw_debug(env_cpu(env), addr, &c, 1, 0)) { + return -1; + } + if (c == 0) { + return len; + } + addr++; + len++; + if (len > INT32_MAX) { + return -1; + } + } while (--left_in_page != 0); + } else { + char *p = memchr(h, 0, left_in_page); + if (p) { + len += p - (char *)h; + return len <= INT32_MAX ? (ssize_t)len : -1; + } + addr += left_in_page; + len += left_in_page; + if (len > INT32_MAX) { + return -1; + } + } + } +} + char *softmmu_lock_user_string(CPUArchState *env, target_ulong addr) { /* TODO: Make this something that isn't fixed size. */ From 429fbcd903c3b741fe86fcaa4e05cb8af67e9671 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Wed, 27 Apr 2022 21:06:58 -0700 Subject: [PATCH 13/69] semihosting: Simplify softmmu_lock_user_string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We are not currently bounding the search to the 1024 bytes that we allocated, possibly overrunning the buffer. Use softmmu_strlen_user to find the length and allocate the correct size from the beginning. Reviewed-by: Alex Bennée Reviewed-by: Peter Maydell Signed-off-by: Richard Henderson (cherry picked from commit 3d5e2b4f26e077e9a8fd94659a1ce2dd49c134b7) --- semihosting/uaccess.c | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/semihosting/uaccess.c b/semihosting/uaccess.c index d6997e3c652bb..801882806976f 100644 --- a/semihosting/uaccess.c +++ b/semihosting/uaccess.c @@ -74,20 +74,11 @@ ssize_t softmmu_strlen_user(CPUArchState *env, target_ulong addr) char *softmmu_lock_user_string(CPUArchState *env, target_ulong addr) { - /* TODO: Make this something that isn't fixed size. */ - char *s = malloc(1024); - size_t len = 0; - - if (!s) { + ssize_t len = softmmu_strlen_user(env, addr); + if (len < 0) { return NULL; } - do { - if (cpu_memory_rw_debug(env_cpu(env), addr++, s + len, 1, 0)) { - free(s); - return NULL; - } - } while (s[len++]); - return s; + return softmmu_lock_user(env, addr, len + 1, true); } void softmmu_unlock_user(CPUArchState *env, void *p, From a968d75b623c1af07c9fc1fb9897e3b042da5d3c Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Wed, 27 Apr 2022 21:38:02 -0700 Subject: [PATCH 14/69] semihosting: Split out guestfd.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In arm-compat-semi.c, we have more advanced treatment of guest file descriptors than we do in other implementations. Split out GuestFD and related functions to a new file so that they can be shared. Reviewed-by: Alex Bennée Reviewed-by: Peter Maydell Signed-off-by: Richard Henderson (cherry picked from commit 1c6ff7205bff49870dc3511f237b3ad90da5f5f7) --- configs/targets/aarch64-linux-user.mak | 1 + configs/targets/aarch64_be-linux-user.mak | 1 + configs/targets/arm-linux-user.mak | 1 + configs/targets/armeb-linux-user.mak | 1 + configs/targets/riscv32-linux-user.mak | 1 + configs/targets/riscv64-linux-user.mak | 1 + include/semihosting/guestfd.h | 83 +++++++++++ semihosting/arm-compat-semi.c | 164 +++------------------- semihosting/guestfd.c | 118 ++++++++++++++++ semihosting/meson.build | 4 + 10 files changed, 233 insertions(+), 142 deletions(-) create mode 100644 include/semihosting/guestfd.h create mode 100644 semihosting/guestfd.c diff --git a/configs/targets/aarch64-linux-user.mak b/configs/targets/aarch64-linux-user.mak index d0c603c54ec2a..db552f1839033 100644 --- a/configs/targets/aarch64-linux-user.mak +++ b/configs/targets/aarch64-linux-user.mak @@ -2,4 +2,5 @@ TARGET_ARCH=aarch64 TARGET_BASE_ARCH=arm TARGET_XML_FILES= gdb-xml/aarch64-core.xml gdb-xml/aarch64-fpu.xml TARGET_HAS_BFLT=y +CONFIG_SEMIHOSTING=y CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y diff --git a/configs/targets/aarch64_be-linux-user.mak b/configs/targets/aarch64_be-linux-user.mak index d3ee10c00f31b..5fa1b37c50de6 100644 --- a/configs/targets/aarch64_be-linux-user.mak +++ b/configs/targets/aarch64_be-linux-user.mak @@ -3,4 +3,5 @@ TARGET_BASE_ARCH=arm TARGET_WORDS_BIGENDIAN=y TARGET_XML_FILES= gdb-xml/aarch64-core.xml gdb-xml/aarch64-fpu.xml TARGET_HAS_BFLT=y +CONFIG_SEMIHOSTING=y CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y diff --git a/configs/targets/arm-linux-user.mak b/configs/targets/arm-linux-user.mak index 3e10d6b15d5d3..7f5d65794c1a6 100644 --- a/configs/targets/arm-linux-user.mak +++ b/configs/targets/arm-linux-user.mak @@ -3,4 +3,5 @@ TARGET_SYSTBL_ABI=common,oabi TARGET_SYSTBL=syscall.tbl TARGET_XML_FILES= gdb-xml/arm-core.xml gdb-xml/arm-vfp.xml gdb-xml/arm-vfp3.xml gdb-xml/arm-vfp-sysregs.xml gdb-xml/arm-neon.xml gdb-xml/arm-m-profile.xml gdb-xml/arm-m-profile-mve.xml TARGET_HAS_BFLT=y +CONFIG_SEMIHOSTING=y CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y diff --git a/configs/targets/armeb-linux-user.mak b/configs/targets/armeb-linux-user.mak index f81e5bf1fe4bd..6fafcc3f583b2 100644 --- a/configs/targets/armeb-linux-user.mak +++ b/configs/targets/armeb-linux-user.mak @@ -4,4 +4,5 @@ TARGET_SYSTBL=syscall.tbl TARGET_WORDS_BIGENDIAN=y TARGET_XML_FILES= gdb-xml/arm-core.xml gdb-xml/arm-vfp.xml gdb-xml/arm-vfp3.xml gdb-xml/arm-vfp-sysregs.xml gdb-xml/arm-neon.xml gdb-xml/arm-m-profile.xml gdb-xml/arm-m-profile-mve.xml TARGET_HAS_BFLT=y +CONFIG_SEMIHOSTING=y CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y diff --git a/configs/targets/riscv32-linux-user.mak b/configs/targets/riscv32-linux-user.mak index bd2f1fd4973f2..9761618e67f4f 100644 --- a/configs/targets/riscv32-linux-user.mak +++ b/configs/targets/riscv32-linux-user.mak @@ -2,4 +2,5 @@ TARGET_ARCH=riscv32 TARGET_BASE_ARCH=riscv TARGET_ABI_DIR=riscv TARGET_XML_FILES= gdb-xml/riscv-32bit-cpu.xml gdb-xml/riscv-32bit-fpu.xml gdb-xml/riscv-64bit-fpu.xml gdb-xml/riscv-32bit-virtual.xml +CONFIG_SEMIHOSTING=y CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y diff --git a/configs/targets/riscv64-linux-user.mak b/configs/targets/riscv64-linux-user.mak index 4aca7662cea62..cfd1fd382f922 100644 --- a/configs/targets/riscv64-linux-user.mak +++ b/configs/targets/riscv64-linux-user.mak @@ -2,4 +2,5 @@ TARGET_ARCH=riscv64 TARGET_BASE_ARCH=riscv TARGET_ABI_DIR=riscv TARGET_XML_FILES= gdb-xml/riscv-64bit-cpu.xml gdb-xml/riscv-32bit-fpu.xml gdb-xml/riscv-64bit-fpu.xml gdb-xml/riscv-64bit-virtual.xml +CONFIG_SEMIHOSTING=y CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y diff --git a/include/semihosting/guestfd.h b/include/semihosting/guestfd.h new file mode 100644 index 0000000000000..ef268abe857dc --- /dev/null +++ b/include/semihosting/guestfd.h @@ -0,0 +1,83 @@ +/* + * Hosted file support for semihosting syscalls. + * + * Copyright (c) 2005, 2007 CodeSourcery. + * Copyright (c) 2019 Linaro + * Copyright © 2020 by Keith Packard + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef SEMIHOSTING_GUESTFD_H +#define SEMIHOSTING_GUESTFD_H + +typedef enum GuestFDType { + GuestFDUnused = 0, + GuestFDHost = 1, + GuestFDGDB = 2, + GuestFDStatic = 3, +} GuestFDType; + +/* + * Guest file descriptors are integer indexes into an array of + * these structures (we will dynamically resize as necessary). + */ +typedef struct GuestFD { + GuestFDType type; + union { + int hostfd; + struct { + const uint8_t *data; + size_t len; + size_t off; + } staticfile; + }; +} GuestFD; + +/** + * alloc_guestfd: + * + * Allocate an unused GuestFD index. The associated guestfd index + * will still be GuestFDUnused until it is initialized. + */ +int alloc_guestfd(void); + +/** + * dealloc_guestfd: + * @guestfd: GuestFD index + * + * Deallocate a GuestFD index. The associated GuestFD structure + * will be recycled for a subsequent allocation. + */ +void dealloc_guestfd(int guestfd); + +/** + * get_guestfd: + * @guestfd: GuestFD index + * + * Return the GuestFD structure associated with an initialized @guestfd, + * or NULL if it has not been allocated, or hasn't been initialized. + */ +GuestFD *get_guestfd(int guestfd); + +/** + * associate_guestfd: + * @guestfd: GuestFD index + * @hostfd: host file descriptor + * + * Initialize the GuestFD for @guestfd to GuestFDHost using @hostfd. + */ +void associate_guestfd(int guestfd, int hostfd); + +/** + * staticfile_guestfd: + * @guestfd: GuestFD index + * @data: data to be read + * @len: length of @data + * + * Initialize the GuestFD for @guestfd to GuestFDStatic. + * The @len bytes at @data will be returned to the guest on reads. + */ +void staticfile_guestfd(int guestfd, const uint8_t *data, size_t len); + +#endif /* SEMIHOSTING_GUESTFD_H */ diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index 22a8d2f7571c9..0bf8316b47893 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -32,12 +32,13 @@ */ #include "qemu/osdep.h" - #include "semihosting/semihost.h" #include "semihosting/console.h" #include "semihosting/common-semi.h" +#include "semihosting/guestfd.h" #include "qemu/timer.h" #include "exec/gdbstub.h" + #ifdef CONFIG_USER_ONLY #include "qemu.h" @@ -115,27 +116,6 @@ static int open_modeflags[12] = { O_RDWR | O_CREAT | O_APPEND | O_BINARY }; -typedef enum GuestFDType { - GuestFDUnused = 0, - GuestFDHost = 1, - GuestFDGDB = 2, - GuestFDFeatureFile = 3, -} GuestFDType; - -/* - * Guest file descriptors are integer indexes into an array of - * these structures (we will dynamically resize as necessary). - */ -typedef struct GuestFD { - GuestFDType type; - union { - int hostfd; - target_ulong featurefile_offset; - }; -} GuestFD; - -static GArray *guestfd_array; - #ifndef CONFIG_USER_ONLY /** @@ -260,98 +240,6 @@ common_semi_sys_exit_extended(CPUState *cs, int nr) #endif -/* - * Allocate a new guest file descriptor and return it; if we - * couldn't allocate a new fd then return -1. - * This is a fairly simplistic implementation because we don't - * expect that most semihosting guest programs will make very - * heavy use of opening and closing fds. - */ -static int alloc_guestfd(void) -{ - guint i; - - if (!guestfd_array) { - /* New entries zero-initialized, i.e. type GuestFDUnused */ - guestfd_array = g_array_new(FALSE, TRUE, sizeof(GuestFD)); - } - - /* SYS_OPEN should return nonzero handle on success. Start guestfd from 1 */ - for (i = 1; i < guestfd_array->len; i++) { - GuestFD *gf = &g_array_index(guestfd_array, GuestFD, i); - - if (gf->type == GuestFDUnused) { - return i; - } - } - - /* All elements already in use: expand the array */ - g_array_set_size(guestfd_array, i + 1); - return i; -} - -/* - * Look up the guestfd in the data structure; return NULL - * for out of bounds, but don't check whether the slot is unused. - * This is used internally by the other guestfd functions. - */ -static GuestFD *do_get_guestfd(int guestfd) -{ - if (!guestfd_array) { - return NULL; - } - - if (guestfd <= 0 || guestfd >= guestfd_array->len) { - return NULL; - } - - return &g_array_index(guestfd_array, GuestFD, guestfd); -} - -/* - * Associate the specified guest fd (which must have been - * allocated via alloc_fd() and not previously used) with - * the specified host/gdb fd. - */ -static void associate_guestfd(int guestfd, int hostfd) -{ - GuestFD *gf = do_get_guestfd(guestfd); - - assert(gf); - gf->type = use_gdb_syscalls() ? GuestFDGDB : GuestFDHost; - gf->hostfd = hostfd; -} - -/* - * Deallocate the specified guest file descriptor. This doesn't - * close the host fd, it merely undoes the work of alloc_fd(). - */ -static void dealloc_guestfd(int guestfd) -{ - GuestFD *gf = do_get_guestfd(guestfd); - - assert(gf); - gf->type = GuestFDUnused; -} - -/* - * Given a guest file descriptor, get the associated struct. - * If the fd is not valid, return NULL. This is the function - * used by the various semihosting calls to validate a handle - * from the guest. - * Note: calling alloc_guestfd() or dealloc_guestfd() will - * invalidate any GuestFD* obtained by calling this function. - */ -static GuestFD *get_guestfd(int guestfd) -{ - GuestFD *gf = do_get_guestfd(guestfd); - - if (!gf || gf->type == GuestFDUnused) { - return NULL; - } - return gf; -} - /* * The semihosting API has no concept of its errno being thread-safe, * as the API design predates SMP CPUs and was intended as a simple @@ -657,22 +545,13 @@ static const uint8_t featurefile_data[] = { SH_EXT_EXIT_EXTENDED | SH_EXT_STDOUT_STDERR, /* Feature byte 0 */ }; -static void init_featurefile_guestfd(int guestfd) -{ - GuestFD *gf = do_get_guestfd(guestfd); - - assert(gf); - gf->type = GuestFDFeatureFile; - gf->featurefile_offset = 0; -} - -static uint32_t featurefile_closefn(CPUState *cs, GuestFD *gf) +static uint32_t staticfile_closefn(CPUState *cs, GuestFD *gf) { /* Nothing to do */ return 0; } -static uint32_t featurefile_writefn(CPUState *cs, GuestFD *gf, +static uint32_t staticfile_writefn(CPUState *cs, GuestFD *gf, target_ulong buf, uint32_t len) { /* This fd can never be open for writing */ @@ -681,7 +560,7 @@ static uint32_t featurefile_writefn(CPUState *cs, GuestFD *gf, return set_swi_errno(cs, -1); } -static uint32_t featurefile_readfn(CPUState *cs, GuestFD *gf, +static uint32_t staticfile_readfn(CPUState *cs, GuestFD *gf, target_ulong buf, uint32_t len) { CPUArchState *env = cs->env_ptr; @@ -695,11 +574,11 @@ static uint32_t featurefile_readfn(CPUState *cs, GuestFD *gf, } for (i = 0; i < len; i++) { - if (gf->featurefile_offset >= sizeof(featurefile_data)) { + if (gf->staticfile.off >= gf->staticfile.len) { break; } - s[i] = featurefile_data[gf->featurefile_offset]; - gf->featurefile_offset++; + s[i] = gf->staticfile.data[gf->staticfile.off]; + gf->staticfile.off++; } unlock_user(s, buf, len); @@ -708,21 +587,21 @@ static uint32_t featurefile_readfn(CPUState *cs, GuestFD *gf, return len - i; } -static uint32_t featurefile_isattyfn(CPUState *cs, GuestFD *gf) +static uint32_t staticfile_isattyfn(CPUState *cs, GuestFD *gf) { return 0; } -static uint32_t featurefile_seekfn(CPUState *cs, GuestFD *gf, +static uint32_t staticfile_seekfn(CPUState *cs, GuestFD *gf, target_ulong offset) { - gf->featurefile_offset = offset; + gf->staticfile.off = offset; return 0; } -static uint32_t featurefile_flenfn(CPUState *cs, GuestFD *gf) +static uint32_t staticfile_flenfn(CPUState *cs, GuestFD *gf) { - return sizeof(featurefile_data); + return gf->staticfile.len; } typedef struct GuestFDFunctions { @@ -751,13 +630,13 @@ static const GuestFDFunctions guestfd_fns[] = { .seekfn = gdb_seekfn, .flenfn = gdb_flenfn, }, - [GuestFDFeatureFile] = { - .closefn = featurefile_closefn, - .writefn = featurefile_writefn, - .readfn = featurefile_readfn, - .isattyfn = featurefile_isattyfn, - .seekfn = featurefile_seekfn, - .flenfn = featurefile_flenfn, + [GuestFDStatic] = { + .closefn = staticfile_closefn, + .writefn = staticfile_writefn, + .readfn = staticfile_readfn, + .isattyfn = staticfile_isattyfn, + .seekfn = staticfile_seekfn, + .flenfn = staticfile_flenfn, }, }; @@ -878,7 +757,8 @@ target_ulong do_common_semihosting(CPUState *cs) errno = EACCES; return set_swi_errno(cs, -1); } - init_featurefile_guestfd(guestfd); + staticfile_guestfd(guestfd, featurefile_data, + sizeof(featurefile_data)); return guestfd; } diff --git a/semihosting/guestfd.c b/semihosting/guestfd.c new file mode 100644 index 0000000000000..b6405f566377d --- /dev/null +++ b/semihosting/guestfd.c @@ -0,0 +1,118 @@ +/* + * Hosted file support for semihosting syscalls. + * + * Copyright (c) 2005, 2007 CodeSourcery. + * Copyright (c) 2019 Linaro + * Copyright © 2020 by Keith Packard + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "exec/gdbstub.h" +#include "semihosting/guestfd.h" + +static GArray *guestfd_array; + +/* + * Allocate a new guest file descriptor and return it; if we + * couldn't allocate a new fd then return -1. + * This is a fairly simplistic implementation because we don't + * expect that most semihosting guest programs will make very + * heavy use of opening and closing fds. + */ +int alloc_guestfd(void) +{ + guint i; + + if (!guestfd_array) { + /* New entries zero-initialized, i.e. type GuestFDUnused */ + guestfd_array = g_array_new(FALSE, TRUE, sizeof(GuestFD)); + } + + /* SYS_OPEN should return nonzero handle on success. Start guestfd from 1 */ + for (i = 1; i < guestfd_array->len; i++) { + GuestFD *gf = &g_array_index(guestfd_array, GuestFD, i); + + if (gf->type == GuestFDUnused) { + return i; + } + } + + /* All elements already in use: expand the array */ + g_array_set_size(guestfd_array, i + 1); + return i; +} + +/* + * Look up the guestfd in the data structure; return NULL + * for out of bounds, but don't check whether the slot is unused. + * This is used internally by the other guestfd functions. + */ +static GuestFD *do_get_guestfd(int guestfd) +{ + if (!guestfd_array) { + return NULL; + } + + if (guestfd <= 0 || guestfd >= guestfd_array->len) { + return NULL; + } + + return &g_array_index(guestfd_array, GuestFD, guestfd); +} + +/* + * Given a guest file descriptor, get the associated struct. + * If the fd is not valid, return NULL. This is the function + * used by the various semihosting calls to validate a handle + * from the guest. + * Note: calling alloc_guestfd() or dealloc_guestfd() will + * invalidate any GuestFD* obtained by calling this function. + */ +GuestFD *get_guestfd(int guestfd) +{ + GuestFD *gf = do_get_guestfd(guestfd); + + if (!gf || gf->type == GuestFDUnused) { + return NULL; + } + return gf; +} + +/* + * Associate the specified guest fd (which must have been + * allocated via alloc_fd() and not previously used) with + * the specified host/gdb fd. + */ +void associate_guestfd(int guestfd, int hostfd) +{ + GuestFD *gf = do_get_guestfd(guestfd); + + assert(gf); + gf->type = use_gdb_syscalls() ? GuestFDGDB : GuestFDHost; + gf->hostfd = hostfd; +} + +void staticfile_guestfd(int guestfd, const uint8_t *data, size_t len) +{ + GuestFD *gf = do_get_guestfd(guestfd); + + assert(gf); + gf->type = GuestFDStatic; + gf->staticfile.data = data; + gf->staticfile.len = len; + gf->staticfile.off = 0; +} + +/* + * Deallocate the specified guest file descriptor. This doesn't + * close the host fd, it merely undoes the work of alloc_fd(). + */ +void dealloc_guestfd(int guestfd) +{ + GuestFD *gf = do_get_guestfd(guestfd); + + assert(gf); + gf->type = GuestFDUnused; +} diff --git a/semihosting/meson.build b/semihosting/meson.build index 10b3b99921f9c..d2c1c37bfdcfd 100644 --- a/semihosting/meson.build +++ b/semihosting/meson.build @@ -1,3 +1,7 @@ +specific_ss.add(when: 'CONFIG_SEMIHOSTING', if_true: files( + 'guestfd.c', +)) + specific_ss.add(when: ['CONFIG_SEMIHOSTING', 'CONFIG_SOFTMMU'], if_true: files( 'config.c', 'console.c', From bef0a54a9713ca7992dcfce8d0c7203a9cc4ce05 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Mon, 16 May 2022 19:25:19 -0700 Subject: [PATCH 15/69] semihosting: Inline set_swi_errno into common_semi_cb Do not store 'err' into errno only to read it back immediately. Use 'ret' for the return value, not 'reg0'. Reviewed-by: Peter Maydell Signed-off-by: Richard Henderson (cherry picked from commit 5aadd1829905aace2a1201ddb8ac9b7f18d104fb) --- semihosting/arm-compat-semi.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index 0bf8316b47893..af927c866362f 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -282,28 +282,29 @@ static target_ulong common_semi_syscall_len; static void common_semi_cb(CPUState *cs, target_ulong ret, target_ulong err) { - target_ulong reg0 = common_semi_arg(cs, 0); - if (ret == (target_ulong)-1) { - errno = err; - set_swi_errno(cs, -1); - reg0 = ret; +#ifdef CONFIG_USER_ONLY + TaskState *ts = cs->opaque; + ts->swi_errno = err; +#else + syscall_err = err; +#endif } else { /* Fixup syscalls that use nonstardard return conventions. */ + target_ulong reg0 = common_semi_arg(cs, 0); switch (reg0) { case TARGET_SYS_WRITE: case TARGET_SYS_READ: - reg0 = common_semi_syscall_len - ret; + ret = common_semi_syscall_len - ret; break; case TARGET_SYS_SEEK: - reg0 = 0; + ret = 0; break; default: - reg0 = ret; break; } } - common_semi_set_ret(cs, reg0); + common_semi_set_ret(cs, ret); } static target_ulong common_semi_flen_buf(CPUState *cs) From e0c643657ff4a554961fd50e37ced3672a4e0c4a Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Fri, 29 Apr 2022 12:32:24 -0700 Subject: [PATCH 16/69] semihosting: Adjust error checking in common_semi_cb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The err parameter is non-zero if and only if an error occured. Use this instead of ret == -1 for determining if we need to update the saved errno. This fixes the errno setting of SYS_ISTTY, which returns 0 on error, not -1. Reviewed-by: Alex Bennée Signed-off-by: Richard Henderson (cherry picked from commit 709fe27b189aa86c801b9bd655f9267fec17d0d0) --- semihosting/arm-compat-semi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index af927c866362f..53d9d4b6f86c9 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -282,7 +282,7 @@ static target_ulong common_semi_syscall_len; static void common_semi_cb(CPUState *cs, target_ulong ret, target_ulong err) { - if (ret == (target_ulong)-1) { + if (err) { #ifdef CONFIG_USER_ONLY TaskState *ts = cs->opaque; ts->swi_errno = err; From 3ae94d4a4009ab7aed449007aaecab7439239f18 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Mon, 16 May 2022 19:34:06 -0700 Subject: [PATCH 17/69] semihosting: Clean up common_semi_flen_cb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Do not read from the gdb struct stat buffer if the callback is reporting an error. Use common_semi_cb to finish returning results. Reviewed-by: Alex Bennée Signed-off-by: Richard Henderson (cherry picked from commit 84ca0dfd1e8c0a23fe8aeb22f4141e0b14da2812) --- semihosting/arm-compat-semi.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index 53d9d4b6f86c9..ae4a74d6e313c 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -338,15 +338,17 @@ static target_ulong common_semi_flen_buf(CPUState *cs) static void common_semi_flen_cb(CPUState *cs, target_ulong ret, target_ulong err) { - /* The size is always stored in big-endian order, extract - the value. We assume the size always fit in 32 bits. */ - uint32_t size; - cpu_memory_rw_debug(cs, common_semi_flen_buf(cs) + 32, - (uint8_t *)&size, 4, 0); - size = be32_to_cpu(size); - common_semi_set_ret(cs, size); - errno = err; - set_swi_errno(cs, -1); + if (!err) { + /* + * The size is always stored in big-endian order, extract + * the value. We assume the size always fit in 32 bits. + */ + uint32_t size; + cpu_memory_rw_debug(cs, common_semi_flen_buf(cs) + 32, + (uint8_t *)&size, 4, 0); + ret = be32_to_cpu(size); + } + common_semi_cb(cs, ret, err); } static int common_semi_open_guestfd; From 3f124bc1d6af7546bb81870ba52bb0440aa5dba1 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Mon, 16 May 2022 19:37:14 -0700 Subject: [PATCH 18/69] semihosting: Clean up common_semi_open_cb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use common_semi_cb to return results instead of calling set_swi_errno and common_semi_set_ret directly. Reviewed-by: Alex Bennée Signed-off-by: Richard Henderson (cherry picked from commit 4cfeff4ac16bf5e3e1df44d5561b83e3bd3aab6c) --- semihosting/arm-compat-semi.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index ae4a74d6e313c..079e3628e1999 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -356,15 +356,13 @@ static int common_semi_open_guestfd; static void common_semi_open_cb(CPUState *cs, target_ulong ret, target_ulong err) { - if (ret == (target_ulong)-1) { - errno = err; - set_swi_errno(cs, -1); + if (err) { dealloc_guestfd(common_semi_open_guestfd); } else { associate_guestfd(common_semi_open_guestfd, ret); ret = common_semi_open_guestfd; } - common_semi_set_ret(cs, ret); + common_semi_cb(cs, ret, err); } static target_ulong From 7874a10d3d8a63435c7d06b6616e1d18911debd9 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Thu, 28 Apr 2022 01:10:55 -0700 Subject: [PATCH 19/69] semihosting: Return void from do_common_semihosting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Perform the cleanup in the FIXME comment in common_semi_gdb_syscall. Do not modify guest registers until the syscall is complete, which in the gdbstub case is asynchronous. In the synchronous non-gdbstub case, use common_semi_set_ret to set the result. Merge set_swi_errno into common_semi_cb. Rely on the latter for combined return value / errno setting. Reviewed-by: Alex Bennée Signed-off-by: Richard Henderson (cherry picked from commit ed3a06b10a6abb53589d794ed23accf21be05633) --- linux-user/aarch64/cpu_loop.c | 2 +- linux-user/arm/cpu_loop.c | 2 +- linux-user/riscv/cpu_loop.c | 2 +- semihosting/arm-compat-semi.c | 541 ++++++++++++++++------------------ semihosting/common-semi.h | 2 +- target/arm/helper.c | 4 +- target/arm/m_helper.c | 2 +- target/riscv/cpu_helper.c | 2 +- 8 files changed, 263 insertions(+), 294 deletions(-) diff --git a/linux-user/aarch64/cpu_loop.c b/linux-user/aarch64/cpu_loop.c index 1737e2ea655a8..bf3485728298a 100644 --- a/linux-user/aarch64/cpu_loop.c +++ b/linux-user/aarch64/cpu_loop.c @@ -155,7 +155,7 @@ void cpu_loop(CPUARMState *env) force_sig_fault(TARGET_SIGTRAP, TARGET_TRAP_BRKPT, env->pc); break; case EXCP_SEMIHOST: - env->xregs[0] = do_common_semihosting(cs); + do_common_semihosting(cs); env->pc += 4; break; case EXCP_YIELD: diff --git a/linux-user/arm/cpu_loop.c b/linux-user/arm/cpu_loop.c index aae375d617927..3da0c5e1e5f1a 100644 --- a/linux-user/arm/cpu_loop.c +++ b/linux-user/arm/cpu_loop.c @@ -450,7 +450,7 @@ void cpu_loop(CPUARMState *env) } break; case EXCP_SEMIHOST: - env->regs[0] = do_common_semihosting(cs); + do_common_semihosting(cs); env->regs[15] += env->thumb ? 2 : 4; break; case EXCP_INTERRUPT: diff --git a/linux-user/riscv/cpu_loop.c b/linux-user/riscv/cpu_loop.c index 26d446f323797..33aa8a294f05c 100644 --- a/linux-user/riscv/cpu_loop.c +++ b/linux-user/riscv/cpu_loop.c @@ -82,7 +82,7 @@ void cpu_loop(CPURISCVState *env) force_sig_fault(TARGET_SIGTRAP, TARGET_TRAP_BRKPT, env->pc); break; case RISCV_EXCP_SEMIHOST: - env->gpr[xA0] = do_common_semihosting(cs); + do_common_semihosting(cs); env->pc += 4; break; default: diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index 079e3628e1999..246089224d7ba 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -253,20 +253,6 @@ static target_ulong syscall_err; #include "semihosting/softmmu-uaccess.h" #endif -static inline uint32_t set_swi_errno(CPUState *cs, uint32_t code) -{ - if (code == (uint32_t)-1) { -#ifdef CONFIG_USER_ONLY - TaskState *ts = cs->opaque; - - ts->swi_errno = errno; -#else - syscall_err = errno; -#endif - } - return code; -} - static inline uint32_t get_swi_errno(CPUState *cs) { #ifdef CONFIG_USER_ONLY @@ -365,54 +351,24 @@ common_semi_open_cb(CPUState *cs, target_ulong ret, target_ulong err) common_semi_cb(cs, ret, err); } -static target_ulong -common_semi_gdb_syscall(CPUState *cs, gdb_syscall_complete_cb cb, - const char *fmt, ...) -{ - va_list va; - - va_start(va, fmt); - gdb_do_syscallv(cb, fmt, va); - va_end(va); - - /* - * FIXME: in softmmu mode, the gdbstub will schedule our callback - * to occur, but will not actually call it to complete the syscall - * until after this function has returned and we are back in the - * CPU main loop. Therefore callers to this function must not - * do anything with its return value, because it is not necessarily - * the result of the syscall, but could just be the old value of X0. - * The only thing safe to do with this is that the callers of - * do_common_semihosting() will write it straight back into X0. - * (In linux-user mode, the callback will have happened before - * gdb_do_syscallv() returns.) - * - * We should tidy this up so neither this function nor - * do_common_semihosting() return a value, so the mistake of - * doing something with the return value is not possible to make. - */ - - return common_semi_arg(cs, 0); -} - /* * Types for functions implementing various semihosting calls * for specific types of guest file descriptor. These must all - * do the work and return the required return value for the guest, - * setting the guest errno if appropriate. + * do the work and return the required return value to the guest + * via common_semi_cb. */ -typedef uint32_t sys_closefn(CPUState *cs, GuestFD *gf); -typedef uint32_t sys_writefn(CPUState *cs, GuestFD *gf, - target_ulong buf, uint32_t len); -typedef uint32_t sys_readfn(CPUState *cs, GuestFD *gf, - target_ulong buf, uint32_t len); -typedef uint32_t sys_isattyfn(CPUState *cs, GuestFD *gf); -typedef uint32_t sys_seekfn(CPUState *cs, GuestFD *gf, - target_ulong offset); -typedef uint32_t sys_flenfn(CPUState *cs, GuestFD *gf); - -static uint32_t host_closefn(CPUState *cs, GuestFD *gf) +typedef void sys_closefn(CPUState *cs, GuestFD *gf); +typedef void sys_writefn(CPUState *cs, GuestFD *gf, + target_ulong buf, uint32_t len); +typedef void sys_readfn(CPUState *cs, GuestFD *gf, + target_ulong buf, uint32_t len); +typedef void sys_isattyfn(CPUState *cs, GuestFD *gf); +typedef void sys_seekfn(CPUState *cs, GuestFD *gf, target_ulong offset); +typedef void sys_flenfn(CPUState *cs, GuestFD *gf); + +static void host_closefn(CPUState *cs, GuestFD *gf) { + int ret; /* * Only close the underlying host fd if it's one we opened on behalf * of the guest in SYS_OPEN. @@ -420,113 +376,106 @@ static uint32_t host_closefn(CPUState *cs, GuestFD *gf) if (gf->hostfd == STDIN_FILENO || gf->hostfd == STDOUT_FILENO || gf->hostfd == STDERR_FILENO) { - return 0; + ret = 0; + } else { + ret = close(gf->hostfd); } - return set_swi_errno(cs, close(gf->hostfd)); + common_semi_cb(cs, ret, ret ? errno : 0); } -static uint32_t host_writefn(CPUState *cs, GuestFD *gf, - target_ulong buf, uint32_t len) +static void host_writefn(CPUState *cs, GuestFD *gf, + target_ulong buf, uint32_t len) { CPUArchState *env = cs->env_ptr; - uint32_t ret; + uint32_t ret = 0; char *s = lock_user(VERIFY_READ, buf, len, 1); (void) env; /* Used in arm softmmu lock_user implicitly */ - if (!s) { - /* Return bytes not written on error */ - return len; - } - ret = set_swi_errno(cs, write(gf->hostfd, s, len)); - unlock_user(s, buf, 0); - if (ret == (uint32_t)-1) { - ret = 0; + if (s) { + ret = write(gf->hostfd, s, len); + unlock_user(s, buf, 0); + if (ret == (uint32_t)-1) { + ret = 0; + } } - /* Return bytes not written */ - return len - ret; + /* Return bytes not written, on error as well. */ + common_semi_cb(cs, len - ret, 0); } -static uint32_t host_readfn(CPUState *cs, GuestFD *gf, - target_ulong buf, uint32_t len) +static void host_readfn(CPUState *cs, GuestFD *gf, + target_ulong buf, uint32_t len) { CPUArchState *env = cs->env_ptr; - uint32_t ret; + uint32_t ret = 0; char *s = lock_user(VERIFY_WRITE, buf, len, 0); (void) env; /* Used in arm softmmu lock_user implicitly */ - if (!s) { - /* return bytes not read */ - return len; - } - do { - ret = set_swi_errno(cs, read(gf->hostfd, s, len)); - } while (ret == -1 && errno == EINTR); - unlock_user(s, buf, len); - if (ret == (uint32_t)-1) { - ret = 0; + if (s) { + do { + ret = read(gf->hostfd, s, len); + } while (ret == -1 && errno == EINTR); + unlock_user(s, buf, len); + if (ret == (uint32_t)-1) { + ret = 0; + } } - /* Return bytes not read */ - return len - ret; + /* Return bytes not read, on error as well. */ + common_semi_cb(cs, len - ret, 0); } -static uint32_t host_isattyfn(CPUState *cs, GuestFD *gf) +static void host_isattyfn(CPUState *cs, GuestFD *gf) { - return isatty(gf->hostfd); + common_semi_cb(cs, isatty(gf->hostfd), 0); } -static uint32_t host_seekfn(CPUState *cs, GuestFD *gf, target_ulong offset) +static void host_seekfn(CPUState *cs, GuestFD *gf, target_ulong offset) { - uint32_t ret = set_swi_errno(cs, lseek(gf->hostfd, offset, SEEK_SET)); - if (ret == (uint32_t)-1) { - return -1; - } - return 0; + off_t ret = lseek(gf->hostfd, offset, SEEK_SET); + common_semi_cb(cs, ret, ret == -1 ? errno : 0); } -static uint32_t host_flenfn(CPUState *cs, GuestFD *gf) +static void host_flenfn(CPUState *cs, GuestFD *gf) { struct stat buf; - uint32_t ret = set_swi_errno(cs, fstat(gf->hostfd, &buf)); - if (ret == (uint32_t)-1) { - return -1; + + if (fstat(gf->hostfd, &buf)) { + common_semi_cb(cs, -1, errno); + } else { + common_semi_cb(cs, buf.st_size, 0); } - return buf.st_size; } -static uint32_t gdb_closefn(CPUState *cs, GuestFD *gf) +static void gdb_closefn(CPUState *cs, GuestFD *gf) { - return common_semi_gdb_syscall(cs, common_semi_cb, "close,%x", gf->hostfd); + gdb_do_syscall(common_semi_cb, "close,%x", gf->hostfd); } -static uint32_t gdb_writefn(CPUState *cs, GuestFD *gf, - target_ulong buf, uint32_t len) +static void gdb_writefn(CPUState *cs, GuestFD *gf, + target_ulong buf, uint32_t len) { common_semi_syscall_len = len; - return common_semi_gdb_syscall(cs, common_semi_cb, "write,%x,%x,%x", - gf->hostfd, buf, len); + gdb_do_syscall(common_semi_cb, "write,%x,%x,%x", gf->hostfd, buf, len); } -static uint32_t gdb_readfn(CPUState *cs, GuestFD *gf, - target_ulong buf, uint32_t len) +static void gdb_readfn(CPUState *cs, GuestFD *gf, + target_ulong buf, uint32_t len) { common_semi_syscall_len = len; - return common_semi_gdb_syscall(cs, common_semi_cb, "read,%x,%x,%x", - gf->hostfd, buf, len); + gdb_do_syscall(common_semi_cb, "read,%x,%x,%x", gf->hostfd, buf, len); } -static uint32_t gdb_isattyfn(CPUState *cs, GuestFD *gf) +static void gdb_isattyfn(CPUState *cs, GuestFD *gf) { - return common_semi_gdb_syscall(cs, common_semi_cb, "isatty,%x", gf->hostfd); + gdb_do_syscall(common_semi_cb, "isatty,%x", gf->hostfd); } -static uint32_t gdb_seekfn(CPUState *cs, GuestFD *gf, target_ulong offset) +static void gdb_seekfn(CPUState *cs, GuestFD *gf, target_ulong offset) { - return common_semi_gdb_syscall(cs, common_semi_cb, "lseek,%x,%x,0", - gf->hostfd, offset); + gdb_do_syscall(common_semi_cb, "lseek,%x,%x,0", gf->hostfd, offset); } -static uint32_t gdb_flenfn(CPUState *cs, GuestFD *gf) +static void gdb_flenfn(CPUState *cs, GuestFD *gf) { - return common_semi_gdb_syscall(cs, common_semi_flen_cb, "fstat,%x,%x", - gf->hostfd, common_semi_flen_buf(cs)); + gdb_do_syscall(common_semi_flen_cb, "fstat,%x,%x", + gf->hostfd, common_semi_flen_buf(cs)); } #define SHFB_MAGIC_0 0x53 @@ -546,63 +495,57 @@ static const uint8_t featurefile_data[] = { SH_EXT_EXIT_EXTENDED | SH_EXT_STDOUT_STDERR, /* Feature byte 0 */ }; -static uint32_t staticfile_closefn(CPUState *cs, GuestFD *gf) +static void staticfile_closefn(CPUState *cs, GuestFD *gf) { /* Nothing to do */ - return 0; + common_semi_cb(cs, 0, 0); } -static uint32_t staticfile_writefn(CPUState *cs, GuestFD *gf, - target_ulong buf, uint32_t len) +static void staticfile_writefn(CPUState *cs, GuestFD *gf, + target_ulong buf, uint32_t len) { /* This fd can never be open for writing */ - - errno = EBADF; - return set_swi_errno(cs, -1); + common_semi_cb(cs, -1, EBADF); } -static uint32_t staticfile_readfn(CPUState *cs, GuestFD *gf, - target_ulong buf, uint32_t len) +static void staticfile_readfn(CPUState *cs, GuestFD *gf, + target_ulong buf, uint32_t len) { CPUArchState *env = cs->env_ptr; - uint32_t i; + uint32_t i = 0; char *s; (void) env; /* Used in arm softmmu lock_user implicitly */ s = lock_user(VERIFY_WRITE, buf, len, 0); - if (!s) { - return len; - } - - for (i = 0; i < len; i++) { - if (gf->staticfile.off >= gf->staticfile.len) { - break; + if (s) { + for (i = 0; i < len; i++) { + if (gf->staticfile.off >= gf->staticfile.len) { + break; + } + s[i] = gf->staticfile.data[gf->staticfile.off]; + gf->staticfile.off++; } - s[i] = gf->staticfile.data[gf->staticfile.off]; - gf->staticfile.off++; + unlock_user(s, buf, len); } - unlock_user(s, buf, len); - /* Return number of bytes not read */ - return len - i; + common_semi_cb(cs, len - i, 0); } -static uint32_t staticfile_isattyfn(CPUState *cs, GuestFD *gf) +static void staticfile_isattyfn(CPUState *cs, GuestFD *gf) { - return 0; + common_semi_cb(cs, 0, 0); } -static uint32_t staticfile_seekfn(CPUState *cs, GuestFD *gf, - target_ulong offset) +static void staticfile_seekfn(CPUState *cs, GuestFD *gf, target_ulong offset) { gf->staticfile.off = offset; - return 0; + common_semi_cb(cs, 0, 0); } -static uint32_t staticfile_flenfn(CPUState *cs, GuestFD *gf) +static void staticfile_flenfn(CPUState *cs, GuestFD *gf) { - return gf->staticfile.len; + common_semi_cb(cs, gf->staticfile.len, 0); } typedef struct GuestFDFunctions { @@ -661,13 +604,11 @@ static inline bool is_64bit_semihosting(CPUArchState *env) #define GET_ARG(n) do { \ if (is_64bit_semihosting(env)) { \ if (get_user_u64(arg ## n, args + (n) * 8)) { \ - errno = EFAULT; \ - return set_swi_errno(cs, -1); \ + goto do_fault; \ } \ } else { \ if (get_user_u32(arg ## n, args + (n) * 4)) { \ - errno = EFAULT; \ - return set_swi_errno(cs, -1); \ + goto do_fault; \ } \ } \ } while (0) @@ -687,7 +628,7 @@ static inline bool is_64bit_semihosting(CPUArchState *env) * leave the register unchanged. We use 0xdeadbeef as the return value * when there isn't a defined return value for the call. */ -target_ulong do_common_semihosting(CPUState *cs) +void do_common_semihosting(CPUState *cs) { CPUArchState *env = cs->env_ptr; target_ulong args; @@ -707,32 +648,23 @@ target_ulong do_common_semihosting(CPUState *cs) switch (nr) { case TARGET_SYS_OPEN: { - int guestfd; + int ret, err = 0; + int hostfd; GET_ARG(0); GET_ARG(1); GET_ARG(2); s = lock_user_string(arg0); if (!s) { - errno = EFAULT; - return set_swi_errno(cs, -1); + goto do_fault; } if (arg1 >= 12) { unlock_user(s, arg0, 0); - errno = EINVAL; - return set_swi_errno(cs, -1); - } - - guestfd = alloc_guestfd(); - if (guestfd < 0) { - unlock_user(s, arg0, 0); - errno = EMFILE; - return set_swi_errno(cs, -1); + common_semi_cb(cs, -1, EINVAL); + break; } if (strcmp(s, ":tt") == 0) { - int result_fileno; - /* * We implement SH_EXT_STDOUT_STDERR, so: * open for read == stdin @@ -740,63 +672,67 @@ target_ulong do_common_semihosting(CPUState *cs) * open for append == stderr */ if (arg1 < 4) { - result_fileno = STDIN_FILENO; + hostfd = STDIN_FILENO; } else if (arg1 < 8) { - result_fileno = STDOUT_FILENO; + hostfd = STDOUT_FILENO; } else { - result_fileno = STDERR_FILENO; + hostfd = STDERR_FILENO; } - associate_guestfd(guestfd, result_fileno); - unlock_user(s, arg0, 0); - return guestfd; - } - if (strcmp(s, ":semihosting-features") == 0) { - unlock_user(s, arg0, 0); + ret = alloc_guestfd(); + associate_guestfd(ret, hostfd); + } else if (strcmp(s, ":semihosting-features") == 0) { /* We must fail opens for modes other than 0 ('r') or 1 ('rb') */ if (arg1 != 0 && arg1 != 1) { - dealloc_guestfd(guestfd); - errno = EACCES; - return set_swi_errno(cs, -1); + ret = -1; + err = EACCES; + } else { + ret = alloc_guestfd(); + staticfile_guestfd(ret, featurefile_data, + sizeof(featurefile_data)); } - staticfile_guestfd(guestfd, featurefile_data, - sizeof(featurefile_data)); - return guestfd; - } - - if (use_gdb_syscalls()) { - common_semi_open_guestfd = guestfd; - ret = common_semi_gdb_syscall(cs, common_semi_open_cb, - "open,%s,%x,1a4", arg0, (int)arg2 + 1, - gdb_open_modeflags[arg1]); + } else if (use_gdb_syscalls()) { + unlock_user(s, arg0, 0); + common_semi_open_guestfd = alloc_guestfd(); + gdb_do_syscall(common_semi_open_cb, + "open,%s,%x,1a4", arg0, (int)arg2 + 1, + gdb_open_modeflags[arg1]); + break; } else { - ret = set_swi_errno(cs, open(s, open_modeflags[arg1], 0644)); - if (ret == (uint32_t)-1) { - dealloc_guestfd(guestfd); + hostfd = open(s, open_modeflags[arg1], 0644); + if (hostfd < 0) { + ret = -1; + err = errno; } else { - associate_guestfd(guestfd, ret); - ret = guestfd; + ret = alloc_guestfd(); + associate_guestfd(ret, hostfd); } } unlock_user(s, arg0, 0); - return ret; + common_semi_cb(cs, ret, err); + break; } + case TARGET_SYS_CLOSE: GET_ARG(0); gf = get_guestfd(arg0); if (!gf) { - errno = EBADF; - return set_swi_errno(cs, -1); + goto do_badf; } - - ret = guestfd_fns[gf->type].closefn(cs, gf); + guestfd_fns[gf->type].closefn(cs, gf); dealloc_guestfd(arg0); - return ret; + break; + case TARGET_SYS_WRITEC: qemu_semihosting_console_outc(cs->env_ptr, args); - return 0xdeadbeef; + common_semi_set_ret(cs, 0xdeadbeef); + break; + case TARGET_SYS_WRITE0: - return qemu_semihosting_console_outs(cs->env_ptr, args); + ret = qemu_semihosting_console_outs(cs->env_ptr, args); + common_semi_set_ret(cs, ret); + break; + case TARGET_SYS_WRITE: GET_ARG(0); GET_ARG(1); @@ -805,11 +741,11 @@ target_ulong do_common_semihosting(CPUState *cs) gf = get_guestfd(arg0); if (!gf) { - errno = EBADF; - return set_swi_errno(cs, -1); + goto do_badf; } + guestfd_fns[gf->type].writefn(cs, gf, arg1, len); + break; - return guestfd_fns[gf->type].writefn(cs, gf, arg1, len); case TARGET_SYS_READ: GET_ARG(0); GET_ARG(1); @@ -818,129 +754,150 @@ target_ulong do_common_semihosting(CPUState *cs) gf = get_guestfd(arg0); if (!gf) { - errno = EBADF; - return set_swi_errno(cs, -1); + goto do_badf; } + guestfd_fns[gf->type].readfn(cs, gf, arg1, len); + break; - return guestfd_fns[gf->type].readfn(cs, gf, arg1, len); case TARGET_SYS_READC: - return qemu_semihosting_console_inc(cs->env_ptr); + ret = qemu_semihosting_console_inc(cs->env_ptr); + common_semi_set_ret(cs, ret); + break; + case TARGET_SYS_ISERROR: GET_ARG(0); - return (target_long) arg0 < 0 ? 1 : 0; + common_semi_set_ret(cs, (target_long)arg0 < 0); + break; + case TARGET_SYS_ISTTY: GET_ARG(0); gf = get_guestfd(arg0); if (!gf) { - errno = EBADF; - return set_swi_errno(cs, -1); + goto do_badf; } + guestfd_fns[gf->type].isattyfn(cs, gf); + break; - return guestfd_fns[gf->type].isattyfn(cs, gf); case TARGET_SYS_SEEK: GET_ARG(0); GET_ARG(1); gf = get_guestfd(arg0); if (!gf) { - errno = EBADF; - return set_swi_errno(cs, -1); + goto do_badf; } + guestfd_fns[gf->type].seekfn(cs, gf, arg1); + break; - return guestfd_fns[gf->type].seekfn(cs, gf, arg1); case TARGET_SYS_FLEN: GET_ARG(0); gf = get_guestfd(arg0); if (!gf) { - errno = EBADF; - return set_swi_errno(cs, -1); + goto do_badf; } + guestfd_fns[gf->type].flenfn(cs, gf); + break; - return guestfd_fns[gf->type].flenfn(cs, gf); case TARGET_SYS_TMPNAM: + { + int len; + char *p; + GET_ARG(0); GET_ARG(1); GET_ARG(2); - if (asprintf(&s, "/tmp/qemu-%x%02x", getpid(), - (int) (arg1 & 0xff)) < 0) { - return -1; - } - ul_ret = (target_ulong) -1; - + len = asprintf(&s, "/tmp/qemu-%x%02x", getpid(), (int)arg1 & 0xff); /* Make sure there's enough space in the buffer */ - if (strlen(s) < arg2) { - char *output = lock_user(VERIFY_WRITE, arg0, arg2, 0); - strcpy(output, s); - unlock_user(output, arg0, arg2); - ul_ret = 0; + if (len < 0 || len >= arg2) { + common_semi_set_ret(cs, -1); + break; } + p = lock_user(VERIFY_WRITE, arg0, len, 0); + if (!p) { + goto do_fault; + } + memcpy(p, s, len + 1); + unlock_user(p, arg0, len); free(s); - return ul_ret; + common_semi_set_ret(cs, 0); + break; + } + case TARGET_SYS_REMOVE: GET_ARG(0); GET_ARG(1); if (use_gdb_syscalls()) { - ret = common_semi_gdb_syscall(cs, common_semi_cb, "unlink,%s", - arg0, (int)arg1 + 1); - } else { - s = lock_user_string(arg0); - if (!s) { - errno = EFAULT; - return set_swi_errno(cs, -1); - } - ret = set_swi_errno(cs, remove(s)); - unlock_user(s, arg0, 0); + gdb_do_syscall(common_semi_cb, "unlink,%s", + arg0, (int)arg1 + 1); + break; + } + s = lock_user_string(arg0); + if (!s) { + goto do_fault; } - return ret; + ret = remove(s); + unlock_user(s, arg0, 0); + common_semi_cb(cs, ret, ret ? errno : 0); + break; + case TARGET_SYS_RENAME: GET_ARG(0); GET_ARG(1); GET_ARG(2); GET_ARG(3); if (use_gdb_syscalls()) { - return common_semi_gdb_syscall(cs, common_semi_cb, "rename,%s,%s", - arg0, (int)arg1 + 1, arg2, - (int)arg3 + 1); + gdb_do_syscall(common_semi_cb, "rename,%s,%s", + arg0, (int)arg1 + 1, arg2, (int)arg3 + 1); } else { char *s2; + s = lock_user_string(arg0); - s2 = lock_user_string(arg2); - if (!s || !s2) { - errno = EFAULT; - ret = set_swi_errno(cs, -1); - } else { - ret = set_swi_errno(cs, rename(s, s2)); + if (!s) { + goto do_fault; } - if (s2) - unlock_user(s2, arg2, 0); - if (s) + s2 = lock_user_string(arg2); + if (!s2) { unlock_user(s, arg0, 0); - return ret; + goto do_fault; + } + ret = rename(s, s2); + unlock_user(s2, arg2, 0); + unlock_user(s, arg0, 0); + common_semi_cb(cs, ret, ret ? errno : 0); } + break; + case TARGET_SYS_CLOCK: - return clock() / (CLOCKS_PER_SEC / 100); + common_semi_set_ret(cs, clock() / (CLOCKS_PER_SEC / 100)); + break; + case TARGET_SYS_TIME: - return set_swi_errno(cs, time(NULL)); + ul_ret = time(NULL); + common_semi_cb(cs, ul_ret, ul_ret == -1 ? errno : 0); + break; + case TARGET_SYS_SYSTEM: GET_ARG(0); GET_ARG(1); if (use_gdb_syscalls()) { - return common_semi_gdb_syscall(cs, common_semi_cb, "system,%s", - arg0, (int)arg1 + 1); - } else { - s = lock_user_string(arg0); - if (!s) { - errno = EFAULT; - return set_swi_errno(cs, -1); - } - ret = set_swi_errno(cs, system(s)); - unlock_user(s, arg0, 0); - return ret; + gdb_do_syscall(common_semi_cb, "system,%s", arg0, (int)arg1 + 1); + break; } + s = lock_user_string(arg0); + if (!s) { + goto do_fault; + } + ret = system(s); + unlock_user(s, arg0, 0); + common_semi_cb(cs, ret, ret == -1 ? errno : 0); + break; + case TARGET_SYS_ERRNO: - return get_swi_errno(cs); + common_semi_set_ret(cs, get_swi_errno(cs)); + break; + case TARGET_SYS_GET_CMDLINE: { /* Build a command-line from the original argv. @@ -991,22 +948,20 @@ target_ulong do_common_semihosting(CPUState *cs) if (output_size > input_size) { /* Not enough space to store command-line arguments. */ - errno = E2BIG; - return set_swi_errno(cs, -1); + common_semi_cb(cs, -1, E2BIG); + break; } /* Adjust the command-line length. */ if (SET_ARG(1, output_size - 1)) { /* Couldn't write back to argument block */ - errno = EFAULT; - return set_swi_errno(cs, -1); + goto do_fault; } /* Lock the buffer on the ARM side. */ output_buffer = lock_user(VERIFY_WRITE, arg0, output_size, 0); if (!output_buffer) { - errno = EFAULT; - return set_swi_errno(cs, -1); + goto do_fault; } /* Copy the command-line arguments. */ @@ -1021,9 +976,8 @@ target_ulong do_common_semihosting(CPUState *cs) if (copy_from_user(output_buffer, ts->info->arg_strings, output_size)) { - errno = EFAULT; - status = set_swi_errno(cs, -1); - goto out; + unlock_user(output_buffer, arg0, 0); + goto do_fault; } /* Separate arguments by white spaces. */ @@ -1036,9 +990,10 @@ target_ulong do_common_semihosting(CPUState *cs) #endif /* Unlock the buffer on the ARM side. */ unlock_user(output_buffer, arg0, output_size); - - return status; + common_semi_cb(cs, status, 0); } + break; + case TARGET_SYS_HEAPINFO: { target_ulong retvals[4]; @@ -1095,12 +1050,13 @@ target_ulong do_common_semihosting(CPUState *cs) if (fail) { /* Couldn't write back to argument block */ - errno = EFAULT; - return set_swi_errno(cs, -1); + goto do_fault; } } - return 0; + common_semi_set_ret(cs, 0); } + break; + case TARGET_SYS_EXIT: case TARGET_SYS_EXIT_EXTENDED: if (common_semi_sys_exit_extended(cs, nr)) { @@ -1130,6 +1086,7 @@ target_ulong do_common_semihosting(CPUState *cs) } gdb_exit(ret); exit(ret); + case TARGET_SYS_ELAPSED: elapsed = get_clock() - clock_start; if (sizeof(target_ulong) == 8) { @@ -1138,10 +1095,14 @@ target_ulong do_common_semihosting(CPUState *cs) SET_ARG(0, (uint32_t) elapsed); SET_ARG(1, (uint32_t) (elapsed >> 32)); } - return 0; + common_semi_set_ret(cs, 0); + break; + case TARGET_SYS_TICKFREQ: /* qemu always uses nsec */ - return 1000000000; + common_semi_set_ret(cs, 1000000000); + break; + case TARGET_SYS_SYNCCACHE: /* * Clean the D-cache and invalidate the I-cache for the specified @@ -1150,16 +1111,24 @@ target_ulong do_common_semihosting(CPUState *cs) */ #ifdef TARGET_ARM if (is_a64(cs->env_ptr)) { - return 0; + common_semi_set_ret(cs, 0); + break; } #endif #ifdef TARGET_RISCV - return 0; + common_semi_set_ret(cs, 0); #endif /* fall through -- invalid for A32/T32 */ default: fprintf(stderr, "qemu: Unsupported SemiHosting SWI 0x%02x\n", nr); cpu_dump_state(cs, stderr, 0); abort(); + + do_badf: + common_semi_cb(cs, -1, EBADF); + break; + do_fault: + common_semi_cb(cs, -1, EFAULT); + break; } } diff --git a/semihosting/common-semi.h b/semihosting/common-semi.h index 0bfab1c669b6b..0a91db7c4149a 100644 --- a/semihosting/common-semi.h +++ b/semihosting/common-semi.h @@ -34,6 +34,6 @@ #ifndef COMMON_SEMI_H #define COMMON_SEMI_H -target_ulong do_common_semihosting(CPUState *cs); +void do_common_semihosting(CPUState *cs); #endif /* COMMON_SEMI_H */ diff --git a/target/arm/helper.c b/target/arm/helper.c index 7d14650615c35..12ebccb38de56 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -10209,13 +10209,13 @@ static void handle_semihosting(CPUState *cs) qemu_log_mask(CPU_LOG_INT, "...handling as semihosting call 0x%" PRIx64 "\n", env->xregs[0]); - env->xregs[0] = do_common_semihosting(cs); + do_common_semihosting(cs); env->pc += 4; } else { qemu_log_mask(CPU_LOG_INT, "...handling as semihosting call 0x%x\n", env->regs[0]); - env->regs[0] = do_common_semihosting(cs); + do_common_semihosting(cs); env->regs[15] += env->thumb ? 2 : 4; } } diff --git a/target/arm/m_helper.c b/target/arm/m_helper.c index b7a0fe0114123..4bf3b73a16f76 100644 --- a/target/arm/m_helper.c +++ b/target/arm/m_helper.c @@ -2373,7 +2373,7 @@ void arm_v7m_cpu_do_interrupt(CPUState *cs) "...handling as semihosting call 0x%x\n", env->regs[0]); #ifdef CONFIG_TCG - env->regs[0] = do_common_semihosting(cs); + do_common_semihosting(cs); #else g_assert_not_reached(); #endif diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c index 1c60fb2e8057a..0a79ad5069249 100644 --- a/target/riscv/cpu_helper.c +++ b/target/riscv/cpu_helper.c @@ -1345,7 +1345,7 @@ void riscv_cpu_do_interrupt(CPUState *cs) if (cause == RISCV_EXCP_SEMIHOST) { if (env->priv >= PRV_S) { - env->gpr[xA0] = do_common_semihosting(cs); + do_common_semihosting(cs); env->pc += 4; return; } From 29bf17cb7cd36bc28e08318105f9448d52a1f7de Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Thu, 28 Apr 2022 01:14:02 -0700 Subject: [PATCH 20/69] semihosting: Move common-semi.h to include/semihosting/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This header is not private to the top-level semihosting directory, so place it in the public include directory. Reviewed-by: Alex Bennée Reviewed-by: Peter Maydell Signed-off-by: Richard Henderson (cherry picked from commit bb3b8821a326d18294ce31bdd350bb27a139940e) --- {semihosting => include/semihosting}/common-semi.h | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {semihosting => include/semihosting}/common-semi.h (100%) diff --git a/semihosting/common-semi.h b/include/semihosting/common-semi.h similarity index 100% rename from semihosting/common-semi.h rename to include/semihosting/common-semi.h From ee229242fe5940f05acb68e46c0a6dba609be368 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Tue, 7 Jun 2022 10:50:43 -0700 Subject: [PATCH 21/69] semihosting: Remove GDB_O_BINARY MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The value is zero, and gdb always opens files in binary mode. Reviewed-by: Alex Bennée Signed-off-by: Richard Henderson (cherry picked from commit a1a2a3e609f619857fcdd8302fe1e832490216a7) --- semihosting/arm-compat-semi.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index 246089224d7ba..abf543ce91084 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -88,17 +88,17 @@ static int gdb_open_modeflags[12] = { GDB_O_RDONLY, - GDB_O_RDONLY | GDB_O_BINARY, + GDB_O_RDONLY, + GDB_O_RDWR, GDB_O_RDWR, - GDB_O_RDWR | GDB_O_BINARY, GDB_O_WRONLY | GDB_O_CREAT | GDB_O_TRUNC, - GDB_O_WRONLY | GDB_O_CREAT | GDB_O_TRUNC | GDB_O_BINARY, + GDB_O_WRONLY | GDB_O_CREAT | GDB_O_TRUNC, GDB_O_RDWR | GDB_O_CREAT | GDB_O_TRUNC, - GDB_O_RDWR | GDB_O_CREAT | GDB_O_TRUNC | GDB_O_BINARY, + GDB_O_RDWR | GDB_O_CREAT | GDB_O_TRUNC, + GDB_O_WRONLY | GDB_O_CREAT | GDB_O_APPEND, GDB_O_WRONLY | GDB_O_CREAT | GDB_O_APPEND, - GDB_O_WRONLY | GDB_O_CREAT | GDB_O_APPEND | GDB_O_BINARY, GDB_O_RDWR | GDB_O_CREAT | GDB_O_APPEND, - GDB_O_RDWR | GDB_O_CREAT | GDB_O_APPEND | GDB_O_BINARY + GDB_O_RDWR | GDB_O_CREAT | GDB_O_APPEND, }; static int open_modeflags[12] = { From 43bf93a363841b7c6f22254786c5145e0e6552f1 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Fri, 29 Apr 2022 14:55:21 -0700 Subject: [PATCH 22/69] semihosting: Use struct gdb_stat in common_semi_flen_cb Load the entire 64-bit size value. While we're at it, use offsetof instead of an integer constant. Reviewed-by: Peter Maydell Signed-off-by: Richard Henderson (cherry picked from commit cd7f29e335a88c2803bfb42ee3cef60417c43274) --- semihosting/arm-compat-semi.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index abf543ce91084..a9e488886a34b 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -325,14 +325,12 @@ static void common_semi_flen_cb(CPUState *cs, target_ulong ret, target_ulong err) { if (!err) { - /* - * The size is always stored in big-endian order, extract - * the value. We assume the size always fit in 32 bits. - */ - uint32_t size; - cpu_memory_rw_debug(cs, common_semi_flen_buf(cs) + 32, - (uint8_t *)&size, 4, 0); - ret = be32_to_cpu(size); + /* The size is always stored in big-endian order, extract the value. */ + uint64_t size; + cpu_memory_rw_debug(cs, common_semi_flen_buf(cs) + + offsetof(struct gdb_stat, gdb_st_size), + &size, 8, 0); + ret = be64_to_cpu(size); } common_semi_cb(cs, ret, err); } From 630335147fd37fa4985c2358656f90bb7a66a421 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Thu, 28 Apr 2022 11:31:04 -0700 Subject: [PATCH 23/69] semihosting: Split is_64bit_semihosting per target We already have some larger ifdef blocks for ARM and RISCV; split the function into multiple implementations per arch. Reviewed-by: Peter Maydell Reviewed-by: Alistair Francis Signed-off-by: Richard Henderson (cherry picked from commit ef9c5ea85d83459361449259418726a5d4d0a751) --- semihosting/arm-compat-semi.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index a9e488886a34b..d2ce21407884e 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -213,6 +213,10 @@ common_semi_sys_exit_extended(CPUState *cs, int nr) return (nr == TARGET_SYS_EXIT_EXTENDED || is_a64(cs->env_ptr)); } +static inline bool is_64bit_semihosting(CPUArchState *env) +{ + return is_a64(env); +} #endif /* TARGET_ARM */ #ifdef TARGET_RISCV @@ -238,6 +242,10 @@ common_semi_sys_exit_extended(CPUState *cs, int nr) return (nr == TARGET_SYS_EXIT_EXTENDED || sizeof(target_ulong) == 8); } +static inline bool is_64bit_semihosting(CPUArchState *env) +{ + return riscv_cpu_mxl(env) != MXL_RV32; +} #endif /* @@ -587,17 +595,6 @@ static const GuestFDFunctions guestfd_fns[] = { * call if the memory read fails. Eventually we could use a generic * CPUState helper function here. */ -static inline bool is_64bit_semihosting(CPUArchState *env) -{ -#if defined(TARGET_ARM) - return is_a64(env); -#elif defined(TARGET_RISCV) - return riscv_cpu_mxl(env) != MXL_RV32; -#else -#error un-handled architecture -#endif -} - #define GET_ARG(n) do { \ if (is_64bit_semihosting(env)) { \ From ecfc470e9de000b63d80a9650e0759a08c357e15 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Fri, 29 Apr 2022 13:59:52 -0700 Subject: [PATCH 24/69] semihosting: Split common_semi_flen_buf per target We already have some larger ifdef blocks for ARM and RISCV; split out common_semi_stack_bottom per target. Reviewed-by: Peter Maydell Reviewed-by: Alistair Francis Signed-off-by: Richard Henderson (cherry picked from commit 3c820ddc1b92f17fea85fdaed2928feaa9c238d7) --- semihosting/arm-compat-semi.c | 44 +++++++++++++++++------------------ 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index d2ce21407884e..7550dce622fab 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -217,6 +217,13 @@ static inline bool is_64bit_semihosting(CPUArchState *env) { return is_a64(env); } + +static inline target_ulong common_semi_stack_bottom(CPUState *cs) +{ + ARMCPU *cpu = ARM_CPU(cs); + CPUARMState *env = &cpu->env; + return is_a64(env) ? env->xregs[31] : env->regs[13]; +} #endif /* TARGET_ARM */ #ifdef TARGET_RISCV @@ -246,6 +253,13 @@ static inline bool is_64bit_semihosting(CPUArchState *env) { return riscv_cpu_mxl(env) != MXL_RV32; } + +static inline target_ulong common_semi_stack_bottom(CPUState *cs) +{ + RISCVCPU *cpu = RISCV_CPU(cs); + CPURISCVState *env = &cpu->env; + return env->gpr[xSP]; +} #endif /* @@ -301,31 +315,15 @@ static void common_semi_cb(CPUState *cs, target_ulong ret, target_ulong err) common_semi_set_ret(cs, ret); } +/* + * Return an address in target memory of 64 bytes where the remote + * gdb should write its stat struct. (The format of this structure + * is defined by GDB's remote protocol and is not target-specific.) + * We put this on the guest's stack just below SP. + */ static target_ulong common_semi_flen_buf(CPUState *cs) { - target_ulong sp; -#ifdef TARGET_ARM - /* Return an address in target memory of 64 bytes where the remote - * gdb should write its stat struct. (The format of this structure - * is defined by GDB's remote protocol and is not target-specific.) - * We put this on the guest's stack just below SP. - */ - ARMCPU *cpu = ARM_CPU(cs); - CPUARMState *env = &cpu->env; - - if (is_a64(env)) { - sp = env->xregs[31]; - } else { - sp = env->regs[13]; - } -#endif -#ifdef TARGET_RISCV - RISCVCPU *cpu = RISCV_CPU(cs); - CPURISCVState *env = &cpu->env; - - sp = env->gpr[xSP]; -#endif - + target_ulong sp = common_semi_stack_bottom(cs); return sp - 64; } From 3684c3fcec218fa3f45b70bc791964fc60fe21f8 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Fri, 29 Apr 2022 14:16:01 -0700 Subject: [PATCH 25/69] semihosting: Split out common_semi_has_synccache We already have some larger ifdef blocks for ARM and RISCV; split out a boolean test for SYS_SYNCCACHE. Reviewed-by: Alistair Francis Signed-off-by: Richard Henderson (cherry picked from commit a1df4bab432fc62a0d14abc192ce063c432afd2e) --- semihosting/arm-compat-semi.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index 7550dce622fab..50f40a2a1a7a7 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -224,6 +224,12 @@ static inline target_ulong common_semi_stack_bottom(CPUState *cs) CPUARMState *env = &cpu->env; return is_a64(env) ? env->xregs[31] : env->regs[13]; } + +static inline bool common_semi_has_synccache(CPUArchState *env) +{ + /* Ok for A64, invalid for A32/T32. */ + return is_a64(env); +} #endif /* TARGET_ARM */ #ifdef TARGET_RISCV @@ -260,6 +266,11 @@ static inline target_ulong common_semi_stack_bottom(CPUState *cs) CPURISCVState *env = &cpu->env; return env->gpr[xSP]; } + +static inline bool common_semi_has_synccache(CPUArchState *env) +{ + return true; +} #endif /* @@ -1102,16 +1113,11 @@ void do_common_semihosting(CPUState *cs) * virtual address range. This is a nop for us since we don't * implement caches. This is only present on A64. */ -#ifdef TARGET_ARM - if (is_a64(cs->env_ptr)) { + if (common_semi_has_synccache(env)) { common_semi_set_ret(cs, 0); break; } -#endif -#ifdef TARGET_RISCV - common_semi_set_ret(cs, 0); -#endif - /* fall through -- invalid for A32/T32 */ + /* fall through */ default: fprintf(stderr, "qemu: Unsupported SemiHosting SWI 0x%02x\n", nr); cpu_dump_state(cs, stderr, 0); From 76921474dcd5df86c8b1da51e96d0f1ac34d496a Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Tue, 7 Jun 2022 10:31:22 -0700 Subject: [PATCH 26/69] semihosting: Split out common-semi-target.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the ARM and RISCV specific helpers into their own header file. Reviewed-by: Alex Bennée Reviewed-by: Luc Michel Signed-off-by: Richard Henderson (cherry picked from commit 1b3b7693b7c3f94bd66a8425201a4bb7de5388e4) --- semihosting/arm-compat-semi.c | 94 +------------------------------ target/arm/common-semi-target.h | 62 ++++++++++++++++++++ target/riscv/common-semi-target.h | 50 ++++++++++++++++ 3 files changed, 113 insertions(+), 93 deletions(-) create mode 100644 target/arm/common-semi-target.h create mode 100644 target/riscv/common-semi-target.h diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index 50f40a2a1a7a7..5e442e549dfa4 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -46,9 +46,6 @@ #else #include "qemu/cutils.h" #include "hw/loader.h" -#ifdef TARGET_ARM -#include "hw/arm/boot.h" -#endif #include "hw/boards.h" #endif @@ -182,96 +179,7 @@ static LayoutInfo common_semi_find_bases(CPUState *cs) #endif -#ifdef TARGET_ARM -static inline target_ulong -common_semi_arg(CPUState *cs, int argno) -{ - ARMCPU *cpu = ARM_CPU(cs); - CPUARMState *env = &cpu->env; - if (is_a64(env)) { - return env->xregs[argno]; - } else { - return env->regs[argno]; - } -} - -static inline void -common_semi_set_ret(CPUState *cs, target_ulong ret) -{ - ARMCPU *cpu = ARM_CPU(cs); - CPUARMState *env = &cpu->env; - if (is_a64(env)) { - env->xregs[0] = ret; - } else { - env->regs[0] = ret; - } -} - -static inline bool -common_semi_sys_exit_extended(CPUState *cs, int nr) -{ - return (nr == TARGET_SYS_EXIT_EXTENDED || is_a64(cs->env_ptr)); -} - -static inline bool is_64bit_semihosting(CPUArchState *env) -{ - return is_a64(env); -} - -static inline target_ulong common_semi_stack_bottom(CPUState *cs) -{ - ARMCPU *cpu = ARM_CPU(cs); - CPUARMState *env = &cpu->env; - return is_a64(env) ? env->xregs[31] : env->regs[13]; -} - -static inline bool common_semi_has_synccache(CPUArchState *env) -{ - /* Ok for A64, invalid for A32/T32. */ - return is_a64(env); -} -#endif /* TARGET_ARM */ - -#ifdef TARGET_RISCV -static inline target_ulong -common_semi_arg(CPUState *cs, int argno) -{ - RISCVCPU *cpu = RISCV_CPU(cs); - CPURISCVState *env = &cpu->env; - return env->gpr[xA0 + argno]; -} - -static inline void -common_semi_set_ret(CPUState *cs, target_ulong ret) -{ - RISCVCPU *cpu = RISCV_CPU(cs); - CPURISCVState *env = &cpu->env; - env->gpr[xA0] = ret; -} - -static inline bool -common_semi_sys_exit_extended(CPUState *cs, int nr) -{ - return (nr == TARGET_SYS_EXIT_EXTENDED || sizeof(target_ulong) == 8); -} - -static inline bool is_64bit_semihosting(CPUArchState *env) -{ - return riscv_cpu_mxl(env) != MXL_RV32; -} - -static inline target_ulong common_semi_stack_bottom(CPUState *cs) -{ - RISCVCPU *cpu = RISCV_CPU(cs); - CPURISCVState *env = &cpu->env; - return env->gpr[xSP]; -} - -static inline bool common_semi_has_synccache(CPUArchState *env) -{ - return true; -} -#endif +#include "common-semi-target.h" /* * The semihosting API has no concept of its errno being thread-safe, diff --git a/target/arm/common-semi-target.h b/target/arm/common-semi-target.h new file mode 100644 index 0000000000000..629d75ca5a75a --- /dev/null +++ b/target/arm/common-semi-target.h @@ -0,0 +1,62 @@ +/* + * Target-specific parts of semihosting/arm-compat-semi.c. + * + * Copyright (c) 2005, 2007 CodeSourcery. + * Copyright (c) 2019, 2022 Linaro + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef TARGET_ARM_COMMON_SEMI_TARGET_H +#define TARGET_ARM_COMMON_SEMI_TARGET_H + +#ifndef CONFIG_USER_ONLY +#include "hw/arm/boot.h" +#endif + +static inline target_ulong common_semi_arg(CPUState *cs, int argno) +{ + ARMCPU *cpu = ARM_CPU(cs); + CPUARMState *env = &cpu->env; + if (is_a64(env)) { + return env->xregs[argno]; + } else { + return env->regs[argno]; + } +} + +static inline void common_semi_set_ret(CPUState *cs, target_ulong ret) +{ + ARMCPU *cpu = ARM_CPU(cs); + CPUARMState *env = &cpu->env; + if (is_a64(env)) { + env->xregs[0] = ret; + } else { + env->regs[0] = ret; + } +} + +static inline bool common_semi_sys_exit_extended(CPUState *cs, int nr) +{ + return (nr == TARGET_SYS_EXIT_EXTENDED || is_a64(cs->env_ptr)); +} + +static inline bool is_64bit_semihosting(CPUArchState *env) +{ + return is_a64(env); +} + +static inline target_ulong common_semi_stack_bottom(CPUState *cs) +{ + ARMCPU *cpu = ARM_CPU(cs); + CPUARMState *env = &cpu->env; + return is_a64(env) ? env->xregs[31] : env->regs[13]; +} + +static inline bool common_semi_has_synccache(CPUArchState *env) +{ + /* Ok for A64, invalid for A32/T32 */ + return is_a64(env); +} + +#endif diff --git a/target/riscv/common-semi-target.h b/target/riscv/common-semi-target.h new file mode 100644 index 0000000000000..7c8a59e0cc3cd --- /dev/null +++ b/target/riscv/common-semi-target.h @@ -0,0 +1,50 @@ +/* + * Target-specific parts of semihosting/arm-compat-semi.c. + * + * Copyright (c) 2005, 2007 CodeSourcery. + * Copyright (c) 2019, 2022 Linaro + * Copyright © 2020 by Keith Packard + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef TARGET_RISCV_COMMON_SEMI_TARGET_H +#define TARGET_RISCV_COMMON_SEMI_TARGET_H + +static inline target_ulong common_semi_arg(CPUState *cs, int argno) +{ + RISCVCPU *cpu = RISCV_CPU(cs); + CPURISCVState *env = &cpu->env; + return env->gpr[xA0 + argno]; +} + +static inline void common_semi_set_ret(CPUState *cs, target_ulong ret) +{ + RISCVCPU *cpu = RISCV_CPU(cs); + CPURISCVState *env = &cpu->env; + env->gpr[xA0] = ret; +} + +static inline bool common_semi_sys_exit_extended(CPUState *cs, int nr) +{ + return (nr == TARGET_SYS_EXIT_EXTENDED || sizeof(target_ulong) == 8); +} + +static inline bool is_64bit_semihosting(CPUArchState *env) +{ + return riscv_cpu_mxl(env) != MXL_RV32; +} + +static inline target_ulong common_semi_stack_bottom(CPUState *cs) +{ + RISCVCPU *cpu = RISCV_CPU(cs); + CPURISCVState *env = &cpu->env; + return env->gpr[xSP]; +} + +static inline bool common_semi_has_synccache(CPUArchState *env) +{ + return true; +} + +#endif From 4e212d0ae5f917d5b76c0b78f8eb93d47ff802a4 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Fri, 29 Apr 2022 14:18:40 -0700 Subject: [PATCH 27/69] semihosting: Use env more often in do_common_semihosting We've already loaded cs->env_ptr into a local variable; use it. Since env is unconditionally used, we don't need a dummy use. Reviewed-by: Luc Michel Signed-off-by: Richard Henderson (cherry picked from commit 189878ae237d443571250f76655161d91c018889) --- semihosting/arm-compat-semi.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index 5e442e549dfa4..adb4e5b581080 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -553,7 +553,6 @@ void do_common_semihosting(CPUState *cs) GuestFD *gf; int64_t elapsed; - (void) env; /* Used implicitly by arm lock_user macro */ nr = common_semi_arg(cs, 0) & 0xffffffffU; args = common_semi_arg(cs, 1); @@ -636,12 +635,12 @@ void do_common_semihosting(CPUState *cs) break; case TARGET_SYS_WRITEC: - qemu_semihosting_console_outc(cs->env_ptr, args); + qemu_semihosting_console_outc(env, args); common_semi_set_ret(cs, 0xdeadbeef); break; case TARGET_SYS_WRITE0: - ret = qemu_semihosting_console_outs(cs->env_ptr, args); + ret = qemu_semihosting_console_outs(env, args); common_semi_set_ret(cs, ret); break; @@ -672,7 +671,7 @@ void do_common_semihosting(CPUState *cs) break; case TARGET_SYS_READC: - ret = qemu_semihosting_console_inc(cs->env_ptr); + ret = qemu_semihosting_console_inc(env); common_semi_set_ret(cs, ret); break; From ea78f823134c2d44b97c4402ad7fd1300a379e67 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Thu, 28 Apr 2022 11:40:14 -0700 Subject: [PATCH 28/69] semihosting: Move GET_ARG/SET_ARG earlier in the file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moving this to be useful for another function besides do_common_semihosting. Reviewed-by: Alex Bennée Reviewed-by: Peter Maydell Signed-off-by: Richard Henderson (cherry picked from commit 3753b00e5747068882c7f0302dcf9b87402993ab) --- semihosting/arm-compat-semi.c | 48 +++++++++++++++++------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index adb4e5b581080..72a1350512103 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -181,6 +181,30 @@ static LayoutInfo common_semi_find_bases(CPUState *cs) #include "common-semi-target.h" +/* + * Read the input value from the argument block; fail the semihosting + * call if the memory read fails. Eventually we could use a generic + * CPUState helper function here. + */ + +#define GET_ARG(n) do { \ + if (is_64bit_semihosting(env)) { \ + if (get_user_u64(arg ## n, args + (n) * 8)) { \ + goto do_fault; \ + } \ + } else { \ + if (get_user_u32(arg ## n, args + (n) * 4)) { \ + goto do_fault; \ + } \ + } \ +} while (0) + +#define SET_ARG(n, val) \ + (is_64bit_semihosting(env) ? \ + put_user_u64(val, args + (n) * 8) : \ + put_user_u32(val, args + (n) * 4)) + + /* * The semihosting API has no concept of its errno being thread-safe, * as the API design predates SMP CPUs and was intended as a simple @@ -507,30 +531,6 @@ static const GuestFDFunctions guestfd_fns[] = { }, }; -/* - * Read the input value from the argument block; fail the semihosting - * call if the memory read fails. Eventually we could use a generic - * CPUState helper function here. - */ - -#define GET_ARG(n) do { \ - if (is_64bit_semihosting(env)) { \ - if (get_user_u64(arg ## n, args + (n) * 8)) { \ - goto do_fault; \ - } \ - } else { \ - if (get_user_u32(arg ## n, args + (n) * 4)) { \ - goto do_fault; \ - } \ - } \ -} while (0) - -#define SET_ARG(n, val) \ - (is_64bit_semihosting(env) ? \ - put_user_u64(val, args + (n) * 8) : \ - put_user_u32(val, args + (n) * 4)) - - /* * Do a semihosting call. * From f3fffa6536281cabd4db37cec464f00dfdd42935 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Thu, 28 Apr 2022 01:44:28 -0700 Subject: [PATCH 29/69] semihosting: Split out semihost_sys_open Split out the non-ARM specific portions of SYS_OPEN to a reusable function. This handles gdb and host file i/o. Add helpers to validate the length of the filename string. Prepare for usage by other semihosting by allowing the filename length parameter to be 0, and calling strlen. Reviewed-by: Luc Michel Signed-off-by: Richard Henderson (cherry picked from commit 5b3f39cb04fda32226e84502f858bab06d83e5c1) --- include/semihosting/syscalls.h | 25 ++++++ semihosting/arm-compat-semi.c | 51 ++--------- semihosting/guestfd.c | 5 ++ semihosting/meson.build | 1 + semihosting/syscalls.c | 156 +++++++++++++++++++++++++++++++++ 5 files changed, 193 insertions(+), 45 deletions(-) create mode 100644 include/semihosting/syscalls.h create mode 100644 semihosting/syscalls.c diff --git a/include/semihosting/syscalls.h b/include/semihosting/syscalls.h new file mode 100644 index 0000000000000..991658bf79fee --- /dev/null +++ b/include/semihosting/syscalls.h @@ -0,0 +1,25 @@ +/* + * Syscall implementations for semihosting. + * + * Copyright (c) 2022 Linaro + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef SEMIHOSTING_SYSCALLS_H +#define SEMIHOSTING_SYSCALLS_H + +/* + * Argument loading from the guest is performed by the caller; + * results are returned via the 'complete' callback. + * + * String operands are in address/len pairs. The len argument may be 0 + * (when the semihosting abi does not already provide the length), + * or non-zero (where it should include the terminating zero). + */ + +void semihost_sys_open(CPUState *cs, gdb_syscall_complete_cb complete, + target_ulong fname, target_ulong fname_len, + int gdb_flags, int mode); + +#endif /* SEMIHOSTING_SYSCALLS_H */ diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index 72a1350512103..07960658d828d 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -32,12 +32,13 @@ */ #include "qemu/osdep.h" +#include "qemu/timer.h" +#include "exec/gdbstub.h" #include "semihosting/semihost.h" #include "semihosting/console.h" #include "semihosting/common-semi.h" #include "semihosting/guestfd.h" -#include "qemu/timer.h" -#include "exec/gdbstub.h" +#include "semihosting/syscalls.h" #ifdef CONFIG_USER_ONLY #include "qemu.h" @@ -98,21 +99,6 @@ static int gdb_open_modeflags[12] = { GDB_O_RDWR | GDB_O_CREAT | GDB_O_APPEND, }; -static int open_modeflags[12] = { - O_RDONLY, - O_RDONLY | O_BINARY, - O_RDWR, - O_RDWR | O_BINARY, - O_WRONLY | O_CREAT | O_TRUNC, - O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, - O_RDWR | O_CREAT | O_TRUNC, - O_RDWR | O_CREAT | O_TRUNC | O_BINARY, - O_WRONLY | O_CREAT | O_APPEND, - O_WRONLY | O_CREAT | O_APPEND | O_BINARY, - O_RDWR | O_CREAT | O_APPEND, - O_RDWR | O_CREAT | O_APPEND | O_BINARY -}; - #ifndef CONFIG_USER_ONLY /** @@ -284,20 +270,6 @@ common_semi_flen_cb(CPUState *cs, target_ulong ret, target_ulong err) common_semi_cb(cs, ret, err); } -static int common_semi_open_guestfd; - -static void -common_semi_open_cb(CPUState *cs, target_ulong ret, target_ulong err) -{ - if (err) { - dealloc_guestfd(common_semi_open_guestfd); - } else { - associate_guestfd(common_semi_open_guestfd, ret); - ret = common_semi_open_guestfd; - } - common_semi_cb(cs, ret, err); -} - /* * Types for functions implementing various semihosting calls * for specific types of guest file descriptor. These must all @@ -601,22 +573,11 @@ void do_common_semihosting(CPUState *cs) staticfile_guestfd(ret, featurefile_data, sizeof(featurefile_data)); } - } else if (use_gdb_syscalls()) { + } else { unlock_user(s, arg0, 0); - common_semi_open_guestfd = alloc_guestfd(); - gdb_do_syscall(common_semi_open_cb, - "open,%s,%x,1a4", arg0, (int)arg2 + 1, - gdb_open_modeflags[arg1]); + semihost_sys_open(cs, common_semi_cb, arg0, arg2 + 1, + gdb_open_modeflags[arg1], 0644); break; - } else { - hostfd = open(s, open_modeflags[arg1], 0644); - if (hostfd < 0) { - ret = -1; - err = errno; - } else { - ret = alloc_guestfd(); - associate_guestfd(ret, hostfd); - } } unlock_user(s, arg0, 0); common_semi_cb(cs, ret, err); diff --git a/semihosting/guestfd.c b/semihosting/guestfd.c index b6405f566377d..7ac2e147a845c 100644 --- a/semihosting/guestfd.c +++ b/semihosting/guestfd.c @@ -11,6 +11,11 @@ #include "qemu/osdep.h" #include "exec/gdbstub.h" #include "semihosting/guestfd.h" +#ifdef CONFIG_USER_ONLY +#include "qemu.h" +#else +#include "semihosting/softmmu-uaccess.h" +#endif static GArray *guestfd_array; diff --git a/semihosting/meson.build b/semihosting/meson.build index d2c1c37bfdcfd..8057db5494a8b 100644 --- a/semihosting/meson.build +++ b/semihosting/meson.build @@ -1,5 +1,6 @@ specific_ss.add(when: 'CONFIG_SEMIHOSTING', if_true: files( 'guestfd.c', + 'syscalls.c', )) specific_ss.add(when: ['CONFIG_SEMIHOSTING', 'CONFIG_SOFTMMU'], if_true: files( diff --git a/semihosting/syscalls.c b/semihosting/syscalls.c new file mode 100644 index 0000000000000..9f9d19a59a76a --- /dev/null +++ b/semihosting/syscalls.c @@ -0,0 +1,156 @@ +/* + * Syscall implementations for semihosting. + * + * Copyright (c) 2022 Linaro + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "exec/gdbstub.h" +#include "semihosting/guestfd.h" +#include "semihosting/syscalls.h" +#ifdef CONFIG_USER_ONLY +#include "qemu.h" +#else +#include "semihosting/softmmu-uaccess.h" +#endif + + +/* + * Validate or compute the length of the string (including terminator). + */ +static int validate_strlen(CPUState *cs, target_ulong str, target_ulong tlen) +{ + CPUArchState *env G_GNUC_UNUSED = cs->env_ptr; + char c; + + if (tlen == 0) { + ssize_t slen = target_strlen(str); + + if (slen < 0) { + return -EFAULT; + } + if (slen >= INT32_MAX) { + return -ENAMETOOLONG; + } + return slen + 1; + } + if (tlen > INT32_MAX) { + return -ENAMETOOLONG; + } + if (get_user_u8(c, str + tlen - 1)) { + return -EFAULT; + } + if (c != 0) { + return -EINVAL; + } + return tlen; +} + +static int validate_lock_user_string(char **pstr, CPUState *cs, + target_ulong tstr, target_ulong tlen) +{ + int ret = validate_strlen(cs, tstr, tlen); + CPUArchState *env G_GNUC_UNUSED = cs->env_ptr; + char *str = NULL; + + if (ret > 0) { + str = lock_user(VERIFY_READ, tstr, ret, true); + ret = str ? 0 : -EFAULT; + } + *pstr = str; + return ret; +} + +/* + * GDB semihosting syscall implementations. + */ + +static gdb_syscall_complete_cb gdb_open_complete; + +static void gdb_open_cb(CPUState *cs, target_ulong ret, target_ulong err) +{ + if (!err) { + int guestfd = alloc_guestfd(); + associate_guestfd(guestfd, ret); + ret = guestfd; + } + gdb_open_complete(cs, ret, err); +} + +static void gdb_open(CPUState *cs, gdb_syscall_complete_cb complete, + target_ulong fname, target_ulong fname_len, + int gdb_flags, int mode) +{ + int len = validate_strlen(cs, fname, fname_len); + if (len < 0) { + complete(cs, -1, -len); + return; + } + + gdb_open_complete = complete; + gdb_do_syscall(gdb_open_cb, "open,%s,%x,%x", + fname, len, (target_ulong)gdb_flags, (target_ulong)mode); +} + +/* + * Host semihosting syscall implementations. + */ + +static void host_open(CPUState *cs, gdb_syscall_complete_cb complete, + target_ulong fname, target_ulong fname_len, + int gdb_flags, int mode) +{ + CPUArchState *env G_GNUC_UNUSED = cs->env_ptr; + char *p; + int ret, host_flags; + + ret = validate_lock_user_string(&p, cs, fname, fname_len); + if (ret < 0) { + complete(cs, -1, -ret); + return; + } + + if (gdb_flags & GDB_O_WRONLY) { + host_flags = O_WRONLY; + } else if (gdb_flags & GDB_O_RDWR) { + host_flags = O_RDWR; + } else { + host_flags = O_RDONLY; + } + if (gdb_flags & GDB_O_CREAT) { + host_flags |= O_CREAT; + } + if (gdb_flags & GDB_O_TRUNC) { + host_flags |= O_TRUNC; + } + if (gdb_flags & GDB_O_EXCL) { + host_flags |= O_EXCL; + } + + ret = open(p, host_flags, mode); + if (ret < 0) { + complete(cs, -1, errno); + } else { + int guestfd = alloc_guestfd(); + associate_guestfd(guestfd, ret); + complete(cs, guestfd, 0); + } + unlock_user(p, fname, 0); +} + +/* + * Syscall entry points. + */ + +void semihost_sys_open(CPUState *cs, gdb_syscall_complete_cb complete, + target_ulong fname, target_ulong fname_len, + int gdb_flags, int mode) +{ + if (use_gdb_syscalls()) { + gdb_open(cs, complete, fname, fname_len, gdb_flags, mode); + } else { + host_open(cs, complete, fname, fname_len, gdb_flags, mode); + } +} From 5d40eb1615cbc1e7e9b1964cd7e470f244c9d7c8 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Thu, 28 Apr 2022 09:22:18 -0700 Subject: [PATCH 30/69] semihosting: Split out semihost_sys_close Split out the non-ARM specific portions of SYS_CLOSE to a reusable function. This handles all GuestFD. Note that gdb_do_syscall %x reads target_ulong, not int. Reviewed-by: Luc Michel Signed-off-by: Richard Henderson (cherry picked from commit 5eadbbfca6232710036ccb9f5cf1481be537c0e7) --- include/semihosting/syscalls.h | 3 +++ semihosting/arm-compat-semi.c | 41 +---------------------------- semihosting/guestfd.c | 7 ++++- semihosting/syscalls.c | 47 ++++++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 41 deletions(-) diff --git a/include/semihosting/syscalls.h b/include/semihosting/syscalls.h index 991658bf79fee..00e718f11d0ea 100644 --- a/include/semihosting/syscalls.h +++ b/include/semihosting/syscalls.h @@ -22,4 +22,7 @@ void semihost_sys_open(CPUState *cs, gdb_syscall_complete_cb complete, target_ulong fname, target_ulong fname_len, int gdb_flags, int mode); +void semihost_sys_close(CPUState *cs, gdb_syscall_complete_cb complete, + int fd); + #endif /* SEMIHOSTING_SYSCALLS_H */ diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index 07960658d828d..0cb3db2a1af4d 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -276,7 +276,6 @@ common_semi_flen_cb(CPUState *cs, target_ulong ret, target_ulong err) * do the work and return the required return value to the guest * via common_semi_cb. */ -typedef void sys_closefn(CPUState *cs, GuestFD *gf); typedef void sys_writefn(CPUState *cs, GuestFD *gf, target_ulong buf, uint32_t len); typedef void sys_readfn(CPUState *cs, GuestFD *gf, @@ -285,23 +284,6 @@ typedef void sys_isattyfn(CPUState *cs, GuestFD *gf); typedef void sys_seekfn(CPUState *cs, GuestFD *gf, target_ulong offset); typedef void sys_flenfn(CPUState *cs, GuestFD *gf); -static void host_closefn(CPUState *cs, GuestFD *gf) -{ - int ret; - /* - * Only close the underlying host fd if it's one we opened on behalf - * of the guest in SYS_OPEN. - */ - if (gf->hostfd == STDIN_FILENO || - gf->hostfd == STDOUT_FILENO || - gf->hostfd == STDERR_FILENO) { - ret = 0; - } else { - ret = close(gf->hostfd); - } - common_semi_cb(cs, ret, ret ? errno : 0); -} - static void host_writefn(CPUState *cs, GuestFD *gf, target_ulong buf, uint32_t len) { @@ -362,11 +344,6 @@ static void host_flenfn(CPUState *cs, GuestFD *gf) } } -static void gdb_closefn(CPUState *cs, GuestFD *gf) -{ - gdb_do_syscall(common_semi_cb, "close,%x", gf->hostfd); -} - static void gdb_writefn(CPUState *cs, GuestFD *gf, target_ulong buf, uint32_t len) { @@ -414,12 +391,6 @@ static const uint8_t featurefile_data[] = { SH_EXT_EXIT_EXTENDED | SH_EXT_STDOUT_STDERR, /* Feature byte 0 */ }; -static void staticfile_closefn(CPUState *cs, GuestFD *gf) -{ - /* Nothing to do */ - common_semi_cb(cs, 0, 0); -} - static void staticfile_writefn(CPUState *cs, GuestFD *gf, target_ulong buf, uint32_t len) { @@ -468,7 +439,6 @@ static void staticfile_flenfn(CPUState *cs, GuestFD *gf) } typedef struct GuestFDFunctions { - sys_closefn *closefn; sys_writefn *writefn; sys_readfn *readfn; sys_isattyfn *isattyfn; @@ -478,7 +448,6 @@ typedef struct GuestFDFunctions { static const GuestFDFunctions guestfd_fns[] = { [GuestFDHost] = { - .closefn = host_closefn, .writefn = host_writefn, .readfn = host_readfn, .isattyfn = host_isattyfn, @@ -486,7 +455,6 @@ static const GuestFDFunctions guestfd_fns[] = { .flenfn = host_flenfn, }, [GuestFDGDB] = { - .closefn = gdb_closefn, .writefn = gdb_writefn, .readfn = gdb_readfn, .isattyfn = gdb_isattyfn, @@ -494,7 +462,6 @@ static const GuestFDFunctions guestfd_fns[] = { .flenfn = gdb_flenfn, }, [GuestFDStatic] = { - .closefn = staticfile_closefn, .writefn = staticfile_writefn, .readfn = staticfile_readfn, .isattyfn = staticfile_isattyfn, @@ -586,13 +553,7 @@ void do_common_semihosting(CPUState *cs) case TARGET_SYS_CLOSE: GET_ARG(0); - - gf = get_guestfd(arg0); - if (!gf) { - goto do_badf; - } - guestfd_fns[gf->type].closefn(cs, gf); - dealloc_guestfd(arg0); + semihost_sys_close(cs, common_semi_cb, arg0); break; case TARGET_SYS_WRITEC: diff --git a/semihosting/guestfd.c b/semihosting/guestfd.c index 7ac2e147a845c..e3122ebba93ef 100644 --- a/semihosting/guestfd.c +++ b/semihosting/guestfd.c @@ -49,6 +49,11 @@ int alloc_guestfd(void) return i; } +static void do_dealloc_guestfd(GuestFD *gf) +{ + gf->type = GuestFDUnused; +} + /* * Look up the guestfd in the data structure; return NULL * for out of bounds, but don't check whether the slot is unused. @@ -119,5 +124,5 @@ void dealloc_guestfd(int guestfd) GuestFD *gf = do_get_guestfd(guestfd); assert(gf); - gf->type = GuestFDUnused; + do_dealloc_guestfd(gf); } diff --git a/semihosting/syscalls.c b/semihosting/syscalls.c index 9f9d19a59a76a..3648b9dd49312 100644 --- a/semihosting/syscalls.c +++ b/semihosting/syscalls.c @@ -94,6 +94,12 @@ static void gdb_open(CPUState *cs, gdb_syscall_complete_cb complete, fname, len, (target_ulong)gdb_flags, (target_ulong)mode); } +static void gdb_close(CPUState *cs, gdb_syscall_complete_cb complete, + GuestFD *gf) +{ + gdb_do_syscall(complete, "close,%x", (target_ulong)gf->hostfd); +} + /* * Host semihosting syscall implementations. */ @@ -140,6 +146,23 @@ static void host_open(CPUState *cs, gdb_syscall_complete_cb complete, unlock_user(p, fname, 0); } +static void host_close(CPUState *cs, gdb_syscall_complete_cb complete, + GuestFD *gf) +{ + /* + * Only close the underlying host fd if it's one we opened on behalf + * of the guest in SYS_OPEN. + */ + if (gf->hostfd != STDIN_FILENO && + gf->hostfd != STDOUT_FILENO && + gf->hostfd != STDERR_FILENO && + close(gf->hostfd) < 0) { + complete(cs, -1, errno); + } else { + complete(cs, 0, 0); + } +} + /* * Syscall entry points. */ @@ -154,3 +177,27 @@ void semihost_sys_open(CPUState *cs, gdb_syscall_complete_cb complete, host_open(cs, complete, fname, fname_len, gdb_flags, mode); } } + +void semihost_sys_close(CPUState *cs, gdb_syscall_complete_cb complete, int fd) +{ + GuestFD *gf = get_guestfd(fd); + + if (!gf) { + complete(cs, -1, EBADF); + return; + } + switch (gf->type) { + case GuestFDGDB: + gdb_close(cs, complete, gf); + break; + case GuestFDHost: + host_close(cs, complete, gf); + break; + case GuestFDStatic: + complete(cs, 0, 0); + break; + default: + g_assert_not_reached(); + } + dealloc_guestfd(fd); +} From f81f3af0b36dfb074358ff3ba7f857654d7e2896 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Thu, 28 Apr 2022 11:40:41 -0700 Subject: [PATCH 31/69] semihosting: Split out semihost_sys_read Split out the non-ARM specific portions of SYS_READ to a reusable function. This handles all GuestFD. Isolate the curious ARM-specific return value processing to a new callback, common_semi_rw_cb. Note that gdb_do_syscall %x reads target_ulong, not int. Reviewed-by: Luc Michel Signed-off-by: Richard Henderson (cherry picked from commit af0484b5025f8b7c951428a00b5bb3f172a2da8d) --- include/semihosting/syscalls.h | 8 ++++ semihosting/arm-compat-semi.c | 85 ++++++++-------------------------- semihosting/syscalls.c | 85 ++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 65 deletions(-) diff --git a/include/semihosting/syscalls.h b/include/semihosting/syscalls.h index 00e718f11d0ea..20da8138b05d3 100644 --- a/include/semihosting/syscalls.h +++ b/include/semihosting/syscalls.h @@ -18,6 +18,8 @@ * or non-zero (where it should include the terminating zero). */ +typedef struct GuestFD GuestFD; + void semihost_sys_open(CPUState *cs, gdb_syscall_complete_cb complete, target_ulong fname, target_ulong fname_len, int gdb_flags, int mode); @@ -25,4 +27,10 @@ void semihost_sys_open(CPUState *cs, gdb_syscall_complete_cb complete, void semihost_sys_close(CPUState *cs, gdb_syscall_complete_cb complete, int fd); +void semihost_sys_read(CPUState *cs, gdb_syscall_complete_cb complete, + int fd, target_ulong buf, target_ulong len); + +void semihost_sys_read_gf(CPUState *cs, gdb_syscall_complete_cb complete, + GuestFD *gf, target_ulong buf, target_ulong len); + #endif /* SEMIHOSTING_SYSCALLS_H */ diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index 0cb3db2a1af4d..8da31d85077a7 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -231,7 +231,6 @@ static void common_semi_cb(CPUState *cs, target_ulong ret, target_ulong err) target_ulong reg0 = common_semi_arg(cs, 0); switch (reg0) { case TARGET_SYS_WRITE: - case TARGET_SYS_READ: ret = common_semi_syscall_len - ret; break; case TARGET_SYS_SEEK: @@ -244,6 +243,25 @@ static void common_semi_cb(CPUState *cs, target_ulong ret, target_ulong err) common_semi_set_ret(cs, ret); } +/* + * SYS_READ and SYS_WRITE always return the number of bytes not read/written. + * There is no error condition, other than returning the original length. + */ +static void common_semi_rw_cb(CPUState *cs, target_ulong ret, target_ulong err) +{ + /* Recover the original length from the third argument. */ + CPUArchState *env G_GNUC_UNUSED = cs->env_ptr; + target_ulong args = common_semi_arg(cs, 1); + target_ulong arg2; + GET_ARG(2); + + if (err) { + do_fault: + ret = 0; /* error: no bytes transmitted */ + } + common_semi_set_ret(cs, arg2 - ret); +} + /* * Return an address in target memory of 64 bytes where the remote * gdb should write its stat struct. (The format of this structure @@ -278,8 +296,6 @@ common_semi_flen_cb(CPUState *cs, target_ulong ret, target_ulong err) */ typedef void sys_writefn(CPUState *cs, GuestFD *gf, target_ulong buf, uint32_t len); -typedef void sys_readfn(CPUState *cs, GuestFD *gf, - target_ulong buf, uint32_t len); typedef void sys_isattyfn(CPUState *cs, GuestFD *gf); typedef void sys_seekfn(CPUState *cs, GuestFD *gf, target_ulong offset); typedef void sys_flenfn(CPUState *cs, GuestFD *gf); @@ -302,26 +318,6 @@ static void host_writefn(CPUState *cs, GuestFD *gf, common_semi_cb(cs, len - ret, 0); } -static void host_readfn(CPUState *cs, GuestFD *gf, - target_ulong buf, uint32_t len) -{ - CPUArchState *env = cs->env_ptr; - uint32_t ret = 0; - char *s = lock_user(VERIFY_WRITE, buf, len, 0); - (void) env; /* Used in arm softmmu lock_user implicitly */ - if (s) { - do { - ret = read(gf->hostfd, s, len); - } while (ret == -1 && errno == EINTR); - unlock_user(s, buf, len); - if (ret == (uint32_t)-1) { - ret = 0; - } - } - /* Return bytes not read, on error as well. */ - common_semi_cb(cs, len - ret, 0); -} - static void host_isattyfn(CPUState *cs, GuestFD *gf) { common_semi_cb(cs, isatty(gf->hostfd), 0); @@ -351,13 +347,6 @@ static void gdb_writefn(CPUState *cs, GuestFD *gf, gdb_do_syscall(common_semi_cb, "write,%x,%x,%x", gf->hostfd, buf, len); } -static void gdb_readfn(CPUState *cs, GuestFD *gf, - target_ulong buf, uint32_t len) -{ - common_semi_syscall_len = len; - gdb_do_syscall(common_semi_cb, "read,%x,%x,%x", gf->hostfd, buf, len); -} - static void gdb_isattyfn(CPUState *cs, GuestFD *gf) { gdb_do_syscall(common_semi_cb, "isatty,%x", gf->hostfd); @@ -398,30 +387,6 @@ static void staticfile_writefn(CPUState *cs, GuestFD *gf, common_semi_cb(cs, -1, EBADF); } -static void staticfile_readfn(CPUState *cs, GuestFD *gf, - target_ulong buf, uint32_t len) -{ - CPUArchState *env = cs->env_ptr; - uint32_t i = 0; - char *s; - - (void) env; /* Used in arm softmmu lock_user implicitly */ - s = lock_user(VERIFY_WRITE, buf, len, 0); - if (s) { - for (i = 0; i < len; i++) { - if (gf->staticfile.off >= gf->staticfile.len) { - break; - } - s[i] = gf->staticfile.data[gf->staticfile.off]; - gf->staticfile.off++; - } - unlock_user(s, buf, len); - } - - /* Return number of bytes not read */ - common_semi_cb(cs, len - i, 0); -} - static void staticfile_isattyfn(CPUState *cs, GuestFD *gf) { common_semi_cb(cs, 0, 0); @@ -440,7 +405,6 @@ static void staticfile_flenfn(CPUState *cs, GuestFD *gf) typedef struct GuestFDFunctions { sys_writefn *writefn; - sys_readfn *readfn; sys_isattyfn *isattyfn; sys_seekfn *seekfn; sys_flenfn *flenfn; @@ -449,21 +413,18 @@ typedef struct GuestFDFunctions { static const GuestFDFunctions guestfd_fns[] = { [GuestFDHost] = { .writefn = host_writefn, - .readfn = host_readfn, .isattyfn = host_isattyfn, .seekfn = host_seekfn, .flenfn = host_flenfn, }, [GuestFDGDB] = { .writefn = gdb_writefn, - .readfn = gdb_readfn, .isattyfn = gdb_isattyfn, .seekfn = gdb_seekfn, .flenfn = gdb_flenfn, }, [GuestFDStatic] = { .writefn = staticfile_writefn, - .readfn = staticfile_readfn, .isattyfn = staticfile_isattyfn, .seekfn = staticfile_seekfn, .flenfn = staticfile_flenfn, @@ -583,13 +544,7 @@ void do_common_semihosting(CPUState *cs) GET_ARG(0); GET_ARG(1); GET_ARG(2); - len = arg2; - - gf = get_guestfd(arg0); - if (!gf) { - goto do_badf; - } - guestfd_fns[gf->type].readfn(cs, gf, arg1, len); + semihost_sys_read(cs, common_semi_rw_cb, arg0, arg1, arg2); break; case TARGET_SYS_READC: diff --git a/semihosting/syscalls.c b/semihosting/syscalls.c index 3648b9dd49312..d42a190746ca6 100644 --- a/semihosting/syscalls.c +++ b/semihosting/syscalls.c @@ -100,6 +100,13 @@ static void gdb_close(CPUState *cs, gdb_syscall_complete_cb complete, gdb_do_syscall(complete, "close,%x", (target_ulong)gf->hostfd); } +static void gdb_read(CPUState *cs, gdb_syscall_complete_cb complete, + GuestFD *gf, target_ulong buf, target_ulong len) +{ + gdb_do_syscall(complete, "read,%x,%x,%x", + (target_ulong)gf->hostfd, buf, len); +} + /* * Host semihosting syscall implementations. */ @@ -163,6 +170,54 @@ static void host_close(CPUState *cs, gdb_syscall_complete_cb complete, } } +static void host_read(CPUState *cs, gdb_syscall_complete_cb complete, + GuestFD *gf, target_ulong buf, target_ulong len) +{ + CPUArchState *env G_GNUC_UNUSED = cs->env_ptr; + void *ptr = lock_user(VERIFY_WRITE, buf, len, 0); + ssize_t ret; + + if (!ptr) { + complete(cs, -1, EFAULT); + return; + } + do { + ret = read(gf->hostfd, ptr, len); + } while (ret == -1 && errno == EINTR); + if (ret == -1) { + complete(cs, -1, errno); + unlock_user(ptr, buf, 0); + } else { + complete(cs, ret, 0); + unlock_user(ptr, buf, ret); + } +} + +/* + * Static file semihosting syscall implementations. + */ + +static void staticfile_read(CPUState *cs, gdb_syscall_complete_cb complete, + GuestFD *gf, target_ulong buf, target_ulong len) +{ + CPUArchState *env G_GNUC_UNUSED = cs->env_ptr; + target_ulong rest = gf->staticfile.len - gf->staticfile.off; + void *ptr; + + if (len > rest) { + len = rest; + } + ptr = lock_user(VERIFY_WRITE, buf, len, 0); + if (!ptr) { + complete(cs, -1, EFAULT); + return; + } + memcpy(ptr, gf->staticfile.data + gf->staticfile.off, len); + gf->staticfile.off += len; + complete(cs, len, 0); + unlock_user(ptr, buf, len); +} + /* * Syscall entry points. */ @@ -201,3 +256,33 @@ void semihost_sys_close(CPUState *cs, gdb_syscall_complete_cb complete, int fd) } dealloc_guestfd(fd); } + +void semihost_sys_read_gf(CPUState *cs, gdb_syscall_complete_cb complete, + GuestFD *gf, target_ulong buf, target_ulong len) +{ + switch (gf->type) { + case GuestFDGDB: + gdb_read(cs, complete, gf, buf, len); + break; + case GuestFDHost: + host_read(cs, complete, gf, buf, len); + break; + case GuestFDStatic: + staticfile_read(cs, complete, gf, buf, len); + break; + default: + g_assert_not_reached(); + } +} + +void semihost_sys_read(CPUState *cs, gdb_syscall_complete_cb complete, + int fd, target_ulong buf, target_ulong len) +{ + GuestFD *gf = get_guestfd(fd); + + if (gf) { + semihost_sys_read_gf(cs, complete, gf, buf, len); + } else { + complete(cs, -1, EBADF); + } +} From 9872855445d5e3c73cf40f05d55bd112788c9d29 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Thu, 28 Apr 2022 11:49:47 -0700 Subject: [PATCH 32/69] semihosting: Split out semihost_sys_write Split out the non-ARM specific portions of SYS_WRITE to a reusable function. This handles all GuestFD. This removes the last use of common_semi_syscall_len. Note that gdb_do_syscall %x reads target_ulong, not int. Reviewed-by: Luc Michel Signed-off-by: Richard Henderson (cherry picked from commit aa915bd0a67d6c0a214b45372ed841521c5cd07a) --- include/semihosting/syscalls.h | 6 ++++ semihosting/arm-compat-semi.c | 52 +------------------------------- semihosting/syscalls.c | 54 ++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 51 deletions(-) diff --git a/include/semihosting/syscalls.h b/include/semihosting/syscalls.h index 20da8138b05d3..2464467579326 100644 --- a/include/semihosting/syscalls.h +++ b/include/semihosting/syscalls.h @@ -33,4 +33,10 @@ void semihost_sys_read(CPUState *cs, gdb_syscall_complete_cb complete, void semihost_sys_read_gf(CPUState *cs, gdb_syscall_complete_cb complete, GuestFD *gf, target_ulong buf, target_ulong len); +void semihost_sys_write(CPUState *cs, gdb_syscall_complete_cb complete, + int fd, target_ulong buf, target_ulong len); + +void semihost_sys_write_gf(CPUState *cs, gdb_syscall_complete_cb complete, + GuestFD *gf, target_ulong buf, target_ulong len); + #endif /* SEMIHOSTING_SYSCALLS_H */ diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index 8da31d85077a7..d591fcd7c276b 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -215,8 +215,6 @@ static inline uint32_t get_swi_errno(CPUState *cs) #endif } -static target_ulong common_semi_syscall_len; - static void common_semi_cb(CPUState *cs, target_ulong ret, target_ulong err) { if (err) { @@ -230,9 +228,6 @@ static void common_semi_cb(CPUState *cs, target_ulong ret, target_ulong err) /* Fixup syscalls that use nonstardard return conventions. */ target_ulong reg0 = common_semi_arg(cs, 0); switch (reg0) { - case TARGET_SYS_WRITE: - ret = common_semi_syscall_len - ret; - break; case TARGET_SYS_SEEK: ret = 0; break; @@ -294,30 +289,10 @@ common_semi_flen_cb(CPUState *cs, target_ulong ret, target_ulong err) * do the work and return the required return value to the guest * via common_semi_cb. */ -typedef void sys_writefn(CPUState *cs, GuestFD *gf, - target_ulong buf, uint32_t len); typedef void sys_isattyfn(CPUState *cs, GuestFD *gf); typedef void sys_seekfn(CPUState *cs, GuestFD *gf, target_ulong offset); typedef void sys_flenfn(CPUState *cs, GuestFD *gf); -static void host_writefn(CPUState *cs, GuestFD *gf, - target_ulong buf, uint32_t len) -{ - CPUArchState *env = cs->env_ptr; - uint32_t ret = 0; - char *s = lock_user(VERIFY_READ, buf, len, 1); - (void) env; /* Used in arm softmmu lock_user implicitly */ - if (s) { - ret = write(gf->hostfd, s, len); - unlock_user(s, buf, 0); - if (ret == (uint32_t)-1) { - ret = 0; - } - } - /* Return bytes not written, on error as well. */ - common_semi_cb(cs, len - ret, 0); -} - static void host_isattyfn(CPUState *cs, GuestFD *gf) { common_semi_cb(cs, isatty(gf->hostfd), 0); @@ -340,13 +315,6 @@ static void host_flenfn(CPUState *cs, GuestFD *gf) } } -static void gdb_writefn(CPUState *cs, GuestFD *gf, - target_ulong buf, uint32_t len) -{ - common_semi_syscall_len = len; - gdb_do_syscall(common_semi_cb, "write,%x,%x,%x", gf->hostfd, buf, len); -} - static void gdb_isattyfn(CPUState *cs, GuestFD *gf) { gdb_do_syscall(common_semi_cb, "isatty,%x", gf->hostfd); @@ -380,13 +348,6 @@ static const uint8_t featurefile_data[] = { SH_EXT_EXIT_EXTENDED | SH_EXT_STDOUT_STDERR, /* Feature byte 0 */ }; -static void staticfile_writefn(CPUState *cs, GuestFD *gf, - target_ulong buf, uint32_t len) -{ - /* This fd can never be open for writing */ - common_semi_cb(cs, -1, EBADF); -} - static void staticfile_isattyfn(CPUState *cs, GuestFD *gf) { common_semi_cb(cs, 0, 0); @@ -404,7 +365,6 @@ static void staticfile_flenfn(CPUState *cs, GuestFD *gf) } typedef struct GuestFDFunctions { - sys_writefn *writefn; sys_isattyfn *isattyfn; sys_seekfn *seekfn; sys_flenfn *flenfn; @@ -412,19 +372,16 @@ typedef struct GuestFDFunctions { static const GuestFDFunctions guestfd_fns[] = { [GuestFDHost] = { - .writefn = host_writefn, .isattyfn = host_isattyfn, .seekfn = host_seekfn, .flenfn = host_flenfn, }, [GuestFDGDB] = { - .writefn = gdb_writefn, .isattyfn = gdb_isattyfn, .seekfn = gdb_seekfn, .flenfn = gdb_flenfn, }, [GuestFDStatic] = { - .writefn = staticfile_writefn, .isattyfn = staticfile_isattyfn, .seekfn = staticfile_seekfn, .flenfn = staticfile_flenfn, @@ -449,7 +406,6 @@ void do_common_semihosting(CPUState *cs) char * s; int nr; uint32_t ret; - uint32_t len; GuestFD *gf; int64_t elapsed; @@ -531,13 +487,7 @@ void do_common_semihosting(CPUState *cs) GET_ARG(0); GET_ARG(1); GET_ARG(2); - len = arg2; - - gf = get_guestfd(arg0); - if (!gf) { - goto do_badf; - } - guestfd_fns[gf->type].writefn(cs, gf, arg1, len); + semihost_sys_write(cs, common_semi_rw_cb, arg0, arg1, arg2); break; case TARGET_SYS_READ: diff --git a/semihosting/syscalls.c b/semihosting/syscalls.c index d42a190746ca6..5cb12d6adc38a 100644 --- a/semihosting/syscalls.c +++ b/semihosting/syscalls.c @@ -107,6 +107,13 @@ static void gdb_read(CPUState *cs, gdb_syscall_complete_cb complete, (target_ulong)gf->hostfd, buf, len); } +static void gdb_write(CPUState *cs, gdb_syscall_complete_cb complete, + GuestFD *gf, target_ulong buf, target_ulong len) +{ + gdb_do_syscall(complete, "write,%x,%x,%x", + (target_ulong)gf->hostfd, buf, len); +} + /* * Host semihosting syscall implementations. */ @@ -193,6 +200,22 @@ static void host_read(CPUState *cs, gdb_syscall_complete_cb complete, } } +static void host_write(CPUState *cs, gdb_syscall_complete_cb complete, + GuestFD *gf, target_ulong buf, target_ulong len) +{ + CPUArchState *env G_GNUC_UNUSED = cs->env_ptr; + void *ptr = lock_user(VERIFY_READ, buf, len, 1); + ssize_t ret; + + if (!ptr) { + complete(cs, -1, EFAULT); + return; + } + ret = write(gf->hostfd, ptr, len); + complete(cs, ret, ret == -1 ? errno : 0); + unlock_user(ptr, buf, 0); +} + /* * Static file semihosting syscall implementations. */ @@ -286,3 +309,34 @@ void semihost_sys_read(CPUState *cs, gdb_syscall_complete_cb complete, complete(cs, -1, EBADF); } } + +void semihost_sys_write_gf(CPUState *cs, gdb_syscall_complete_cb complete, + GuestFD *gf, target_ulong buf, target_ulong len) +{ + switch (gf->type) { + case GuestFDGDB: + gdb_write(cs, complete, gf, buf, len); + break; + case GuestFDHost: + host_write(cs, complete, gf, buf, len); + break; + case GuestFDStatic: + /* Static files are never open for writing: EBADF. */ + complete(cs, -1, EBADF); + break; + default: + g_assert_not_reached(); + } +} + +void semihost_sys_write(CPUState *cs, gdb_syscall_complete_cb complete, + int fd, target_ulong buf, target_ulong len) +{ + GuestFD *gf = get_guestfd(fd); + + if (gf) { + semihost_sys_write_gf(cs, complete, gf, buf, len); + } else { + complete(cs, -1, EBADF); + } +} From 099eb21573464a5de214b96f7a0af5d1d6ae25be Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Fri, 29 Apr 2022 12:15:14 -0700 Subject: [PATCH 33/69] semihosting: Bound length for semihost_sys_{read,write} Fixes a minor bug in which a 64-bit guest on a 32-bit host could truncate the length. This would only ever cause a problem if there were no bits set in the low 32, so that it truncates to 0. Reviewed-by: Luc Michel Signed-off-by: Richard Henderson (cherry picked from commit 40f1219a8b2f95808ed5a18798dbce1b57fef211) --- semihosting/syscalls.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/semihosting/syscalls.c b/semihosting/syscalls.c index 5cb12d6adc38a..eefbae74f1e12 100644 --- a/semihosting/syscalls.c +++ b/semihosting/syscalls.c @@ -283,6 +283,14 @@ void semihost_sys_close(CPUState *cs, gdb_syscall_complete_cb complete, int fd) void semihost_sys_read_gf(CPUState *cs, gdb_syscall_complete_cb complete, GuestFD *gf, target_ulong buf, target_ulong len) { + /* + * Bound length for 64-bit guests on 32-bit hosts, not overlowing ssize_t. + * Note the Linux kernel does this with MAX_RW_COUNT, so it's not a bad + * idea to do this unconditionally. + */ + if (len > INT32_MAX) { + len = INT32_MAX; + } switch (gf->type) { case GuestFDGDB: gdb_read(cs, complete, gf, buf, len); @@ -313,6 +321,14 @@ void semihost_sys_read(CPUState *cs, gdb_syscall_complete_cb complete, void semihost_sys_write_gf(CPUState *cs, gdb_syscall_complete_cb complete, GuestFD *gf, target_ulong buf, target_ulong len) { + /* + * Bound length for 64-bit guests on 32-bit hosts, not overlowing ssize_t. + * Note the Linux kernel does this with MAX_RW_COUNT, so it's not a bad + * idea to do this unconditionally. + */ + if (len > INT32_MAX) { + len = INT32_MAX; + } switch (gf->type) { case GuestFDGDB: gdb_write(cs, complete, gf, buf, len); From 2608e6de89365691405c1948f4a225a50da674ed Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Thu, 28 Apr 2022 12:04:44 -0700 Subject: [PATCH 34/69] semihosting: Split out semihost_sys_lseek Split out the non-ARM specific portions of SYS_SEEK to a reusable function. This handles all GuestFD. Isolate the curious ARM-specific return value processing to a new callback, common_semi_seek_cb. Expand the internal type of the offset to int64_t, and provide the whence argument, which will be required by m68k and nios2 semihosting. Note that gdb_do_syscall %x reads target_ulong, not int. Reviewed-by: Luc Michel Signed-off-by: Richard Henderson (cherry picked from commit 9a89470449a5171eb1bd06f681361e0888d15cf7) --- include/exec/gdbstub.h | 5 +++ include/semihosting/syscalls.h | 3 ++ semihosting/arm-compat-semi.c | 51 ++++++--------------- semihosting/syscalls.c | 81 ++++++++++++++++++++++++++++++++++ 4 files changed, 102 insertions(+), 38 deletions(-) diff --git a/include/exec/gdbstub.h b/include/exec/gdbstub.h index 53644303bd1fe..2d8e85b52a66a 100644 --- a/include/exec/gdbstub.h +++ b/include/exec/gdbstub.h @@ -41,6 +41,11 @@ #define GDB_ENAMETOOLONG 91 #define GDB_EUNKNOWN 9999 +/* For gdb file i/o remote protocol lseek whence. */ +#define GDB_SEEK_SET 0 +#define GDB_SEEK_CUR 1 +#define GDB_SEEK_END 2 + /* For gdb file i/o stat/fstat. */ typedef uint32_t gdb_mode_t; typedef uint32_t gdb_time_t; diff --git a/include/semihosting/syscalls.h b/include/semihosting/syscalls.h index 2464467579326..841a93d25bc54 100644 --- a/include/semihosting/syscalls.h +++ b/include/semihosting/syscalls.h @@ -39,4 +39,7 @@ void semihost_sys_write(CPUState *cs, gdb_syscall_complete_cb complete, void semihost_sys_write_gf(CPUState *cs, gdb_syscall_complete_cb complete, GuestFD *gf, target_ulong buf, target_ulong len); +void semihost_sys_lseek(CPUState *cs, gdb_syscall_complete_cb complete, + int fd, int64_t off, int gdb_whence); + #endif /* SEMIHOSTING_SYSCALLS_H */ diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index d591fcd7c276b..a117d180bcf04 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -224,16 +224,6 @@ static void common_semi_cb(CPUState *cs, target_ulong ret, target_ulong err) #else syscall_err = err; #endif - } else { - /* Fixup syscalls that use nonstardard return conventions. */ - target_ulong reg0 = common_semi_arg(cs, 0); - switch (reg0) { - case TARGET_SYS_SEEK: - ret = 0; - break; - default: - break; - } } common_semi_set_ret(cs, ret); } @@ -257,6 +247,18 @@ static void common_semi_rw_cb(CPUState *cs, target_ulong ret, target_ulong err) common_semi_set_ret(cs, arg2 - ret); } +/* + * SYS_SEEK returns 0 on success, not the resulting offset. + */ +static void common_semi_seek_cb(CPUState *cs, target_ulong ret, + target_ulong err) +{ + if (!err) { + ret = 0; + } + common_semi_cb(cs, ret, err); +} + /* * Return an address in target memory of 64 bytes where the remote * gdb should write its stat struct. (The format of this structure @@ -290,7 +292,6 @@ common_semi_flen_cb(CPUState *cs, target_ulong ret, target_ulong err) * via common_semi_cb. */ typedef void sys_isattyfn(CPUState *cs, GuestFD *gf); -typedef void sys_seekfn(CPUState *cs, GuestFD *gf, target_ulong offset); typedef void sys_flenfn(CPUState *cs, GuestFD *gf); static void host_isattyfn(CPUState *cs, GuestFD *gf) @@ -298,12 +299,6 @@ static void host_isattyfn(CPUState *cs, GuestFD *gf) common_semi_cb(cs, isatty(gf->hostfd), 0); } -static void host_seekfn(CPUState *cs, GuestFD *gf, target_ulong offset) -{ - off_t ret = lseek(gf->hostfd, offset, SEEK_SET); - common_semi_cb(cs, ret, ret == -1 ? errno : 0); -} - static void host_flenfn(CPUState *cs, GuestFD *gf) { struct stat buf; @@ -320,11 +315,6 @@ static void gdb_isattyfn(CPUState *cs, GuestFD *gf) gdb_do_syscall(common_semi_cb, "isatty,%x", gf->hostfd); } -static void gdb_seekfn(CPUState *cs, GuestFD *gf, target_ulong offset) -{ - gdb_do_syscall(common_semi_cb, "lseek,%x,%x,0", gf->hostfd, offset); -} - static void gdb_flenfn(CPUState *cs, GuestFD *gf) { gdb_do_syscall(common_semi_flen_cb, "fstat,%x,%x", @@ -353,12 +343,6 @@ static void staticfile_isattyfn(CPUState *cs, GuestFD *gf) common_semi_cb(cs, 0, 0); } -static void staticfile_seekfn(CPUState *cs, GuestFD *gf, target_ulong offset) -{ - gf->staticfile.off = offset; - common_semi_cb(cs, 0, 0); -} - static void staticfile_flenfn(CPUState *cs, GuestFD *gf) { common_semi_cb(cs, gf->staticfile.len, 0); @@ -366,24 +350,20 @@ static void staticfile_flenfn(CPUState *cs, GuestFD *gf) typedef struct GuestFDFunctions { sys_isattyfn *isattyfn; - sys_seekfn *seekfn; sys_flenfn *flenfn; } GuestFDFunctions; static const GuestFDFunctions guestfd_fns[] = { [GuestFDHost] = { .isattyfn = host_isattyfn, - .seekfn = host_seekfn, .flenfn = host_flenfn, }, [GuestFDGDB] = { .isattyfn = gdb_isattyfn, - .seekfn = gdb_seekfn, .flenfn = gdb_flenfn, }, [GuestFDStatic] = { .isattyfn = staticfile_isattyfn, - .seekfn = staticfile_seekfn, .flenfn = staticfile_flenfn, }, }; @@ -520,12 +500,7 @@ void do_common_semihosting(CPUState *cs) case TARGET_SYS_SEEK: GET_ARG(0); GET_ARG(1); - - gf = get_guestfd(arg0); - if (!gf) { - goto do_badf; - } - guestfd_fns[gf->type].seekfn(cs, gf, arg1); + semihost_sys_lseek(cs, common_semi_seek_cb, arg0, arg1, GDB_SEEK_SET); break; case TARGET_SYS_FLEN: diff --git a/semihosting/syscalls.c b/semihosting/syscalls.c index eefbae74f1e12..9e3eb464b5da7 100644 --- a/semihosting/syscalls.c +++ b/semihosting/syscalls.c @@ -114,6 +114,13 @@ static void gdb_write(CPUState *cs, gdb_syscall_complete_cb complete, (target_ulong)gf->hostfd, buf, len); } +static void gdb_lseek(CPUState *cs, gdb_syscall_complete_cb complete, + GuestFD *gf, int64_t off, int gdb_whence) +{ + gdb_do_syscall(complete, "lseek,%x,%lx,%x", + (target_ulong)gf->hostfd, off, (target_ulong)gdb_whence); +} + /* * Host semihosting syscall implementations. */ @@ -216,6 +223,29 @@ static void host_write(CPUState *cs, gdb_syscall_complete_cb complete, unlock_user(ptr, buf, 0); } +static void host_lseek(CPUState *cs, gdb_syscall_complete_cb complete, + GuestFD *gf, int64_t off, int whence) +{ + /* So far, all hosts use the same values. */ + QEMU_BUILD_BUG_ON(GDB_SEEK_SET != SEEK_SET); + QEMU_BUILD_BUG_ON(GDB_SEEK_CUR != SEEK_CUR); + QEMU_BUILD_BUG_ON(GDB_SEEK_END != SEEK_END); + + off_t ret = off; + int err = 0; + + if (ret == off) { + ret = lseek(gf->hostfd, ret, whence); + if (ret == -1) { + err = errno; + } + } else { + ret = -1; + err = EINVAL; + } + complete(cs, ret, err); +} + /* * Static file semihosting syscall implementations. */ @@ -241,6 +271,33 @@ static void staticfile_read(CPUState *cs, gdb_syscall_complete_cb complete, unlock_user(ptr, buf, len); } +static void staticfile_lseek(CPUState *cs, gdb_syscall_complete_cb complete, + GuestFD *gf, int64_t off, int gdb_whence) +{ + int64_t ret; + + switch (gdb_whence) { + case GDB_SEEK_SET: + ret = off; + break; + case GDB_SEEK_CUR: + ret = gf->staticfile.off + off; + break; + case GDB_SEEK_END: + ret = gf->staticfile.len + off; + break; + default: + ret = -1; + break; + } + if (ret >= 0 && ret <= gf->staticfile.len) { + gf->staticfile.off = ret; + complete(cs, ret, 0); + } else { + complete(cs, -1, EINVAL); + } +} + /* * Syscall entry points. */ @@ -356,3 +413,27 @@ void semihost_sys_write(CPUState *cs, gdb_syscall_complete_cb complete, complete(cs, -1, EBADF); } } + +void semihost_sys_lseek(CPUState *cs, gdb_syscall_complete_cb complete, + int fd, int64_t off, int gdb_whence) +{ + GuestFD *gf = get_guestfd(fd); + + if (!gf) { + complete(cs, -1, EBADF); + return; + } + switch (gf->type) { + case GuestFDGDB: + gdb_lseek(cs, complete, gf, off, gdb_whence); + return; + case GuestFDHost: + host_lseek(cs, complete, gf, off, gdb_whence); + break; + case GuestFDStatic: + staticfile_lseek(cs, complete, gf, off, gdb_whence); + break; + default: + g_assert_not_reached(); + } +} From 6a0d861372b25752c3558424a5bbf9b35c0192b1 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Thu, 28 Apr 2022 12:31:25 -0700 Subject: [PATCH 35/69] semihosting: Split out semihost_sys_isatty Split out the non-ARM specific portions of SYS_ISTTY to a reusable function. This handles all GuestFD. Add a common_semi_istty_cb helper to translate the Posix error return, 0+ENOTTY, to the Arm semihosting not-a-file success result. Reviewed-by: Luc Michel Signed-off-by: Richard Henderson (cherry picked from commit a2212474301bc354bea46e3bdc6c21e33e0b5b2b) --- include/semihosting/syscalls.h | 3 +++ semihosting/arm-compat-semi.c | 40 ++++++++++++---------------------- semihosting/syscalls.c | 36 ++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 26 deletions(-) diff --git a/include/semihosting/syscalls.h b/include/semihosting/syscalls.h index 841a93d25bc54..c60ebafb854b4 100644 --- a/include/semihosting/syscalls.h +++ b/include/semihosting/syscalls.h @@ -42,4 +42,7 @@ void semihost_sys_write_gf(CPUState *cs, gdb_syscall_complete_cb complete, void semihost_sys_lseek(CPUState *cs, gdb_syscall_complete_cb complete, int fd, int64_t off, int gdb_whence); +void semihost_sys_isatty(CPUState *cs, gdb_syscall_complete_cb complete, + int fd); + #endif /* SEMIHOSTING_SYSCALLS_H */ diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index a117d180bcf04..3cdc2b6efc039 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -247,6 +247,19 @@ static void common_semi_rw_cb(CPUState *cs, target_ulong ret, target_ulong err) common_semi_set_ret(cs, arg2 - ret); } +/* + * Convert from Posix ret+errno to Arm SYS_ISTTY return values. + * With gdbstub, err is only ever set for protocol errors to EIO. + */ +static void common_semi_istty_cb(CPUState *cs, target_ulong ret, + target_ulong err) +{ + if (err) { + ret = (err == ENOTTY ? 0 : -1); + } + common_semi_cb(cs, ret, err); +} + /* * SYS_SEEK returns 0 on success, not the resulting offset. */ @@ -291,14 +304,8 @@ common_semi_flen_cb(CPUState *cs, target_ulong ret, target_ulong err) * do the work and return the required return value to the guest * via common_semi_cb. */ -typedef void sys_isattyfn(CPUState *cs, GuestFD *gf); typedef void sys_flenfn(CPUState *cs, GuestFD *gf); -static void host_isattyfn(CPUState *cs, GuestFD *gf) -{ - common_semi_cb(cs, isatty(gf->hostfd), 0); -} - static void host_flenfn(CPUState *cs, GuestFD *gf) { struct stat buf; @@ -310,11 +317,6 @@ static void host_flenfn(CPUState *cs, GuestFD *gf) } } -static void gdb_isattyfn(CPUState *cs, GuestFD *gf) -{ - gdb_do_syscall(common_semi_cb, "isatty,%x", gf->hostfd); -} - static void gdb_flenfn(CPUState *cs, GuestFD *gf) { gdb_do_syscall(common_semi_flen_cb, "fstat,%x,%x", @@ -338,32 +340,23 @@ static const uint8_t featurefile_data[] = { SH_EXT_EXIT_EXTENDED | SH_EXT_STDOUT_STDERR, /* Feature byte 0 */ }; -static void staticfile_isattyfn(CPUState *cs, GuestFD *gf) -{ - common_semi_cb(cs, 0, 0); -} - static void staticfile_flenfn(CPUState *cs, GuestFD *gf) { common_semi_cb(cs, gf->staticfile.len, 0); } typedef struct GuestFDFunctions { - sys_isattyfn *isattyfn; sys_flenfn *flenfn; } GuestFDFunctions; static const GuestFDFunctions guestfd_fns[] = { [GuestFDHost] = { - .isattyfn = host_isattyfn, .flenfn = host_flenfn, }, [GuestFDGDB] = { - .isattyfn = gdb_isattyfn, .flenfn = gdb_flenfn, }, [GuestFDStatic] = { - .isattyfn = staticfile_isattyfn, .flenfn = staticfile_flenfn, }, }; @@ -489,12 +482,7 @@ void do_common_semihosting(CPUState *cs) case TARGET_SYS_ISTTY: GET_ARG(0); - - gf = get_guestfd(arg0); - if (!gf) { - goto do_badf; - } - guestfd_fns[gf->type].isattyfn(cs, gf); + semihost_sys_isatty(cs, common_semi_istty_cb, arg0); break; case TARGET_SYS_SEEK: diff --git a/semihosting/syscalls.c b/semihosting/syscalls.c index 9e3eb464b5da7..1f1baf7e2d276 100644 --- a/semihosting/syscalls.c +++ b/semihosting/syscalls.c @@ -121,6 +121,12 @@ static void gdb_lseek(CPUState *cs, gdb_syscall_complete_cb complete, (target_ulong)gf->hostfd, off, (target_ulong)gdb_whence); } +static void gdb_isatty(CPUState *cs, gdb_syscall_complete_cb complete, + GuestFD *gf) +{ + gdb_do_syscall(complete, "isatty,%x", (target_ulong)gf->hostfd); +} + /* * Host semihosting syscall implementations. */ @@ -246,6 +252,13 @@ static void host_lseek(CPUState *cs, gdb_syscall_complete_cb complete, complete(cs, ret, err); } +static void host_isatty(CPUState *cs, gdb_syscall_complete_cb complete, + GuestFD *gf) +{ + int ret = isatty(gf->hostfd); + complete(cs, ret, ret ? 0 : errno); +} + /* * Static file semihosting syscall implementations. */ @@ -437,3 +450,26 @@ void semihost_sys_lseek(CPUState *cs, gdb_syscall_complete_cb complete, g_assert_not_reached(); } } + +void semihost_sys_isatty(CPUState *cs, gdb_syscall_complete_cb complete, int fd) +{ + GuestFD *gf = get_guestfd(fd); + + if (!gf) { + complete(cs, 0, EBADF); + return; + } + switch (gf->type) { + case GuestFDGDB: + gdb_isatty(cs, complete, gf); + break; + case GuestFDHost: + host_isatty(cs, complete, gf); + break; + case GuestFDStatic: + complete(cs, 0, ENOTTY); + break; + default: + g_assert_not_reached(); + } +} From 3e4baa0689a4344fc3f0887ac90d5b87121c037e Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Fri, 29 Apr 2022 09:05:36 -0700 Subject: [PATCH 36/69] semihosting: Split out semihost_sys_flen The ARM-specific SYS_FLEN isn't really something that can be reused by other semihosting apis, but there are parts that can reused for the implementation of semihost_sys_fstat. Reviewed-by: Luc Michel Signed-off-by: Richard Henderson (cherry picked from commit a6300ed6b7ecd19eb5ae0fd8a3eb876681cb8e6e) --- include/semihosting/syscalls.h | 4 ++ semihosting/arm-compat-semi.c | 74 ++++++---------------------------- semihosting/syscalls.c | 49 ++++++++++++++++++++++ 3 files changed, 66 insertions(+), 61 deletions(-) diff --git a/include/semihosting/syscalls.h b/include/semihosting/syscalls.h index c60ebafb854b4..1ae5ba6716189 100644 --- a/include/semihosting/syscalls.h +++ b/include/semihosting/syscalls.h @@ -45,4 +45,8 @@ void semihost_sys_lseek(CPUState *cs, gdb_syscall_complete_cb complete, void semihost_sys_isatty(CPUState *cs, gdb_syscall_complete_cb complete, int fd); +void semihost_sys_flen(CPUState *cs, gdb_syscall_complete_cb fstat_cb, + gdb_syscall_complete_cb flen_cb, + int fd, target_ulong fstat_addr); + #endif /* SEMIHOSTING_SYSCALLS_H */ diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index 3cdc2b6efc039..68e13d9077412 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -285,44 +285,25 @@ static target_ulong common_semi_flen_buf(CPUState *cs) } static void -common_semi_flen_cb(CPUState *cs, target_ulong ret, target_ulong err) +common_semi_flen_fstat_cb(CPUState *cs, target_ulong ret, target_ulong err) { if (!err) { /* The size is always stored in big-endian order, extract the value. */ uint64_t size; - cpu_memory_rw_debug(cs, common_semi_flen_buf(cs) + - offsetof(struct gdb_stat, gdb_st_size), - &size, 8, 0); - ret = be64_to_cpu(size); + if (cpu_memory_rw_debug(cs, common_semi_flen_buf(cs) + + offsetof(struct gdb_stat, gdb_st_size), + &size, 8, 0)) { + ret = -1, err = EFAULT; + } else { + size = be64_to_cpu(size); + if (ret != size) { + ret = -1, err = EOVERFLOW; + } + } } common_semi_cb(cs, ret, err); } -/* - * Types for functions implementing various semihosting calls - * for specific types of guest file descriptor. These must all - * do the work and return the required return value to the guest - * via common_semi_cb. - */ -typedef void sys_flenfn(CPUState *cs, GuestFD *gf); - -static void host_flenfn(CPUState *cs, GuestFD *gf) -{ - struct stat buf; - - if (fstat(gf->hostfd, &buf)) { - common_semi_cb(cs, -1, errno); - } else { - common_semi_cb(cs, buf.st_size, 0); - } -} - -static void gdb_flenfn(CPUState *cs, GuestFD *gf) -{ - gdb_do_syscall(common_semi_flen_cb, "fstat,%x,%x", - gf->hostfd, common_semi_flen_buf(cs)); -} - #define SHFB_MAGIC_0 0x53 #define SHFB_MAGIC_1 0x48 #define SHFB_MAGIC_2 0x46 @@ -340,27 +321,6 @@ static const uint8_t featurefile_data[] = { SH_EXT_EXIT_EXTENDED | SH_EXT_STDOUT_STDERR, /* Feature byte 0 */ }; -static void staticfile_flenfn(CPUState *cs, GuestFD *gf) -{ - common_semi_cb(cs, gf->staticfile.len, 0); -} - -typedef struct GuestFDFunctions { - sys_flenfn *flenfn; -} GuestFDFunctions; - -static const GuestFDFunctions guestfd_fns[] = { - [GuestFDHost] = { - .flenfn = host_flenfn, - }, - [GuestFDGDB] = { - .flenfn = gdb_flenfn, - }, - [GuestFDStatic] = { - .flenfn = staticfile_flenfn, - }, -}; - /* * Do a semihosting call. * @@ -379,7 +339,6 @@ void do_common_semihosting(CPUState *cs) char * s; int nr; uint32_t ret; - GuestFD *gf; int64_t elapsed; nr = common_semi_arg(cs, 0) & 0xffffffffU; @@ -493,12 +452,8 @@ void do_common_semihosting(CPUState *cs) case TARGET_SYS_FLEN: GET_ARG(0); - - gf = get_guestfd(arg0); - if (!gf) { - goto do_badf; - } - guestfd_fns[gf->type].flenfn(cs, gf); + semihost_sys_flen(cs, common_semi_flen_fstat_cb, common_semi_cb, + arg0, common_semi_flen_buf(cs)); break; case TARGET_SYS_TMPNAM: @@ -820,9 +775,6 @@ void do_common_semihosting(CPUState *cs) cpu_dump_state(cs, stderr, 0); abort(); - do_badf: - common_semi_cb(cs, -1, EBADF); - break; do_fault: common_semi_cb(cs, -1, EFAULT); break; diff --git a/semihosting/syscalls.c b/semihosting/syscalls.c index 1f1baf7e2d276..fff9550c89440 100644 --- a/semihosting/syscalls.c +++ b/semihosting/syscalls.c @@ -127,6 +127,12 @@ static void gdb_isatty(CPUState *cs, gdb_syscall_complete_cb complete, gdb_do_syscall(complete, "isatty,%x", (target_ulong)gf->hostfd); } +static void gdb_fstat(CPUState *cs, gdb_syscall_complete_cb complete, + GuestFD *gf, target_ulong addr) +{ + gdb_do_syscall(complete, "fstat,%x,%x", (target_ulong)gf->hostfd, addr); +} + /* * Host semihosting syscall implementations. */ @@ -259,6 +265,18 @@ static void host_isatty(CPUState *cs, gdb_syscall_complete_cb complete, complete(cs, ret, ret ? 0 : errno); } +static void host_flen(CPUState *cs, gdb_syscall_complete_cb complete, + GuestFD *gf) +{ + struct stat buf; + + if (fstat(gf->hostfd, &buf) < 0) { + complete(cs, -1, errno); + } else { + complete(cs, buf.st_size, 0); + } +} + /* * Static file semihosting syscall implementations. */ @@ -311,6 +329,12 @@ static void staticfile_lseek(CPUState *cs, gdb_syscall_complete_cb complete, } } +static void staticfile_flen(CPUState *cs, gdb_syscall_complete_cb complete, + GuestFD *gf) +{ + complete(cs, gf->staticfile.len, 0); +} + /* * Syscall entry points. */ @@ -473,3 +497,28 @@ void semihost_sys_isatty(CPUState *cs, gdb_syscall_complete_cb complete, int fd) g_assert_not_reached(); } } + +void semihost_sys_flen(CPUState *cs, gdb_syscall_complete_cb fstat_cb, + gdb_syscall_complete_cb flen_cb, int fd, + target_ulong fstat_addr) +{ + GuestFD *gf = get_guestfd(fd); + + if (!gf) { + flen_cb(cs, -1, EBADF); + return; + } + switch (gf->type) { + case GuestFDGDB: + gdb_fstat(cs, fstat_cb, gf, fstat_addr); + break; + case GuestFDHost: + host_flen(cs, flen_cb, gf); + break; + case GuestFDStatic: + staticfile_flen(cs, flen_cb, gf); + break; + default: + g_assert_not_reached(); + } +} From f25db3b24b09f3b6bb838be7c91174f7333f0b66 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Fri, 29 Apr 2022 09:29:27 -0700 Subject: [PATCH 37/69] semihosting: Split out semihost_sys_remove Split out the non-ARM specific portions of SYS_REMOVE to a reusable function. Reviewed-by: Luc Michel Signed-off-by: Richard Henderson (cherry picked from commit d49e79b8e276b56817509114a86b036358246af8) --- include/semihosting/syscalls.h | 3 +++ semihosting/arm-compat-semi.c | 13 +---------- semihosting/syscalls.c | 40 ++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 12 deletions(-) diff --git a/include/semihosting/syscalls.h b/include/semihosting/syscalls.h index 1ae5ba6716189..748a4b5e47a31 100644 --- a/include/semihosting/syscalls.h +++ b/include/semihosting/syscalls.h @@ -49,4 +49,7 @@ void semihost_sys_flen(CPUState *cs, gdb_syscall_complete_cb fstat_cb, gdb_syscall_complete_cb flen_cb, int fd, target_ulong fstat_addr); +void semihost_sys_remove(CPUState *cs, gdb_syscall_complete_cb complete, + target_ulong fname, target_ulong fname_len); + #endif /* SEMIHOSTING_SYSCALLS_H */ diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index 68e13d9077412..f4ce3851a4db5 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -484,18 +484,7 @@ void do_common_semihosting(CPUState *cs) case TARGET_SYS_REMOVE: GET_ARG(0); GET_ARG(1); - if (use_gdb_syscalls()) { - gdb_do_syscall(common_semi_cb, "unlink,%s", - arg0, (int)arg1 + 1); - break; - } - s = lock_user_string(arg0); - if (!s) { - goto do_fault; - } - ret = remove(s); - unlock_user(s, arg0, 0); - common_semi_cb(cs, ret, ret ? errno : 0); + semihost_sys_remove(cs, common_semi_cb, arg0, arg1 + 1); break; case TARGET_SYS_RENAME: diff --git a/semihosting/syscalls.c b/semihosting/syscalls.c index fff9550c89440..5ec4e8f8271fa 100644 --- a/semihosting/syscalls.c +++ b/semihosting/syscalls.c @@ -133,6 +133,18 @@ static void gdb_fstat(CPUState *cs, gdb_syscall_complete_cb complete, gdb_do_syscall(complete, "fstat,%x,%x", (target_ulong)gf->hostfd, addr); } +static void gdb_remove(CPUState *cs, gdb_syscall_complete_cb complete, + target_ulong fname, target_ulong fname_len) +{ + int len = validate_strlen(cs, fname, fname_len); + if (len < 0) { + complete(cs, -1, -len); + return; + } + + gdb_do_syscall(complete, "unlink,%s", fname, len); +} + /* * Host semihosting syscall implementations. */ @@ -277,6 +289,24 @@ static void host_flen(CPUState *cs, gdb_syscall_complete_cb complete, } } +static void host_remove(CPUState *cs, gdb_syscall_complete_cb complete, + target_ulong fname, target_ulong fname_len) +{ + CPUArchState *env G_GNUC_UNUSED = cs->env_ptr; + char *p; + int ret; + + ret = validate_lock_user_string(&p, cs, fname, fname_len); + if (ret < 0) { + complete(cs, -1, -ret); + return; + } + + ret = remove(p); + complete(cs, ret, ret ? errno : 0); + unlock_user(p, fname, 0); +} + /* * Static file semihosting syscall implementations. */ @@ -522,3 +552,13 @@ void semihost_sys_flen(CPUState *cs, gdb_syscall_complete_cb fstat_cb, g_assert_not_reached(); } } + +void semihost_sys_remove(CPUState *cs, gdb_syscall_complete_cb complete, + target_ulong fname, target_ulong fname_len) +{ + if (use_gdb_syscalls()) { + gdb_remove(cs, complete, fname, fname_len); + } else { + host_remove(cs, complete, fname, fname_len); + } +} From 5a0d1b1059cac604f2f8a10f183d1f9150f57ef3 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Fri, 29 Apr 2022 10:11:31 -0700 Subject: [PATCH 38/69] semihosting: Split out semihost_sys_rename Split out the non-ARM specific portions of SYS_RENAME to a reusable function. Reviewed-by: Luc Michel Signed-off-by: Richard Henderson (cherry picked from commit 25a95da0beeb854570f974028b77cd35826433b7) --- include/semihosting/syscalls.h | 4 +++ semihosting/arm-compat-semi.c | 21 +------------ semihosting/syscalls.c | 57 ++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 20 deletions(-) diff --git a/include/semihosting/syscalls.h b/include/semihosting/syscalls.h index 748a4b5e47a31..21430aa0ef405 100644 --- a/include/semihosting/syscalls.h +++ b/include/semihosting/syscalls.h @@ -52,4 +52,8 @@ void semihost_sys_flen(CPUState *cs, gdb_syscall_complete_cb fstat_cb, void semihost_sys_remove(CPUState *cs, gdb_syscall_complete_cb complete, target_ulong fname, target_ulong fname_len); +void semihost_sys_rename(CPUState *cs, gdb_syscall_complete_cb complete, + target_ulong oname, target_ulong oname_len, + target_ulong nname, target_ulong nname_len); + #endif /* SEMIHOSTING_SYSCALLS_H */ diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index f4ce3851a4db5..14d37ac9dafc5 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -492,26 +492,7 @@ void do_common_semihosting(CPUState *cs) GET_ARG(1); GET_ARG(2); GET_ARG(3); - if (use_gdb_syscalls()) { - gdb_do_syscall(common_semi_cb, "rename,%s,%s", - arg0, (int)arg1 + 1, arg2, (int)arg3 + 1); - } else { - char *s2; - - s = lock_user_string(arg0); - if (!s) { - goto do_fault; - } - s2 = lock_user_string(arg2); - if (!s2) { - unlock_user(s, arg0, 0); - goto do_fault; - } - ret = rename(s, s2); - unlock_user(s2, arg2, 0); - unlock_user(s, arg0, 0); - common_semi_cb(cs, ret, ret ? errno : 0); - } + semihost_sys_rename(cs, common_semi_cb, arg0, arg1 + 1, arg2, arg3 + 1); break; case TARGET_SYS_CLOCK: diff --git a/semihosting/syscalls.c b/semihosting/syscalls.c index 5ec4e8f8271fa..223916b110032 100644 --- a/semihosting/syscalls.c +++ b/semihosting/syscalls.c @@ -145,6 +145,26 @@ static void gdb_remove(CPUState *cs, gdb_syscall_complete_cb complete, gdb_do_syscall(complete, "unlink,%s", fname, len); } +static void gdb_rename(CPUState *cs, gdb_syscall_complete_cb complete, + target_ulong oname, target_ulong oname_len, + target_ulong nname, target_ulong nname_len) +{ + int olen, nlen; + + olen = validate_strlen(cs, oname, oname_len); + if (olen < 0) { + complete(cs, -1, -olen); + return; + } + nlen = validate_strlen(cs, nname, nname_len); + if (nlen < 0) { + complete(cs, -1, -nlen); + return; + } + + gdb_do_syscall(complete, "rename,%s,%s", oname, olen, nname, nlen); +} + /* * Host semihosting syscall implementations. */ @@ -307,6 +327,32 @@ static void host_remove(CPUState *cs, gdb_syscall_complete_cb complete, unlock_user(p, fname, 0); } +static void host_rename(CPUState *cs, gdb_syscall_complete_cb complete, + target_ulong oname, target_ulong oname_len, + target_ulong nname, target_ulong nname_len) +{ + CPUArchState *env G_GNUC_UNUSED = cs->env_ptr; + char *ostr, *nstr; + int ret; + + ret = validate_lock_user_string(&ostr, cs, oname, oname_len); + if (ret < 0) { + complete(cs, -1, -ret); + return; + } + ret = validate_lock_user_string(&nstr, cs, nname, nname_len); + if (ret < 0) { + unlock_user(ostr, oname, 0); + complete(cs, -1, -ret); + return; + } + + ret = rename(ostr, nstr); + complete(cs, ret, ret ? errno : 0); + unlock_user(ostr, oname, 0); + unlock_user(nstr, nname, 0); +} + /* * Static file semihosting syscall implementations. */ @@ -562,3 +608,14 @@ void semihost_sys_remove(CPUState *cs, gdb_syscall_complete_cb complete, host_remove(cs, complete, fname, fname_len); } } + +void semihost_sys_rename(CPUState *cs, gdb_syscall_complete_cb complete, + target_ulong oname, target_ulong oname_len, + target_ulong nname, target_ulong nname_len) +{ + if (use_gdb_syscalls()) { + gdb_rename(cs, complete, oname, oname_len, nname, nname_len); + } else { + host_rename(cs, complete, oname, oname_len, nname, nname_len); + } +} From 60bdfa9c118195a668a8ffc87312197b667253b9 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Fri, 29 Apr 2022 13:57:19 -0700 Subject: [PATCH 39/69] semihosting: Split out semihost_sys_system Split out the non-ARM specific portions of SYS_SYSTEM to a reusable function. Reviewed-by: Luc Michel Signed-off-by: Richard Henderson (cherry picked from commit 90d8e0b09c4a0085bb12e9659eb33ea3773d7ccc) --- include/semihosting/syscalls.h | 3 +++ semihosting/arm-compat-semi.c | 12 +--------- semihosting/syscalls.c | 40 ++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/include/semihosting/syscalls.h b/include/semihosting/syscalls.h index 21430aa0ef405..c9f9e66be11f6 100644 --- a/include/semihosting/syscalls.h +++ b/include/semihosting/syscalls.h @@ -56,4 +56,7 @@ void semihost_sys_rename(CPUState *cs, gdb_syscall_complete_cb complete, target_ulong oname, target_ulong oname_len, target_ulong nname, target_ulong nname_len); +void semihost_sys_system(CPUState *cs, gdb_syscall_complete_cb complete, + target_ulong cmd, target_ulong cmd_len); + #endif /* SEMIHOSTING_SYSCALLS_H */ diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index 14d37ac9dafc5..7ab6afd7fc8d6 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -507,17 +507,7 @@ void do_common_semihosting(CPUState *cs) case TARGET_SYS_SYSTEM: GET_ARG(0); GET_ARG(1); - if (use_gdb_syscalls()) { - gdb_do_syscall(common_semi_cb, "system,%s", arg0, (int)arg1 + 1); - break; - } - s = lock_user_string(arg0); - if (!s) { - goto do_fault; - } - ret = system(s); - unlock_user(s, arg0, 0); - common_semi_cb(cs, ret, ret == -1 ? errno : 0); + semihost_sys_system(cs, common_semi_cb, arg0, arg1 + 1); break; case TARGET_SYS_ERRNO: diff --git a/semihosting/syscalls.c b/semihosting/syscalls.c index 223916b110032..de846ced32fb0 100644 --- a/semihosting/syscalls.c +++ b/semihosting/syscalls.c @@ -165,6 +165,18 @@ static void gdb_rename(CPUState *cs, gdb_syscall_complete_cb complete, gdb_do_syscall(complete, "rename,%s,%s", oname, olen, nname, nlen); } +static void gdb_system(CPUState *cs, gdb_syscall_complete_cb complete, + target_ulong cmd, target_ulong cmd_len) +{ + int len = validate_strlen(cs, cmd, cmd_len); + if (len < 0) { + complete(cs, -1, -len); + return; + } + + gdb_do_syscall(complete, "system,%s", cmd, len); +} + /* * Host semihosting syscall implementations. */ @@ -353,6 +365,24 @@ static void host_rename(CPUState *cs, gdb_syscall_complete_cb complete, unlock_user(nstr, nname, 0); } +static void host_system(CPUState *cs, gdb_syscall_complete_cb complete, + target_ulong cmd, target_ulong cmd_len) +{ + CPUArchState *env G_GNUC_UNUSED = cs->env_ptr; + char *p; + int ret; + + ret = validate_lock_user_string(&p, cs, cmd, cmd_len); + if (ret < 0) { + complete(cs, -1, -ret); + return; + } + + ret = system(p); + complete(cs, ret, ret == -1 ? errno : 0); + unlock_user(p, cmd, 0); +} + /* * Static file semihosting syscall implementations. */ @@ -619,3 +649,13 @@ void semihost_sys_rename(CPUState *cs, gdb_syscall_complete_cb complete, host_rename(cs, complete, oname, oname_len, nname, nname_len); } } + +void semihost_sys_system(CPUState *cs, gdb_syscall_complete_cb complete, + target_ulong cmd, target_ulong cmd_len) +{ + if (use_gdb_syscalls()) { + gdb_system(cs, complete, cmd, cmd_len); + } else { + host_system(cs, complete, cmd, cmd_len); + } +} From 3b7f4ee5612b575638037c5a913ea56180f8dac0 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Fri, 29 Apr 2022 15:45:25 -0700 Subject: [PATCH 40/69] semihosting: Create semihost_sys_{stat,fstat} These syscalls will be used by m68k and nios2 semihosting. Reviewed-by: Luc Michel Signed-off-by: Richard Henderson (cherry picked from commit dffeb7756674af4577e77b4510f1cea5ac585ca0) --- include/semihosting/syscalls.h | 7 ++ semihosting/syscalls.c | 137 +++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) diff --git a/include/semihosting/syscalls.h b/include/semihosting/syscalls.h index c9f9e66be11f6..ecc97751a9fc4 100644 --- a/include/semihosting/syscalls.h +++ b/include/semihosting/syscalls.h @@ -49,6 +49,13 @@ void semihost_sys_flen(CPUState *cs, gdb_syscall_complete_cb fstat_cb, gdb_syscall_complete_cb flen_cb, int fd, target_ulong fstat_addr); +void semihost_sys_fstat(CPUState *cs, gdb_syscall_complete_cb complete, + int fd, target_ulong addr); + +void semihost_sys_stat(CPUState *cs, gdb_syscall_complete_cb complete, + target_ulong fname, target_ulong fname_len, + target_ulong addr); + void semihost_sys_remove(CPUState *cs, gdb_syscall_complete_cb complete, target_ulong fname, target_ulong fname_len); diff --git a/semihosting/syscalls.c b/semihosting/syscalls.c index de846ced32fb0..d21064716d53b 100644 --- a/semihosting/syscalls.c +++ b/semihosting/syscalls.c @@ -63,6 +63,52 @@ static int validate_lock_user_string(char **pstr, CPUState *cs, return ret; } +/* + * TODO: Note that gdb always stores the stat structure big-endian. + * So far, that's ok, as the only two targets using this are also + * big-endian. Until we do something with gdb, also produce the + * same big-endian result from the host. + */ +static int copy_stat_to_user(CPUState *cs, target_ulong addr, + const struct stat *s) +{ + CPUArchState *env G_GNUC_UNUSED = cs->env_ptr; + struct gdb_stat *p; + + if (s->st_dev != (uint32_t)s->st_dev || + s->st_ino != (uint32_t)s->st_ino) { + return -EOVERFLOW; + } + + p = lock_user(VERIFY_WRITE, addr, sizeof(struct gdb_stat), 0); + if (!p) { + return -EFAULT; + } + + p->gdb_st_dev = cpu_to_be32(s->st_dev); + p->gdb_st_ino = cpu_to_be32(s->st_ino); + p->gdb_st_mode = cpu_to_be32(s->st_mode); + p->gdb_st_nlink = cpu_to_be32(s->st_nlink); + p->gdb_st_uid = cpu_to_be32(s->st_uid); + p->gdb_st_gid = cpu_to_be32(s->st_gid); + p->gdb_st_rdev = cpu_to_be32(s->st_rdev); + p->gdb_st_size = cpu_to_be64(s->st_size); +#ifdef _WIN32 + /* Windows stat is missing some fields. */ + p->gdb_st_blksize = 0; + p->gdb_st_blocks = 0; +#else + p->gdb_st_blksize = cpu_to_be64(s->st_blksize); + p->gdb_st_blocks = cpu_to_be64(s->st_blocks); +#endif + p->gdb_st_atime = cpu_to_be32(s->st_atime); + p->gdb_st_mtime = cpu_to_be32(s->st_mtime); + p->gdb_st_ctime = cpu_to_be32(s->st_ctime); + + unlock_user(p, addr, sizeof(struct gdb_stat)); + return 0; +} + /* * GDB semihosting syscall implementations. */ @@ -133,6 +179,19 @@ static void gdb_fstat(CPUState *cs, gdb_syscall_complete_cb complete, gdb_do_syscall(complete, "fstat,%x,%x", (target_ulong)gf->hostfd, addr); } +static void gdb_stat(CPUState *cs, gdb_syscall_complete_cb complete, + target_ulong fname, target_ulong fname_len, + target_ulong addr) +{ + int len = validate_strlen(cs, fname, fname_len); + if (len < 0) { + complete(cs, -1, -len); + return; + } + + gdb_do_syscall(complete, "stat,%s,%x", fname, len, addr); +} + static void gdb_remove(CPUState *cs, gdb_syscall_complete_cb complete, target_ulong fname, target_ulong fname_len) { @@ -321,6 +380,51 @@ static void host_flen(CPUState *cs, gdb_syscall_complete_cb complete, } } +static void host_fstat(CPUState *cs, gdb_syscall_complete_cb complete, + GuestFD *gf, target_ulong addr) +{ + struct stat buf; + int ret; + + ret = fstat(gf->hostfd, &buf); + if (ret) { + complete(cs, -1, errno); + return; + } + ret = copy_stat_to_user(cs, addr, &buf); + complete(cs, ret ? -1 : 0, ret ? -ret : 0); +} + +static void host_stat(CPUState *cs, gdb_syscall_complete_cb complete, + target_ulong fname, target_ulong fname_len, + target_ulong addr) +{ + CPUArchState *env G_GNUC_UNUSED = cs->env_ptr; + struct stat buf; + char *name; + int ret, err; + + ret = validate_lock_user_string(&name, cs, fname, fname_len); + if (ret < 0) { + complete(cs, -1, -ret); + return; + } + + ret = stat(name, &buf); + if (ret) { + err = errno; + } else { + ret = copy_stat_to_user(cs, addr, &buf); + err = 0; + if (ret < 0) { + err = -ret; + ret = -1; + } + } + complete(cs, ret, err); + unlock_user(name, fname, 0); +} + static void host_remove(CPUState *cs, gdb_syscall_complete_cb complete, target_ulong fname, target_ulong fname_len) { @@ -629,6 +733,39 @@ void semihost_sys_flen(CPUState *cs, gdb_syscall_complete_cb fstat_cb, } } +void semihost_sys_fstat(CPUState *cs, gdb_syscall_complete_cb complete, + int fd, target_ulong addr) +{ + GuestFD *gf = get_guestfd(fd); + + if (!gf) { + complete(cs, -1, EBADF); + return; + } + switch (gf->type) { + case GuestFDGDB: + gdb_fstat(cs, complete, gf, addr); + break; + case GuestFDHost: + host_fstat(cs, complete, gf, addr); + break; + case GuestFDStatic: + default: + g_assert_not_reached(); + } +} + +void semihost_sys_stat(CPUState *cs, gdb_syscall_complete_cb complete, + target_ulong fname, target_ulong fname_len, + target_ulong addr) +{ + if (use_gdb_syscalls()) { + gdb_stat(cs, complete, fname, fname_len, addr); + } else { + host_stat(cs, complete, fname, fname_len, addr); + } +} + void semihost_sys_remove(CPUState *cs, gdb_syscall_complete_cb complete, target_ulong fname, target_ulong fname_len) { From 08530d4e32ef41695553c90638cef85e0b660e7b Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Fri, 29 Apr 2022 16:05:49 -0700 Subject: [PATCH 41/69] semihosting: Create semihost_sys_gettimeofday This syscall will be used by m68k and nios2 semihosting. Reviewed-by: Luc Michel Signed-off-by: Richard Henderson (cherry picked from commit 1875dab0eea908b2ceaf74d1204f1e72c69d3a73) --- include/semihosting/syscalls.h | 3 +++ semihosting/syscalls.c | 42 ++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/include/semihosting/syscalls.h b/include/semihosting/syscalls.h index ecc97751a9fc4..347200cb9f974 100644 --- a/include/semihosting/syscalls.h +++ b/include/semihosting/syscalls.h @@ -66,4 +66,7 @@ void semihost_sys_rename(CPUState *cs, gdb_syscall_complete_cb complete, void semihost_sys_system(CPUState *cs, gdb_syscall_complete_cb complete, target_ulong cmd, target_ulong cmd_len); +void semihost_sys_gettimeofday(CPUState *cs, gdb_syscall_complete_cb complete, + target_ulong tv_addr, target_ulong tz_addr); + #endif /* SEMIHOSTING_SYSCALLS_H */ diff --git a/semihosting/syscalls.c b/semihosting/syscalls.c index d21064716d53b..db1e9f6cc9b36 100644 --- a/semihosting/syscalls.c +++ b/semihosting/syscalls.c @@ -236,6 +236,12 @@ static void gdb_system(CPUState *cs, gdb_syscall_complete_cb complete, gdb_do_syscall(complete, "system,%s", cmd, len); } +static void gdb_gettimeofday(CPUState *cs, gdb_syscall_complete_cb complete, + target_ulong tv_addr, target_ulong tz_addr) +{ + gdb_do_syscall(complete, "gettimeofday,%x,%x", tv_addr, tz_addr); +} + /* * Host semihosting syscall implementations. */ @@ -487,6 +493,32 @@ static void host_system(CPUState *cs, gdb_syscall_complete_cb complete, unlock_user(p, cmd, 0); } +static void host_gettimeofday(CPUState *cs, gdb_syscall_complete_cb complete, + target_ulong tv_addr, target_ulong tz_addr) +{ + CPUArchState *env G_GNUC_UNUSED = cs->env_ptr; + struct gdb_timeval *p; + int64_t rt; + + /* GDB fails on non-null TZ, so be consistent. */ + if (tz_addr != 0) { + complete(cs, -1, EINVAL); + return; + } + + p = lock_user(VERIFY_WRITE, tv_addr, sizeof(struct gdb_timeval), 0); + if (!p) { + complete(cs, -1, EFAULT); + return; + } + + /* TODO: Like stat, gdb always produces big-endian results; match it. */ + rt = g_get_real_time(); + p->tv_sec = cpu_to_be32(rt / G_USEC_PER_SEC); + p->tv_usec = cpu_to_be64(rt % G_USEC_PER_SEC); + unlock_user(p, tv_addr, sizeof(struct gdb_timeval)); +} + /* * Static file semihosting syscall implementations. */ @@ -796,3 +828,13 @@ void semihost_sys_system(CPUState *cs, gdb_syscall_complete_cb complete, host_system(cs, complete, cmd, cmd_len); } } + +void semihost_sys_gettimeofday(CPUState *cs, gdb_syscall_complete_cb complete, + target_ulong tv_addr, target_ulong tz_addr) +{ + if (use_gdb_syscalls()) { + gdb_gettimeofday(cs, complete, tv_addr, tz_addr); + } else { + host_gettimeofday(cs, complete, tv_addr, tz_addr); + } +} From ec9f2914f0588dfee5b28ec31c806c07c6016264 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Fri, 29 Apr 2022 16:21:43 -0700 Subject: [PATCH 42/69] gdbstub: Adjust gdb_syscall_complete_cb declaration Change 'ret' to uint64_t. This resolves a FIXME in the m68k and nios2 semihosting that we've lost data. Change 'err' to int. There is nothing target-specific about the width of the errno value. Reviewed-by: Luc Michel Signed-off-by: Richard Henderson (cherry picked from commit 64c8c6a99221877f0e92f973c66e0e66a10ab2ff) --- gdbstub.c | 7 ++++--- include/exec/gdbstub.h | 3 +-- semihosting/arm-compat-semi.c | 12 +++++------- semihosting/console.c | 7 +++---- semihosting/syscalls.c | 2 +- target/m68k/m68k-semi.c | 10 +++------- target/nios2/nios2-semi.c | 8 +++----- 7 files changed, 20 insertions(+), 29 deletions(-) diff --git a/gdbstub.c b/gdbstub.c index c8375e3c3ffea..a56660ad3263e 100644 --- a/gdbstub.c +++ b/gdbstub.c @@ -1863,11 +1863,12 @@ static void handle_read_all_regs(GArray *params, void *user_ctx) static void handle_file_io(GArray *params, void *user_ctx) { if (params->len >= 1 && gdbserver_state.current_syscall_cb) { - target_ulong ret, err; + uint64_t ret; + int err; - ret = (target_ulong)get_param(params, 0)->val_ull; + ret = get_param(params, 0)->val_ull; if (params->len >= 2) { - err = (target_ulong)get_param(params, 1)->val_ull; + err = get_param(params, 1)->val_ull; } else { err = 0; } diff --git a/include/exec/gdbstub.h b/include/exec/gdbstub.h index 2d8e85b52a66a..f64133178b229 100644 --- a/include/exec/gdbstub.h +++ b/include/exec/gdbstub.h @@ -74,8 +74,7 @@ struct gdb_timeval { #ifdef NEED_CPU_H #include "cpu.h" -typedef void (*gdb_syscall_complete_cb)(CPUState *cpu, - target_ulong ret, target_ulong err); +typedef void (*gdb_syscall_complete_cb)(CPUState *cpu, uint64_t ret, int err); /** * gdb_do_syscall: diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index 7ab6afd7fc8d6..1b0505987a7dc 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -215,7 +215,7 @@ static inline uint32_t get_swi_errno(CPUState *cs) #endif } -static void common_semi_cb(CPUState *cs, target_ulong ret, target_ulong err) +static void common_semi_cb(CPUState *cs, uint64_t ret, int err) { if (err) { #ifdef CONFIG_USER_ONLY @@ -232,7 +232,7 @@ static void common_semi_cb(CPUState *cs, target_ulong ret, target_ulong err) * SYS_READ and SYS_WRITE always return the number of bytes not read/written. * There is no error condition, other than returning the original length. */ -static void common_semi_rw_cb(CPUState *cs, target_ulong ret, target_ulong err) +static void common_semi_rw_cb(CPUState *cs, uint64_t ret, int err) { /* Recover the original length from the third argument. */ CPUArchState *env G_GNUC_UNUSED = cs->env_ptr; @@ -251,8 +251,7 @@ static void common_semi_rw_cb(CPUState *cs, target_ulong ret, target_ulong err) * Convert from Posix ret+errno to Arm SYS_ISTTY return values. * With gdbstub, err is only ever set for protocol errors to EIO. */ -static void common_semi_istty_cb(CPUState *cs, target_ulong ret, - target_ulong err) +static void common_semi_istty_cb(CPUState *cs, uint64_t ret, int err) { if (err) { ret = (err == ENOTTY ? 0 : -1); @@ -263,8 +262,7 @@ static void common_semi_istty_cb(CPUState *cs, target_ulong ret, /* * SYS_SEEK returns 0 on success, not the resulting offset. */ -static void common_semi_seek_cb(CPUState *cs, target_ulong ret, - target_ulong err) +static void common_semi_seek_cb(CPUState *cs, uint64_t ret, int err) { if (!err) { ret = 0; @@ -285,7 +283,7 @@ static target_ulong common_semi_flen_buf(CPUState *cs) } static void -common_semi_flen_fstat_cb(CPUState *cs, target_ulong ret, target_ulong err) +common_semi_flen_fstat_cb(CPUState *cs, uint64_t ret, int err) { if (!err) { /* The size is always stored in big-endian order, extract the value. */ diff --git a/semihosting/console.c b/semihosting/console.c index ef6958d8445ab..4e49202b2a6dc 100644 --- a/semihosting/console.c +++ b/semihosting/console.c @@ -64,11 +64,10 @@ static GString *copy_user_string(CPUArchState *env, target_ulong addr) return s; } -static void semihosting_cb(CPUState *cs, target_ulong ret, target_ulong err) +static void semihosting_cb(CPUState *cs, uint64_t ret, int err) { - if (ret == (target_ulong) -1) { - qemu_log("%s: gdb console output failed ("TARGET_FMT_ld")", - __func__, err); + if (err) { + qemu_log("%s: gdb console output failed (%d)\n", __func__, err); } } diff --git a/semihosting/syscalls.c b/semihosting/syscalls.c index db1e9f6cc9b36..13a9bdeda67b0 100644 --- a/semihosting/syscalls.c +++ b/semihosting/syscalls.c @@ -115,7 +115,7 @@ static int copy_stat_to_user(CPUState *cs, target_ulong addr, static gdb_syscall_complete_cb gdb_open_complete; -static void gdb_open_cb(CPUState *cs, target_ulong ret, target_ulong err) +static void gdb_open_cb(CPUState *cs, uint64_t ret, int err) { if (!err) { int guestfd = alloc_guestfd(); diff --git a/target/m68k/m68k-semi.c b/target/m68k/m68k-semi.c index 1d72ab270bed6..8a717c8a1c3de 100644 --- a/target/m68k/m68k-semi.c +++ b/target/m68k/m68k-semi.c @@ -95,7 +95,7 @@ static void translate_stat(CPUM68KState *env, target_ulong addr, struct stat *s) unlock_user(p, addr, sizeof(struct gdb_stat)); } -static void m68k_semi_return_u32(CPUM68KState *env, uint32_t ret, uint32_t err) +static void m68k_semi_return_u32(CPUM68KState *env, uint32_t ret, int err) { target_ulong args = env->dregs[1]; if (put_user_u32(ret, args) || @@ -110,7 +110,7 @@ static void m68k_semi_return_u32(CPUM68KState *env, uint32_t ret, uint32_t err) } } -static void m68k_semi_return_u64(CPUM68KState *env, uint64_t ret, uint32_t err) +static void m68k_semi_return_u64(CPUM68KState *env, uint64_t ret, int err) { target_ulong args = env->dregs[1]; if (put_user_u32(ret >> 32, args) || @@ -124,16 +124,12 @@ static void m68k_semi_return_u64(CPUM68KState *env, uint64_t ret, uint32_t err) static int m68k_semi_is_fseek; -static void m68k_semi_cb(CPUState *cs, target_ulong ret, target_ulong err) +static void m68k_semi_cb(CPUState *cs, uint64_t ret, int err) { M68kCPU *cpu = M68K_CPU(cs); CPUM68KState *env = &cpu->env; if (m68k_semi_is_fseek) { - /* - * FIXME: We've already lost the high bits of the fseek - * return value. - */ m68k_semi_return_u64(env, ret, err); m68k_semi_is_fseek = 0; } else { diff --git a/target/nios2/nios2-semi.c b/target/nios2/nios2-semi.c index 0f8ef785c1ff8..098a0021a72c1 100644 --- a/target/nios2/nios2-semi.c +++ b/target/nios2/nios2-semi.c @@ -109,8 +109,7 @@ static bool translate_stat(CPUNios2State *env, target_ulong addr, return true; } -static void nios2_semi_return_u32(CPUNios2State *env, uint32_t ret, - uint32_t err) +static void nios2_semi_return_u32(CPUNios2State *env, uint32_t ret, int err) { target_ulong args = env->regs[R_ARG1]; if (put_user_u32(ret, args) || @@ -125,8 +124,7 @@ static void nios2_semi_return_u32(CPUNios2State *env, uint32_t ret, } } -static void nios2_semi_return_u64(CPUNios2State *env, uint64_t ret, - uint32_t err) +static void nios2_semi_return_u64(CPUNios2State *env, uint64_t ret, int err) { target_ulong args = env->regs[R_ARG1]; if (put_user_u32(ret >> 32, args) || @@ -140,7 +138,7 @@ static void nios2_semi_return_u64(CPUNios2State *env, uint64_t ret, static int nios2_semi_is_lseek; -static void nios2_semi_cb(CPUState *cs, target_ulong ret, target_ulong err) +static void nios2_semi_cb(CPUState *cs, uint64_t ret, int err) { Nios2CPU *cpu = NIOS2_CPU(cs); CPUNios2State *env = &cpu->env; From 84cffff88167fb77203703640dc1a2d5766930fe Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Sun, 1 May 2022 12:21:19 -0700 Subject: [PATCH 43/69] semihosting: Pass CPUState to qemu_semihosting_console_inc We don't need CPUArchState, and we do want the CPUState of the thread performing the operation -- use this instead of current_cpu. Reviewed-by: Luc Michel Reviewed-by: Peter Maydell Signed-off-by: Richard Henderson (cherry picked from commit 3367d452b001e91547634756e32246610701df5c) --- include/semihosting/console.h | 4 ++-- linux-user/semihost.c | 2 +- semihosting/arm-compat-semi.c | 2 +- semihosting/console.c | 12 ++++++------ 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/semihosting/console.h b/include/semihosting/console.h index 0238f540f4b19..5241bf3ad7679 100644 --- a/include/semihosting/console.h +++ b/include/semihosting/console.h @@ -39,7 +39,7 @@ void qemu_semihosting_console_outc(CPUArchState *env, target_ulong c); /** * qemu_semihosting_console_inc: - * @env: CPUArchState + * @cs: CPUState * * Receive single character from debug console. This may be the remote * gdb session if a softmmu guest is currently being debugged. As this @@ -51,7 +51,7 @@ void qemu_semihosting_console_outc(CPUArchState *env, target_ulong c); * * Returns: character read OR cpu_loop_exit! */ -target_ulong qemu_semihosting_console_inc(CPUArchState *env); +target_ulong qemu_semihosting_console_inc(CPUState *cs); /** * qemu_semihosting_log_out: diff --git a/linux-user/semihost.c b/linux-user/semihost.c index 17f074ac56524..f14c6ae21df1c 100644 --- a/linux-user/semihost.c +++ b/linux-user/semihost.c @@ -56,7 +56,7 @@ void qemu_semihosting_console_outc(CPUArchState *env, target_ulong addr) * program is expecting more normal behaviour. This is slow but * nothing using semihosting console reading is expecting to be fast. */ -target_ulong qemu_semihosting_console_inc(CPUArchState *env) +target_ulong qemu_semihosting_console_inc(CPUState *cs) { uint8_t c; struct termios old_tio, new_tio; diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index 1b0505987a7dc..40f373077852a 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -428,7 +428,7 @@ void do_common_semihosting(CPUState *cs) break; case TARGET_SYS_READC: - ret = qemu_semihosting_console_inc(env); + ret = qemu_semihosting_console_inc(cs); common_semi_set_ret(cs, ret); break; diff --git a/semihosting/console.c b/semihosting/console.c index 4e49202b2a6dc..17ece6bdcaf42 100644 --- a/semihosting/console.c +++ b/semihosting/console.c @@ -144,17 +144,17 @@ static void console_read(void *opaque, const uint8_t *buf, int size) c->sleeping_cpus = NULL; } -target_ulong qemu_semihosting_console_inc(CPUArchState *env) +target_ulong qemu_semihosting_console_inc(CPUState *cs) { uint8_t ch; SemihostingConsole *c = &console; + g_assert(qemu_mutex_iothread_locked()); - g_assert(current_cpu); if (fifo8_is_empty(&c->fifo)) { - c->sleeping_cpus = g_slist_prepend(c->sleeping_cpus, current_cpu); - current_cpu->halted = 1; - current_cpu->exception_index = EXCP_HALTED; - cpu_loop_exit(current_cpu); + c->sleeping_cpus = g_slist_prepend(c->sleeping_cpus, cs); + cs->halted = 1; + cs->exception_index = EXCP_HALTED; + cpu_loop_exit(cs); /* never returns */ } ch = fifo8_pop(&c->fifo); From 581a9a471e56ded16fa60201a4e28a3197131fff Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Sun, 1 May 2022 12:31:08 -0700 Subject: [PATCH 44/69] semihosting: Expand qemu_semihosting_console_inc to read Allow more than one character to be read at one time. Will be used by m68k and nios2 semihosting for stdio. Reviewed-by: Luc Michel Signed-off-by: Richard Henderson (cherry picked from commit e7fb6f320548c1b0c25d291466a0249ee80d91b6) --- include/semihosting/console.h | 17 +++++++++-------- linux-user/semihost.c | 10 ++++++---- semihosting/arm-compat-semi.c | 11 +++++++++-- semihosting/console.c | 16 ++++++++++++---- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/include/semihosting/console.h b/include/semihosting/console.h index 5241bf3ad7679..39dbf1b062578 100644 --- a/include/semihosting/console.h +++ b/include/semihosting/console.h @@ -38,20 +38,21 @@ int qemu_semihosting_console_outs(CPUArchState *env, target_ulong s); void qemu_semihosting_console_outc(CPUArchState *env, target_ulong c); /** - * qemu_semihosting_console_inc: + * qemu_semihosting_console_read: * @cs: CPUState + * @buf: host buffer + * @len: buffer size + * + * Receive at least one character from debug console. As this call may + * block if no data is available we suspend the CPU and will re-execute the + * instruction when data is there. Therefore two conditions must be met: * - * Receive single character from debug console. This may be the remote - * gdb session if a softmmu guest is currently being debugged. As this - * call may block if no data is available we suspend the CPU and will - * re-execute the instruction when data is there. Therefore two - * conditions must be met: * - CPUState is synchronized before calling this function * - pc is only updated once the character is successfully returned * - * Returns: character read OR cpu_loop_exit! + * Returns: number of characters read, OR cpu_loop_exit! */ -target_ulong qemu_semihosting_console_inc(CPUState *cs); +int qemu_semihosting_console_read(CPUState *cs, void *buf, int len); /** * qemu_semihosting_log_out: diff --git a/linux-user/semihost.c b/linux-user/semihost.c index f14c6ae21df1c..2029fb674c0c4 100644 --- a/linux-user/semihost.c +++ b/linux-user/semihost.c @@ -56,21 +56,23 @@ void qemu_semihosting_console_outc(CPUArchState *env, target_ulong addr) * program is expecting more normal behaviour. This is slow but * nothing using semihosting console reading is expecting to be fast. */ -target_ulong qemu_semihosting_console_inc(CPUState *cs) +int qemu_semihosting_console_read(CPUState *cs, void *buf, int len) { - uint8_t c; + int ret; struct termios old_tio, new_tio; /* Disable line-buffering and echo */ tcgetattr(STDIN_FILENO, &old_tio); new_tio = old_tio; new_tio.c_lflag &= (~ICANON & ~ECHO); + new_tio.c_cc[VMIN] = 1; + new_tio.c_cc[VTIME] = 0; tcsetattr(STDIN_FILENO, TCSANOW, &new_tio); - c = getchar(); + ret = fread(buf, 1, len, stdin); /* restore config */ tcsetattr(STDIN_FILENO, TCSANOW, &old_tio); - return (target_ulong) c; + return ret; } diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index 40f373077852a..fdb143ace88a6 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -428,8 +428,15 @@ void do_common_semihosting(CPUState *cs) break; case TARGET_SYS_READC: - ret = qemu_semihosting_console_inc(cs); - common_semi_set_ret(cs, ret); + { + uint8_t ch; + int ret = qemu_semihosting_console_read(cs, &ch, 1); + if (ret == 1) { + common_semi_cb(cs, ch, 0); + } else { + common_semi_cb(cs, -1, EIO); + } + } break; case TARGET_SYS_ISERROR: diff --git a/semihosting/console.c b/semihosting/console.c index 17ece6bdcaf42..e5ac3f20bac17 100644 --- a/semihosting/console.c +++ b/semihosting/console.c @@ -144,12 +144,14 @@ static void console_read(void *opaque, const uint8_t *buf, int size) c->sleeping_cpus = NULL; } -target_ulong qemu_semihosting_console_inc(CPUState *cs) +int qemu_semihosting_console_read(CPUState *cs, void *buf, int len) { - uint8_t ch; SemihostingConsole *c = &console; + int ret = 0; g_assert(qemu_mutex_iothread_locked()); + + /* Block if the fifo is completely empty. */ if (fifo8_is_empty(&c->fifo)) { c->sleeping_cpus = g_slist_prepend(c->sleeping_cpus, cs); cs->halted = 1; @@ -157,8 +159,14 @@ target_ulong qemu_semihosting_console_inc(CPUState *cs) cpu_loop_exit(cs); /* never returns */ } - ch = fifo8_pop(&c->fifo); - return (target_ulong) ch; + + /* Read until buffer full or fifo exhausted. */ + do { + *(char *)(buf + ret) = fifo8_pop(&c->fifo); + ret++; + } while (ret < len && !fifo8_is_empty(&c->fifo)); + + return ret; } void qemu_semihosting_console_init(void) From f799064f4532d350beec2c8d29d28c4ebb717fc2 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Sun, 1 May 2022 16:59:06 -0700 Subject: [PATCH 45/69] semihosting: Cleanup chardev init Rename qemu_semihosting_connect_chardevs to qemu_semihosting_chardev_init; pass the result directly to qemu_semihosting_console_init. Store the chardev in SemihostingConsole instead of SemihostingConfig, which lets us drop semihosting_get_chardev. Reviewed-by: Luc Michel Signed-off-by: Richard Henderson (cherry picked from commit fb08790b35174a98301ecbac4d5234d0cbfebea0) --- include/semihosting/semihost.h | 13 ++----------- semihosting/config.c | 17 +++++++---------- semihosting/console.c | 31 +++++++++++++++---------------- softmmu/vl.c | 3 +-- stubs/semihost.c | 6 +----- 5 files changed, 26 insertions(+), 44 deletions(-) diff --git a/include/semihosting/semihost.h b/include/semihosting/semihost.h index 0c55ade3ac1c1..5b36a76f0848e 100644 --- a/include/semihosting/semihost.h +++ b/include/semihosting/semihost.h @@ -51,14 +51,6 @@ static inline const char *semihosting_get_cmdline(void) { return NULL; } - -static inline Chardev *semihosting_get_chardev(void) -{ - return NULL; -} -static inline void qemu_semihosting_console_init(void) -{ -} #else /* !CONFIG_USER_ONLY */ bool semihosting_enabled(void); SemihostingTarget semihosting_get_target(void); @@ -66,12 +58,11 @@ const char *semihosting_get_arg(int i); int semihosting_get_argc(void); const char *semihosting_get_cmdline(void); void semihosting_arg_fallback(const char *file, const char *cmd); -Chardev *semihosting_get_chardev(void); /* for vl.c hooks */ void qemu_semihosting_enable(void); int qemu_semihosting_config_options(const char *opt); -void qemu_semihosting_connect_chardevs(void); -void qemu_semihosting_console_init(void); +void qemu_semihosting_chardev_init(void); +void qemu_semihosting_console_init(Chardev *); #endif /* CONFIG_USER_ONLY */ #endif /* SEMIHOST_H */ diff --git a/semihosting/config.c b/semihosting/config.c index 3afacf54ab25e..e171d4d6bc336 100644 --- a/semihosting/config.c +++ b/semihosting/config.c @@ -51,7 +51,6 @@ QemuOptsList qemu_semihosting_config_opts = { typedef struct SemihostingConfig { bool enabled; SemihostingTarget target; - Chardev *chardev; char **argv; int argc; const char *cmdline; /* concatenated argv */ @@ -122,11 +121,6 @@ void semihosting_arg_fallback(const char *file, const char *cmd) } } -Chardev *semihosting_get_chardev(void) -{ - return semihosting.chardev; -} - void qemu_semihosting_enable(void) { semihosting.enabled = true; @@ -172,16 +166,19 @@ int qemu_semihosting_config_options(const char *optarg) return 0; } -void qemu_semihosting_connect_chardevs(void) +/* We had to defer this until chardevs were created */ +void qemu_semihosting_chardev_init(void) { - /* We had to defer this until chardevs were created */ + Chardev *chr = NULL; + if (semihost_chardev) { - Chardev *chr = qemu_chr_find(semihost_chardev); + chr = qemu_chr_find(semihost_chardev); if (chr == NULL) { error_report("semihosting chardev '%s' not found", semihost_chardev); exit(1); } - semihosting.chardev = chr; } + + qemu_semihosting_console_init(chr); } diff --git a/semihosting/console.c b/semihosting/console.c index e5ac3f20bac17..1d16a290c4333 100644 --- a/semihosting/console.c +++ b/semihosting/console.c @@ -27,11 +27,21 @@ #include "qapi/error.h" #include "qemu/fifo8.h" +/* Access to this structure is protected by the BQL */ +typedef struct SemihostingConsole { + CharBackend backend; + Chardev *chr; + GSList *sleeping_cpus; + bool got; + Fifo8 fifo; +} SemihostingConsole; + +static SemihostingConsole console; + int qemu_semihosting_log_out(const char *s, int len) { - Chardev *chardev = semihosting_get_chardev(); - if (chardev) { - return qemu_chr_write_all(chardev, (uint8_t *) s, len); + if (console.chr) { + return qemu_chr_write_all(console.chr, (uint8_t *) s, len); } else { return write(STDERR_FILENO, s, len); } @@ -106,16 +116,6 @@ void qemu_semihosting_console_outc(CPUArchState *env, target_ulong addr) #define FIFO_SIZE 1024 -/* Access to this structure is protected by the BQL */ -typedef struct SemihostingConsole { - CharBackend backend; - GSList *sleeping_cpus; - bool got; - Fifo8 fifo; -} SemihostingConsole; - -static SemihostingConsole console; - static int console_can_read(void *opaque) { SemihostingConsole *c = opaque; @@ -169,10 +169,9 @@ int qemu_semihosting_console_read(CPUState *cs, void *buf, int len) return ret; } -void qemu_semihosting_console_init(void) +void qemu_semihosting_console_init(Chardev *chr) { - Chardev *chr = semihosting_get_chardev(); - + console.chr = chr; if (chr) { fifo8_create(&console.fifo, FIFO_SIZE); qemu_chr_fe_init(&console.backend, chr, &error_abort); diff --git a/softmmu/vl.c b/softmmu/vl.c index 6f646531a0aeb..5f125171fb288 100644 --- a/softmmu/vl.c +++ b/softmmu/vl.c @@ -2016,8 +2016,7 @@ static void qemu_create_late_backends(void) exit(1); /* now chardevs have been created we may have semihosting to connect */ - qemu_semihosting_connect_chardevs(); - qemu_semihosting_console_init(); + qemu_semihosting_chardev_init(); } static bool have_custom_ram_size(void) diff --git a/stubs/semihost.c b/stubs/semihost.c index 4bf2cf71b9b41..f486651afbbc6 100644 --- a/stubs/semihost.c +++ b/stubs/semihost.c @@ -65,10 +65,6 @@ void semihosting_arg_fallback(const char *file, const char *cmd) { } -void qemu_semihosting_connect_chardevs(void) -{ -} - -void qemu_semihosting_console_init(void) +void qemu_semihosting_chardev_init(void) { } From d8e8d59546f3a6eb5f6455c772fe04b081e8b265 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Sun, 1 May 2022 12:42:37 -0700 Subject: [PATCH 46/69] semihosting: Create qemu_semihosting_console_write Will replace qemu_semihosting_console_{outs,outc}, but we need more plumbing first. Reviewed-by: Luc Michel Signed-off-by: Richard Henderson (cherry picked from commit cd66f20f614bb492e4e5be11e4b65d58b4a046ca) --- include/semihosting/console.h | 12 ++++++++++++ linux-user/semihost.c | 5 +++++ semihosting/console.c | 9 +++++++++ 3 files changed, 26 insertions(+) diff --git a/include/semihosting/console.h b/include/semihosting/console.h index 39dbf1b062578..6994f23c820b1 100644 --- a/include/semihosting/console.h +++ b/include/semihosting/console.h @@ -54,6 +54,18 @@ void qemu_semihosting_console_outc(CPUArchState *env, target_ulong c); */ int qemu_semihosting_console_read(CPUState *cs, void *buf, int len); +/** + * qemu_semihosting_console_write: + * @buf: host buffer + * @len: buffer size + * + * Write len bytes from buf to the debug console. + * + * Returns: number of bytes written -- this should only ever be short + * on some sort of i/o error. + */ +int qemu_semihosting_console_write(void *buf, int len); + /** * qemu_semihosting_log_out: * @s: pointer to string diff --git a/linux-user/semihost.c b/linux-user/semihost.c index 2029fb674c0c4..871edf993a5d1 100644 --- a/linux-user/semihost.c +++ b/linux-user/semihost.c @@ -76,3 +76,8 @@ int qemu_semihosting_console_read(CPUState *cs, void *buf, int len) return ret; } + +int qemu_semihosting_console_write(void *buf, int len) +{ + return fwrite(buf, 1, len, stderr); +} diff --git a/semihosting/console.c b/semihosting/console.c index 1d16a290c4333..540aa0cd4b7dd 100644 --- a/semihosting/console.c +++ b/semihosting/console.c @@ -169,6 +169,15 @@ int qemu_semihosting_console_read(CPUState *cs, void *buf, int len) return ret; } +int qemu_semihosting_console_write(void *buf, int len) +{ + if (console.chr) { + return qemu_chr_write_all(console.chr, (uint8_t *)buf, len); + } else { + return fwrite(buf, 1, len, stderr); + } +} + void qemu_semihosting_console_init(Chardev *chr) { console.chr = chr; From 3c14324658c21e5cb242751fc24f919300df797f Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Sun, 1 May 2022 13:11:45 -0700 Subject: [PATCH 47/69] semihosting: Add GuestFDConsole MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a GuestFDType for connecting to the semihosting console. Hook up to read, write, isatty, and fstat syscalls. Note that the arm-specific syscall flen cannot be applied to the console, because the console is not a descriptor exposed to the guest. Reviewed-by: Luc Michel Reviewed-by: Alex Bennée Signed-off-by: Richard Henderson (cherry picked from commit 008e147572863a7a54c54403e626aaed3e50574f) --- include/semihosting/guestfd.h | 7 ++-- semihosting/syscalls.c | 68 +++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/include/semihosting/guestfd.h b/include/semihosting/guestfd.h index ef268abe857dc..a7ea1041ea0b6 100644 --- a/include/semihosting/guestfd.h +++ b/include/semihosting/guestfd.h @@ -13,9 +13,10 @@ typedef enum GuestFDType { GuestFDUnused = 0, - GuestFDHost = 1, - GuestFDGDB = 2, - GuestFDStatic = 3, + GuestFDHost, + GuestFDGDB, + GuestFDStatic, + GuestFDConsole, } GuestFDType; /* diff --git a/semihosting/syscalls.c b/semihosting/syscalls.c index 13a9bdeda67b0..9e499b1751a7e 100644 --- a/semihosting/syscalls.c +++ b/semihosting/syscalls.c @@ -10,6 +10,7 @@ #include "exec/gdbstub.h" #include "semihosting/guestfd.h" #include "semihosting/syscalls.h" +#include "semihosting/console.h" #ifdef CONFIG_USER_ONLY #include "qemu.h" #else @@ -577,6 +578,56 @@ static void staticfile_flen(CPUState *cs, gdb_syscall_complete_cb complete, complete(cs, gf->staticfile.len, 0); } +/* + * Console semihosting syscall implementations. + */ + +static void console_read(CPUState *cs, gdb_syscall_complete_cb complete, + GuestFD *gf, target_ulong buf, target_ulong len) +{ + CPUArchState *env G_GNUC_UNUSED = cs->env_ptr; + char *ptr; + int ret; + + ptr = lock_user(VERIFY_WRITE, buf, len, 0); + if (!ptr) { + complete(cs, -1, EFAULT); + return; + } + ret = qemu_semihosting_console_read(cs, ptr, len); + complete(cs, ret, 0); + unlock_user(ptr, buf, ret); +} + +static void console_write(CPUState *cs, gdb_syscall_complete_cb complete, + GuestFD *gf, target_ulong buf, target_ulong len) +{ + CPUArchState *env G_GNUC_UNUSED = cs->env_ptr; + char *ptr = lock_user(VERIFY_READ, buf, len, 1); + int ret; + + if (!ptr) { + complete(cs, -1, EFAULT); + return; + } + ret = qemu_semihosting_console_write(ptr, len); + complete(cs, ret ? ret : -1, ret ? 0 : EIO); + unlock_user(ptr, buf, ret); +} + +static void console_fstat(CPUState *cs, gdb_syscall_complete_cb complete, + GuestFD *gf, target_ulong addr) +{ + static const struct stat tty_buf = { + .st_mode = 020666, /* S_IFCHR, ugo+rw */ + .st_rdev = 5, /* makedev(5, 0) -- linux /dev/tty */ + }; + int ret; + + ret = copy_stat_to_user(cs, addr, &tty_buf); + complete(cs, ret ? -1 : 0, ret ? -ret : 0); +} + /* * Syscall entry points. */ @@ -608,6 +659,7 @@ void semihost_sys_close(CPUState *cs, gdb_syscall_complete_cb complete, int fd) host_close(cs, complete, gf); break; case GuestFDStatic: + case GuestFDConsole: complete(cs, 0, 0); break; default: @@ -637,6 +689,9 @@ void semihost_sys_read_gf(CPUState *cs, gdb_syscall_complete_cb complete, case GuestFDStatic: staticfile_read(cs, complete, gf, buf, len); break; + case GuestFDConsole: + console_read(cs, complete, gf, buf, len); + break; default: g_assert_not_reached(); } @@ -672,6 +727,9 @@ void semihost_sys_write_gf(CPUState *cs, gdb_syscall_complete_cb complete, case GuestFDHost: host_write(cs, complete, gf, buf, len); break; + case GuestFDConsole: + console_write(cs, complete, gf, buf, len); + break; case GuestFDStatic: /* Static files are never open for writing: EBADF. */ complete(cs, -1, EBADF); @@ -712,6 +770,9 @@ void semihost_sys_lseek(CPUState *cs, gdb_syscall_complete_cb complete, case GuestFDStatic: staticfile_lseek(cs, complete, gf, off, gdb_whence); break; + case GuestFDConsole: + complete(cs, -1, ESPIPE); + break; default: g_assert_not_reached(); } @@ -735,6 +796,9 @@ void semihost_sys_isatty(CPUState *cs, gdb_syscall_complete_cb complete, int fd) case GuestFDStatic: complete(cs, 0, ENOTTY); break; + case GuestFDConsole: + complete(cs, 1, 0); + break; default: g_assert_not_reached(); } @@ -760,6 +824,7 @@ void semihost_sys_flen(CPUState *cs, gdb_syscall_complete_cb fstat_cb, case GuestFDStatic: staticfile_flen(cs, flen_cb, gf); break; + case GuestFDConsole: default: g_assert_not_reached(); } @@ -781,6 +846,9 @@ void semihost_sys_fstat(CPUState *cs, gdb_syscall_complete_cb complete, case GuestFDHost: host_fstat(cs, complete, gf, addr); break; + case GuestFDConsole: + console_fstat(cs, complete, gf, addr); + break; case GuestFDStatic: default: g_assert_not_reached(); From 7f2d8179f80a3bd0e953045d75df7b16a893e46a Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Sun, 1 May 2022 17:21:00 -0700 Subject: [PATCH 48/69] semihosting: Create qemu_semihosting_guestfd_init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For arm-compat, initialize console_{in,out}_gf; otherwise, initialize stdio file descriptors. This will go some way to cleaning up arm-compat, and will allow other semihosting to use normal stdio. Reviewed-by: Luc Michel Reviewed-by: Alex Bennée Signed-off-by: Richard Henderson (cherry picked from commit e4a4aaa51b4c71914a6f30ca504ab78e8f695aee) --- include/semihosting/guestfd.h | 7 +++++ include/semihosting/semihost.h | 1 + linux-user/main.c | 9 ++++++ semihosting/console.c | 2 ++ semihosting/guestfd.c | 52 +++++++++++++++++++++++++++------- 5 files changed, 61 insertions(+), 10 deletions(-) diff --git a/include/semihosting/guestfd.h b/include/semihosting/guestfd.h index a7ea1041ea0b6..3d426fedab390 100644 --- a/include/semihosting/guestfd.h +++ b/include/semihosting/guestfd.h @@ -35,6 +35,13 @@ typedef struct GuestFD { }; } GuestFD; +/* + * For ARM semihosting, we have a separate structure for routing + * data for the console which is outside the guest fd address space. + */ +extern GuestFD console_in_gf; +extern GuestFD console_out_gf; + /** * alloc_guestfd: * diff --git a/include/semihosting/semihost.h b/include/semihosting/semihost.h index 5b36a76f0848e..93a3c21b44dea 100644 --- a/include/semihosting/semihost.h +++ b/include/semihosting/semihost.h @@ -64,5 +64,6 @@ int qemu_semihosting_config_options(const char *opt); void qemu_semihosting_chardev_init(void); void qemu_semihosting_console_init(Chardev *); #endif /* CONFIG_USER_ONLY */ +void qemu_semihosting_guestfd_init(void); #endif /* SEMIHOST_H */ diff --git a/linux-user/main.c b/linux-user/main.c index fbc9bcfd5f5f4..ed7c573d13343 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -55,6 +55,10 @@ #include "loader.h" #include "user-mmap.h" +#ifdef CONFIG_SEMIHOSTING +#include "semihosting/semihost.h" +#endif + #ifndef AT_FLAGS_PRESERVE_ARGV0 #define AT_FLAGS_PRESERVE_ARGV0_BIT 0 #define AT_FLAGS_PRESERVE_ARGV0 (1 << AT_FLAGS_PRESERVE_ARGV0_BIT) @@ -894,6 +898,11 @@ int main(int argc, char **argv, char **envp) } gdb_handlesig(cpu, 0); } + +#ifdef CONFIG_SEMIHOSTING + qemu_semihosting_guestfd_init(); +#endif + cpu_loop(env); /* never exits */ return 0; diff --git a/semihosting/console.c b/semihosting/console.c index 540aa0cd4b7dd..955880514e958 100644 --- a/semihosting/console.c +++ b/semihosting/console.c @@ -190,4 +190,6 @@ void qemu_semihosting_console_init(Chardev *chr) NULL, NULL, &console, NULL, true); } + + qemu_semihosting_guestfd_init(); } diff --git a/semihosting/guestfd.c b/semihosting/guestfd.c index e3122ebba93ef..b05c52f26ff5b 100644 --- a/semihosting/guestfd.c +++ b/semihosting/guestfd.c @@ -10,15 +10,56 @@ #include "qemu/osdep.h" #include "exec/gdbstub.h" +#include "semihosting/semihost.h" #include "semihosting/guestfd.h" #ifdef CONFIG_USER_ONLY #include "qemu.h" #else #include "semihosting/softmmu-uaccess.h" +#include CONFIG_DEVICES #endif static GArray *guestfd_array; +#ifdef CONFIG_ARM_COMPATIBLE_SEMIHOSTING +GuestFD console_in_gf; +GuestFD console_out_gf; +#endif + +void qemu_semihosting_guestfd_init(void) +{ + /* New entries zero-initialized, i.e. type GuestFDUnused */ + guestfd_array = g_array_new(FALSE, TRUE, sizeof(GuestFD)); + +#ifdef CONFIG_ARM_COMPATIBLE_SEMIHOSTING + /* For ARM-compat, the console is in a separate namespace. */ + if (use_gdb_syscalls()) { + console_in_gf.type = GuestFDGDB; + console_in_gf.hostfd = 0; + console_out_gf.type = GuestFDGDB; + console_out_gf.hostfd = 2; + } else { + console_in_gf.type = GuestFDConsole; + console_out_gf.type = GuestFDConsole; + } +#else + /* Otherwise, the stdio file descriptors apply. */ + guestfd_array = g_array_set_size(guestfd_array, 3); +#ifndef CONFIG_USER_ONLY + if (!use_gdb_syscalls()) { + GuestFD *gf = &g_array_index(guestfd_array, GuestFD, 0); + gf[0].type = GuestFDConsole; + gf[1].type = GuestFDConsole; + gf[2].type = GuestFDConsole; + return; + } +#endif + associate_guestfd(0, 0); + associate_guestfd(1, 1); + associate_guestfd(2, 2); +#endif +} + /* * Allocate a new guest file descriptor and return it; if we * couldn't allocate a new fd then return -1. @@ -30,11 +71,6 @@ int alloc_guestfd(void) { guint i; - if (!guestfd_array) { - /* New entries zero-initialized, i.e. type GuestFDUnused */ - guestfd_array = g_array_new(FALSE, TRUE, sizeof(GuestFD)); - } - /* SYS_OPEN should return nonzero handle on success. Start guestfd from 1 */ for (i = 1; i < guestfd_array->len; i++) { GuestFD *gf = &g_array_index(guestfd_array, GuestFD, i); @@ -61,11 +97,7 @@ static void do_dealloc_guestfd(GuestFD *gf) */ static GuestFD *do_get_guestfd(int guestfd) { - if (!guestfd_array) { - return NULL; - } - - if (guestfd <= 0 || guestfd >= guestfd_array->len) { + if (guestfd < 0 || guestfd >= guestfd_array->len) { return NULL; } From ae0340ec409e2a50695c703053defd277a90f82d Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Sun, 1 May 2022 17:42:43 -0700 Subject: [PATCH 49/69] semihosting: Use console_in_gf for SYS_READC Reviewed-by: Luc Michel Signed-off-by: Richard Henderson (cherry picked from commit 1577eec0fca6fd67bdc0727d10de4bdc3f8afa95) --- semihosting/arm-compat-semi.c | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index fdb143ace88a6..9d4d6d28122c5 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -302,6 +302,22 @@ common_semi_flen_fstat_cb(CPUState *cs, uint64_t ret, int err) common_semi_cb(cs, ret, err); } +static void +common_semi_readc_cb(CPUState *cs, uint64_t ret, int err) +{ + if (!err) { + CPUArchState *env G_GNUC_UNUSED = cs->env_ptr; + uint8_t ch; + + if (get_user_u8(ch, common_semi_stack_bottom(cs) - 1)) { + ret = -1, err = EFAULT; + } else { + ret = ch; + } + } + common_semi_cb(cs, ret, err); +} + #define SHFB_MAGIC_0 0x53 #define SHFB_MAGIC_1 0x48 #define SHFB_MAGIC_2 0x46 @@ -428,15 +444,8 @@ void do_common_semihosting(CPUState *cs) break; case TARGET_SYS_READC: - { - uint8_t ch; - int ret = qemu_semihosting_console_read(cs, &ch, 1); - if (ret == 1) { - common_semi_cb(cs, ch, 0); - } else { - common_semi_cb(cs, -1, EIO); - } - } + semihost_sys_read_gf(cs, common_semi_readc_cb, &console_in_gf, + common_semi_stack_bottom(cs) - 1, 1); break; case TARGET_SYS_ISERROR: From 3dd03d52e460dfc75ecc58fa35fe7b368cc9cf3a Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Sun, 1 May 2022 17:55:20 -0700 Subject: [PATCH 50/69] semihosting: Use console_out_gf for SYS_WRITEC Reviewed-by: Luc Michel Signed-off-by: Richard Henderson (cherry picked from commit 5d77289dac9917db89d56f558bcf7c3a82332222) --- semihosting/arm-compat-semi.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index 9d4d6d28122c5..d61b773f984e3 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -228,6 +228,15 @@ static void common_semi_cb(CPUState *cs, uint64_t ret, int err) common_semi_set_ret(cs, ret); } +/* + * Use 0xdeadbeef as the return value when there isn't a defined + * return value for the call. + */ +static void common_semi_dead_cb(CPUState *cs, uint64_t ret, int err) +{ + common_semi_set_ret(cs, 0xdeadbeef); +} + /* * SYS_READ and SYS_WRITE always return the number of bytes not read/written. * There is no error condition, other than returning the original length. @@ -341,8 +350,7 @@ static const uint8_t featurefile_data[] = { * The specification always says that the "return register" either * returns a specific value or is corrupted, so we don't need to * report to our caller whether we are returning a value or trying to - * leave the register unchanged. We use 0xdeadbeef as the return value - * when there isn't a defined return value for the call. + * leave the register unchanged. */ void do_common_semihosting(CPUState *cs) { @@ -420,8 +428,12 @@ void do_common_semihosting(CPUState *cs) break; case TARGET_SYS_WRITEC: - qemu_semihosting_console_outc(env, args); - common_semi_set_ret(cs, 0xdeadbeef); + /* + * FIXME: the byte to be written is in a target_ulong slot, + * which means this is wrong for a big-endian guest. + */ + semihost_sys_write_gf(cs, common_semi_dead_cb, + &console_out_gf, args, 1); break; case TARGET_SYS_WRITE0: From 00e8e59d5d527be45e85850026bc7b5ff1bb655a Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Sun, 1 May 2022 17:57:22 -0700 Subject: [PATCH 51/69] semihosting: Remove qemu_semihosting_console_outc This function has been replaced by *_write. Reviewed-by: Luc Michel Signed-off-by: Richard Henderson (cherry picked from commit 004d2abe3f2f856bd6f70fa3d8933d5f6d620142) --- include/semihosting/console.h | 13 ------------- linux-user/semihost.c | 16 ---------------- semihosting/console.c | 18 ------------------ 3 files changed, 47 deletions(-) diff --git a/include/semihosting/console.h b/include/semihosting/console.h index 6994f23c820b1..d6c1cc58abdc4 100644 --- a/include/semihosting/console.h +++ b/include/semihosting/console.h @@ -24,19 +24,6 @@ */ int qemu_semihosting_console_outs(CPUArchState *env, target_ulong s); -/** - * qemu_semihosting_console_outc: - * @env: CPUArchState - * @s: host address of null terminated guest string - * - * Send single character from guest memory to the debug console. This - * may be the remote gdb session if a softmmu guest is currently being - * debugged. - * - * Returns: nothing - */ -void qemu_semihosting_console_outc(CPUArchState *env, target_ulong c); - /** * qemu_semihosting_console_read: * @cs: CPUState diff --git a/linux-user/semihost.c b/linux-user/semihost.c index 871edf993a5d1..f8bc8889f3282 100644 --- a/linux-user/semihost.c +++ b/linux-user/semihost.c @@ -33,22 +33,6 @@ int qemu_semihosting_console_outs(CPUArchState *env, target_ulong addr) return len; } -void qemu_semihosting_console_outc(CPUArchState *env, target_ulong addr) -{ - char c; - - if (get_user_u8(c, addr)) { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: passed inaccessible address " TARGET_FMT_lx, - __func__, addr); - } else { - if (write(STDERR_FILENO, &c, 1) != 1) { - qemu_log_mask(LOG_UNIMP, "%s: unexpected write to stdout failure", - __func__); - } - } -} - /* * For linux-user we can safely block. However as we want to return as * soon as a character is read we need to tweak the termio to disable diff --git a/semihosting/console.c b/semihosting/console.c index 955880514e958..fe7ee8513787d 100644 --- a/semihosting/console.c +++ b/semihosting/console.c @@ -96,24 +96,6 @@ int qemu_semihosting_console_outs(CPUArchState *env, target_ulong addr) return out; } -void qemu_semihosting_console_outc(CPUArchState *env, target_ulong addr) -{ - CPUState *cpu = env_cpu(env); - uint8_t c; - - if (cpu_memory_rw_debug(cpu, addr, &c, 1, 0) == 0) { - if (use_gdb_syscalls()) { - gdb_do_syscall(semihosting_cb, "write,2,%x,%x", addr, 1); - } else { - qemu_semihosting_log_out((const char *) &c, 1); - } - } else { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: passed inaccessible address " TARGET_FMT_lx, - __func__, addr); - } -} - #define FIFO_SIZE 1024 static int console_can_read(void *opaque) From 3f4d7c56b751ab433091e9b884f2fd9a2ec71b96 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Sun, 1 May 2022 18:02:53 -0700 Subject: [PATCH 52/69] semihosting: Use console_out_gf for SYS_WRITE0 Reviewed-by: Luc Michel Signed-off-by: Richard Henderson (cherry picked from commit 7281550cfb30738f0d4bc5113e92780b8a38ec78) --- semihosting/arm-compat-semi.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index d61b773f984e3..1a1e2a696054c 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -437,8 +437,15 @@ void do_common_semihosting(CPUState *cs) break; case TARGET_SYS_WRITE0: - ret = qemu_semihosting_console_outs(env, args); - common_semi_set_ret(cs, ret); + { + ssize_t len = target_strlen(args); + if (len < 0) { + common_semi_dead_cb(cs, -1, EFAULT); + } else { + semihost_sys_write_gf(cs, common_semi_dead_cb, + &console_out_gf, args, len); + } + } break; case TARGET_SYS_WRITE: From 85e87eda6598f6602a0641c8568c5391d4b81796 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Sun, 1 May 2022 18:04:27 -0700 Subject: [PATCH 53/69] semihosting: Remove qemu_semihosting_console_outs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This function has been replaced by *_write. Reviewed-by: Luc Michel Reviewed-by: Alex Bennée Signed-off-by: Richard Henderson (cherry picked from commit 2d010c2719da360d44a5c44d279d49eca21c5de8) --- include/semihosting/console.h | 13 ---------- linux-user/semihost.c | 17 ------------ semihosting/console.c | 49 ----------------------------------- 3 files changed, 79 deletions(-) diff --git a/include/semihosting/console.h b/include/semihosting/console.h index d6c1cc58abdc4..20c31d89d4f96 100644 --- a/include/semihosting/console.h +++ b/include/semihosting/console.h @@ -11,19 +11,6 @@ #include "cpu.h" -/** - * qemu_semihosting_console_outs: - * @env: CPUArchState - * @s: host address of null terminated guest string - * - * Send a null terminated guest string to the debug console. This may - * be the remote gdb session if a softmmu guest is currently being - * debugged. - * - * Returns: number of bytes written. - */ -int qemu_semihosting_console_outs(CPUArchState *env, target_ulong s); - /** * qemu_semihosting_console_read: * @cs: CPUState diff --git a/linux-user/semihost.c b/linux-user/semihost.c index f8bc8889f3282..cee62a365c4f7 100644 --- a/linux-user/semihost.c +++ b/linux-user/semihost.c @@ -16,23 +16,6 @@ #include "user-internals.h" #include -int qemu_semihosting_console_outs(CPUArchState *env, target_ulong addr) -{ - int len = target_strlen(addr); - void *s; - if (len < 0){ - qemu_log_mask(LOG_GUEST_ERROR, - "%s: passed inaccessible address " TARGET_FMT_lx, - __func__, addr); - return 0; - } - s = lock_user(VERIFY_READ, addr, (long)(len + 1), 1); - g_assert(s); /* target_strlen has already verified this will work */ - len = write(STDERR_FILENO, s, len); - unlock_user(s, addr, 0); - return len; -} - /* * For linux-user we can safely block. However as we want to return as * soon as a character is read we need to tweak the termio to disable diff --git a/semihosting/console.c b/semihosting/console.c index fe7ee8513787d..c84ab97ab6dfd 100644 --- a/semihosting/console.c +++ b/semihosting/console.c @@ -47,55 +47,6 @@ int qemu_semihosting_log_out(const char *s, int len) } } -/* - * A re-implementation of lock_user_string that we can use locally - * instead of relying on softmmu-semi. Hopefully we can deprecate that - * in time. Copy string until we find a 0 or address error. - */ -static GString *copy_user_string(CPUArchState *env, target_ulong addr) -{ - CPUState *cpu = env_cpu(env); - GString *s = g_string_sized_new(128); - uint8_t c; - - do { - if (cpu_memory_rw_debug(cpu, addr++, &c, 1, 0) == 0) { - if (c) { - s = g_string_append_c(s, c); - } - } else { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: passed inaccessible address " TARGET_FMT_lx, - __func__, addr); - break; - } - } while (c!=0); - - return s; -} - -static void semihosting_cb(CPUState *cs, uint64_t ret, int err) -{ - if (err) { - qemu_log("%s: gdb console output failed (%d)\n", __func__, err); - } -} - -int qemu_semihosting_console_outs(CPUArchState *env, target_ulong addr) -{ - GString *s = copy_user_string(env, addr); - int out = s->len; - - if (use_gdb_syscalls()) { - gdb_do_syscall(semihosting_cb, "write,2,%x,%x", addr, s->len); - } else { - out = qemu_semihosting_log_out(s->str, s->len); - } - - g_string_free(s, true); - return out; -} - #define FIFO_SIZE 1024 static int console_can_read(void *opaque) From 6f321b12590247c4620e69aace08aca1a43ce1c5 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Mon, 2 May 2022 11:15:40 -0700 Subject: [PATCH 54/69] semihosting: Create semihost_sys_poll_one This will be used for implementing the xtensa select_one system call. Choose "poll" over "select" so that we can reuse Glib's g_poll constants and to avoid struct timeval. Reviewed-by: Luc Michel Signed-off-by: Richard Henderson (cherry picked from commit 1b9177f7495086f1595d7c989c810013f1c9eb5a) --- include/semihosting/console.h | 16 ++++++++ include/semihosting/syscalls.h | 3 ++ semihosting/console.c | 19 ++++++++- semihosting/syscalls.c | 70 ++++++++++++++++++++++++++++++++++ 4 files changed, 106 insertions(+), 2 deletions(-) diff --git a/include/semihosting/console.h b/include/semihosting/console.h index 20c31d89d4f96..61b0cb3a9463f 100644 --- a/include/semihosting/console.h +++ b/include/semihosting/console.h @@ -53,4 +53,20 @@ int qemu_semihosting_console_write(void *buf, int len); */ int qemu_semihosting_log_out(const char *s, int len); +/* + * qemu_semihosting_console_block_until_ready: + * @cs: CPUState + * + * If no data is available we suspend the CPU and will re-execute the + * instruction when data is available. + */ +void qemu_semihosting_console_block_until_ready(CPUState *cs); + +/** + * qemu_semihosting_console_ready: + * + * Return true if characters are available for read; does not block. + */ +bool qemu_semihosting_console_ready(void); + #endif /* SEMIHOST_CONSOLE_H */ diff --git a/include/semihosting/syscalls.h b/include/semihosting/syscalls.h index 347200cb9f974..3a5ec229ebfb3 100644 --- a/include/semihosting/syscalls.h +++ b/include/semihosting/syscalls.h @@ -69,4 +69,7 @@ void semihost_sys_system(CPUState *cs, gdb_syscall_complete_cb complete, void semihost_sys_gettimeofday(CPUState *cs, gdb_syscall_complete_cb complete, target_ulong tv_addr, target_ulong tz_addr); +void semihost_sys_poll_one(CPUState *cs, gdb_syscall_complete_cb complete, + int fd, GIOCondition cond, int timeout); + #endif /* SEMIHOSTING_SYSCALLS_H */ diff --git a/semihosting/console.c b/semihosting/console.c index c84ab97ab6dfd..cda7cf190516c 100644 --- a/semihosting/console.c +++ b/semihosting/console.c @@ -77,10 +77,17 @@ static void console_read(void *opaque, const uint8_t *buf, int size) c->sleeping_cpus = NULL; } -int qemu_semihosting_console_read(CPUState *cs, void *buf, int len) +bool qemu_semihosting_console_ready(void) +{ + SemihostingConsole *c = &console; + + g_assert(qemu_mutex_iothread_locked()); + return !fifo8_is_empty(&c->fifo); +} + +void qemu_semihosting_console_block_until_ready(CPUState *cs) { SemihostingConsole *c = &console; - int ret = 0; g_assert(qemu_mutex_iothread_locked()); @@ -92,6 +99,14 @@ int qemu_semihosting_console_read(CPUState *cs, void *buf, int len) cpu_loop_exit(cs); /* never returns */ } +} + +int qemu_semihosting_console_read(CPUState *cs, void *buf, int len) +{ + SemihostingConsole *c = &console; + int ret = 0; + + qemu_semihosting_console_block_until_ready(cs); /* Read until buffer full or fifo exhausted. */ do { diff --git a/semihosting/syscalls.c b/semihosting/syscalls.c index 9e499b1751a7e..4847f66c02301 100644 --- a/semihosting/syscalls.c +++ b/semihosting/syscalls.c @@ -520,6 +520,21 @@ static void host_gettimeofday(CPUState *cs, gdb_syscall_complete_cb complete, unlock_user(p, tv_addr, sizeof(struct gdb_timeval)); } +#ifndef CONFIG_USER_ONLY +static void host_poll_one(CPUState *cs, gdb_syscall_complete_cb complete, + GuestFD *gf, GIOCondition cond, int timeout) +{ + /* + * Since this is only used by xtensa in system mode, and stdio is + * handled through GuestFDConsole, and there are no semihosting + * system calls for sockets and the like, that means this descriptor + * must be a normal file. Normal files never block and are thus + * always ready. + */ + complete(cs, cond & (G_IO_IN | G_IO_OUT), 0); +} +#endif + /* * Static file semihosting syscall implementations. */ @@ -628,6 +643,34 @@ static void console_fstat(CPUState *cs, gdb_syscall_complete_cb complete, complete(cs, ret ? -1 : 0, ret ? -ret : 0); } +#ifndef CONFIG_USER_ONLY +static void console_poll_one(CPUState *cs, gdb_syscall_complete_cb complete, + GuestFD *gf, GIOCondition cond, int timeout) +{ + /* The semihosting console does not support urgent data or errors. */ + cond &= G_IO_IN | G_IO_OUT; + + /* + * Since qemu_semihosting_console_write never blocks, we can + * consider output always ready -- leave G_IO_OUT alone. + * All that remains is to conditionally signal input ready. + * Since output ready causes an immediate return, only block + * for G_IO_IN alone. + * + * TODO: Implement proper timeout. For now, only support + * indefinite wait or immediate poll. + */ + if (cond == G_IO_IN && timeout < 0) { + qemu_semihosting_console_block_until_ready(cs); + /* We returned -- input must be ready. */ + } else if ((cond & G_IO_IN) && !qemu_semihosting_console_ready()) { + cond &= ~G_IO_IN; + } + + complete(cs, cond, 0); +} +#endif + /* * Syscall entry points. */ @@ -906,3 +949,30 @@ void semihost_sys_gettimeofday(CPUState *cs, gdb_syscall_complete_cb complete, host_gettimeofday(cs, complete, tv_addr, tz_addr); } } + +#ifndef CONFIG_USER_ONLY +void semihost_sys_poll_one(CPUState *cs, gdb_syscall_complete_cb complete, + int fd, GIOCondition cond, int timeout) +{ + GuestFD *gf = get_guestfd(fd); + + if (!gf) { + complete(cs, G_IO_NVAL, 1); + return; + } + switch (gf->type) { + case GuestFDGDB: + complete(cs, G_IO_NVAL, 1); + break; + case GuestFDHost: + host_poll_one(cs, complete, gf, cond, timeout); + break; + case GuestFDConsole: + console_poll_one(cs, complete, gf, cond, timeout); + break; + case GuestFDStatic: + default: + g_assert_not_reached(); + } +} +#endif From 7258d7d033eced326764da4c9b1583b86bf936cb Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Tue, 28 Jun 2022 16:46:59 +0530 Subject: [PATCH 55/69] semihosting: Remove qemu_semihosting_log_out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The function is no longer used. Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Richard Henderson Message-Id: <20220628111701.677216-7-richard.henderson@linaro.org> Signed-off-by: Philippe Mathieu-Daudé (cherry picked from commit 938fcd741ad656b2c4aeb1654bfc4ff221c26bbf) --- include/semihosting/console.h | 13 ------------- semihosting/console.c | 9 --------- 2 files changed, 22 deletions(-) diff --git a/include/semihosting/console.h b/include/semihosting/console.h index 61b0cb3a9463f..bd78e5f03fce2 100644 --- a/include/semihosting/console.h +++ b/include/semihosting/console.h @@ -40,19 +40,6 @@ int qemu_semihosting_console_read(CPUState *cs, void *buf, int len); */ int qemu_semihosting_console_write(void *buf, int len); -/** - * qemu_semihosting_log_out: - * @s: pointer to string - * @len: length of string - * - * Send a string to the debug output. Unlike console_out these strings - * can't be sent to a remote gdb instance as they don't exist in guest - * memory. - * - * Returns: number of bytes written - */ -int qemu_semihosting_log_out(const char *s, int len); - /* * qemu_semihosting_console_block_until_ready: * @cs: CPUState diff --git a/semihosting/console.c b/semihosting/console.c index cda7cf190516c..5b1ec0a1c393b 100644 --- a/semihosting/console.c +++ b/semihosting/console.c @@ -38,15 +38,6 @@ typedef struct SemihostingConsole { static SemihostingConsole console; -int qemu_semihosting_log_out(const char *s, int len) -{ - if (console.chr) { - return qemu_chr_write_all(console.chr, (uint8_t *) s, len); - } else { - return write(STDERR_FILENO, s, len); - } -} - #define FIFO_SIZE 1024 static int console_can_read(void *opaque) From e02adaf53db508ed460afa6ef2af48fd3754db38 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Tue, 28 Jun 2022 16:46:54 +0530 Subject: [PATCH 56/69] target/mips: Create report_fault for semihosting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The UHI specification does not have an EFAULT value, and further specifies that "undefined UHI operations should not return control to the target". So, log the error and abort. Signed-off-by: Richard Henderson Reviewed-by: Philippe Mathieu-Daudé Message-Id: <20220628111701.677216-2-richard.henderson@linaro.org> Signed-off-by: Philippe Mathieu-Daudé (cherry picked from commit d53a3ed44605f7f070add30729e93bc7971ff6b1) --- target/mips/tcg/sysemu/mips-semi.c | 33 ++++++++++++++---------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/target/mips/tcg/sysemu/mips-semi.c b/target/mips/tcg/sysemu/mips-semi.c index 67c35fe7f9c79..153df1fa153c8 100644 --- a/target/mips/tcg/sysemu/mips-semi.c +++ b/target/mips/tcg/sysemu/mips-semi.c @@ -114,6 +114,13 @@ enum UHIErrno { UHI_EXDEV = 18, }; +static void report_fault(CPUMIPSState *env) +{ + int op = env->active_tc.gpr[25]; + error_report("Fault during UHI operation %d", op); + abort(); +} + static int errno_mips(int host_errno) { /* Errno values taken from asm-mips/errno.h */ @@ -136,8 +143,7 @@ static int copy_stat_to_target(CPUMIPSState *env, const struct stat *src, hwaddr len = sizeof(struct UHIStat); UHIStat *dst = lock_user(VERIFY_WRITE, vaddr, len, 0); if (!dst) { - errno = EFAULT; - return -1; + report_fault(env); } dst->uhi_st_dev = tswap16(src->st_dev); @@ -188,8 +194,7 @@ static int write_to_file(CPUMIPSState *env, target_ulong fd, int num_of_bytes; void *dst = lock_user(VERIFY_READ, vaddr, len, 1); if (!dst) { - errno = EFAULT; - return -1; + report_fault(env); } num_of_bytes = write(fd, dst, len); @@ -204,8 +209,7 @@ static int read_from_file(CPUMIPSState *env, target_ulong fd, int num_of_bytes; void *dst = lock_user(VERIFY_WRITE, vaddr, len, 0); if (!dst) { - errno = EFAULT; - return -1; + report_fault(env); } num_of_bytes = read(fd, dst, len); @@ -220,7 +224,7 @@ static int copy_argn_to_target(CPUMIPSState *env, int arg_num, int strsize = strlen(semihosting_get_arg(arg_num)) + 1; char *dst = lock_user(VERIFY_WRITE, vaddr, strsize, 0); if (!dst) { - return -1; + report_fault(env); } strcpy(dst, semihosting_get_arg(arg_num)); @@ -233,9 +237,7 @@ static int copy_argn_to_target(CPUMIPSState *env, int arg_num, do { \ p = lock_user_string(addr); \ if (!p) { \ - gpr[2] = -1; \ - gpr[3] = EFAULT; \ - return; \ + report_fault(env); \ } \ } while (0) @@ -243,16 +245,11 @@ static int copy_argn_to_target(CPUMIPSState *env, int arg_num, do { \ p = lock_user_string(addr); \ if (!p) { \ - gpr[2] = -1; \ - gpr[3] = EFAULT; \ - return; \ + report_fault(env); \ } \ p2 = lock_user_string(addr2); \ if (!p2) { \ - unlock_user(p, addr, 0); \ - gpr[2] = -1; \ - gpr[3] = EFAULT; \ - return; \ + report_fault(env); \ } \ } while (0) @@ -375,7 +372,7 @@ void mips_semihosting(CPUMIPSState *env) break; #endif default: - fprintf(stderr, "Unknown UHI operation %d\n", op); + error_report("Unknown UHI operation %d", op); abort(); } return; From ed685a5ca9a7795566d9f224e6f0bf88a9db28d0 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Tue, 28 Jun 2022 16:46:55 +0530 Subject: [PATCH 57/69] target/mips: Drop link syscall from semihosting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We don't implement it with _WIN32 hosts, and the syscall is missing from the gdb remote file i/o interface. Since we can't implement it universally, drop it. Signed-off-by: Richard Henderson Reviewed-by: Philippe Mathieu-Daudé Message-Id: <20220628111701.677216-3-richard.henderson@linaro.org> Signed-off-by: Philippe Mathieu-Daudé (cherry picked from commit 3d748e41c759c7d207806b136be7694cfe2b6d65) --- target/mips/tcg/sysemu/mips-semi.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/target/mips/tcg/sysemu/mips-semi.c b/target/mips/tcg/sysemu/mips-semi.c index 153df1fa153c8..93c9d3d0b3308 100644 --- a/target/mips/tcg/sysemu/mips-semi.c +++ b/target/mips/tcg/sysemu/mips-semi.c @@ -362,15 +362,6 @@ void mips_semihosting(CPUMIPSState *env) FREE_TARGET_STRING(p, gpr[4]); abort(); break; -#ifndef _WIN32 - case UHI_link: - GET_TARGET_STRINGS_2(p, gpr[4], p2, gpr[5]); - gpr[2] = link(p, p2); - gpr[3] = errno_mips(errno); - FREE_TARGET_STRING(p2, gpr[5]); - FREE_TARGET_STRING(p, gpr[4]); - break; -#endif default: error_report("Unknown UHI operation %d", op); abort(); From 4a00c99c2c2056e3fd95199fe93cbb604ed31147 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Tue, 28 Jun 2022 16:46:56 +0530 Subject: [PATCH 58/69] target/mips: Use semihosting/syscalls.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This separates guest file descriptors from host file descriptors, and utilizes shared infrastructure for integration with gdbstub. Signed-off-by: Richard Henderson Reviewed-by: Philippe Mathieu-Daudé Message-Id: <20220628111701.677216-4-richard.henderson@linaro.org> Signed-off-by: Philippe Mathieu-Daudé (cherry picked from commit 18639a28bb313195308a97cacb6aa6a418fd73db) --- target/mips/tcg/sysemu/mips-semi.c | 211 +++++++++++++---------------- 1 file changed, 91 insertions(+), 120 deletions(-) diff --git a/target/mips/tcg/sysemu/mips-semi.c b/target/mips/tcg/sysemu/mips-semi.c index 93c9d3d0b3308..5b78cf21a76e6 100644 --- a/target/mips/tcg/sysemu/mips-semi.c +++ b/target/mips/tcg/sysemu/mips-semi.c @@ -20,9 +20,11 @@ #include "qemu/osdep.h" #include "cpu.h" #include "qemu/log.h" +#include "exec/gdbstub.h" #include "semihosting/softmmu-uaccess.h" #include "semihosting/semihost.h" #include "semihosting/console.h" +#include "semihosting/syscalls.h" #include "internal.h" typedef enum UHIOp { @@ -121,101 +123,79 @@ static void report_fault(CPUMIPSState *env) abort(); } -static int errno_mips(int host_errno) +static void uhi_cb(CPUState *cs, uint64_t ret, int err) { - /* Errno values taken from asm-mips/errno.h */ - switch (host_errno) { - case 0: return 0; - case ENAMETOOLONG: return 78; -#ifdef EOVERFLOW - case EOVERFLOW: return 79; -#endif -#ifdef ELOOP - case ELOOP: return 90; -#endif - default: return EINVAL; - } -} + CPUMIPSState *env = cs->env_ptr; -static int copy_stat_to_target(CPUMIPSState *env, const struct stat *src, - target_ulong vaddr) -{ - hwaddr len = sizeof(struct UHIStat); - UHIStat *dst = lock_user(VERIFY_WRITE, vaddr, len, 0); - if (!dst) { +#define E(N) case E##N: err = UHI_E##N; break + + switch (err) { + case 0: + break; + E(PERM); + E(NOENT); + E(INTR); + E(BADF); + E(BUSY); + E(EXIST); + E(NOTDIR); + E(ISDIR); + E(INVAL); + E(NFILE); + E(MFILE); + E(FBIG); + E(NOSPC); + E(SPIPE); + E(ROFS); + E(NAMETOOLONG); + default: + err = UHI_EINVAL; + break; + case EFAULT: report_fault(env); } - dst->uhi_st_dev = tswap16(src->st_dev); - dst->uhi_st_ino = tswap16(src->st_ino); - dst->uhi_st_mode = tswap32(src->st_mode); - dst->uhi_st_nlink = tswap16(src->st_nlink); - dst->uhi_st_uid = tswap16(src->st_uid); - dst->uhi_st_gid = tswap16(src->st_gid); - dst->uhi_st_rdev = tswap16(src->st_rdev); - dst->uhi_st_size = tswap64(src->st_size); - dst->uhi_st_atime = tswap64(src->st_atime); - dst->uhi_st_mtime = tswap64(src->st_mtime); - dst->uhi_st_ctime = tswap64(src->st_ctime); -#ifdef _WIN32 - dst->uhi_st_blksize = 0; - dst->uhi_st_blocks = 0; -#else - dst->uhi_st_blksize = tswap64(src->st_blksize); - dst->uhi_st_blocks = tswap64(src->st_blocks); -#endif - unlock_user(dst, vaddr, len); - return 0; +#undef E + + env->active_tc.gpr[2] = ret; + env->active_tc.gpr[3] = err; } -static int get_open_flags(target_ulong target_flags) +static void uhi_fstat_cb(CPUState *cs, uint64_t ret, int err) { - int open_flags = 0; - - if (target_flags & UHIOpen_RDWR) { - open_flags |= O_RDWR; - } else if (target_flags & UHIOpen_WRONLY) { - open_flags |= O_WRONLY; - } else { - open_flags |= O_RDONLY; - } - - open_flags |= (target_flags & UHIOpen_APPEND) ? O_APPEND : 0; - open_flags |= (target_flags & UHIOpen_CREAT) ? O_CREAT : 0; - open_flags |= (target_flags & UHIOpen_TRUNC) ? O_TRUNC : 0; - open_flags |= (target_flags & UHIOpen_EXCL) ? O_EXCL : 0; + QEMU_BUILD_BUG_ON(sizeof(UHIStat) < sizeof(struct gdb_stat)); - return open_flags; -} + if (!err) { + CPUMIPSState *env = cs->env_ptr; + target_ulong addr = env->active_tc.gpr[5]; + UHIStat *dst = lock_user(VERIFY_WRITE, addr, sizeof(UHIStat), 1); + struct gdb_stat s; -static int write_to_file(CPUMIPSState *env, target_ulong fd, - target_ulong vaddr, target_ulong len) -{ - int num_of_bytes; - void *dst = lock_user(VERIFY_READ, vaddr, len, 1); - if (!dst) { - report_fault(env); - } + if (!dst) { + report_fault(env); + } - num_of_bytes = write(fd, dst, len); + memcpy(&s, dst, sizeof(struct gdb_stat)); + memset(dst, 0, sizeof(UHIStat)); - unlock_user(dst, vaddr, 0); - return num_of_bytes; -} + dst->uhi_st_dev = tswap16(be32_to_cpu(s.gdb_st_dev)); + dst->uhi_st_ino = tswap16(be32_to_cpu(s.gdb_st_ino)); + dst->uhi_st_mode = tswap32(be32_to_cpu(s.gdb_st_mode)); + dst->uhi_st_nlink = tswap16(be32_to_cpu(s.gdb_st_nlink)); + dst->uhi_st_uid = tswap16(be32_to_cpu(s.gdb_st_uid)); + dst->uhi_st_gid = tswap16(be32_to_cpu(s.gdb_st_gid)); + dst->uhi_st_rdev = tswap16(be32_to_cpu(s.gdb_st_rdev)); + dst->uhi_st_size = tswap64(be64_to_cpu(s.gdb_st_size)); + dst->uhi_st_atime = tswap64(be32_to_cpu(s.gdb_st_atime)); + dst->uhi_st_mtime = tswap64(be32_to_cpu(s.gdb_st_mtime)); + dst->uhi_st_ctime = tswap64(be32_to_cpu(s.gdb_st_ctime)); + dst->uhi_st_blksize = tswap64(be64_to_cpu(s.gdb_st_blksize)); + dst->uhi_st_blocks = tswap64(be64_to_cpu(s.gdb_st_blocks)); -static int read_from_file(CPUMIPSState *env, target_ulong fd, - target_ulong vaddr, target_ulong len) -{ - int num_of_bytes; - void *dst = lock_user(VERIFY_WRITE, vaddr, len, 0); - if (!dst) { - report_fault(env); + unlock_user(dst, addr, sizeof(UHIStat)); } - num_of_bytes = read(fd, dst, len); - - unlock_user(dst, vaddr, len); - return num_of_bytes; + uhi_cb(cs, ret, err); } static int copy_argn_to_target(CPUMIPSState *env, int arg_num, @@ -260,68 +240,59 @@ static int copy_argn_to_target(CPUMIPSState *env, int arg_num, void mips_semihosting(CPUMIPSState *env) { + CPUState *cs = env_cpu(env); target_ulong *gpr = env->active_tc.gpr; const UHIOp op = gpr[25]; char *p, *p2; switch (op) { case UHI_exit: - qemu_log("UHI(%d): exit(%d)\n", op, (int)gpr[4]); + gdb_exit(gpr[4]); exit(gpr[4]); + case UHI_open: - GET_TARGET_STRING(p, gpr[4]); - if (!strcmp("/dev/stdin", p)) { - gpr[2] = 0; - } else if (!strcmp("/dev/stdout", p)) { - gpr[2] = 1; - } else if (!strcmp("/dev/stderr", p)) { - gpr[2] = 2; - } else { - gpr[2] = open(p, get_open_flags(gpr[5]), gpr[6]); - gpr[3] = errno_mips(errno); + { + int ret = -1; + + GET_TARGET_STRING(p, gpr[4]); + if (!strcmp("/dev/stdin", p)) { + ret = 0; + } else if (!strcmp("/dev/stdout", p)) { + ret = 1; + } else if (!strcmp("/dev/stderr", p)) { + ret = 2; + } + FREE_TARGET_STRING(p, gpr[4]); + + /* FIXME: reusing a guest fd doesn't seem correct. */ + if (ret >= 0) { + gpr[2] = ret; + break; + } + + semihost_sys_open(cs, uhi_cb, gpr[4], 0, gpr[5], gpr[6]); } - FREE_TARGET_STRING(p, gpr[4]); break; + case UHI_close: - if (gpr[4] < 3) { - /* ignore closing stdin/stdout/stderr */ - gpr[2] = 0; - return; - } - gpr[2] = close(gpr[4]); - gpr[3] = errno_mips(errno); + semihost_sys_close(cs, uhi_cb, gpr[4]); break; case UHI_read: - gpr[2] = read_from_file(env, gpr[4], gpr[5], gpr[6]); - gpr[3] = errno_mips(errno); + semihost_sys_read(cs, uhi_cb, gpr[4], gpr[5], gpr[6]); break; case UHI_write: - gpr[2] = write_to_file(env, gpr[4], gpr[5], gpr[6]); - gpr[3] = errno_mips(errno); + semihost_sys_write(cs, uhi_cb, gpr[4], gpr[5], gpr[6]); break; case UHI_lseek: - gpr[2] = lseek(gpr[4], gpr[5], gpr[6]); - gpr[3] = errno_mips(errno); + semihost_sys_lseek(cs, uhi_cb, gpr[4], gpr[5], gpr[6]); break; case UHI_unlink: - GET_TARGET_STRING(p, gpr[4]); - gpr[2] = remove(p); - gpr[3] = errno_mips(errno); - FREE_TARGET_STRING(p, gpr[4]); + semihost_sys_remove(cs, uhi_cb, gpr[4], 0); break; case UHI_fstat: - { - struct stat sbuf; - memset(&sbuf, 0, sizeof(sbuf)); - gpr[2] = fstat(gpr[4], &sbuf); - gpr[3] = errno_mips(errno); - if (gpr[2]) { - return; - } - gpr[2] = copy_stat_to_target(env, &sbuf, gpr[5]); - gpr[3] = errno_mips(errno); - } + semihost_sys_fstat(cs, uhi_fstat_cb, gpr[4], gpr[5]); break; + case UHI_argc: gpr[2] = semihosting_get_argc(); break; From 21498105eb86ddad61c4c7fbd4a27438b9363a2f Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Tue, 28 Jun 2022 16:46:57 +0530 Subject: [PATCH 59/69] target/mips: Avoid qemu_semihosting_log_out for UHI_plog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use semihost_sys_write and/or qemu_semihosting_console_write for implementing plog. When using gdbstub, copy the temp string below the stack so that gdb has a guest address from which to perform the log. Signed-off-by: Richard Henderson Reviewed-by: Philippe Mathieu-Daudé Message-Id: <20220628111701.677216-5-richard.henderson@linaro.org> Signed-off-by: Philippe Mathieu-Daudé (cherry picked from commit ea4210600db3c5721f90d46d9ad9ece120010041) --- target/mips/tcg/sysemu/mips-semi.c | 52 +++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/target/mips/tcg/sysemu/mips-semi.c b/target/mips/tcg/sysemu/mips-semi.c index 5b78cf21a76e6..ad11a4682065c 100644 --- a/target/mips/tcg/sysemu/mips-semi.c +++ b/target/mips/tcg/sysemu/mips-semi.c @@ -310,20 +310,50 @@ void mips_semihosting(CPUMIPSState *env) } gpr[2] = copy_argn_to_target(env, gpr[4], gpr[5]); break; + case UHI_plog: - GET_TARGET_STRING(p, gpr[4]); - p2 = strstr(p, "%d"); - if (p2) { - int char_num = p2 - p; - GString *s = g_string_new_len(p, char_num); - g_string_append_printf(s, "%d%s", (int)gpr[5], p2 + 2); - gpr[2] = qemu_semihosting_log_out(s->str, s->len); - g_string_free(s, true); - } else { - gpr[2] = qemu_semihosting_log_out(p, strlen(p)); + { + target_ulong addr = gpr[4]; + ssize_t len = target_strlen(addr); + GString *str; + char *pct_d; + + if (len < 0) { + report_fault(env); + } + p = lock_user(VERIFY_READ, addr, len, 1); + if (!p) { + report_fault(env); + } + + pct_d = strstr(p, "%d"); + if (!pct_d) { + FREE_TARGET_STRING(p, addr); + semihost_sys_write(cs, uhi_cb, 2, addr, len); + break; + } + + str = g_string_new_len(p, pct_d - p); + g_string_append_printf(str, "%d%s", (int)gpr[5], pct_d + 2); + FREE_TARGET_STRING(p, addr); + + /* + * When we're using gdb, we need a guest address, so + * drop the string onto the stack below the stack pointer. + */ + if (use_gdb_syscalls()) { + addr = gpr[29] - str->len; + p = lock_user(VERIFY_WRITE, addr, str->len, 0); + memcpy(p, str->str, str->len); + unlock_user(p, addr, str->len); + semihost_sys_write(cs, uhi_cb, 2, addr, str->len); + } else { + gpr[2] = qemu_semihosting_console_write(str->str, str->len); + } + g_string_free(str, true); } - FREE_TARGET_STRING(p, gpr[4]); break; + case UHI_assert: GET_TARGET_STRINGS_2(p, gpr[4], p2, gpr[5]); printf("assertion '"); From 070a9098e260744956576ab6acc58db9ef9c138a Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 25 Jul 2022 15:05:13 +0100 Subject: [PATCH 60/69] semihosting: Don't return negative values on qemu_semihosting_console_write() failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The documentation comment for qemu_semihosting_console_write() says * Returns: number of bytes written -- this should only ever be short * on some sort of i/o error. and the callsites rely on this. However, the implementation code path which sends console output to a chardev doesn't honour this, and will return negative values on error. Bring it into line with the other implementation codepaths and the documentation, so that it returns 0 on error. Spotted by Coverity, because console_write() passes the return value to unlock_user(), which doesn't accept a negative length. Resolves: Coverity CID 1490288 Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Message-Id: <20220719121110.225657-2-peter.maydell@linaro.org> Signed-off-by: Alex Bennée Reviewed-by: Philippe Mathieu-Daudé Message-Id: <20220725140520.515340-7-alex.bennee@linaro.org> (cherry picked from commit aed04e6357fa10735c43574ea67a9551580d6e82) --- semihosting/console.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/semihosting/console.c b/semihosting/console.c index 5b1ec0a1c393b..0f976fe8cb1f2 100644 --- a/semihosting/console.c +++ b/semihosting/console.c @@ -111,7 +111,8 @@ int qemu_semihosting_console_read(CPUState *cs, void *buf, int len) int qemu_semihosting_console_write(void *buf, int len) { if (console.chr) { - return qemu_chr_write_all(console.chr, (uint8_t *)buf, len); + int r = qemu_chr_write_all(console.chr, (uint8_t *)buf, len); + return r < 0 ? 0 : r; } else { return fwrite(buf, 1, len, stderr); } From 05e8dc817b5df6769e0237ac471f83a7a4704989 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 25 Jul 2022 15:05:14 +0100 Subject: [PATCH 61/69] semihosting: Don't copy buffer after console_write() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The console_write() semihosting function outputs guest data from a buffer; it doesn't update that buffer. It therefore doesn't need to pass a length value to unlock_user(), but can pass 0, meaning "do not copy any data back to the guest memory". Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Message-Id: <20220719121110.225657-3-peter.maydell@linaro.org> Signed-off-by: Alex Bennée Message-Id: <20220725140520.515340-8-alex.bennee@linaro.org> (cherry picked from commit 45704e89047fc0a151c5909f006d8757140f23d6) --- semihosting/syscalls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semihosting/syscalls.c b/semihosting/syscalls.c index 4847f66c02301..508a0ad88c68b 100644 --- a/semihosting/syscalls.c +++ b/semihosting/syscalls.c @@ -627,7 +627,7 @@ static void console_write(CPUState *cs, gdb_syscall_complete_cb complete, } ret = qemu_semihosting_console_write(ptr, len); complete(cs, ret ? ret : -1, ret ? 0 : EIO); - unlock_user(ptr, buf, ret); + unlock_user(ptr, buf, 0); } static void console_fstat(CPUState *cs, gdb_syscall_complete_cb complete, From 925e45084b53609cd397331a45c8af1e094f9c91 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 25 Jul 2022 15:05:15 +0100 Subject: [PATCH 62/69] semihosting: Check for errors on SET_ARG() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SET_ARG() macro returns an error indication; we check this in the TARGET_SYS_GET_CMDLINE case but not when we use it in implementing TARGET_SYS_ELAPSED. Check for and handle the errors via the do_fault codepath, and update the comment documenting the SET_ARG() and GET_ARG() macros to note how they handle memory access errors. Resolves: Coverity CID 1490287 Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Message-Id: <20220719121110.225657-4-peter.maydell@linaro.org> Signed-off-by: Alex Bennée Message-Id: <20220725140520.515340-9-alex.bennee@linaro.org> (cherry picked from commit fed49cdf6a721d76f9ac1cf76fd05b3fbd8b4892) --- semihosting/arm-compat-semi.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index 1a1e2a696054c..d12288fc806a6 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -171,6 +171,12 @@ static LayoutInfo common_semi_find_bases(CPUState *cs) * Read the input value from the argument block; fail the semihosting * call if the memory read fails. Eventually we could use a generic * CPUState helper function here. + * Note that GET_ARG() handles memory access errors by jumping to + * do_fault, so must be used as the first thing done in handling a + * semihosting call, to avoid accidentally leaking allocated resources. + * SET_ARG(), since it unavoidably happens late, instead returns an + * error indication (0 on success, non-0 for error) which the caller + * should check. */ #define GET_ARG(n) do { \ @@ -739,10 +745,14 @@ void do_common_semihosting(CPUState *cs) case TARGET_SYS_ELAPSED: elapsed = get_clock() - clock_start; if (sizeof(target_ulong) == 8) { - SET_ARG(0, elapsed); + if (SET_ARG(0, elapsed)) { + goto do_fault; + } } else { - SET_ARG(0, (uint32_t) elapsed); - SET_ARG(1, (uint32_t) (elapsed >> 32)); + if (SET_ARG(0, (uint32_t) elapsed) || + SET_ARG(1, (uint32_t) (elapsed >> 32))) { + goto do_fault; + } } common_semi_set_ret(cs, 0); break; From 50c3ca1c7fcd723f9ed81b98950384fe8af502af Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 25 Jul 2022 15:05:16 +0100 Subject: [PATCH 63/69] semihosting: Fix handling of buffer in TARGET_SYS_TMPNAM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The TARGET_SYS_TMPNAM implementation has two bugs spotted by Coverity: * confusion about whether 'len' has the length of the string including or excluding the terminating NUL means we lock_user() len bytes of memory but memcpy() len + 1 bytes * In the error-exit cases we forget to free() the buffer that asprintf() returned to us Resolves: Coverity CID 1490285, 1490289 Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Message-Id: <20220719121110.225657-5-peter.maydell@linaro.org> Signed-off-by: Alex Bennée Message-Id: <20220725140520.515340-10-alex.bennee@linaro.org> (cherry picked from commit 9b1268f55ceb0d9390a051cad299b3021dfa9896) --- semihosting/arm-compat-semi.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index d12288fc806a6..e741674238f34 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -504,16 +504,25 @@ void do_common_semihosting(CPUState *cs) GET_ARG(1); GET_ARG(2); len = asprintf(&s, "/tmp/qemu-%x%02x", getpid(), (int)arg1 & 0xff); + if (len < 0) { + common_semi_set_ret(cs, -1); + break; + } + + /* Allow for trailing NUL */ + len++; /* Make sure there's enough space in the buffer */ - if (len < 0 || len >= arg2) { + if (len > arg2) { + free(s); common_semi_set_ret(cs, -1); break; } p = lock_user(VERIFY_WRITE, arg0, len, 0); if (!p) { + free(s); goto do_fault; } - memcpy(p, s, len + 1); + memcpy(p, s, len); unlock_user(p, arg0, len); free(s); common_semi_set_ret(cs, 0); From 1520b5d8b0342a6b405692d5c3d87354d9e9dd50 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 22 Aug 2022 15:12:24 +0100 Subject: [PATCH 64/69] semihosting: Allow optional use of semihosting from userspace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently our semihosting implementations generally prohibit use of semihosting calls in system emulation from the guest userspace. This is a very long standing behaviour justified originally "to provide some semblance of security" (since code with access to the semihosting ABI can do things like read and write arbitrary files on the host system). However, it is sometimes useful to be able to run trusted guest code which performs semihosting calls from guest userspace, notably for test code. Add a command line suboption to the existing semihosting-config option group so that you can explicitly opt in to semihosting from guest userspace with -semihosting-config userspace=on (There is no equivalent option for the user-mode emulator, because there by definition all code runs in userspace and has access to semihosting already.) This commit adds the infrastructure for the command line option and adds a bool 'is_user' parameter to the function semihosting_userspace_enabled() that target code can use to check whether it should be permitting the semihosting call for userspace. It mechanically makes all the callsites pass 'false', so they continue checking "is semihosting enabled in general". Subsequent commits will make each target that implements semihosting honour the userspace=on option by passing the correct value and removing whatever "don't do this for userspace" checking they were doing by hand. Signed-off-by: Peter Maydell Acked-by: Alex Bennée Reviewed-by: Alistair Francis Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20220822141230.3658237-2-peter.maydell@linaro.org> Signed-off-by: Richard Henderson (cherry picked from commit 5202861b20d77b1d638da5f10af0f51ebcfc61bf) --- include/semihosting/semihost.h | 10 ++++++++-- qemu-options.hx | 11 +++++++++-- semihosting/config.c | 10 ++++++++-- softmmu/vl.c | 2 +- stubs/semihost.c | 2 +- target/arm/translate-a64.c | 2 +- target/arm/translate.c | 6 +++--- target/m68k/op_helper.c | 2 +- target/xtensa/translate.c | 6 +++--- 9 files changed, 35 insertions(+), 16 deletions(-) diff --git a/include/semihosting/semihost.h b/include/semihosting/semihost.h index 93a3c21b44dea..efd2efa25ae9d 100644 --- a/include/semihosting/semihost.h +++ b/include/semihosting/semihost.h @@ -27,7 +27,7 @@ typedef enum SemihostingTarget { } SemihostingTarget; #ifdef CONFIG_USER_ONLY -static inline bool semihosting_enabled(void) +static inline bool semihosting_enabled(bool is_user) { return true; } @@ -52,7 +52,13 @@ static inline const char *semihosting_get_cmdline(void) return NULL; } #else /* !CONFIG_USER_ONLY */ -bool semihosting_enabled(void); +/** + * semihosting_enabled: + * @is_user: true if guest code is in usermode (i.e. not privileged) + * + * Return true if guest code is allowed to make semihosting calls. + */ +bool semihosting_enabled(bool is_user); SemihostingTarget semihosting_get_target(void); const char *semihosting_get_arg(int i); int semihosting_get_argc(void); diff --git a/qemu-options.hx b/qemu-options.hx index 34e9b32a5c00b..7a3c2ee31b1e3 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -4540,12 +4540,12 @@ SRST information about the facilities this enables. ERST DEF("semihosting-config", HAS_ARG, QEMU_OPTION_semihosting_config, - "-semihosting-config [enable=on|off][,target=native|gdb|auto][,chardev=id][,arg=str[,...]]\n" \ + "-semihosting-config [enable=on|off][,target=native|gdb|auto][,chardev=id][,userspace=on|off][,arg=str[,...]]\n" \ " semihosting configuration\n", QEMU_ARCH_ARM | QEMU_ARCH_M68K | QEMU_ARCH_XTENSA | QEMU_ARCH_MIPS | QEMU_ARCH_NIOS2 | QEMU_ARCH_RISCV) SRST -``-semihosting-config [enable=on|off][,target=native|gdb|auto][,chardev=id][,arg=str[,...]]`` +``-semihosting-config [enable=on|off][,target=native|gdb|auto][,chardev=id][,userspace=on|off][,arg=str[,...]]`` Enable and configure semihosting (ARM, M68K, Xtensa, MIPS, Nios II, RISC-V only). @@ -4572,6 +4572,13 @@ SRST Send the output to a chardev backend output for native or auto output when not in gdb + ``userspace=on|off`` + Allows code running in guest userspace to access the semihosting + interface. The default is that only privileged guest code can + make semihosting calls. Note that setting ``userspace=on`` should + only be used if all guest code is trusted (for example, in + bare-metal test case code). + ``arg=str1,arg=str2,...`` Allows the user to pass input arguments, and can be used multiple times to build up a list. The old-style diff --git a/semihosting/config.c b/semihosting/config.c index e171d4d6bc336..89a1759687905 100644 --- a/semihosting/config.c +++ b/semihosting/config.c @@ -34,6 +34,9 @@ QemuOptsList qemu_semihosting_config_opts = { { .name = "enable", .type = QEMU_OPT_BOOL, + }, { + .name = "userspace", + .type = QEMU_OPT_BOOL, }, { .name = "target", .type = QEMU_OPT_STRING, @@ -50,6 +53,7 @@ QemuOptsList qemu_semihosting_config_opts = { typedef struct SemihostingConfig { bool enabled; + bool userspace_enabled; SemihostingTarget target; char **argv; int argc; @@ -59,9 +63,9 @@ typedef struct SemihostingConfig { static SemihostingConfig semihosting; static const char *semihost_chardev; -bool semihosting_enabled(void) +bool semihosting_enabled(bool is_user) { - return semihosting.enabled; + return semihosting.enabled && (!is_user || semihosting.userspace_enabled); } SemihostingTarget semihosting_get_target(void) @@ -137,6 +141,8 @@ int qemu_semihosting_config_options(const char *optarg) if (opts != NULL) { semihosting.enabled = qemu_opt_get_bool(opts, "enable", true); + semihosting.userspace_enabled = qemu_opt_get_bool(opts, "userspace", + false); const char *target = qemu_opt_get(opts, "target"); /* setup of chardev is deferred until they are initialised */ semihost_chardev = qemu_opt_get(opts, "chardev"); diff --git a/softmmu/vl.c b/softmmu/vl.c index 5f125171fb288..71634f25b8cd8 100644 --- a/softmmu/vl.c +++ b/softmmu/vl.c @@ -1911,7 +1911,7 @@ static void qemu_apply_machine_options(QDict *qdict) current_machine->boot_order = boot_order; current_machine->boot_once = boot_once; - if (semihosting_enabled() && !semihosting_get_argc()) { + if (semihosting_enabled(false) && !semihosting_get_argc()) { /* fall back to the -kernel/-append */ semihosting_arg_fallback(current_machine->kernel_filename, current_machine->kernel_cmdline); } diff --git a/stubs/semihost.c b/stubs/semihost.c index f486651afbbc6..d65c9fd5dcf49 100644 --- a/stubs/semihost.c +++ b/stubs/semihost.c @@ -23,7 +23,7 @@ QemuOptsList qemu_semihosting_config_opts = { }; /* Queries to config status default to off */ -bool semihosting_enabled(void) +bool semihosting_enabled(bool is_user) { return false; } diff --git a/target/arm/translate-a64.c b/target/arm/translate-a64.c index 9333d7be41aa9..729ff08a56484 100644 --- a/target/arm/translate-a64.c +++ b/target/arm/translate-a64.c @@ -2122,7 +2122,7 @@ static void disas_exc(DisasContext *s, uint32_t insn) * it is required for halting debug disabled: it will UNDEF. * Secondly, "HLT 0xf000" is the A64 semihosting syscall instruction. */ - if (semihosting_enabled() && imm16 == 0xf000) { + if (semihosting_enabled(false) && imm16 == 0xf000) { #ifndef CONFIG_USER_ONLY /* In system mode, don't allow userspace access to semihosting, * to provide some semblance of security (and for consistency diff --git a/target/arm/translate.c b/target/arm/translate.c index bf2196b9e24c3..2ff6444a64b10 100644 --- a/target/arm/translate.c +++ b/target/arm/translate.c @@ -1130,7 +1130,7 @@ static inline void gen_hlt(DisasContext *s, int imm) * semihosting, to provide some semblance of security * (and for consistency with our 32-bit semihosting). */ - if (semihosting_enabled() && + if (semihosting_enabled(false) && #ifndef CONFIG_USER_ONLY s->current_el != 0 && #endif @@ -6549,7 +6549,7 @@ static bool trans_BKPT(DisasContext *s, arg_BKPT *a) /* BKPT is OK with ECI set and leaves it untouched */ s->eci_handled = true; if (arm_dc_feature(s, ARM_FEATURE_M) && - semihosting_enabled() && + semihosting_enabled(false) && #ifndef CONFIG_USER_ONLY !IS_USER(s) && #endif @@ -8772,7 +8772,7 @@ static bool trans_SVC(DisasContext *s, arg_SVC *a) { const uint32_t semihost_imm = s->thumb ? 0xab : 0x123456; - if (!arm_dc_feature(s, ARM_FEATURE_M) && semihosting_enabled() && + if (!arm_dc_feature(s, ARM_FEATURE_M) && semihosting_enabled(false) && #ifndef CONFIG_USER_ONLY !IS_USER(s) && #endif diff --git a/target/m68k/op_helper.c b/target/m68k/op_helper.c index 8decc612409cc..6ccddc9f65b1e 100644 --- a/target/m68k/op_helper.c +++ b/target/m68k/op_helper.c @@ -203,7 +203,7 @@ static void cf_interrupt_all(CPUM68KState *env, int is_hw) cf_rte(env); return; case EXCP_HALT_INSN: - if (semihosting_enabled() + if (semihosting_enabled(false) && (env->sr & SR_S) != 0 && (env->pc & 3) == 0 && cpu_lduw_code(env, env->pc - 4) == 0x4e71 diff --git a/target/xtensa/translate.c b/target/xtensa/translate.c index 96f1a59fbee79..2747c5524eed6 100644 --- a/target/xtensa/translate.c +++ b/target/xtensa/translate.c @@ -2402,9 +2402,9 @@ static uint32_t test_exceptions_simcall(DisasContext *dc, bool ill = true; #else /* Between RE.2 and RE.3 simcall opcode's become nop for the hardware. */ - bool ill = dc->config->hw_version <= 250002 && !semihosting_enabled(); + bool ill = dc->config->hw_version <= 250002 && !semihosting_enabled(false); #endif - if (ill || !semihosting_enabled()) { + if (ill || !semihosting_enabled(false)) { qemu_log_mask(LOG_GUEST_ERROR, "SIMCALL but semihosting is disabled\n"); } return ill ? XTENSA_OP_ILL : 0; @@ -2414,7 +2414,7 @@ static void translate_simcall(DisasContext *dc, const OpcodeArg arg[], const uint32_t par[]) { #ifndef CONFIG_USER_ONLY - if (semihosting_enabled()) { + if (semihosting_enabled(false)) { gen_helper_simcall(cpu_env); } #endif From 67fb26baf43b43b357939b64874c41fc84574fa9 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 22 Aug 2022 15:12:27 +0100 Subject: [PATCH 65/69] target/mips: Honour -semihosting-config userspace=on MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Honour the commandline -semihosting-config userspace=on option, instead of always permitting userspace semihosting calls in system emulation mode, by passing the correct value to the is_userspace argument of semihosting_enabled(). Note that this is a behaviour change: if the user wants to do semihosting calls from userspace they must now specifically enable them on the command line. MIPS semihosting is not implemented for linux-user builds. Signed-off-by: Peter Maydell Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20220822141230.3658237-5-peter.maydell@linaro.org> Signed-off-by: Richard Henderson (cherry picked from commit b35d74015b0262260b7dbb75c80ea85aeebab89b) --- target/mips/tcg/micromips_translate.c.inc | 6 +++--- target/mips/tcg/mips16e_translate.c.inc | 2 +- target/mips/tcg/nanomips_translate.c.inc | 4 ++-- target/mips/tcg/translate.c | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/target/mips/tcg/micromips_translate.c.inc b/target/mips/tcg/micromips_translate.c.inc index 274caf2c3c478..db5e9bb1e9e69 100644 --- a/target/mips/tcg/micromips_translate.c.inc +++ b/target/mips/tcg/micromips_translate.c.inc @@ -825,7 +825,7 @@ static void gen_pool16c_insn(DisasContext *ctx) generate_exception_break(ctx, extract32(ctx->opcode, 0, 4)); break; case SDBBP16: - if (is_uhi(extract32(ctx->opcode, 0, 4))) { + if (is_uhi(ctx, extract32(ctx->opcode, 0, 4))) { generate_exception_end(ctx, EXCP_SEMIHOST); } else { /* @@ -941,7 +941,7 @@ static void gen_pool16c_r6_insn(DisasContext *ctx) break; case R6_SDBBP16: /* SDBBP16 */ - if (is_uhi(extract32(ctx->opcode, 6, 4))) { + if (is_uhi(ctx, extract32(ctx->opcode, 6, 4))) { generate_exception_end(ctx, EXCP_SEMIHOST); } else { if (ctx->hflags & MIPS_HFLAG_SBRI) { @@ -1310,7 +1310,7 @@ static void gen_pool32axf(CPUMIPSState *env, DisasContext *ctx, int rt, int rs) generate_exception_end(ctx, EXCP_SYSCALL); break; case SDBBP: - if (is_uhi(extract32(ctx->opcode, 16, 10))) { + if (is_uhi(ctx, extract32(ctx->opcode, 16, 10))) { generate_exception_end(ctx, EXCP_SEMIHOST); } else { check_insn(ctx, ISA_MIPS_R1); diff --git a/target/mips/tcg/mips16e_translate.c.inc b/target/mips/tcg/mips16e_translate.c.inc index 0a3ba252e4daf..1cfe5157b20ec 100644 --- a/target/mips/tcg/mips16e_translate.c.inc +++ b/target/mips/tcg/mips16e_translate.c.inc @@ -951,7 +951,7 @@ static int decode_ase_mips16e(CPUMIPSState *env, DisasContext *ctx) } break; case RR_SDBBP: - if (is_uhi(extract32(ctx->opcode, 5, 6))) { + if (is_uhi(ctx, extract32(ctx->opcode, 5, 6))) { generate_exception_end(ctx, EXCP_SEMIHOST); } else { /* diff --git a/target/mips/tcg/nanomips_translate.c.inc b/target/mips/tcg/nanomips_translate.c.inc index 5b0e4683a7692..ec13730d8fae8 100644 --- a/target/mips/tcg/nanomips_translate.c.inc +++ b/target/mips/tcg/nanomips_translate.c.inc @@ -3690,7 +3690,7 @@ static int decode_nanomips_32_48_opc(CPUMIPSState *env, DisasContext *ctx) generate_exception_end(ctx, EXCP_BREAK); break; case NM_SDBBP: - if (is_uhi(extract32(ctx->opcode, 0, 19))) { + if (is_uhi(ctx, extract32(ctx->opcode, 0, 19))) { generate_exception_end(ctx, EXCP_SEMIHOST); } else { if (ctx->hflags & MIPS_HFLAG_SBRI) { @@ -4608,7 +4608,7 @@ static int decode_isa_nanomips(CPUMIPSState *env, DisasContext *ctx) generate_exception_end(ctx, EXCP_BREAK); break; case NM_SDBBP16: - if (is_uhi(extract32(ctx->opcode, 0, 3))) { + if (is_uhi(ctx, extract32(ctx->opcode, 0, 3))) { generate_exception_end(ctx, EXCP_SEMIHOST); } else { if (ctx->hflags & MIPS_HFLAG_SBRI) { diff --git a/target/mips/tcg/translate.c b/target/mips/tcg/translate.c index 8cff454c68d4e..2d6fc59fd6242 100644 --- a/target/mips/tcg/translate.c +++ b/target/mips/tcg/translate.c @@ -12085,12 +12085,13 @@ static void gen_cache_operation(DisasContext *ctx, uint32_t op, int base, tcg_temp_free_i32(t0); } -static inline bool is_uhi(int sdbbp_code) +static inline bool is_uhi(DisasContext *ctx, int sdbbp_code) { #ifdef CONFIG_USER_ONLY return false; #else - return semihosting_enabled() && sdbbp_code == 1; + bool is_user = (ctx->hflags & MIPS_HFLAG_KSU) == MIPS_HFLAG_UM; + return semihosting_enabled(is_user) && sdbbp_code == 1; #endif } @@ -13901,7 +13902,7 @@ static void decode_opc_special_r6(CPUMIPSState *env, DisasContext *ctx) } break; case R6_OPC_SDBBP: - if (is_uhi(extract32(ctx->opcode, 6, 20))) { + if (is_uhi(ctx, extract32(ctx->opcode, 6, 20))) { generate_exception_end(ctx, EXCP_SEMIHOST); } else { if (ctx->hflags & MIPS_HFLAG_SBRI) { @@ -14313,9 +14314,8 @@ static void decode_opc_special2_legacy(CPUMIPSState *env, DisasContext *ctx) gen_cl(ctx, op1, rd, rs); break; case OPC_SDBBP: - if (is_uhi(extract32(ctx->opcode, 6, 20))) { + if (is_uhi(ctx, extract32(ctx->opcode, 6, 20))) { generate_exception_end(ctx, EXCP_SEMIHOST); - } else { /* * XXX: not clear which exception should be raised * when in debug mode... From 7febad6b85dc997260cf5dcca09dc77f1dabb470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= Date: Thu, 29 Sep 2022 12:42:21 +0100 Subject: [PATCH 66/69] semihosting: update link to spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old link has moved but it seems the document is now hosted on Arm's github along with a license update to CC-BY-SA-4.0. Signed-off-by: Alex Bennée Reviewed-by: Richard Henderson Message-Id: <20220929114231.583801-42-alex.bennee@linaro.org> (cherry picked from commit 424d5ecf520621e9b9bd802995558f83bd0e37af) --- semihosting/arm-compat-semi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index e741674238f34..bfea9e9337bff 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -24,7 +24,7 @@ * * ARM Semihosting is documented in: * Semihosting for AArch32 and AArch64 Release 2.0 - * https://static.docs.arm.com/100863/0200/semihosting.pdf + * https://github.com/ARM-software/abi-aa/blob/main/semihosting/semihosting.rst * * RISC-V Semihosting is documented in: * RISC-V Semihosting From 95c951e207719ea971b09754746a98f2fc9fad77 Mon Sep 17 00:00:00 2001 From: Bin Meng Date: Thu, 27 Oct 2022 19:36:16 +0100 Subject: [PATCH 67/69] semihosting/arm-compat-semi: Avoid using hardcoded /tmp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use g_get_tmp_dir() to get the directory to use for temporary files. Signed-off-by: Bin Meng Reviewed-by: Alex Bennée Signed-off-by: Alex Bennée Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Richard Henderson Message-Id: <20221006151927.2079583-2-bmeng.cn@gmail.com> Message-Id: <20221027183637.2772968-11-alex.bennee@linaro.org> (cherry picked from commit 3878d0c7d7de9fe201513b8ee31e38e53361a97d) --- semihosting/arm-compat-semi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c index bfea9e9337bff..62d8bae97f0e8 100644 --- a/semihosting/arm-compat-semi.c +++ b/semihosting/arm-compat-semi.c @@ -503,7 +503,8 @@ void do_common_semihosting(CPUState *cs) GET_ARG(0); GET_ARG(1); GET_ARG(2); - len = asprintf(&s, "/tmp/qemu-%x%02x", getpid(), (int)arg1 & 0xff); + len = asprintf(&s, "%s/qemu-%x%02x", g_get_tmp_dir(), + getpid(), (int)arg1 & 0xff); if (len < 0) { common_semi_set_ret(cs, -1); break; From 157cd1bc1619586c260833240c9dc744ea1ab452 Mon Sep 17 00:00:00 2001 From: Richard Henderson Date: Thu, 21 Apr 2022 08:17:02 -0700 Subject: [PATCH 68/69] target/nios2: Create EXCP_SEMIHOST for semi-hosting Decode 'break 1' during translation, rather than doing it again during exception processing. Reviewed-by: Peter Maydell Signed-off-by: Richard Henderson Message-Id: <20220421151735.31996-32-richard.henderson@linaro.org> (cherry picked from commit 24ca31346e41e80166c696dd04b33027b93d8559) --- target/nios2/cpu.h | 1 + target/nios2/helper.c | 14 ++++++-------- target/nios2/translate.c | 17 ++++++++++++++++- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/target/nios2/cpu.h b/target/nios2/cpu.h index ca0f3420cd1b1..fe1455a39d6b6 100644 --- a/target/nios2/cpu.h +++ b/target/nios2/cpu.h @@ -133,6 +133,7 @@ struct Nios2CPUClass { /* Exceptions */ #define EXCP_BREAK 0x1000 +#define EXCP_SEMIHOST 0x1001 #define EXCP_RESET 0 #define EXCP_PRESET 1 #define EXCP_IRQ 2 diff --git a/target/nios2/helper.c b/target/nios2/helper.c index e5c98650e1aab..d6f579e591a0a 100644 --- a/target/nios2/helper.c +++ b/target/nios2/helper.c @@ -171,14 +171,6 @@ void nios2_cpu_do_interrupt(CPUState *cs) case EXCP_BREAK: qemu_log_mask(CPU_LOG_INT, "BREAK exception at pc=%x\n", env->regs[R_PC]); - /* The semihosting instruction is "break 1". */ - if (semihosting_enabled() && - cpu_ldl_code(env, env->regs[R_PC]) == 0x003da07a) { - qemu_log_mask(CPU_LOG_INT, "Entering semihosting\n"); - env->regs[R_PC] += 4; - do_nios2_semihosting(env); - break; - } if ((env->regs[CR_STATUS] & CR_STATUS_EH) == 0) { env->regs[CR_BSTATUS] = env->regs[CR_STATUS]; @@ -194,6 +186,12 @@ void nios2_cpu_do_interrupt(CPUState *cs) env->regs[R_PC] = cpu->exception_addr; break; + case EXCP_SEMIHOST: + qemu_log_mask(CPU_LOG_INT, "BREAK semihosting at pc=%x\n", env->regs[R_PC]); + env->regs[R_PC] += 4; + do_nios2_semihosting(env); + break; + default: cpu_abort(cs, "unhandled exception type=%d\n", cs->exception_index); diff --git a/target/nios2/translate.c b/target/nios2/translate.c index f89271dbed686..c30fdf7ffc3e0 100644 --- a/target/nios2/translate.c +++ b/target/nios2/translate.c @@ -33,6 +33,7 @@ #include "exec/translator.h" #include "qemu/qemu-print.h" #include "exec/gen-icount.h" +#include "semihosting/semihost.h" /* is_jmp field values */ #define DISAS_JUMP DISAS_TARGET_0 /* only pc was modified dynamically */ @@ -644,6 +645,20 @@ static void trap(DisasContext *dc, uint32_t code, uint32_t flags) t_gen_helper_raise_exception(dc, EXCP_TRAP); } +static void gen_break(DisasContext *dc, uint32_t code, uint32_t flags) +{ +#ifndef CONFIG_USER_ONLY + /* The semihosting instruction is "break 1". */ + R_TYPE(instr, code); + if (semihosting_enabled() && instr.imm5 == 1) { + t_gen_helper_raise_exception(dc, EXCP_SEMIHOST); + return; + } +#endif + + t_gen_helper_raise_exception(dc, EXCP_BREAK); +} + static const Nios2Instruction r_type_instructions[] = { INSTRUCTION_ILLEGAL(), INSTRUCTION(eret), /* eret */ @@ -697,7 +712,7 @@ static const Nios2Instruction r_type_instructions[] = { INSTRUCTION(add), /* add */ INSTRUCTION_ILLEGAL(), INSTRUCTION_ILLEGAL(), - INSTRUCTION_FLG(gen_excp, EXCP_BREAK), /* break */ + INSTRUCTION(gen_break), /* break */ INSTRUCTION_ILLEGAL(), INSTRUCTION(nop), /* nop */ INSTRUCTION_ILLEGAL(), From b2a5fada7c0a7837ff31a2407562c8b7a58a989b Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 22 Aug 2022 15:12:28 +0100 Subject: [PATCH 69/69] target/nios2: Honour -semihosting-config userspace=on Honour the commandline -semihosting-config userspace=on option, instead of always permitting userspace semihosting calls in system emulation mode, by passing the correct value to the is_userspace argument of semihosting_enabled(). Note that this is a behaviour change: if the user wants to do semihosting calls from userspace they must now specifically enable them on the command line. nios2 semihosting is not implemented for linux-user builds. Signed-off-by: Peter Maydell Reviewed-by: Richard Henderson Message-Id: <20220822141230.3658237-6-peter.maydell@linaro.org> Signed-off-by: Richard Henderson (cherry picked from commit cab9f19370b5e26afbc7f62e08293507bf2a5f6b) --- target/nios2/translate.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/target/nios2/translate.c b/target/nios2/translate.c index c30fdf7ffc3e0..0e8c950e1f3fa 100644 --- a/target/nios2/translate.c +++ b/target/nios2/translate.c @@ -649,8 +649,9 @@ static void gen_break(DisasContext *dc, uint32_t code, uint32_t flags) { #ifndef CONFIG_USER_ONLY /* The semihosting instruction is "break 1". */ + bool is_user = dc->base.tb->flags & CR_STATUS_U; R_TYPE(instr, code); - if (semihosting_enabled() && instr.imm5 == 1) { + if (semihosting_enabled(is_user) && instr.imm5 == 1) { t_gen_helper_raise_exception(dc, EXCP_SEMIHOST); return; }