mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-11-01 09:13:37 +00:00
The Breaking-Event-Address-Register (BEAR) stores the address of the last breaking event instruction. Breaking events are usually instructions that change the program flow - for example branches, and instructions that modify the address in the PSW like lpswe. This is useful for debugging wild branches, because one could easily figure out where the wild branch was originating from. What is problematic is that lpswe is considered a breaking event, and therefore overwrites BEAR on kernel exit. The BEAR enhancement facility adds new instructions that allow to save/restore BEAR and also an lpswey instruction that doesn't cause a breaking event. So we can save BEAR on kernel entry and restore it on exit to user space. Signed-off-by: Sven Schnelle <svens@linux.ibm.com> Reviewed-by: Heiko Carstens <hca@linux.ibm.com> Signed-off-by: Vasily Gorbik <gor@linux.ibm.com>
409 lines
11 KiB
C
409 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* S390 version
|
|
* Copyright IBM Corp. 1999, 2000
|
|
* Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com),
|
|
* Denis Joseph Barrow (djbarrow@de.ibm.com,barrow_dj@yahoo.com),
|
|
*
|
|
* Derived from "arch/i386/kernel/traps.c"
|
|
* Copyright (C) 1991, 1992 Linus Torvalds
|
|
*/
|
|
|
|
/*
|
|
* 'Traps.c' handles hardware traps and faults after we have saved some
|
|
* state in 'asm.s'.
|
|
*/
|
|
#include "asm/irqflags.h"
|
|
#include "asm/ptrace.h"
|
|
#include <linux/kprobes.h>
|
|
#include <linux/kdebug.h>
|
|
#include <linux/randomize_kstack.h>
|
|
#include <linux/extable.h>
|
|
#include <linux/ptrace.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/sched/debug.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/entry-common.h>
|
|
#include <asm/fpu/api.h>
|
|
#include <asm/vtime.h>
|
|
#include "entry.h"
|
|
|
|
static inline void __user *get_trap_ip(struct pt_regs *regs)
|
|
{
|
|
unsigned long address;
|
|
|
|
if (regs->int_code & 0x200)
|
|
address = current->thread.trap_tdb.data[3];
|
|
else
|
|
address = regs->psw.addr;
|
|
return (void __user *) (address - (regs->int_code >> 16));
|
|
}
|
|
|
|
int is_valid_bugaddr(unsigned long addr)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
void do_report_trap(struct pt_regs *regs, int si_signo, int si_code, char *str)
|
|
{
|
|
if (user_mode(regs)) {
|
|
force_sig_fault(si_signo, si_code, get_trap_ip(regs));
|
|
report_user_fault(regs, si_signo, 0);
|
|
} else {
|
|
const struct exception_table_entry *fixup;
|
|
fixup = s390_search_extables(regs->psw.addr);
|
|
if (!fixup || !ex_handle(fixup, regs))
|
|
die(regs, str);
|
|
}
|
|
}
|
|
|
|
static void do_trap(struct pt_regs *regs, int si_signo, int si_code, char *str)
|
|
{
|
|
if (notify_die(DIE_TRAP, str, regs, 0,
|
|
regs->int_code, si_signo) == NOTIFY_STOP)
|
|
return;
|
|
do_report_trap(regs, si_signo, si_code, str);
|
|
}
|
|
NOKPROBE_SYMBOL(do_trap);
|
|
|
|
void do_per_trap(struct pt_regs *regs)
|
|
{
|
|
if (notify_die(DIE_SSTEP, "sstep", regs, 0, 0, SIGTRAP) == NOTIFY_STOP)
|
|
return;
|
|
if (!current->ptrace)
|
|
return;
|
|
force_sig_fault(SIGTRAP, TRAP_HWBKPT,
|
|
(void __force __user *) current->thread.per_event.address);
|
|
}
|
|
NOKPROBE_SYMBOL(do_per_trap);
|
|
|
|
static void default_trap_handler(struct pt_regs *regs)
|
|
{
|
|
if (user_mode(regs)) {
|
|
report_user_fault(regs, SIGSEGV, 0);
|
|
do_exit(SIGSEGV);
|
|
} else
|
|
die(regs, "Unknown program exception");
|
|
}
|
|
|
|
#define DO_ERROR_INFO(name, signr, sicode, str) \
|
|
static void name(struct pt_regs *regs) \
|
|
{ \
|
|
do_trap(regs, signr, sicode, str); \
|
|
}
|
|
|
|
DO_ERROR_INFO(addressing_exception, SIGILL, ILL_ILLADR,
|
|
"addressing exception")
|
|
DO_ERROR_INFO(execute_exception, SIGILL, ILL_ILLOPN,
|
|
"execute exception")
|
|
DO_ERROR_INFO(divide_exception, SIGFPE, FPE_INTDIV,
|
|
"fixpoint divide exception")
|
|
DO_ERROR_INFO(overflow_exception, SIGFPE, FPE_INTOVF,
|
|
"fixpoint overflow exception")
|
|
DO_ERROR_INFO(hfp_overflow_exception, SIGFPE, FPE_FLTOVF,
|
|
"HFP overflow exception")
|
|
DO_ERROR_INFO(hfp_underflow_exception, SIGFPE, FPE_FLTUND,
|
|
"HFP underflow exception")
|
|
DO_ERROR_INFO(hfp_significance_exception, SIGFPE, FPE_FLTRES,
|
|
"HFP significance exception")
|
|
DO_ERROR_INFO(hfp_divide_exception, SIGFPE, FPE_FLTDIV,
|
|
"HFP divide exception")
|
|
DO_ERROR_INFO(hfp_sqrt_exception, SIGFPE, FPE_FLTINV,
|
|
"HFP square root exception")
|
|
DO_ERROR_INFO(operand_exception, SIGILL, ILL_ILLOPN,
|
|
"operand exception")
|
|
DO_ERROR_INFO(privileged_op, SIGILL, ILL_PRVOPC,
|
|
"privileged operation")
|
|
DO_ERROR_INFO(special_op_exception, SIGILL, ILL_ILLOPN,
|
|
"special operation exception")
|
|
DO_ERROR_INFO(transaction_exception, SIGILL, ILL_ILLOPN,
|
|
"transaction constraint exception")
|
|
|
|
static inline void do_fp_trap(struct pt_regs *regs, __u32 fpc)
|
|
{
|
|
int si_code = 0;
|
|
/* FPC[2] is Data Exception Code */
|
|
if ((fpc & 0x00000300) == 0) {
|
|
/* bits 6 and 7 of DXC are 0 iff IEEE exception */
|
|
if (fpc & 0x8000) /* invalid fp operation */
|
|
si_code = FPE_FLTINV;
|
|
else if (fpc & 0x4000) /* div by 0 */
|
|
si_code = FPE_FLTDIV;
|
|
else if (fpc & 0x2000) /* overflow */
|
|
si_code = FPE_FLTOVF;
|
|
else if (fpc & 0x1000) /* underflow */
|
|
si_code = FPE_FLTUND;
|
|
else if (fpc & 0x0800) /* inexact */
|
|
si_code = FPE_FLTRES;
|
|
}
|
|
do_trap(regs, SIGFPE, si_code, "floating point exception");
|
|
}
|
|
|
|
static void translation_exception(struct pt_regs *regs)
|
|
{
|
|
/* May never happen. */
|
|
panic("Translation exception");
|
|
}
|
|
|
|
static void illegal_op(struct pt_regs *regs)
|
|
{
|
|
__u8 opcode[6];
|
|
__u16 __user *location;
|
|
int is_uprobe_insn = 0;
|
|
int signal = 0;
|
|
|
|
location = get_trap_ip(regs);
|
|
|
|
if (user_mode(regs)) {
|
|
if (get_user(*((__u16 *) opcode), (__u16 __user *) location))
|
|
return;
|
|
if (*((__u16 *) opcode) == S390_BREAKPOINT_U16) {
|
|
if (current->ptrace)
|
|
force_sig_fault(SIGTRAP, TRAP_BRKPT, location);
|
|
else
|
|
signal = SIGILL;
|
|
#ifdef CONFIG_UPROBES
|
|
} else if (*((__u16 *) opcode) == UPROBE_SWBP_INSN) {
|
|
is_uprobe_insn = 1;
|
|
#endif
|
|
} else
|
|
signal = SIGILL;
|
|
}
|
|
/*
|
|
* We got either an illegal op in kernel mode, or user space trapped
|
|
* on a uprobes illegal instruction. See if kprobes or uprobes picks
|
|
* it up. If not, SIGILL.
|
|
*/
|
|
if (is_uprobe_insn || !user_mode(regs)) {
|
|
if (notify_die(DIE_BPT, "bpt", regs, 0,
|
|
3, SIGTRAP) != NOTIFY_STOP)
|
|
signal = SIGILL;
|
|
}
|
|
if (signal)
|
|
do_trap(regs, signal, ILL_ILLOPC, "illegal operation");
|
|
}
|
|
NOKPROBE_SYMBOL(illegal_op);
|
|
|
|
DO_ERROR_INFO(specification_exception, SIGILL, ILL_ILLOPN,
|
|
"specification exception");
|
|
|
|
static void vector_exception(struct pt_regs *regs)
|
|
{
|
|
int si_code, vic;
|
|
|
|
if (!MACHINE_HAS_VX) {
|
|
do_trap(regs, SIGILL, ILL_ILLOPN, "illegal operation");
|
|
return;
|
|
}
|
|
|
|
/* get vector interrupt code from fpc */
|
|
save_fpu_regs();
|
|
vic = (current->thread.fpu.fpc & 0xf00) >> 8;
|
|
switch (vic) {
|
|
case 1: /* invalid vector operation */
|
|
si_code = FPE_FLTINV;
|
|
break;
|
|
case 2: /* division by zero */
|
|
si_code = FPE_FLTDIV;
|
|
break;
|
|
case 3: /* overflow */
|
|
si_code = FPE_FLTOVF;
|
|
break;
|
|
case 4: /* underflow */
|
|
si_code = FPE_FLTUND;
|
|
break;
|
|
case 5: /* inexact */
|
|
si_code = FPE_FLTRES;
|
|
break;
|
|
default: /* unknown cause */
|
|
si_code = 0;
|
|
}
|
|
do_trap(regs, SIGFPE, si_code, "vector exception");
|
|
}
|
|
|
|
static void data_exception(struct pt_regs *regs)
|
|
{
|
|
save_fpu_regs();
|
|
if (current->thread.fpu.fpc & FPC_DXC_MASK)
|
|
do_fp_trap(regs, current->thread.fpu.fpc);
|
|
else
|
|
do_trap(regs, SIGILL, ILL_ILLOPN, "data exception");
|
|
}
|
|
|
|
static void space_switch_exception(struct pt_regs *regs)
|
|
{
|
|
/* Set user psw back to home space mode. */
|
|
if (user_mode(regs))
|
|
regs->psw.mask |= PSW_ASC_HOME;
|
|
/* Send SIGILL. */
|
|
do_trap(regs, SIGILL, ILL_PRVOPC, "space switch event");
|
|
}
|
|
|
|
static void monitor_event_exception(struct pt_regs *regs)
|
|
{
|
|
const struct exception_table_entry *fixup;
|
|
|
|
if (user_mode(regs))
|
|
return;
|
|
|
|
switch (report_bug(regs->psw.addr - (regs->int_code >> 16), regs)) {
|
|
case BUG_TRAP_TYPE_NONE:
|
|
fixup = s390_search_extables(regs->psw.addr);
|
|
if (fixup)
|
|
ex_handle(fixup, regs);
|
|
break;
|
|
case BUG_TRAP_TYPE_WARN:
|
|
break;
|
|
case BUG_TRAP_TYPE_BUG:
|
|
die(regs, "monitor event");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void kernel_stack_overflow(struct pt_regs *regs)
|
|
{
|
|
bust_spinlocks(1);
|
|
printk("Kernel stack overflow.\n");
|
|
show_regs(regs);
|
|
bust_spinlocks(0);
|
|
panic("Corrupt kernel stack, can't continue.");
|
|
}
|
|
NOKPROBE_SYMBOL(kernel_stack_overflow);
|
|
|
|
static void __init test_monitor_call(void)
|
|
{
|
|
int val = 1;
|
|
|
|
if (!IS_ENABLED(CONFIG_BUG))
|
|
return;
|
|
asm volatile(
|
|
" mc 0,0\n"
|
|
"0: xgr %0,%0\n"
|
|
"1:\n"
|
|
EX_TABLE(0b,1b)
|
|
: "+d" (val));
|
|
if (!val)
|
|
panic("Monitor call doesn't work!\n");
|
|
}
|
|
|
|
void __init trap_init(void)
|
|
{
|
|
sort_extable(__start_amode31_ex_table, __stop_amode31_ex_table);
|
|
local_mcck_enable();
|
|
test_monitor_call();
|
|
}
|
|
|
|
static void (*pgm_check_table[128])(struct pt_regs *regs);
|
|
|
|
void noinstr __do_pgm_check(struct pt_regs *regs)
|
|
{
|
|
unsigned int trapnr;
|
|
irqentry_state_t state;
|
|
|
|
regs->int_code = *(u32 *)&S390_lowcore.pgm_ilc;
|
|
regs->int_parm_long = S390_lowcore.trans_exc_code;
|
|
|
|
state = irqentry_enter(regs);
|
|
|
|
if (user_mode(regs)) {
|
|
update_timer_sys();
|
|
if (!static_branch_likely(&cpu_has_bear)) {
|
|
if (regs->last_break < 4096)
|
|
regs->last_break = 1;
|
|
}
|
|
current->thread.last_break = regs->last_break;
|
|
}
|
|
|
|
if (S390_lowcore.pgm_code & 0x0200) {
|
|
/* transaction abort */
|
|
current->thread.trap_tdb = S390_lowcore.pgm_tdb;
|
|
}
|
|
|
|
if (S390_lowcore.pgm_code & PGM_INT_CODE_PER) {
|
|
if (user_mode(regs)) {
|
|
struct per_event *ev = ¤t->thread.per_event;
|
|
|
|
set_thread_flag(TIF_PER_TRAP);
|
|
ev->address = S390_lowcore.per_address;
|
|
ev->cause = *(u16 *)&S390_lowcore.per_code;
|
|
ev->paid = S390_lowcore.per_access_id;
|
|
} else {
|
|
/* PER event in kernel is kprobes */
|
|
__arch_local_irq_ssm(regs->psw.mask & ~PSW_MASK_PER);
|
|
do_per_trap(regs);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (!irqs_disabled_flags(regs->psw.mask))
|
|
trace_hardirqs_on();
|
|
__arch_local_irq_ssm(regs->psw.mask & ~PSW_MASK_PER);
|
|
|
|
trapnr = regs->int_code & PGM_INT_CODE_MASK;
|
|
if (trapnr)
|
|
pgm_check_table[trapnr](regs);
|
|
out:
|
|
local_irq_disable();
|
|
irqentry_exit(regs, state);
|
|
}
|
|
|
|
/*
|
|
* The program check table contains exactly 128 (0x00-0x7f) entries. Each
|
|
* line defines the function to be called corresponding to the program check
|
|
* interruption code.
|
|
*/
|
|
static void (*pgm_check_table[128])(struct pt_regs *regs) = {
|
|
[0x00] = default_trap_handler,
|
|
[0x01] = illegal_op,
|
|
[0x02] = privileged_op,
|
|
[0x03] = execute_exception,
|
|
[0x04] = do_protection_exception,
|
|
[0x05] = addressing_exception,
|
|
[0x06] = specification_exception,
|
|
[0x07] = data_exception,
|
|
[0x08] = overflow_exception,
|
|
[0x09] = divide_exception,
|
|
[0x0a] = overflow_exception,
|
|
[0x0b] = divide_exception,
|
|
[0x0c] = hfp_overflow_exception,
|
|
[0x0d] = hfp_underflow_exception,
|
|
[0x0e] = hfp_significance_exception,
|
|
[0x0f] = hfp_divide_exception,
|
|
[0x10] = do_dat_exception,
|
|
[0x11] = do_dat_exception,
|
|
[0x12] = translation_exception,
|
|
[0x13] = special_op_exception,
|
|
[0x14] = default_trap_handler,
|
|
[0x15] = operand_exception,
|
|
[0x16] = default_trap_handler,
|
|
[0x17] = default_trap_handler,
|
|
[0x18] = transaction_exception,
|
|
[0x19] = default_trap_handler,
|
|
[0x1a] = default_trap_handler,
|
|
[0x1b] = vector_exception,
|
|
[0x1c] = space_switch_exception,
|
|
[0x1d] = hfp_sqrt_exception,
|
|
[0x1e ... 0x37] = default_trap_handler,
|
|
[0x38] = do_dat_exception,
|
|
[0x39] = do_dat_exception,
|
|
[0x3a] = do_dat_exception,
|
|
[0x3b] = do_dat_exception,
|
|
[0x3c] = default_trap_handler,
|
|
[0x3d] = do_secure_storage_access,
|
|
[0x3e] = do_non_secure_storage_access,
|
|
[0x3f] = do_secure_storage_violation,
|
|
[0x40] = monitor_event_exception,
|
|
[0x41 ... 0x7f] = default_trap_handler,
|
|
};
|
|
|
|
#define COND_TRAP(x) asm( \
|
|
".weak " __stringify(x) "\n\t" \
|
|
".set " __stringify(x) "," \
|
|
__stringify(default_trap_handler))
|
|
|
|
COND_TRAP(do_secure_storage_access);
|
|
COND_TRAP(do_non_secure_storage_access);
|
|
COND_TRAP(do_secure_storage_violation);
|