From 374e658eb110a5250ce6e32561359b54def07960 Mon Sep 17 00:00:00 2001 From: Denis Drakhnya Date: Sat, 20 Feb 2021 14:17:37 +0200 Subject: [PATCH] e2k: Add access_hw_stacks and backtrace syscalls. Special system calls for managing hardware stacks. Required for C++ exceptions. Signed-off-by: Denis Drakhnya --- linux-user/e2k/signal.c | 6 +- linux-user/e2k/target_syscall.h | 2 +- linux-user/strace.list | 6 + linux-user/syscall.c | 423 +++++++++++++++++++++++++++++++- target/e2k/cpu.h | 16 +- target/e2k/helper.c | 11 +- target/e2k/helper_int.c | 4 +- target/e2k/translate.c | 7 +- target/e2k/translate/alc.c | 1 + 9 files changed, 440 insertions(+), 36 deletions(-) diff --git a/linux-user/e2k/signal.c b/linux-user/e2k/signal.c index 63d2cefff4..98dc3cb1cc 100644 --- a/linux-user/e2k/signal.c +++ b/linux-user/e2k/signal.c @@ -223,7 +223,7 @@ static void target_setup_frame(int sig, struct target_sigaction *ka, env->wd.psize = 0; env->usd.size = env->sbr - frame_addr; env->usd.base = frame_addr; - helper_signal_frame(env, 2, E2K_SYSRET_ADDR_CTPR); + helper_signal_frame(env, 2, E2K_SIGRET_ADDR); env->ip = ka->_sa_handler; env->regs[0] = sig; @@ -232,9 +232,9 @@ static void target_setup_frame(int sig, struct target_sigaction *ka, if (info && (ka->sa_flags & TARGET_SA_SIGINFO)) { tswap_siginfo(&frame->info, info); - env->regs[1] = (uint64_t) &frame->info; + env->regs[1] = frame_addr + offsetof(struct target_sigframe, info); env->tags[1] = E2K_TAG_NUMBER64; - env->regs[2] = (uint64_t) &frame->uc; + env->regs[2] = frame_addr + offsetof(struct target_sigframe, uc); env->tags[2] = E2K_TAG_NUMBER64; } diff --git a/linux-user/e2k/target_syscall.h b/linux-user/e2k/target_syscall.h index f1721e5cc7..aa39d55781 100644 --- a/linux-user/e2k/target_syscall.h +++ b/linux-user/e2k/target_syscall.h @@ -70,7 +70,7 @@ struct target_pt_regs { #define TARGET_PAGE_OFFSET 0x0000d00000000000UL #define TARGET_TASK_SIZE TARGET_PAGE_OFFSET #else -#define TARGET_TASK_SIZE 0xf0000000UL +#define TARGET_TASK_SIZE 0xe0000000UL #endif /* modes for sys_access_hw_stacks */ diff --git a/linux-user/strace.list b/linux-user/strace.list index 467e26a039..95f2ffdda0 100644 --- a/linux-user/strace.list +++ b/linux-user/strace.list @@ -133,6 +133,12 @@ { TARGET_NR_access_hw_stacks, "access_hw_stacks", NULL, print_access_hw_stacks, NULL }, #endif +#ifdef TARGET_NR_set_backtrace +{ TARGET_NR_set_backtrace, "set_backtrace", "%s(%p,%lu,%lu,%lu)", NULL, NULL }, +#endif +#ifdef TARGET_NR_get_backtrace +{ TARGET_NR_get_backtrace, "get_backtrace", "%s(%p,%lu,%lu,%lu)", NULL, NULL }, +#endif #ifdef TARGET_NR_epoll_create { TARGET_NR_epoll_create, "epoll_create", "%s(%d)", NULL, NULL }, #endif diff --git a/linux-user/syscall.c b/linux-user/syscall.c index 5860ceb35b..043f72bbbd 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -7089,6 +7089,405 @@ static abi_long do_e2k_longjmp2(CPUE2KState *env, struct target_jmp_info *jmp_in return 0; } + +static abi_long copy_current_chain_stack(abi_ulong dst, abi_ulong src, + abi_ulong size) +{ + E2KCrs *from, *to; + abi_long ret = 0; + int i; + + if (!QEMU_IS_ALIGNED(src, sizeof(*from)) || + !QEMU_IS_ALIGNED(size, sizeof(*from))) + { + return -TARGET_EINVAL; + } + + from = lock_user(VERIFY_READ, src, size, 1); + to = lock_user(VERIFY_WRITE, dst, size, 0); + if (!to || !from) { + ret = -TARGET_EFAULT; + goto exit; + } + + for (i = 0; i < size; i += sizeof(E2KCrs), to++, from++) { + E2KCrs crs = *from; + target_ulong ip; + + memset(to, 0, sizeof(*to)); + + to->cr0_lo = from->cr0_lo; + ip = crs.cr0_hi & ~7; + if (ip < TARGET_TASK_SIZE) { + to->cr0_hi = ip; + to->cr1.ussz = from->cr1.ussz; + } + // FIXME: check what exactly needs to be copied + to->cr1 = from->cr1; + } + +exit: + unlock_user(from, src, size); + unlock_user(to, dst, size); + return ret; +} + +static abi_long copy_procedure_stack(abi_ulong dst, abi_ulong dst_tag, abi_ulong src, + abi_ulong size) +{ + abi_ullong *from, *to; + uint8_t *to_tag = NULL; + abi_long ret = 0; + int i; + + from = lock_user(VERIFY_READ, src, size, 1); + to = lock_user(VERIFY_WRITE, dst, size, 0); + if (!to || !from) { + ret = -TARGET_EFAULT; + goto exit; + } + + if (dst_tag) { + to_tag = lock_user(VERIFY_WRITE, dst_tag, size / 8, 0); + if (!to_tag) { + ret = -TARGET_EFAULT; + goto exit; + } + + // TODO: what to do with tags? + memset(to_tag, 0, size / 8); + } + + for (i = 0; i < size; i += sizeof(abi_ullong), to++, from++) { + *to = *from; + } + +exit: + unlock_user(from, src, size); + unlock_user(to_tag, dst_tag, size / 8); + unlock_user(to, dst, size); + return ret; +} + +static abi_long do_e2k_access_hw_stacks(CPUState *cpu, abi_ulong arg2, + abi_ulong arg3, abi_ulong arg4, abi_ulong arg5, abi_ulong arg6) +{ + E2KCPU *e2k_cpu = E2K_CPU(cpu); + CPUE2KState *env = &e2k_cpu->env; + abi_ulong mode = arg2; + abi_ulong frame_addr = arg3; // __user (abi_ullong *) + abi_ulong buf_addr = arg4; // __user (char *) + abi_ulong buf_size = arg5; + abi_ulong size_addr = arg6; // __user (void *) + int ret = 0; + + switch (mode) { + case GET_PROCEDURE_STACK_SIZE: + ret = put_user(env->psp.index, size_addr, target_ulong); + break; + case GET_CHAIN_STACK_SIZE: + ret = put_user(env->pcsp.index + sizeof(E2KCrs), size_addr, target_ulong); + break; + case GET_CHAIN_STACK_OFFSET: + { + abi_ullong frame, pcs_top; + + ret = get_user(frame, frame_addr, abi_ullong); + if (ret) { + return ret; + } + pcs_top = env->pcsp.base + env->pcsp.size; + if (env->pcsp.base > frame || pcs_top <= frame) { + return -TARGET_ESRCH; + } + ret = put_user(frame - env->pcsp.base, size_addr, target_ulong); + break; + } + case READ_CHAIN_STACK: + { + abi_ullong frame, pcs_used_top; + abi_ulong used_size; + + ret = get_user(frame, frame_addr, abi_ullong); + if (ret) { + return ret; + } + pcs_used_top = env->pcsp.base + env->pcsp.index; + if (frame < env->pcsp.base || frame > pcs_used_top) { + return -TARGET_EINVAL; + } + used_size = frame - env->pcsp.base; + if (size_addr) { + ret = put_user(used_size, size_addr, target_ulong); + if (ret) { + return ret; + } + } + if (used_size > buf_size) { + return -TARGET_ENOMEM; + } + ret = copy_current_chain_stack(buf_addr, env->pcsp.base, + used_size); + break; + } + case READ_CHAIN_STACK_EX: + case WRITE_CHAIN_STACK_EX: + { + abi_ullong frame; + abi_ulong dst, src; + + ret = get_user(frame, frame_addr, abi_ullong); + if (ret) { + return ret; + } + if ((env->pcsp.index + sizeof(E2KCrs)) < (frame + buf_size)) { + return -TARGET_EFAULT; + } + if (mode == READ_CHAIN_STACK_EX) { + dst = buf_addr; + src = env->pcsp.base + frame; + } else { + dst = env->pcsp.base + frame; + src = buf_addr; + } + ret = copy_current_chain_stack(dst, src, buf_size); + break; + } + case READ_PROCEDURE_STACK: + case WRITE_PROCEDURE_STACK: + { + abi_ullong offset, ps_used_top; + abi_ulong used_size, dst, dst_tag, src; + + ret = get_user(offset, frame_addr, abi_ullong); + if (ret) { + return ret; + } + ps_used_top = env->psp.base + env->psp.index; + if (offset < env->psp.base || offset > ps_used_top) { + return -TARGET_EINVAL; + } + used_size = offset - env->psp.base; + if (size_addr) { + ret = put_user(used_size, size_addr, target_ulong); + if (ret) { + return ret; + } + } + if (used_size > buf_size) { + return -TARGET_ENOMEM; + } + if (mode == READ_PROCEDURE_STACK) { + dst = buf_addr; + dst_tag = 0; + src = env->psp.base; + } else { + dst = env->psp.base; + dst_tag = env->psp.base_tag; + src = buf_addr; + } + ret = copy_procedure_stack(dst, dst_tag, src, used_size); + break; + } + case READ_PROCEDURE_STACK_EX: + case WRITE_PROCEDURE_STACK_EX: + { + abi_ullong offset; + abi_ulong dst, dst_tag, src; + + ret = get_user(offset, frame_addr, abi_ullong); + if (ret) { + return ret; + } + if (env->psp.index < (offset + buf_size)) { + return -TARGET_EFAULT; + } + if (mode == READ_PROCEDURE_STACK_EX) { + dst = buf_addr; + dst_tag = 0; + src = env->psp.base + offset; + } else { + dst = env->psp.base + offset; + dst_tag = env->psp.base_tag + offset / 8; + src = buf_addr; + } + ret = copy_procedure_stack(dst, dst_tag, src, buf_size); + break; + } + default: + return -TARGET_ENOSYS; + } + return ret; +} + +static bool is_privileged_return(target_ulong ip) +{ + return ip == E2K_SYSRET_BACKTRACE_ADDR; +} + +static abi_long do_e2k_set_backtrace(CPUState *cpu, abi_ulong buf_addr, + abi_ulong count, abi_ulong skip, abi_ulong flags) +{ + E2KCPU *e2k_cpu = E2K_CPU(cpu); + CPUE2KState *env = &e2k_cpu->env; + E2KCrs *pcs_base, *frame; + target_ulong *buf; + abi_long ret = 0; + int nr_written = 0; + uint64_t cr0_hi; + E2KCr1 cr1; + + if (flags) { + return -TARGET_EINVAL; + } + + buf = lock_user(VERIFY_READ, buf_addr, count * sizeof(*buf), 1); + pcs_base = lock_user(VERIFY_WRITE, env->pcsp.base, env->pcsp.index, 1); + if (!buf || !pcs_base) { + ret = -TARGET_EFAULT; + goto exit; + } + frame = pcs_base + env->pcsp.index / sizeof(*frame); + + while (nr_written < count) { + target_ulong prev_ip, ip; + + frame -= 1; + if (frame < pcs_base) { + /* Not an error: we will just return the size */ + break; + } + + __get_user(ip, buf); + __get_user(cr0_hi, &frame->cr0_hi); + __get_user(cr1.lo, &frame->cr1.lo); + + if (ip == -1) { + ip = E2K_SYSRET_BACKTRACE_ADDR; + } + + prev_ip = cr0_hi & ~7; + + /* skip fake kernel frames */ + if (prev_ip >= TARGET_TASK_SIZE) { + continue; + } + + /* Skip the requested number of frames */ + if (skip) { + --skip; + continue; + } + + if (!is_privileged_return(ip) && !access_ok(cpu, VERIFY_READ, ip, 8)) { + ret = -TARGET_EFAULT; + break; + } + + /* Forbid changing of special return value into normal + * one - to avoid cases when user changes to special and + * back to normal function to avoid security checks. */ + if (is_privileged_return(prev_ip) && !is_privileged_return(ip)) { + ret = -TARGET_EPERM; + break; + } + + // TODO: ip/prev_ip page flags checks + + cr0_hi = ip & ~7; + + __put_user(cr0_hi, &frame->cr0_hi); + if (is_privileged_return(ip)) { + cr1.pm = 1; + cr1.ic = 0; + __put_user(cr1.lo, &frame->cr1.lo); + } + + buf += 1; + nr_written += 1; + } + + if (nr_written) { + ret = nr_written; + } + +exit: + unlock_user(pcs_base, env->pcsp.base, env->pcsp.index); + unlock_user(buf, buf_addr, count * sizeof(buf)); + return ret; +} + +static abi_long do_e2k_get_backtrace(CPUState *cpu, abi_ulong buf_addr, + abi_ulong count, abi_ulong skip, abi_ulong flags) +{ + E2KCPU *e2k_cpu = E2K_CPU(cpu); + CPUE2KState *env = &e2k_cpu->env; + E2KCrs *pcs_base, *frame; + target_ulong *buf; + abi_long ret = 0; + int nr_read = 0; + uint64_t cr0_hi; + + if (flags) { + return -TARGET_EINVAL; + } + + buf = lock_user(VERIFY_WRITE, buf_addr, count * sizeof(*buf), 0); + pcs_base = lock_user(VERIFY_READ, env->pcsp.base, env->pcsp.index, 1); + if (!buf || !pcs_base) { + ret = -TARGET_EFAULT; + goto exit; + } + frame = pcs_base + env->pcsp.index / sizeof(*frame); + + while (nr_read < count) { + target_ulong ip; + + frame -= 1; + if (frame < pcs_base) { + /* Not an error: we will just return the size */ + break; + } + + __get_user(cr0_hi, &frame->cr0_hi); + ip = cr0_hi & ~7; + + /* skip fake kernel frames */ + if (!is_privileged_return(ip) && ip >= TARGET_TASK_SIZE) { + continue; + } + + /* Skip the requested number of frames */ + if (skip) { + --skip; + continue; + } + + if (!is_privileged_return(ip) && !access_ok(cpu, VERIFY_READ, ip, 8)) { + ret = -TARGET_EFAULT; + break; + } + + /* Special case of "just return" function */ + if (is_privileged_return(ip)) { + ip = -1; + } + + __put_user(ip, buf); + + buf += 1; + nr_read += 1; + } + + if (nr_read) { + ret = nr_read; + } + +exit: + unlock_user(pcs_base, env->pcsp.base, env->pcsp.index); + unlock_user(buf, buf_addr, count * sizeof(buf)); + return ret; +} #endif /* end of TARGET_E2K */ static abi_long do_fcntl(int fd, int cmd, abi_ulong arg) @@ -11999,24 +12398,24 @@ static abi_long do_syscall1(CPUArchState *cpu_env, int num, abi_long arg1, if (ret) { break; } - do_e2k_longjmp2(env, &ji); + ret = do_e2k_longjmp2(env, &ji); + if (ret) { + break; + } return arg2; } #endif #ifdef TARGET_NR_access_hw_stacks case TARGET_NR_access_hw_stacks: - { -#if 0 - abi_ulong mode = arg2; - abi_ulong frame_ptr = arg3; // __user (abi_ullong *) - abi_ulong buf = arg4; // __user (char *) - abi_ulong buf_size = arg5; - abi_ulong real_size = arg6; // __user (void *) + return do_e2k_access_hw_stacks(cpu, arg1, arg2, arg3, arg4, arg5); #endif - - // TODO: e2k_sys_access_hw_stacks - return -TARGET_ENOSYS; - } +#ifdef TARGET_NR_set_backtrace + case TARGET_NR_set_backtrace: + return do_e2k_set_backtrace(cpu, arg1, arg2, arg3, arg4); +#endif +#ifdef TARGET_NR_get_backtrace + case TARGET_NR_get_backtrace: + return do_e2k_get_backtrace(cpu, arg1, arg2, arg3, arg4); #endif #ifdef CONFIG_ATTR #ifdef TARGET_NR_setxattr diff --git a/target/e2k/cpu.h b/target/e2k/cpu.h index e8ae05dc8b..68709b2cb2 100644 --- a/target/e2k/cpu.h +++ b/target/e2k/cpu.h @@ -93,19 +93,17 @@ typedef enum { # if TARGET_LONG_BITS == 64 # define E2K_FAKE_KERN_START 0xe20000000000 # define E2K_FAKE_KERN_END 0xe30000000000 -# define E2K_SYSRET_ADDR (E2K_FAKE_KERN_START + 0x8000000000) -# define E2K_SYSRET_ADDR_CTPR (E2K_FAKE_KERN_START + 0xfffffffff8) -# define E2K_SYSCALL_ADDR3 (E2K_FAKE_KERN_START + 0x800 * 3) -# define E2K_SYSCALL_ADDR6 (E2K_FAKE_KERN_START + 0x800 * 6) # else /* TARGET_LONG_BITS == 32 */ # define E2K_FAKE_KERN_START 0xe0000000 # define E2K_FAKE_KERN_END 0xe3000000 -# define E2K_SYSRET_ADDR (E2K_FAKE_KERN_START + 0x800000) -# define E2K_SYSRET_ADDR_CTPR (E2K_FAKE_KERN_START + 0xfffff8) -# define E2K_SYSCALL_ADDR1 (E2K_FAKE_KERN_START + 0x800 * 1) -# define E2K_SYSCALL_ADDR4 (E2K_FAKE_KERN_START + 0x800 * 4) # endif +# define E2K_SYSCALL_ADDR1 (E2K_FAKE_KERN_START + 0x800 * 1) +# define E2K_SYSCALL_ADDR3 (E2K_FAKE_KERN_START + 0x800 * 3) +# define E2K_SYSCALL_ADDR4 (E2K_FAKE_KERN_START + 0x800 * 4) +# define E2K_SYSCALL_ADDR6 (E2K_FAKE_KERN_START + 0x800 * 6) +# define E2K_SYSRET_ADDR (E2K_FAKE_KERN_START + 0x15700) # define E2K_SIGRET_ADDR (E2K_FAKE_KERN_START + 0x15800) +# define E2K_SYSRET_BACKTRACE_ADDR (E2K_FAKE_KERN_START + 0x15900) #endif #define WD_BASE_OFF 0 @@ -373,7 +371,7 @@ typedef struct { uint64_t unused2: 40; uint64_t cui: 16; uint64_t ic: 1; - uint64_t pm: 1; + uint64_t pm: 1; /* privileged mode */ uint64_t ie: 1; uint64_t sge: 1; uint64_t lw: 1; diff --git a/target/e2k/helper.c b/target/e2k/helper.c index 5c163f06d7..84e7c7a43b 100644 --- a/target/e2k/helper.c +++ b/target/e2k/helper.c @@ -226,15 +226,10 @@ uint64_t HELPER(prep_return)(CPUE2KState *env, int ipd) target_ulong addr = env->pcsp.base + env->pcsp.index + offsetof(E2KCrs, cr0_hi); uint64_t cr0_hi = cpu_ldq_le_data(env, addr) & ~7; - if (cr0_hi == E2K_SYSRET_ADDR_CTPR) { - ret.base = E2K_SIGRET_ADDR; - ret.opc = CTPR_OPC_SIGRET; - } else { - ret.base = cr0_hi; - } - - ret.tag = CTPR_TAG_RETURN; ret.ipd = ipd; + ret.base = cr0_hi; + ret.tag = CTPR_TAG_RETURN; + ret.opc = cr0_hi == E2K_SIGRET_ADDR ? CTPR_OPC_SIGRET : 0; return ret.raw; } diff --git a/target/e2k/helper_int.c b/target/e2k/helper_int.c index 1c138f0041..134edfd977 100644 --- a/target/e2k/helper_int.c +++ b/target/e2k/helper_int.c @@ -40,8 +40,10 @@ uint64_t helper_state_reg_read_i64(CPUE2KState *env, int idx) { switch (idx) { case 0x01: return e2k_state_wd(env); /* %wd */ - case 0x0f: return e2k_state_pcsp_lo(env); /* %pcsp.lo */ + case 0x07: return e2k_state_psp_hi(env); /* %psp.hi */ + case 0x09: return e2k_state_psp_lo(env); /* %psp.lo */ case 0x0d: return e2k_state_pcsp_hi(env); /* %pcsp.hi */ + case 0x0f: return e2k_state_pcsp_lo(env); /* %pcsp.lo */ case 0x13: return 0; /* %pcshtp */ case 0x2c: return env->usd.hi; /* %usd.hi */ case 0x2d: return env->usd.lo; /* %usd.lo */ diff --git a/target/e2k/translate.c b/target/e2k/translate.c index 2cb16a3aa8..8a549c9223 100644 --- a/target/e2k/translate.c +++ b/target/e2k/translate.c @@ -1150,7 +1150,9 @@ static void e2k_tr_translate_insn(DisasContextBase *db, CPUState *cs) gen_helper_syscall(cpu_env); tcg_gen_exit_tb(NULL, TB_EXIT_IDX0); break; - case E2K_SYSRET_ADDR: { + case E2K_SYSRET_BACKTRACE_ADDR: + case E2K_SYSRET_ADDR: + { /* fake return from syscall handler */ TCGv_i32 t0 = tcg_const_i32(0); @@ -1170,7 +1172,8 @@ static void e2k_tr_translate_insn(DisasContextBase *db, CPUState *cs) tcg_gen_exit_tb(NULL, TB_EXIT_IDX0); break; #endif /* CONFIG_USER_ONLY */ - default: { + default: + { pc_next = do_decode(ctx, cs); #ifdef CONFIG_USER_ONLY if (ctx->bundle2.cs1.type == CS1_CALL) { diff --git a/target/e2k/translate/alc.c b/target/e2k/translate/alc.c index 00b8efcda2..f184874b1c 100644 --- a/target/e2k/translate/alc.c +++ b/target/e2k/translate/alc.c @@ -1617,6 +1617,7 @@ static inline void gen_rr_i32(Instr *instr) TCGv_i32 t0 = tcg_const_i32(instr->src1); gen_save_cpu_state(instr->ctx); + // FIXME: output size should be affected by wdbl gen_helper_state_reg_read_i32(dst, cpu_env, t0); set_al_result_reg32(instr, dst); tcg_temp_free_i32(t0);