/* * Semihosting support for systems modeled on the Arm "Angel" * semihosting syscalls design. This includes Arm and RISC-V processors * * Copyright (c) 2005, 2007 CodeSourcery. * Copyright (c) 2019 Linaro * Written by Paul Brook. * * Copyright © 2020 by Keith Packard * Adapted for systems other than ARM, including RISC-V, by Keith Packard * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * ARM Semihosting is documented in: * Semihosting for AArch32 and AArch64 Release 2.0 * https://static.docs.arm.com/100863/0200/semihosting.pdf * * RISC-V Semihosting is documented in: * RISC-V Semihosting * https://github.com/riscv/riscv-semihosting-spec/blob/main/riscv-semihosting-spec.adoc */ #include "qemu/osdep.h" #include "cpu.h" #include "semihosting/semihost.h" #include "semihosting/console.h" #include "semihosting/common-semi.h" #include "qemu/timer.h" #ifdef CONFIG_USER_ONLY #include "qemu.h" #define COMMON_SEMI_HEAP_SIZE (128 * 1024 * 1024) #else #include "exec/gdbstub.h" #include "qemu/cutils.h" #ifdef TARGET_ARM #include "hw/arm/boot.h" #endif #include "hw/boards.h" #endif #define TARGET_SYS_OPEN 0x01 #define TARGET_SYS_CLOSE 0x02 #define TARGET_SYS_WRITEC 0x03 #define TARGET_SYS_WRITE0 0x04 #define TARGET_SYS_WRITE 0x05 #define TARGET_SYS_READ 0x06 #define TARGET_SYS_READC 0x07 #define TARGET_SYS_ISERROR 0x08 #define TARGET_SYS_ISTTY 0x09 #define TARGET_SYS_SEEK 0x0a #define TARGET_SYS_FLEN 0x0c #define TARGET_SYS_TMPNAM 0x0d #define TARGET_SYS_REMOVE 0x0e #define TARGET_SYS_RENAME 0x0f #define TARGET_SYS_CLOCK 0x10 #define TARGET_SYS_TIME 0x11 #define TARGET_SYS_SYSTEM 0x12 #define TARGET_SYS_ERRNO 0x13 #define TARGET_SYS_GET_CMDLINE 0x15 #define TARGET_SYS_HEAPINFO 0x16 #define TARGET_SYS_EXIT 0x18 #define TARGET_SYS_SYNCCACHE 0x19 #define TARGET_SYS_EXIT_EXTENDED 0x20 #define TARGET_SYS_ELAPSED 0x30 #define TARGET_SYS_TICKFREQ 0x31 /* ADP_Stopped_ApplicationExit is used for exit(0), * anything else is implemented as exit(1) */ #define ADP_Stopped_ApplicationExit (0x20026) #ifndef O_BINARY #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, 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_RDWR | GDB_O_CREAT | GDB_O_TRUNC, GDB_O_RDWR | GDB_O_CREAT | GDB_O_TRUNC | GDB_O_BINARY, 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 }; 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 }; 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 #include "exec/address-spaces.h" /* * Find the base of a RAM region containing the specified address */ static inline hwaddr common_semi_find_region_base(hwaddr addr) { MemoryRegion *subregion; /* * Find the chunk of R/W memory containing the address. This is * used for the SYS_HEAPINFO semihosting call, which should * probably be using information from the loaded application. */ QTAILQ_FOREACH(subregion, &get_system_memory()->subregions, subregions_link) { if (subregion->ram && !subregion->readonly) { Int128 top128 = int128_add(int128_make64(subregion->addr), subregion->size); Int128 addr128 = int128_make64(addr); if (subregion->addr <= addr && int128_lt(addr128, top128)) { return subregion->addr; } } } return 0; } #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)); } #ifndef CONFIG_USER_ONLY #include "hw/arm/boot.h" static inline target_ulong common_semi_rambase(CPUState *cs) { CPUArchState *env = cs->env_ptr; const struct arm_boot_info *info = env->boot_info; target_ulong sp; if (info) { return info->loader_start; } if (is_a64(env)) { sp = env->xregs[31]; } else { sp = env->regs[13]; } return common_semi_find_region_base(sp); } #endif #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); } #ifndef CONFIG_USER_ONLY static inline target_ulong common_semi_rambase(CPUState *cs) { RISCVCPU *cpu = RISCV_CPU(cs); CPURISCVState *env = &cpu->env; return common_semi_find_region_base(env->gpr[xSP]); } #endif #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 * real-hardware set of debug functionality. For QEMU, we make the * errno be per-thread in linux-user mode; in softmmu it is a simple * global, and we assume that the guest takes care of avoiding any races. */ #ifndef CONFIG_USER_ONLY static target_ulong syscall_err; #include "exec/softmmu-semi.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 TaskState *ts = cs->opaque; return ts->swi_errno; #else return syscall_err; #endif } 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; } else { /* Fixup syscalls that use nonstardard return conventions. */ switch (reg0) { case TARGET_SYS_WRITE: case TARGET_SYS_READ: reg0 = common_semi_syscall_len - ret; break; case TARGET_SYS_SEEK: reg0 = 0; break; default: reg0 = ret; break; } } common_semi_set_ret(cs, reg0); } 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 return sp - 64; } 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); } 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); 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); } 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. */ 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) { /* * 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) { return 0; } return set_swi_errno(cs, close(gf->hostfd)); } static uint32_t host_writefn(CPUState *cs, GuestFD *gf, target_ulong buf, uint32_t len) { CPUArchState *env = cs->env_ptr; uint32_t ret; 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; } /* Return bytes not written */ return len - ret; } static uint32_t host_readfn(CPUState *cs, GuestFD *gf, target_ulong buf, uint32_t len) { CPUArchState *env = cs->env_ptr; uint32_t ret; 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; } /* Return bytes not read */ return len - ret; } static uint32_t host_isattyfn(CPUState *cs, GuestFD *gf) { return isatty(gf->hostfd); } static uint32_t 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; } static uint32_t 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; } return buf.st_size; } static uint32_t gdb_closefn(CPUState *cs, GuestFD *gf) { return common_semi_gdb_syscall(cs, common_semi_cb, "close,%x", gf->hostfd); } static uint32_t 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); } static uint32_t 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); } static uint32_t gdb_isattyfn(CPUState *cs, GuestFD *gf) { return common_semi_gdb_syscall(cs, common_semi_cb, "isatty,%x", gf->hostfd); } static uint32_t 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); } static uint32_t 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)); } #define SHFB_MAGIC_0 0x53 #define SHFB_MAGIC_1 0x48 #define SHFB_MAGIC_2 0x46 #define SHFB_MAGIC_3 0x42 /* Feature bits reportable in feature byte 0 */ #define SH_EXT_EXIT_EXTENDED (1 << 0) #define SH_EXT_STDOUT_STDERR (1 << 1) static const uint8_t featurefile_data[] = { SHFB_MAGIC_0, SHFB_MAGIC_1, SHFB_MAGIC_2, SHFB_MAGIC_3, 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) { /* Nothing to do */ return 0; } static uint32_t featurefile_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); } static uint32_t featurefile_readfn(CPUState *cs, GuestFD *gf, target_ulong buf, uint32_t len) { CPUArchState *env = cs->env_ptr; uint32_t i; 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->featurefile_offset >= sizeof(featurefile_data)) { break; } s[i] = featurefile_data[gf->featurefile_offset]; gf->featurefile_offset++; } unlock_user(s, buf, len); /* Return number of bytes not read */ return len - i; } static uint32_t featurefile_isattyfn(CPUState *cs, GuestFD *gf) { return 0; } static uint32_t featurefile_seekfn(CPUState *cs, GuestFD *gf, target_ulong offset) { gf->featurefile_offset = offset; return 0; } static uint32_t featurefile_flenfn(CPUState *cs, GuestFD *gf) { return sizeof(featurefile_data); } typedef struct GuestFDFunctions { sys_closefn *closefn; sys_writefn *writefn; sys_readfn *readfn; sys_isattyfn *isattyfn; sys_seekfn *seekfn; sys_flenfn *flenfn; } GuestFDFunctions; static const GuestFDFunctions guestfd_fns[] = { [GuestFDHost] = { .closefn = host_closefn, .writefn = host_writefn, .readfn = host_readfn, .isattyfn = host_isattyfn, .seekfn = host_seekfn, .flenfn = host_flenfn, }, [GuestFDGDB] = { .closefn = gdb_closefn, .writefn = gdb_writefn, .readfn = gdb_readfn, .isattyfn = gdb_isattyfn, .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, }, }; /* * 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. */ static inline bool is_64bit_semihosting(CPUArchState *env) { #if defined(TARGET_ARM) return is_a64(env); #elif defined(TARGET_RISCV) return !riscv_cpu_is_32bit(env); #else #error un-handled architecture #endif } #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); \ } \ } else { \ if (get_user_u32(arg ## n, args + (n) * 4)) { \ errno = EFAULT; \ return set_swi_errno(cs, -1); \ } \ } \ } 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. * * 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. */ target_ulong do_common_semihosting(CPUState *cs) { CPUArchState *env = cs->env_ptr; target_ulong args; target_ulong arg0, arg1, arg2, arg3; target_ulong ul_ret; char * s; int nr; uint32_t ret; uint32_t len; 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); switch (nr) { case TARGET_SYS_OPEN: { int guestfd; GET_ARG(0); GET_ARG(1); GET_ARG(2); s = lock_user_string(arg0); if (!s) { errno = EFAULT; return set_swi_errno(cs, -1); } 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); } if (strcmp(s, ":tt") == 0) { int result_fileno; /* * We implement SH_EXT_STDOUT_STDERR, so: * open for read == stdin * open for write == stdout * open for append == stderr */ if (arg1 < 4) { result_fileno = STDIN_FILENO; } else if (arg1 < 8) { result_fileno = STDOUT_FILENO; } else { result_fileno = 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); /* 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); } init_featurefile_guestfd(guestfd); 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 { ret = set_swi_errno(cs, open(s, open_modeflags[arg1], 0644)); if (ret == (uint32_t)-1) { dealloc_guestfd(guestfd); } else { associate_guestfd(guestfd, ret); ret = guestfd; } } unlock_user(s, arg0, 0); return ret; } case TARGET_SYS_CLOSE: GET_ARG(0); gf = get_guestfd(arg0); if (!gf) { errno = EBADF; return set_swi_errno(cs, -1); } ret = guestfd_fns[gf->type].closefn(cs, gf); dealloc_guestfd(arg0); return ret; case TARGET_SYS_WRITEC: qemu_semihosting_console_outc(cs->env_ptr, args); return 0xdeadbeef; case TARGET_SYS_WRITE0: return qemu_semihosting_console_outs(cs->env_ptr, args); case TARGET_SYS_WRITE: GET_ARG(0); GET_ARG(1); GET_ARG(2); len = arg2; gf = get_guestfd(arg0); if (!gf) { errno = EBADF; return set_swi_errno(cs, -1); } return guestfd_fns[gf->type].writefn(cs, gf, arg1, len); case TARGET_SYS_READ: GET_ARG(0); GET_ARG(1); GET_ARG(2); len = arg2; gf = get_guestfd(arg0); if (!gf) { errno = EBADF; return set_swi_errno(cs, -1); } return guestfd_fns[gf->type].readfn(cs, gf, arg1, len); case TARGET_SYS_READC: return qemu_semihosting_console_inc(cs->env_ptr); case TARGET_SYS_ISERROR: GET_ARG(0); return (target_long) arg0 < 0 ? 1 : 0; case TARGET_SYS_ISTTY: GET_ARG(0); gf = get_guestfd(arg0); if (!gf) { errno = EBADF; return set_swi_errno(cs, -1); } 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); } 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); } return guestfd_fns[gf->type].flenfn(cs, gf); case TARGET_SYS_TMPNAM: 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; /* 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; } free(s); return ul_ret; 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); } return ret; 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); } 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 (s2) unlock_user(s2, arg2, 0); if (s) unlock_user(s, arg0, 0); return ret; } case TARGET_SYS_CLOCK: return clock() / (CLOCKS_PER_SEC / 100); case TARGET_SYS_TIME: return set_swi_errno(cs, time(NULL)); 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; } case TARGET_SYS_ERRNO: return get_swi_errno(cs); case TARGET_SYS_GET_CMDLINE: { /* Build a command-line from the original argv. * * The inputs are: * * arg0, pointer to a buffer of at least the size * specified in arg1. * * arg1, size of the buffer pointed to by arg0 in * bytes. * * The outputs are: * * arg0, pointer to null-terminated string of the * command line. * * arg1, length of the string pointed to by arg0. */ char *output_buffer; size_t input_size; size_t output_size; int status = 0; #if !defined(CONFIG_USER_ONLY) const char *cmdline; #else TaskState *ts = cs->opaque; #endif GET_ARG(0); GET_ARG(1); input_size = arg1; /* Compute the size of the output string. */ #if !defined(CONFIG_USER_ONLY) cmdline = semihosting_get_cmdline(); if (cmdline == NULL) { cmdline = ""; /* Default to an empty line. */ } output_size = strlen(cmdline) + 1; /* Count terminating 0. */ #else unsigned int i; output_size = ts->info->arg_end - ts->info->arg_start; if (!output_size) { /* * We special-case the "empty command line" case (argc==0). * Just provide the terminating 0. */ output_size = 1; } #endif if (output_size > input_size) { /* Not enough space to store command-line arguments. */ errno = E2BIG; return set_swi_errno(cs, -1); } /* 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); } /* 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); } /* Copy the command-line arguments. */ #if !defined(CONFIG_USER_ONLY) pstrcpy(output_buffer, output_size, cmdline); #else if (output_size == 1) { /* Empty command-line. */ output_buffer[0] = '\0'; goto out; } if (copy_from_user(output_buffer, ts->info->arg_start, output_size)) { errno = EFAULT; status = set_swi_errno(cs, -1); goto out; } /* Separate arguments by white spaces. */ for (i = 0; i < output_size - 1; i++) { if (output_buffer[i] == 0) { output_buffer[i] = ' '; } } out: #endif /* Unlock the buffer on the ARM side. */ unlock_user(output_buffer, arg0, output_size); return status; } case TARGET_SYS_HEAPINFO: { target_ulong retvals[4]; target_ulong limit; int i; #ifdef CONFIG_USER_ONLY TaskState *ts = cs->opaque; #else target_ulong rambase = common_semi_rambase(cs); #endif GET_ARG(0); #ifdef CONFIG_USER_ONLY /* * Some C libraries assume the heap immediately follows .bss, so * allocate it using sbrk. */ if (!ts->heap_limit) { abi_ulong ret; ts->heap_base = do_brk(0); limit = ts->heap_base + COMMON_SEMI_HEAP_SIZE; /* Try a big heap, and reduce the size if that fails. */ for (;;) { ret = do_brk(limit); if (ret >= limit) { break; } limit = (ts->heap_base >> 1) + (limit >> 1); } ts->heap_limit = limit; } retvals[0] = ts->heap_base; retvals[1] = ts->heap_limit; retvals[2] = ts->stack_base; retvals[3] = 0; /* Stack limit. */ #else limit = current_machine->ram_size; /* TODO: Make this use the limit of the loaded application. */ retvals[0] = rambase + limit / 2; retvals[1] = rambase + limit; retvals[2] = rambase + limit; /* Stack base */ retvals[3] = rambase; /* Stack limit. */ #endif for (i = 0; i < ARRAY_SIZE(retvals); i++) { bool fail; if (is_64bit_semihosting(env)) { fail = put_user_u64(retvals[i], arg0 + i * 8); } else { fail = put_user_u32(retvals[i], arg0 + i * 4); } if (fail) { /* Couldn't write back to argument block */ errno = EFAULT; return set_swi_errno(cs, -1); } } return 0; } case TARGET_SYS_EXIT: case TARGET_SYS_EXIT_EXTENDED: if (common_semi_sys_exit_extended(cs, nr)) { /* * The A64 version of SYS_EXIT takes a parameter block, * so the application-exit type can return a subcode which * is the exit status code from the application. * SYS_EXIT_EXTENDED is an a new-in-v2.0 optional function * which allows A32/T32 guests to also provide a status code. */ GET_ARG(0); GET_ARG(1); if (arg0 == ADP_Stopped_ApplicationExit) { ret = arg1; } else { ret = 1; } } else { /* * The A32/T32 version of SYS_EXIT specifies only * Stopped_ApplicationExit as normal exit, but does not * allow the guest to specify the exit status code. * Everything else is considered an error. */ ret = (args == ADP_Stopped_ApplicationExit) ? 0 : 1; } gdb_exit(ret); exit(ret); case TARGET_SYS_ELAPSED: elapsed = get_clock() - clock_start; if (sizeof(target_ulong) == 8) { SET_ARG(0, elapsed); } else { SET_ARG(0, (uint32_t) elapsed); SET_ARG(1, (uint32_t) (elapsed >> 32)); } return 0; case TARGET_SYS_TICKFREQ: /* qemu always uses nsec */ return 1000000000; case TARGET_SYS_SYNCCACHE: /* * Clean the D-cache and invalidate the I-cache for the specified * 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)) { return 0; } #endif #ifdef TARGET_RISCV return 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(); } }