| /* | |
| * Emulation of Linux signals | |
| * | |
| * Copyright (c) 2003 Fabrice Bellard | |
| * | |
| * 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 <http://www.gnu.org/licenses/>. | |
| */ | |
| #include "qemu/osdep.h" | |
| #include "qemu.h" | |
| #include "user-internals.h" | |
| #include "signal-common.h" | |
| #include "linux-user/trace.h" | |
| struct target_sigcontext { | |
| abi_ulong sc_pc; | |
| abi_ulong sc_ps; | |
| abi_ulong sc_lbeg; | |
| abi_ulong sc_lend; | |
| abi_ulong sc_lcount; | |
| abi_ulong sc_sar; | |
| abi_ulong sc_acclo; | |
| abi_ulong sc_acchi; | |
| abi_ulong sc_a[16]; | |
| abi_ulong sc_xtregs; | |
| }; | |
| struct target_ucontext { | |
| abi_ulong tuc_flags; | |
| abi_ulong tuc_link; | |
| target_stack_t tuc_stack; | |
| struct target_sigcontext tuc_mcontext; | |
| target_sigset_t tuc_sigmask; | |
| }; | |
| struct target_rt_sigframe { | |
| target_siginfo_t info; | |
| struct target_ucontext uc; | |
| /* TODO: xtregs */ | |
| uint8_t retcode[6]; | |
| abi_ulong window[4]; | |
| }; | |
| static abi_ulong get_sigframe(struct target_sigaction *sa, | |
| CPUXtensaState *env, | |
| unsigned long framesize) | |
| { | |
| abi_ulong sp; | |
| sp = target_sigsp(get_sp_from_cpustate(env), sa); | |
| return (sp - framesize) & -16; | |
| } | |
| static int flush_window_regs(CPUXtensaState *env) | |
| { | |
| uint32_t wb = env->sregs[WINDOW_BASE]; | |
| uint32_t ws = xtensa_replicate_windowstart(env) >> (wb + 1); | |
| unsigned d = ctz32(ws) + 1; | |
| unsigned i; | |
| int ret = 0; | |
| for (i = d; i < env->config->nareg / 4; i += d) { | |
| uint32_t ssp, osp; | |
| unsigned j; | |
| ws >>= d; | |
| xtensa_rotate_window(env, d); | |
| if (ws & 0x1) { | |
| ssp = env->regs[5]; | |
| d = 1; | |
| } else if (ws & 0x2) { | |
| ssp = env->regs[9]; | |
| ret |= get_user_ual(osp, env->regs[1] - 12); | |
| osp -= 32; | |
| d = 2; | |
| } else if (ws & 0x4) { | |
| ssp = env->regs[13]; | |
| ret |= get_user_ual(osp, env->regs[1] - 12); | |
| osp -= 48; | |
| d = 3; | |
| } else { | |
| g_assert_not_reached(); | |
| } | |
| for (j = 0; j < 4; ++j) { | |
| ret |= put_user_ual(env->regs[j], ssp - 16 + j * 4); | |
| } | |
| for (j = 4; j < d * 4; ++j) { | |
| ret |= put_user_ual(env->regs[j], osp - 16 + j * 4); | |
| } | |
| } | |
| xtensa_rotate_window(env, d); | |
| g_assert(env->sregs[WINDOW_BASE] == wb); | |
| return ret == 0; | |
| } | |
| static int setup_sigcontext(struct target_rt_sigframe *frame, | |
| CPUXtensaState *env) | |
| { | |
| struct target_sigcontext *sc = &frame->uc.tuc_mcontext; | |
| int i; | |
| __put_user(env->pc, &sc->sc_pc); | |
| __put_user(env->sregs[PS], &sc->sc_ps); | |
| __put_user(env->sregs[LBEG], &sc->sc_lbeg); | |
| __put_user(env->sregs[LEND], &sc->sc_lend); | |
| __put_user(env->sregs[LCOUNT], &sc->sc_lcount); | |
| if (!flush_window_regs(env)) { | |
| return 0; | |
| } | |
| for (i = 0; i < 16; ++i) { | |
| __put_user(env->regs[i], sc->sc_a + i); | |
| } | |
| __put_user(0, &sc->sc_xtregs); | |
| /* TODO: xtregs */ | |
| return 1; | |
| } | |
| static void install_sigtramp(uint8_t *tramp) | |
| { | |
| #if TARGET_BIG_ENDIAN | |
| /* Generate instruction: MOVI a2, __NR_rt_sigreturn */ | |
| __put_user(0x22, &tramp[0]); | |
| __put_user(0x0a, &tramp[1]); | |
| __put_user(TARGET_NR_rt_sigreturn, &tramp[2]); | |
| /* Generate instruction: SYSCALL */ | |
| __put_user(0x00, &tramp[3]); | |
| __put_user(0x05, &tramp[4]); | |
| __put_user(0x00, &tramp[5]); | |
| #else | |
| /* Generate instruction: MOVI a2, __NR_rt_sigreturn */ | |
| __put_user(0x22, &tramp[0]); | |
| __put_user(0xa0, &tramp[1]); | |
| __put_user(TARGET_NR_rt_sigreturn, &tramp[2]); | |
| /* Generate instruction: SYSCALL */ | |
| __put_user(0x00, &tramp[3]); | |
| __put_user(0x50, &tramp[4]); | |
| __put_user(0x00, &tramp[5]); | |
| #endif | |
| } | |
| void setup_rt_frame(int sig, struct target_sigaction *ka, | |
| target_siginfo_t *info, | |
| target_sigset_t *set, CPUXtensaState *env) | |
| { | |
| abi_ulong frame_addr; | |
| struct target_rt_sigframe *frame; | |
| uint32_t ra; | |
| bool abi_call0; | |
| unsigned base; | |
| int i; | |
| frame_addr = get_sigframe(ka, env, sizeof(*frame)); | |
| trace_user_setup_rt_frame(env, frame_addr); | |
| if (!lock_user_struct(VERIFY_WRITE, frame, frame_addr, 0)) { | |
| goto give_sigsegv; | |
| } | |
| if (ka->sa_flags & SA_SIGINFO) { | |
| tswap_siginfo(&frame->info, info); | |
| } | |
| __put_user(0, &frame->uc.tuc_flags); | |
| __put_user(0, &frame->uc.tuc_link); | |
| target_save_altstack(&frame->uc.tuc_stack, env); | |
| if (!setup_sigcontext(frame, env)) { | |
| unlock_user_struct(frame, frame_addr, 0); | |
| goto give_sigsegv; | |
| } | |
| for (i = 0; i < TARGET_NSIG_WORDS; ++i) { | |
| __put_user(set->sig[i], &frame->uc.tuc_sigmask.sig[i]); | |
| } | |
| if (ka->sa_flags & TARGET_SA_RESTORER) { | |
| ra = ka->sa_restorer; | |
| } else { | |
| /* Not used, but retain for ABI compatibility. */ | |
| install_sigtramp(frame->retcode); | |
| ra = default_rt_sigreturn; | |
| } | |
| memset(env->regs, 0, sizeof(env->regs)); | |
| env->pc = ka->_sa_handler; | |
| env->regs[1] = frame_addr; | |
| env->sregs[WINDOW_BASE] = 0; | |
| env->sregs[WINDOW_START] = 1; | |
| abi_call0 = (env->sregs[PS] & PS_WOE) == 0; | |
| env->sregs[PS] = PS_UM | (3 << PS_RING_SHIFT); | |
| if (abi_call0) { | |
| base = 0; | |
| env->regs[base] = ra; | |
| } else { | |
| env->sregs[PS] |= PS_WOE | (1 << PS_CALLINC_SHIFT); | |
| base = 4; | |
| env->regs[base] = (ra & 0x3fffffff) | 0x40000000; | |
| } | |
| env->regs[base + 2] = sig; | |
| env->regs[base + 3] = frame_addr + offsetof(struct target_rt_sigframe, | |
| info); | |
| env->regs[base + 4] = frame_addr + offsetof(struct target_rt_sigframe, uc); | |
| unlock_user_struct(frame, frame_addr, 1); | |
| return; | |
| give_sigsegv: | |
| force_sigsegv(sig); | |
| return; | |
| } | |
| static void restore_sigcontext(CPUXtensaState *env, | |
| struct target_rt_sigframe *frame) | |
| { | |
| struct target_sigcontext *sc = &frame->uc.tuc_mcontext; | |
| uint32_t ps; | |
| int i; | |
| __get_user(env->pc, &sc->sc_pc); | |
| __get_user(ps, &sc->sc_ps); | |
| __get_user(env->sregs[LBEG], &sc->sc_lbeg); | |
| __get_user(env->sregs[LEND], &sc->sc_lend); | |
| __get_user(env->sregs[LCOUNT], &sc->sc_lcount); | |
| env->sregs[WINDOW_BASE] = 0; | |
| env->sregs[WINDOW_START] = 1; | |
| env->sregs[PS] = deposit32(env->sregs[PS], | |
| PS_CALLINC_SHIFT, | |
| PS_CALLINC_LEN, | |
| extract32(ps, PS_CALLINC_SHIFT, | |
| PS_CALLINC_LEN)); | |
| for (i = 0; i < 16; ++i) { | |
| __get_user(env->regs[i], sc->sc_a + i); | |
| } | |
| /* TODO: xtregs */ | |
| } | |
| long do_rt_sigreturn(CPUXtensaState *env) | |
| { | |
| abi_ulong frame_addr = env->regs[1]; | |
| struct target_rt_sigframe *frame; | |
| sigset_t set; | |
| trace_user_do_rt_sigreturn(env, frame_addr); | |
| if (!lock_user_struct(VERIFY_READ, frame, frame_addr, 1)) { | |
| goto badframe; | |
| } | |
| target_to_host_sigset(&set, &frame->uc.tuc_sigmask); | |
| set_sigmask(&set); | |
| restore_sigcontext(env, frame); | |
| target_restore_altstack(&frame->uc.tuc_stack, env); | |
| unlock_user_struct(frame, frame_addr, 0); | |
| return -QEMU_ESIGRETURN; | |
| badframe: | |
| unlock_user_struct(frame, frame_addr, 0); | |
| force_sig(TARGET_SIGSEGV); | |
| return -QEMU_ESIGRETURN; | |
| } | |
| void setup_sigtramp(abi_ulong sigtramp_page) | |
| { | |
| uint8_t *tramp = lock_user(VERIFY_WRITE, sigtramp_page, 6, 0); | |
| assert(tramp != NULL); | |
| default_rt_sigreturn = sigtramp_page; | |
| install_sigtramp(tramp); | |
| unlock_user(tramp, sigtramp_page, 6); | |
| } |