diff --git a/target/arm/arm-semi.c b/target/arm/arm-semi.c index 302529f227..a4741d7e11 100644 --- a/target/arm/arm-semi.c +++ b/target/arm/arm-semi.c @@ -106,6 +106,113 @@ static int open_modeflags[12] = { O_RDWR | O_CREAT | O_APPEND | O_BINARY }; +typedef enum GuestFDType { + GuestFDUnused = 0, + GuestFDHost = 1, +} GuestFDType; + +/* + * Guest file descriptors are integer indexes into an array of + * these structures (we will dynamically resize as necessary). + */ +typedef struct GuestFD { + GuestFDType type; + int hostfd; +} GuestFD; + +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. + */ +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)); + } + + for (i = 0; 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 fd. + */ +static void associate_guestfd(int guestfd, int hostfd) +{ + GuestFD *gf = do_get_guestfd(guestfd); + + assert(gf); + gf->type = 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; +} + #ifdef CONFIG_USER_ONLY static inline uint32_t set_swi_errno(TaskState *ts, uint32_t code) { @@ -207,6 +314,34 @@ static void arm_semi_flen_cb(CPUState *cs, target_ulong ret, target_ulong err) #endif } +static int arm_semi_open_guestfd; + +static void arm_semi_open_cb(CPUState *cs, target_ulong ret, target_ulong err) +{ + ARMCPU *cpu = ARM_CPU(cs); + CPUARMState *env = &cpu->env; +#ifdef CONFIG_USER_ONLY + TaskState *ts = cs->opaque; +#endif + if (ret == (target_ulong)-1) { +#ifdef CONFIG_USER_ONLY + ts->swi_errno = err; +#else + syscall_err = err; +#endif + dealloc_guestfd(arm_semi_open_guestfd); + } else { + associate_guestfd(arm_semi_open_guestfd, ret); + ret = arm_semi_open_guestfd; + } + + if (is_a64(env)) { + env->xregs[0] = ret; + } else { + env->regs[0] = ret; + } +} + static target_ulong arm_gdb_syscall(ARMCPU *cpu, gdb_syscall_complete_cb cb, const char *fmt, ...) { @@ -283,6 +418,7 @@ target_ulong do_arm_semihosting(CPUARMState *env) #else CPUARMState *ts = env; #endif + GuestFD *gf; if (is_a64(env)) { /* Note that the syscall number is in W0, not X0 */ @@ -295,6 +431,9 @@ target_ulong do_arm_semihosting(CPUARMState *env) switch (nr) { case TARGET_SYS_OPEN: + { + int guestfd; + GET_ARG(0); GET_ARG(1); GET_ARG(2); @@ -308,26 +447,52 @@ target_ulong do_arm_semihosting(CPUARMState *env) errno = EINVAL; return set_swi_errno(ts, -1); } + + guestfd = alloc_guestfd(); + if (guestfd < 0) { + unlock_user(s, arg0, 0); + errno = EMFILE; + return set_swi_errno(ts, -1); + } + if (strcmp(s, ":tt") == 0) { int result_fileno = arg1 < 4 ? STDIN_FILENO : STDOUT_FILENO; + associate_guestfd(guestfd, result_fileno); unlock_user(s, arg0, 0); - return result_fileno; + return guestfd; } if (use_gdb_syscalls()) { - ret = arm_gdb_syscall(cpu, arm_semi_cb, "open,%s,%x,1a4", arg0, + arm_semi_open_guestfd = guestfd; + ret = arm_gdb_syscall(cpu, arm_semi_open_cb, "open,%s,%x,1a4", arg0, (int)arg2+1, gdb_open_modeflags[arg1]); } else { ret = set_swi_errno(ts, 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); - if (use_gdb_syscalls()) { - return arm_gdb_syscall(cpu, arm_semi_cb, "close,%x", arg0); - } else { - return set_swi_errno(ts, close(arg0)); + + gf = get_guestfd(arg0); + if (!gf) { + errno = EBADF; + return set_swi_errno(ts, -1); } + + if (use_gdb_syscalls()) { + ret = arm_gdb_syscall(cpu, arm_semi_cb, "close,%x", gf->hostfd); + } else { + ret = set_swi_errno(ts, close(gf->hostfd)); + } + dealloc_guestfd(arg0); + return ret; case TARGET_SYS_WRITEC: qemu_semihosting_console_outc(env, args); return 0xdeadbeef; @@ -338,17 +503,24 @@ target_ulong do_arm_semihosting(CPUARMState *env) GET_ARG(1); GET_ARG(2); len = arg2; + + gf = get_guestfd(arg0); + if (!gf) { + errno = EBADF; + return set_swi_errno(ts, -1); + } + if (use_gdb_syscalls()) { arm_semi_syscall_len = len; return arm_gdb_syscall(cpu, arm_semi_cb, "write,%x,%x,%x", - arg0, arg1, len); + gf->hostfd, arg1, len); } else { s = lock_user(VERIFY_READ, arg1, len, 1); if (!s) { /* Return bytes not written on error */ return len; } - ret = set_swi_errno(ts, write(arg0, s, len)); + ret = set_swi_errno(ts, write(gf->hostfd, s, len)); unlock_user(s, arg1, 0); if (ret == (uint32_t)-1) { ret = 0; @@ -361,10 +533,17 @@ target_ulong do_arm_semihosting(CPUARMState *env) GET_ARG(1); GET_ARG(2); len = arg2; + + gf = get_guestfd(arg0); + if (!gf) { + errno = EBADF; + return set_swi_errno(ts, -1); + } + if (use_gdb_syscalls()) { arm_semi_syscall_len = len; return arm_gdb_syscall(cpu, arm_semi_cb, "read,%x,%x,%x", - arg0, arg1, len); + gf->hostfd, arg1, len); } else { s = lock_user(VERIFY_WRITE, arg1, len, 0); if (!s) { @@ -372,7 +551,7 @@ target_ulong do_arm_semihosting(CPUARMState *env) return len; } do { - ret = set_swi_errno(ts, read(arg0, s, len)); + ret = set_swi_errno(ts, read(gf->hostfd, s, len)); } while (ret == -1 && errno == EINTR); unlock_user(s, arg1, len); if (ret == (uint32_t)-1) { @@ -386,31 +565,52 @@ target_ulong do_arm_semihosting(CPUARMState *env) return 0; case TARGET_SYS_ISTTY: GET_ARG(0); + + gf = get_guestfd(arg0); + if (!gf) { + errno = EBADF; + return set_swi_errno(ts, -1); + } + if (use_gdb_syscalls()) { - return arm_gdb_syscall(cpu, arm_semi_cb, "isatty,%x", arg0); + return arm_gdb_syscall(cpu, arm_semi_cb, "isatty,%x", gf->hostfd); } else { - return isatty(arg0); + return isatty(gf->hostfd); } case TARGET_SYS_SEEK: GET_ARG(0); GET_ARG(1); + + gf = get_guestfd(arg0); + if (!gf) { + errno = EBADF; + return set_swi_errno(ts, -1); + } + if (use_gdb_syscalls()) { return arm_gdb_syscall(cpu, arm_semi_cb, "lseek,%x,%x,0", - arg0, arg1); + gf->hostfd, arg1); } else { - ret = set_swi_errno(ts, lseek(arg0, arg1, SEEK_SET)); + ret = set_swi_errno(ts, lseek(gf->hostfd, arg1, SEEK_SET)); if (ret == (uint32_t)-1) return -1; return 0; } case TARGET_SYS_FLEN: GET_ARG(0); + + gf = get_guestfd(arg0); + if (!gf) { + errno = EBADF; + return set_swi_errno(ts, -1); + } + if (use_gdb_syscalls()) { return arm_gdb_syscall(cpu, arm_semi_flen_cb, "fstat,%x,%x", - arg0, arm_flen_buf(cpu)); + gf->hostfd, arm_flen_buf(cpu)); } else { struct stat buf; - ret = set_swi_errno(ts, fstat(arg0, &buf)); + ret = set_swi_errno(ts, fstat(gf->hostfd, &buf)); if (ret == (uint32_t)-1) return -1; return buf.st_size;