go-agent: github.com/sqreen/go-agent/agent/sqlib/sqhook Index | Files

package sqhook

import "github.com/sqreen/go-agent/agent/sqlib/sqhook"

Package sqhook provides a pure Go implementation of hooks to be inserted into function definitions in order to be able to attach at run time prolog and epilog callbacks getting read/write access to the arguments and returned values of the function call.

A hook needs to be globally created and associated to a function symbol at package initialization time. Callbacks can then be accessed in the function call to pass the call arguments and return values.

On the other side, callbacks can be attached to a hook at run time. Prolog callbacks get read/write access to the arguments of the function call before it gets executed, while epilog callbacks get read/write access to the return values before returning from the call. Therefore, the callbacks' signature need to match the function signature.

Given a function F:

func F(A, B, C) (R, S, T)

The expected prolog signature is:

type prolog = func(*A, *B, *C) (epilog, error)

The expected epilog signature is:

type epilog = func(*R, *S, *T)

Note 1: the prolog callback returns the epilog callback - which can be nil when not required - so that context can be shared using a closure.

Note 2: a prolog for a method should accept the method receiver pointer as first argument wrapped into a `sqhook.MethodReceiver` value.

Example:

// Define the hook globally
var exampleHook *sqhook.Hook

// Initialization needs to be done in the init() function because of some
// Go initialization limitations.
func init() {
	exampleHook = sqhook.New(Example)
}

func Example(arg1 int, arg2 string) (ret1 []byte, ret2 error) {
	// Use the hook first and call its callbacks
	{
		type Epilog = func(*[]byte, *error)
		type Prolog = func(*int, *string) (Epilog, error)
		// Get the prolog callback and call it if it is not nil
		prolog := exampleHook.Prolog()
		if prolog, ok := prolog.(Prolog); ok {
			// Pass pointers to the arguments
			epilog, err := prolog(&w, &r, &headers, &statusCode, &body)
			// If an error is returned, the function execution is aborted.
			// The epilog still needs to be called if set. A deferred call to it
			// does the job.
			if epilog != nil {
				// Pass pointers to the return values
				defer epilog(&ret1, &ret2)
			}
			if err != nil {
				return
			}
		}
	}
	/* .. function code ... */
}

Main requirements:

- Concurrent access and modification of callbacks. - Ability to read/write arguments and return values. - Hook to the prolog and epilog of a function. - Epilog callbacks should be able to recover from a panic. - Callbacks should be reentrant. If any context needs to be shared, it

should be done through the closure.

- Fast call dispatch for callbacks that don't need to be generic, ie.

callbacks that are designed to be attached to specific functions.
Type-assertion instead of `reflect.Call` is therefore used while generic
callbacks that are not tied a specific function will be attached using
`reflect.MakeFunc` in order to match the function signature. The usage
of dynamic calls using `reflect` is indeed much slower and consumes
memory.

Design constraints:

- There are no compilation-time functions or macros that would have allowed

to provide helpers setting up the hooks in the function definitions.

- Access and modification of callbacks need to be atomic. - There are no way to add custom sections to the binary file, which would

have made possible defining the index of hooks at compilation-time (
things that can be easily done with GCC).

Index

Package Files

hook.go

type Error Uses

type Error int

Errors that hooks can return in order to modify the control flow of the function.

const (

    // Abort the execution of the function by returning from it.
    AbortError Error
)

func (Error) Error Uses

func (e Error) Error() string

type Hook Uses

type Hook struct {
    // contains filtered or unexported fields
}

func Find Uses

func Find(symbol string) *Hook

Find returns the hook associated to the given symbol string when it was created using `New()`, nil otherwise.

func New Uses

func New(fn interface{}) *Hook

New returns a hook for function `fn` to be used in the function definition in order to be able to attach callbacks to it. It returns nil if the fn is not a non-nil function or if the symbol name of `fn` cannot be retrieved.

func (*Hook) Attach Uses

func (h *Hook) Attach(prolog PrologCallback) error

Attach atomically attaches a prolog callback to the hook. It is possible to pass a `nil` value to remove the attached callback.

func (*Hook) Prolog Uses

func (h *Hook) Prolog() (prolog PrologCallback)

Callbacks atomically accesses the attached prolog.

func (*Hook) String Uses

func (h *Hook) String() string

type MethodReceiver Uses

type MethodReceiver struct{ Receiver interface{} }

MethodReceiver should be the first argument of the prolog of a method.

type PrologCallback Uses

type PrologCallback interface{}

PrologCallback is an interface type to a prolog function. Given a function F:

func F(A, B, C) (R, S, T)

The expected prolog signature is:

type prolog = func(*A, *B, *C) (epilog, error)

The expected epilog signature is:

type epilog = func(*R, *S, *T)

The returned epilog value can be nil when there is no need for epilog.

Package sqhook imports 6 packages (graph) and is imported by 4 packages. Updated 2019-07-25. Refresh now. Tools for package owners.