On Fri, May 30, 2025 at 10:48:47AM -0700, Kees Cook wrote:
> On Fri, May 30, 2025 at 04:01:40PM +0200, Peter Zijlstra wrote:
> > I'm not really concerned with performance here, but more with the size
> > of the code emitted by WARN_ONCE(). There are a *ton* of WARN sites,
> > while only one report_bug() and printk().
> > 
> > The really offensive thing is that this is for a feature most nobody
> > will ever need :/
> 
> Well, it won't be enabled often -- this reminds me of ftrace: it needs
> to work, but it'll be off most of the time.

Well, ftrace is useful, but when would I *ever* care about this stuff? I
can't operate kunit, don't give a crap about kunit, and if I were to
magically run it, I would be more than capable of ignoring WARNs.


> I like the patch! Can you add a _little_ documentation, though? e.g.
> explaining that BUG_WARN ... BUG_WARN_END is for format string args,
> etc.

Cleaned it up a little bit... I'll add some comments on later :-)

I also need to go fix WARN_ONCE(), at least for the n<=2 cases that can
use BUGFLAG_ONCE now.

---
diff --git a/arch/x86/include/asm/bug.h b/arch/x86/include/asm/bug.h
index f0e9acf72547..3aecedf46d0c 100644
--- a/arch/x86/include/asm/bug.h
+++ b/arch/x86/include/asm/bug.h
@@ -5,6 +5,7 @@
 #include <linux/stringify.h>
 #include <linux/instrumentation.h>
 #include <linux/objtool.h>
+#include <linux/args.h>
 
 /*
  * Despite that some emulators terminate on UD2, we use it for WARN().
@@ -26,52 +27,52 @@
 #define BUG_UD2                        0xfffe
 #define BUG_UD1                        0xfffd
 #define BUG_UD1_UBSAN          0xfffc
+#define BUG_UD1_WARN           0xfffb
 #define BUG_EA                 0xffea
 #define BUG_LOCK               0xfff0
 
 #ifdef CONFIG_GENERIC_BUG
 
+#define BUG_HAS_FORMAT
+
 #ifdef CONFIG_X86_32
-# define __BUG_REL(val)        ".long " __stringify(val)
+#define ASM_BUG_REL(val)       .long val
 #else
-# define __BUG_REL(val)        ".long " __stringify(val) " - ."
+#define ASM_BUG_REL(val)       .long val - .
 #endif
 
 #ifdef CONFIG_DEBUG_BUGVERBOSE
+#define ASM_BUGTABLE_VERBOSE(file, line)                               \
+       ASM_BUG_REL(file) ;                                             \
+       .word line
+#define ASM_BUGTABLE_VERBOSE_SIZE      6
+#else
+#define ASM_BUGTABLE_VERBOSE(file, line)
+#define ASM_BUGTABLE_VERBOSE_SIZE      0
+#endif
 
-#define _BUG_FLAGS(ins, flags, extra)                                  \
-do {                                                                   \
-       asm_inline volatile("1:\t" ins "\n"                             \
-                    ".pushsection __bug_table,\"aw\"\n"                \
-                    "2:\t" __BUG_REL(1b) "\t# bug_entry::bug_addr\n"   \
-                    "\t"  __BUG_REL(%c0) "\t# bug_entry::file\n"       \
-                    "\t.word %c1"        "\t# bug_entry::line\n"       \
-                    "\t.word %c2"        "\t# bug_entry::flags\n"      \
-                    "\t.org 2b+%c3\n"                                  \
-                    ".popsection\n"                                    \
-                    extra                                              \
-                    : : "i" (__FILE__), "i" (__LINE__),                \
-                        "i" (flags),                                   \
-                        "i" (sizeof(struct bug_entry)));               \
-} while (0)
+#define ASM_BUGTABLE_FORMAT(format)                                    \
+       ASM_BUG_REL(format)
 
-#else /* !CONFIG_DEBUG_BUGVERBOSE */
+#define ASM_BUGTABLE_FLAGS(at, format, file, line, flags)              \
+       .pushsection __bug_table, "aw" ;                                \
+       123:    ASM_BUG_REL(at) ;                                       \
+       ASM_BUGTABLE_FORMAT(format) ;                                   \
+       ASM_BUGTABLE_VERBOSE(file, line) ;                              \
+       .word   flags ;                                                 \
+       .org 123b + 6 + 4 + ASM_BUGTABLE_VERBOSE_SIZE ;                 \
+       .popsection
 
 #define _BUG_FLAGS(ins, flags, extra)                                  \
 do {                                                                   \
        asm_inline volatile("1:\t" ins "\n"                             \
-                    ".pushsection __bug_table,\"aw\"\n"                \
-                    "2:\t" __BUG_REL(1b) "\t# bug_entry::bug_addr\n"   \
-                    "\t.word %c0"        "\t# bug_entry::flags\n"      \
-                    "\t.org 2b+%c1\n"                                  \
-                    ".popsection\n"                                    \
-                    extra                                              \
-                    : : "i" (flags),                                   \
-                        "i" (sizeof(struct bug_entry)));               \
+           __stringify(ASM_BUGTABLE_FLAGS(1b, %c[fmt], %c[file], %c[line], 
%c[fl])) "\n"       \
+                           extra                                       \
+                    : : [fmt] "i" (NULL), [file] "i" (__FILE__),       \
+                        [line] "i" (__LINE__),                         \
+                        [fl] "i" (flags));                             \
 } while (0)
 
-#endif /* CONFIG_DEBUG_BUGVERBOSE */
-
 #else
 
 #define _BUG_FLAGS(ins, flags, extra)  asm volatile(ins)
@@ -100,6 +101,62 @@ do {                                                       
        \
        instrumentation_end();                                  \
 } while (0)
 
+#ifndef __ASSEMBLER__
+struct pt_regs;
+extern void __warn_printf(const char *fmt, struct pt_regs *regs);
+#define __warn_printf __warn_printf
+#endif
+
+#define __WARN_printf_0(taint, format)                                 \
+do {                                                                   \
+       __auto_type __flags = BUGFLAG_WARNING | BUGFLAG_FORMAT | 
BUGFLAG_TAINT(taint); \
+       instrumentation_begin();                                        \
+       asm_inline volatile("1: ud2\n"                                  \
+                           ANNOTATE_REACHABLE(1b)                      \
+           __stringify(ASM_BUGTABLE_FLAGS(1b, %c[fmt], %c[file], %c[line], 
%c[fl])) "\n" \
+                    : : [file] "i" (__FILE__), [line] "i" (__LINE__),  \
+                        [fl] "i" (__flags), [fmt] "i" (format));       \
+       instrumentation_end();                                          \
+} while (0)
+
+#define __WARN_printf_1(taint, format, arg1)                           \
+do {                                                                   \
+       __auto_type __flags = BUGFLAG_WARNING | BUGFLAG_FORMAT | 
BUGFLAG_TAINT(taint); \
+       instrumentation_begin();                                        \
+       asm_inline volatile("1: ud1 (%%rcx), %[a1]\n"                   \
+                           ANNOTATE_REACHABLE(1b)                      \
+           __stringify(ASM_BUGTABLE_FLAGS(1b, %c[fmt], %c[file], %c[line], 
%c[fl])) "\n" \
+                    : : [file] "i" (__FILE__), [line] "i" (__LINE__),  \
+                        [fl] "i" (__flags), [fmt] "i" (format),        \
+                        [a1] "r" ((unsigned long)(arg1)));             \
+       instrumentation_end();                                          \
+} while (0)
+
+#define __WARN_printf_2(taint, format, arg1, arg2)                     \
+do {                                                                   \
+       __auto_type __flags = BUGFLAG_WARNING | BUGFLAG_FORMAT | 
BUGFLAG_TAINT(taint); \
+       instrumentation_begin();                                        \
+       asm_inline volatile("1: ud1 %[a2], %[a1]\n"                     \
+                           ANNOTATE_REACHABLE(1b)                      \
+           __stringify(ASM_BUGTABLE_FLAGS(1b, %c[fmt], %c[file], %c[line], 
%c[fl])) "\n" \
+                    : : [file] "i" (__FILE__), [line] "i" (__LINE__),  \
+                        [fl] "i" (__flags), [fmt] "i" (format),        \
+                        [a1] "r" ((unsigned long)(arg1)),              \
+                        [a2] "r" ((unsigned long)(arg2)));             \
+       instrumentation_end();                                          \
+} while (0)
+
+#define __WARN_printf_n(taint, fmt, arg...) do {                       \
+               instrumentation_begin();                                \
+               __warn_printk(fmt, arg);                                \
+               __WARN_FLAGS(BUGFLAG_NO_CUT_HERE | BUGFLAG_TAINT(taint));\
+               instrumentation_end();                                  \
+       } while (0)
+
+#define WARN_ARGS(X...) __COUNT_ARGS(, ##X, n, n, n, n, n, n, n, n, n, n, n, 
n, n, 2, 1, 0)
+
+#define __WARN_printf(taint, fmt, arg...) CONCATENATE(__WARN_printf_, 
WARN_ARGS(arg))(taint, fmt, ## arg)
+
 #include <asm-generic/bug.h>
 
 #endif /* _ASM_X86_BUG_H */
diff --git a/arch/x86/kernel/traps.c b/arch/x86/kernel/traps.c
index 94c0236963c6..bb9193fd3d2d 100644
--- a/arch/x86/kernel/traps.c
+++ b/arch/x86/kernel/traps.c
@@ -81,18 +81,6 @@
 
 DECLARE_BITMAP(system_vectors, NR_VECTORS);
 
-__always_inline int is_valid_bugaddr(unsigned long addr)
-{
-       if (addr < TASK_SIZE_MAX)
-               return 0;
-
-       /*
-        * We got #UD, if the text isn't readable we'd have gotten
-        * a different exception.
-        */
-       return *(unsigned short *)addr == INSN_UD2;
-}
-
 /*
  * Check for UD1 or UD2, accounting for Address Size Override Prefixes.
  * If it's a UD1, further decode to determine its use:
@@ -103,24 +91,39 @@ __always_inline int is_valid_bugaddr(unsigned long addr)
  * UBSan{10}:    67 0f b9 40 10          ud1    0x10(%eax),%eax
  * static_call:  0f b9 cc                ud1    %esp,%ecx
  *
- * Notably UBSAN uses EAX, static_call uses ECX.
+ * WARN_printf_0:                        ud2
+ * WARN_printf_1:                        ud1    (%ecx),%reg
+ * WARN_printf_2:                        ud1    %reg,%reg
+ *
+ * Notably UBSAN uses (%eax), static_call does not get a bug_entry.
  */
-__always_inline int decode_bug(unsigned long addr, s32 *imm, int *len)
+__always_inline int decode_bug(unsigned long addr, u32 *regs, s32 *imm, int 
*len)
 {
        unsigned long start = addr;
+       u8 v, rex = 0, reg, rm;
        bool lock = false;
-       u8 v;
+       int type = BUG_UD1;
 
        if (addr < TASK_SIZE_MAX)
                return BUG_NONE;
 
-       v = *(u8 *)(addr++);
-       if (v == INSN_ASOP)
+       for (;;) {
                v = *(u8 *)(addr++);
 
-       if (v == INSN_LOCK) {
-               lock = true;
-               v = *(u8 *)(addr++);
+               if (v == INSN_ASOP)
+                       continue;
+
+               if (v == INSN_LOCK) {
+                       lock = true;
+                       continue;
+               }
+
+               if ((v & 0xf0) == 0x40) {
+                       rex = v;
+                       continue;
+               }
+
+               break;
        }
 
        switch (v) {
@@ -141,6 +144,9 @@ __always_inline int decode_bug(unsigned long addr, s32 
*imm, int *len)
                return BUG_NONE;
        }
 
+       *regs = 0;
+       *imm = 0;
+
        v = *(u8 *)(addr++);
        if (v == SECOND_BYTE_OPCODE_UD2) {
                *len = addr - start;
@@ -150,16 +156,22 @@ __always_inline int decode_bug(unsigned long addr, s32 
*imm, int *len)
        if (v != SECOND_BYTE_OPCODE_UD1)
                return BUG_NONE;
 
-       *imm = 0;
        v = *(u8 *)(addr++);            /* ModRM */
-
        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);
+       *regs = (rm << 4) | reg;
+
        /* 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 == 1) {          /* CX */
+                       type = BUG_UD1_WARN;
+                       *regs |= 0x100;
+               }
                break;
 
        case 1: *imm = *(s8 *)addr;
@@ -170,18 +182,37 @@ __always_inline int decode_bug(unsigned long addr, s32 
*imm, int *len)
                addr += 4;
                break;
 
-       case 3: break;
+       case 3: type = BUG_UD1_WARN;
+               *regs |= 0x300;
+               break;
        }
 
        /* record instruction length */
        *len = addr - start;
 
-       if (X86_MODRM_REG(v) == 0)      /* EAX */
+       if (!rm && X86_MODRM_MOD(v) != 3)       /* (%eax) */
                return BUG_UD1_UBSAN;
 
-       return BUG_UD1;
+       return type;
 }
 
+int is_valid_bugaddr(unsigned long addr)
+{
+       int ud_type, ud_len;
+       u32 ud_regs;
+       s32 ud_imm;
+
+       if (addr < TASK_SIZE_MAX)
+               return 0;
+
+       /*
+        * We got #UD, if the text isn't readable we'd have gotten
+        * a different exception.
+        */
+       ud_type = decode_bug(addr, &ud_regs, &ud_imm, &ud_len);
+
+       return ud_type == BUG_UD2 || ud_type == BUG_UD1_WARN;
+}
 
 static nokprobe_inline int
 do_trap_no_signal(struct task_struct *tsk, int trapnr, const char *str,
@@ -305,14 +336,42 @@ static inline void handle_invalid_op(struct pt_regs *regs)
                      ILL_ILLOPN, error_get_trap_addr(regs));
 }
 
+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));
+}
+
+void __warn_printf(const char *fmt, struct pt_regs *regs)
+{
+       u32 r = regs->orig_ax;
+       unsigned long a1 = pt_regs_val(regs, (r >> 0) & 0xf);
+       unsigned long a2 = pt_regs_val(regs, (r >> 4) & 0xf);
+
+       switch ((r >> 8) & 0x3) {
+       case 0:
+               printk(fmt);
+               return;
+       case 1:
+               printk(fmt, a1);
+               return;
+       case 3:
+               printk(fmt, a1, a2);
+               return;
+       }
+}
+
 static noinstr bool handle_bug(struct pt_regs *regs)
 {
        unsigned long addr = regs->ip;
        bool handled = false;
        int ud_type, ud_len;
+       u32 ud_regs;
        s32 ud_imm;
 
-       ud_type = decode_bug(addr, &ud_imm, &ud_len);
+       ud_type = decode_bug(addr, &ud_regs, &ud_imm, &ud_len);
        if (ud_type == BUG_NONE)
                return handled;
 
@@ -334,7 +393,9 @@ static noinstr bool handle_bug(struct pt_regs *regs)
                raw_local_irq_enable();
 
        switch (ud_type) {
+       case BUG_UD1_WARN:
        case BUG_UD2:
+               regs->orig_ax = ud_regs;
                if (report_bug(regs->ip, regs) == BUG_TRAP_TYPE_WARN) {
                        handled = true;
                        break;
diff --git a/include/asm-generic/bug.h b/include/asm-generic/bug.h
index 387720933973..b8336259f81d 100644
--- a/include/asm-generic/bug.h
+++ b/include/asm-generic/bug.h
@@ -13,6 +13,7 @@
 #define BUGFLAG_ONCE           (1 << 1)
 #define BUGFLAG_DONE           (1 << 2)
 #define BUGFLAG_NO_CUT_HERE    (1 << 3)        /* CUT_HERE already sent */
+#define BUGFLAG_FORMAT         (1 << 4)
 #define BUGFLAG_TAINT(taint)   ((taint) << 8)
 #define BUG_GET_TAINT(bug)     ((bug)->flags >> 8)
 #endif
@@ -36,6 +37,13 @@ struct bug_entry {
 #else
        signed int      bug_addr_disp;
 #endif
+#ifdef BUG_HAS_FORMAT
+#ifndef CONFIG_GENERIC_BUG_RELATIVE_POINTERS
+       const char      *format;
+#else
+       signed int      format_disp;
+#endif
+#endif
 #ifdef CONFIG_DEBUG_BUGVERBOSE
 #ifndef CONFIG_GENERIC_BUG_RELATIVE_POINTERS
        const char      *file;
@@ -101,12 +109,16 @@ extern __printf(1, 2) void __warn_printk(const char *fmt, 
...);
        } while (0)
 #else
 #define __WARN()               __WARN_FLAGS(BUGFLAG_TAINT(TAINT_WARN))
+
+#ifndef __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
+
 #define WARN_ON_ONCE(condition) ({                             \
        int __ret_warn_on = !!(condition);                      \
        if (unlikely(__ret_warn_on))                            \
@@ -114,6 +126,7 @@ extern __printf(1, 2) void __warn_printk(const char *fmt, 
...);
                             BUGFLAG_TAINT(TAINT_WARN));        \
        unlikely(__ret_warn_on);                                \
 })
