go-kiss / monkey

Go语言猴子补丁框架
https://taoshu.in/go/monkey/
MIT License
119 stars 18 forks source link

signal SIGSEGV: segmentation violation #11

Closed limpo1989 closed 1 year ago

limpo1989 commented 1 year ago

使用monkey配合plugin,实现一个简单的热修复功能会发生崩溃,刚开始patching之后一切正常,运行一会以后就会发生错误

另外: agiledragon/gomonkey/#132 存在同样的问题

测试环境:

main.go

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
    "plugin"
    "reflect"
    "sync/atomic"
    "time"

    "github.com/go-kiss/monkey"
    "gocase/gopatch/route"
)

var HotfixVersion = "main"

func IndexHandler() func(http.ResponseWriter, *http.Request) {
    //return route.Index
    var counter int32
    return func(writer http.ResponseWriter, request *http.Request) {
        fmt.Fprintln(writer, "plugin handle:", atomic.AddInt32(&counter, 1))
    }
}

func main() {
    http.HandleFunc("/", route.Index)

    // 热修复 route.Index 逻辑
    {
        p, err := plugin.Open("http_v1.so")
        if nil != err {
            panic(err)
        }

        sym, err := p.Lookup("IndexHandler")
        if nil != err {
            panic(err)
        }

        fn := sym.(func() func(http.ResponseWriter, *http.Request))
        handler := fn()

        fmt.Println("patching...", reflect.ValueOf(route.Index).Pointer(), " => ", reflect.ValueOf(handler).Pointer())

        monkey.Patch(route.Index, handler, monkey.OptGlobal)
    }

    // 等3s后开始启动客户端访问接口
    time.AfterFunc(time.Second*3, func() {

        fmt.Println("start client...")
        for i := 0; i < 3; i++ {
            go startClient()
        }
    })

    // 启动http服务器
    fmt.Println("http server listen...")
    err := http.ListenAndServe(":8080", nil)
    fmt.Println("http server listen failed: ", err)
}

func startClient() {
    for {
        resp, err := http.Get("http://127.0.0.1:8080/")
        if nil != err {
            fmt.Println("http.Get: ", err.Error())
            return
        }
        io.Copy(os.Stdout, resp.Body)
        resp.Body.Close()
    }
}

route.go

var counter int32
func Index(writer http.ResponseWriter, request *http.Request) {
    fmt.Fprintln(writer, "main: hello gopatch/", atomic.AddInt32(&counter, 1))
}

run_http.sh

#!/bin/bash

set -e

echo "build main program..."
go build -gcflags=all=-l -ldflags="-X main.HotfixVersion=main" -o http_main main.go

echo "please modify v1 plugin, press enter key to continue..."
read input

echo "build plugin v1..."
go build -gcflags=all=-l -buildmode=plugin -ldflags="-X main.HotfixVersion=v1" -o http_v1.so main.go

echo "run main program..."
./http_main

输出结果

patching... 8645408  =>  140521878991296
http server listen...
start client...
plugin handle: 1
plugin handle: 3
plugin handle: 2
plugin handle: 4
plugin handle: 6
plugin handle: 7
plugin handle: 5
plugin handle: 8
plugin handle: 9
plugin handle: 11
plugin handle: 10
plugin handle: 12
plugin handle: 13
plugin handle: 14
plugin handle: 15
plugin handle: 16
plugin handle: 17
plugin handle: 18
plugin handle: 19
plugin handle: 20
plugin handle: 22
plugin handle: 21
plugin handle: 23
plugin handle: 24
plugin handle: 25
plugin handle: 26
plugin handle: 27
plugin handle: 28
plugin handle: 30
plugin handle: 29
plugin handle: 31
plugin handle: 32
plugin handle: 33
plugin handle: 34
plugin handle: 35
plugin handle: 36
plugin handle: 37
plugin handle: 38
plugin handle: 41
plugin handle: 39
plugin handle: 40
plugin handle: 12346
plugin handle: 12347
plugin handle: 12348
plugin handle: 12349
plugin handle: 12350
plugin handle: 12351
unexpected fault address 0x931cc0
fatal error: fault
[signal SIGSEGV: segmentation violation code=0x2 addr=0x931cc0 pc=0x931cc0]

goroutine 182 [running]:
runtime.throw({0x8e39fd?, 0xc00022a000?})
        /usr/local/go/src/runtime/panic.go:1047 +0x5d fp=0xc0004f4a70 sp=0xc0004f4a40 pc=0x5906dd
runtime.sigpanic()
        /usr/local/go/src/runtime/signal_unix.go:851 +0x1e5 fp=0xc0004f4aa0 sp=0xc0004f4a70 pc=0x5a7465
net/http.HandlerFunc.ServeHTTP(0x688a65?, {0x934080?, 0xc00058a0e0?}, 0x92ceb8?)
        /usr/local/go/src/net/http/server.go:2123 +0x2f fp=0xc0004f4ac8 sp=0xc0004f4aa0 pc=0x80644f
net/http.(*ServeMux).ServeHTTP(0x0?, {0x934080, 0xc00058a0e0}, 0xc0001d2300)
        /usr/local/go/src/net/http/server.go:2500 +0xc2 fp=0xc0004f4b00 sp=0xc0004f4ac8 pc=0x8079a2
net/http.serverHandler.ServeHTTP({0xc000482a80?}, {0x934080?, 0xc00058a0e0?}, 0xc0001d2300?)
        /usr/local/go/src/net/http/server.go:2936 +0x23c fp=0xc0004f4b98 sp=0xc0004f4b00 pc=0x808fdc
net/http.(*conn).serve(0xc0004847e0, {0x934488, 0xc00011af30})
        /usr/local/go/src/net/http/server.go:1995 +0xad0 fp=0xc0004f4fb8 sp=0xc0004f4b98 pc=0x8055b0
net/http.(*Server).Serve.func3()
        /usr/local/go/src/net/http/server.go:3089 +0x2e fp=0xc0004f4fe0 sp=0xc0004f4fb8 pc=0x80992e
runtime.goexit()
        /usr/local/go/src/runtime/asm_amd64.s:1598 +0x1 fp=0xc0004f4fe8 sp=0xc0004f4fe0 pc=0x5c4861
created by net/http.(*Server).Serve
        /usr/local/go/src/net/http/server.go:3089 +0x45f
...
taoso commented 1 year ago

monkey 本质上是还修改全局函数指针。多协程同时修改会出问题,所以不能用于实现热更新之类的功能。

limpo1989 commented 1 year ago

@taoso 可是我上面的例子是没有 “多协程同时修改” 仅在初始化阶段进行了修改,之后就没有进行修改了

limpo1989 commented 1 year ago

切换为 gohook 后运行正常了,不会崩溃了