Core kernel bug handling infrastructure changes for v6.19:

- Improve WARN(), which has vararg printf like arguments,
     to work with the x86 #UD based WARN-optimizing infrastructure
     by hiding the format in the bug_table and replacing this
     first argument with the address of the bug-table entry,
     while making the actual function that's called a UD1 instruction.
     (Peter Zijlstra)
 
   - Introduce the CONFIG_DEBUG_BUGVERBOSE_DETAILED Kconfig switch
     (Ingo Molnar, s390 support by Heiko Carstens)
 
 Fixes and cleanups:
 
   - bugs/s390: Remove private WARN_ON() implementation (Heiko Carstens)
 
   - <asm/bugs.h>: Make i386 use GENERIC_BUG_RELATIVE_POINTERS
     (Peter Zijlstra)
 
 Signed-off-by: Ingo Molnar <mingo@kernel.org>
 -----BEGIN PGP SIGNATURE-----
 
 iQJFBAABCgAvFiEEBpT5eoXrXCwVQwEKEnMQ0APhK1gFAmktffcRHG1pbmdvQGtl
 cm5lbC5vcmcACgkQEnMQ0APhK1gLCQ//ZMHTpv6UF+xY4Kq3rdVFJWRR7Prs2hDn
 4V/cFHu7Xb2lRwpJDBNWnbiEkLbGR5qlWD+0CpAMK4mGuL9VjnoeflEzXxOPP9W1
 7SWw1HDc6NkjHM3BNnvkPQbzWF4RL4ZGs4Lbb1nv7pjoTSdBMXNrD5RL+iNmfHHd
 QfOG9ZiRyD5A/b07bfyjIXNaeC/Hot+FeVXTMfD7/vCfc2ywhL2Sm5G/igY/shTY
 Una7q8sbFDD/bFFtWSR2eFQeHQQfT6c/Pu39ZcAIdTlLk1FtZ+A2wcRtwYv/FdPV
 6KDOAxZK7fLMHoCdNTswsg+LbazhABOb/V1J7TaHq2tF/PeyN+B+ucxVY2KFcxQJ
 V5eG5crMrUIL22QO/UaT3dPWxGbJYkNlAWl416tAKdgXA52W4OsPd+k4DGjeP569
 KogAy3SY0D/80v1QN2HcFEJMvr7W2SukxtErqdtA5OKt/ZevB49lGqZoBecqASDO
 QjI1K0yLKnb+erbMIpCpNj+o/Fr1JQgWbYVpwipL2GON4an6vrowimGTsUG7qqxN
 Hwb7IaTNnWQ/4iyCkVV44q6Ln1gyP2hz5Lnzo7QJINUdzp98UjJLAEK4NRG44T4L
 p0t5NMxnvREpAv35rwy03xhmITYeQMOqzN9JEBBvegQyld5ghbThxI7g9BDzcXyL
 Bd0mF7WOV9M=
 =bMUL
 -----END PGP SIGNATURE-----

Merge tag 'core-bugs-2025-12-01' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull bug handling infrastructure updates from Ingo Molnar:
 "Core updates:

   - Improve WARN(), which has vararg printf like arguments, to work
     with the x86 #UD based WARN-optimizing infrastructure by hiding the
     format in the bug_table and replacing this first argument with the
     address of the bug-table entry, while making the actual function
     that's called a UD1 instruction (Peter Zijlstra)

   - Introduce the CONFIG_DEBUG_BUGVERBOSE_DETAILED Kconfig switch (Ingo
     Molnar, s390 support by Heiko Carstens)

  Fixes and cleanups:

   - bugs/s390: Remove private WARN_ON() implementation (Heiko Carstens)

   - <asm/bugs.h>: Make i386 use GENERIC_BUG_RELATIVE_POINTERS (Peter
     Zijlstra)"

* tag 'core-bugs-2025-12-01' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (31 commits)
  x86/bugs: Make i386 use GENERIC_BUG_RELATIVE_POINTERS
  x86/bug: Fix BUG_FORMAT vs KASLR
  x86_64/bug: Inline the UD1
  x86/bug: Implement WARN_ONCE()
  x86_64/bug: Implement __WARN_printf()
  x86/bug: Use BUG_FORMAT for DEBUG_BUGVERBOSE_DETAILED
  x86/bug: Add BUG_FORMAT basics
  bug: Allow architectures to provide __WARN_printf()
  bug: Implement WARN_ON() using __WARN_FLAGS()
  bug: Add report_bug_entry()
  bug: Add BUG_FORMAT_ARGS infrastructure
  bug: Clean up CONFIG_GENERIC_BUG_RELATIVE_POINTERS
  bug: Add BUG_FORMAT infrastructure
  x86: Rework __bug_table helpers
  bugs/s390: Remove private WARN_ON() implementation
  bugs/core: Reorganize fields in the first line of WARNING output, add ->comm[] output
  bugs/sh: Concatenate 'cond_str' with '__FILE__' in __WARN_FLAGS(), to extend WARN_ON/BUG_ON output
  bugs/parisc: Concatenate 'cond_str' with '__FILE__' in __WARN_FLAGS(), to extend WARN_ON/BUG_ON output
  bugs/riscv: Concatenate 'cond_str' with '__FILE__' in __BUG_FLAGS(), to extend WARN_ON/BUG_ON output
  bugs/riscv: Pass in 'cond_str' to __BUG_FLAGS()
  ...
