LoongArch: Relay BCE exceptions to userland as SIGSEGV with si_code=SEGV_BNDERR
SEGV_BNDERR was introduced initially for supporting the Intel MPX, but
fell into disuse after the MPX support was removed. The LoongArch
bounds-checking instructions behave very differently than MPX, but
overall the interface is still kind of suitable for conveying the
information to userland when bounds-checking assertions trigger, so we
wouldn't have to invent more UAPI. Specifically, when the BCE triggers,
a SEGV_BNDERR is sent to userland, with si_addr set to the out-of-bounds
address or value (in asrt{gt,le}'s case), and one of si_lower or
si_upper set to the configured bound depending on the faulting
instruction. The other bound is set to either 0 or ULONG_MAX to resemble
a range with both lower and upper bounds.
Note that it is possible to have si_addr == si_lower in case of a
failing asrtgt or {ld,st}gt, because those instructions test for strict
greater-than relationship. This should not pose a problem for userland,
though, because the faulting PC is available for the application to
associate back to the exact instruction for figuring out the
expectation.
Example exception context generated by a faulting `asrtgt.d t0, t1`
(assert t0 > t1 or BCE) with t0=100 and t1=200:
> pc 00005555558206a4 ra 00007ffff2d854fc tp 00007ffff2f2f180 sp 00007ffffbf9fb80
> a0 0000000000000002 a1 00007ffffbf9fce8 a2 00007ffffbf9fd00 a3 00007ffff2ed4558
> a4 0000000000000000 a5 00007ffff2f044c8 a6 00007ffffbf9fce0 a7 fffffffffffff000
> t0 0000000000000064 t1 00000000000000c8 t2 00007ffffbfa2d5e t3 00007ffff2f12aa0
> t4 00007ffff2ed6158 t5 00007ffff2ed6158 t6 000000000000002e t7 0000000003d8f538
> t8 0000000000000005 u0 0000000000000000 s9 0000000000000000 s0 00007ffffbf9fce8
> s1 0000000000000002 s2 0000000000000000 s3 00007ffff2f2c038 s4 0000555555820610
> s5 00007ffff2ed5000 s6 0000555555827e38 s7 00007ffffbf9fd00 s8 0000555555827e38
> ra: 00007ffff2d854fc
> ERA: 00005555558206a4
> CRMD: 000000b0 (PLV0 -IE -DA +PG DACF=CC DACM=CC -WE)
> PRMD: 00000007 (PPLV3 +PIE -PWE)
> EUEN: 00000000 (-FPE -SXE -ASXE -BTE)
> ECFG: 0007181c (LIE=2-4,11-12 VS=7)
> ESTAT: 000a0000 [BCE] (IS= ECode=10 EsubCode=0)
> PRID: 0014c010 (Loongson-64bit, Loongson-3A5000)
Signed-off-by: WANG Xuerui <git@xen0n.name>
Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
This commit is contained in:
@@ -121,6 +121,8 @@ enum reg2bstrd_op {
|
||||
};
|
||||
|
||||
enum reg3_op {
|
||||
asrtle_op = 0x02,
|
||||
asrtgt_op = 0x03,
|
||||
addw_op = 0x20,
|
||||
addd_op = 0x21,
|
||||
subw_op = 0x22,
|
||||
@@ -176,6 +178,30 @@ enum reg3_op {
|
||||
amord_op = 0x70c7,
|
||||
amxorw_op = 0x70c8,
|
||||
amxord_op = 0x70c9,
|
||||
fldgts_op = 0x70e8,
|
||||
fldgtd_op = 0x70e9,
|
||||
fldles_op = 0x70ea,
|
||||
fldled_op = 0x70eb,
|
||||
fstgts_op = 0x70ec,
|
||||
fstgtd_op = 0x70ed,
|
||||
fstles_op = 0x70ee,
|
||||
fstled_op = 0x70ef,
|
||||
ldgtb_op = 0x70f0,
|
||||
ldgth_op = 0x70f1,
|
||||
ldgtw_op = 0x70f2,
|
||||
ldgtd_op = 0x70f3,
|
||||
ldleb_op = 0x70f4,
|
||||
ldleh_op = 0x70f5,
|
||||
ldlew_op = 0x70f6,
|
||||
ldled_op = 0x70f7,
|
||||
stgtb_op = 0x70f8,
|
||||
stgth_op = 0x70f9,
|
||||
stgtw_op = 0x70fa,
|
||||
stgtd_op = 0x70fb,
|
||||
stleb_op = 0x70fc,
|
||||
stleh_op = 0x70fd,
|
||||
stlew_op = 0x70fe,
|
||||
stled_op = 0x70ff,
|
||||
};
|
||||
|
||||
enum reg3sa2_op {
|
||||
|
||||
@@ -82,6 +82,7 @@ SYM_FUNC_END(except_vec_cex)
|
||||
|
||||
BUILD_HANDLER ade ade badv
|
||||
BUILD_HANDLER ale ale badv
|
||||
BUILD_HANDLER bce bce none
|
||||
BUILD_HANDLER bp bp none
|
||||
BUILD_HANDLER fpe fpe fcsr
|
||||
BUILD_HANDLER fpu fpu none
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include <asm/break.h>
|
||||
#include <asm/cpu.h>
|
||||
#include <asm/fpu.h>
|
||||
#include <asm/inst.h>
|
||||
#include <asm/loongarch.h>
|
||||
#include <asm/mmu_context.h>
|
||||
#include <asm/pgtable.h>
|
||||
@@ -51,6 +52,7 @@
|
||||
|
||||
extern asmlinkage void handle_ade(void);
|
||||
extern asmlinkage void handle_ale(void);
|
||||
extern asmlinkage void handle_bce(void);
|
||||
extern asmlinkage void handle_sys(void);
|
||||
extern asmlinkage void handle_bp(void);
|
||||
extern asmlinkage void handle_ri(void);
|
||||
@@ -588,6 +590,95 @@ static void bug_handler(struct pt_regs *regs)
|
||||
}
|
||||
}
|
||||
|
||||
asmlinkage void noinstr do_bce(struct pt_regs *regs)
|
||||
{
|
||||
bool user = user_mode(regs);
|
||||
unsigned long era = exception_era(regs);
|
||||
u64 badv = 0, lower = 0, upper = ULONG_MAX;
|
||||
union loongarch_instruction insn;
|
||||
irqentry_state_t state = irqentry_enter(regs);
|
||||
|
||||
if (regs->csr_prmd & CSR_PRMD_PIE)
|
||||
local_irq_enable();
|
||||
|
||||
current->thread.trap_nr = read_csr_excode();
|
||||
|
||||
die_if_kernel("Bounds check error in kernel code", regs);
|
||||
|
||||
/*
|
||||
* Pull out the address that failed bounds checking, and the lower /
|
||||
* upper bound, by minimally looking at the faulting instruction word
|
||||
* and reading from the correct register.
|
||||
*/
|
||||
if (__get_inst(&insn.word, (u32 *)era, user))
|
||||
goto bad_era;
|
||||
|
||||
switch (insn.reg3_format.opcode) {
|
||||
case asrtle_op:
|
||||
if (insn.reg3_format.rd != 0)
|
||||
break; /* not asrtle */
|
||||
badv = regs->regs[insn.reg3_format.rj];
|
||||
upper = regs->regs[insn.reg3_format.rk];
|
||||
break;
|
||||
|
||||
case asrtgt_op:
|
||||
if (insn.reg3_format.rd != 0)
|
||||
break; /* not asrtgt */
|
||||
badv = regs->regs[insn.reg3_format.rj];
|
||||
lower = regs->regs[insn.reg3_format.rk];
|
||||
break;
|
||||
|
||||
case ldleb_op:
|
||||
case ldleh_op:
|
||||
case ldlew_op:
|
||||
case ldled_op:
|
||||
case stleb_op:
|
||||
case stleh_op:
|
||||
case stlew_op:
|
||||
case stled_op:
|
||||
case fldles_op:
|
||||
case fldled_op:
|
||||
case fstles_op:
|
||||
case fstled_op:
|
||||
badv = regs->regs[insn.reg3_format.rj];
|
||||
upper = regs->regs[insn.reg3_format.rk];
|
||||
break;
|
||||
|
||||
case ldgtb_op:
|
||||
case ldgth_op:
|
||||
case ldgtw_op:
|
||||
case ldgtd_op:
|
||||
case stgtb_op:
|
||||
case stgth_op:
|
||||
case stgtw_op:
|
||||
case stgtd_op:
|
||||
case fldgts_op:
|
||||
case fldgtd_op:
|
||||
case fstgts_op:
|
||||
case fstgtd_op:
|
||||
badv = regs->regs[insn.reg3_format.rj];
|
||||
lower = regs->regs[insn.reg3_format.rk];
|
||||
break;
|
||||
}
|
||||
|
||||
force_sig_bnderr((void __user *)badv, (void __user *)lower, (void __user *)upper);
|
||||
|
||||
out:
|
||||
if (regs->csr_prmd & CSR_PRMD_PIE)
|
||||
local_irq_disable();
|
||||
|
||||
irqentry_exit(regs, state);
|
||||
return;
|
||||
|
||||
bad_era:
|
||||
/*
|
||||
* Cannot pull out the instruction word, hence cannot provide more
|
||||
* info than a regular SIGSEGV in this case.
|
||||
*/
|
||||
force_sig(SIGSEGV);
|
||||
goto out;
|
||||
}
|
||||
|
||||
asmlinkage void noinstr do_bp(struct pt_regs *regs)
|
||||
{
|
||||
bool user = user_mode(regs);
|
||||
@@ -955,6 +1046,7 @@ void __init trap_init(void)
|
||||
|
||||
set_handler(EXCCODE_ADE * VECSIZE, handle_ade, VECSIZE);
|
||||
set_handler(EXCCODE_ALE * VECSIZE, handle_ale, VECSIZE);
|
||||
set_handler(EXCCODE_BCE * VECSIZE, handle_bce, VECSIZE);
|
||||
set_handler(EXCCODE_SYS * VECSIZE, handle_sys, VECSIZE);
|
||||
set_handler(EXCCODE_BP * VECSIZE, handle_bp, VECSIZE);
|
||||
set_handler(EXCCODE_INE * VECSIZE, handle_ri, VECSIZE);
|
||||
|
||||
Reference in New Issue
Block a user