+
 #endif
 
 /* used internally by panic.c */
@@ -148,8 +161,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)
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 62b3416f5e43..4f7bc0e500ba 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -8703,6 +8703,10 @@ void __init sched_init(void)
        preempt_dynamic_init();
 
        scheduler_running = 1;
+
+       WARN(true, "Ultimate answer: %d\n", 42);
+       WARN(true, "XXX2 %d %d\n", 43, 44);
+       WARN(true, "XXX3 %d %d %d\n", 45, 46, 47);
 }
 
 #ifdef CONFIG_DEBUG_ATOMIC_SLEEP
diff --git a/lib/bug.c b/lib/bug.c
index b1f07459c2ee..085889e9c096 100644
--- a/lib/bug.c
+++ b/lib/bug.c
@@ -139,6 +139,17 @@ 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 CONFIG_GENERIC_BUG_RELATIVE_POINTERS
+       format = (const char *)&bug->format_disp + bug->format_disp;
+#else
+       format = bug->format;
+#endif
+       return format;
+}
+
 struct bug_entry *find_bug(unsigned long bugaddr)
 {
        struct bug_entry *bug;
@@ -150,6 +161,14 @@ struct bug_entry *find_bug(unsigned long bugaddr)
        return module_find_bug(bugaddr);
 }
 
+#ifndef __warn_printf
+static void __warn_printf(const char *fmt, struct pt_regs *regs)
+{
+       printk(fmt);
+}
+#define __warn_printf __warn_printf
+#endif
+
 static enum bug_trap_type __report_bug(unsigned long bugaddr, struct pt_regs 
*regs)
 {
        struct bug_entry *bug;
@@ -190,6 +209,9 @@ static enum bug_trap_type __report_bug(unsigned long 
bugaddr, struct pt_regs *re
        if ((bug->flags & BUGFLAG_NO_CUT_HERE) == 0)
                printk(KERN_DEFAULT CUT_HERE);
 
+       if (bug->flags & BUGFLAG_FORMAT)
+               __warn_printf(bug_get_format(bug), regs);
+
        if (warning) {
                /* this is a WARN_ON rather than BUG/BUG_ON */
                __warn(file, line, (void *)bugaddr, BUG_GET_TAINT(bug), regs,

Reply via email to