This commit is contained in:
Linus Torvalds 2025-12-01 21:33:01 -08:00
commit 4a26e7032d
17 changed files with 480 additions and 181 deletions

View File

@ -19,7 +19,7 @@
unreachable(); \
} while (0)
#define __WARN_FLAGS(flags) __BUG_FLAGS(BUGFLAG_WARNING|(flags))
#define __WARN_FLAGS(cond_str, flags) __BUG_FLAGS(BUGFLAG_WARNING|(flags))
#define HAVE_ARCH_BUG

View File

@ -11,7 +11,7 @@
#else
#define __BUGVERBOSE_LOCATION(file, line) \
.pushsection .rodata.str, "aMS", @progbits, 1; \
10002: .string file; \
10002: .ascii file "\0"; \
.popsection; \
\
.long 10002b - .; \
@ -20,39 +20,38 @@
#endif
#ifndef CONFIG_GENERIC_BUG
#define __BUG_ENTRY(flags)
#define __BUG_ENTRY(cond_str, flags)
#else
#define __BUG_ENTRY(flags) \
#define __BUG_ENTRY(cond_str, flags) \
.pushsection __bug_table, "aw"; \
.align 2; \
10000: .long 10001f - .; \
_BUGVERBOSE_LOCATION(__FILE__, __LINE__) \
.short flags; \
_BUGVERBOSE_LOCATION(WARN_CONDITION_STR(cond_str) __FILE__, __LINE__) \
.short flags; \
.popsection; \
10001:
#endif
#define ASM_BUG_FLAGS(flags) \
__BUG_ENTRY(flags) \
#define ASM_BUG_FLAGS(cond_str, flags) \
__BUG_ENTRY(cond_str, flags) \
break BRK_BUG;
#define ASM_BUG() ASM_BUG_FLAGS(0)
#define ASM_BUG() ASM_BUG_FLAGS("", 0)
#define __BUG_FLAGS(flags, extra) \
asm_inline volatile (__stringify(ASM_BUG_FLAGS(flags)) \
extra);
#define __BUG_FLAGS(cond_str, flags, extra) \
asm_inline volatile (__stringify(ASM_BUG_FLAGS(cond_str, flags)) extra);
#define __WARN_FLAGS(flags) \
#define __WARN_FLAGS(cond_str, flags) \
do { \
instrumentation_begin(); \
__BUG_FLAGS(BUGFLAG_WARNING|(flags), ANNOTATE_REACHABLE(10001b));\
__BUG_FLAGS(cond_str, BUGFLAG_WARNING|(flags), ANNOTATE_REACHABLE(10001b));\
instrumentation_end(); \
} while (0)
#define BUG() \
do { \
instrumentation_begin(); \
__BUG_FLAGS(0, ""); \
__BUG_FLAGS("", 0, ""); \
unreachable(); \
} while (0)

View File

@ -50,7 +50,7 @@
#endif
#ifdef CONFIG_DEBUG_BUGVERBOSE
#define __WARN_FLAGS(flags) \
#define __WARN_FLAGS(cond_str, flags) \
do { \
asm volatile("\n" \
"1:\t" PARISC_BUG_BREAK_ASM "\n" \
@ -61,12 +61,12 @@
"\t.short %1, %2\n" \
"\t.blockz %3-2*4-2*2\n" \
"\t.popsection" \
: : "i" (__FILE__), "i" (__LINE__), \
: : "i" (WARN_CONDITION_STR(cond_str) __FILE__), "i" (__LINE__), \
"i" (BUGFLAG_WARNING|(flags)), \
"i" (sizeof(struct bug_entry)) ); \
} while(0)
#else
#define __WARN_FLAGS(flags) \
#define __WARN_FLAGS(cond_str, flags) \
do { \
asm volatile("\n" \
"1:\t" PARISC_BUG_BREAK_ASM "\n" \

View File

@ -51,11 +51,11 @@
".previous\n"
#endif
#define BUG_ENTRY(insn, flags, ...) \
#define BUG_ENTRY(cond_str, insn, flags, ...) \
__asm__ __volatile__( \
"1: " insn "\n" \
_EMIT_BUG_ENTRY \
: : "i" (__FILE__), "i" (__LINE__), \
: : "i" (WARN_CONDITION_STR(cond_str) __FILE__), "i" (__LINE__), \
"i" (flags), \
"i" (sizeof(struct bug_entry)), \
##__VA_ARGS__)
@ -67,12 +67,12 @@
*/
#define BUG() do { \
BUG_ENTRY("twi 31, 0, 0", 0); \
BUG_ENTRY("", "twi 31, 0, 0", 0); \
unreachable(); \
} while (0)
#define HAVE_ARCH_BUG
#define __WARN_FLAGS(flags) BUG_ENTRY("twi 31, 0, 0", BUGFLAG_WARNING | (flags))
#define __WARN_FLAGS(cond_str, flags) BUG_ENTRY(cond_str, "twi 31, 0, 0", BUGFLAG_WARNING | (flags))
#ifdef CONFIG_PPC64
#define BUG_ON(x) do { \
@ -80,7 +80,7 @@
if (x) \
BUG(); \
} else { \
BUG_ENTRY(PPC_TLNEI " %4, 0", 0, "r" ((__force long)(x))); \
BUG_ENTRY(#x, PPC_TLNEI " %4, 0", 0, "r" ((__force long)(x))); \
} \
} while (0)
@ -90,7 +90,7 @@
if (__ret_warn_on) \
__WARN(); \
} else { \
BUG_ENTRY(PPC_TLNEI " %4, 0", \
BUG_ENTRY(#x, PPC_TLNEI " %4, 0", \
BUGFLAG_WARNING | BUGFLAG_TAINT(TAINT_WARN), \
"r" (__ret_warn_on)); \
} \

View File

@ -60,28 +60,28 @@ typedef u32 bug_insn_t;
".org 2b + " size "\n\t" \
".popsection" \
#define __BUG_FLAGS(flags) \
#define __BUG_FLAGS(cond_str, flags) \
do { \
__asm__ __volatile__ ( \
ARCH_WARN_ASM("%0", "%1", "%2", "%3") \
: \
: "i" (__FILE__), "i" (__LINE__), \
: "i" (WARN_CONDITION_STR(cond_str) __FILE__), "i" (__LINE__), \
"i" (flags), \
"i" (sizeof(struct bug_entry))); \
} while (0)
#else /* CONFIG_GENERIC_BUG */
#define __BUG_FLAGS(flags) do { \
#define __BUG_FLAGS(cond_str, flags) do { \
__asm__ __volatile__ ("ebreak\n"); \
} while (0)
#endif /* CONFIG_GENERIC_BUG */
#define BUG() do { \
__BUG_FLAGS(0); \
__BUG_FLAGS("", 0); \
unreachable(); \
} while (0)
#define __WARN_FLAGS(flags) __BUG_FLAGS(BUGFLAG_WARNING|(flags))
#define __WARN_FLAGS(cond_str, flags) __BUG_FLAGS(cond_str, BUGFLAG_WARNING|(flags))
#define ARCH_WARN_REACHABLE

View File

@ -2,69 +2,55 @@
#ifndef _ASM_S390_BUG_H
#define _ASM_S390_BUG_H
#include <linux/compiler.h>
#include <linux/stringify.h>
#ifdef CONFIG_BUG
#ifndef CONFIG_DEBUG_BUGVERBOSE
#define _BUGVERBOSE_LOCATION(file, line)
#else
#define __BUGVERBOSE_LOCATION(file, line) \
.pushsection .rodata.str, "aMS", @progbits, 1; \
10002: .ascii file "\0"; \
.popsection; \
\
.long 10002b - .; \
.short line;
#define _BUGVERBOSE_LOCATION(file, line) __BUGVERBOSE_LOCATION(file, line)
#endif
#ifdef CONFIG_DEBUG_BUGVERBOSE
#ifndef CONFIG_GENERIC_BUG
#define __BUG_ENTRY(cond_str, flags)
#else
#define __BUG_ENTRY(cond_str, flags) \
.pushsection __bug_table, "aw"; \
.align 4; \
10000: .long 10001f - .; \
_BUGVERBOSE_LOCATION(WARN_CONDITION_STR(cond_str) __FILE__, __LINE__) \
.short flags; \
.popsection; \
10001:
#endif
#define __EMIT_BUG(x) do { \
asm_inline volatile( \
"0: mc 0,0\n" \
".section .rodata.str,\"aMS\",@progbits,1\n" \
"1: .asciz \""__FILE__"\"\n" \
".previous\n" \
".section __bug_table,\"aw\"\n" \
"2: .long 0b-.\n" \
" .long 1b-.\n" \
" .short %0,%1\n" \
" .org 2b+%2\n" \
".previous\n" \
: : "i" (__LINE__), \
"i" (x), \
"i" (sizeof(struct bug_entry))); \
#define ASM_BUG_FLAGS(cond_str, flags) \
__BUG_ENTRY(cond_str, flags) \
mc 0,0
#define ASM_BUG() ASM_BUG_FLAGS("", 0)
#define __BUG_FLAGS(cond_str, flags) \
asm_inline volatile(__stringify(ASM_BUG_FLAGS(cond_str, flags)));
#define __WARN_FLAGS(cond_str, flags) \
do { \
__BUG_FLAGS(cond_str, BUGFLAG_WARNING|(flags)); \
} while (0)
#else /* CONFIG_DEBUG_BUGVERBOSE */
#define __EMIT_BUG(x) do { \
asm_inline volatile( \
"0: mc 0,0\n" \
".section __bug_table,\"aw\"\n" \
"1: .long 0b-.\n" \
" .short %0\n" \
" .org 1b+%1\n" \
".previous\n" \
: : "i" (x), \
"i" (sizeof(struct bug_entry))); \
#define BUG() \
do { \
__BUG_FLAGS("", 0); \
unreachable(); \
} while (0)
#endif /* CONFIG_DEBUG_BUGVERBOSE */
#define BUG() do { \
__EMIT_BUG(0); \
unreachable(); \
} while (0)
#define __WARN_FLAGS(flags) do { \
__EMIT_BUG(BUGFLAG_WARNING|(flags)); \
} while (0)
#define WARN_ON(x) ({ \
int __ret_warn_on = !!(x); \
if (__builtin_constant_p(__ret_warn_on)) { \
if (__ret_warn_on) \
__WARN(); \
} else { \
if (unlikely(__ret_warn_on)) \
__WARN(); \
} \
unlikely(__ret_warn_on); \
})
#define HAVE_ARCH_BUG
#define HAVE_ARCH_WARN_ON
#endif /* CONFIG_BUG */
#include <asm-generic/bug.h>

View File

@ -52,14 +52,14 @@ do { \
unreachable(); \
} while (0)
#define __WARN_FLAGS(flags) \
#define __WARN_FLAGS(cond_str, flags) \
do { \
__asm__ __volatile__ ( \
"1:\t.short %O0\n" \
_EMIT_BUG_ENTRY \
: \
: "n" (TRAPA_BUG_OPCODE), \
"i" (__FILE__), \
"i" (WARN_CONDITION_STR(cond_str) __FILE__), \
"i" (__LINE__), \
"i" (BUGFLAG_WARNING|(flags)), \
"i" (sizeof(struct bug_entry))); \

View File

@ -381,7 +381,7 @@ config GENERIC_CSUM
config GENERIC_BUG
def_bool y
depends on BUG
select GENERIC_BUG_RELATIVE_POINTERS if X86_64
select GENERIC_BUG_RELATIVE_POINTERS
config GENERIC_BUG_RELATIVE_POINTERS
bool

View File

@ -32,6 +32,14 @@ SYM_FUNC_END(write_ibpb)
/* For KVM */
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
/*

View File

@ -7,6 +7,11 @@
#include <linux/objtool.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().
*/
@ -31,53 +36,77 @@
#define BUG_UD2 0xfffe
#define BUG_UD1 0xfffd
#define BUG_UD1_UBSAN 0xfffc
#define BUG_UD1_WARN 0xfffb
#define BUG_UDB 0xffd6
#define BUG_LOCK 0xfff0
#ifdef CONFIG_GENERIC_BUG
#ifdef CONFIG_X86_32
# define __BUG_REL(val) ".long " val
#else
# define __BUG_REL(val) ".long " val " - ."
#endif
#ifdef CONFIG_DEBUG_BUGVERBOSE
#define __BUG_ENTRY(file, line, flags) \
"2:\t" __BUG_REL("1b") "\t# bug_entry::bug_addr\n" \
"\t" __BUG_REL(file) "\t# bug_entry::file\n" \
"\t.word " line "\t# bug_entry::line\n" \
"\t.word " flags "\t# bug_entry::flags\n"
#define __BUG_ENTRY_VERBOSE(file, line) \
"\t.long " file " - .\t# bug_entry::file\n" \
"\t.word " line "\t# bug_entry::line\n"
#else
#define __BUG_ENTRY(file, line, flags) \
"2:\t" __BUG_REL("1b") "\t# bug_entry::bug_addr\n" \
"\t.word " flags "\t# bug_entry::flags\n"
#define __BUG_ENTRY_VERBOSE(file, line)
#endif
#define _BUG_FLAGS_ASM(ins, file, line, flags, size, extra) \
"1:\t" ins "\n" \
".pushsection __bug_table,\"aw\"\n" \
#if defined(CONFIG_X86_64) || defined(CONFIG_DEBUG_BUGVERBOSE_DETAILED)
#define HAVE_ARCH_BUG_FORMAT
#define __BUG_ENTRY_FORMAT(format) \
"\t.long " format " - .\t# bug_entry::format\n"
#else
#define __BUG_ENTRY_FORMAT(format)
#endif
#ifdef CONFIG_X86_64
#define HAVE_ARCH_BUG_FORMAT_ARGS
#endif
#define __BUG_ENTRY(format, file, line, flags) \
"\t.long 1b - ." "\t# bug_entry::bug_addr\n" \
__BUG_ENTRY_FORMAT(format) \
__BUG_ENTRY_VERBOSE(file, line) \
"\t.word " flags "\t# bug_entry::flags\n"
#define _BUG_FLAGS_ASM(format, file, line, flags, size, extra) \
".pushsection __bug_table,\"aw\"\n\t" \
ANNOTATE_DATA_SPECIAL \
__BUG_ENTRY(file, line, flags) \
"2:\n\t" \
__BUG_ENTRY(format, file, line, flags) \
"\t.org 2b + " size "\n" \
".popsection\n" \
extra
#define _BUG_FLAGS(ins, flags, extra) \
#ifdef CONFIG_DEBUG_BUGVERBOSE_DETAILED
#define WARN_CONDITION_STR(cond_str) cond_str
#else
#define WARN_CONDITION_STR(cond_str) ""
#endif
#define _BUG_FLAGS(cond_str, ins, flags, extra) \
do { \
asm_inline volatile(_BUG_FLAGS_ASM(ins, "%c0", \
"%c1", "%c2", "%c3", extra) \
: : "i" (__FILE__), "i" (__LINE__), \
"i" (flags), \
"i" (sizeof(struct bug_entry))); \
asm_inline volatile("1:\t" ins "\n" \
_BUG_FLAGS_ASM("%c[fmt]", "%c[file]", \
"%c[line]", "%c[fl]", \
"%c[size]", extra) \
: : [fmt] "i" (WARN_CONDITION_STR(cond_str)), \
[file] "i" (__FILE__), \
[line] "i" (__LINE__), \
[fl] "i" (flags), \
[size] "i" (sizeof(struct bug_entry))); \
} while (0)
#define ARCH_WARN_ASM(file, line, flags, size) \
_BUG_FLAGS_ASM(ASM_UD2, file, line, flags, size, "")
".pushsection .rodata.str1.1, \"aMS\", @progbits, 1\n" \
"99:\n" \
"\t.string \"\"\n" \
".popsection\n" \
"1:\t " ASM_UD2 "\n" \
_BUG_FLAGS_ASM("99b", file, line, flags, size, "")
#else
#define _BUG_FLAGS(ins, flags, extra) asm volatile(ins)
#define _BUG_FLAGS(cond_str, ins, flags, extra) asm volatile(ins)
#endif /* CONFIG_GENERIC_BUG */
@ -85,7 +114,7 @@ do { \
#define BUG() \
do { \
instrumentation_begin(); \
_BUG_FLAGS(ASM_UD2, 0, ""); \
_BUG_FLAGS("", ASM_UD2, 0, ""); \
__builtin_unreachable(); \
} while (0)
@ -98,14 +127,69 @@ do { \
#define ARCH_WARN_REACHABLE ANNOTATE_REACHABLE(1b)
#define __WARN_FLAGS(flags) \
do { \
__auto_type __flags = BUGFLAG_WARNING|(flags); \
instrumentation_begin(); \
_BUG_FLAGS(ASM_UD2, __flags, ARCH_WARN_REACHABLE); \
instrumentation_end(); \
#define __WARN_FLAGS(cond_str, flags) \
do { \
__auto_type __flags = BUGFLAG_WARNING|(flags); \
instrumentation_begin(); \
_BUG_FLAGS(cond_str, ASM_UD2, __flags, ARCH_WARN_REACHABLE); \
instrumentation_end(); \
} while (0)
#ifdef HAVE_ARCH_BUG_FORMAT_ARGS
#ifndef __ASSEMBLY__
#include <linux/static_call_types.h>
DECLARE_STATIC_CALL(WARN_trap, __WARN_trap);
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 ; \
static_call_mod(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)
#define WARN_ONCE(cond, format, arg...) ({ \
int __ret_warn_on = !!(cond); \
if (unlikely(__ret_warn_on)) { \
__WARN_print_arg(BUGFLAG_ONCE|BUGFLAG_TAINT(TAINT_WARN),\
format, ## arg); \
} \
__ret_warn_on; \
})
#endif /* HAVE_ARCH_BUG_FORMAT_ARGS */
#include <asm-generic/bug.h>
#endif /* _ASM_X86_BUG_H */

View File

@ -26,6 +26,11 @@ static const u8 xor5rax[] = { 0x2e, 0x2e, 0x2e, 0x31, 0xc0 };
static const u8 retinsn[] = { RET_INSN_OPCODE, 0xcc, 0xcc, 0xcc, 0xcc };
/*
* ud1 (%edx),%rdi -- see __WARN_trap() / decode_bug()
*/
static const u8 warninsn[] = { 0x67, 0x48, 0x0f, 0xb9, 0x3a };
static u8 __is_Jcc(u8 *insn) /* Jcc.d32 */
{
u8 ret = 0;
@ -69,7 +74,10 @@ static void __ref __static_call_transform(void *insn, enum insn_type type,
emulate = code;
code = &xor5rax;
}
if (func == &__WARN_trap) {
emulate = code;
code = &warninsn;
}
break;
case NOP:
@ -128,7 +136,8 @@ static void __static_call_validate(u8 *insn, bool tail, bool tramp)
} else {
if (opcode == CALL_INSN_OPCODE ||
!memcmp(insn, x86_nops[5], 5) ||
!memcmp(insn, xor5rax, 5))
!memcmp(insn, xor5rax, 5) ||
!memcmp(insn, warninsn, 5))
return;
}

View File

@ -31,6 +31,7 @@
#include <linux/kexec.h>
#include <linux/sched.h>
#include <linux/sched/task_stack.h>
#include <linux/static_call.h>
#include <linux/timer.h>
#include <linux/init.h>
#include <linux/bug.h>
@ -102,25 +103,37 @@ __always_inline int is_valid_bugaddr(unsigned long addr)
* UBSan{0}: 67 0f b9 00 ud1 (%eax),%eax
* UBSan{10}: 67 0f b9 40 10 ud1 0x10(%eax),%eax
* 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)
{
unsigned long start = addr;
u8 v, reg, rm, rex = 0;
int type = BUG_UD1;
bool lock = false;
u8 v;
if (addr < TASK_SIZE_MAX)
return BUG_NONE;
v = *(u8 *)(addr++);
if (v == INSN_ASOP)
for (;;) {
v = *(u8 *)(addr++);
if (v == INSN_ASOP)
continue;
if (v == INSN_LOCK) {
lock = true;
v = *(u8 *)(addr++);
if (v == INSN_LOCK) {
lock = true;
continue;
}
if ((v & 0xf0) == 0x40) {
rex = v;
continue;
}
break;
}
switch (v) {
@ -156,18 +169,33 @@ __always_inline int decode_bug(unsigned long addr, s32 *imm, int *len)
if (X86_MODRM_MOD(v) != 3 && X86_MODRM_RM(v) == 4)
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 */
switch (X86_MODRM_MOD(v)) {
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;
case 1: *imm = *(s8 *)addr;
addr += 1;
if (rm == 0) /* (%eax) */
type = BUG_UD1_UBSAN;
break;
case 2: *imm = *(s32 *)addr;
addr += 4;
if (rm == 0) /* (%eax) */
type = BUG_UD1_UBSAN;
break;
case 3: break;
@ -176,12 +204,76 @@ __always_inline int decode_bug(unsigned long addr, s32 *imm, int *len)
/* record instruction length */
*len = addr - start;
if (X86_MODRM_REG(v) == 0) /* EAX */
return BUG_UD1_UBSAN;
return BUG_UD1;
return type;
}
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
DEFINE_STATIC_CALL(WARN_trap, __WARN_trap);
EXPORT_STATIC_CALL_TRAMP(WARN_trap);
/*
* 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
do_trap_no_signal(struct task_struct *tsk, int trapnr, const char *str,
@ -334,6 +426,11 @@ static noinstr bool handle_bug(struct pt_regs *regs)
raw_local_irq_enable();
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:
if (report_bug(regs->ip, regs) == BUG_TRAP_TYPE_WARN) {
handled = true;

View File

@ -13,10 +13,19 @@
#define BUGFLAG_ONCE (1 << 1)
#define BUGFLAG_DONE (1 << 2)
#define BUGFLAG_NO_CUT_HERE (1 << 3) /* CUT_HERE already sent */
#define BUGFLAG_ARGS (1 << 4)
#define BUGFLAG_TAINT(taint) ((taint) << 8)
#define BUG_GET_TAINT(bug) ((bug)->flags >> 8)
#endif
#ifndef WARN_CONDITION_STR
#ifdef CONFIG_DEBUG_BUGVERBOSE_DETAILED
# define WARN_CONDITION_STR(cond_str) "[" cond_str "] "
#else
# define WARN_CONDITION_STR(cond_str)
#endif
#endif /* WARN_CONDITION_STR */
#ifndef __ASSEMBLY__
#include <linux/panic.h>
#include <linux/printk.h>
@ -29,19 +38,20 @@ void __warn(const char *file, int line, void *caller, unsigned taint,
#ifdef CONFIG_BUG
#ifndef CONFIG_GENERIC_BUG_RELATIVE_POINTERS
#define BUG_REL(type, name) type name
#else
#define BUG_REL(type, name) signed int name##_disp
#endif
#ifdef CONFIG_GENERIC_BUG
struct bug_entry {
#ifndef CONFIG_GENERIC_BUG_RELATIVE_POINTERS
unsigned long bug_addr;
#else
signed int bug_addr_disp;
BUG_REL(unsigned long, bug_addr);
#ifdef HAVE_ARCH_BUG_FORMAT
BUG_REL(const char *, format);
#endif
#ifdef CONFIG_DEBUG_BUGVERBOSE
#ifndef CONFIG_GENERIC_BUG_RELATIVE_POINTERS
const char *file;
#else
signed int file_disp;
#endif
BUG_REL(const char *, file);
unsigned short line;
#endif
unsigned short flags;
@ -92,28 +102,50 @@ void warn_slowpath_fmt(const char *file, const int line, unsigned taint,
const char *fmt, ...);
extern __printf(1, 2) void __warn_printk(const char *fmt, ...);
#ifndef __WARN_FLAGS
#define __WARN() __WARN_printf(TAINT_WARN, NULL)
#ifdef __WARN_FLAGS
#define __WARN() __WARN_FLAGS("", BUGFLAG_TAINT(TAINT_WARN))
#ifndef WARN_ON
#define WARN_ON(condition) ({ \
int __ret_warn_on = !!(condition); \
if (unlikely(__ret_warn_on)) \
__WARN_FLAGS(#condition, \
BUGFLAG_TAINT(TAINT_WARN)); \
unlikely(__ret_warn_on); \
})
#endif
#ifndef WARN_ON_ONCE
#define WARN_ON_ONCE(condition) ({ \
int __ret_warn_on = !!(condition); \
if (unlikely(__ret_warn_on)) \
__WARN_FLAGS(#condition, \
BUGFLAG_ONCE | \
BUGFLAG_TAINT(TAINT_WARN)); \
unlikely(__ret_warn_on); \
})
#endif
#endif /* __WARN_FLAGS */
#if defined(__WARN_FLAGS) && !defined(__WARN_printf)
#define __WARN_printf(taint, arg...) do { \
instrumentation_begin(); \
__warn_printk(arg); \
__WARN_FLAGS("", BUGFLAG_NO_CUT_HERE | BUGFLAG_TAINT(taint));\
instrumentation_end(); \
} while (0)
#endif
#ifndef __WARN_printf
#define __WARN_printf(taint, arg...) do { \
instrumentation_begin(); \
warn_slowpath_fmt(__FILE__, __LINE__, taint, arg); \
instrumentation_end(); \
} while (0)
#else
#define __WARN() __WARN_FLAGS(BUGFLAG_TAINT(TAINT_WARN))
#define __WARN_printf(taint, arg...) do { \
instrumentation_begin(); \
__warn_printk(arg); \
__WARN_FLAGS(BUGFLAG_NO_CUT_HERE | BUGFLAG_TAINT(taint));\
instrumentation_end(); \
} while (0)
#define WARN_ON_ONCE(condition) ({ \
int __ret_warn_on = !!(condition); \
if (unlikely(__ret_warn_on)) \
__WARN_FLAGS(BUGFLAG_ONCE | \
BUGFLAG_TAINT(TAINT_WARN)); \
unlikely(__ret_warn_on); \
})
#endif
#ifndef __WARN
#define __WARN() __WARN_printf(TAINT_WARN, NULL)
#endif
/* used internally by panic.c */
@ -148,8 +180,10 @@ extern __printf(1, 2) void __warn_printk(const char *fmt, ...);
DO_ONCE_LITE_IF(condition, WARN_ON, 1)
#endif
#ifndef WARN_ONCE
#define WARN_ONCE(condition, format...) \
DO_ONCE_LITE_IF(condition, WARN, 1, format)
#endif
#define WARN_TAINT_ONCE(condition, taint, format...) \
DO_ONCE_LITE_IF(condition, WARN_TAINT, 1, taint, format)

View File

@ -42,6 +42,7 @@ void bug_get_file_line(struct bug_entry *bug, const char **file,
struct bug_entry *find_bug(unsigned long bugaddr);
enum bug_trap_type report_bug(unsigned long bug_addr, struct pt_regs *regs);
enum bug_trap_type report_bug_entry(struct bug_entry *bug, struct pt_regs *regs);
/* These are defined by the architecture */
int is_valid_bugaddr(unsigned long addr);
@ -62,6 +63,13 @@ static inline enum bug_trap_type report_bug(unsigned long bug_addr,
}
struct bug_entry;
static inline enum bug_trap_type
report_bug_entry(struct bug_entry *bug, struct pt_regs *regs)
{
return BUG_TRAP_TYPE_BUG;
}
static inline void bug_get_file_line(struct bug_entry *bug, const char **file,
unsigned int *line)
{

View File

@ -873,13 +873,15 @@ void __warn(const char *file, int line, void *caller, unsigned taint,
disable_trace_on_warning();
if (file)
pr_warn("WARNING: CPU: %d PID: %d at %s:%d %pS\n",
raw_smp_processor_id(), current->pid, file, line,
caller);
else
pr_warn("WARNING: CPU: %d PID: %d at %pS\n",
raw_smp_processor_id(), current->pid, caller);
if (file) {
pr_warn("WARNING: %s:%d at %pS, CPU#%d: %s/%d\n",
file, line, caller,
raw_smp_processor_id(), current->comm, current->pid);
} else {
pr_warn("WARNING: at %pS, CPU#%d: %s/%d\n",
caller,
raw_smp_processor_id(), current->comm, current->pid);
}
#pragma GCC diagnostic push
#ifndef __clang__

View File

@ -206,6 +206,16 @@ config DEBUG_BUGVERBOSE
of the BUG call as well as the EIP and oops trace. This aids
debugging but costs about 70-100K of memory.
config DEBUG_BUGVERBOSE_DETAILED
bool "Verbose WARN_ON_ONCE() reporting (adds 100K)" if DEBUG_BUGVERBOSE
help
Say Y here to make WARN_ON_ONCE() output the condition string of the
warning, in addition to the file name and line number.
This helps debugging, but costs about 100K of memory.
Say N if unsure.
endmenu # "printk and dmesg options"
config DEBUG_KERNEL

View File

@ -139,6 +139,29 @@ void bug_get_file_line(struct bug_entry *bug, const char **file,
#endif
}
static const char *bug_get_format(struct bug_entry *bug)
{
const char *format = NULL;
#ifdef HAVE_ARCH_BUG_FORMAT
#ifdef CONFIG_GENERIC_BUG_RELATIVE_POINTERS
/*
* Allow an architecture to:
* - relative encode NULL (difficult vs KASLR);
* - use a literal 0 (there are no valid objects inside
* the __bug_table itself to refer to after all);
* - use an empty string.
*/
if (bug->format_disp)
format = (const char *)&bug->format_disp + bug->format_disp;
if (format && format[0] == '\0')
format = NULL;
#else
format = bug->format;
#endif
#endif
return format;
}
struct bug_entry *find_bug(unsigned long bugaddr)
{
struct bug_entry *bug;
@ -150,26 +173,51 @@ struct bug_entry *find_bug(unsigned long bugaddr)
return module_find_bug(bugaddr);
}
static enum bug_trap_type __report_bug(unsigned long bugaddr, struct pt_regs *regs)
static void __warn_printf(const char *fmt, struct pt_regs *regs)
{
struct bug_entry *bug;
const char *file;
unsigned line, warning, once, done;
if (!fmt)
return;
if (!is_valid_bugaddr(bugaddr))
return BUG_TRAP_TYPE_NONE;
#ifdef HAVE_ARCH_BUG_FORMAT_ARGS
if (regs) {
struct arch_va_list _args;
va_list *args = __warn_args(&_args, regs);
bug = find_bug(bugaddr);
if (!bug)
return BUG_TRAP_TYPE_NONE;
if (args) {
vprintk(fmt, *args);
return;
}
}
#endif
printk("%s", fmt);
}
static enum bug_trap_type __report_bug(struct bug_entry *bug, unsigned long bugaddr, struct pt_regs *regs)
{
bool warning, once, done, no_cut, has_args;
const char *file, *fmt;
unsigned line;
if (!bug) {
if (!is_valid_bugaddr(bugaddr))
return BUG_TRAP_TYPE_NONE;
bug = find_bug(bugaddr);
if (!bug)
return BUG_TRAP_TYPE_NONE;
}
disable_trace_on_warning();
bug_get_file_line(bug, &file, &line);
fmt = bug_get_format(bug);
warning = (bug->flags & BUGFLAG_WARNING) != 0;
once = (bug->flags & BUGFLAG_ONCE) != 0;
done = (bug->flags & BUGFLAG_DONE) != 0;
warning = bug->flags & BUGFLAG_WARNING;
once = bug->flags & BUGFLAG_ONCE;
done = bug->flags & BUGFLAG_DONE;
no_cut = bug->flags & BUGFLAG_NO_CUT_HERE;
has_args = bug->flags & BUGFLAG_ARGS;
if (warning && once) {
if (done)
@ -187,8 +235,10 @@ static enum bug_trap_type __report_bug(unsigned long bugaddr, struct pt_regs *re
* "cut here" line now. WARN() issues its own "cut here" before the
* extra debugging message it writes before triggering the handler.
*/
if ((bug->flags & BUGFLAG_NO_CUT_HERE) == 0)
if (!no_cut) {
printk(KERN_DEFAULT CUT_HERE);
__warn_printf(fmt, has_args ? regs : NULL);
}
if (warning) {
/* this is a WARN_ON rather than BUG/BUG_ON */
@ -206,13 +256,25 @@ static enum bug_trap_type __report_bug(unsigned long bugaddr, struct pt_regs *re
return BUG_TRAP_TYPE_BUG;
}
enum bug_trap_type report_bug_entry(struct bug_entry *bug, struct pt_regs *regs)
{
enum bug_trap_type ret;
bool rcu = false;
rcu = warn_rcu_enter();
ret = __report_bug(bug, 0, regs);
warn_rcu_exit(rcu);
return ret;
}
enum bug_trap_type report_bug(unsigned long bugaddr, struct pt_regs *regs)
{
enum bug_trap_type ret;
bool rcu = false;
rcu = warn_rcu_enter();
ret = __report_bug(bugaddr, regs);
ret = __report_bug(NULL, bugaddr, regs);
warn_rcu_exit(rcu);
return ret;