Skip to content

runtime monkey patching in Go using plugin / dynamic hotfix / 热更新 / 热修复

Notifications You must be signed in to change notification settings

lsg2020/go-hotfix

Repository files navigation

go-hotfix

  • go-hotfix is a use plugin and debug symbol hotfix function

Only support linux platform(windows does not support plugin,macos delve does not support plugin debugging)

Features

  • Use delve to load the executable and share object debug symbols to find the code address corresponding to the function pathname
  • Patch package uses go plugin for easy compilation
  • Thread safety, using tracer block the process and then apply the patch
  • Runtime repair support: Export Functions/Private Functions/Member Methods
    • github.com/lsg2020/go-hotfix/examples/data.TestAdd
    • github.com/lsg2020/go-hotfix/examples/data.(*DataType).TestHotfix
    • github.com/lsg2020/go-hotfix/examples/data.testPrivateFunc
    • github.com/lsg2020/go-hotfix/examples/data.(*DataType).test

Principle Analysis

  • The foundation of everything
    • monkey
    • go plugin It's good to give us the compilation of the patch package, The types and global variables loaded in plugin are the same as in the main program,Then we compile the main package directly with -buildmode=plugin to compile the patch package
  • monkey Patch function func Patch(target, replacement interface{}) ,You need to pass in the target function and the replacement function as parameters,There are three additional questions
    • jump code If it is longer than the original code, it may result in writing out of bounds
    • Not thread-safe, if there are other threads that happen to execute this code while it is being replaced, there will be a problem
    • How to distinguish between old and new versions of functions, If you call monkey.Patch directly in the plugin code, the target function and the replacement function are both implemented in the patch package and do not affect the functions in the main program at all
  • Question 1:
    • We found that the go program has embedded debug symbolsdwarf ,The debug symbol contains information about the function name and the jump code block of the function code
    • More convenient is the debuggerdelve ,Directly provides parsing of debug symbols ,That would be very convenient direct load check
  • Question 2:
    • We can learn gdb to use ptrace
    • Implement a tool ,Block all threads of the current process and check if the thread execution address is within the code to be replaced, if it is within the range, then execute it out in a single step
    • Reference
  • Question 3:
    • Using delve load the patch package, you can find that the corresponding functions in the debug symbols of the main program and the patch package are of the same name, so you can get the different entry addresses of the old and new versions of the functions
  • New Question: monkey.Patch requires a runtime function object, how to create the corresponding function object based on the entry address?
  • The final implementation is relatively simple Hotfix(path string, names []string, threadSafe bool) (string, error)

Warning

  • Don't use the compile parameter -ldflags="-s -w" it will not load debug information
  • If you can't find the function, you can consider if it is optimized inline, you can use the compile parameter -gcflags=all=-l to turn off inline optimization
  • The types in the main package are two different types in the main program and the patch package, do not hotfix functions that use such types as parameters/return values
  • Types and global variables referenced in patch packages are loaded the first time the object is loaded
  • Do not modify the parameters and return value type of the function
  • You can add new types, but do not modify existing type definitions
  • Compiling plugin packages
    • Prevent plug-ins from failing to load due to version differencesComment code ,You need to make sure that the patch package version you load is the same as the main program.
    • Unreferenced functions will not compile, consider adding an exported function func Hotfix() { main() } to compile
    • It is better to have version changes for different versions of the main package to prevent plugin already loaded

Example

  • Comment code
  • Build tracer go build tools/tracer/tracer.go
  • Build example go build -gcflags=all=-l examples/hello/hello.go
  • Build patch package
    • Modify github.com/lsg2020/go-hotfix/examples/data
    • go build -gcflags=all=-l -buildmode=plugin -ldflags="-X main.HotfixVersion=1" -o hello_v1.so examples/hello/hello.go
    • Modify github.com/lsg2020/go-hotfix/examples/data
    • go build -gcflags=all=-l -buildmode=plugin -ldflags="-X main.HotfixVersion=2" -o hello_v2.so examples/hello/hello.go
  • ./hello
in testPrivateFunc
in func (d *DataType) test()
--------------------------- hello_v1.so
in testPrivateFunc v1
in func (d *DataType) test() v1
--------------------------- hello_v2.so
in testPrivateFunc v2
in func (d *DataType) test() v2

About

runtime monkey patching in Go using plugin / dynamic hotfix / 热更新 / 热修复

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages