P Petr Mladek #
Message details and related content
Hi, I would like to hear opinion from a bigger audience. It is an userspace interface that we might need to maintain forewer. Adding few more people in to CC: Steven Rostedt [off-list ref]: printk co-maintainer Alexey Dobriyan [off-list ref]: fs/proc maintainer Greg Kroah-Hartman [off-list ref]: sysfs maintainer Jason Baron [off-list ref]: dynamic_debug maintainer Kees Cook [off-list ref]: security POV linux-api@vger.kernel.org: Linux API mailing list Of course, we should also ask if this is the right approach for the think that you want to achieve. The motivation for this patch is that the strings printed by kernels are not reliable and you want a simple way to compare differences bethween versions. Do I get it right? See more comments below. On Thu 2021-02-04 15:37:52, Chris Down wrote:
We have a number of systems industry-wide that have a subset of their functionality that works as follows:
1. Receive a message from local kmsg, serial console, or netconsole; 2. Apply a set of rules to classify the message; 3. Do something based on this classification (like scheduling a remediation for the machine), rinse, and repeat. As a couple of examples of places we have this implemented just inside Facebook, although this isn't a Facebook-specific problem, we have this inside our netconsole processing (for alarm classification), and as part of our machine health checking. We use these messages to determine fairly important metrics around production health, and it's important that we get them right. While for some kinds of issues we have counters, tracepoints, or metrics with a stable interface which can reliably indicate the issue, in order to react to production issues quickly we need to work with the interface which most kernel developers naturally use when developing: printk. Most production issues come from unexpected phenomena, and as such usually the code in question doesn't have easily usable tracepoints or other counters available for the specific problem being mitigated. We have a number of lines of monitoring defence against problems in production (host metrics, process metrics, service metrics, etc), and where it's not feasible to reliably monitor at another level, this kind of pragmatic netconsole monitoring is essential. As you'd expect, monitoring using printk is rather brittle for a number of reasons -- most notably that the message might disappear entirely in a new version of the kernel, or that the message may change in some way that the regex or other classification methods start to silently fail.
Another is that printk() is not reliable on its own. Messages might
get lost. The size of the log buffer is limited. Deamon reading
/dev/kmsg need not be scheduled in time or often enough. Console
might be slow. The messages are filtered on the console by console_loglevel.
One factor that makes this even harder is that, under normal operation,
many of these messages are never expected to be hit. For example, there
may be some rare hardware bug which you want to detect if it was to ever
happen again, but its recurrence is not likely or anticipated. This
precludes using something like checking whether the printk in question
was printed somewhere fleetwide recently to determine whether the
message in question is still present or not, since we don't anticipate
that it should be printed anywhere, but still need to monitor for its
future presence in the long-term.
This class of issue has happened on a number of occasions, causing
unhealthy machines with hardware issues to remain in production for
longer than ideal. As a recent example, some monitoring around
blk_update_request fell out of date and caused semi-broken machines to
remain in production for longer than would be desirable.
Searching through the codebase to find the message is also extremely
fragile, because many of the messages are further constructed beyond
their callsite (eg. btrfs_printk and other module-specific wrappers,
each with their own functionality). Even if they aren't, guessing the
format and formulation of the underlying message based on the aesthetics
of the message emitted is not a recipe for success at scale, and our
previous issues with fleetwide machine health checking demonstrate as
much.
This patch provides a solution to the issue of silently changed or
deleted printks: we record pointers to all printk format strings known
at compile time into a new .printk_fmts section, both in vmlinux and
modules. At runtime, this can then be iterated by looking at
/proc/printk_formats, which emits the same format as `printk` itself,
which we already export elsewhere (for example, in netconsole):
# Format: <module>,<facility><level><format>\0
$ perl -p -e 's/\n/\\n/g;s/\0/\n/g' /proc/printk_formats | shuf -n 5
vmlinux,6Disabling APIC timer\n
intel_rapl_common,3intel_rapl_common: Cannot find matching power limit for constraint %d\n
dm_crypt,3device-mapper: crypt: %s: INTEGRITY AEAD ERROR, sector %llu\n
mac80211,6%s: AP bug: HT capability missing from AssocResp\n
vmlinux,3zpool: couldn't create zpool - out of memory\n
The facility and log level are not well separated from the format string.
Also this is yet another style how the format is displayed. We already have
+ console/syslog: formated by record_print_text()
+ /dev/kmsg: formatted by info_print_ext_header(), msg_print_ext_body().
+ /sys/kernel/debug/dynamic_debug/control
+ /sys/kernel/debug/tracing/printk_formats
We should get some inspiration from the existing interfaces.
But we first should decide what information might be useful:
+ 'facility' should not be needed. All messages should be from
kernel.
+ <module> is already optinaly added by pr_fmt() to the printed strings
as: pr_fmt(): ...
+ dynamic_debug seems to print KBUILD_MODNAME even when the module
is built in.
+ dynamic debug also prints <source_file:line>
quoted hunk
This mitigates the majority of cases where we have a highly-specific printk which we want to match on, as we can now enumerate and check whether the format changed or the printk callsite disappeared entirely in userspace. This allows us to catch changes to printks we monitor earlier and decide what to do about it before it becomes problematic. There is no additional runtime cost for printk callers or printk itself, and the assembly generated is exactly the same. Signed-off-by: Chris Down <chris@chrisdown.name> Cc: Petr Mladek <pmladek@suse.com> Cc: Sergey Senozhatsky <redacted> Cc: John Ogness <john.ogness@linutronix.de> Cc: Johannes Weiner <hannes@cmpxchg.org> Cc: Andrew Morton <akpm@linux-foundation.org> --- arch/arm/kernel/entry-v7m.S | 2 +- arch/arm/lib/backtrace-clang.S | 2 +- arch/arm/lib/backtrace.S | 2 +- arch/arm/mach-rpc/io-acorn.S | 2 +- arch/arm/vfp/vfphw.S | 6 +- arch/openrisc/kernel/entry.S | 6 +- arch/powerpc/kernel/head_fsl_booke.S | 2 +- arch/x86/kernel/head_32.S | 2 +- include/asm-generic/vmlinux.lds.h | 13 ++ include/linux/module.h | 5 + include/linux/printk.h | 43 +++++- init/Kconfig | 15 ++ kernel/module.c | 5 + kernel/printk/printk.c | 196 ++++++++++++++++++++++++++- 14 files changed, 280 insertions(+), 21 deletions(-)diff --git a/arch/arm/kernel/entry-v7m.S b/arch/arm/kernel/entry-v7m.S index d0e898608d30..7bde93c10962 100644 --- a/arch/arm/kernel/entry-v7m.S +++ b/arch/arm/kernel/entry-v7m.S@@ -23,7 +23,7 @@ __invalid_entry: adr r0, strerr mrs r1, ipsr mov r2, lr
It should be defined after #define TRACEDATA to follow the existing style. But honestly I am not much familiar with the sections definitions. I am curious why TRACE_PRINTKS() and __dyndbg are defined a bit different way.
quoted hunk
+ #ifdef CONFIG_THERMAL #define THERMAL_TABLE(name) \ . = ALIGN(8); \@@ -480,6 +491,8 @@ \ TRACEDATA \ \ + PRINTK_FMTS \ + \ /* Kernel symbol table: Normal symbols */ \ __ksymtab : AT(ADDR(__ksymtab) - LOAD_OFFSET) { \ __start___ksymtab = .; \diff --git a/include/linux/module.h b/include/linux/module.h index 7a0bcb5b1ffc..4235b14a22ef 100644 --- a/include/linux/module.h +++ b/include/linux/module.h@@ -516,6 +516,11 @@ struct module { struct klp_modinfo *klp_info; #endif +#ifdef CONFIG_PRINTK_ENUMERATION + unsigned int printk_fmts_sec_size; + const char **printk_fmts_start; +#endif + #ifdef CONFIG_MODULE_UNLOAD /* What modules depend on me? */ struct list_head source_list;diff --git a/include/linux/printk.h b/include/linux/printk.h index d3c08095a9a3..745a9915ec72 100644 --- a/include/linux/printk.h +++ b/include/linux/printk.h@@ -173,12 +173,12 @@ asmlinkage __printf(1, 0) int vprintk(const char *fmt, va_list args); asmlinkage __printf(1, 2) __cold -int printk(const char *fmt, ...); +int _printk(const char *fmt, ...);
You probably should get inspiration from t_show() in trace_printk.c. It handles newlines, ... Or by ddebug_proc_show(). It uses seq_escape(). Anyway, there is something wrong at the moment. The output looks fine with cat. But "less" says that it is a binary format and the output is a bit messy: $> less /proc/printk_formats "/proc/printk_formats" may be a binary file. See it anyway? vmlinux,^A3Warning: unable to open an initial console. ^@vmlinux,^A3Failed to execute %s (error %d) ^@vmlinux,^A6Kernel memory protection disabled. ^@vmlinux,^A3Starting init: %s exists but couldn't execute it (error %d) That is for now. I still have to think about it. And I am also curious about what others thing about this idea. Best Regards, Petr
quoted hunk
+ } + + mutex_unlock(&printk_fmts_mutex); + + return 0; +} + +static int proc_pf_open(struct inode *inode, struct file *file) +{ + /* + * We don't need to hold the mutex here to ensure that + * printk_fmts_total_size doesn't change prior to iteration -- worst + * case, seq_read_iter() will reallocate. + */ + return single_open_size(file, proc_pf_show, NULL, + READ_ONCE(printk_fmts_total_size)); +} + +static const struct proc_ops printk_proc_ops = { + .proc_flags = PROC_ENTRY_PERMANENT, + .proc_open = proc_pf_open, + .proc_read_iter = seq_read_iter, + .proc_lseek = seq_lseek, + .proc_release = single_release, +}; + +static int __init init_printk_fmts(void) +{ + const struct proc_dir_entry *pd = NULL; + + store_printk_fmt_sec(NULL, __start_printk_fmts, __stop_printk_fmts); + pd = proc_create("printk_formats", 0, NULL, &printk_proc_ops); + + return pd ? 0 : -ENOMEM; +} + +core_initcall(init_printk_fmts); + +#endif /* CONFIG_PRINTK_ENUMERATION */ +