From e4553573b37c3f72533683cb5f3a1ad300b18d37 Mon Sep 17 00:00:00 2001 From: "Maciej W. Rozycki" Date: Fri, 22 Jan 2016 05:20:26 +0000 Subject: [PATCH] MIPS: math-emu: Correctly handle NOP emulation Fix an issue introduced with commit 9ab4471c9f1b ("MIPS: math-emu: Correct delay-slot exception propagation") where the emulation of a NOP instruction signals the need to terminate the emulation loop. This in turn, if the PC has not changed from the entry to the loop, will cause the kernel to terminate the program with SIGILL. Consider this program: static double div(double d) { do d /= 2.0; while (d > .5); return d; } int main(int argc, char **argv) { return div(argc); } which gets compiled to the following binary code: 00400490
: 400490: 44840000 mtc1 a0,$f0 400494: 3c020040 lui v0,0x40 400498: d44207f8 ldc1 $f2,2040(v0) 40049c: 46800021 cvt.d.w $f0,$f0 4004a0: 46220002 mul.d $f0,$f0,$f2 4004a4: 4620103c c.lt.d $f2,$f0 4004a8: 4501fffd bc1t 4004a0 4004ac: 00000000 nop 4004b0: 4620000d trunc.w.d $f0,$f0 4004b4: 03e00008 jr ra 4004b8: 44020000 mfc1 v0,$f0 4004bc: 00000000 nop Where the FPU emulator is used, depending on the number of command-line arguments this code will either run to completion or terminate with SIGILL. If no arguments are specified, then BC1T will not be taken, NOP will not be emulated and code will complete successfully. If one argument is specified, then BC1T will be taken once and NOP will be emulated. At this point the entry PC value will be 0x400498 and the new PC value, set by `mips_dsemul' will be 0x4004a0, the target of BC1T. The emulation loop will terminate, but SIGILL will not be issued, because the PC has changed. The FPU emulator will be entered again and on the second execution BC1T will not be taken, NOP will not be emulated and code will complete successfully. If two or more arguments are specified, then the first execution of BC1T will proceed as above. Upon reentering the FPU emulator the emulation loop will continue to BC1T, at which point the branch will be taken and NOP emulated again. At this point however the entry PC value will be 0x4004a0, the same as the target of BC1T. This will make the emulator conclude that execution has not advanced and therefore an unsupported FPU instruction has been encountered, and SIGILL will be sent to the process. Fix the problem by extending the internal API of `mips_dsemul', making it return -1 if no delay slot emulation frame has been made, the instruction has been handled and execution of the emulation loop needs to continue as if nothing happened. Remove code from `mips_dsemul' to reproduce steps made by the emulation loop at the conclusion of each iteration, as those will be reached normally now. Adjust call sites accordingly. Document the API. Signed-off-by: Maciej W. Rozycki Cc: Aurelien Jarno Cc: linux-mips@linux-mips.org Patchwork: https://patchwork.linux-mips.org/patch/12172/ Signed-off-by: Ralf Baechle --- arch/mips/math-emu/cp1emu.c | 4 ++++ arch/mips/math-emu/dsemul.c | 14 ++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/arch/mips/math-emu/cp1emu.c b/arch/mips/math-emu/cp1emu.c index 32f0e19a0d7f..cdfd44ffa51c 100644 --- a/arch/mips/math-emu/cp1emu.c +++ b/arch/mips/math-emu/cp1emu.c @@ -1266,6 +1266,8 @@ branch_common: */ sig = mips_dsemul(xcp, ir, contpc); + if (sig < 0) + break; if (sig) xcp->cp0_epc = bcpc; /* @@ -1319,6 +1321,8 @@ branch_common: * instruction in the dslot */ sig = mips_dsemul(xcp, ir, contpc); + if (sig < 0) + break; if (sig) xcp->cp0_epc = bcpc; /* SIGILL forces out of the emulation loop. */ diff --git a/arch/mips/math-emu/dsemul.c b/arch/mips/math-emu/dsemul.c index cbb36c14b155..70e4824c64dc 100644 --- a/arch/mips/math-emu/dsemul.c +++ b/arch/mips/math-emu/dsemul.c @@ -31,18 +31,20 @@ struct emuframe { unsigned long epc; }; +/* + * Set up an emulation frame for instruction IR, from a delay slot of + * a branch jumping to CPC. Return 0 if successful, -1 if no emulation + * required, otherwise a signal number causing a frame setup failure. + */ int mips_dsemul(struct pt_regs *regs, mips_instruction ir, unsigned long cpc) { struct emuframe __user *fr; int err; + /* NOP is easy */ if ((get_isa16_mode(regs->cp0_epc) && ((ir >> 16) == MM_NOP16)) || - (ir == 0)) { - /* NOP is easy */ - regs->cp0_epc = cpc; - clear_delay_slot(regs); - return 0; - } + (ir == 0)) + return -1; pr_debug("dsemul %lx %lx\n", regs->cp0_epc, cpc); -- 2.20.1