探索一次Golang程序unexpected fault fatal error

众所周知,在go协程中操作线程不安全的对象是犯了大忌。但是讲道理,上层的recover应该能hold住大部分的情况,没想到这次居然导致整个程序挂了。虽然代码不是我写的,为了排除其他潜在的隐患,于是我进行了一波探索与模拟。程序退出前,控制台日志输出大致如下:

1
2
3
unexpected fault address 0x
fatal error: fault
[signal SIGSEGV. segmentation violation code= addr= pc=]

对于能挂掉整个程序的协程,怀疑是协程中有协程没recover住panic?检查了波代码,并没有出现这样的情况。看了堆栈的信息是定位到了一行append上,append调用了runtime包下的slice.go中的growslice,growSlice又调用了memmove才导致了如上的错误,于是就把关注点集中到了memmove上。memmove是汇编实现的,为了能调用汇编代码,使用了go:linkname去链接这个汇编函数。

1
2
// go:linkname memmove runtime.memmove
func memmove(to, from unsafe.Pointer, n uintptr)

这样就可以在测试代码中调用memmove了,接下来写一波测试代码模拟并发调用的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func TestMemmove(t *testing.T)  {
    bytes := []byte("test")
    bytes1 := []byte("test")
    defer func() {
        if r := recover(); r != nil {
            fmt.Println(r)
        }
    }()
    wg := sync.WaitGroup{}
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            memmove(unsafe.Pointer(&bytes), unsafe.Pointer(&bytes1), 1024000000)
            wg.Done()
        }()
    }
    wg.Wait()
}

果然不出所料,recover并没有recover住,程序直接fatal error。可见golang runtime在对内存进行一些wild的操作也是无法recover的。由于业务代码较多,就用了上述的方式模拟。
切记在并发操作中一定要关注线程安全问题。

作者

ZhongHuihong

发布于

2021-07-31

更新于

2021-10-02

许可协议