mirror of
https://github.com/torvalds/linux.git
synced 2026-03-08 04:04:43 +01:00
x86_64/bug: Implement __WARN_printf()
The basic idea is to have __WARN_printf() be a vararg function such
that the compiler can do the optimal calling convention for us. This
function body will be a #UD and then set up a va_list in the exception
from pt_regs.
But because the trap will be in a called function, the bug_entry must
be passed in. Have that be the first argument, with the format tucked
away inside the bug_entry.
The comments should clarify the real fun details.
The big downside is that all WARNs will now show:
RIP: 0010:__WARN_trap:+0
One possible solution is to simply discard the top frame when
unwinding. A follow up patch takes care of this slightly differently
by abusing the x86 static_call implementation.
This changes (with the next patches):
WARN_ONCE(preempt_count() != 2*PREEMPT_DISABLE_OFFSET,
"corrupted preempt_count: %s/%d/0x%x\n",
from:
cmpl $2, %ecx #, _7
jne .L1472
...
.L1472:
cmpb $0, __already_done.11(%rip)
je .L1513
...
.L1513
movb $1, __already_done.11(%rip)
movl 1424(%r14), %edx # _15->pid, _15->pid
leaq 1912(%r14), %rsi #, _17
movq $.LC43, %rdi #,
call __warn_printk #
ud2
.pushsection __bug_table,"aw"
2:
.long 1b - . # bug_entry::bug_addr
.long .LC1 - . # bug_entry::file
.word 5093 # bug_entry::line
.word 2313 # bug_entry::flags
.org 2b + 12
.popsection
.pushsection .discard.annotate_insn,"M", @progbits, 8
.long 1b - .
.long 8 # ANNOTYPE_REACHABLE
.popsection
into:
cmpl $2, %ecx #, _7
jne .L1442 #,
...
.L1442:
lea (2f)(%rip), %rdi
1:
.pushsection __bug_table,"aw"
2:
.long 1b - . # bug_entry::bug_addr
.long .LC43 - . # bug_entry::format
.long .LC1 - . # bug_entry::file
.word 5093 # bug_entry::line
.word 2323 # bug_entry::flags
.org 2b + 16
.popsection
movl 1424(%r14), %edx # _19->pid, _19->pid
leaq 1912(%r14), %rsi #, _13
ud1 (%edx), %rdi
Notably, by pushing everything into the exception handler it can take
care of the ONCE thing.
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://patch.msgid.link/20251110115758.213813530@infradead.org
This commit is contained in:
parent
4f1b701f24
commit
5b472b6e5b
3 changed files with 171 additions and 16 deletions
|
|
@ -32,6 +32,14 @@ SYM_FUNC_END(write_ibpb)
|
||||||
/* For KVM */
|
/* For KVM */
|
||||||
EXPORT_SYMBOL_GPL(write_ibpb);
|
EXPORT_SYMBOL_GPL(write_ibpb);
|
||||||
|
|
||||||
|
SYM_FUNC_START(__WARN_trap)
|
||||||
|
ANNOTATE_NOENDBR
|
||||||
|
ANNOTATE_REACHABLE
|
||||||
|
ud1 (%edx), %_ASM_ARG1
|
||||||
|
RET
|
||||||
|
SYM_FUNC_END(__WARN_trap)
|
||||||
|
EXPORT_SYMBOL(__WARN_trap)
|
||||||
|
|
||||||
.popsection
|
.popsection
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,11 @@
|
||||||
#include <linux/objtool.h>
|
#include <linux/objtool.h>
|
||||||
#include <asm/asm.h>
|
#include <asm/asm.h>
|
||||||
|
|
||||||
|
#ifndef __ASSEMBLY__
|
||||||
|
struct bug_entry;
|
||||||
|
extern void __WARN_trap(struct bug_entry *bug, ...);
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Despite that some emulators terminate on UD2, we use it for WARN().
|
* Despite that some emulators terminate on UD2, we use it for WARN().
|
||||||
*/
|
*/
|
||||||
|
|
@ -31,6 +36,7 @@
|
||||||
#define BUG_UD2 0xfffe
|
#define BUG_UD2 0xfffe
|
||||||
#define BUG_UD1 0xfffd
|
#define BUG_UD1 0xfffd
|
||||||
#define BUG_UD1_UBSAN 0xfffc
|
#define BUG_UD1_UBSAN 0xfffc
|
||||||
|
#define BUG_UD1_WARN 0xfffb
|
||||||
#define BUG_UDB 0xffd6
|
#define BUG_UDB 0xffd6
|
||||||
#define BUG_LOCK 0xfff0
|
#define BUG_LOCK 0xfff0
|
||||||
|
|
||||||
|
|
@ -58,14 +64,17 @@
|
||||||
#define __BUG_ENTRY_FORMAT(format)
|
#define __BUG_ENTRY_FORMAT(format)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_X86_64
|
||||||
|
#define HAVE_ARCH_BUG_FORMAT_ARGS
|
||||||
|
#endif
|
||||||
|
|
||||||
#define __BUG_ENTRY(format, file, line, flags) \
|
#define __BUG_ENTRY(format, file, line, flags) \
|
||||||
__BUG_REL("1b") "\t# bug_entry::bug_addr\n" \
|
__BUG_REL("1b") "\t# bug_entry::bug_addr\n" \
|
||||||
__BUG_ENTRY_FORMAT(format) \
|
__BUG_ENTRY_FORMAT(format) \
|
||||||
__BUG_ENTRY_VERBOSE(file, line) \
|
__BUG_ENTRY_VERBOSE(file, line) \
|
||||||
"\t.word " flags "\t# bug_entry::flags\n"
|
"\t.word " flags "\t# bug_entry::flags\n"
|
||||||
|
|
||||||
#define _BUG_FLAGS_ASM(ins, format, file, line, flags, size, extra) \
|
#define _BUG_FLAGS_ASM(format, file, line, flags, size, extra) \
|
||||||
"1:\t" ins "\n" \
|
|
||||||
".pushsection __bug_table,\"aw\"\n\t" \
|
".pushsection __bug_table,\"aw\"\n\t" \
|
||||||
ANNOTATE_DATA_SPECIAL \
|
ANNOTATE_DATA_SPECIAL \
|
||||||
"2:\n\t" \
|
"2:\n\t" \
|
||||||
|
|
@ -82,7 +91,8 @@
|
||||||
|
|
||||||
#define _BUG_FLAGS(cond_str, ins, flags, extra) \
|
#define _BUG_FLAGS(cond_str, ins, flags, extra) \
|
||||||
do { \
|
do { \
|
||||||
asm_inline volatile(_BUG_FLAGS_ASM(ins, "%c[fmt]", "%c[file]", \
|
asm_inline volatile("1:\t" ins "\n" \
|
||||||
|
_BUG_FLAGS_ASM("%c[fmt]", "%c[file]", \
|
||||||
"%c[line]", "%c[fl]", \
|
"%c[line]", "%c[fl]", \
|
||||||
"%c[size]", extra) \
|
"%c[size]", extra) \
|
||||||
: : [fmt] "i" (WARN_CONDITION_STR(cond_str)), \
|
: : [fmt] "i" (WARN_CONDITION_STR(cond_str)), \
|
||||||
|
|
@ -93,7 +103,8 @@ do { \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
#define ARCH_WARN_ASM(file, line, flags, size) \
|
#define ARCH_WARN_ASM(file, line, flags, size) \
|
||||||
_BUG_FLAGS_ASM(ASM_UD2, "0", file, line, flags, size, "")
|
"1:\t " ASM_UD2 "\n" \
|
||||||
|
_BUG_FLAGS_ASM("0", file, line, flags, size, "")
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
|
|
@ -126,6 +137,49 @@ do { \
|
||||||
instrumentation_end(); \
|
instrumentation_end(); \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
|
#ifdef HAVE_ARCH_BUG_FORMAT_ARGS
|
||||||
|
|
||||||
|
#ifndef __ASSEMBLY__
|
||||||
|
struct pt_regs;
|
||||||
|
struct sysv_va_list { /* from AMD64 System V ABI */
|
||||||
|
unsigned int gp_offset;
|
||||||
|
unsigned int fp_offset;
|
||||||
|
void *overflow_arg_area;
|
||||||
|
void *reg_save_area;
|
||||||
|
};
|
||||||
|
struct arch_va_list {
|
||||||
|
unsigned long regs[6];
|
||||||
|
struct sysv_va_list args;
|
||||||
|
};
|
||||||
|
extern void *__warn_args(struct arch_va_list *args, struct pt_regs *regs);
|
||||||
|
#endif /* __ASSEMBLY__ */
|
||||||
|
|
||||||
|
#define __WARN_bug_entry(flags, format) ({ \
|
||||||
|
struct bug_entry *bug; \
|
||||||
|
asm_inline volatile("lea (2f)(%%rip), %[addr]\n1:\n" \
|
||||||
|
_BUG_FLAGS_ASM("%c[fmt]", "%c[file]", \
|
||||||
|
"%c[line]", "%c[fl]", \
|
||||||
|
"%c[size]", "") \
|
||||||
|
: [addr] "=r" (bug) \
|
||||||
|
: [fmt] "i" (format), \
|
||||||
|
[file] "i" (__FILE__), \
|
||||||
|
[line] "i" (__LINE__), \
|
||||||
|
[fl] "i" (flags), \
|
||||||
|
[size] "i" (sizeof(struct bug_entry))); \
|
||||||
|
bug; })
|
||||||
|
|
||||||
|
#define __WARN_print_arg(flags, format, arg...) \
|
||||||
|
do { \
|
||||||
|
int __flags = (flags) | BUGFLAG_WARNING | BUGFLAG_ARGS ; \
|
||||||
|
__WARN_trap(__WARN_bug_entry(__flags, format), ## arg); \
|
||||||
|
asm (""); /* inhibit tail-call optimization */ \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define __WARN_printf(taint, fmt, arg...) \
|
||||||
|
__WARN_print_arg(BUGFLAG_TAINT(taint), fmt, ## arg)
|
||||||
|
|
||||||
|
#endif /* HAVE_ARCH_BUG_FORMAT_ARGS */
|
||||||
|
|
||||||
#include <asm-generic/bug.h>
|
#include <asm-generic/bug.h>
|
||||||
|
|
||||||
#endif /* _ASM_X86_BUG_H */
|
#endif /* _ASM_X86_BUG_H */
|
||||||
|
|
|
||||||
|
|
@ -102,25 +102,37 @@ __always_inline int is_valid_bugaddr(unsigned long addr)
|
||||||
* UBSan{0}: 67 0f b9 00 ud1 (%eax),%eax
|
* UBSan{0}: 67 0f b9 00 ud1 (%eax),%eax
|
||||||
* UBSan{10}: 67 0f b9 40 10 ud1 0x10(%eax),%eax
|
* UBSan{10}: 67 0f b9 40 10 ud1 0x10(%eax),%eax
|
||||||
* static_call: 0f b9 cc ud1 %esp,%ecx
|
* static_call: 0f b9 cc ud1 %esp,%ecx
|
||||||
|
* __WARN_trap: 67 48 0f b9 3a ud1 (%edx),%reg
|
||||||
*
|
*
|
||||||
* Notably UBSAN uses EAX, static_call uses ECX.
|
* Notable, since __WARN_trap can use all registers, the distinction between
|
||||||
|
* UD1 users is through R/M.
|
||||||
*/
|
*/
|
||||||
__always_inline int decode_bug(unsigned long addr, s32 *imm, int *len)
|
__always_inline int decode_bug(unsigned long addr, s32 *imm, int *len)
|
||||||
{
|
{
|
||||||
unsigned long start = addr;
|
unsigned long start = addr;
|
||||||
|
u8 v, reg, rm, rex = 0;
|
||||||
|
int type = BUG_UD1;
|
||||||
bool lock = false;
|
bool lock = false;
|
||||||
u8 v;
|
|
||||||
|
|
||||||
if (addr < TASK_SIZE_MAX)
|
if (addr < TASK_SIZE_MAX)
|
||||||
return BUG_NONE;
|
return BUG_NONE;
|
||||||
|
|
||||||
v = *(u8 *)(addr++);
|
for (;;) {
|
||||||
if (v == INSN_ASOP)
|
|
||||||
v = *(u8 *)(addr++);
|
v = *(u8 *)(addr++);
|
||||||
|
if (v == INSN_ASOP)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (v == INSN_LOCK) {
|
if (v == INSN_LOCK) {
|
||||||
lock = true;
|
lock = true;
|
||||||
v = *(u8 *)(addr++);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((v & 0xf0) == 0x40) {
|
||||||
|
rex = v;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (v) {
|
switch (v) {
|
||||||
|
|
@ -156,18 +168,33 @@ __always_inline int decode_bug(unsigned long addr, s32 *imm, int *len)
|
||||||
if (X86_MODRM_MOD(v) != 3 && X86_MODRM_RM(v) == 4)
|
if (X86_MODRM_MOD(v) != 3 && X86_MODRM_RM(v) == 4)
|
||||||
addr++; /* SIB */
|
addr++; /* SIB */
|
||||||
|
|
||||||
|
reg = X86_MODRM_REG(v) + 8*!!X86_REX_R(rex);
|
||||||
|
rm = X86_MODRM_RM(v) + 8*!!X86_REX_B(rex);
|
||||||
|
|
||||||
/* Decode immediate, if present */
|
/* Decode immediate, if present */
|
||||||
switch (X86_MODRM_MOD(v)) {
|
switch (X86_MODRM_MOD(v)) {
|
||||||
case 0: if (X86_MODRM_RM(v) == 5)
|
case 0: if (X86_MODRM_RM(v) == 5)
|
||||||
addr += 4; /* RIP + disp32 */
|
addr += 4; /* RIP + disp32 */
|
||||||
|
|
||||||
|
if (rm == 0) /* (%eax) */
|
||||||
|
type = BUG_UD1_UBSAN;
|
||||||
|
|
||||||
|
if (rm == 2) { /* (%edx) */
|
||||||
|
*imm = reg;
|
||||||
|
type = BUG_UD1_WARN;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 1: *imm = *(s8 *)addr;
|
case 1: *imm = *(s8 *)addr;
|
||||||
addr += 1;
|
addr += 1;
|
||||||
|
if (rm == 0) /* (%eax) */
|
||||||
|
type = BUG_UD1_UBSAN;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2: *imm = *(s32 *)addr;
|
case 2: *imm = *(s32 *)addr;
|
||||||
addr += 4;
|
addr += 4;
|
||||||
|
if (rm == 0) /* (%eax) */
|
||||||
|
type = BUG_UD1_UBSAN;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 3: break;
|
case 3: break;
|
||||||
|
|
@ -176,12 +203,73 @@ __always_inline int decode_bug(unsigned long addr, s32 *imm, int *len)
|
||||||
/* record instruction length */
|
/* record instruction length */
|
||||||
*len = addr - start;
|
*len = addr - start;
|
||||||
|
|
||||||
if (X86_MODRM_REG(v) == 0) /* EAX */
|
return type;
|
||||||
return BUG_UD1_UBSAN;
|
|
||||||
|
|
||||||
return BUG_UD1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline unsigned long pt_regs_val(struct pt_regs *regs, int nr)
|
||||||
|
{
|
||||||
|
int offset = pt_regs_offset(regs, nr);
|
||||||
|
if (WARN_ON_ONCE(offset < -0))
|
||||||
|
return 0;
|
||||||
|
return *((unsigned long *)((void *)regs + offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_ARCH_BUG_FORMAT_ARGS
|
||||||
|
/*
|
||||||
|
* Create a va_list from an exception context.
|
||||||
|
*/
|
||||||
|
void *__warn_args(struct arch_va_list *args, struct pt_regs *regs)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Register save area; populate with function call argument registers
|
||||||
|
*/
|
||||||
|
args->regs[0] = regs->di;
|
||||||
|
args->regs[1] = regs->si;
|
||||||
|
args->regs[2] = regs->dx;
|
||||||
|
args->regs[3] = regs->cx;
|
||||||
|
args->regs[4] = regs->r8;
|
||||||
|
args->regs[5] = regs->r9;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* From the ABI document:
|
||||||
|
*
|
||||||
|
* @gp_offset - the element holds the offset in bytes from
|
||||||
|
* reg_save_area to the place where the next available general purpose
|
||||||
|
* argument register is saved. In case all argument registers have
|
||||||
|
* been exhausted, it is set to the value 48 (6*8).
|
||||||
|
*
|
||||||
|
* @fp_offset - the element holds the offset in bytes from
|
||||||
|
* reg_save_area to the place where the next available floating point
|
||||||
|
* argument is saved. In case all argument registers have been
|
||||||
|
* exhausted, it is set to the value 176 (6*8 + 8*16)
|
||||||
|
*
|
||||||
|
* @overflow_arg_area - this pointer is used to fetch arguments passed
|
||||||
|
* on the stack. It is initialized with the address of the first
|
||||||
|
* argument passed on the stack, if any, and then always updated to
|
||||||
|
* point to the start of the next argument on the stack.
|
||||||
|
*
|
||||||
|
* @reg_save_area - the element points to the start of the register
|
||||||
|
* save area.
|
||||||
|
*
|
||||||
|
* Notably the vararg starts with the second argument and there are no
|
||||||
|
* floating point arguments in the kernel.
|
||||||
|
*/
|
||||||
|
args->args.gp_offset = 1*8;
|
||||||
|
args->args.fp_offset = 6*8 + 8*16;
|
||||||
|
args->args.reg_save_area = &args->regs;
|
||||||
|
args->args.overflow_arg_area = (void *)regs->sp;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the exception came from __WARN_trap, there is a return
|
||||||
|
* address on the stack, skip that. This is why any __WARN_trap()
|
||||||
|
* caller must inhibit tail-call optimization.
|
||||||
|
*/
|
||||||
|
if ((void *)regs->ip == &__WARN_trap)
|
||||||
|
args->args.overflow_arg_area += 8;
|
||||||
|
|
||||||
|
return &args->args;
|
||||||
|
}
|
||||||
|
#endif /* HAVE_ARCH_BUG_FORMAT */
|
||||||
|
|
||||||
static nokprobe_inline int
|
static nokprobe_inline int
|
||||||
do_trap_no_signal(struct task_struct *tsk, int trapnr, const char *str,
|
do_trap_no_signal(struct task_struct *tsk, int trapnr, const char *str,
|
||||||
|
|
@ -334,6 +422,11 @@ static noinstr bool handle_bug(struct pt_regs *regs)
|
||||||
raw_local_irq_enable();
|
raw_local_irq_enable();
|
||||||
|
|
||||||
switch (ud_type) {
|
switch (ud_type) {
|
||||||
|
case BUG_UD1_WARN:
|
||||||
|
if (report_bug_entry((void *)pt_regs_val(regs, ud_imm), regs) == BUG_TRAP_TYPE_WARN)
|
||||||
|
handled = true;
|
||||||
|
break;
|
||||||
|
|
||||||
case BUG_UD2:
|
case BUG_UD2:
|
||||||
if (report_bug(regs->ip, regs) == BUG_TRAP_TYPE_WARN) {
|
if (report_bug(regs->ip, regs) == BUG_TRAP_TYPE_WARN) {
|
||||||
handled = true;
|
handled = true;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue