- 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)
- 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
- The foundation of everything
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?- If you know the function type
reflect.Type
, you can create a function object based onreflect.MakeFunc
, set the code entry address of the function object
- If you know the function type
- The final implementation is relatively simple
Hotfix(path string, names []string, threadSafe bool) (string, error)
- Example:
Hotfix("hello_v1.so", []string{ "github.com/lsg2020/go-hotfix/examples/data.(*DataType).test"}, false)
path
: Patch package load pathnames
: List of pathnames of functions that need to be updatedthreadSafe
: Patch can be replaced bymonkey.Patch
if it is thread-safe, or tracer if it is not
- Basic Process
- Example:
- 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
- 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
- Modify
./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