]> git.proxmox.com Git - mirror_ubuntu-jammy-kernel.git/commitdiff
mm, slub: extend slub_debug syntax for multiple blocks
authorVlastimil Babka <vbabka@suse.cz>
Fri, 7 Aug 2020 06:18:35 +0000 (23:18 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Fri, 7 Aug 2020 18:33:22 +0000 (11:33 -0700)
Patch series "slub_debug fixes and improvements".

The slub_debug kernel boot parameter can either apply a single set of
options to all caches or a list of caches.  There is a use case where
debugging is applied for all caches and then disabled at runtime for
specific caches, for performance and memory consumption reasons [1].  As
runtime changes are dangerous, extend the boot parameter syntax so that
multiple blocks of either global or slab-specific options can be
specified, with blocks delimited by ';'.  This will also support the use
case of [1] without runtime changes.

For details see the updated Documentation/vm/slub.rst

[1] https://lore.kernel.org/r/1383cd32-1ddc-4dac-b5f8-9c42282fa81c@codeaurora.org

[weiyongjun1@huawei.com: make parse_slub_debug_flags() static]
Link: http://lkml.kernel.org/r/20200702150522.4940-1-weiyongjun1@huawei.com
Signed-off-by: Vlastimil Babka <vbabka@suse.cz>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Reviewed-by: Kees Cook <keescook@chromium.org>
Cc: Vlastimil Babka <vbabka@suse.cz>
Cc: Christoph Lameter <cl@linux.com>
Cc: Jann Horn <jannh@google.com>
Cc: Roman Gushchin <guro@fb.com>
Cc: Vijayanand Jitta <vjitta@codeaurora.org>
Cc: Pekka Enberg <penberg@kernel.org>
Cc: David Rientjes <rientjes@google.com>
Cc: Joonsoo Kim <iamjoonsoo.kim@lge.com>
Link: http://lkml.kernel.org/r/20200610163135.17364-2-vbabka@suse.cz
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Documentation/admin-guide/kernel-parameters.txt
Documentation/vm/slub.rst
mm/slub.c

index ef66b3c45ba221041b0bcf0b58afa337392945a4..c955522696892b413f5836b0a7318394eabbb474 100644 (file)
                        fragmentation.  Defaults to 1 for systems with
                        more than 32MB of RAM, 0 otherwise.
 
-       slub_debug[=options[,slabs]]    [MM, SLUB]
+       slub_debug[=options[,slabs][;[options[,slabs]]...]      [MM, SLUB]
                        Enabling slub_debug allows one to determine the
                        culprit if slab objects become corrupted. Enabling
                        slub_debug can create guard zones around objects and
index 4eee598555c997ede3ba93d4e6d3af499e2e3025..cfccb258cf42b27a841cd9a912558617cc10be6a 100644 (file)
@@ -41,6 +41,11 @@ slub_debug=<Debug-Options>,<slab name1>,<slab name2>,...
        Enable options only for select slabs (no spaces
        after a comma)
 
+Multiple blocks of options for all slabs or selected slabs can be given, with
+blocks of options delimited by ';'. The last of "all slabs" blocks is applied
+to all slabs except those that match one of the "select slabs" block. Options
+of the first "select slabs" blocks that matches the slab's name are applied.
+
 Possible debug options are::
 
        F               Sanity checks on (enables SLAB_DEBUG_CONSISTENCY_CHECKS
@@ -83,6 +88,19 @@ switch off debugging for such caches by default, use::
 
        slub_debug=O
 
+You can apply different options to different list of slab names, using blocks
+of options. This will enable red zoning for dentry and user tracking for
+kmalloc. All other slabs will not get any debugging enabled::
+
+       slub_debug=Z,dentry;U,kmalloc-*
+
+You can also enable options (e.g. sanity checks and poisoning) for all caches
+except some that are deemed too performance critical and don't need to be
+debugged by specifying global debug options followed by a list of slab names
+with "-" as options::
+
+       slub_debug=FZ;-,zs_handle,zspage
+
 In case you forgot to enable debugging on the kernel command line: It is
 possible to enable debugging manually when the kernel is up. Look at the
 contents of::
index 7f0764b6a3dfc6786cc64956e2a751350b8c0b53..829985d7c7c528632745d81d25843cabf3a361f6 100644 (file)
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -499,7 +499,7 @@ static slab_flags_t slub_debug = DEBUG_DEFAULT_FLAGS;
 static slab_flags_t slub_debug;
 #endif
 
-static char *slub_debug_slabs;
+static char *slub_debug_string;
 static int disable_higher_order_debug;
 
 /*
@@ -1262,68 +1262,132 @@ out:
        return ret;
 }
 
-static int __init setup_slub_debug(char *str)
+/*
+ * Parse a block of slub_debug options. Blocks are delimited by ';'
+ *
+ * @str:    start of block
+ * @flags:  returns parsed flags, or DEBUG_DEFAULT_FLAGS if none specified
+ * @slabs:  return start of list of slabs, or NULL when there's no list
+ * @init:   assume this is initial parsing and not per-kmem-create parsing
+ *
+ * returns the start of next block if there's any, or NULL
+ */
+static char *
+parse_slub_debug_flags(char *str, slab_flags_t *flags, char **slabs, bool init)
 {
-       slub_debug = DEBUG_DEFAULT_FLAGS;
-       if (*str++ != '=' || !*str)
-               /*
-                * No options specified. Switch on full debugging.
-                */
-               goto out;
+       bool higher_order_disable = false;
 
-       if (*str == ',')
+       /* Skip any completely empty blocks */
+       while (*str && *str == ';')
+               str++;
+
+       if (*str == ',') {
                /*
                 * No options but restriction on slabs. This means full
                 * debugging for slabs matching a pattern.
                 */
+               *flags = DEBUG_DEFAULT_FLAGS;
                goto check_slabs;
+       }
+       *flags = 0;
 
-       slub_debug = 0;
-       if (*str == '-')
-               /*
-                * Switch off all debugging measures.
-                */
-               goto out;
-
-       /*
-        * Determine which debug features should be switched on
-        */
-       for (; *str && *str != ','; str++) {
+       /* Determine which debug features should be switched on */
+       for (; *str && *str != ',' && *str != ';'; str++) {
                switch (tolower(*str)) {
+               case '-':
+                       *flags = 0;
+                       break;
                case 'f':
-                       slub_debug |= SLAB_CONSISTENCY_CHECKS;
+                       *flags |= SLAB_CONSISTENCY_CHECKS;
                        break;
                case 'z':
-                       slub_debug |= SLAB_RED_ZONE;
+                       *flags |= SLAB_RED_ZONE;
                        break;
                case 'p':
-                       slub_debug |= SLAB_POISON;
+                       *flags |= SLAB_POISON;
                        break;
                case 'u':
-                       slub_debug |= SLAB_STORE_USER;
+                       *flags |= SLAB_STORE_USER;
                        break;
                case 't':
-                       slub_debug |= SLAB_TRACE;
+                       *flags |= SLAB_TRACE;
                        break;
                case 'a':
-                       slub_debug |= SLAB_FAILSLAB;
+                       *flags |= SLAB_FAILSLAB;
                        break;
                case 'o':
                        /*
                         * Avoid enabling debugging on caches if its minimum
                         * order would increase as a result.
                         */
-                       disable_higher_order_debug = 1;
+                       higher_order_disable = true;
                        break;
                default:
-                       pr_err("slub_debug option '%c' unknown. skipped\n",
-                              *str);
+                       if (init)
+                               pr_err("slub_debug option '%c' unknown. skipped\n", *str);
                }
        }
-
 check_slabs:
        if (*str == ',')
-               slub_debug_slabs = str + 1;
+               *slabs = ++str;
+       else
+               *slabs = NULL;
+
+       /* Skip over the slab list */
+       while (*str && *str != ';')
+               str++;
+
+       /* Skip any completely empty blocks */
+       while (*str && *str == ';')
+               str++;
+
+       if (init && higher_order_disable)
+               disable_higher_order_debug = 1;
+
+       if (*str)
+               return str;
+       else
+               return NULL;
+}
+
+static int __init setup_slub_debug(char *str)
+{
+       slab_flags_t flags;
+       char *saved_str;
+       char *slab_list;
+       bool global_slub_debug_changed = false;
+       bool slab_list_specified = false;
+
+       slub_debug = DEBUG_DEFAULT_FLAGS;
+       if (*str++ != '=' || !*str)
+               /*
+                * No options specified. Switch on full debugging.
+                */
+               goto out;
+
+       saved_str = str;
+       while (str) {
+               str = parse_slub_debug_flags(str, &flags, &slab_list, true);
+
+               if (!slab_list) {
+                       slub_debug = flags;
+                       global_slub_debug_changed = true;
+               } else {
+                       slab_list_specified = true;
+               }
+       }
+
+       /*
+        * For backwards compatibility, a single list of flags with list of
+        * slabs means debugging is only enabled for those slabs, so the global
+        * slub_debug should be 0. We can extended that to multiple lists as
+        * long as there is no option specifying flags without a slab list.
+        */
+       if (slab_list_specified) {
+               if (!global_slub_debug_changed)
+                       slub_debug = 0;
+               slub_debug_string = saved_str;
+       }
 out:
        if ((static_branch_unlikely(&init_on_alloc) ||
             static_branch_unlikely(&init_on_free)) &&
@@ -1352,36 +1416,47 @@ slab_flags_t kmem_cache_flags(unsigned int object_size,
 {
        char *iter;
        size_t len;
+       char *next_block;
+       slab_flags_t block_flags;
 
        /* If slub_debug = 0, it folds into the if conditional. */
-       if (!slub_debug_slabs)
+       if (!slub_debug_string)
                return flags | slub_debug;
 
        len = strlen(name);
-       iter = slub_debug_slabs;
-       while (*iter) {
-               char *end, *glob;
-               size_t cmplen;
-
-               end = strchrnul(iter, ',');
+       next_block = slub_debug_string;
+       /* Go through all blocks of debug options, see if any matches our slab's name */
+       while (next_block) {
+               next_block = parse_slub_debug_flags(next_block, &block_flags, &iter, false);
+               if (!iter)
+                       continue;
+               /* Found a block that has a slab list, search it */
+               while (*iter) {
+                       char *end, *glob;
+                       size_t cmplen;
+
+                       end = strchrnul(iter, ',');
+                       if (next_block && next_block < end)
+                               end = next_block - 1;
+
+                       glob = strnchr(iter, end - iter, '*');
+                       if (glob)
+                               cmplen = glob - iter;
+                       else
+                               cmplen = max_t(size_t, len, (end - iter));
 
-               glob = strnchr(iter, end - iter, '*');
-               if (glob)
-                       cmplen = glob - iter;
-               else
-                       cmplen = max_t(size_t, len, (end - iter));
+                       if (!strncmp(name, iter, cmplen)) {
+                               flags |= block_flags;
+                               return flags;
+                       }
 
-               if (!strncmp(name, iter, cmplen)) {
-                       flags |= slub_debug;
-                       break;
+                       if (!*end || *end == ';')
+                               break;
+                       iter = end + 1;
                }
-
-               if (!*end)
-                       break;
-               iter = end + 1;
        }
 
-       return flags;
+       return slub_debug;
 }
 #else /* !CONFIG_SLUB_DEBUG */
 static inline void setup_object_debug(struct kmem_cache *s,