hotfix

package module
v0.0.0-...-2b13507 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 7, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

README

go-hotfix

hotfix

Runtime function hot-patching for Go applications


Warning: This project is experimental. Do not use in production without thorough testing.

How It Works

hotfix patches running Go functions at runtime using gomonkey + go plugin:

  1. Build a fixed version of your code as a .so plugin
  2. Load the plugin into the running process
  3. Redirect old function calls to the new implementations via binary patching

Features

  • Patch at package, class, or function granularity
  • Patch exported functions, private functions, and struct methods
  • Thread-safe patching via stop-the-world (STW) mechanism
  • Support for generic functions (Go 1.18+)
  • Linux (amd64, arm64) and macOS (amd64, arm64/Apple Silicon)

Quick Start

# Build your application with inlining disabled
go build -gcflags="all=-l -N" -o myapp .

# Run it
./myapp

# Build a patch plugin from the fixed source
go build -gcflags="all=-l -N" -buildmode=plugin -o patch_v1.so .

# Apply the patch (via API call, HTTP handler, etc.)

Usage

Basic

import "github.com/go-hotfix/hotfix"

// Patch specific functions
result := hotfix.Hotfix("patch_v1.so", hotfix.Func(
    "myapp/service.CalcPrice",
    "myapp/service.(*Order).Total",
))

// Patch all methods of a struct
result := hotfix.Hotfix("patch_v1.so", hotfix.Classes(
    "myapp/service.Order",        // value receiver methods
    "*myapp/service.Order",       // pointer receiver methods
))

// Patch all functions in a package
result := hotfix.Hotfix("patch_v1.so", hotfix.Package("myapp/service"))

// Combine multiple pickers
result := hotfix.Hotfix("patch_v1.so", hotfix.Any(
    hotfix.Func("myapp/service.CalcPrice"),
    hotfix.Package("myapp/util"),
))

Result

result := hotfix.Hotfix("patch.so", picker)
if result.Err != nil {
    log.Fatal(result.Err)
}
fmt.Printf("patched %d functions in %s\n", len(result.Methods), result.Cost)
fmt.Println(result.Message) // debug log

Custom Patcher

// Use the default gomonkey-based patcher
result := hotfix.DoHotfix("patch.so", picker, hotfix.GoMonkey())

// Or provide your own FuncPatcher implementation
result := hotfix.DoHotfix("patch.so", picker, myCustomPatcher)

Example

A complete web application example is in example/webapp, demonstrating:

  • A running HTTP server with a bug in calcDiscount
  • Building a fixed plugin
  • Applying the hotfix at runtime
  • Verifying the fix without restarting the server

Limitations

  • Platforms: Linux and macOS only (due to go plugin constraints)
  • No closure patching: Logic requiring hotfixes must not reside in closures
  • No signature changes: Cannot modify data structures or function signatures — use only for bug fixes
  • Build flags: The target program must be compiled with -gcflags="all=-l -N" (disable inlining and optimizations)
  • Environment consistency: The plugin must be built with the same Go compiler version, build flags, and dependencies as the main program
  • No unloading: Loaded plugins cannot be unloaded; excessive patching may increase memory usage
  • Plugin uniqueness: Each plugin's main package must differ from previously loaded ones. Use -ldflags="-X main.HotfixVersion=v1.0.1" to ensure uniqueness
  • Init execution: The plugin's main package init functions run once on load — avoid duplicate initialization

Testing

go test -gcflags="all=-l" -v ./...

Acknowledgments

Inspired by lsg2020/go-hotfix.

License

Apache License 2.0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type FuncPatcher

type FuncPatcher func(req Request) error

FuncPatcher applies binary patches to redirect function calls. Implementations receive a fully populated Request and must return a non-nil error if patching fails.

func GoMonkey

func GoMonkey() FuncPatcher

GoMonkey returns a FuncPatcher that uses gomonkey to rewrite function entry points. It performs type validation before entering stop-the-world (STW) to ensure thread-safe binary patching. If patching fails partway through, all previously applied patches are rolled back.

type FuncPicker

type FuncPicker func(dwarfAssembly assembly.DwarfAssembly) ([]string, error)

FuncPicker selects which functions should be hot-patched. Given a DwarfAssembly for runtime introspection, it returns the fully qualified names of the target functions.

func Any

func Any(funcPickers ...FuncPicker) FuncPicker

Any combines multiple FuncPickers into one. The selected function names are concatenated in order. If any picker returns an error, the combined picker returns that error immediately.

func Classes

func Classes(classNames ...string) FuncPicker

Classes returns a FuncPicker that selects all methods of the specified struct types. The className must be the fully qualified type name:

example/data.DataType        — value receiver methods
*example/data.DataType       — pointer receiver methods

func Func

func Func(funcNames ...string) FuncPicker

Func returns a FuncPicker that selects specific functions by their fully qualified names. Names must use the Go runtime format:

example/data.TestAdd
example/data.(*DataType).TestHotfix
example/data.testPrivateFunc

func Package

func Package(pkgs ...string) FuncPicker

Package returns a FuncPicker that selects all functions and methods belonging to the specified package(s). The pkg must be the full import path:

example/data

type Request

type Request struct {
	// Logger receives debug log output during the patching process.
	Logger *log.Logger
	// Patch is the file path of the loaded plugin (.so).
	Patch string
	// Methods is the list of fully qualified function names to patch.
	Methods []string
	// Assembly provides DWARF-based access to runtime type and function information.
	Assembly assembly.DwarfAssembly
	// OldFuncEntrys contains the original function entry points from the main binary.
	OldFuncEntrys []*proc.Function
	// OldFunctions holds callable reflect.Values pointing to the original function entry points.
	OldFunctions []reflect.Value
	// NewFunctions holds callable reflect.Values from the loaded plugin.
	NewFunctions []reflect.Value
}

Request contains all the information needed to apply a hot patch.

type Result

type Result struct {
	// Assembly is the DWARF assembly used during the operation.
	Assembly assembly.DwarfAssembly
	// Patch is the resolved file path of the loaded plugin.
	Patch string
	// Methods is the list of function names that were patched.
	Methods []string
	// Cost is the total wall-clock time of the operation.
	Cost time.Duration
	// Err is non-nil if the operation failed.
	Err error
	// Message contains the debug log output from the operation.
	Message string
}

Result contains the outcome of a hotfix operation.

func DoHotfix

func DoHotfix(libPath string, funcPicker FuncPicker, funcPatcher FuncPatcher) (result Result)

DoHotfix applies a hot patch using a custom FuncPatcher implementation.

func Hotfix

func Hotfix(libPath string, funcPicker FuncPicker) Result

Hotfix applies a hot patch using the default gomonkey-based patcher. libPath is the file path of the plugin (.so) built from the fixed source. funcPicker selects which functions to patch.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL