Extend list debugging to cover hlists
PATCH 1/1 Extend list debugging to cover hlists
We've seen a couple of recent reports which point to corrupt hlists. The DEBUG_LIST config option only checks regular lists, but it's not hard to cover hlists too.
include/linux/list.h +12 -0
@@ -714,10 +714,17 @@ static inline int hlist_empty(const struct hlist_head *h)return !h->first; } +#ifdef CONFIG_DEBUG_LIST +extern void hlist_check (struct hlist_node *n); +#else +#define hlist_check (n) do { } while (0) +#endif +static inline void __hlist_del (struct hlist_node *n) {struct hlist_node *next = n->next;struct hlist_node **pprev = n->pprev; +hlist_check (n); *pprev = next;if (next) next->pprev = pprev;
@@ -775,6 +782,7 @@ static inline void hlist_replace_rcu(struct hlist_node *old, {struct hlist_node *next = old->next; +hlist_check (old); new->next = next; new->pprev = old->pprev;smp_wmb ();
@@ -830,6 +838,7 @@ static inline void hlist_add_head_rcu(struct hlist_node *n,static inline void hlist_add_before (struct hlist_node *n,struct hlist_node *next) { +hlist_check (next); n->pprev = next->pprev; n->next = next; next->pprev = &n->next;
@@ -839,6 +848,7 @@ static inline void hlist_add_before(struct hlist_node *n,static inline void hlist_add_after (struct hlist_node *n,struct hlist_node *next) { +hlist_check (next); next->next = n->next; n->next = next; next->pprev = &n->next;
@@ -868,6 +878,7 @@ static inline void hlist_add_after(struct hlist_node *n,static inline void hlist_add_before_rcu (struct hlist_node *n,struct hlist_node *next) { +hlist_check (next); n->pprev = next->pprev; n->next = next;smp_wmb ();
@@ -896,6 +907,7 @@ static inline void hlist_add_before_rcu(struct hlist_node *n,static inline void hlist_add_after_rcu (struct hlist_node *prev,struct hlist_node *n) { +hlist_check (prev); n->next = prev->next; n->pprev = &prev->next;smp_wmb ();
lib/list_debug.c +18 -0
@@ -1,5 +1,8 @@/* * Copyright 2006, Red Hat, Inc., Dave Jones +* Copyright 2008 Intel Corporation +* Author: Matthew Wilcox <[email protected]> +* * Released under the General Public License (GPL). * * This file contains the linked list implementations for
@@ -76,3 +79,18 @@ void list_del(struct list_head *entry) entry->prev =LIST_POISON2 ; }EXPORT_SYMBOL (list_del); + +void hlist_check (struct hlist_node *n) +{ +if (unlikely (*n->pprev != n)) { +printk (KERN_ERR "hlist corruption. *pprev should be %p, " +"but was %p\n" , n, *n->pprev); +BUG (); + } +if (unlikely (n->next !=NULL && n->next->pprev != &n->next)) { +printk (KERN_ERR "hlist corruption. n->next->pprev should be" +"%p, but was %p\n" , &n->next, n->next->pprev); +BUG (); + } +} +EXPORT_SYMBOL (hlist_check);
Signature · 4 lines
Intel are signing my paycheques ... these opinions are still mine "Bill, look, we understand that you're interested in selling us this operating system, but compare it to ours. We can't possibly take such a retrograde step."
Quoted 15 lines · On Fri, 2 May 2008 12:27:46 -0600 Matthew Wilcox <[email protected]> wrote:
lib/list_debug.c +4
-0+void hlist_check(struct hlist_node *n) +{ + if (unlikely(*n->pprev != n)) { + printk(KERN_ERR "hlist corruption. *pprev should be%p, "
lib/list_debug.c +4
-0+ "but was %p\n", n, *n->pprev); +BUG (); + } +if (unlikely (n->next !=NULL && n->next->pprev != &n->next)){
(quoted diff) +1
-0+ printk(KERN_ERR "hlist corruption. n->next->pprevshould be"
(quoted diff) +1
-0+ "%p, but was %p\n", &n->next,
n->next->pprev);
lib/list_debug.c void list_del(struct list_head *entry) · line 96
Quoted 4 lines
lib/list_debug.c +4
-0+BUG (); + } +} +EXPORT_SYMBOL (hlist_check);
Hi,
I like the concept of the patch; however... BUG() is a tad on the rude side... how about WARN_ON(1) ?
Quoted 2 lines · Arjan van de Ven
I like the concept of the patch; however...
BUG()is a tad on the rude side... how aboutWARN_ON(1)?
I'm just copying the conventions of the code above. If those should be WARN_ON(1) then that should be a followup patch.
Signature · 4 lines
Intel are signing my paycheques ... these opinions are still mine "Bill, look, we understand that you're interested in selling us this operating system, but compare it to ours. We can't possibly take such a retrograde step."
Quoted 6 lines · Matthew Wilcox
Quoted 2 lines · Arjan van de Ven
I like the concept of the patch; however...
BUG()is a tad on the rude side... how aboutWARN_ON(1)?I'm just copying the conventions of the code above. If those should be
WARN_ON(1)then that should be a followup patch.
No objections from me either way. I chose to BUG originally because the bug I was chasing was corrupting memory in such a way that we'd die horribly shortly afterwards anyway.
Dave
Signature · 1 line
http://www.codemonkey.org.uk
Andrew Morton
maintainerQuoted 5 lines · On Fri, 2 May 2008 12:27:46 -0600 Matthew Wilcox <[email protected]> wrote:
We've seen a couple of recent reports which point to corrupt hlists. The
DEBUG_LISTconfig option only checks regular lists, but it's not hard to cover hlists too.
patching file include/linux/list.h
Hunk #1 succeeded at 499 (offset -215 lines).
Hunk #2 FAILED at 567.
Hunk #3 succeeded at 545 (offset -293 lines).
Hunk #4 succeeded at 555 (offset -293 lines).
Hunk #5 FAILED at 585.
Hunk #6 FAILED at 614.
3 out of 6 hunks FAILED -- saving rejects to file include/linux/list.h.rej patching file lib/list_debug.c
Failed to apply extend-list-debugging-to-cover-hlists
linux-next is a better tree against which to prepare 2.6.x+1 patches.
Quoted 1 line · Andrew Morton
linux-next is a better tree against which to prepare 2.6.x+1 patches.
Is there a reason to wait for 2.6.x+1? People are having problems today with corrupt hlists.
Signature · 4 lines
Intel are signing my paycheques ... these opinions are still mine "Bill, look, we understand that you're interested in selling us this operating system, but compare it to ours. We can't possibly take such a retrograde step."
Andrew Morton
maintainerQuoted 5 lines · On Fri, 2 May 2008 17:17:27 -0600 Matthew Wilcox <[email protected]> wrote:
Quoted 1 line · Andrew Morton
linux-next is a better tree against which to prepare 2.6.x+1 patches.
Is there a reason to wait for 2.6.x+1? People are having problems today with corrupt hlists.
dunno, really. Merging it would bust git-sched and linux-next, which is of course solveable, but a hassle. If there are bugs in there then linux-next and -mm testing will pick up some of them. Mainline testing would pick up more. Is that delta worth it? dunno, really.
Experience tells us that decent testing of new debug infrastructure (especially that which goes BUG) before it goes mainline is a good idea.
<looks at the patch>
include/linux/list.h +0 -0
include/linux/list.h static inline int hlist_empty(const struct hlist_head *h) · line 720
Quoted 11 lines
index 08cf4f6..a325d9f 100644
include/linux/list.h +4
-0@@ -714,10 +714,17 @@ static inline int hlist_empty(const struct hlist_head *h)return !h->first; } +#ifdef CONFIG_DEBUG_LIST +extern void hlist_check (struct hlist_node *n); +#else +#define hlist_check (n) do { } while (0)
a static inline would be better.
- It matches the other version, which is aesthetically pleasing
- It provides type-checking when CONFIG_DEBUG_LIST=n
PATCH 1/1 Make
In reply to Matthew WilcoxArjan noted that the list_head debugging is BUG'ing when it detects corruption. By causing the box to panic immediately, we're possibly
losing some bug reports. Changing this to a WARN_ON should mean
we at the least start seeing reports collected at kerneloops.org
[ I chose to BUG() when I first added that code, because I was
chasing a bug which caused a lockup anyway, so it made little difference to me. ]
lib/list_debug.c +4 -4
@@ -24,13 +24,13 @@ void __list_add(struct list_head *new,printk (KERN_ERR "list_add corruption. next->prev should be " "prev (%p), but was %p. (next=%p).\n" , prev, next->prev, next); -BUG (); +WARN_ON (1 ); }if (unlikely (prev->next != next)) {printk (KERN_ERR "list_add corruption. prev->next should be " "next (%p), but was %p. (prev=%p).\n" , next, prev->next, prev); -BUG (); +WARN_ON (1 ); } next->prev = new; new->next = next;
@@ -64,12 +64,12 @@ void list_del(struct list_head *entry)if (unlikely (entry->prev->next != entry)) {printk (KERN_ERR "list_del corruption. prev->next should be %p, " "but was %p\n" , entry, entry->prev->next); -BUG (); +WARN_ON (1 ); }if (unlikely (entry->next->prev != entry)) {printk (KERN_ERR "list_del corruption. next->prev should be %p, " "but was %p\n" , entry, entry->next->prev); -BUG (); +WARN_ON (1 ); }__list_del (entry->prev, entry->next); entry->next =LIST_POISON1 ;
Signature · 1 line
http://www.codemonkey.org.uk
Re: [PATCH] Make
lib/list_debug.c void __list_add(struct list_head *new, · line 28
Quoted 6 lines · On Mon, 19 May 2008 23:24:48 -0400 Dave Jones <[email protected]> wrote:
printk(KERN_ERR "
list_addcorruption.next->prev
should be " "prev (%p), but was %p. (next=%p).\n",lib/list_debug.c +1
-1prev, next->prev, next); -BUG (); +WARN_ON (1 ); }
now that -mm has a WARN(condition, printk arguments), could we make this use it? The advantage (apart from smaller C code) is that it puts the printk inside the ---[ cut here ]--- which makes it more likely that reporters (and kerneloops.org) get the actual text....
Re: [PATCH] Make
Quoted 15 lines · Arjan van de Ven
Review
Quoted 6 lines · On Mon, 19 May 2008 23:24:48 -0400 Dave Jones <[email protected]> wrote:
printk(KERN_ERR "
list_addcorruption.next->prev
should be " "prev (%p), but was %p. (next=%p).\n",(quoted diff) +1
-1prev, next->prev, next); - BUG(); + WARN_ON(1); }now that -mm has a
WARN(condition, printk arguments), could we make this use it? The advantage (apart from smaller C code) is that it puts the printk inside the ---[ cut here ]--- which makes it more likely that reporters (and kerneloops.org) get the actual text....
Like this ? (I don't have a -mm handy, so guessed based on mm-commits mail,
patch uncompiled, but should dtrt if I understood your diff correctly)
Dave
lib/list_debug.c +4 -8
@@ -21,16 +21,14 @@ void __list_add(struct list_head *new, struct list_head *next) {if (unlikely (next->prev != prev)) { -printk (KERN_ERR "list_add corruption. next->prev should be " +WARN (1 ,"list_add corruption. next->prev should be " "prev (%p), but was %p. (next=%p).\n" , prev, next->prev, next); -BUG (); }if (unlikely (prev->next != next)) { -printk (KERN_ERR "list_add corruption. prev->next should be " +WARN (1 ,"list_add corruption. prev->next should be " "next (%p), but was %p. (prev=%p).\n" , next, prev->next, prev); -BUG (); } next->prev = new; new->next = next;
@@ -62,14 +60,12 @@ EXPORT_SYMBOL(list_add);void list_del (struct list_head *entry) {if (unlikely (entry->prev->next != entry)) { -printk (KERN_ERR "list_del corruption. prev->next should be %p, " +WARN (1 ,"list_del corruption. prev->next should be %p, " "but was %p\n" , entry, entry->prev->next); -BUG (); }if (unlikely (entry->next->prev != entry)) { -printk (KERN_ERR "list_del corruption. next->prev should be %p, " +WARN (1 ,"list_del corruption. next->prev should be %p, " "but was %p\n" , entry, entry->next->prev); -BUG (); }__list_del (entry->prev, entry->next); entry->next =LIST_POISON1 ;
Signature · 1 line
http://www.codemonkey.org.uk
Re: [PATCH] Make
Quoted 9 lines · Dave Jones
Like this ? (I don't have a -mm handy, so guessed based on mm-commits mail,
patch uncompiled, but should dtrt if I understood your diff correctly)lib/list_debug.c +0
-0
lib/list_debug.c void __list_add(struct list_head *new, · line 27
Quoted 9 lines
lib/list_debug.c +1
-2@@ -21,16 +21,14 @@ void __list_add(struct list_head *new, struct list_head *next) {if (unlikely (next->prev != prev)) { -printk (KERN_ERR "list_add corruption. next->prev should be " +WARN (1 ,"list_add corruption. next->prev should be " "prev (%p), but was %p. (next=%p).\n" , prev, next->prev, next); -BUG (); }
I think Arjan meant like
WARN(next->prev != prev, "list_add corruption. next->prev should be " "prev (%p), but was %p. (next=%p).\n", prev, next->prev, next);
without any "if()" statement at all.
Linus
Re: [PATCH] Make
In reply to Dave JonesQuoted 9 lines · Dave Jones
Quoted 8 lines · Arjan van de Ven
lib/list_debug.c void __list_add(struct list_head *new,
Quoted 5 lines · On Mon, 19 May 2008 23:24:48 -0400 Dave Jones <[email protected]> wrote:
printk(KERN_ERR "
list_addcorruption.next->prev
should be " "prev (%p), but was %p. (next=%p).\n",lib/list_debug.c +1
-1prev, next->prev, next); -BUG (); +WARN_ON (1 );
Why list_head corruptions are special?
Re: [PATCH] Make
Quoted 2 lines · Alexey Dobriyan
Why
list_headcorruptions are special?
It's not that list corruptions are special, but:
- *any* corruption is very interesting, and the earlier we find it the better
- we have a lot of lists in the kernel, so testing pointers is actually likely to find stuff.
- and lists are something we can *test* for corruption
That third one is important. Most random pointers we can't sanely test because they don't have trivial and important patterns.
The second one is relevant too: some of the pointers that we *could* test (struct task has a pointer to the thread struct and back, and we could test that) are just not common enough to be worth testing. And other concepts don't have any nice centralized routines for adding the test.
So no, lists aren't "special" in any inherent way, they are just special in these kinds of "incidentally, a lot of random data structure corruption has traditionally shown up in lists, because there are so many of them".
Linus
Re: [PATCH] Make
In reply to Linus TorvaldsQuoted 26 lines · On Tue, 20 May 2008 07:54:10 -0700 (PDT) Linus Torvalds…
Quoted 17 lines · Dave Jones
Like this ? (I don't have a -mm handy, so guessed based on mm-commits mail, patch uncompiled, but should dtrt if I understood your diff correctly)
lib/list_debug.c +0
-1@@ -21,16 +21,14 @@ void __list_add(struct list_head *new, struct list_head *next) {if (unlikely (next->prev != prev)) { - printk(KERN_ERR "list_add corruption. next->prevshould be "
lib/list_debug.c +1
-0+ WARN(1, "list_add corruption. next->prev should be" "prev (%p), but was %p. (next=%p).\n",
lib/list_debug.c void __list_add(struct list_head *new, · line 27
Quoted 3 lines
lib/list_debug.c +0
-1prev, next->prev, next); -BUG (); }I think Arjan meant like
Quoted 6 lines
WARN(next->prev != prev, "list_add corruption. next->prev should be " "prev (%p), but was %p. (next=%p).\n", prev, next->prev, next);without any "
if()" statement at all.
Dave...what Linus said ;)
would be nice to get WARN() into mainline soon... ;)
Re: [PATCH] Make
In reply to Linus TorvaldsQuoted 3 lines · Linus Torvalds
So no, lists aren't "special" in any inherent way, they are just special in these kinds of "incidentally, a lot of random data structure corruption has traditionally shown up in lists, because there are so many of them".
It's also been _really_ useful for showing up random bit flips in bad hardware.
"hey, if that bit had been a 1, this pointer would have looked valid and we
wouldn't have oopsed" has led to quite a few cases where the reporter then
found a session with memtest86 enlightening.
Dave
Signature · 1 line
http://www.codemonkey.org.uk
Re: [PATCH] Make
In reply to Linus TorvaldsQuoted 8 lines · Linus Torvalds
I think Arjan meant like
WARN(next->prev != prev, "list_add corruption. next->prev should be " "prev (%p), but was %p. (next=%p).\n", prev, next->prev, next);without any "
if()" statement at all.
Nice. I like that.
lib/list_debug.c +14 -22
@@ -20,18 +20,14 @@ void __list_add(struct list_head *new, struct list_head *prev, struct list_head *next) { - if (unlikely(next->prev != prev)) { - printk(KERN_ERR "list_add corruption. next->prev should be " -"prev (%p), but was %p. (next=%p).\n" , - prev, next->prev, next); -BUG (); - } -if (unlikely (prev->next != next)) { -printk (KERN_ERR "list_add corruption. prev->next should be " -"next (%p), but was %p. (prev=%p).\n" , - next, prev->next, prev); -BUG (); - } + WARN(next->prev != prev, +"list_add corruption. next->prev should be " +"prev (%p), but was %p. (next=%p).\n" , + prev, next->prev, next); +WARN (prev->next != next, +"list_add corruption. prev->next should be " +"next (%p), but was %p. (prev=%p).\n" , + next, prev->next, prev); next->prev = new; new->next = next; new->prev = prev;
@@ -61,16 +57,12 @@ EXPORT_SYMBOL(list_add);*/ void list_del (struct list_head *entry) { -if (unlikely (entry->prev->next != entry)) { -printk (KERN_ERR "list_del corruption. prev->next should be %p, " -"but was %p\n" , entry, entry->prev->next); -BUG (); - } -if (unlikely (entry->next->prev != entry)) { -printk (KERN_ERR "list_del corruption. next->prev should be %p, " -"but was %p\n" , entry, entry->next->prev); -BUG (); - } +WARN (entry->prev->next != entry, +"list_del corruption. prev->next should be %p, " +"but was %p\n" , entry, entry->prev->next); +WARN (entry->next->prev != entry, +"list_del corruption. next->prev should be %p, " +"but was %p\n" , entry, entry->next->prev);__list_del (entry->prev, entry->next); entry->next =LIST_POISON1 ; entry->prev =LIST_POISON2 ;
Signature · 1 line
http://www.codemonkey.org.uk
Re: [PATCH] Make (LIST_DEBUG WARN not BUG)
In reply to Dave JonesQuoted 11 lines · Dave Jones
Quoted 3 lines · Linus Torvalds
So no, lists aren't "special" in any inherent way, they are just special in these kinds of "incidentally, a lot of random data structure corruption has traditionally shown up in lists, because there are so many of them".
It's also been _really_ useful for showing up random bit flips in bad hardware.
"hey, if that bit had been a 1, this pointer would have looked valid and we
wouldn't have oopsed" has led to quite a few cases where the reporter then
found a session with memtest86 enlightening.
Affirmative, LIST_DEBUG is very useful.
Let's look again at what patch actually does: writes to corrupted memory will be done even if it's known for sure it's corrupted.
There is
BUG_ON(!list_empty(&father->children));
in forget_original_parent(). Should it be changed to WARN_ON? Why [not]?