Soft Lockup in "__udp4_lib_lookup", Maybe a GCC's bug

2017-04-05 Thread Cai, Jason
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

2017-04-05 Thread Cai, Jason
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>: