Panic in sync/atomic/asm_386.s

I’m getting a report of a nil pointer dereference panic in one of my applications.

The error is:

/usr/local/go/src/sync/atomic/asm_386.s:118 - runtime error: invalid memory address or nil pointer dereference

Here’s the code up to that line (I’ve replaced // comments with # for highlighting):

TEXT ·AddUint64(SB),NOSPLIT,$0-20
	# no XADDQ so use CMPXCHG8B loop
	MOVL    addr+0(FP), BP
	TESTL   $7, BP
	JZ      2(PC)
	MOVL    0, AX # crash with nil ptr deref

Line 118 is the last MOVL, which is apparently the source of the panic. I’m a little rusty on my assembly. Does this mean the ISA doesn’t support XADDQ? (What is XADDQ anyway? I’m guessing that CMPXCHG8B is a compare-and-swap…)

The system it’s running on:

# uname -a
Linux xxxxxxxxxx 3.16.0-4-686-pae #1 SMP Debian 3.16.7-ckt11-1+deb8u3 (2015-08-04) i686 GNU/Linux

# lscpu
Architecture:          i686
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                1
On-line CPU(s) list:   0
Thread(s) per core:    1
Core(s) per socket:    1
Socket(s):             1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 60
Model name:            Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz
Stepping:              3
CPU MHz:               3600.068
BogoMIPS:              7200.13
Hypervisor vendor:     Microsoft
Virtualization type:   full
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              8192K

Also, I don’t know if it matters, but nowhere in my code do I call atomic.AddUint64. I do use atomic.AddInt64 and atomic.AddInt32 but not Uint64. What would cause the standard library to panic here?

I figured it out while I was drafting the question, but I’ll post it here in case it helps others with the same problem.

At the very bottom of the sync/atomic page, there’s a bug:

☞ On x86-32, the 64-bit functions use instructions unavailable before the Pentium MMX. On non-Linux ARM, the 64-bit functions use instructions unavailable before the ARMv6k core. On both ARM and x86-32, it is the caller’s responsibility to arrange for 64-bit alignment of 64-bit words accessed atomically. The first word in a global variable or in an allocated struct or slice can be relied upon to be 64-bit aligned.

The answer is in the last sentence. I was calling AddInt64 on a int64 located in the second field in a struct:

type UpstreamHost struct {
	Name   string
	Conns  int64
	// ...
}

Since struct fields are allocated sequentially, the string preceding the int64 knocks it out of alignment. Switching places fixed the problem:

type UpstreamHost struct {
	Conns  int64
	Name   string
	// ...
}
4 Likes

Yeah, this is a no good, terrible, nasty corner of the stdlib. Sorry you hit it, glad you found the solution and posted it here.