Soft Lockup in "__udp4_lib_lookup", Maybe a GCC's bug
Hi guys, I'm using linux-3.2, yes, it's pretty old I know, and I'm going to move on a latest stable version. I hit a soft lockup issue in function `__udp4_lib_lookup`. And it turns out that the soft lockup results from that it got a hlist_nulls_node from a hash slot, but that hlist_nulls_node relates to another hash slot, and the code will spin as the following: ``` begin: result = NULL; badness = -1; sk_nulls_for_each_rcu(sk, node, >head) { score = compute_score(sk, net, saddr, hnum, sport, daddr, dport, dif); if (score > badness) { result = sk; badness = score; } } /* * if the nulls value we got at the end of this lookup is * not the expected one, we must restart lookup. * We probably met an item that was moved to another chain. */ if (get_nulls_value(node) != slot) goto begin; ``` After analyzing the disassembly, I would imagine that maybe it's GCC's bad, it incorrectly reused the register `r8`, so that it won't re-access `hslot->head` when restarting `sk_nulls_for_each_rcu()` The GCC I'm using is 4.5.1, it is also pretty old, yes, I know. And please look at the followings (added some inline comments): Dump of assembler code for function __udp4_lib_lookup: linux-3.2/net/ipv4/udp.c: 451 { 0x8134c98f <+0>: push %rbp 0x8134c990 <+1>: mov%rsp,%rbp 0x8134c993 <+4>: push %r15 0x8134c995 <+6>: push %r14 0x8134c997 <+8>: push %r13 0x8134c999 <+10>:push %r12 0x8134c99b <+12>:push %rbx 0x8134c99c <+13>:sub$0x48,%rsp 0x8134c9a0 <+17>:callq 0x813a2e80 include/linux/swab.h: 51 return ___constant_swab16(val); 0x8134c9a5 <+22>:rol$0x8,%r8w /linux-3.2/net/ipv4/udp.c: 451 { 0x8134c9aa <+27>:mov0x10(%rbp),%r13 include/linux/swab.h: 51 return ___constant_swab16(val); 0x8134c9ae <+31>:mov%r8w,-0x32(%rbp) /linux-3.2/net/ipv4/udp.c: 451 { 0x8134c9b3 <+36>:mov%ecx,%r15d 452 struct sock *sk, *result; 453 struct hlist_nulls_node *node; 454 unsigned short hnum = ntohs(dport); 455 unsigned int hash2, slot2, slot = udp_hashfn(net, hnum, udptable->mask); 0x8134c9b6 <+39>:mov0x10(%r13),%r8d 0x8134c9ba <+43>:movzwl -0x32(%rbp),%r14d include/net/netns/hash.h: 16 return (unsigned)(((unsigned long)net) >> L1_CACHE_SHIFT); 0x8134c9bf <+48>:mov%rdi,%rax /linux-3.2/net/ipv4/udp.c: 451 { 0x8134c9c2 <+51>:mov%rdi,%r12 include/net/netns/hash.h: 16 return (unsigned)(((unsigned long)net) >> L1_CACHE_SHIFT); 0x8134c9c5 <+54>:shr$0x6,%rax /linux-3.2/net/ipv4/udp.c: 451 { 0x8134c9c9 <+58>:mov%esi,-0x38(%rbp) include/linux/udp.h: 52 return (num + net_hash_mix(net)) & mask; 0x8134c9cc <+61>:lea(%r14,%rax,1),%eax /linux-3.2/net/ipv4/udp.c: 451 { 0x8134c9d0 <+65>:mov%r9d,-0x3c(%rbp) 456 struct udp_hslot *hslot2, *hslot = >hash[slot]; 0x8134c9d4 <+69>:and%r8d,%eax 451 { 0x8134c9d7 <+72>:mov%dx,-0x3e(%rbp) 456 struct udp_hslot *hslot2, *hslot = >hash[slot]; 0x8134c9db <+76>:mov%rax,%rbx 0x8134c9de <+79>:mov%rax,-0x48(%rbp) 0x8134c9e2 <+83>:shl$0x5,%rbx 0x8134c9e6 <+87>:add0x0(%r13),%rbx ^~ rbx is hslot 457 int score, badness; 458 459 rcu_read_lock(); 460 if (hslot->count > 10) { 0x8134c9ea <+91>:mov0x8(%rbx),%ecx 0x8134c9ed <+94>:cmp$0xa,%ecx 0x8134c9f0 <+97>:jle0x8134ca9e <__udp4_lib_lookup+271> 461 hash2 = udp4_portaddr_hash(net, daddr, hnum); 0x8134c9f6 <+103>: mov%r14d,%edx 0x8134c9f9 <+106>: mov%ecx,-0x60(%rbp) 0x8134c9fc <+109>: mov%r8d,-0x58(%rbp) 0x8134ca00 <+113>: mov%r15d,%esi 0x8134ca03 <+116>: callq 0x8134a74f 462 slot2 = hash2 & udptable->mask; 0x8134ca08 <+121>: mov-0x58(%rbp),%r8d 464 if (hslot->count < hslot2->count) 0x8134ca0c <+125>: mov-0x60(%rbp),%ecx 462 slot2 = hash2 & udptable->mask; 0x8134ca0f <+128>: and%r8d,%eax 463 hslot2 = >hash2[slot2]; 0x8134ca12 <+131>: mov%eax,%edx 0x8134ca14 <+133>:
Soft Lockup in "__udp4_lib_lookup", Maybe a GCC's bug
Hi guys, I'm using linux-3.2, yes, it's pretty old I know, and I'm going to move on a latest stable version. I hit a soft lockup issue in function `__udp4_lib_lookup`. And it turns out that the soft lockup results from that it got a hlist_nulls_node from a hash slot, but that hlist_nulls_node relates to another hash slot, and the code will spin as the following: ``` begin: result = NULL; badness = -1; sk_nulls_for_each_rcu(sk, node, >head) { score = compute_score(sk, net, saddr, hnum, sport, daddr, dport, dif); if (score > badness) { result = sk; badness = score; } } /* * if the nulls value we got at the end of this lookup is * not the expected one, we must restart lookup. * We probably met an item that was moved to another chain. */ if (get_nulls_value(node) != slot) goto begin; ``` After analyzing the disassembly, I would imagine that maybe it's GCC's bad, it incorrectly reused the register `r8`, so that it won't re-access `hslot->head` when restarting `sk_nulls_for_each_rcu()` The GCC I'm using is 4.5.1, it is also pretty old, yes, I know. And please look at the followings (added some inline comments): Dump of assembler code for function __udp4_lib_lookup: linux-3.2/net/ipv4/udp.c: 451 { 0x8134c98f <+0>: push %rbp 0x8134c990 <+1>: mov%rsp,%rbp 0x8134c993 <+4>: push %r15 0x8134c995 <+6>: push %r14 0x8134c997 <+8>: push %r13 0x8134c999 <+10>:push %r12 0x8134c99b <+12>:push %rbx 0x8134c99c <+13>:sub$0x48,%rsp 0x8134c9a0 <+17>:callq 0x813a2e80 include/linux/swab.h: 51 return ___constant_swab16(val); 0x8134c9a5 <+22>:rol$0x8,%r8w /linux-3.2/net/ipv4/udp.c: 451 { 0x8134c9aa <+27>:mov0x10(%rbp),%r13 include/linux/swab.h: 51 return ___constant_swab16(val); 0x8134c9ae <+31>:mov%r8w,-0x32(%rbp) /linux-3.2/net/ipv4/udp.c: 451 { 0x8134c9b3 <+36>:mov%ecx,%r15d 452 struct sock *sk, *result; 453 struct hlist_nulls_node *node; 454 unsigned short hnum = ntohs(dport); 455 unsigned int hash2, slot2, slot = udp_hashfn(net, hnum, udptable->mask); 0x8134c9b6 <+39>:mov0x10(%r13),%r8d 0x8134c9ba <+43>:movzwl -0x32(%rbp),%r14d include/net/netns/hash.h: 16 return (unsigned)(((unsigned long)net) >> L1_CACHE_SHIFT); 0x8134c9bf <+48>:mov%rdi,%rax /linux-3.2/net/ipv4/udp.c: 451 { 0x8134c9c2 <+51>:mov%rdi,%r12 include/net/netns/hash.h: 16 return (unsigned)(((unsigned long)net) >> L1_CACHE_SHIFT); 0x8134c9c5 <+54>:shr$0x6,%rax /linux-3.2/net/ipv4/udp.c: 451 { 0x8134c9c9 <+58>:mov%esi,-0x38(%rbp) include/linux/udp.h: 52 return (num + net_hash_mix(net)) & mask; 0x8134c9cc <+61>:lea(%r14,%rax,1),%eax /linux-3.2/net/ipv4/udp.c: 451 { 0x8134c9d0 <+65>:mov%r9d,-0x3c(%rbp) 456 struct udp_hslot *hslot2, *hslot = >hash[slot]; 0x8134c9d4 <+69>:and%r8d,%eax 451 { 0x8134c9d7 <+72>:mov%dx,-0x3e(%rbp) 456 struct udp_hslot *hslot2, *hslot = >hash[slot]; 0x8134c9db <+76>:mov%rax,%rbx 0x8134c9de <+79>:mov%rax,-0x48(%rbp) 0x8134c9e2 <+83>:shl$0x5,%rbx 0x8134c9e6 <+87>:add0x0(%r13),%rbx ^~ rbx is hslot 457 int score, badness; 458 459 rcu_read_lock(); 460 if (hslot->count > 10) { 0x8134c9ea <+91>:mov0x8(%rbx),%ecx 0x8134c9ed <+94>:cmp$0xa,%ecx 0x8134c9f0 <+97>:jle0x8134ca9e <__udp4_lib_lookup+271> 461 hash2 = udp4_portaddr_hash(net, daddr, hnum); 0x8134c9f6 <+103>: mov%r14d,%edx 0x8134c9f9 <+106>: mov%ecx,-0x60(%rbp) 0x8134c9fc <+109>: mov%r8d,-0x58(%rbp) 0x8134ca00 <+113>: mov%r15d,%esi 0x8134ca03 <+116>: callq 0x8134a74f 462 slot2 = hash2 & udptable->mask; 0x8134ca08 <+121>: mov-0x58(%rbp),%r8d 464 if (hslot->count < hslot2->count) 0x8134ca0c <+125>: mov-0x60(%rbp),%ecx 462 slot2 = hash2 & udptable->mask; 0x8134ca0f <+128>: and%r8d,%eax 463 hslot2 = >hash2[slot2]; 0x8134ca12 <+131>: mov%eax,%edx 0x8134ca14 <+133>: